diff --git a/controllers/NewsController.php b/controllers/NewsController.php index fd7a17d1..e9e7a566 100644 --- a/controllers/NewsController.php +++ b/controllers/NewsController.php @@ -61,8 +61,9 @@ public function deleteAction(Player $me, News $article) private function getCategories() { - return $this->getQueryBuilder('NewsCategory') - ->sortBy('name') - ->getModels(); + return NewsCategory::getQueryBuilder() + ->orderBy('name') + ->getModels(true) + ; } } diff --git a/migrations/20180206040040_news_category_status_column_conversion.php b/migrations/20180206040040_news_category_status_column_conversion.php new file mode 100644 index 00000000..7de1ad5c --- /dev/null +++ b/migrations/20180206040040_news_category_status_column_conversion.php @@ -0,0 +1,63 @@ +table('news_categories'); + $newsCategoryTable + ->addColumn('is_read_only', 'boolean', [ + 'after' => 'protected', + 'null' => false, + 'default' => false, + 'comment' => 'When set to true, no new articles should be able to use this category' + ]) + ->addColumn('is_deleted', 'boolean', [ + 'after' => 'is_read_only', + 'null' => false, + 'default' => false, + 'comment' => 'Whether or not the news category has been soft deleted', + ]) + ->changeColumn('protected', 'boolean', [ + 'default' => false, + 'null' => false, + 'comment' => 'When set to true, prevents the category from being deleted from the UI', + ]) + ->update() + ; + + $this->query("UPDATE news_categories SET is_deleted = 1 WHERE status = 'deleted';"); + + $newsCategoryTable + ->removeColumn('status') + ->renameColumn('protected', 'is_protected') + ->update() + ; + } + + public function down() + { + $newsCategoryTable = $this->table('news_categories'); + $newsCategoryTable + ->addColumn('status', 'set', [ + 'values' => ['enabled', 'disabled', 'deleted'], + 'after' => 'is_deleted', + 'null' => false, + 'default' => 'enabled', + 'comment' => 'The status of the news element', + ]) + ->renameColumn('is_protected', 'protected') + ->update() + ; + + $this->query("UPDATE news_categories SET status = 'deleted' WHERE is_deleted = 1;"); + + $newsCategoryTable + ->removeColumn('is_deleted') + ->removeColumn('is_read_only') + ->update() + ; + } +} diff --git a/models/NewsCategory.php b/models/NewsCategory.php index 979470a0..90a62f68 100644 --- a/models/NewsCategory.php +++ b/models/NewsCategory.php @@ -8,6 +8,7 @@ /** * @TODO Create permissions for creating, editing, and modifying categories + * @TODO Set up methods to modify the News Categories */ /** @@ -16,17 +17,15 @@ */ class NewsCategory extends AliasModel { - /** - * Whether or not the category is protected from being deleted - * @var bool - */ - protected $protected; + /** @var bool Whether or not the category is protected from being deleted from the UI */ + protected $is_protected; + + /** @var bool When set to true, no new articles can be assigned this category */ + protected $is_read_only; const DEFAULT_STATUS = 'enabled'; - /** - * The name of the database table used for queries - */ + const DELETED_COLUMN = 'is_deleted'; const TABLE = "news_categories"; /** @@ -36,58 +35,33 @@ protected function assignResult($category) { $this->alias = $category['alias']; $this->name = $category['name']; - $this->protected = $category['protected']; - $this->status = $category['status']; + $this->is_protected = $category['is_protected']; + $this->is_deleted = $category['is_deleted']; } /** * Delete a category. Only delete a category if it is not protected + * + * @throws DeletionDeniedException + * @throws Exception */ public function delete() { - // Get any articles using this category - $articles = News::fetchIdsFrom("category", $this->getId()); - - // Only delete a category if it is not protected and is not being used - if (!$this->isProtected() && count($articles) == 0) { - parent::delete(); - } - } + $hasArticles = (bool) News::getQueryBuilder() + ->where('category', '=', $this->getId()) + ->active() + ->count() + ; - /** - * Disable the category - * - * @return void - */ - public function disableCategory() - { - if ($this->getStatus() != "disabled") { - $this->status = "disabled"; - $this->update("status", "disabled"); + if ($hasArticles) { + throw new DeletionDeniedException('This category has news articles and cannot be deleted.'); } - } - /** - * Enable the category - * - * @return void - */ - public function enableCategory() - { - if ($this->getStatus() != "enabled") { - $this->status = "enabled"; - $this->update("status", "enabled"); + if ($this->isProtected()) { + throw new DeletionDeniedException('This category is protected and cannot be deleted.'); } - } - /** - * Get the status of the category - * - * @return string Either 'enabled', 'disabled', or 'deleted' - */ - public function getStatus() - { - return $this->status; + parent::delete(); } /** @@ -119,13 +93,23 @@ public function getNews($start = 0, $limit = 5, $getDrafts = false) } /** - * Check if the category is protected from being deleted + * Check if the category is protected from being deleted. * * @return bool Whether or not the category is protected */ public function isProtected() { - return (bool) $this->protected; + return (bool) $this->is_protected; + } + + /** + * Check if new News article can be assigned this category. + * + * @return bool + */ + public function isReadOnly() + { + return (bool) $this->is_read_only; } /** @@ -138,41 +122,39 @@ public function isProtected() public static function addCategory($name) { return self::create(array( - 'alias' => self::generateAlias($name), - 'name' => $name, - 'protected' => 0, - 'status' => 'enabled' + 'alias' => self::generateAlias($name), + 'name' => $name, )); } /** * Get all of the categories for the news * + * @throws Exception + * * @return NewsCategory[] An array of categories */ public static function getCategories() { - return self::arrayIdToModel( - self::fetchIdsFrom( - "status", array("deleted"), true, - "ORDER BY name ASC" - ) - ); + return self::getQueryBuilder() + ->orderBy('name', 'ASC') + ->active() + ->getModels(true) + ; } /** - * Get a query builder for news categories - * @return QueryBuilder + * Get a query builder for news categories. + * + * @throws Exception + * + * @return QueryBuilderFlex */ public static function getQueryBuilder() { - return new QueryBuilder('NewsCategory', array( - 'columns' => array( - 'name' => 'name', - 'status' => 'status' - ), - 'name' => 'name', - )); + return QueryBuilderFlex::createForModel(NewsCategory::class) + ->setNameColumn('name') + ; } /** diff --git a/src/Exception/DeletionDeniedException.php b/src/Exception/DeletionDeniedException.php new file mode 100644 index 00000000..cb64d60f --- /dev/null +++ b/src/Exception/DeletionDeniedException.php @@ -0,0 +1,9 @@ +connectToDatabase(); + parent::setUp(); $this->player_with_create_perms = $this->getNewPlayer(); $this->player_without_create_perms = $this->getNewPlayer(); $this->player_with_create_perms->addRole(Role::ADMINISTRATOR); - $this->newsCategory = NewsCategory::addCategory("Sample Category"); + $this->newsCategory = NewsCategory::addCategory('Sample Category'); + } + + public function tearDown() + { + parent::tearDown(); + + $this->wipe($this->newsCategory); } public function testCustomNewsCategorySetup() { - $this->assertInstanceOf('NewsCategory', $this->newsCategory); - $this->assertEquals(array("enabled"), $this->newsCategory->getActiveStatuses()); - $this->assertEquals("category", $this->newsCategory->getParamName()); - $this->assertEquals("news category", $this->newsCategory->getTypeForHumans()); + $this->assertInstanceOf(NewsCategory::class, $this->newsCategory); + $this->assertEquals('category', $this->newsCategory->getParamName()); + $this->assertEquals('news category', $this->newsCategory->getTypeForHumans()); } public function testCustomNewsCategoryExists() @@ -47,79 +53,40 @@ public function testCustomNewsCategoryIsNotProtected() $this->assertFalse($this->newsCategory->isProtected()); } - public function testCustomNewsCategoryDefaultStatus() - { - $this->assertEquals("enabled", $this->newsCategory->getStatus()); - } - - public function testDisablingCustomNewsCategory() - { - $this->newsCategory->disableCategory(); - $this->assertEquals("disabled", $this->newsCategory->getStatus()); - } - - public function testDisablingDisabledCustomNewsCategory() + public function testCustomNewsCategoryIsNotDeletedByDefault() { - $this->newsCategory->disableCategory(); - $this->assertEquals("disabled", $this->newsCategory->getStatus()); - - $this->newsCategory->disableCategory(); - $this->assertEquals("disabled", $this->newsCategory->getStatus()); + $this->assertFalse($this->newsCategory->isDeleted()); } - public function testEnableDisabledCustomNewsCategory() + public function testCustomNewsCategoryIsNotReadOnlyByDefault() { - $this->newsCategory->disableCategory(); - $this->assertEquals("disabled", $this->newsCategory->getStatus()); - - $this->newsCategory->enableCategory(); - $this->assertEquals("enabled", $this->newsCategory->getStatus()); + $this->assertFalse($this->newsCategory->isReadOnly()); } public function testDeletingProtectedNewsCategory() { - $newsCategory = NewsCategory::get(1); + $this->expectException(DeletionDeniedException::class); + $newsCategory = NewsCategory::get(1); $newsCategory->delete(); - $this->assertEquals('enabled', $newsCategory->getStatus()); - - $this->assertArrayContainsModel($newsCategory, NewsCategory::getCategories()); } public function testDeletingCustomNewsCategoryWithoutPosts() { $this->newsCategory->delete(); - $this->assertEquals('deleted', $this->newsCategory->getStatus()); + $this->assertTrue($this->newsCategory->isDeleted()); $this->assertArrayDoesNotContainModel($this->newsCategory, NewsCategory::getCategories()); } public function testDeletingCustomNewsCategoryWithPosts() { - $news = News::addNews(StringMocks::SampleTitleOne, StringMocks::LargeContent, $this->player_with_create_perms->getId(), $this->newsCategory->getId()); + $this->expectException(DeletionDeniedException::class); + + $this->createdModels[] = News::addNews(StringMocks::SampleTitleOne, StringMocks::LargeContent, $this->player_with_create_perms->getId(), $this->newsCategory->getId()); $this->assertArrayLengthEquals($this->newsCategory->getNews(), 1); $this->newsCategory->delete(); - $this->assertEquals('enabled', $this->newsCategory->getStatus()); - - $this->assertArrayContainsModel($this->newsCategory, NewsCategory::getCategories()); - - $this->wipe($news); - } - - public function testCustomNewsCategoryQueryBuilder() - { - $qb = $this->newsCategory->getQueryBuilder(); - $this->assertInstanceOf('QueryBuilder', $qb); - - $results = $qb->where("status")->equals("enabled")->getModels(); - $this->assertArrayLengthEquals($results, 2); - } - - public function tearDown() - { - $this->wipe($this->newsCategory); - parent::tearDown(); } }