Merge branch '2014.11-develop' into 2015.11
authorPhilipp Schüle <p.schuele@metaways.de>
Mon, 21 Dec 2015 11:08:21 +0000 (12:08 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Mon, 21 Dec 2015 11:08:21 +0000 (12:08 +0100)
38 files changed:
tests/tine20/Calendar/Controller/EventNotificationsTests.php
tests/tine20/TestCase.php
tests/tine20/Tinebase/ControllerServerTest.php
tests/tine20/Tinebase/User/SqlTest.php
tests/tine20/phpunit_server.xml [deleted file]
tine20/Addressbook/Controller.php
tine20/Admin/Controller/User.php
tine20/Admin/Event/DeleteAccount.php [deleted file]
tine20/Calendar/Backend/Sql.php
tine20/Calendar/Backend/Sql/Attendee.php
tine20/Calendar/Controller.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Controller/EventNotifications.php
tine20/Calendar/Controller/Resource.php
tine20/Calendar/Model/AttenderFilter.php
tine20/Calendar/Model/EventFilter.php
tine20/Calendar/js/AttendeeGridPanel.js
tine20/Calendar/js/DaysView.js
tine20/Calendar/js/Model.js
tine20/Crm/Controller.php
tine20/ExampleApplication/Controller.php
tine20/Filemanager/Controller.php
tine20/Inventory/Controller.php
tine20/Projects/Controller.php
tine20/SimpleFAQ/Controller.php
tine20/Tasks/Controller.php
tine20/Tinebase/AccessLog.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/Config.php
tine20/Tinebase/Container.php
tine20/Tinebase/Controller.php
tine20/Tinebase/Controller/Abstract.php
tine20/Tinebase/Event/User/DeleteAccount.php [new file with mode: 0644]
tine20/Tinebase/User/Interface.php
tine20/Tinebase/User/Sql.php

index bcb26da..fe8a90e 100644 (file)
@@ -1338,7 +1338,7 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
     {
         // create resource with email address of unittest user
         $resource = $this->_getResource();
-        $resource->email = Tinebase_Core::getUser()->accountEmailAddress;
+        $resource->email = $this->_GetPersonasContacts('pwulf')->email;
         $resource->suppress_notification = $suppress_notification;
         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
         
@@ -1354,7 +1354,7 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
         $messages = self::getMessages();
 
         if ($suppress_notification) {
-            $this->assertEquals(1, count($messages), 'one mails should be send to current user (only attender)');
+            $this->assertEquals(2, count($messages), 'two mails should be send to attender (invite) + resource');
         } else {
             $this->assertEquals(2, count($messages), 'two mails should be send to current user (resource + attender)');
         }
@@ -1363,13 +1363,14 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
     /**
      * Enable by a preference which sends mails to every user who got permissions to edit the resource
      */
-    public function testResourceNotificationForGrantedUsers($userIsAttendee = true)
+    public function testResourceNotificationForGrantedUsers($userIsAttendee = true, $suppress_notification = false)
     {
         // Enable feature, disabled by default!
         Calendar_Config::getInstance()->set(Calendar_Config::RESOURCE_MAIL_FOR_EDITORS, true);
 
         $resource = $this->_getResource();
         $resource->email = Tinebase_Core::getUser()->accountEmailAddress;
+        $resource->suppress_notification = $suppress_notification;
         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
 
         $event = $this->_getEvent(/*now = */ true);
@@ -1406,8 +1407,12 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
         $this->assertContains('Resource "' . $persistentResource->name . '" was booked', print_r($messages, true));
         $this->assertContains('Meeting Room (Required, No response)', print_r($messages, true));
 
-        $this->assertEquals(4, count($messages), 'four mails should be send to current user (resource + attender + everybody who is allowed to edit this resource)');
-        $this->assertEquals(count($event->attendee), count($persistentEvent->attendee));
+        if ($suppress_notification) {
+            $this->assertEquals(2, count($messages), 'two mails should be send to current user (resource + attender)');
+        } else {
+            $this->assertEquals(4, count($messages), 'four mails should be send to current user (resource + attender + everybody who is allowed to edit this resource)');
+            $this->assertEquals(count($event->attendee), count($persistentEvent->attendee));
+        }
     }
 
     /**
@@ -1425,7 +1430,8 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
      */
     public function testResourceNotificationMuteForEditors()
     {
-        $this->testResourceNotification(/* $suppress_notification = */ false);
+        $this->testResourceNotification(/* $suppress_notification = */ true);
+        $this->testResourceNotificationForGrantedUsers(/* $userIsAttendee = */ false, /* $suppress_notification = */ true);
     }
     
     /**
index 6128e32..17f34d1 100644 (file)
@@ -486,4 +486,15 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
         
         Tinebase_Container::getInstance()->setGrants($containerId, $grants, TRUE);
     }
+
+    /**
+     * set current user
+     *
+     * @param $user
+     * @throws Tinebase_Exception_InvalidArgument
+     */
+    protected function _setUser($user)
+    {
+        Tinebase_Core::set(Tinebase_Core::USER, $user);
+    }
 }
index f03cd0a..ddb09ac 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Tinebase
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2014-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2014-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
@@ -20,8 +20,6 @@ class Tinebase_ControllerServerTest extends ServerTestCase
      */
     public function testValidLogin()
     {
-        Zend_Session::$_unitTestEnabled = true;
-        
         $request = \Zend\Http\PhpEnvironment\Request::fromString(<<<EOS
 POST /index.php HTTP/1.1\r
 Content-Type: application/json\r
@@ -53,8 +51,6 @@ EOS
      */
     public function testInvalidLogin()
     {
-        Zend_Session::$_unitTestEnabled = true;
-        
         $request = \Zend\Http\PhpEnvironment\Request::fromString(<<<EOS
 POST /index.php HTTP/1.1\r
 Content-Type: application/json\r
@@ -83,11 +79,16 @@ EOS
     
     /**
      * @group ServerTests
+     *
+     * @see 0011440: rework login failure handling
      */
     public function testAccountBlocking()
     {
-        Zend_Session::$_unitTestEnabled = true;
-        
+        // NOTE: end transaction here as NOW() returns the start of the current transaction in pgsql
+        //  and is used in user status statement (think about using statement_timestamp() instead of NOW() with pgsql)
+        Tinebase_TransactionManager::getInstance()->commitTransaction($this->_transactionId);
+        $this->_transactionId = null;
+
         $request = \Zend\Http\PhpEnvironment\Request::fromString(<<<EOS
 POST /index.php HTTP/1.1\r
 Content-Type: application/json\r
@@ -109,17 +110,23 @@ EOS
         
         $credentials = $this->getTestCredentials();
         
-        $maxLoginFailures = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_LOGIN_FAILURES, 5);
-        
-        for ($i=0; $i<=$maxLoginFailures; $i++) {
+        for ($i=0; $i <= 3; $i++) {
             $result = Tinebase_Controller::getInstance()->login($credentials['username'], 'foobar', $request);
-            
             $this->assertFalse($result);
         }
         
-        // account must be blocked now
         $result = Tinebase_Controller::getInstance()->login($credentials['username'], $credentials['password'], $request);
-        
-        $this->assertFalse($result);
+        $this->assertFalse($result, 'account must be blocked now');
+
+        // wait for some time (2^4 = 16 +1 seconds)
+        $timeToWait = 17;
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+            . ' Waiting for ' . $timeToWait . ' seconds...');
+
+        sleep($timeToWait);
+
+        $result = Tinebase_Controller::getInstance()->login($credentials['username'], $credentials['password'], $request);
+        $this->assertTrue($result, 'account should be unblocked now');
     }
 }
index 1672e19..07a312c 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Account
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
  */
 require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
 
-if (!defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Tinebase_User_SqlTest::main');
-}
-
 /**
  * Test class for Tinebase_User
  */
-class Tinebase_User_SqlTest extends PHPUnit_Framework_TestCase
+class Tinebase_User_SqlTest extends TestCase
 {
     /**
      * sql user backend
@@ -36,18 +32,6 @@ class Tinebase_User_SqlTest extends PHPUnit_Framework_TestCase
     protected $objects = array();
 
     /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tinebase_User_SqlTest');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
-
-    /**
      * Sets up the fixture.
      * This method is called before a test is executed.
      *
@@ -61,30 +45,17 @@ class Tinebase_User_SqlTest extends PHPUnit_Framework_TestCase
         
         $this->_backend = Tinebase_User::factory(Tinebase_User::SQL);
 
-        // remove user left over by broken tests
-        try {
-            $user = $this->_backend->getUserByLoginName('tine20phpunituser', 'Tinebase_Model_FullUser');
-            $this->_backend->deleteUser($user);
-        } catch (Tinebase_Exception_NotFound $tenf) {
-            // do nothing 
-        }
-        
-        $this->objects['users'] = array();
+        parent::setUp();
     }
 
-    /**
-     * Tears down the fixture
-     * This method is called after a test is executed.
-     *
-     * @access protected
-     */
     protected function tearDown()
     {
-        foreach ($this->objects['users'] as $user) {
-            $this->_backend->deleteUser($user);
-        }
+        parent::tearDown();
+
+        Tinebase_Config::getInstance()->set(Tinebase_Config::ACCOUNT_DELETION_EVENTCONFIGURATION, new Tinebase_Config_Struct(array(
+        )));
     }
-    
+
     /**
      * try to add an account
      *
@@ -331,7 +302,65 @@ class Tinebase_User_SqlTest extends PHPUnit_Framework_TestCase
         
         $this->_backend->getUserById($testUser, 'Tinebase_Model_FullUser');
     }
-    
+
+    /**
+     * test if deleted users data is removed
+     *
+     * @see TODO add mantis issue
+     *
+     * TODO add test cases for keepOrganizerEvents and $_keepAsContact and $_keepAsContact
+     */
+    public function testDeleteUsersData()
+    {
+        // configure removal of data
+        Tinebase_Config::getInstance()->set(Tinebase_Config::ACCOUNT_DELETION_EVENTCONFIGURATION, new Tinebase_Config_Struct(array(
+            '_deletePersonalContainers' => true,
+
+        )));
+
+        // we need a valid group and a contact for this test
+        $userContact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_given' => 'testuser'
+        )));
+        $testUser = $this->getTestRecord();
+        $testUser->contact_id = $userContact->getId();
+        $this->_backend->addUser($testUser);
+        Tinebase_Group::getInstance()->addGroupMember($testUser->accountPrimaryGroup, $testUser->getId());
+
+        $this->_setUser($testUser);
+
+        // add a contact and an event to personal folders
+        $event = Calendar_Controller_Event::getInstance()->create(new Calendar_Model_Event(array(
+            'summary' => 'testevent',
+            'dtstart' => '2015-12-24 12:00:00',
+            'dtend' => '2015-12-24 13:00:00',
+//            'organizer' => $testUser->conta
+        ), true));
+        $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_given' => 'testcontact'
+        )));
+
+        $this->_setUser($this->_originalTestUser);
+
+        $this->_backend->deleteUser($testUser);
+
+        // check if contact and event are removed
+        $adbBackend = new Addressbook_Backend_Sql();
+        try {
+            $adbBackend->get($contact->getId());
+            $this->fail('contact be deleted');
+        } catch (Exception $e) {
+            $this->assertTrue($e instanceof Tinebase_Exception_NotFound);
+        }
+        $calBackend = new Calendar_Backend_Sql();
+        try {
+            $calBackend->get($event->getId());
+            $this->fail('event should be deleted: ' . print_r($event->toArray(), true));
+        } catch (Exception $e) {
+            $this->assertTrue($e instanceof Tinebase_Exception_NotFound);
+        }
+    }
+
     public function testSanitizeAccountPrimaryGroupId()
     {
         $account = Tinebase_Core::get('currentAccount');
diff --git a/tests/tine20/phpunit_server.xml b/tests/tine20/phpunit_server.xml
deleted file mode 100644 (file)
index 4cf667f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<phpunit backupGlobals="false"
-         backupStaticAttributes="false"
-         convertErrorsToExceptions="true"
-         convertNoticesToExceptions="true"
-         convertWarningsToExceptions="true"
-         processIsolation="false"
-         stopOnFailure="false"
-         syntaxCheck="false"
-         bootstrap="ServerTestHelper.php">
-    <listeners>
-        <listener class="LogListener" file="LogListener.php" />
-    </listeners>
-</phpunit>
\ No newline at end of file
index c375a92..bfbb4c3 100644 (file)
@@ -79,9 +79,24 @@ class Addressbook_Controller extends Tinebase_Controller_Event implements Tineba
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
-            case 'Admin_Event_DeleteAccount':
-                foreach ($_eventObject->accountIds as $accountId) {
-                    $this->deletePersonalFolder($accountId);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
+
+                //make to be deleted accounts (user) contact a normal contact
+                if ($_eventObject->keepAsContact()) {
+                    $contact = Addressbook_Controller_Contact::getInstance()->get($_eventObject->account->contact_id);
+                    $contact->type = Addressbook_Model_Contact::CONTACTTYPE_CONTACT;
+                    Addressbook_Controller_Contact::getInstance()->update($contact);
+
+                } else {
+                    //or just delete it
+                    $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
+                    $contactsBackend->delete($_eventObject->account->contact_id);
                 }
                 break;
         }
@@ -115,16 +130,6 @@ class Addressbook_Controller extends Tinebase_Controller_Event implements Tineba
         
         return $container;
     }
