DTGaraGe | Автомобили, Офроуд, Заваряване, Linux, AI

Регистрирайте безплатен акаунт днес, за да станете член! След като влезете, ще можете да участвате в този сайт, като добавяте свои собствени теми и публикации, както и да се свързвате с други членове чрез вашата лична пощенска кутия!

XenForo 2 XenForo одобрява потребители, но мълчи - ето как му дадох глас

XenForo одобрява потребители, но мълчи - ето как му дадох глас



Киберфордж- автоматичен имейл при одобрение.png

Функцията си стоеше в кода от години. Просто никой не беше дръпнал ключа, който я пуска.


Форумите с ръчно одобрение на регистрации имат тих проблем, за който никой не пише - потребителят чака в опашката, admin-ът го одобрява, и... нищо. Никакъв имейл. Човекът разбира, че е одобрен, само ако сам се сети да опита да влезе отново.

Мислех, че липсва вградена функция. Грешах - XenForo си има готов темплейт
Code:
user_account_approved
и код, който го праща. Проблемът е другаде.




Диагнозата


Копах директно в
Code:
src/XF/ApprovalQueue/User.php
:


Code:
public function actionApprove(\XF\Entity\User $user)
{
    $user->user_state = 'valid';
    $user->save();

    $notify = $this->getInput('notify', $user->user_id);
    if ($notify && $user->email)
    {
        \XF::app()->mailer()->newMail()
            ->setTemplate('user_account_approved', ['user' => $user])
            ->setToUser($user)
            ->send();
    }

    // (съкратено - методът продължава с логване и trigger на completion actions,
    // без връзка с темата тук)
}

Имейлът се праща само ако
Code:
$notify
е true. Проверих темплейта на самата страница за одобрение (
Code:
approval_queue_macros
) - никъде няма checkbox с име
Code:
notify
. Значи полето винаги идва празно.

Кодът е там, работи технически, но никога не се задейства през нормалния интерфейс.




Решението - малък addon, не пипане на core


Правилото е просто - никога не редактираш core файлове на XenForo директно, защото следващият ъпдейт ще ти ги презапише. Вместо да пипам
Code:
ApprovalQueue/User.php
, закачих се на нещо по-стабилно - XenForo-вата event система.

Всеки път, когато Entity се запази, XenForo хвърля събитие
Code:
entity_post_save
. Важно е да е ясно - това събитие гърми при всяко запазване на потребител. Бан, промяна на профил, дори тих ъпдейт на last_activity - всичко минава оттук. Затова проверката не е само "статусът е moderated", а конкретно:


Code:
$entity->isStateChanged('user_state', 'moderated') === 'leave'

Code:
isStateChanged()
сравнява старата и новата стойност в рамките на същия save.
Code:
'leave'
означава точно едно нещо - старата стойност е била
Code:
moderated
, новата вече не е. Това се случва само в мига на одобрение (или отхвърляне), никога при обикновена редакция на профил или бан на вече одобрен потребител. Комбинирано с проверката
Code:
user_state === 'valid'
(за да изключим прехода към
Code:
rejected
), листенърът стреля точно веднъж - при истинско одобрение, нито повече, нито по-рано.




Файловете


addon.json


Code:
{
    "title": "ApprovalMail - Автоматичен имейл при одобрение",
    "description": "Праща имейл автоматично при всяко одобрение на потребител от опашката за модерация, без нужда от checkbox. Използва собствен темплейт и фрази, не пипа core.",
    "version_id": 1000070,
    "version_string": "1.0.0",
    "dev": "AiFlux (Тони Ангелчовски)",
    "dev_url": "https://dtgarage.eu/",
    "require": {
        "XF": [2021570, "2.2.15+"]
    },
    "icon": "icon.png",
    "modifier": "AiFlux",
    "support_url": "https://dtgarage.eu/forums/"
}

Дребна подробност, която се бърка често -
Code:
version_id
не е произволно число. Схемата на XenForo е major + minor(2 цифри) + patch(2 цифри) + state(2 цифри), където
Code:
70
значи stable. Затова 1.0.0 stable е
Code:
1000070
, а изискването
Code:
2021570
се чете като 2.2.15 stable.


