Knowledge Base Management Hooks
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
Runs before an article is saved. New records and edits share this gate, and the id tells you which one you have.
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
Runs after the article is saved. This is where you refresh your own search index.
Hook::add('action:knowledgebase.article.saved', 10, function ($article, $isNew) {
Acme::reindex('kb', (int) ($article['id'] ?? 0));
});Stopping an article deletion
Runs before articles are deleted. Even when one article goes, what you receive is a list.
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
Runs after an article is deleted. Unlike the gate you get one article here: with several deleted, the hook fires once per article.
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
Runs before a category is saved.
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
Runs after the category is saved.
Hook::add('action:knowledgebase.category.saved', 10, function ($category, $isNew) {
Acme::refreshMenu((int) ($category['id'] ?? 0));
});Stopping a category deletion
Runs before categories are deleted. The deletion reaches down the branches: sub-categories you do not see in the list go too.
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
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.
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
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.
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.
Related Articles
- Knowledge Base and Notification Hooks
- Notification Hooks
- How Hooks Work
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.