diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 065a95140490..731930de2413 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -112,7 +112,8 @@ public function __construct($view, $info, $shareManager = null, Request $request */ public function put($data) { $path = $this->fileView->getAbsolutePath($this->path); - return $this->emittingCall(function () use (&$data) { + $emitPostEvent = false; + return $this->emittingCall(function () use (&$data, &$emitPostEvent) { try { $exists = $this->fileView->file_exists($this->path); if ($this->info && $exists && !$this->info->isUpdateable()) { @@ -251,10 +252,11 @@ public function put($data) { throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage()); } + $emitPostEvent = true; return '"' . $this->info->getEtag() . '"'; }, [ 'before' => ['path' => $path], - 'after' => ['path' => $path]], + 'after' => ['path' => $path, 'processPostEvent' => &$emitPostEvent]], 'file', 'create'); } diff --git a/apps/dav/lib/HookManager.php b/apps/dav/lib/HookManager.php index 4e5fe604c546..88dd1f1ecf42 100644 --- a/apps/dav/lib/HookManager.php +++ b/apps/dav/lib/HookManager.php @@ -29,6 +29,8 @@ use OCP\IUser; use OCP\IUserManager; use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\GenericEvent; class HookManager { @@ -50,6 +52,9 @@ class HookManager { /** @var IL10N */ private $l10n; + /** @var EventDispatcher */ + private $eventDispatcher; + /** @var array */ private $calendarsToDelete; @@ -60,47 +65,37 @@ public function __construct(IUserManager $userManager, SyncService $syncService, CalDavBackend $calDav, CardDavBackend $cardDav, - IL10N $l10n) { + IL10N $l10n, + EventDispatcher $eventDispatcher) { $this->userManager = $userManager; $this->syncService = $syncService; $this->calDav = $calDav; $this->cardDav = $cardDav; $this->l10n = $l10n; + $this->eventDispatcher = $eventDispatcher; } public function setup() { - Util::connectHook('OC_User', - 'post_createUser', - $this, - 'postCreateUser'); - Util::connectHook('OC_User', - 'pre_deleteUser', - $this, - 'preDeleteUser'); - Util::connectHook('OC_User', - 'post_deleteUser', - $this, - 'postDeleteUser'); - Util::connectHook('OC_User', - 'changeUser', - $this, - 'changeUser'); + $this->eventDispatcher->addListener('user.aftercreateuser', [$this, 'postCreateUser']); + $this->eventDispatcher->addListener('user.beforedelete', [$this, 'preDeleteUser']); + $this->eventDispatcher->addListener('user.afterdelete', [$this, 'postDeleteUser']); + $this->eventDispatcher->addListener('user.beforefeaturechange', [$this, 'changeUser']); } - public function postCreateUser($params) { - $user = $this->userManager->get($params['uid']); + public function postCreateUser(GenericEvent $params) { + $user = $this->userManager->get($params->getArgument('uid')); $this->syncService->updateUser($user); } - public function preDeleteUser($params) { - $uid = $params['uid']; + public function preDeleteUser(GenericEvent $params) { + $uid = $params->getArgument('uid'); $this->usersToDelete[$uid] = $this->userManager->get($uid); $this->calendarsToDelete = $this->calDav->getUsersOwnCalendars('principals/users/' . $uid); $this->addressBooksToDelete = $this->cardDav->getUsersOwnAddressBooks('principals/users/' . $uid); } - public function postDeleteUser($params) { - $uid = $params['uid']; + public function postDeleteUser(GenericEvent $params) { + $uid = $params->getArgument('uid'); if (isset($this->usersToDelete[$uid])){ $this->syncService->deleteUser($this->usersToDelete[$uid]); } @@ -115,8 +110,8 @@ public function postDeleteUser($params) { } } - public function changeUser($params) { - $user = $params['user']; + public function changeUser(GenericEvent $params) { + $user = $params->getArgument('user'); $this->syncService->updateUser($user); } diff --git a/apps/dav/tests/unit/DAV/HookManagerTest.php b/apps/dav/tests/unit/DAV/HookManagerTest.php index fa503d3fcbe1..515a1602765e 100644 --- a/apps/dav/tests/unit/DAV/HookManagerTest.php +++ b/apps/dav/tests/unit/DAV/HookManagerTest.php @@ -31,8 +31,15 @@ use OCA\DAV\HookManager; use OCP\IUser; use OCP\IUserManager; +use Symfony\Component\EventDispatcher\GenericEvent; use Test\TestCase; +/** + * Class HookManagerTest + * + * @group DB + * @package OCA\DAV\Tests\unit\DAV + */ class HookManagerTest extends TestCase { /** @var L10N */ @@ -89,7 +96,7 @@ public function test() { 'principals/users/newUser', 'contacts', ['{DAV:}displayname' => $this->l10n->t('Contacts')]); - $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n); + $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n, \OC::$server->getEventDispatcher()); $hm->firstLogin($user); } @@ -127,7 +134,7 @@ public function testWithExisting() { ]); $card->expects($this->never())->method('createAddressBook'); - $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n); + $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n, \OC::$server->getEventDispatcher()); $hm->firstLogin($user); } @@ -171,7 +178,7 @@ public function testWithBirthdayCalendar() { 'principals/users/newUser', 'contacts', ['{DAV:}displayname' => $this->l10n->t('Contacts')]); - $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n); + $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n, \OC::$server->getEventDispatcher()); $hm->firstLogin($user); } @@ -212,9 +219,10 @@ public function testDeleteCalendar() { ]); $card->expects($this->once())->method('deleteAddressBook')->with('personal'); - $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n); - $hm->preDeleteUser(['uid' => 'newUser']); - $hm->postDeleteUser(['uid' => 'newUser']); + $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n, \OC::$server->getEventDispatcher()); + $params = new GenericEvent(null, ['uid' => 'newUser']); + $hm->preDeleteUser($params); + $hm->postDeleteUser($params); } } diff --git a/apps/files_sharing/lib/Helper.php b/apps/files_sharing/lib/Helper.php index ecd5ed17d31c..51ae14faa52c 100644 --- a/apps/files_sharing/lib/Helper.php +++ b/apps/files_sharing/lib/Helper.php @@ -38,10 +38,12 @@ class Helper { public static function registerHooks() { - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', '\OCA\Files_Sharing\Updater', 'renameHook'); - \OCP\Util::connectHook('OC_Filesystem', 'post_delete', '\OCA\Files_Sharing\Hooks', 'unshareChildren'); + $hooks = new Hooks(); + $updater = new Updater(); + \OC::$server->getEventDispatcher()->addListener('file.afterrename', [$updater, 'renameHook']); + \OC::$server->getEventDispatcher()->addListener('file.afterdelete', [$hooks, 'unshareChildren']); - \OCP\Util::connectHook('OC_User', 'post_deleteUser', '\OCA\Files_Sharing\Hooks', 'deleteUser'); + \OC::$server->getEventDispatcher()->addListener('user.afterdelete', [$hooks, 'deleteUser']); } /** diff --git a/apps/files_sharing/lib/Hooks.php b/apps/files_sharing/lib/Hooks.php index 94e96cf19474..da0281877b93 100644 --- a/apps/files_sharing/lib/Hooks.php +++ b/apps/files_sharing/lib/Hooks.php @@ -27,10 +27,11 @@ use OC\Files\Filesystem; use OCA\FederatedFileSharing\DiscoveryManager; +use Symfony\Component\EventDispatcher\GenericEvent; class Hooks { - public static function deleteUser($params) { + public static function deleteUser(GenericEvent $params) { $discoveryManager = new DiscoveryManager( \OC::$server->getMemCacheFactory(), \OC::$server->getHTTPClientService() @@ -41,13 +42,17 @@ public static function deleteUser($params) { \OC\Files\Filesystem::getLoader(), \OC::$server->getNotificationManager(), \OC::$server->getEventDispatcher(), - $params['uid']); + $params->getArgument('uid')); - $manager->removeUserShares($params['uid']); + $manager->removeUserShares($params->getArgument('uid')); } public static function unshareChildren($params) { - $path = Filesystem::getView()->getAbsolutePath($params['path']); + if ($params instanceof GenericEvent) { + $path = $params->getArgument('path'); + } else { + $path = Filesystem::getView()->getAbsolutePath($params['path']); + } $view = new \OC\Files\View('/'); // find share mount points within $path and unmount them diff --git a/apps/files_sharing/lib/Updater.php b/apps/files_sharing/lib/Updater.php index 24d0bbdf1b75..6a5ceb702d54 100644 --- a/apps/files_sharing/lib/Updater.php +++ b/apps/files_sharing/lib/Updater.php @@ -24,14 +24,18 @@ namespace OCA\Files_Sharing; +use Symfony\Component\EventDispatcher\GenericEvent; + class Updater { /** * @param array $params */ - static public function renameHook($params) { - self::renameChildren($params['oldpath'], $params['newpath']); - self::moveShareToShare($params['newpath']); + static public function renameHook(GenericEvent $params) { + if ($params->getArgument('processPostEvent') === true) { + self::renameChildren($params->getArgument('oldpath'), $params->getArgument('newpath')); + self::moveShareToShare($params->getArgument('newpath')); + } } /** @@ -52,7 +56,21 @@ static private function moveShareToShare($path) { return; } - $src = $userFolder->get($path); + /** + * What if the path is not relative to $userFolder So as a precaution + * we could also try to get the src from the RootFolder. + */ + try { + $src = $userFolder->get($path); + } catch (\Exception $e) { + try { + $src = \OC::$server->getRootFolder()->get($path); + } catch (\Exception $e) { + //There is no point in proceeding further just log and return + \OC::$server->getLogger()->logException($e); + return; + } + } $shareManager = \OC::$server->getShareManager(); diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 5f94aef9edb7..4ff7f4f5ba1c 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -987,7 +987,7 @@ public static function registerHooks() { // pre and post-rename, disable trash logic for the copy+unlink case \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook'); \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook'); + \OC::$server->getEventDispatcher()->addListener('file.afterrename', [\OCA\Files_Trashbin\Storage::class, 'postRenameHook']); } /** diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index 5220674c2078..21b095cf80c0 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -752,6 +752,7 @@ public function testSingleStorageDeleteFileLoggedOut() { if (!$this->userView->file_exists('test.txt')) { $this->markTestSkipped('Skipping since the current home storage backend requires the user to logged in'); } else { + Filesystem::init($this->user, '/' . $this->user . '/files/'); $this->userView->unlink('test.txt'); } } diff --git a/apps/files_versions/lib/Hooks.php b/apps/files_versions/lib/Hooks.php index 78838b7d5c06..e43783ea6b6d 100644 --- a/apps/files_versions/lib/Hooks.php +++ b/apps/files_versions/lib/Hooks.php @@ -32,16 +32,20 @@ namespace OCA\Files_Versions; +use OC\Files\Filesystem; +use Symfony\Component\EventDispatcher\GenericEvent; + class Hooks { public static function connectHooks() { + $hooks = new Hooks(); // Listen to write signals \OCP\Util::connectHook('OC_Filesystem', 'write', 'OCA\Files_Versions\Hooks', 'write_hook'); // Listen to delete and rename signals \OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Files_Versions\Hooks', 'remove_hook'); \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Versions\Hooks', 'pre_remove_hook'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Versions\Hooks', 'rename_hook'); - \OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Files_Versions\Hooks', 'copy_hook'); + \OC::$server->getEventDispatcher()->addListener('file.afterrename', [$hooks, 'rename_hook']); + \OC::$server->getEventDispatcher()->addListener('file.aftercopy', [$hooks, 'copy_hook']); \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Versions\Hooks', 'pre_renameOrCopy_hook'); \OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Files_Versions\Hooks', 'pre_renameOrCopy_hook'); @@ -98,11 +102,13 @@ public static function pre_remove_hook($params) { * This function is connected to the rename signal of OC_Filesystem and adjust the name and location * of the stored versions along the actual file */ - public static function rename_hook($params) { + public static function rename_hook(GenericEvent $params) { if (\OCP\App::isEnabled('files_versions')) { - $oldpath = $params['oldpath']; - $newpath = $params['newpath']; + $oldpath = $params->getArgument('oldpath'); + $oldpath = Filesystem::getView()->getRelativePath($oldpath); + $newpath = $params->getArgument('newpath'); + $newpath = Filesystem::getView()->getRelativePath($newpath); if($oldpath<>'' && $newpath<>'') { Storage::renameOrCopy($oldpath, $newpath, 'rename'); } @@ -116,11 +122,13 @@ public static function rename_hook($params) { * This function is connected to the copy signal of OC_Filesystem and copies the * the stored versions to the new location */ - public static function copy_hook($params) { + public static function copy_hook(GenericEvent $params) { if (\OCP\App::isEnabled('files_versions')) { - $oldpath = $params['oldpath']; - $newpath = $params['newpath']; + $oldpath = $params->getArgument('oldpath'); + $oldpath = Filesystem::getView()->getRelativePath($oldpath); + $newpath = $params->getArgument('newpath'); + $newpath = Filesystem::getView()->getRelativePath($newpath); if($oldpath<>'' && $newpath<>'') { Storage::renameOrCopy($oldpath, $newpath, 'copy'); } diff --git a/lib/base.php b/lib/base.php index 6b7588d8c98d..7e270f1d5917 100644 --- a/lib/base.php +++ b/lib/base.php @@ -759,9 +759,11 @@ private static function registerEncryptionWrapper() { private static function registerEncryptionHooks() { $enabled = self::$server->getEncryptionManager()->isEnabled(); if ($enabled) { - \OCP\Util::connectHook('OCP\Share', 'post_shared', 'OC\Encryption\HookManager', 'postShared'); - \OCP\Util::connectHook('OCP\Share', 'post_unshare', 'OC\Encryption\HookManager', 'postUnshared'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OC\Encryption\HookManager', 'postRename'); + $eventDispatcher = OC::$server->getEventDispatcher(); + $hookManager = new \OC\Encryption\HookManager(); + $eventDispatcher->addListener('file.aftercreateshare', [$hookManager, 'postShared']); + $eventDispatcher->addListener('file.afterunshare',[$hookManager, 'postUnshared']); + $eventDispatcher->addListener('file.afterrename', [$hookManager, 'postRename']); \OCP\Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', 'OC\Encryption\HookManager', 'postRestore'); } } diff --git a/lib/private/Encryption/Update.php b/lib/private/Encryption/Update.php index 5036a3536b81..222c61979e3a 100644 --- a/lib/private/Encryption/Update.php +++ b/lib/private/Encryption/Update.php @@ -26,6 +26,7 @@ use OC\Files\Filesystem; use \OC\Files\Mount; use \OC\Files\View; +use Symfony\Component\EventDispatcher\GenericEvent; /** * update encrypted files, e.g. because a file was shared @@ -81,10 +82,10 @@ public function __construct( * * @param array $params */ - public function postShared($params) { + public function postShared(GenericEvent $params) { if ($this->encryptionManager->isEnabled()) { - if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { - $path = Filesystem::getPath($params['fileSource']); + if ($params->getArgument('itemType') === 'file' || $params->getArgument('itemType') === 'folder') { + $path = Filesystem::getPath($params->getArgument('fileSource')); list($owner, $ownerPath) = $this->getOwnerPath($path); $absPath = '/' . $owner . '/files/' . $ownerPath; $this->update($absPath); @@ -97,10 +98,10 @@ public function postShared($params) { * * @param array $params */ - public function postUnshared($params) { + public function postUnshared(GenericEvent $params) { if ($this->encryptionManager->isEnabled()) { - if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { - $path = Filesystem::getPath($params['fileSource']); + if ($params->getArgument('itemType') === 'file' || $params->getArgument('itemType') === 'folder') { + $path = Filesystem::getPath($params->getArgument('fileSource')); list($owner, $ownerPath) = $this->getOwnerPath($path); $absPath = '/' . $owner . '/files/' . $ownerPath; $this->update($absPath); diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php index 67a364e9b0b5..ca5c341877a0 100644 --- a/lib/private/Files/Node/HookConnector.php +++ b/lib/private/Files/Node/HookConnector.php @@ -25,6 +25,8 @@ use OC\Files\Filesystem; use OC\Files\View; use OCP\Util; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\GenericEvent; class HookConnector { /** @@ -37,6 +39,8 @@ class HookConnector { */ private $view; + /** @var EventDispatcher */ + private $eventDispatcher; /** * @var FileInfo[] */ @@ -48,9 +52,10 @@ class HookConnector { * @param Root $root * @param View $view */ - public function __construct(Root $root, View $view) { + public function __construct(Root $root, View $view, EventDispatcher $eventDispatcher) { $this->root = $root; $this->view = $view; + $this->eventDispatcher = $eventDispatcher; } public function viewToNode() { @@ -58,16 +63,16 @@ public function viewToNode() { Util::connectHook('OC_Filesystem', 'post_write', $this, 'postWrite'); Util::connectHook('OC_Filesystem', 'create', $this, 'create'); - Util::connectHook('OC_Filesystem', 'post_create', $this, 'postCreate'); + $this->eventDispatcher->addListener('file.aftercreate', [$this, 'postCreate']); Util::connectHook('OC_Filesystem', 'delete', $this, 'delete'); - Util::connectHook('OC_Filesystem', 'post_delete', $this, 'postDelete'); + $this->eventDispatcher->addListener('file.afterdelete', [$this, 'postDelete']); Util::connectHook('OC_Filesystem', 'rename', $this, 'rename'); - Util::connectHook('OC_Filesystem', 'post_rename', $this, 'postRename'); + $this->eventDispatcher->addListener('file.afterrename', [$this, 'postRename']); Util::connectHook('OC_Filesystem', 'copy', $this, 'copy'); - Util::connectHook('OC_Filesystem', 'post_copy', $this, 'postCopy'); + $this->eventDispatcher->addListener('file.aftercopy', [$this, 'postCopy']); Util::connectHook('OC_Filesystem', 'touch', $this, 'touch'); Util::connectHook('OC_Filesystem', 'post_touch', $this, 'postTouch'); @@ -88,9 +93,11 @@ public function create($arguments) { $this->root->emit('\OC\Files', 'preCreate', [$node]); } - public function postCreate($arguments) { - $node = $this->getNodeForPath($arguments['path']); - $this->root->emit('\OC\Files', 'postCreate', [$node]); + public function postCreate(GenericEvent $arguments) { + if ($arguments->getArgument('processPostEvent') === true) { + $node = $this->getNodeForPath($arguments->getArgument('path')); + $this->root->emit('\OC\Files', 'postCreate', [$node]); + } } public function delete($arguments) { @@ -99,10 +106,12 @@ public function delete($arguments) { $this->root->emit('\OC\Files', 'preDelete', [$node]); } - public function postDelete($arguments) { - $node = $this->getNodeForPath($arguments['path']); - unset($this->deleteMetaCache[$node->getPath()]); - $this->root->emit('\OC\Files', 'postDelete', [$node]); + public function postDelete(GenericEvent $arguments) { + if ($arguments->getArgument('processPostEvent') === true) { + $node = $this->getNodeForPath($arguments->getArgument('path')); + unset($this->deleteMetaCache[$node->getPath()]); + $this->root->emit('\OC\Files', 'postDelete', [$node]); + } } public function touch($arguments) { @@ -121,10 +130,12 @@ public function rename($arguments) { $this->root->emit('\OC\Files', 'preRename', [$source, $target]); } - public function postRename($arguments) { - $source = $this->getNodeForPath($arguments['oldpath']); - $target = $this->getNodeForPath($arguments['newpath']); - $this->root->emit('\OC\Files', 'postRename', [$source, $target]); + public function postRename(GenericEvent $arguments) { + if ($arguments->getArgument('processPostEvent') === true) { + $source = $this->getNodeForPath($arguments->getArgument('oldpath')); + $target = $this->getNodeForPath($arguments->getArgument('newpath')); + $this->root->emit('\OC\Files', 'postRename', [$source, $target]); + } } public function copy($arguments) { @@ -133,10 +144,12 @@ public function copy($arguments) { $this->root->emit('\OC\Files', 'preCopy', [$source, $target]); } - public function postCopy($arguments) { - $source = $this->getNodeForPath($arguments['oldpath']); - $target = $this->getNodeForPath($arguments['newpath']); - $this->root->emit('\OC\Files', 'postCopy', [$source, $target]); + public function postCopy(GenericEvent $arguments) { + if ($arguments->getArgument('processPostEvent') === true) { + $source = $this->getNodeForPath($arguments->getArgument('oldpath')); + $target = $this->getNodeForPath($arguments->getArgument('newpath')); + $this->root->emit('\OC\Files', 'postCopy', [$source, $target]); + } } private function getNodeForPath($path) { diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index e0624428f166..336489529281 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -103,8 +103,10 @@ class View { /** @var \OCP\ILogger */ private $logger; + /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ private $eventDispatcher; + private $emitPostEvent; /** * @param string $root @@ -124,6 +126,7 @@ public function __construct($root = '') { $this->userManager = \OC::$server->getUserManager(); $this->logger = \OC::$server->getLogger(); $this->eventDispatcher = \OC::$server->getEventDispatcher(); + $this->emitPostEvent = false; } public function getAbsolutePath($path = '/') { @@ -273,10 +276,16 @@ public function getLocalFolder($path) { * for \OC\Files\Storage\Storage via basicOperation(). */ public function mkdir($path) { - return $this->emittingCall(function () use (&$path) { + $processPostEvent = false; + return $this->emittingCall(function () use (&$path, &$processPostEvent) { $result = $this->basicOperation('mkdir', $path, ['create', 'write']); + $processPostEvent = $this->emitPostEvent; + $this->emitPostEvent = false; return $result; - }, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'create'); + }, [ + 'before' => ['path' => $this->getAbsolutePath($path)], + 'after' => ['path' => $this->getAbsolutePath($path), 'processPostEvent' => &$processPostEvent] + ], 'file', 'create'); } /** @@ -349,7 +358,8 @@ protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, * @return bool|mixed */ public function rmdir($path) { - return $this->emittingCall(function () use (&$path) { + $emitPostEvent = false; + return $this->emittingCall(function () use (&$path, &$emitPostEvent) { $absolutePath = $this->getAbsolutePath($path); $mount = Filesystem::getMountManager()->find($absolutePath); if ($mount->getInternalPath($absolutePath) === '') { @@ -368,8 +378,12 @@ public function rmdir($path) { $storage->getUpdater()->remove($internalPath); } + $emitPostEvent = true; return $result; - }, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'delete'); + }, [ + 'before' => ['path' => $this->getAbsolutePath($path)], + 'after' => ['path' => $this->getAbsolutePath($path), 'processPostEvent' => &$emitPostEvent] + ], 'file', 'delete'); } /** @@ -638,7 +652,7 @@ protected function emit_file_hooks_pre($exists, $path, &$run) { */ protected function emit_file_hooks_post($exists, $path) { // A post event so no before event args required - return $this->emittingCall(function () use (&$exists, &$path){ + return $this->emittingCall(function () use (&$exists, &$path, &$emitPostEvents){ if (!$exists) { \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [ Filesystem::signal_param_path => $this->getHookPath($path), @@ -652,7 +666,10 @@ protected function emit_file_hooks_post($exists, $path) { \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [ Filesystem::signal_param_path => $this->getHookPath($path), ]); - }, ['before' => ['path' => $path], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'create'); + }, [ + 'before' => ['path' => $path], + 'after' => ['path' => $this->getAbsolutePath($path), 'processPostEvent' => true] + ], 'file', 'create'); } /** @@ -720,7 +737,8 @@ public function file_put_contents($path, $data) { * @return bool|mixed */ public function unlink($path) { - return $this->emittingCall(function () use (&$path) { + $processPostEvent = false; + return $this->emittingCall(function () use (&$path, &$processPostEvent) { if ($path === '' || $path === '/') { // do not allow deleting the root return false; @@ -736,6 +754,9 @@ public function unlink($path) { } else { $result = $this->basicOperation('unlink', $path, array('delete')); } + $processPostEvent = $this->emitPostEvent; + //Reset the emitPostEvent to false again + $this->emitPostEvent = false; if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete $storage = $mount->getStorage(); @@ -745,7 +766,10 @@ public function unlink($path) { } else { return $result; } - }, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'delete'); + }, [ + 'before' => ['path' => $this->getAbsolutePath($path)], + 'after' => ['path' => $this->getAbsolutePath($path), 'processPostEvent' => &$processPostEvent] + ], 'file', 'delete'); } /** @@ -765,7 +789,9 @@ public function deleteAll($directory) { * @return bool|mixed */ public function rename($path1, $path2) { - return $this->emittingCall(function () use (&$path1, &$path2) { + $emitPostEvents = false; + $oldHookPath = ''; $newHookPath = ''; + return $this->emittingCall(function () use (&$path1, &$path2, &$emitPostEvents, &$newHookPath, &$oldHookPath) { $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); $result = false; @@ -860,6 +886,7 @@ public function rename($path1, $path2) { } } elseif ($result) { if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) { + $emitPostEvents = true; \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_rename, @@ -880,7 +907,8 @@ public function rename($path1, $path2) { 'before' => ['oldpath' => $this->getAbsolutePath($path1), 'newpath' => $this->getAbsolutePath($path2)], 'after' => ['oldpath' => $this->getAbsolutePath($path1), - 'newpath' => $this->getAbsolutePath($path2)] + 'newpath' => $this->getAbsolutePath($path2), + 'processPostEvent' => &$emitPostEvents] ], 'file', 'rename'); } @@ -894,7 +922,8 @@ public function rename($path1, $path2) { * @return bool|mixed */ public function copy($path1, $path2, $preserveMtime = false) { - return $this->emittingCall(function () use (&$path1, &$path2, &$preserveMtime) { + $emitPostEvents = false; + return $this->emittingCall(function () use (&$path1, &$path2, &$preserveMtime, &$emitPostEvents) { $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); $result = false; @@ -958,6 +987,7 @@ public function copy($path1, $path2, $preserveMtime = false) { $lockTypePath2 = ILockingProvider::LOCK_SHARED; if ($this->shouldEmitHooks() && $result !== false) { + $emitPostEvents = true; \OC_Hook::emit( Filesystem::CLASSNAME, Filesystem::signal_post_copy, @@ -987,7 +1017,8 @@ public function copy($path1, $path2, $preserveMtime = false) { 'newpath' => $this->getAbsolutePath($path2)], 'after' => [ 'oldpath' => $this->getAbsolutePath($path1), - 'newpath' => $this->getAbsolutePath($path2) + 'newpath' => $this->getAbsolutePath($path2), + 'processPostEvent' => &$emitPostEvents ]], 'file', 'copy'); } @@ -1226,6 +1257,7 @@ private function basicOperation($operation, $path, $hooks = [], $extraParam = nu if ($this->shouldEmitHooks($path) && $result !== false) { if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open $this->runHooks($hooks, $path, true); + $this->emitPostEvent = true; } } @@ -1303,6 +1335,12 @@ private function runHooks($hooks, $path, $post = false) { Filesystem::signal_param_path => $path ] ); + if (($hook === 'create') && ($post === true)) { + $this->eventDispatcher->dispatch('file.aftercreate', + new GenericEvent(null, + ['path' => $this->getAbsolutePath($path), 'processPostEvent' => true] + )); + } } elseif (!$post) { \OC_Hook::emit( Filesystem::CLASSNAME, diff --git a/lib/private/Server.php b/lib/private/Server.php index 730712452a29..aa31fb0d1a63 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -227,7 +227,8 @@ public function __construct($webRoot, \OC\Config $config) { $manager = \OC\Files\Filesystem::getMountManager(null); $view = new View(); $root = new Root($manager, $view, null); - $connector = new HookConnector($root, $view); + $eventDispatcher = \OC::$server->getEventDispatcher(); + $connector = new HookConnector($root, $view, $eventDispatcher); $connector->viewToNode(); return $root; }); @@ -314,6 +315,9 @@ public function __construct($webRoot, \OC\Config $config) { $userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) { /** @var $user \OC\User\User */ \OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]); + $this->emittingCall(function () {return true;}, + ['after' => ['uid' => $user->getUID(), 'password' => $password]], + 'user', 'createuser'); }); $userSession->listen('\OC\User', 'preDelete', function ($user) { /** @var $user \OC\User\User */ @@ -322,17 +326,20 @@ public function __construct($webRoot, \OC\Config $config) { $userSession->listen('\OC\User', 'postDelete', function ($user) { /** @var $user \OC\User\User */ \OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]); - $this->emittingCall(function () use (&$user) { - return true; - }, ['before' => [], 'after' => ['uid' => $user->getUID()]], 'user', 'delete'); }); $userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) { /** @var $user \OC\User\User */ \OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]); + $this->emittingCall(function () { return false;}, + ['before' => ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]], + 'user', 'setpassword'); }); $userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) { /** @var $user \OC\User\User */ \OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]); + $this->emittingCall(function () { return true;}, + ['after' => ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]], + 'user', 'setpassphrase'); }); $userSession->listen('\OC\User', 'preLogin', function ($uid, $password) { \OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]); @@ -352,6 +359,7 @@ public function __construct($webRoot, \OC\Config $config) { /** @var $user \OC\User\User */ \OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value]); $this->emittingCall(function () use (&$user, &$feature, &$value) { + return false; }, ['before' => ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value]], 'user', 'featurechange'); }); return $userSession; diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 0aa6a7f148cc..192de8fa3dc6 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -28,6 +28,7 @@ use OC\Cache\CappedMemoryCache; use OC\Files\Mount\MoveableMount; +use OCP\Events\EventEmitterTrait; use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; @@ -50,6 +51,7 @@ * This class is the communication hub for all sharing related operations. */ class Manager implements IManager { + use EventEmitterTrait; /** @var IProviderFactory */ private $factory; @@ -619,49 +621,53 @@ public function createShare(\OCP\Share\IShare $share) { // Pre share hook $run = true; $error = ''; - $preHookData = [ - 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', - 'itemSource' => $share->getNode()->getId(), - 'shareType' => $share->getShareType(), - 'uidOwner' => $share->getSharedBy(), - 'permissions' => $share->getPermissions(), - 'fileSource' => $share->getNode()->getId(), - 'expiration' => $share->getExpirationDate(), - 'token' => $share->getToken(), - 'itemTarget' => $share->getTarget(), - 'shareWith' => $share->getSharedWith(), - 'run' => &$run, - 'error' => &$error, - ]; - \OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData); + $preHookData = []; + $postHookData = []; + return $this->emittingCall(function () use (&$run, &$error, &$share, &$preHookData, &$postHookData) { + $preHookData = [ + 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getNode()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'itemTarget' => $share->getTarget(), + 'shareWith' => $share->getSharedWith(), + 'run' => &$run, + 'error' => &$error, + ]; + \OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData); - if ($run === false) { - throw new \Exception($error); - } + if ($run === false) { + throw new \Exception($error); + } - $provider = $this->factory->getProviderForType($share->getShareType()); - $share = $provider->create($share); - - // Post share hook - $postHookData = [ - 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', - 'itemSource' => $share->getNode()->getId(), - 'shareType' => $share->getShareType(), - 'uidOwner' => $share->getSharedBy(), - 'permissions' => $share->getPermissions(), - 'fileSource' => $share->getNode()->getId(), - 'expiration' => $share->getExpirationDate(), - 'token' => $share->getToken(), - 'id' => $share->getId(), - 'shareWith' => $share->getSharedWith(), - 'itemTarget' => $share->getTarget(), - 'fileTarget' => $share->getTarget(), - 'passwordEnabled' => (!is_null($share->getPassword()) and ($share->getPassword() !== '')), - ]; + $provider = $this->factory->getProviderForType($share->getShareType()); + $share = $provider->create($share); + + // Post share hook + $postHookData = [ + 'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getNode()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getNode()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'id' => $share->getId(), + 'shareWith' => $share->getSharedWith(), + 'itemTarget' => $share->getTarget(), + 'fileTarget' => $share->getTarget(), + 'passwordEnabled' => (!is_null($share->getPassword()) and ($share->getPassword() !== '')), + ]; - \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData); + \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData); - return $share; + return $share; + }, ['before' => $preHookData, 'after' => $postHookData], 'file', 'createshare'); } /** @@ -843,26 +849,28 @@ public function deleteShare(\OCP\Share\IShare $share) { $hookParams = self::formatUnshareHookParams($share); - // Emit pre-hook - \OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams); + return $this->emittingCall(function () use (&$hookParams, &$share) { + // Emit pre-hook + \OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams); - // Get all children and delete them as well - $deletedShares = $this->deleteChildren($share); + // Get all children and delete them as well + $deletedShares = $this->deleteChildren($share); - // Do the actual delete - $provider = $this->factory->getProviderForType($share->getShareType()); - $provider->delete($share); + // Do the actual delete + $provider = $this->factory->getProviderForType($share->getShareType()); + $provider->delete($share); - // All the deleted shares caused by this delete - $deletedShares[] = $share; + // All the deleted shares caused by this delete + $deletedShares[] = $share; - //Format hook info - $formattedDeletedShares = array_map('self::formatUnshareHookParams', $deletedShares); + //Format hook info + $formattedDeletedShares = array_map('self::formatUnshareHookParams', $deletedShares); - $hookParams['deletedShares'] = $formattedDeletedShares; + $hookParams['deletedShares'] = $formattedDeletedShares; - // Emit post hook - \OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams); + // Emit post hook + \OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams); + }, ['before' => $hookParams, 'after' => $hookParams], 'file', 'unshare'); } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 34bf2c7dacec..932089955f91 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -934,6 +934,7 @@ public function logout() { $this->unsetMagicInCookie(); $this->session->clear(); $this->manager->emit('\OC\User', 'postLogout'); + return true; }, ['before' => ['uid' => ''], 'after' => []], 'user', 'logout'); } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 02062a256174..7b7dd9e3619a 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -207,43 +207,46 @@ public function updateLastLoginTimestamp() { * @return bool */ public function delete() { - if ($this->emitter) { - $this->emitter->emit('\OC\User', 'preDelete', [$this]); - } - // get the home now because it won't return it after user deletion - $homePath = $this->getHome(); - $this->mapper->delete($this->account); - $bi = $this->account->getBackendInstance(); - if (!is_null($bi)) { - $bi->deleteUser($this->account->getUserId()); - } + return $this->emittingCall(function () { + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'preDelete', [$this]); + } + // get the home now because it won't return it after user deletion + $homePath = $this->getHome(); + $this->mapper->delete($this->account); + $bi = $this->account->getBackendInstance(); + if (!is_null($bi)) { + $bi->deleteUser($this->account->getUserId()); + } - // FIXME: Feels like an hack - suggestions? + // FIXME: Feels like an hack - suggestions? - // We have to delete the user from all groups - foreach (\OC::$server->getGroupManager()->getUserGroups($this) as $group) { - $group->removeUser($this); - } - // Delete the user's keys in preferences - \OC::$server->getConfig()->deleteAllUserValues($this->getUID()); - - // Delete user files in /data/ - if ($homePath !== false) { - // FIXME: this operates directly on FS, should use View instead... - // also this is not testable/mockable... - \OC_Helper::rmdirr($homePath); - } + // We have to delete the user from all groups + foreach (\OC::$server->getGroupManager()->getUserGroups($this) as $group) { + $group->removeUser($this); + } + // Delete the user's keys in preferences + \OC::$server->getConfig()->deleteAllUserValues($this->getUID()); + + // Delete user files in /data/ + if ($homePath !== false) { + // FIXME: this operates directly on FS, should use View instead... + // also this is not testable/mockable... + \OC_Helper::rmdirr($homePath); + } - // Delete the users entry in the storage table - Storage::remove('home::' . $this->getUID()); + // Delete the users entry in the storage table + Storage::remove('home::' . $this->getUID()); - \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->getUID()); - \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this); + \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->getUID()); + \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this); - if ($this->emitter) { - $this->emitter->emit('\OC\User', 'postDelete', [$this]); - } - return true; + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postDelete', [$this]); + } + return true; + }, + ['before' => ['uid' => $this->getUID()], 'after' => ['uid' => $this->getUID()]], 'user', 'delete'); } /** diff --git a/tests/lib/Files/Node/HookConnectorTest.php b/tests/lib/Files/Node/HookConnectorTest.php index 7245dd375935..7ecc80e80b2d 100644 --- a/tests/lib/Files/Node/HookConnectorTest.php +++ b/tests/lib/Files/Node/HookConnectorTest.php @@ -13,6 +13,8 @@ use OC\Files\Storage\Temporary; use OC\Files\View; use OCP\Files\Node; +use OCP\Files\NotFoundException; +use Symfony\Component\EventDispatcher\EventDispatcher; use Test\TestCase; use Test\Traits\MountProviderTrait; use Test\Traits\UserTrait; @@ -38,6 +40,11 @@ class HookConnectorTest extends TestCase { */ private $root; + /** + * @var EventDispatcher + */ + + private $eventDispatcher; /** * @var string */ @@ -51,6 +58,7 @@ public function setUp() { \OC_Util::setupFS($this->userId); $this->view = new View(); $this->root = new Root(Filesystem::getMountManager(), $this->view, \OC::$server->getUserManager()->get($this->userId)); + $this->eventDispatcher = \OC::$server->getEventDispatcher(); } public function tearDown() { @@ -116,7 +124,8 @@ public function viewToNodeProvider() { * @dataProvider viewToNodeProvider */ public function testViewToNode(callable $operation, $expectedHook) { - $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $postEvents = ["postCreate", "postDelete"]; + $connector = new \OC\Files\Node\HookConnector($this->root, $this->view, $this->eventDispatcher); $connector->viewToNode(); $hookCalled = false; /** @var Node $hookNode */ @@ -130,7 +139,11 @@ public function testViewToNode(callable $operation, $expectedHook) { $operation(); $this->assertTrue($hookCalled); - $this->assertEquals('/' . $this->userId . '/files/test.txt', $hookNode->getPath()); + if (in_array($expectedHook, $postEvents)) { + $this->assertEquals($this->userId . '/files/test.txt', $hookNode->getInternalPath()); + } else { + $this->assertEquals('/' . $this->userId . '/files/test.txt', $hookNode->getPath()); + } } public function viewToNodeProviderCopyRename() { @@ -160,7 +173,8 @@ public function viewToNodeProviderCopyRename() { * @dataProvider viewToNodeProviderCopyRename */ public function testViewToNodeCopyRename(callable $operation, $expectedHook) { - $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $postEvents = ["postRename", "postCopy"]; + $connector = new \OC\Files\Node\HookConnector($this->root, $this->view, $this->eventDispatcher); $connector->viewToNode(); $hookCalled = false; /** @var Node $hookSourceNode */ @@ -177,12 +191,20 @@ public function testViewToNodeCopyRename(callable $operation, $expectedHook) { $operation(); $this->assertTrue($hookCalled); - $this->assertEquals('/' . $this->userId . '/files/source', $hookSourceNode->getPath()); - $this->assertEquals('/' . $this->userId . '/files/target', $hookTargetNode->getPath()); + if (in_array($expectedHook, $postEvents)) { + $this->assertEquals($this->userId . '/files/source', $hookSourceNode->getInternalPath()); + $this->assertEquals($this->userId . '/files/target', $hookTargetNode->getInternalPath()); + } else { + $this->assertEquals('/' . $this->userId . '/files/source', $hookSourceNode->getPath()); + $this->assertEquals('/' . $this->userId . '/files/target', $hookTargetNode->getPath()); + } } + /** + * @expectedException \OCP\Files\NotFoundException + */ public function testPostDeleteMeta() { - $connector = new \OC\Files\Node\HookConnector($this->root, $this->view); + $connector = new \OC\Files\Node\HookConnector($this->root, $this->view, $this->eventDispatcher); $connector->viewToNode(); $hookCalled = false; /** @var Node $hookNode */ @@ -198,6 +220,6 @@ public function testPostDeleteMeta() { Filesystem::unlink('test.txt'); $this->assertTrue($hookCalled); - $this->assertEquals($hookNode->getId(), $info->getId()); + $hookNode->getId(); } } diff --git a/tests/lib/User/UserTest.php b/tests/lib/User/UserTest.php index 4ff081bbfb84..6992625db166 100644 --- a/tests/lib/User/UserTest.php +++ b/tests/lib/User/UserTest.php @@ -194,8 +194,25 @@ public function testChangeAvatarSupported($expected, $implements, $canChange) { } public function testDelete() { + $calledBeforeDeleteEvent = []; + $calledAfterDeleteEvent = []; + \OC::$server->getEventDispatcher()->addListener('user.beforedelete', function ($event) use (&$calledBeforeDeleteEvent) { + $calledBeforeDeleteEvent[] = 'user.beforedelete'; + $calledBeforeDeleteEvent[] = $event; + }); + \OC::$server->getEventDispatcher()->addListener('user.afterdelete', function ($event) use (&$calledAfterDeleteEvent) { + $calledAfterDeleteEvent[] = 'user.afterdelete'; + $calledAfterDeleteEvent[] = $event; + }); $this->accountMapper->expects($this->once())->method('delete')->willReturn($this->account); $this->assertTrue($this->user->delete()); + + $this->assertInstanceOf(GenericEvent::class, $calledBeforeDeleteEvent[1]); + $this->assertInstanceOf(GenericEvent::class, $calledBeforeDeleteEvent[1]); + $this->assertEquals('user.afterdelete', $calledAfterDeleteEvent[0]); + $this->assertEquals('user.beforedelete', $calledBeforeDeleteEvent[0]); + $this->assertArrayHasKey('uid', $calledBeforeDeleteEvent[1]); + $this->assertArrayHasKey('uid', $calledAfterDeleteEvent[1]); } public function testGetHome() {