diff --git a/lib/Database.php b/lib/Database.php index e69ef832..2974d092 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -258,61 +258,6 @@ class Database { return true; } - public function subscriptionAdd(string $user, string $url, string $fetchUser = "", string $fetchPassword = ""): int { - // If the user isn't authorized to perform this action then throw an exception. - if (!Data::$user->authorize($user, __FUNCTION__)) { - throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); - } - // If the user doesn't exist throw an exception. - if (!$this->userExists($user)) { - throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); - } - - $this->db->begin(); - - // If the feed doesn't already exist in the database then add it to the database - // after determining its validity with PicoFeed. - $qFeed = $this->db->prepare("SELECT id from arsse_feeds where url is ? and username is ? and password is ?", "str", "str", "str"); - $feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getValue(); - if ($feed === null) { - $feed = new Feed($url); - $feed->parse(); - - // Add the feed to the database and return its Id which will be used when adding - // its articles to the database. - $feedID = $this->db->prepare( - 'INSERT INTO arsse_feeds(url,title,favicon,source,updated,modified,etag,username,password) - values(?,?,?,?,?,?,?,?,?)', - 'str', 'str', 'str', 'str', 'datetime', 'datetime', 'str', 'str', 'str')->run( - $url, - $feed->data->title, - // Grab the favicon for the feed; returns an empty string if it cannot find one. - $feed->favicon, - $feed->data->siteUrl, - $feed->data->date, - $feed->resource->getLastModified(), - $feed->resource->getEtag(), - $fetchUser, - $fetchPassword - )->lastId(); - - // Add each of the articles to the database. - foreach ($feed->data->items as $i) { - $this->articleAdd($i); - } - } - - // Add the feed to the user's subscriptions. - $sub = $this->db->prepare('INSERT INTO arsse_subscriptions(owner,feed) values(?,?)', 'str', 'int')->run($user, $feedID)->lastId(); - $this->db->commit(); - return $sub; - } - - public function subscriptionRemove(string $user, int $id): bool { - if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); - return (bool) $this->db->prepare("DELETE from arsse_subscriptions where id is ?", "int")->run($id)->changes(); - } - public function folderAdd(string $user, array $data): int { // If the user isn't authorized to perform this action then throw an exception. if (!Data::$user->authorize($user, __FUNCTION__)) { @@ -379,6 +324,79 @@ class Database { } } + public function folderRemove(string $user, int $id): bool { + if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + // if the user doesn't exist throw an exception. + if (!$this->userExists($user)) { + throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); + } + // common table expression to list all descendant folders of the target folder + $cte = "RECURSIVE folders(id) as (SELECT id from arsse_folders where owner is ? and id is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id) "; + $changes = 0; + $this->db->begin(); + // first delete any feed subscriptions contained within the folder tree (this may not be necessary because of foreign keys) + $changes += $this->db->prepare("WITH $cte"."DELETE FROM arsse_subscriptions where folder in(select id from folders)", "str", "int")->run($user, $id)->changes(); + // next delete the folders themselves + $changes += $this->db->prepare("WITH $cte"."DELETE FROM arsse_folders where id in(select id from folders)", "str", "int")->run($user, $id)->changes(); + $this->db->commit(); + return (bool) $changes; + } + + public function subscriptionAdd(string $user, string $url, string $fetchUser = "", string $fetchPassword = ""): int { + // If the user isn't authorized to perform this action then throw an exception. + if (!Data::$user->authorize($user, __FUNCTION__)) { + throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + } + // If the user doesn't exist throw an exception. + if (!$this->userExists($user)) { + throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]); + } + + $this->db->begin(); + + // If the feed doesn't already exist in the database then add it to the database + // after determining its validity with PicoFeed. + $qFeed = $this->db->prepare("SELECT id from arsse_feeds where url is ? and username is ? and password is ?", "str", "str", "str"); + $feed = $qFeed->run($url, $fetchUser, $fetchPassword)->getValue(); + if ($feed === null) { + $feed = new Feed($url); + $feed->parse(); + + // Add the feed to the database and return its Id which will be used when adding + // its articles to the database. + $feedID = $this->db->prepare( + 'INSERT INTO arsse_feeds(url,title,favicon,source,updated,modified,etag,username,password) + values(?,?,?,?,?,?,?,?,?)', + 'str', 'str', 'str', 'str', 'datetime', 'datetime', 'str', 'str', 'str')->run( + $url, + $feed->data->title, + // Grab the favicon for the feed; returns an empty string if it cannot find one. + $feed->favicon, + $feed->data->siteUrl, + $feed->data->date, + $feed->resource->getLastModified(), + $feed->resource->getEtag(), + $fetchUser, + $fetchPassword + )->lastId(); + + // Add each of the articles to the database. + foreach ($feed->data->items as $i) { + $this->articleAdd($i); + } + } + + // Add the feed to the user's subscriptions. + $sub = $this->db->prepare('INSERT INTO arsse_subscriptions(owner,feed) values(?,?)', 'str', 'int')->run($user, $feedID)->lastId(); + $this->db->commit(); + return $sub; + } + + public function subscriptionRemove(string $user, int $id): bool { + if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); + return (bool) $this->db->prepare("DELETE from arsse_subscriptions where owner is ? and id is ?", "str", "int")->run($user, $id)->changes(); + } + public function articleAdd(PicoFeed\Parser\Item $article): int { $this->db->begin(); @@ -522,18 +540,4 @@ class Database { $this->db->commit(); return 1; } - - public function folderRemove(string $user, int $id): bool { - if(!Data::$user->authorize($user, __FUNCTION__)) throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]); - // common table expression to list all descendant folders of the target folder - $cte = "RECURSIVE folders(id) as (SELECT id from arsse_folders where owner is ? and id is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id) "; - $changes = 0; - $this->db->begin(); - // first delete any feed subscriptions contained within the folder tree (this may not be necessary because of foreign keys) - $changes += $this->db->prepare("WITH $cte"."DELETE FROM arsse_subscriptions where folder in(select id from folders)", "str", "int")->run($user, $id)->changes(); - // next delete the folders themselves - $changes += $this->db->prepare("WITH $cte"."DELETE FROM arsse_folders where id in(select id from folders)", "str", "int")->run($user, $id)->changes(); - $this->db->commit(); - return (bool) $changes; - } } \ No newline at end of file diff --git a/tests/lib/Database/SeriesFolder.php b/tests/lib/Database/SeriesFolder.php index 8e392fdb..61c6cb6e 100644 --- a/tests/lib/Database/SeriesFolder.php +++ b/tests/lib/Database/SeriesFolder.php @@ -153,4 +153,39 @@ trait SeriesFolder { $this->assertException("notAuthorized", "User", "ExceptionAuthz"); Data::$db->folderList("john.doe@example.com"); } + + function testRemoveAFolder() { + $this->assertTrue(Data::$db->folderRemove("john.doe@example.com", 6)); + $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'root', 'name']]); + array_pop($state['arsse_folders']['rows']); + $this->compareExpectations($state); + } + + function testRemoveAFolderTree() { + $this->assertTrue(Data::$db->folderRemove("john.doe@example.com", 1)); + $state = $this->primeExpectations($this->data, ['arsse_folders' => ['id','owner', 'parent', 'root', 'name']]); + foreach([0,1,2,5] as $index) { + unset($state['arsse_folders']['rows'][$index]); + } + $this->compareExpectations($state); + } + + function testRemoveAMissingFolder() { + $this->assertFalse(Data::$db->folderRemove("john.doe@example.com", 2112)); + } + + function testRemoveFolderOfTheWrongOwner() { + $this->assertFalse(Data::$db->folderRemove("john.doe@example.com", 4)); // folder ID 4 belongs to Jane + } + + function testRemoveAFolderForAMissingUser() { + $this->assertException("doesNotExist", "User"); + Data::$db->folderRemove("john.doe@example.org", 1); + } + + function testRemoveAFolderWithoutAuthority() { + Phake::when(Data::$user)->authorize->thenReturn(false); + $this->assertException("notAuthorized", "User", "ExceptionAuthz"); + Data::$db->folderList("john.doe@example.com", 1); + } } \ No newline at end of file