Skip to content

标准#

当 XenForo 需要根据某些用户选择的条件(标准)来测试某些内容(用户/页面/帖子等)时,它会使用标准系统。

一些使用标准系统的地方包括:

  • 奖杯
  • 用户组晋升
  • 论坛通知

插件也可以使用这个系统。

标准类型#

考虑以下标准:

  • 用户有/没有头像
  • 用户有超过 300 条消息
  • 用户正在创建线程
  • 当前用户选择的导航标签是“成员”

前两个标准涉及用户本身。其余的标准涉及用户在论坛上的当前位置。看来我们有不同类别或类型的标准。

XenForo 默认有两种标准类型:

  • 用户标准 —— 处理与用户本身相关的标准
  • 页面标准 —— 处理与用户当前位置相关的标准 + 时间标准

一些插件可能还会添加自己的标准类型。

从代码的角度来看,标准类型只是抽象类 AbstractCriteria 的子类。它们包含处理特定类型选定标准的代码。

AbstractCriteria 反过来提供了处理标准的一般方法,无论它们的含义如何。

标准#

标准是用户可选择的预定义条件。

为什么是可选择的? 因为管理员/用户可以选择它们(记得奖杯创建过程)。

为什么是预定义的? 因为 XenForo 已经知道如何处理它们(使用标准类方法)。

每个标准由两部分组成:规则和(可选的)数据

规则#

标准规则只是一个 snake case 格式的字符串(单词之间用下划线分隔)。

它有两个基本目的:

  1. 用于区分标准
  2. 在执行匹配时,规则会转换为处理此标准的方法的 camel case 名称(参见 "标准如何工作")。

数据#

它只是一个可选的附加标准数据数组。例如,“用户至少发布了 X 条消息”标准有一个包含一个元素的数组:消息数量。

标准系统如何工作#

在本节中,我们将从头到尾描述标准系统的工作原理。

模板#

这一切都从模板代码开始。以下是标准在模板中的样子:

HTML
<xf:checkbox label="标准容器">

    <!-- 标准 -->
    <xf:option name="foo_criteria[criterion_1_rule][rule]" value="criterion_1_rule" ... />

    <!-- 带数据的标准 -->
    <xf:option name="bar_criteria[criterion_2_rule][rule]" value="criterion_2_rule" ... >
        <xf:... name="bar_criteria[criterion_2_rule][data][param_1]" ... />
        <xf:... name="bar_criteria[criterion_2_rule][data][param_2]" ... />
    </xf:option>

</xf:checkbox>

如你所见,标准只是一个带有可选输入字段(标准数据)的复选框。让我们分析一下代码:

  • foo_criteriabar_criteria 是输入容器,通常 foobar 部分指的是标准类型。例如,user_criteria[...] 让我们知道这些标准属于用户标准。
  • value="criterion_1_rule"value="criterion_2_rule" 显然是标准的规则。

注意

请记住,name 属性中的 criterion_1/2_rule 不一定是标准规则!这些只是输入容器的名称。你可以轻松地写 <xf:option name="foo[bar][rule]" value="criterion_rule" />,它会正常工作。标准规则将是 criterion_rule,而不是 bar

(可选)存储选定的标准#

在控制器中,可以过滤、编码并保存上一节中的标准表单数据到 mediumblob 类型的数据库列中,以备将来使用:

PHP
$fooCriteriaInput = $this->filter('foo_criteria', 'array');
$barCriteriaInput = $this->filter('bar_criteria', 'array');

$form->basicEntitySave($bazEntity, [
    'foo_criteria' => $fooCriteriaInput,
    'bar_criteria' => $barCriteriaInput
]);

示例 $bazEntity 结构:

PHP
public static function getStructure(Structure $structure)
{
    $structure->table = 'xf_baz';
    $structure->shortName = 'XF:Baz';
    $structure->primaryKey = 'baz_id';
    $structure->columns = [
        'baz_id' => ['type' => self::UINT, 'autoIncrement' => true],
        'foo_criteria' => ['type' => self::JSON_ARRAY, 'default' => [], 'required' => 'please_select_criteria_that_must_be_met'],
        'bar_criteria' => ['type' => self::JSON_ARRAY, 'default' => []]
    ];

    return $structure;
}

标准对象#

为了使用标准系统,我们需要从选定的标准表单数据中创建一个标准对象。这可以通过应用程序的 criteria() 方法完成:

PHP
/** @var \Qux\Criteria\Foo $fooCriteria */
$fooCriteria = \XF::app()->criteria('Qux:Foo', $bazEntity->foo_criteria);

/** @var \Qux\Criteria\Bar $barCriteria */
$barCriteria = \XF::app()->criteria('Qux:Bar', $bazEntity->bar_criteria);

从现在开始,我们可以使用所有 AbstractCriteria 功能以及我们在子类 Foo/Bar 中额外编写的所有内容。

匹配#

当我们想要检查某物(用户)是否匹配选定的标准时,我们使用 isMatched 方法:

PHP
$visitor= \XF::visitor();

if ($fooCriteria->isMatched($visitor))
{
    // 访客匹配所有选定的标准
}
else
{
    // 访客不匹配一个或多个标准
}

isMatched() 将标准规则转换为带有 _match 前缀的 camel case 方法名称:criterion_1_rule > _matchCriterion1Rule,并尝试在标准类型类中找到这样的方法(在我们的示例中是 Foo 类):

PHP
// Qux/Criteria/Foo.php

protected function _matchCriterion1Rule(array $data, \XF\Entity\User $user)
{
    /* ... 处理标准 ... */

    return true; // 用户匹配当前标准

    /* 或者 */

    return false; // 用户不匹配当前标准
}

如果在类中找不到某些方法,isMatched() 会调用 isUnknownMatched(),其行为可以在 AbstractCriteria 祖先中设置(默认返回 false)。

如果没有选择任何标准,isMatched() 会返回 $matchOnEmpty 变量,默认情况下为 true。你可以通过调用 $crteriaObj->setMatchOnEmpty(false) 来改变这种行为,使用 isMatched() 方法之前:

PHP
$visitor= \XF::visitor();

$fooCriteria->setMatchOnEmpty(false);

if ($fooCriteria->isMatched($visitor))
{
    // 访客匹配所有选定的标准
}
else
{
    // 访客不匹配一个或多个标准
}

标准如何工作(示例)#

想象一下,你想奖励所有拥有头像并收到至少 5 个赞的用户一个奖杯。

在创建奖杯时,你选择“用户有头像”(规则 has_avatar)和“用户收到至少 X 个赞”(规则 like_count)标准。最后一个标准还有一个包含一个元素的数组:赞的数量。

你选择的标准存储在 xf_trophy 表的 user_criteria 列中。

当 XenForo 决定是否奖励用户奖杯时,它会将规则转换为 camel case 方法名称:

  • like_count > _matchLikeCount()
  • has_avatar > _matchHasAvatar()

由于这两个选定的标准都是用户标准,XenForo 会调用用户标准类,并尝试在其中找到这些方法:

src/XF/Criteria/User.php
//...

protected function _matchLikeCount(array $data, \XF\Entity\User $user)
{
    return ($user->like_count && $user->like_count >= $data['likes']);
}

//...

protected function _matchHasAvatar(array $data, \XF\Entity\User $user)
{
    return $user->user_id && ($user->avatar_date || $user->gravatar);
}

//...

如果所有调用的方法都返回 true,我们的用户就匹配了选定的标准,因此将获得奖杯。

如果在用户标准类中找不到某些方法,XenForo 会调用 isUnknownMatched() 方法,该方法会触发 criteria_user 事件,允许插件开发者添加他们的自定义标准处理程序(参见 "自定义用户/页面标准示例")。