-    
-    /**
-     * delete all personal user folders and the contacts associated with these folders
-     *
-     * @param Tinebase_Model_User $_account the accountd object
-     * @todo implement and write test
-     */
-    public function deletePersonalFolder($_account)
-    {
-    }
 
     /**
      * returns contact image
index 2a6cb75..1351139 100644 (file)
@@ -386,10 +386,6 @@ class Admin_Controller_User extends Tinebase_Controller_Abstract
                 $groupsController->removeGroupMember($groupId, $accountId);
             }
             
-            if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true && !empty($oldUser->contact_id)) {
-                $this->_deleteContact($oldUser->contact_id);
-            }
-            
             $this->_userBackend->deleteUser($accountId);
         }
     }
@@ -481,16 +477,4 @@ class Admin_Controller_User extends Tinebase_Controller_Abstract
         
         return $contact;
     }
-    
-    /**
-     * delete contact associated with user
-     * 
-     * @param string  $_contactId
-     */
-    protected function _deleteContact($_contactId)
-    {
-        $contactsBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
-        
-        $contactsBackend->delete($_contactId);
-    }
 }
diff --git a/tine20/Admin/Event/DeleteAccount.php b/tine20/Admin/Event/DeleteAccount.php
deleted file mode 100644 (file)
index 49659fa..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-/**
- * Tine 2.0
- *
- * @package     Admin
- * @license     
- * @copyright   
- * @author      
- */
-
-/**
- * event class for deleted accounts
- *
- *    MOD: added
- *
- * @package     Admin
- */
-class Admin_Event_DeleteAccount extends Tinebase_Event_Abstract
-{
-    /**
-     * array of account ids
-     *
-     * @var array
-     */
-    public $accountIds;
-
-}
index a9d95dd..1acb89b 100644 (file)
@@ -912,4 +912,30 @@ class Calendar_Backend_Sql extends Tinebase_Backend_Sql_Abstract
             }
         }
     }
+
+    /**
+     * @param Tinebase_Model_Container $sourceContainer
+     * @param Tinebase_Model_Container $destinationContainer
+     */
+    public function moveEventsToContainer(Tinebase_Model_Container $sourceContainer, Tinebase_Model_Container $destinationContainer)
+    {
+        $this->_db->update($this->_tablePrefix . $this->_tableName, array('container_id' => $destinationContainer->getId()),
+            $this->_db->quoteInto($this->_db->quoteIdentifier('container_id') . ' = ?', $sourceContainer->getId()));
+
+        $attendeeBackend = new Calendar_Backend_Sql_Attendee();
+        $attendeeBackend->moveEventsToContainer($sourceContainer, $destinationContainer);
+    }
+
+    /**
+     * @param string $oldContactId
+     * @param string $newContactId
+     */
+    public function replaceContactId($oldContactId, $newContactId)
+    {
+        $this->_db->update($this->_tablePrefix . $this->_tableName, array('organizer' => $newContactId),
+            $this->_db->quoteInto($this->_db->quoteIdentifier('organizer') . ' = ?', $oldContactId));
+
+        $attendeeBackend = new Calendar_Backend_Sql_Attendee();
+        $attendeeBackend->replaceContactId($oldContactId, $newContactId);
+    }
 }
index 60273c8..a10ba4d 100644 (file)
@@ -40,4 +40,24 @@ class Calendar_Backend_Sql_Attendee extends Tinebase_Backend_Sql_Abstract
      * @var boolean
      */
     protected $_modlogActive = TRUE;
+
+    /**
+     * @param Tinebase_Model_Container $sourceContainer
+     * @param Tinebase_Model_Container $destinationContainer
+     */
+    public function moveEventsToContainer(Tinebase_Model_Container $sourceContainer, Tinebase_Model_Container $destinationContainer)
+    {
+        $this->_db->update($this->_tablePrefix . $this->_tableName, array('displaycontainer_id' => $destinationContainer->getId()),
+            $this->_db->quoteInto($this->_db->quoteIdentifier('displaycontainer_id') . ' = ?', $sourceContainer->getId()));
+    }
+
+    /**
+     * @param string $oldContactId
+     * @param string $newContactId
+     */
+    public function replaceContactId($oldContactId, $newContactId)
+    {
+        $this->_db->update($this->_tablePrefix . $this->_tableName, array('user_id' => $newContactId),
+            $this->_db->quoteInto($this->_db->quoteIdentifier('user_id') . ' = ?', $oldContactId));
+    }
 }
index 0370711..c51605b 100644 (file)
@@ -61,16 +61,20 @@ class Calendar_Controller extends Tinebase_Controller_Event implements Tinebase_
     {
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' (' . __LINE__ . ') handle event of type ' . get_class($_eventObject));
         
