Knowledge Base Management Hooks

1 views Markdown

The eight hooks over saving and deleting articles and categories: a gate you can stop at each step, then an event that reports it.

Overview

The knowledge base is managed from two sides: the articles and the categories that hold them. Saving and deleting each carry a gate and an event: the gate can stop the write, the event reports it afterwards.

Two distinctions make the work easier. In the save gates a zero id means a new record. The delete gates always receive a list, even when a single record goes.

Reference

Stopping an article being saved

gateknowledgebase.article.save
AdminKnowledgebase before the write

Runs before an article is saved. New records and edits share this gate, and the id tells you which one you have.

Parameters 2
$idintThe id of the article being edited; a zero on a new record.
$categoryintThe target category; a zero means the article has none.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:knowledgebase.article.save', 10, function ($id, $category) {
    // A zero id means a new record: do not allow one without a category.
    if ($id === 0 && $category === 0) return 'A new article needs a category.';

    return null;
});

Following an article being saved

actionknowledgebase.article.saved
AdminKnowledgebase after the write

Runs after the article is saved. This is where you refresh your own search index.

Parameters 2
$articlearrayThe freshly saved record: its id, category, status, privacy and rank.
$isNewboolTrue when it was created, false when an existing one was updated.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:knowledgebase.article.saved', 10, function ($article, $isNew) {
    Acme::reindex('kb', (int) ($article['id'] ?? 0));
});

Stopping an article deletion

gateknowledgebase.article.delete
AdminKnowledgebase always a list

Runs before articles are deleted. Even when one article goes, what you receive is a list.

Parameters 1
$idarrayThe ids about to be deleted. A single deletion still arrives as a one-element array: do not treat it as a number.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:knowledgebase.article.delete', 10, function ($id) {
    // It is an array even for a single deletion.
    foreach ($id as $one)
        if (Acme::isPinned((int) $one)) return 'A pinned article cannot be deleted.';

    return null;
});

Following an article deletion

actionknowledgebase.article.deleted
AdminKnowledgebase after deletion

Runs after an article is deleted. Unlike the gate you get one article here: with several deleted, the hook fires once per article.

Parameters 2
$iintThe id of the deleted article.
$articlearrayThe snapshot from before deletion, title included. The record is gone: take the title from here if you need it.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:knowledgebase.article.deleted', 10, function ($i, $article) {
    // The hook runs per article, so a bulk deletion calls it several times.
    Acme::dropFromIndex('kb', $i);
});

Stopping a category being saved

gateknowledgebase.category.save
AdminKnowledgebase before the write

Runs before a category is saved.

Parameters 2
$idintThe id of the category being edited; a zero on a new record.
$parentintThe parent category; a zero puts it at the root.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:knowledgebase.category.save', 10, function ($id, $parent) {
    // Keep the root level fixed.
    if ($parent === 0 && $id === 0) return 'A new category needs a parent.';

    return null;
});

Following a category being saved

actionknowledgebase.category.saved
AdminKnowledgebase after the write

Runs after the category is saved.

Parameters 2
$categoryarrayThe fresh category record: id, parent, type, status and rank.
$isNewboolTrue when it was newly created.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:knowledgebase.category.saved', 10, function ($category, $isNew) {
    Acme::refreshMenu((int) ($category['id'] ?? 0));
});

Stopping a category deletion

gateknowledgebase.category.delete
AdminKnowledgebase sub-branches included

Runs before categories are deleted. The deletion reaches down the branches: sub-categories you do not see in the list go too.

Parameters 1
$idarrayThe ids about to be deleted, an array even for one. Sub-categories are not listed here but they are deleted too: write your check to cover the branches below.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:knowledgebase.category.delete', 10, function ($id) {
    // The branches are not listed but they go as well: walk the tree yourself.
    foreach ($id as $one)
        if (Acme::branchHasPinned((int) $one)) return 'A pinned article sits in this branch.';

    return null;
});

Following a category deletion

actionknowledgebase.category.deleted
AdminKnowledgebase per parent category

Runs after a category is deleted. The hook fires for the parent; the branches that went with it do not each get their own call.

Parameters 2
$iintThe id of the deleted parent category.
$categoryarrayThe snapshot from before deletion, title included.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:knowledgebase.category.deleted', 10, function ($i, $category) {
    // No separate call arrives for the branches: cover them in your cleanup.
    Acme::dropBranchFromIndex($i);
});

Pitfalls

The delete gate always receives a list

Even for a single article the parameter is a one-element array. A check that expects a number and compares it directly quietly catches nothing.

Category deletion goes down the branch, the hook does not

Deleting a category takes the whole branch beneath it. The delete event, though, fires only for the parent: no separate call arrives for the sub-categories. Write your cleanup to cover the whole tree.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.