额外的标准数据#

有时,在编写标准模板代码时,你需要访问未通过视图参数传递的额外数据。

这就是 getExtraTemplateData() 方法存在的原因。默认情况下,它包含现有的用户组、语言、样式、时区。

你可以在自定义标准类型类中覆盖此方法。

在自定义标准类型中添加数据#

在你的自定义标准类中覆盖 getExtraTemplateData() 方法:

PHP
public function getExtraTemplateData()
{
    $templateData = parent::getExtraTemplateData();

    $additionalData = [];

    /** @var \XF\Repository\Smilie $smilieRepo */
    $smilieRepo = \XF::repository('XF:Smilie');

    $additionalData['smilies'] = $smilieRepo->findSmiliesForList()->fetch();

    return array_merge($templateData, $additionalData);
}

向现有标准类型添加数据#

你可以使用 criteria_template_data 事件监听器来添加你自己的额外标准数据:

PHP
public static function criteriaTemplateData(array &$templateData)
{
    /** @var \XF\Repository\Smilie $smilieRepo */
    $smilieRepo = \XF::repository('XF:Smilie');

    $templateData['smilies'] = $smilieRepo->findSmiliesForList()->fetch();
}

"helper_criteria" 模板#

每当你作为插件开发者希望让目标用户/管理员能够选择用户/页面/其他插件的标准(甚至一次性选择所有标准)时,你可以简单地使用 helper_criteria

简而言之,helper_criteria 是一个管理模板,它允许在多个地方使用标准类型的复选框界面,而无需复制粘贴相同的代码。

helper_criteria 包含两种类型的宏:*criteria_name*_tabs*criteria_name*_panes,用于每种标准类型。例如:用户标准类型的 user_tabsuser_panes 宏。

标签#

标签用于在模板中区分不同的标准类型:

标准标签演示。

使用标签时,第一个标签通常包含与标准无关的字段/选项。然后是标准标签。

在上图中,第一个标签包含通知的选项。红色框中的前两个标签与用户标准类型相关。最后一个与页面标准类型相关。

helper_criteria 中的标签按标准类型宏分组:

HTML
<xf:macro name="foo_tabs" arg-container="" arg-active="">
    <xf:set var="$tabs">
        <a class="tabs-tab{{ $active == 'foo' ? ' is-active' : '' }}"
            role="tab" tabindex="0" aria-controls="{{ unique_id('criteriaFoo') }}">Foo 标准</a>
        <a class="tabs-tab{{ $active == 'foo_extra' ? ' is-active' : '' }}"
           role="tab" tabindex="0" aria-controls="{{ unique_id('criteriaFooExtra') }}">Foo 标准额外</a>
    </xf:set>
    <xf:if is="$container">
        <div class="tabs" role="tablist">
            {$tabs|raw}
        </div>
    <xf:else />
        {$tabs|raw}
    </xf:if>
</xf:macro>

在上面的代码中,foo 是一个标准类型。它有两个标签,一个用于一般 foo 标准,另一个用于额外的 foo 标准。

面板#

面板只包含标准。

与标签一样,helper_criteria 中的面板按标准类型宏分组:

HTML
<xf:macro name="foo_panes" arg-container="" arg-active="" arg-criteria="!" arg-data="!">
    <xf:set var="$panes">
        <li class="{{ $active == 'foo' ? ' is-active' : '' }}" role="tabpanel" id="{{ unique_id('criteriaFoo') }}">

            <xf:checkboxrow label="标准组 1">
                <xf:option name="foo_criteria[criterion_1_rule][rule]" value="criterion_1_rule" ... />
                <xf:option name="foo_criteria[criterion_2_rule][rule]" value="criterion_2_rule" ... />
            </xf:checkboxrow>

            <xf:checkboxrow label="标准组 2">
                <xf:option name="foo_criteria[criterion_3_rule][rule]" value="criterion_3_rule" ... />
                <xf:option name="foo_criteria[criterion_4_rule][rule]" value="criterion_4_rule" ... />
            </xf:checkboxrow>

        </li>
    </xf:set>

    <xf:if is="$container">
        <ul class="tabPanes">
            {$panes|raw}
        </ul>
    <xf:else />
        {$panes|raw}
    </xf:if>