-        switch(get_class($_eventObject)) {
+        switch (get_class($_eventObject)) {
             case 'Admin_Event_AddAccount':
                 //$this->createPersonalFolder($_eventObject->account);
                 Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::DEFAULTCALENDAR, $_eventObject->account->getId());
                 break;
                 
-            case 'Admin_Event_DeleteAccount':
-                // not a good idea, as it could be the originaters container for invitations
-                // we need to move all contained events first
-                //$this->deletePersonalFolder($_eventObject->account);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                $this->_handleDeleteUserEvent($_eventObject);
+
+                // let event bubble
+                parent::_handleEvent($_eventObject);
                 break;
                 
             case 'Admin_Event_UpdateGroup':
@@ -92,16 +96,146 @@ class Calendar_Controller extends Tinebase_Controller_Event implements Tinebase_
                 break;
         }
     }
-    
+
+    protected function _handleDeleteUserEvent($_eventObject)
+    {
+        if ($_eventObject->keepOrganizerEvents()) {
+            $this->_keepOrganizerEvents($_eventObject);
+        }
+
+        if ($_eventObject->deletePersonalContainers()) {
+            $this->deletePersonalFolder($_eventObject->account);
+        }
+    }
+
+    protected function _keepOrganizerEvents($_eventObject)
+    {
+        $accountId = $_eventObject->account->getId();
+        $contactId = $_eventObject->account->contact_id;
+
+        $contact = null;
+        $newContact = null;
+        $contactEmail = null;
+        if ($_eventObject->keepAsContact()) {
+            try {
+                $contact = Addressbook_Controller_Contact::getInstance()->get($contactId);
+                $contactEmail = $contact->getPreferedEmailAddress();
+            } catch (Tinebase_Exception_NotFound $tenf) {
+                // ignore
+                $contactEmail = $_eventObject->account->accountEmailAddress;
+            }
+        } else {
+            $contactEmail = $_eventObject->account->accountEmailAddress;
+        }
+
+        if (null === $contact) {
+            $newContact = Calendar_Model_Attender::resolveEmailToContact(array(
+                'email' => $contactEmail,
+            ));
+        }
+
+        $eventController = Calendar_Controller_Event::getInstance();
+        $oldState = $eventController->doContainerACLChecks(false);
+        // delete all events where our deletee is organizer and that are private (no matter if they have attendees or not)
+        /*$filter = new Calendar_Model_EventFilter(array(
+            array('field' => 'class', 'operator' => 'equals', 'value' => Calendar_Model_Event::CLASS_PRIVATE),
+            array('field' => 'organizer', 'operator' => 'equals', 'value' => $contactId),
+        ));
+        $eventController->deleteByFilter($filter);*/
+
+        // delete all events where our deletee is organizer and that dont have any additional attenders except the organizer / deletee himself
+        $filter = new Calendar_Model_EventFilter(array(
+            array('field' => 'organizer', 'operator' => 'equals', 'value' => $contactId),
+            array('field' => 'attender', 'operator' => 'notHasSomeExcept', 'value' => array(
+                'user_type' => Calendar_Model_Attender::USERTYPE_USER,
+                'user_id' => $contactId,
+            )),
+        ));
+        $eventController->deleteByFilter($filter);
+
+        $eventController->doContainerACLChecks($oldState);
+
+        // get all personal containers
+        $containers = Tinebase_Container::getInstance()->getPersonalContainer($accountId, $this->getDefaultModel(), $accountId, '*', true);
+        if ($containers->count() > 0) {
+            // take the first one and make it an invitation container
+            $container = $containers->getByIndex(0);
+            $this->convertToInvitationContainer($container, $contactEmail);
+
+            // if there are more than 1 container, move contents to invitation container, then delete them
+            $i = 1;
+            while ($containers->count() > 1) {
+                $moveContainer = $containers->getByIndex($i);
+                $containers->offsetUnset($i++);
+
+                //move $moveContainer content to $container
+                $eventController->getBackend()->moveEventsToContainer($moveContainer, $container);
+                //delete $moveContainer
+                Tinebase_Container::getInstance()->deleteContainer($moveContainer, true);
+            }
+        }
+
+        // replace old contactId with newContact->getId()
+        if (null !== $newContact) {
+            $eventController->getBackend()->replaceContactId($contactId, $newContact->getId());
+        }
+    }
+
+    /**
+     * Converts the calendar to be a calendar for external organizer
+     *
+     * @param Tinebase_Model_Container $container
+     */
+    public function convertToInvitationContainer(Tinebase_Model_Container $container, $emailAddress)
+    {
+        if ($container->model !== 'Calendar_Model_Event') {
+            Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' container provided needs to have the model Calendar_Model_Event instead of ' . $container->model);
+            throw Tinebase_Exception_UnexpectedValue('container provided needs to have the model Calendar_Model_Event instead of ' . $container->model);
+        }
+
+        $tbc = Tinebase_Container::getInstance();
+        try {
+            $oldContainer = $tbc->getContainerByName('Calendar', $emailAddress, Tinebase_Model_Container::TYPE_SHARED);
+
+            // TODO fix me!
+            // bad, we should move the events from $oldContainer to $container
+
+            $tbc->deleteContainer($oldContainer, true);
+        } catch (Tinebase_Exception_NotFound $tenf) {
+            //good, ignore
+        }
+
+        $container->name = $emailAddress;
+        $container->color = '#333399';
+        $container->type = Tinebase_Model_Container::TYPE_SHARED;
+        $tbc->update($container);
+
+        $grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(
+            array(
+                'account_id'      => '0',
+                'account_type'    => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
+                Tinebase_Model_Grants::GRANT_ADD         => true,
+                Tinebase_Model_Grants::GRANT_EDIT        => true,
+                Tinebase_Model_Grants::GRANT_DELETE      => true,
+            )
+        ));
+        $tbc->setGrants($container->getId(), $grants, true, false);
+    }
+
     /**
      * Get/Create Calendar for external organizer
      * 
      * @param  Addressbook_Model_Contact $organizer organizer id
+     * @param  string $emailAddress
      * @return Tinebase_Model_Container  container id
      */
-    public function getInvitationContainer($organizer)
+    public function getInvitationContainer($organizer, $emailAddress = null)
     {
-        $containerName = $organizer->getPreferedEmailAddress();
+        if (null!==$organizer) {
+            $containerName = $organizer->getPreferedEmailAddress();
+        } else {
+            $containerName = $emailAddress;
+        }
         
         try {
             $container = Tinebase_Container::getInstance()->getContainerByName('Calendar', $containerName, Tinebase_Model_Container::TYPE_SHARED);
@@ -164,17 +298,6 @@ class Calendar_Controller extends Tinebase_Controller_Event implements Tinebase_
     }
     
     /**
-     * delete all personal user folders and the contacts associated with these folders
-     *
-     * @param Tinebase_Model_User $_account the accountd object
-     * @todo implement and write test
-     */
-    public function deletePersonalFolder($_account)
-    {
-        
-    }
-    
-    /**
      * handler for Tinebase_Event_Container_BeforeCreate
      * - give owner of personal container all grants
      * - give freebusy grants to anyone for personal container
index 1d370d4..a186636 100644 (file)
@@ -2015,7 +2015,12 @@ class Calendar_Controller_Event extends Tinebase_Controller_Record_Abstract impl
         if ($attender->displaycontainer_id) {
             // check if user is allowed to set status
             if (! $preserveStatus && ! Tinebase_Core::getUser()->hasGrant($attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_EDIT)) {
-                $attender->status = Calendar_Model_Attender::STATUS_NEEDSACTION;
+                if ($attender->user_type === Calendar_Model_Attender::USERTYPE_RESOURCE) {
+                    //If resource has an default status use this
+                    $attender->status = isset($resource->status) ? $resource->status : Calendar_Model_Attender::STATUS_NEEDSACTION;
+                } else {
+                    $attender->status = Calendar_Model_Attender::STATUS_NEEDSACTION;
+                }
             }
         }
 
@@ -2123,7 +2128,8 @@ class Calendar_Controller_Event extends Tinebase_Controller_Record_Abstract impl
 
         // reset all status but calUser on reschedule except resources (Resources might have a configured default value)
         if ($isRescheduled && !$attender->isSame($this->getCalendarUser())) {
-            if ($attender->user_type == Calendar_Model_Attender::USERTYPE_RESOURCE) {
+            if ($attender->user_type === Calendar_Model_Attender::USERTYPE_RESOURCE) {
+                //If resource has a default status reset to this
                 $resource = Calendar_Controller_Resource::getInstance()->get($attender->user_id);
                 $attender->status = isset($resource->status) ? $resource->status : Calendar_Model_Attender::STATUS_NEEDSACTION;
             } else {
index 9333540..6f3a1ae 100644 (file)
 
             $recipients = array($attendee);
 
-            $this->_handleResourceEditors($_attender, $_notificationLevel, $recipients, $_action, $sendLevel);
+            $this->_handleResourceEditors($_attender, $_notificationLevel, $recipients, $_action, $sendLevel, $_updates);
 
             // check if user wants this notification NOTE: organizer gets mails unless she set notificationlevel to NONE
             // NOTE prefUser is organizer for external notifications
       * @param $sendLevel
       * @return bool
       */
-     protected function _handleResourceEditors($attender, $_notificationLevel, &$recipients, &$action, &$sendLevel)
+     protected function _handleResourceEditors($attender, $_notificationLevel, &$recipients, &$action, &$sendLevel, $_updates)
      {
          // Add additional recipients for resources
          if ($attender->user_type !== Calendar_Model_Attender::USERTYPE_RESOURCE
              return true;
          }
          
+         // Set custom startus booked
+         if ($action == 'created') {
+             $action = 'booked';
+         }
+         
          $resource = Calendar_Controller_Resource::getInstance()->get($attender->user_id);
          if ($resource->suppress_notification) {
              if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                      . " Do not send Notifications for this resource: ". $resource->name);
+             // $recipients will still contain the resource itself
              return true;
          }
-
-         if ($action == 'created') {
-             $action = 'booked';
+         
+         // The resource has no account there for the organizer preference (sendLevel) is used. We don't want that
+         $sendLevel = self::NOTIFICATION_LEVEL_EVENT_RESCHEDULE;
+         //handle attendee status change
+         if(! empty($_updates['attendee']) && ! empty($_updates['attendee']['toUpdate'])) {
+             foreach ($_updates['attendee']['toUpdate'] as $updatedAttendee) {
+                 if ($updatedAttendee->user_type == Calendar_Model_Attender::USERTYPE_RESOURCE && $resource->getId() == $updatedAttendee->user_id) {
+                     $sendLevel = self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE;
+                 }
+             }
          }
-
-         // Consider all notification level (Resource Users have a send level of 30)
-         $sendLevel = self::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE;
+         
+         /*
+         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                 . " Attender: ". $attender);
+         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                 . " Action: ". $action);
+         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                 . " Notification Level: ". $_notificationLevel);
+         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                 . " Send Level: ". $sendLevel);
+         */
+         
          $recipients = array_merge($recipients,
              Calendar_Controller_Resource::getInstance()->getNotificationRecipients(
-                 Calendar_Controller_Resource::getInstance()->get($attender->user_id),
-                 $_notificationLevel
+                 Calendar_Controller_Resource::getInstance()->get($attender->user_id)
              )
          );
      }
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " unknown action '$_action'");
                 break;
         }