Listener/UserListener.php


Code:
<?php

namespace Flux\ApprovalMail\Listener;

use XF\Entity\User;
use XF\Mvc\Entity\Entity;

class UserListener
{
    /**
     * Тригърва се при всяко entity_post_save за XF:User.
     * Праща собствения flux_approvalmail_notice имейл автоматично,
     * когато потребител излиза от опашката за одобрение с валиден статус.
     */
    public static function postSave(Entity $entity)
    {
        if (!$entity instanceof User)
        {
            return;
        }

        $leftModerated = $entity->isStateChanged('user_state', 'moderated') === 'leave';

        if (!$leftModerated || $entity->user_state !== 'valid' || !$entity->email)
        {
            return;
        }

        \XF::app()->mailer()->newMail()
            ->setTemplate('flux_approvalmail_notice', ['user' => $entity])
            ->setToUser($entity)
            ->queue();
    }
}

Забележете
Code:
queue()
, не
Code:
send()
. При
Code:
send()
SMTP връзката се отваря вътре в save-а на User entity-то - ако пощенският сървър се забави, админът виси на екрана за одобрение и чака. С
Code:
queue()
имейлът влиза в
Code:
xf_mail_queue
и тръгва при следващия cron, а одобрението приключва мигновено.


_data/ - декларативната част


Code:
code_event_listeners.xml
закача листенъра. Hint-ът е
Code:
XF:User
, защото
Code:
entity_post_save
се хвърля със
Code:
structure()->shortName
, не с пълното име на класа - сбъркаш ли това, листенърът се вика за всяко Entity в системата:


Code:
<?xml version="1.0" encoding="utf-8"?>
<code_event_listeners>
  <listener event_id="entity_post_save" execute_order="10" callback_class="Flux\ApprovalMail\Listener\UserListener" callback_method="postSave" active="1" hint="XF:User" description="Праща имейл flux_approvalmail_notice когато потребител излезе от опашката за одобрение с валиден статус."/>
</code_event_listeners>

Code:
templates.xml
носи собствения имейл темплейт:


Code:
<?xml version="1.0" encoding="utf-8"?>
<templates>
  <template type="email" title="flux_approvalmail_notice" version_id="1000070" version_string="1.0.0"><![CDATA[<xf:title>{{ phrase('flux_approvalmail_notice_subject', {'board_title': $xf.options.boardTitle}) }}</xf:title>

{{ phrase('flux_approvalmail_notice_body_html', {
    'username': $user.username,
    'board_title': $xf.options.boardTitle
})|raw }}

<p><a href="{{ link('canonical:index') }}">{{ phrase('flux_approvalmail_notice_visit_link', {'board_title': $xf.options.boardTitle}) }}</a></p>
]]></template>
</templates>

Code:
phrases.xml
- целият текст на имейла, на български, в собствени фрази:


Code:
<?xml version="1.0" encoding="utf-8"?>
<phrases>
  <phrase title="flux_approvalmail_notice_subject" version_id="1000070" version_string="1.0.0"><![CDATA[Вратата е отворена - {board_title}]]></phrase>
  <phrase title="flux_approvalmail_notice_body_html" version_id="1000070" version_string="1.0.0"><![CDATA[<p>{username}, портата е отворена.</p><p>Профилът ти в {board_title} мина проверка и вече е активен. Влизаш като редовен - четеш, пишеш, участваш без ограничения.</p>]]></phrase>
  <phrase title="flux_approvalmail_notice_visit_link" version_id="1000070" version_string="1.0.0"><![CDATA[Влез в {board_title}]]></phrase>
</phrases>

Структурата в готовия архив:

Code:
upload/src/addons/Flux/ApprovalMail/
├── addon.json
├── icon.png                          (128x128, по избор)
├── Listener/
│   └── UserListener.php
└── _data/
    ├── code_event_listeners.xml
    ├── templates.xml
    └── phrases.xml

Онова
Code:
upload/
в корена не е украса - точно него търси XenForo при "Install from archive". Сложиш ли
Code:
Flux/
направо в корена на zip-а, инсталаторът не разпознава нищо.




Инсталация - истинският начин, не заобиколка