</xf:macro>

使用 "helper_criteria"#

要使用 "helper_criteria" 功能,你需要包含它的宏。

准备数据#

如果你没有将选定的标准保存在数据库中的某个地方,或者你想要使用的标准类型不需要任何额外数据,则可以跳过本节。

首先,你需要检索保存的选定标准,并从中创建一个标准对象。在本节中,我们将以页面标准为例:

PHP
$savedCriteria = /* 以某种方式检索它... */

// 标准对象
$criteria = $this->app()->criteria('XF:Page', $savedCriteria)->getCriteriaForTemplate();

// 标准额外数据
$criteriaData = $criteria->getExtraTemplateData();

$viewParams = [
    /* ... */
    'criteria' => $criteria,
    'criteriaData' => $criteriaData
];

return $this->view(/* ... */, $viewParams);

包含不带标签的标准#

要包含不带标签的标准,你需要使用 <xf:macro... 标签,并将 arg-container 属性设置为 0

HTML
<xf:macro template="helper_criteria" name="page_panes" arg-container="0" arg-criteria="{$criteria}" arg-data="{$criteriaData}" />

如果你没有保存的标准,你可以将空数组 {{ [] }} 传递给 arg-criteria 属性。别忘了将 page_panes 中的 page 替换为你想要使用的标准类型的名称。

请注意,所有标准都包裹在 <li> 标签中,因此你需要应用一些 CSS 样式(例如 list-style-type: none;)。

带标签#

为了使用标准标签,你需要组织页面。遵循以下示例结构:

HTML
<xf:form ... class="block">
    <div class="block-container">

        <!-- 标签 -->
        <h2 class="block-tabHeader tabs hScroller" data-xf-init="h-scroller tabs" role="tablist">
            <span class="hScroller-scroll">
                <!-- 主标签,包含字段/选项 -->
                <a class="tabs-tab is-active" role="tab" tabindex="0" aria-controls="MAIN_TAB_ID">主标签标题</a>

                <!-- 标准标签 -->
                <xf:macro template="helper_criteria" name="page_tabs" arg-userTabTitle="自定义标签名称(可选)" />
            </span>
        </h2>

        <!-- 面板 -->
        <ul class="block-body tabPanes">
            <!-- 主面板 -->
            <li class="is-active" role="tabpanel" id="MAIN_TAB_ID">
                <!-- 字段和选项 -->
            </li>

            <!-- 标准面板 -->
            <xf:macro template="helper_criteria" name="page_panes"
                arg-criteria="{$criteria}"
                arg-data="{$criteriaData}" />
        </ul>

        <xf:submitrow sticky="true" icon="save" />
    </div>
</xf:form>

再次提醒,如果你没有任何保存的标准,或者你甚至不打算保存它,可以将 {{ [] }} 传递给 arg-criteria 属性。

将自定义标准类型添加到 "helper_criteria"#

如果你想将自定义标准类型添加到 helper_criteira 模板中,你需要创建 helper_criteria 模板的模板修改。

转到 ACP 中的“外观 > 模板修改”,切换到“管理员”选项卡,然后点击“添加模板修改”按钮。

我们希望将我们的标签和面板添加到模板的最底部,因此将“搜索类型”切换为“正则表达式”。

在“查找”字段中输入 /$/

最后,在“替换”字段中添加标签和面板宏代码。示例:

HTML
xf:macro name="foo_tabs" arg-container="" arg-active="">
    <xf:set var="$tabs">
        <a class="tabs-tab{{ $active == 'foo' ? ' is-active' : '' }}"
            role="tab" tabindex="0" aria-controls="{{ unique_id('criteriaFoo') }}">Foo 条件</a>
        <a class="tabs-tab{{ $active == 'foo_extra' ? ' is-active' : '' }}"
           role="tab" tabindex="0" aria-controls="{{ unique_id('criteriaFooExtra') }}">Foo 额外条件</a>
    </xf:set>
    <xf:if is="$container">
        <div class="tabs" role="tablist">
            {$tabs|raw}
        </div>
    <xf:else />
        {$tabs|raw}
    </xf:if>
</xf:macro>

<xf:macro name="foo_panes" arg-container="" arg-active="" arg-criteria="!" arg-data="!">
    <xf:set var="$panes">
        <li class="{{ $active == 'foo' ? ' is-active' : '' }}" role="tabpanel" id="{{ unique_id('criteriaFoo') }}">

            <xf:checkboxrow label="条件组 1">
                <xf:option name="foo_criteria[criterion_1_rule][rule]" value="criterion_1_rule" ... />
                <xf:option name="foo_criteria[criterion_2_rule][rule]" value="criterion_2_rule" ... />
            </xf:checkboxrow>

            <xf:checkboxrow label="条件组 2">
                <xf:option name="foo_criteria[criterion_3_rule][rule]" value="criterion_3_rule" ... />
                <xf:option name="foo_criteria[criterion_4_rule][rule]" value="criterion_4_rule" ... />
            </xf:checkboxrow>

        </li>
    </xf:set>

    <xf:if is="$container">
        <ul class="tabPanes">
            {$panes|raw}
        </ul>
    <xf:else />
        {$panes|raw}
    </xf:if>
</xf:macro>

现在,您可以在任何地方使用您的条件(参见"使用 helper_criteria")。

自定义用户/页面条件示例#

假设我们想创建一个条件,用于检查我们的用户是否在单条消息上获得了 X 个或更多的赞。

由于我们的条件与用户相关,我们将创建一个属于用户条件的条件。

添加模板修改#

首先,我们需要将我们的条件添加到用户条件列表中。转到 ACP 中的“模板修改”页面,选择“Admin”选项卡,然后点击右上角的“添加模板修改”按钮。

警告

如果没有“Admin”选项卡,请确保您已启用开发模式

我们将修改 helper_criteria 模板,因此将其写入“模板”字段。在本示例中,我将使用 likes_on_single_message 作为此模板修改的“修改键”。

我们的条件与消息上的赞有关。这意味着它应该位于“内容和成就”部分。这意味着我们只需找到 <!--[XF:user:content_bottom]--> 并将其替换为以下代码:

HTML
<xf:option name="user_criteria[likes_on_single][rule]" value="likes_on_single" selected="{$criteria.likes_on_single}"  label="单条消息的赞数:">
    <xf:numberbox name="user_criteria[likes_on_single][data][likes]" value="{$criteria.likes_on_single.likes}" size="5" min="0" step="1" />
</xf:option>

$0

从这一刻起,我们可以在创建奖杯、通知和用户组晋升时看到甚至设置我们的条件值。

添加代码事件监听器#

我们已经创建了我们的条件。但对 XenForo 来说,它是未知的,当匹配此类条件时,它将始终返回 false。我们需要告诉 XenForo,当它遇到未知条件时该怎么做。

转到“开发 > 代码事件监听器”页面,然后点击“添加代码事件监听器”按钮。

在“监听事件”字段中选择 criteria_useruser 因为我们的条件属于用户条件)。在“执行回调”字段中,我们应该指定在匹配条件时要调用的类和方法。

如果您还没有在插件根文件夹中创建文件 Listener.php,请创建一个,并在其中添加一个新方法 criteriaUser

PHP
<?php

namespace YOUR_ADDON_ID;

class Listener
{
    public static function criteriaUser($rule, array $data, \XF\Entity\User $user, &$returnValue)
    {

    }
}