-        
+        if ($attender->user_type === Calendar_Model_Attender::USERTYPE_RESOURCE) {
+            $messageSubject = '[' . $translate->_('Resource Management') . '] ' . $messageSubject;
+        }
         return $messageSubject;
     }
     
index 2b317fc..10722e5 100644 (file)
@@ -174,7 +174,7 @@ class Calendar_Controller_Resource extends Tinebase_Controller_Record_Abstract
      * @param  Calendar_Model_Resource $_lead
      * @return array          array of int|Addressbook_Model_Contact
      */
-    public function getNotificationRecipients(Calendar_Model_Resource $resource, $_notificationLevel)
+    public function getNotificationRecipients(Calendar_Model_Resource $resource)
     {
         $recipients = array();
 
@@ -196,11 +196,7 @@ class Calendar_Controller_Resource extends Tinebase_Controller_Record_Abstract
                 if ($grant['account_type'] == Tinebase_Acl_Rights::ACCOUNT_TYPE_USER && $grant[Tinebase_Model_Grants::GRANT_EDIT] == 1) {
                     try {
                         $recipient = Addressbook_Controller_Contact::getInstance()->getContactByUserId($grant['account_id'], TRUE);
-                        $sendLevel = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::NOTIFICATION_LEVEL, $grant['account_id']);
-                        // only add recipients that want that kind of notification (consider the send Level)
-                        if ($sendLevel >= $_notificationLevel) {
                             $recipients[] = $recipient;
-                        }
                     } catch (Addressbook_Exception_NotFound $aenf) {
                         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__CLASS__ . '::' . __METHOD__ . '::' . __LINE__
                             . ' Do not send notification to non-existant user: ' . $aenf->getMessage());
index 030ef9e..ac6e988 100644 (file)
@@ -28,7 +28,11 @@ class Calendar_Model_AttenderFilter extends Tinebase_Model_Filter_Abstract
         1 => 'not',
         2 => 'in',
         3 => 'notin',
-        4 => 'specialNode' // one of {allResources}
+        4 => 'specialNode', // one of {allResources}
+        5 => 'hasSomeExcept',
+        6 => 'notHasSomeExcept',
+        7 => 'hasSomeExceptIn',
+        8 => 'notHasSomeExceptIn',
     );
 
     /**
@@ -41,10 +45,14 @@ class Calendar_Model_AttenderFilter extends Tinebase_Model_Filter_Abstract
         switch ($this->_operator) {
             case 'equals':
             case 'not':
+            case 'hasSomeExcept':
+            case 'notHasSomeExcept':
                 $this->_value = array($_value);
                 break;
             case 'in':
             case 'notin':
+            case 'hasSomeExceptIn':
+            case 'notHasSomeExceptIn':
                 $this->_value = $_value;
                 break;
             case 'specialNode' :
@@ -88,6 +96,8 @@ class Calendar_Model_AttenderFilter extends Tinebase_Model_Filter_Abstract
         
         $gs = new Tinebase_Backend_Sql_Filter_GroupSelect($_select);
         $adapter = $_backend->getAdapter();
+        $isExcept = strpos($this->_operator, 'Except') !== false;
+        $sign = $isExcept ? '<>' : '=';
         
         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' (' . __LINE__ . ') value: ' . print_r($this->_value, true));
         foreach ($this->_value as $attenderValue) {
@@ -104,12 +114,14 @@ class Calendar_Model_AttenderFilter extends Tinebase_Model_Filter_Abstract
                     array(
                         'user_type' => Calendar_Model_Attender::USERTYPE_USER,
                         'user_id'   => $attenderValue['user_id']
-                    ),
-                    array(
-                        'user_type' => Calendar_Model_Attender::USERTYPE_GROUPMEMBER,
-                        'user_id'   => $attenderValue['user_id']
                     )
                 );
+                if (!$isExcept) {
+                    $attendee[] = array(
+                        'user_type' => Calendar_Model_Attender::USERTYPE_GROUPMEMBER,
+                        'user_id' => $attenderValue['user_id']
+                    );
+                }
             } else if ($attenderValue['user_type'] == self::USERTYPE_MEMBEROF) {
                 // resolve group members
                 $group = Tinebase_Group::getInstance()->getGroupById($attenderValue['user_id']);
@@ -125,10 +137,12 @@ class Calendar_Model_AttenderFilter extends Tinebase_Model_Filter_Abstract
                             'user_type' => Calendar_Model_Attender::USERTYPE_USER,
                             'user_id'   => $member
                         );
-                        $attendee[] = array(
-                            'user_type' => Calendar_Model_Attender::USERTYPE_GROUPMEMBER,
-                            'user_id'   => $member
-                        );
+                        if (!$isExcept) {
+                            $attendee[] = array(
+                                'user_type' => Calendar_Model_Attender::USERTYPE_GROUPMEMBER,
+                                'user_id' => $member
+                            );
+                        }
                     }
                 }
             } else {
@@ -137,23 +151,39 @@ class Calendar_Model_AttenderFilter extends Tinebase_Model_Filter_Abstract
             
             foreach ($attendee as $attender) {
                 $gs->orWhere(
-                    $adapter->quoteInto($adapter->quoteIdentifier('attendee.user_type') . ' = ?', $attender['user_type']) . ' AND ' .
-                    $adapter->quoteInto($adapter->quoteIdentifier('attendee.user_id') .   ' = ?', $attender['user_id'])
+                    ($isExcept ? '' : $adapter->quoteInto($adapter->quoteIdentifier('attendee.user_type') . ' = ?', $attender['user_type']) . ' AND ') .
+                    $adapter->quoteInto($adapter->quoteIdentifier('attendee.user_id') .   ' ' . $sign . ' ?', $attender['user_id'])
                 );
             }
         }
-        
+
         if (substr($this->_operator, 0, 3) === 'not') {
             // join attendee to be excluded as a new column. records having this column NULL don't have the attendee
             $dname = 'attendee-not-' . Tinebase_Record_Abstract::generateUID(5);
             $_select->joinLeft(
-                    /* table  */ array($dname => $_backend->getTablePrefix() . 'cal_attendee'),
-                    /* on     */ $adapter->quoteIdentifier($dname . '.cal_event_id') . ' = ' . $adapter->quoteIdentifier($_backend->getTableName() . '.id') .
-                                 ' AND ' .  $gs->getSQL(),
-                    /* select */ array($dname => $_backend->getDbCommand()->getAggregate($dname . '.id')));
+            /* table  */
+                array($dname => $_backend->getTablePrefix() . 'cal_attendee'),
+                /* on     */
+                $adapter->quoteIdentifier($dname . '.cal_event_id') . ' = ' . $adapter->quoteIdentifier($_backend->getTableName() . '.id') .
+                ' AND ' . $gs->getSQL(),
+                /* select */
+                array($dname => $_backend->getDbCommand()->getAggregate($dname . '.id')));
             $_select->having($_backend->getDbCommand()->getAggregate($dname . '.id') . ' IS NULL');
         } else {
-            $gs->appendWhere(Zend_Db_Select::SQL_OR);
+            if ($isExcept) {
+                $dname = 'attendee-hasSome-' . Tinebase_Record_Abstract::generateUID(5);
+                $_select->joinLeft(
+                /* table  */
+                    array($dname => $_backend->getTablePrefix() . 'cal_attendee'),
+                    /* on     */
+                    $adapter->quoteIdentifier($dname . '.cal_event_id') . ' = ' . $adapter->quoteIdentifier($_backend->getTableName() . '.id') .
+                    ' AND ' . $gs->getSQL(),
+                    /* select */
+                    array($dname => $_backend->getDbCommand()->getAggregate($dname . '.id')));
+                $_select->having($_backend->getDbCommand()->getAggregate($dname . '.id') . ' IS NOT NULL');
+            } else {
+                $gs->appendWhere(Zend_Db_Select::SQL_OR);
+            }
         }
     }
     