Първия път го регистрирах през ръчни INSERT-и в базата, директно от терминала. Не защото е "правилният" начин - а защото
Code:
dev mode
беше изключен на продукционния форум в момента на диагностиката, а аз имах нужда от бърз работещ фикс на живата инсталация веднага, не от полирана инсталационна процедура.

За споделяне обаче addon-ът трябва да минава през истинския install flow. С
Code:
_data/
директорията горе инсталацията е две стъпки - Admin CP → Add-ons → Install from archive, посочваш zip-а, готово. Никакво SQL, никакво презареждане на кеш на ръка. XenForo сам чете XML-ите, вкарва листенъра, темплейта и фразите, и си оправя кеша.


Хакът, който спасява продукцията днес, и пакетът, който даваш на други хора, рядко са едно и също нещо.



Защо не пипнах core фразите изобщо


Първата ми версия просто презаписваше
Code:
user_account_approved_subject
и
Code:
user_account_approved_body_html
с български текст. Изглежда логично - това са фразите на готовия темплейт, защо да не ги преведа.

Защото не са мои. Таблицата
Code:
xf_phrase
има уникален ключ по (language_id, title), а тези фрази са собственост на add-on
Code:
XF
. Импортирам ли фрази със същите заглавия, в най-добрия случай ги отнемам от core, в най-лошия инсталацията гърми с duplicate key.

Отделен капан е
Code:
visit_board_html
(бутонът "Visit board") - споделена фраза, ползвана и от activity summary, и от mail container-а. Преведеш ли я, текстът се сменя навсякъде, не само тук.

Затова финалната версия не се бори с чужди фрази - носи собствен темплейт
Code:
flux_approvalmail_notice
със собствени фрази. Нула допир до core, нула
Code:
str_replace
модификации, които се чупят при следващия ъпдейт на XenForo. И като бонус - когато някой друг го инсталира, неговите останали имейли не мърдат.




Проверка


Тествано конкретно на XenForo 2.2.15.

  1. Създай тестов потребител в moderated статус.
  2. Смени му user_state на valid и запази.
  3. Провери xf_mail_queue в базата - трябва да се появи нов ред за този потребител.
  4. Изчакай cron-а или го дръпни ръчно, за да излезе имейлът от опашката.
  5. Ако нищо не идва - виж Admin CP → Tools → Server Error Log за изключения от Flux\ApprovalMail.

На 2.3 event системата може да се държи различно (XenForo сменят детайли по мажорни версии) - не съм тествал там, затова изисквам в
Code:
addon.json
минимум 2.2.15, не по-широк диапазон.




Защо изобщо си струва


Малка функция, но затваря дупка, която повечето admin-и на модерирани форуми дори не знаят, че имат - потребителят чака в тишина, а системата има всичко нужно да му каже "готово", просто никой не е дръпнал ключа.

30-40 реда код, нула пипане на core файлове, изцяло upgrade-safe.


Понякога решението не е да напишеш нова функция - а да намериш защо старата вече съществуваща никога не се е задействала.


Готовият пакет е качен в ресурсите: ApprovalMail 1.0.0 - zip-ът е с upload/ структурата отгоре, инсталира се направо от Admin CP.


Ключови думи: XenForo, addon, ApprovalMail, одобрение на потребители, entity_post_save, code event listener, isStateChanged, mail queue, phrase система, upgrade-safe addon, администрация на форум, PHP, MySQL
Източници: XenForo 2.2.15 source - src/XF/ApprovalQueue/User.php, src/XF/Mvc/Entity/Entity.php, темплейт approval_queue_macros; собствена реализация и тестване на dtgarage.eu


toni@dtgarage:~$ whoami
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ cat LICENSE
🔒 Копирането и препубликуването без разрешение не е позволено.
toni@dtgarage:~$ ./support.sh
☕ Подкрепи DTGaraGe и независимото техническо съдържание
toni@dtgarage:~$ █
 
Last edited:
Top Bottom
🛡️ Този сайт използва аналитични инструменти за подобряване на потребителското изживяване. Никакви лични данни не се събират. С продължаването си в Потока приемаш тази философия на прозрачност и уважение.