您可以将“类”和“方法”字段分别填写为 YOUR_ADDON_ID\ListenercriteriaUser

处理条件#

由于我们的 criteriaUser 方法会为每个未知条件触发,我们需要确保 $rule 等于 likes_on_single(我们在 HTML 标记中指定的规则):

PHP
public static function criteriaUser($rule, array $data, \XF\Entity\User $user, &$returnValue)
{
    switch ($rule)
    {
        case 'likes_on_single':
            /** 处理代码在这里! */
            break;
    }
}

现在,我们需要编写实际检查用户是否有一条消息获得 X 个或更多赞的代码。

这可以通过简单的 SQL 查询轻松实现,该查询从 xf_post 中选择一条记录,其中赞数(likes 列)大于 X,并且 user_id 等于当前匹配的用户 ID。

所以,查询如下:

SQL
SELECT `likes` FROM `xf_post` WHERE `user_id` = ? ORDER BY `likes` DESC LIMIT 1

方法代码如下:

PHP
public static function criteriaUser($rule, array $data, \XF\Entity\User $user, &$returnValue)
{
    switch ($rule)
    {
        case 'likes_on_single':

            // 获取数据库
            $db = \XF::db();

            // 用于选择单个用户帖子最大赞数的数据库查询
            $query = "SELECT `likes` FROM `xf_post` WHERE `user_id` = ? ORDER BY `likes` DESC LIMIT 1";

            // 检索最大赞数
            $likes = $db->fetchOne($query, [$user->user_id]);

            // 检查我们是否有数据库结果(我们期望一个数字)
            if (is_int($likes)) {
                // 如果用户有一条消息获得 X 个或更多赞,则返回 true,否则返回 false
                $returnValue = ($likes >= $data['likes']);
            } else {
                $returnValue = false;
            }

            break;
    }
}

请注意以下几点:

  • 我们使用 $user 变量来检索当前匹配的用户。我们可以使用此变量,因为我们的条件属于用户条件。
  • 我们可以通过 $data 数组访问数据。它包含来自我们在模板修改中添加的字段的数据。我们只添加了一个 <xf:numberbox...,其 name 属性等于 user_criteria[likes_on_single][data][likes]。这就是为什么我们可以在上面的代码中使用 $data['likes']

现在一切都完成了。让我们测试一下!

测试(奖杯)#

创建一个“All for one”奖杯。在“用户条件”选项卡上,设置“单条消息的赞数”字段,例如 5。

接下来,在您的论坛上创建一个测试消息,然后用五个不同的用户点赞五次(或手动设置 likes 列的值)。

然后,转到“工具 > Cron 条目”并通过点击圆圈箭头按钮运行“更新用户奖杯”cron。

"All for one" 奖杯授予通知。

不错!

警告

如果您没有获得“All for one”奖杯,请尝试注销、登录并重新运行“更新用户奖杯”cron。

测试(通知)#

转到“通信 > 通知”并点击“添加通知”按钮。在“用户条件”选项卡上,设置“单条消息的赞数”字段,再次设置为 5。保存通知。

接下来,在您的论坛上创建一个测试消息,然后用五个不同的用户点赞五次(或手动设置 likes 列的值)。

现在,您应该会看到一个通知:

通知演示。

您可以下载基于此示例构建的插件源代码(2.0.10)。

自定义条件类型示例#

想象一下,我们正在创建一个插件(插件 ID:PostsRemover),用于删除所有符合选定条件的帖子。可用条件列表:

  • 帖子至少有 X 个赞
  • 帖子作者的用户名为 X
  • 帖子至少被编辑了 X 次
  • 帖子被编辑的次数不超过 X 次
  • 帖子在 X 之前发布
  • 帖子在 X 之后发布

显然,对于此类条件,我们需要一个新的条件类型:帖子条件。

条件类型类#

我们应该首先在我们的插件的 Criteria 目录中创建一个继承 AbstractCriteria 的新类 Post