index 1d09349..a4a3f45 100644 (file)
@@ -47,7 +47,7 @@ class Calendar_Model_EventFilter extends Tinebase_Model_Filter_FilterGroup
         'attender_status'       => array('filter' => 'Calendar_Model_AttenderStatusFilter'),
         'attender_role'         => array('filter' => 'Calendar_Model_AttenderRoleFilter'),
         'organizer'             => array('filter' => 'Addressbook_Model_ContactIdFilter', 'options' => array('modelName' => 'Addressbook_Model_Contact')),
-        //'class'               => array('filter' => 'Tinebase_Model_Filter_Text'),
+        'class'                 => array('filter' => 'Tinebase_Model_Filter_Text'),
         //'status'              => array('filter' => 'Tinebase_Model_Filter_Text'),
         'tag'                   => array('filter' => 'Tinebase_Model_Filter_Tag', 'options' => array(
             'idProperty' => 'cal_events.id',
index f147bdc..1ae6ca0 100644 (file)
@@ -514,20 +514,8 @@ Tine.Calendar.AttendeeGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
      */
     onRecordUpdate: function(record) {
         this.stopEditing(false);
-        
-        var attendee = [];
-        this.store.each(function(attender) {
-            var user_id = attender.get('user_id');
-            if (user_id/* && user_id.id*/) {
-                if (typeof user_id.get == 'function') {
-                    attender.data.user_id = user_id.data;
-                }
-                
-               attendee.push(attender.data);
-            }
-        }, this);
-        
-        record.set('attendee', attendee);
+
+        Tine.Calendar.Model.Attender.getAttendeeStore.getData(this.store, record);
     },
     
     onKeyDown: function(e) {
index 6fddce8..800bc68 100644 (file)
@@ -312,10 +312,12 @@ Ext.extend(Tine.Calendar.DaysView, Ext.Container, {
         this.dd = new Ext.dd.DropZone(this.mainWrap.dom, {
             ddGroup: 'cal-event',
 
-            me: this,
+            view: this,
 
             notifyOver : function(dd, e, data) {
-                var sourceEl = Ext.fly(data.sourceEl);
+                var sourceEl = Ext.fly(data.sourceEl),
+                    sourceView = data.scope;
+
                 sourceEl.setStyle({'border-style': 'dashed'});
                 sourceEl.setOpacity(0.5);
                 
@@ -330,7 +332,7 @@ Ext.extend(Tine.Calendar.DaysView, Ext.Container, {
                         }
 
                         if (event.get('editGrant')) {
-                            return Math.abs(targetDateTime.getTime() - event.get('dtstart').getTime()) < Date.msMINUTE ? 'cal-daysviewpanel-event-drop-nodrop' : 'cal-daysviewpanel-event-drop-ok';
+                            return this.view == sourceView && Math.abs(targetDateTime.getTime() - event.get('dtstart').getTime()) < Date.msMINUTE ? 'cal-daysviewpanel-event-drop-nodrop' : 'cal-daysviewpanel-event-drop-ok';
                         }
                     }
                 }
@@ -364,7 +366,7 @@ Ext.extend(Tine.Calendar.DaysView, Ext.Container, {
                     }
 
                     // deny drop for missing edit grant or no time change
-                    if (! event.get('editGrant') || Math.abs(targetDate.getTime() - event.get('dtstart').getTime()) < Date.msMINUTE
+                    if (! event.get('editGrant') || (v == this.view && Math.abs(targetDate.getTime() - event.get('dtstart').getTime()) < Date.msMINUTE)
                             || outOfCropBounds) {
                         return false;
                     }
@@ -383,13 +385,21 @@ Ext.extend(Tine.Calendar.DaysView, Ext.Container, {
                     }
 
                     event.set('is_all_day_event', targetDate.is_all_day_event);
-                    event.endEdit();
 
-                    if (this.me.ownerCt.attendee) {
-                        var attendee = new Tine.Calendar.Model.Attender(this.me.ownerCt.attendee);
-                        event.data.attendee[0].user_id = attendee.data.data.user_id;
+
+                    // change attendee in split view
+                    if (this.view.ownerCt.attendee) {
+                        var attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(event.get('attendee')),
+                            sourceAttendee = Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord(attendeeStore, event.view.ownerCt.attendee),
+                            destinationAttendee = new Tine.Calendar.Model.Attender(this.view.ownerCt.attendee.data);
+
+                        attendeeStore.remove(sourceAttendee);
+                        attendeeStore.add(destinationAttendee);
+
+                        Tine.Calendar.Model.Attender.getAttendeeStore.getData(attendeeStore, event);
                     }
 
+                    event.endEdit();
                     v.fireEvent('updateEvent', event);
                 }
                 
index 1af0105..d8f3274 100644 (file)
@@ -639,19 +639,19 @@ Tine.Calendar.Model.Attender.getAttendeeStore = function(attendeeData) {
  * @static
  */
 Tine.Calendar.Model.Attender.getAttendeeStore.getMyAttenderRecord = function(attendeeStore) {
-        var currentAccountId = Tine.Tinebase.registry.get('currentAccount').accountId;
-        var myRecord = false;
-        
-        attendeeStore.each(function(attender) {
-            var userAccountId = attender.getUserAccountId();
-            if (userAccountId == currentAccountId) {
-                myRecord = attender;
-                return false;
-            }
-        }, this);
-        
-        return myRecord;
-    }
+    var currentAccountId = Tine.Tinebase.registry.get('currentAccount').accountId;
+    var myRecord = false;
+
+    attendeeStore.each(function(attender) {
+        var userAccountId = attender.getUserAccountId();
+        if (userAccountId == currentAccountId) {
+            myRecord = attender;
+            return false;
+        }
+    }, this);
+
+    return myRecord;
+};
     
 /**
  * returns attendee record of given attendee if exists, else false
@@ -668,13 +668,40 @@ Tine.Calendar.Model.Attender.getAttendeeStore.getAttenderRecord = function(atten
             attendeeType.push('groupmember');
         }
 
-        if (attendeeType.indexOf(r.get('user_type') >= 0) && r.getUserId() == attendee.getUserId()) {
+        if (attendeeType.indexOf(r.get('user_type')) >= 0 && r.getUserId() == attendee.getUserId()) {
             attendeeRecord = r;
             return false;
         }
     }, this);
     
     return attendeeRecord;
+};
+
+/**
+ * returns attendee data
+ * optinally fills into event record
+ */
+Tine.Calendar.Model.Attender.getAttendeeStore.getData = function(attendeeStore, event) {
+    var attendeeData = [];
+
+    Tine.Tinebase.common.assertComparable(attendeeData);
+
+    attendeeStore.each(function (attender) {
+        var user_id = attender.get('user_id');
+        if (user_id/* && user_id.id*/) {
+            if (typeof user_id.get == 'function') {
+                attender.data.user_id = user_id.data;
+            }
+
+            attendeeData.push(attender.data);
+        }
+    }, this);
+
+    if (event) {
+        event.set('attendee', attendeeData);
+    }
+
+    return attendeeData;
 }
 
 /**
index bd57466..b848b7a 100644 (file)
@@ -93,8 +93,13 @@ class Crm_Controller extends Tinebase_Controller_Event implements Tinebase_Conta
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
-            case 'Admin_Event_DeleteAccount':
-                $this->deletePersonalFolder($_eventObject->account);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
                 break;
         }
     }
index 814e906..64312f3 100644 (file)
@@ -98,6 +98,14 @@ class ExampleApplication_Controller extends Tinebase_Controller_Event implements
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
+                break;
         }
     }
 }
index 7b185f2..4a1ab81 100644 (file)
@@ -78,9 +78,12 @@ class Filemanager_Controller extends Tinebase_Controller_Event implements Tineba
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
-            case 'Admin_Event_DeleteAccount':
-                foreach ($_eventObject->accountIds as $accountId) {
-                    $this->deletePersonalFolder($accountId);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
                 }
                 break;
         }
@@ -114,14 +117,4 @@ class Filemanager_Controller extends Tinebase_Controller_Event implements Tineba
         
         return $container;
     }
-    
-    /**
-     * delete all personal user folders and the contacts associated with these folders
-     *
-     * @param Tinebase_Model_User $_account the accountd object
-     * @todo implement and write test
-     */
-    public function deletePersonalFolder($_account)
-    {
-    }
 }
index ff855a9..9e2e9ba 100644 (file)
@@ -98,6 +98,14 @@ class Inventory_Controller extends Tinebase_Controller_Event implements Tinebase
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
+                break;
         }
     }
 }
index bad355d..50e16c4 100644 (file)
@@ -105,8 +105,13 @@ class Projects_Controller extends Tinebase_Controller_Event implements Tinebase_
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
-            case 'Admin_Event_DeleteAccount':
-                #$this->deletePersonalFolder($_eventObject->account);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
                 break;
         }
     }
index b45d9e6..5ff4163 100644 (file)
@@ -33,7 +33,7 @@ Class SimpleFAQ_Controller extends Tinebase_Controller_Event implements Tinebase
      * holds the default Model of this application
      * @var string
      */
-    protected static $_defaultModel = 'SimpleFAQ_Model_FAQ';
+    protected static $_defaultModel = 'SimpleFAQ_Model_Faq';
     
     /**
      * the constructor
@@ -91,8 +91,13 @@ Class SimpleFAQ_Controller extends Tinebase_Controller_Event implements Tinebase
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
-            case 'Admin_Event_DeleteAccount':
-                $this->deletePersonalFolder($_eventObject->account);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
                 break;
         }
     }
@@ -126,16 +131,6 @@ Class SimpleFAQ_Controller extends Tinebase_Controller_Event implements Tinebase
 
         return $container;
     }
-
-    /**
-     * delete all personal user folders and the contacts associated with these folders
-     *
-     * @param Tinebase_Model_User $_account the accountd object
-     * @todo implement and write test
-     */
-    public function deletePersonalFolder($_account)
-    {
-    }
     
     /**
      * Returns settings for SimpleFAQ app
index 66403b6..cc07b28 100644 (file)
@@ -128,8 +128,13 @@ class Tasks_Controller extends Tinebase_Controller_Event implements Tinebase_Con
             case 'Admin_Event_AddAccount':
                 $this->createPersonalFolder($_eventObject->account);
                 break;
-            case 'Admin_Event_DeleteAccount':
-                #$this->deletePersonalFolder($_eventObject->account);
+            case 'Tinebase_Event_User_DeleteAccount':
+                /**
+                 * @var Tinebase_Event_User_DeleteAccount $_eventObject
+                 */
+                if ($_eventObject->deletePersonalContainers()) {
+                    $this->deletePersonalFolder($_eventObject->account);
+                }
                 break;
         }
     }
index a2476a3..3a1dce4 100644 (file)
@@ -56,7 +56,76 @@ class Tinebase_AccessLog extends Tinebase_Controller_Record_Abstract
         
         return self::$_instance;
     }
-    
+
+    /**
+     * returns false if not blocked and number of failed logins if
+     *
+     * @param Tinebase_Model_FullUser  $_user
+     * @param Tinebase_Model_AccessLog $_accessLog
+     * @return bool|integer
+     */
+    public function isUserAgentBlocked(Tinebase_Model_FullUser $_user, Tinebase_Model_AccessLog $_accessLog)
+    {
+        if ($this->_tooManyUserAgents($_user)) {
+            return true;
+        }
+
+        $db = $this->_backend->getAdapter();
+        $dbCommand = Tinebase_Backend_Sql_Command::factory($db);
+        $select = $db->select()
+            ->from($this->_backend->getTablePrefix() . $this->_backend->getTableName(), new Zend_Db_Expr('count(*)'))
+            ->where( $db->quoteIdentifier('account_id') . ' = ?', $_user->getId() )
+            ->where( $db->quoteIdentifier('li') . ' > NOW() - ' . $dbCommand->getInterval('MINUTE', '1'))
+            ->where( $db->quoteIdentifier('result') . ' <> ?', Tinebase_Auth::SUCCESS, Zend_Db::PARAM_INT)
+            ->where( $db->quoteIdentifier('user_agent') . ' = ?', $_accessLog->user_agent);
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
+
+        $stmt = $db->query($select);
+        $count = $stmt->fetchColumn();
+        $stmt->closeCursor();
+
+        if ($count > 0) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
+                . ' UserAgent blocked. Login failures: ' . $count);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * check if user connected with too many user agent during the last hour
+     *
+     * @param Tinebase_Model_FullUser  $_user
+     * @param int $numberOfAllowedUserAgents
+     * @return bool
+     */
+    protected function _tooManyUserAgents($_user, $numberOfAllowedUserAgents = 3)
+    {
+        $result = false;
+        $db = $this->_backend->getAdapter();
+        $dbCommand = Tinebase_Backend_Sql_Command::factory($db);
+        $select = $db->select()
+            ->distinct(true)
+            ->from($this->_backend->getTablePrefix() . $this->_backend->getTableName(), 'user_agent')
+            ->where( $db->quoteIdentifier('account_id') . ' = ?', $_user->getId() )
+            ->where( $db->quoteIdentifier('li') . ' > NOW() - ' . $dbCommand->getInterval('HOUR', '1'))
+            ->where( $db->quoteIdentifier('result') . ' <> ?', Tinebase_Auth::SUCCESS, Zend_Db::PARAM_INT)
+            ->limit(10);
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
+
+        $stmt = $db->query($select);
+
+        if ($stmt->columnCount() > $numberOfAllowedUserAgents) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                . ' More than ' . $numberOfAllowedUserAgents . ' different UserAgents? we don\'t trust you!');
+            $result = true;
+        }
+        $stmt->closeCursor();
+        return $result;
+    }
+
     /**
      * get previous access log entry
      * 
@@ -118,7 +187,7 @@ class Tinebase_AccessLog extends Tinebase_Controller_Record_Abstract
      *
      * @param string $_sessionId the session id
      * @param string $_ipAddress the ip address the user connects from
-     * @return void|Tinebase_Model_AccessLog
+     * @return null|Tinebase_Model_AccessLog
      */
     public function setLogout()
     {
@@ -128,7 +197,7 @@ class Tinebase_AccessLog extends Tinebase_Controller_Record_Abstract
             $loginRecord = $this->_backend->getByProperty($sessionId, 'sessionid');
         } catch (Tinebase_Exception_NotFound $tenf) {
             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not find access log login record for session id ' . $_sessionId);
-            return;
+            return null;
         }
         
         $loginRecord->lo = Tinebase_DateTime::now();
