# Knowledge Base Management Hooks

https://dev.wisecp.com/es/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

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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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 PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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 PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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 PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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 PHP

```php
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.

## Related Articles

- Knowledge Base and Notification Hooks
- [Notification Hooks](https://dev.wisecp.com/en/notification-hooks)
- [How Hooks Work](https://dev.wisecp.com/en/how-hooks-work)