PHP
<?php

namespace PostsRemover\Criteria;

use XF\Criteria\AbstractCriteria;

class Post extends AbstractCriteria
{

}

现在我们需要为我们插件支持的所有条件编写代码。在本示例中,我将编写上述列表中前三个条件的代码:

PHP
<?php

namespace PostsRemover\Criteria;

use XF\Criteria\AbstractCriteria;

class Post extends AbstractCriteria
{
    // 帖子至少有 X 个赞
    protected function _matchLikeCount(array $data, \XF\Entity\Post $post)
    {
        return ($post->likes && $post->likes >= $data['likes']);
    }

    // 帖子作者的用户名为 X
    protected function _matchUsername(array $data, \XF\Entity\Post $post)
    {
        return $post->username === $data['name'];
    }

    // 帖子至少被编辑了 X 次
    protected function _matchEditedCount(array $data, \XF\Entity\Post $post)
    {
        return $post->edit_count && $post->edit_count >= $data['count'];
    }

    /* ================ 处理其他条件 ================ */
}

isMatched(...) 方法用于调用我们刚刚创建的 _match 方法,它只接受用户实体,我们需要编写 isMatched()isUnknownMatched()isSpecialMatched() 方法的自定义变体。

由于我们正在创建帖子条件,我们需要创建自己的 isMatchedPost() 方法:

PHP
public function isMatchedPost(\XF\Entity\Post $post)
{
    if (!$this->criteria)
    {
        return $this->matchOnEmpty;
    }

    foreach ($this->criteria AS $criterion)
    {
        $rule = $criterion['rule'];
        $data = $criterion['data'];

        $specialResult = $this->isSpecialMatchedPost($rule, $data, $post);
        if ($specialResult === false)
        {
            return false;
        }
        else if ($specialResult === true)
        {
            continue;
        }

        $method = '_match' . \XF\Util\Php::camelCase($rule);
        if (method_exists($this, $method))
        {
            $result = $this->$method($data, $post);
            if (!$result)
            {
                return false;
            }
        }
        else
        {
            if (!$this->isUnknownMatched($rule, $data, $post))
            {
                return false;
            }
        }
    }

    return true;
}

protected function isSpecialMatchedPost($rule, array $data, \XF\Entity\Post $post)
{
    return null;
}

protected function isUnknownMatchedPost($rule, array $data, \XF\Entity\Post $post)
{
    return false;
}

我们简单地使用了 isMatched(...) 方法代码,将用户实体类型的 $user 变量替换为帖子实体类型的 $post 变量。

由于我们不打算处理特殊和未知条件,我们在 isSpecialMatchedPost 中返回 null,在 isUnknownMathcedPost 中返回 false

模板#

将添加管理路由、编写控制器和在幕后执行其他操作的过程放在一边,让我们直接跳到我们页面的模板代码:

HTML
<xf:title>帖子删除器</xf:title>

<xf:form action="{{ link('posts-remover/remove') }}" ajax="true" class="block">
    <div class="block-container">
        <xf:checkboxrow label="帖子条件">

            <xf:option label="帖子至少有 X 个赞" name="post_criteria[like_count][rule]" value="like_count">
                <xf:numberbox name="post_criteria[like_count][data][likes]" size="5" min="0" step="1" />
            </xf:option>

            <xf:option label="帖子作者的用户名为 X" name="post_criteria[username][rule]" value="username">
                <xf:textbox name="post_criteria[username][data][name]" ac="true" />
            </xf:option>

            <xf:option label="帖子至少被编辑了 X 次" name="post_criteria[edited_count][rule]" value="edited_count">
                <xf:numberbox name="post_criteria[edited_count][data][count]" size="5" min="0" step="1" />
            </xf:option>

        </xf:checkboxrow>

        <!-- 其他条件的模板代码 -->

        <xf:submitrow sticky="true" icon="delete"/>
    </div>