index f4401a6..e932fe9 100755 (executable)
@@ -111,4 +111,23 @@ interface Tinebase_Backend_Sql_Command_Interface
      * @param Setup_Backend_Interface $backend
      */
     public function initProcedures(Setup_Backend_Interface $backend);
+
+    /**
+     * returns something similar to "interval $staticPart * $dynamicPart $timeUnit"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @param string $dynamicPart
+     * @return string
+     */
+    public function getDynamicInterval($timeUnit, $staticPart, $dynamicPart);
+
+    /**
+     * returns something similar to "interval $staticPart $timeUnit"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @return string
+     */
+    public function getInterval($timeUnit, $staticPart);
 }
index 218af68..1882192 100644 (file)
@@ -199,4 +199,29 @@ class Tinebase_Backend_Sql_Command_Mysql implements Tinebase_Backend_Sql_Command
      {
 
      }
+
+    /**
+     * returns something similar to "interval $staticPart * $dynamicPart $timeUnit"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @param string $dynamicPart
+     * @return string
+     */
+    public function getDynamicInterval($timeUnit, $staticPart, $dynamicPart)
+    {
+        return 'INTERVAL ' . $staticPart . ' * ' . $dynamicPart . ' ' . $timeUnit;
+    }
+
+    /**
+     * returns something similar to "interval $staticPart $timeUnit"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @return string
+     */
+    public function getInterval($timeUnit, $staticPart)
+    {
+        return 'INTERVAL ' . $staticPart . ' ' . $timeUnit;
+    }
 }
index 6e888eb..4a1dbb5 100755 (executable)
@@ -250,4 +250,29 @@ class Tinebase_Backend_Sql_Command_Oracle implements Tinebase_Backend_Sql_Comman
             PARALLEL_ENABLE AGGREGATE USING t_string_agg;";
          $backend->execQueryVoid($group_concat);
      }
+
+    /**
+     * returns something similar to "interval $staticPart * $dynamicPart $timeUnit"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @param string $dynamicPart
+     * @return string
+     */
+    public function getDynamicInterval($timeUnit, $staticPart, $dynamicPart)
+    {
+        return 'INTERVAL ' . $staticPart . ' * ' . $dynamicPart . ' ' . $timeUnit;
+    }
+
+    /**
+     * returns something similar to "interval $staticPart $timeUnit"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @return string
+     */
+    public function getInterval($timeUnit, $staticPart)
+    {
+        return 'INTERVAL ' . $staticPart . ' ' . $timeUnit;
+    }
 }
