Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions com.woltlab.wcf/objectType.xml
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,11 @@
<definitionname>com.woltlab.wcf.box.articleList.condition</definitionname>
<classname>wcf\system\condition\article\ArticleCategoryCondition</classname>
</type>
<type>
<name>com.woltlab.wcf.articleFeatured</name>
<definitionname>com.woltlab.wcf.box.articleList.condition</definitionname>
<classname>wcf\system\condition\article\ArticleFeaturedCondition</classname>
</type>
<!-- /article list box condition -->
<!-- recent activity list box condition -->
<type>
Expand Down
3 changes: 3 additions & 0 deletions com.woltlab.wcf/templates/articleContentHeaderTitle.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
<li>
{icon name='flag'}
{content}
{if $article->isFeatured}
<span class="badge green">{lang}wcf.article.featured{/lang}</span>
{/if}
{if $article->isDeleted}
<span class="badge red">{lang}wcf.message.status.deleted{/lang}</span>
{/if}
Expand Down
1 change: 1 addition & 0 deletions com.woltlab.wcf/templates/articleListItems.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
{hascontent}
<div class="entryCardList__item__badges">
{content}
{if $article->isFeatured}<span class="badge green">{lang}wcf.article.featured{/lang}</span>{/if}
{if $article->isDeleted}<span class="badge red">{lang}wcf.message.status.deleted{/lang}</span>{/if}
{if !$article->isPublished()}<span class="badge green">{lang}wcf.message.status.disabled{/lang}</span>{/if}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
SmallintDatabaseTableColumn::create('attachments')
->notNull()
->defaultValue(0)
->drop()
->drop(),
DefaultFalseBooleanDatabaseTableColumn::create('isFeatured'),
]),
PartialDatabaseTable::create('wcf1_article_content')
->columns([
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace wcf\command\article;

use wcf\data\article\Article;
use wcf\data\article\ArticleBuilder;
use wcf\event\article\ArticleSetAsFeatured;
use wcf\system\event\EventHandler;

/**
* Flags an article as featured.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class SetArticleAsFeatured
{
public function __construct(private readonly Article $article) {}

public function __invoke(): void
{
ArticleBuilder::forUpdate($this->article)
->setIsFeatured(true)
->update();

$event = new ArticleSetAsFeatured($this->article);
EventHandler::getInstance()->fire($event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace wcf\command\article;

use wcf\data\article\Article;
use wcf\data\article\ArticleBuilder;
use wcf\event\article\ArticleUnsetAsFeatured;
use wcf\system\event\EventHandler;

/**
* Removes the featured flag from an article.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class UnsetArticleAsFeatured
{
public function __construct(private readonly Article $article) {}

public function __invoke(): void
{
ArticleBuilder::forUpdate($this->article)
->setIsFeatured(false)
->update();

$event = new ArticleUnsetAsFeatured($this->article);
EventHandler::getInstance()->fire($event);
}
}
11 changes: 11 additions & 0 deletions wcfsetup/install/files/lib/data/article/Article.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* @property-read int $cumulativeLikes cumulative result of likes for the article
* @property-read 0|1 $isDeleted is 1 if the article is in trash bin, otherwise 0
* @property-read 0|1 $hasLabels is `1` if labels are assigned to the article
* @property-read 0|1 $isFeatured is `1` if the article is featured which pins it at the top, otherwise `0`
*
* @extends CollectionDatabaseObject<ArticleCollection>
*/
Expand Down Expand Up @@ -173,6 +174,16 @@ public function canPublish(): bool
return false;
}

/**
* Returns true if the active user can flag articles as featured.
*
* @since 6.3
*/
public static function canFeature(): bool
{
return WCF::getSession()->hasPermission('admin.content.article.canManageArticle');
}

/**
* Returns true if the active user can create articles in at least one category.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public function setEnableComments(bool $enableComments): static
return $this;
}

public function setIsFeatured(bool $isFeatured): static
{
$this->properties['isFeatured'] = $isFeatured ? 1 : 0;

return $this;
}

public function setIsDeleted(bool $isDeleted): static
{
$this->properties['isDeleted'] = $isDeleted ? 1 : 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace wcf\event\article;

use wcf\data\article\Article;
use wcf\event\IPsr14Event;

/**
* Indicates that an article has been flagged as featured.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class ArticleSetAsFeatured implements IPsr14Event
{
public function __construct(public readonly Article $article)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace wcf\event\article;

use wcf\data\article\Article;
use wcf\event\IPsr14Event;

/**
* Indicates that the featured flag of an article has been removed.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
final class ArticleUnsetAsFeatured implements IPsr14Event
{
public function __construct(public readonly Article $article)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ protected function getObjectList(): AccessibleArticleList
#[\Override]
protected function createListView(): ArticleListView
{
return new ArticleListView();
$listView = new ArticleListView();
$listView->setPinFeaturedArticles(false);

return $listView;
}

#[\Override]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace wcf\system\condition\article;

use wcf\data\article\ArticleList;
use wcf\data\DatabaseObjectList;
use wcf\system\condition\AbstractCheckboxCondition;
use wcf\system\condition\IObjectListCondition;

/**
* Condition implementation for articles to only include featured articles.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*
* @implements IObjectListCondition<ArticleList>
*/
class ArticleFeaturedCondition extends AbstractCheckboxCondition implements IObjectListCondition
{
/**
* @inheritDoc
*/
protected $fieldName = 'articleIsFeatured';

/**
* @inheritDoc
*/
protected $label = 'wcf.article.condition.isFeatured';

#[\Override]
public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData): void
{
$objectList->getConditionBuilder()->add('article.isFeatured = ?', [1]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace wcf\system\endpoint\controller\core\articles;

use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use wcf\data\article\Article;
use wcf\http\Helper;
use wcf\system\endpoint\IController;
use wcf\system\endpoint\PostRequest;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;

/**
* Flags the article with the given ID as featured.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
#[PostRequest('/core/articles/{id:\d+}/set-as-featured')]
final class SetArticleAsFeatured implements IController
{
#[\Override]
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
{
if (\MODULE_ARTICLE === 0) {
throw new IllegalLinkException();
}

if (!Article::canFeature()) {
throw new PermissionDeniedException();
}

$article = Helper::fetchObjectFromRequestParameter($variables['id'], Article::class);

if ($article->isFeatured === 0) {
new \wcf\command\article\SetArticleAsFeatured($article)();
}

return new JsonResponse([]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace wcf\system\endpoint\controller\core\articles;

use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use wcf\data\article\Article;
use wcf\http\Helper;
use wcf\system\endpoint\IController;
use wcf\system\endpoint\PostRequest;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;

/**
* Removes the featured flag from the article with the given ID.
*
* @author Marcel Werk
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/
#[PostRequest('/core/articles/{id:\d+}/unset-as-featured')]
final class UnsetArticleAsFeatured implements IController
{
#[\Override]
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
{
if (\MODULE_ARTICLE === 0) {
throw new IllegalLinkException();
}

if (!Article::canFeature()) {
throw new PermissionDeniedException();
}

$article = Helper::fetchObjectFromRequestParameter($variables['id'], Article::class);

if ($article->isFeatured !== 0) {
new \wcf\command\article\UnsetArticleAsFeatured($article)();
}

return new JsonResponse([]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ public function render(mixed $value, DatabaseObject $row): string
WCF::getLanguage()->get('wcf.acp.article.publicationStatus.delayed')
);
}
if ($row->isFeatured !== 0) {
if ($badges !== '') {
$badges .= ' ';
}
$badges .= \sprintf(
'<span class="badge green">%s</span>',
WCF::getLanguage()->get('wcf.article.featured')
);
}

// @phpstan-ignore property.notFound
$articleTitle = StringUtil::encodeHTML($row->title);
Expand Down Expand Up @@ -203,6 +212,7 @@ public function applyFilter(DatabaseObjectList $list, string $value): void
'wcf.acp.article.publicationStatus'
),
new BooleanFilter('isDeleted', 'wcf.acp.article.isDeleted'),
new BooleanFilter('isFeatured', 'wcf.article.featured'),
]);
$provider = new ArticleInteractions();
$provider->addInteractions([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,30 @@ public function __construct()
return $article->publicationStatus === Article::PUBLISHED;
}
),
new RpcInteraction(
'set-as-featured',
'core/articles/%s/set-as-featured',
'wcf.article.button.setAsFeatured',
isAvailableCallback: static function (Article $article): bool {
if (!Article::canFeature()) {
return false;
}

return $article->isFeatured === 0;
}
),
new RpcInteraction(
'unset-as-featured',
'core/articles/%s/unset-as-featured',
'wcf.article.button.unsetAsFeatured',
isAvailableCallback: static function (Article $article): bool {
if (!Article::canFeature()) {
return false;
}

return $article->isFeatured !== 0;
}
),
new FormBuilderDialogInteraction(
'toggle-i18n',
LinkHandler::getInstance()->getControllerLink(
Expand Down
Loading
Loading