</xf:form>

匹配条件#

在我们页面的控制器中,我们需要创建一个名为 actionRemove 的方法来处理“删除”按钮点击:

PHP
public function actionRemove()
{
}

首先,让我们从页面表单中检索 post_criteria 数组:

PHP
public function actionRemove()
{
    $postCriteriaInput = $this->filter('post_criteria', 'array');
}

其次,我们需要从检索到的页面表单数据中创建一个条件对象:

PHP
public function actionRemove()
{
    $postCriteriaInput = $this->filter('post_criteria', 'array');

    /** @var \PostsRemover\Criteria\Post $postCriteria */
    $postCriteria = $this->app()->criteria('PostsRemover:Post', $postCriteriaInput);
}

默认情况下,我们的帖子将匹配空条件(当未选择任何内容时),这将导致删除所有论坛帖子。为了避免这种情况,我们需要通过 setMatchOnEmpty() 方法手动设置匹配空条件的结果:

PHP
public function actionRemove()
{
    $postCriteriaInput = $this->filter('post_criteria', 'array');

    /** @var \PostsRemover\Criteria\Post $postCriteria */
    $postCriteria = $this->app()->criteria('PostsRemover:Post', $postCriteriaInput);

    $postCriteria->setMatchOnEmpty(false); // 如果未选择任何条件,则不会删除任何内容
}

最后,我们需要将所有论坛帖子与选定的条件进行匹配。如果帖子符合条件,我们将删除它:

PHP
public function actionRemove()
{
    $postCriteriaInput = $this->filter('post_criteria', 'array');

    /** @var \PostsRemover\Criteria\Post $postCriteria */
    $postCriteria = $this->app()->criteria('PostsRemover:Post', $postCriteriaInput);

    $postCriteria->setMatchOnEmpty(false); // 如果未选择任何条件,则不会删除任何内容

    // 获取所有论坛帖子
    $posts = $this->finder('XF:Post')->fetch();

    $deletedCounter = 0;

    /** @var \XF\Entity\Post $post */
    foreach ($posts as $post)
    {
        if ($postCriteria->isMatchedPost($post)) // 检查帖子是否符合选定的条件
        {
            $post->delete(); // 如果帖子符合选定的条件,则删除它
            $deletedCounter++;
        }
    }

    return $this->message('完成!删除了 ' . $deletedCounter . ' 条帖子!');
}

注意

请记住,我们在 XenForo 2.1 以下版本中使用 isMatchedPost($post) 方法!

警告

通常,一次性从数据库中检索所有实体(上面代码中的 $this->finder('XF:Post')->fetch();)是一种不好的做法。可能有数百万条论坛帖子,一次性选择它们将是一个非常漫长的过程,可能会以错误告终。 考虑使用作业系统来处理数十(100+)个数据库项目。

测试#

是时候测试我们的自定义条件类型了!

我在我的测试论坛上创建了三个帖子。第一个帖子获得了 500 个赞,第二个帖子被编辑了 5 次。第三个帖子只是一个普通的、未经处理的帖子,没有赞。

删除前的演示。

现在,在我们的“帖子删除器”ACP 页面上,让我们选择“帖子至少有 X 个赞”(值为 250)和“帖子至少被编辑了 X 次”(值为 5):

选定的条件。

当我点击“删除”按钮时,我看到一条提示消息,告诉我没有删除任何内容。为什么?显然,因为没有帖子同时满足至少有 250 个赞和至少 5 次编辑的条件。

这就是为什么我们需要仅选择第一个条件,然后点击“删除”。这将删除一条有 500 个赞的帖子。接下来,我们需要仅选择最后一个条件并执行删除。有 5 次编辑的帖子将被删除。

结果,只有一条测试帖子在我们的测试中幸存下来:

删除后的演示。

您可以下载基于此示例构建的插件源代码(2.0.10)。您将在“工具”部分下找到“帖子删除器”ACP 页面。