index 0efe13f..e8ce604 100755 (executable)
@@ -235,4 +235,31 @@ class Tinebase_Backend_Sql_Command_Pgsql implements Tinebase_Backend_Sql_Command
      {
 
      }
+
+    /**
+     * returns something similar to "(interval '$staticPart $timeUnit' * $dynamicPart)"
+     *
+     * @see http://www.postgresql.org/docs/9.1/static/functions-datetime.html
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @param string $dynamicPart
+     * @return string
+     */
+    public function getDynamicInterval($timeUnit, $staticPart, $dynamicPart)
+    {
+        return '(' . $this->getInterval($timeUnit, $staticPart) .  ' * ' . $dynamicPart . ')';
+    }
+
+    /**
+     * returns something similar to "interval '$staticPart $timeUnit'"
+     *
+     * @param string $timeUnit
+     * @param string $staticPart
+     * @return string
+     */
+    public function getInterval($timeUnit, $staticPart)
+    {
+        return "INTERVAL '" . $staticPart . ' ' . $timeUnit . "'";
+    }
 }
index c3ce876..6a4a6f8 100644 (file)
@@ -280,11 +280,11 @@ class Tinebase_Config extends Tinebase_Config_Abstract
     const LAST_SESSIONS_CLEANUP_RUN = 'lastSessionsCleanupRun';
     
     /**
-     * MAX_LOGIN_FAILURES
+     * WARN_LOGIN_FAILURES
      *
      * @var string
      */
-    const MAX_LOGIN_FAILURES = 'maxLoginFailures';
+    const WARN_LOGIN_FAILURES = 'warnLoginFailures';
      
     /**
      * ANYONE_ACCOUNT_DISABLED
@@ -306,6 +306,13 @@ class Tinebase_Config extends Tinebase_Config_Abstract
      * @var string
      */
     const ACCOUNT_DEACTIVATION_NOTIFICATION = 'accountDeactivationNotification';
+
+    /**
+     * ACCOUNT_DELETION_EVENTCONFIGURATION
+     *
+     * @var string
+     */
+    const ACCOUNT_DELETION_EVENTCONFIGURATION = 'accountDeletionEventConfiguration';
     
     /**
      * roleChangeAllowed
@@ -340,6 +347,27 @@ class Tinebase_Config extends Tinebase_Config_Abstract
      * @see tine20/Tinebase/Config/Definition::$_properties
      */
     protected static $_properties = array(
+        /**
+         * possible values:
+         *
+         * $_deletePersonalContainers => delete personal containers
+         * $_keepAsContact => keep "account" as contact in the addressbook
+         * $_keepOrganizerEvents => keep accounts organizer events as external events in the calendar
+         * $_keepAsContact => keep accounts calender event attendee as external attendee
+         *
+         * TODO add more options (like move to another container)
+         */
+        self::ACCOUNT_DELETION_EVENTCONFIGURATION => array(
+            //_('Account Deletion Event')
+            'label'                 => 'Account Deletion Event',
+            //_('Configure what should happen to data of deleted users')
+            'description'           => 'Configure what should happen to data of deleted users',
+            'type'                  => 'object',
+            'class'                 => 'Tinebase_Config_Struct',
+            'clientRegistryInclude' => FALSE,
+            'setByAdminModule'      => TRUE,
+            'setBySetupModule'      => TRUE,
+        ),
         self::IMAP => array(
                                    //_('System IMAP')
             'label'                 => 'System IMAP',
@@ -696,15 +724,16 @@ class Tinebase_Config extends Tinebase_Config_Abstract
             'setByAdminModule'      => FALSE,
             'setBySetupModule'      => FALSE,
         ),
-        self::MAX_LOGIN_FAILURES => array(
-        //_('Maximum login failures')
-            'label'                 => 'Maximum login failures',
-        //_('Maximum allowed login failures before blocking account')
-            'description'           => 'Maximum allowed login failures before blocking account',
+        self::WARN_LOGIN_FAILURES => array(
+            //_('Warn after X login failures')
+            'label'                 => 'Warn after X login failures',
+            //_('Maximum allowed login failures before writing warn log messages')
+            'description'           => 'Maximum allowed login failures before writing warn log messages',
             'type'                  => 'int',
             'clientRegistryInclude' => FALSE,
             'setByAdminModule'      => FALSE,
             'setBySetupModule'      => TRUE,
+            'default'               => 4
         ),
         self::ANYONE_ACCOUNT_DISABLED => array(
                                    //_('Disable Anyone Account')
index 5fa1d64..891bdd6 100644 (file)
@@ -1106,6 +1106,7 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
         $tm->commitTransaction($myTransactionId);
         
         return $deletedContainer;
+
         /*
         // move all contained objects to next available personal container and try again to delete container
         $app = Tinebase_Application::getApplicationById($container->application_id);
@@ -1136,7 +1137,6 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
      */
     public function deleteContainerContents($container, $_ignoreAcl = FALSE)
     {
-        // set records belonging to this container to deleted
         $model = $container->model;
 
         if (empty($model)) {
@@ -1145,9 +1145,6 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
             return;
         }
 
-        Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
-            . ' Deleting container contents ...');
-
         $controller = Tinebase_Core::getApplicationInstance($model);
         $filterName = $model . 'Filter';
 
@@ -1155,19 +1152,22 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
             $acl = $controller->doContainerACLChecks(FALSE);
         }
         if ($controller && class_exists($filterName)) {
-            $filter = new $filterName(array(
-                array(
-                    'field'    => 'container_id',
-                    'operator' => 'equals',
-                    'value'    => intval($container->id)
-                ),
-                array(
-                    'field'    => 'is_deleted',
-                    'operator' => 'equals',
-                    'value'    => 0
-                )),
-                'AND');
-            $controller::getInstance()->deleteByFilter($filter);
+            Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+                . ' Delete ' . $model . ' records in container ' . $container->getId());
+
+            $filter = new $filterName(array(array(
+                'field'    => 'container_id',
+                'operator' => 'equals',
+                'value'    => intval($container->id)
+            )), Tinebase_Model_Filter_FilterGroup::CONDITION_AND, array('ignoreAcl' => $_ignoreAcl));
+
+            if ($_ignoreAcl) {
+                $backend = $controller::getInstance()->getBackend();
+                $idsToDelete = $backend->search($filter, null, /* $_onlyIds */ true);
+                $controller::getInstance()->delete($idsToDelete);
+            } else {
+                $controller::getInstance()->deleteByFilter($filter);
+            }
         }
 
         if ($_ignoreAcl === TRUE && method_exists($controller, 'doContainerACLChecks')) {
index 1372ba1..860c6be 100644 (file)
@@ -182,7 +182,13 @@ class Tinebase_Controller extends Tinebase_Controller_Event
             
             // too many login failures?
             else if ($_user->accountStatus == Tinebase_User::STATUS_BLOCKED) {
-                if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::'
+
+                // first check if the current user agent should be blocked
+                if (! Tinebase_AccessLog::getInstance()->isUserAgentBlocked($_user, $_accessLog)) {
+                    return;
+                }
+
+                if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::'
                     . __LINE__ . ' Account: '. $_user->accountLoginName . ' is blocked');
                 $_accessLog->result = Tinebase_Auth::FAILURE_BLOCKED;
             }
@@ -272,19 +278,33 @@ class Tinebase_Controller extends Tinebase_Controller_Event
      */
     protected function _loginFailed($authResult, Tinebase_Model_AccessLog $accessLog)
     {
-        if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(
-            __METHOD__ . '::' . __LINE__ . " Login with username {$accessLog->login_name} from {$accessLog->ip} failed ({$accessLog->result})!");
-        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
-            __METHOD__ . '::' . __LINE__ . ' Failure messages: ' . print_r($authResult->getMessages(), TRUE));
-        
         // @todo update sql schema to allow empty sessionid column
         $accessLog->sessionid = Tinebase_Record_Abstract::generateUID();
         $accessLog->lo = $accessLog->li;
-        
-        Tinebase_User::getInstance()->setLastLoginFailure($accessLog->login_name);
+        $user = null;
+
+        if (Tinebase_Auth::FAILURE_CREDENTIAL_INVALID == $accessLog->result) {
+            $user = Tinebase_User::getInstance()->setLastLoginFailure($accessLog->login_name);
+        }
+
+        $loglevel = Zend_Log::INFO;
+        if (null !== $user) {
+            $accessLog->account_id = $user->getId();
+            $warnLoginFailures = Tinebase_Config::getInstance()->get(Tinebase_Config::WARN_LOGIN_FAILURES, 4);
+            if ($user->loginFailures >= $warnLoginFailures) {
+                $loglevel = Zend_Log::WARN;
+            }
+        }
+
+        if (Tinebase_Core::isLogLevel($loglevel)) Tinebase_Core::getLogger()->log(
+            __METHOD__ . '::' . __LINE__
+                . " Login with username {$accessLog->login_name} from {$accessLog->ip} failed ({$accessLog->result})!"
+                . ($user ? ' Auth failure count: ' . $user->loginFailures : ''),
+            $loglevel);
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
+            __METHOD__ . '::' . __LINE__ . ' Auth result messages: ' . print_r($authResult->getMessages(), TRUE));
+
         Tinebase_AccessLog::getInstance()->create($accessLog);
-        
-        sleep(mt_rand(2,5));
     }
     
      /**
@@ -645,6 +665,10 @@ class Tinebase_Controller extends Tinebase_Controller_Event
         $user = $this->_getLoginUser($authResult->getIdentity(), $accessLog);
         
         if ($accessLog->result !== Tinebase_Auth::SUCCESS || !$user) {
+
+            if ($user) {
+                $accessLog->account_id = $user->getId();
+            }
             $this->_loginFailed($authResult, $accessLog);
             
             return false;
index 895d75c..c2235a1 100755 (executable)
@@ -192,4 +192,28 @@ abstract class Tinebase_Controller_Abstract extends Tinebase_Pluggable_Abstract
         
         return call_user_func(array($_controllerName, 'getInstance'));
     }
+
+    /**
+     * delete all personal user folders and the content associated with these folders
+     *
+     * @param Tinebase_Model_User|string $_accountId the account object
+     */
+    public function deletePersonalFolder($_accountId, $model = '')
+    {
+        if ($_accountId instanceof Tinebase_Record_Abstract) {
+            $_accountId = $_accountId->getId();
+        }
+
+        if ('' === $model) {
+            $model = static::$_defaultModel;
+        }
+        // attention, currently everybody who has admin rights on a personal container is the owner of it
+        // even if multiple users have admin rights on that personal container! (=> multiple owners)
+        $containers = Tinebase_Container::getInstance()->getPersonalContainer($_accountId, $model, $_accountId, '*', true);
+
+        foreach ($containers as $container) {
+            //Tinebase_Container::getInstance()->deleteContainerContents($container, true);
+            Tinebase_Container::getInstance()->deleteContainer($container, true);
+        }
+    }
 }
diff --git a/tine20/Tinebase/Event/User/DeleteAccount.php b/tine20/Tinebase/Event/User/DeleteAccount.php
new file mode 100644 (file)
index 0000000..c7b6571
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Event
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ *
+ */
+
+/**
+ * event class for deleted accounts
+ *
+ * @package     Tinebase
+ */
+class Tinebase_Event_User_DeleteAccount extends Tinebase_Event_Abstract
+{
+    /**
+     * the account to be deleted
+     *
+     * @var Tinebase_Model_FullUser
+     */
+    public $account;
+
+    /**
+     * delete personal containers
+     *
+     * @var boolean
+     */
+    protected $_deletePersonalContainers = false;
+
+    /**
+     * keep "account" as contact in the addressbook (which addressbook?)
+     *
+     * @var boolean
+     */
+    protected $_keepAsContact = false;
+
+    /**
+     * keep accounts organizer events as external events in the calendar
+     *
+     * @var boolean
+     */
+    protected $_keepOrganizerEvents = false;
+
+    /**
+     * keep accounts calender event attendee as external attendee
+     *
+     * @var boolean
+     */
+    protected $_keepAttendeeEvents = false;
+
+    public function deletePersonalContainers()
+    {
+        return $this->_deletePersonalContainers;
+    }
+
+    public function keepAsContact()
+    {
+        return $this->_keepAsContact;
+    }
+
+    public function keepOrganizerEvents()
+    {
+        return $this->_keepOrganizerEvents;
+    }
+
+    public function keepAttendeeEvents()
+    {
+        return $this->_keepAttendeeEvents;
+    }
+}
index 1997cdf..baa8594 100644 (file)
@@ -62,6 +62,7 @@ interface Tinebase_User_Interface
      * increase bad password counter and store last login failure timestamp if user exists
      * 
      * @param string $_loginName
+     * @return  Tinebase_Model_FullUser user
      */
     public function setLastLoginFailure($_loginName);
 
index fadf34d..2792ac4 100644 (file)
@@ -276,7 +276,9 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
         
         $select = $this->_getUserSelectObject()
             ->where($this->_db->quoteInto($this->_db->quoteIdentifier( SQL_TABLE_PREFIX . 'accounts.' . $this->rowNameMapping[$_property]) . ' = ?', $value));
-        
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . $select);
+
         $stmt = $select->query();
 
         $row = $stmt->fetch(Zend_Db::FETCH_ASSOC);
@@ -284,6 +286,8 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
             throw new Tinebase_Exception_NotFound('User with ' . $_property . ' = ' . $value . ' not found.');
         }
 
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($row, true));
+
         try {
             $account = new $_accountClass(NULL, TRUE);
             $account->setFromArray($row);
@@ -332,27 +336,17 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
      */
     protected function _getUserSelectObject()
     {
-        /*
-         * CASE WHEN `status` = 'enabled' THEN (CASE WHEN DATE(NOW()) > `expires_at` THEN 'expired'
-         * WHEN ( `login_failures` > 5 AND DATE(`last_login_failure_at`) + INTERVAL '15' MINUTE > DATE(NOW())) THEN 'blocked'
-         * ELSE 'enabled' END) WHEN `status` = 'expired' THEN 'expired' ELSE 'disabled' END
-         */
-        
-        $maxLoginFailures = Tinebase_Config::getInstance()->get(Tinebase_Config::MAX_LOGIN_FAILURES, 5);
-        if ($maxLoginFailures > 0) {
-            $loginFailuresCondition = 'WHEN ( ' . $this->_db->quoteIdentifier($this->rowNameMapping['loginFailures']) . " > {$maxLoginFailures} AND "
-                . $this->_dbCommand->setDate($this->_db->quoteIdentifier($this->rowNameMapping['lastLoginFailure'])) . " + INTERVAL '{$this->_blockTime}' MINUTE > "
-                . $this->_dbCommand->setDate('NOW()') .") THEN 'blocked'";
-        } else {
-            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
-                . ' User blocking disabled.');
-            $loginFailuresCondition = '';
-        }
+        $interval = $this->_dbCommand->getDynamicInterval(
+            'SECOND',
+            '1',
+            'CASE WHEN ' . $this->_db->quoteIdentifier($this->rowNameMapping['loginFailures'])
+            . ' > 5 THEN 60 ELSE POWER(2, ' . $this->_db->quoteIdentifier($this->rowNameMapping['loginFailures']) . ') END');
         
         $statusSQL = 'CASE WHEN ' . $this->_db->quoteIdentifier($this->rowNameMapping['accountStatus']) . ' = ' . $this->_db->quote('enabled') . ' THEN ('
             . 'CASE WHEN '.$this->_dbCommand->setDate('NOW()') .' > ' . $this->_db->quoteIdentifier($this->rowNameMapping['accountExpires'])
             . ' THEN ' . $this->_db->quote('expired')
-            . ' ' . $loginFailuresCondition
+            . ' WHEN ( ' . $this->_db->quoteIdentifier($this->rowNameMapping['loginFailures']) . ' > 0 AND '
+            . $this->_db->quoteIdentifier($this->rowNameMapping['lastLoginFailure']) . ' + ' . $interval . ' > NOW()) THEN ' . $this->_db->quote('blocked')
             . ' ELSE ' . $this->_db->quote('enabled') . ' END)'
             . ' WHEN ' . $this->_db->quoteIdentifier($this->rowNameMapping['accountStatus']) . ' = ' . $this->_db->quote('expired')
                 . ' THEN ' . $this->_db->quote('expired')
@@ -379,6 +373,7 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
             'contact_id',
             'openid',
             'visibility',
+            'NOW()', // only needed for debugging
         );
 
         // modlog fields have been added later
@@ -646,17 +641,16 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
      * set last login failure in accounts table
      * 
      * @param string $_loginName
+     * @return Tinebase_Model_FullUser|null user if found
      * @see Tinebase/User/Tinebase_User_Interface::setLastLoginFailure()
      */
     public function setLastLoginFailure($_loginName)
     {
-        Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Login of user ' . $_loginName . ' failed.');
-        
         try {
-            $user = $this->getUserByPropertyFromSqlBackend('accountLoginName', $_loginName);
+            $user = $this->getUserByPropertyFromSqlBackend('accountLoginName', $_loginName, 'Tinebase_Model_FullUser');
         } catch (Tinebase_Exception_NotFound $tenf) {
             // nothing todo => is no existing user
-            return;
+            return null;
         }
         
         $values = array(
@@ -669,6 +663,8 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
         );
         
         $this->_db->update(SQL_TABLE_PREFIX . 'accounts', $values, $where);
+
+        return $user;
     }
     
     /**
@@ -983,6 +979,12 @@ class Tinebase_User_Sql extends Tinebase_User_Abstract
 
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
             . ' Deleting user' . $user->accountLoginName);
+
+        $event = new Tinebase_Event_User_DeleteAccount(
+            Tinebase_Config::getInstance()->get(Tinebase_Config::ACCOUNT_DELETION_EVENTCONFIGURATION, new Tinebase_Config_Struct())->toArray()
+        );
+        $event->account = $user;
+        Tinebase_Event::fireEvent($event);
         
         $accountsTable          = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'accounts'));
         $groupMembersTable      = new Tinebase_Db_Table(array('name' => SQL_TABLE_PREFIX . 'group_members'));