# Blog and Comment Hooks

https://dev.wisecp.com/es/client-blog-hooks

The seven hooks over blog comments: moderation, editing, deletion, votes and the list.

## Overview

The path of a blog comment lives here: it is written, filtered, listed, moderated and if need be deleted.

Two distinctions to hold from the start. **On a guest comment the author id is zero.** And the moderation gate hands you the **request** while the event hands you the **result**: because pinning is a toggle, the two are not the same.

## Reference

### Stopping a comment moderation

gateclient.blog_comment_moderate

`ClientBlog` the request, not the result

Runs before a comment is moderated.

Parameters 5

$cidintThe comment id.

$actionstringThe operation **requested**: approve, spam or pin. ? Pinning is a **toggle** request: whether it ends as pinning or unpinning depends on the current state. This value states the request, not the result.

$actor_idintThe id of the administrator doing it.

$author_idintThe author of the comment. It is **zero** on a guest comment: test before doing anything tied to a member.

$owner_idintThe article the comment belongs to.

Return 1

string|null**A non-empty text blocks the operation** and is shown as the error. An empty return lets it carry on.

Listener PHP

```php
Hook::add('gate:client.blog_comment_moderate', 10,
    function ($cid, $action, $actor_id, $author_id, $owner_id) {
        // 'pin' is a TOGGLE request: you cannot tell the result from here.
        if ($action === 'spam' && Acme::protectedAuthor($author_id))
            return 'Comments from this author cannot be marked as spam.';

        return null;
    });
```

### Following a comment moderation

actionclient.blog_comment_moderated

`ClientBlog` the result, not the request

Runs after the moderation is applied. Unlike the gate, the value here is the operation that **actually happened**.

Parameters 5

$cidintThe comment id.

$actionstringThe operation that **happened**: approved, spam, pinned or unpinned. Against the three values in the gate there are **four** here: the toggle request has split into two separate results.

$actor_idintThe administrator who did it.

$author_idintThe author of the comment. It is **zero** on a guest comment: test before doing anything tied to a member.

$owner_idintThe article the comment belongs to.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:client.blog_comment_moderated', 10,
    function ($cid, $action, $actor_id, $author_id, $owner_id) {
        // FOUR values here: the toggle has split in two.
        if ($action === 'approved' && $author_id) Acme::thankAuthor($author_id);
    });
```

### Following a comment being edited

actionclient.blog_comment_edited

`ClientBlog` the body is encoded

Runs after a comment is edited by an administrator.

Parameters 5

$cidintThe comment id.

$actor_idintThe administrator who edited it.

$author_idintThe author of the comment. It is **zero** on a guest comment: test before doing anything tied to a member.

$oldstringThe body before the edit. Both arrive **encoded**: showing them directly turns markup into visible text.

$newstringThe body after the edit, as saved.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:client.blog_comment_edited', 10,
    function ($cid, $actor_id, $author_id, $old, $new) {
        // Both bodies arrive ENCODED.
        Acme::auditEdit($cid, $actor_id, html_entity_decode($old), html_entity_decode($new));
    });
```

### Following a comment being deleted

actionclient.blog_comment_deleted

`ClientBlog` the replies go too

Runs after a comment is deleted.

Parameters 5

$cidintThe id of the deleted comment; the record is gone.

$actor_idintThe administrator who deleted it.

$author_idintThe author of the comment. It is **zero** on a guest comment: test before doing anything tied to a member.

$owner_idintThe article the comment belongs to.

$parent_idintThe comment replied to. ? **A zero means a root comment** went, **and its replies went with it** — with no separate call for them. Clear the whole branch in your own store.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:client.blog_comment_deleted', 10,
    function ($cid, $actor_id, $author_id, $owner_id, $parent_id) {
        // On a root comment the replies went too, with no separate call.
        if ($parent_id === 0) Acme::dropBranch($cid);
        else Acme::dropComment($cid);
    });
```

### Following a comment vote

actionclient.blog_comment_voted

`ClientBlog` a toggle

Runs when a comment is marked helpful or the mark is taken back.

Parameters 4

$cidintThe comment voted on.

$viewer_idintThe member voting. This flow is members only, so the value is **always filled**.

$activeboolThe direction: true for a vote, false for taking it back.

$countintThe current total after the vote.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:client.blog_comment_voted', 10,
    function ($cid, $viewer_id, $active, $count) {
        if ($active && $count >= 10) Acme::promoteComment($cid);
    });
```

### Changing a comment body before it is saved

filterclient.blog_comment_message

`ClientBlog` raw text

Runs before a comment is saved. The place to put a filter of your own.

Parameters 2

$messagestringby linkThe comment body, **raw** and not yet encoded. What you write over it is saved.

$ctxarrayby linkContext: the article, the comment replied to, the member id and whether it is a guest. The member id is zero for a guest.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.blog_comment_message', 10, function (&$message, &$ctx) {
    // The body is RAW: encoding happens afterwards.
    $message = Acme::stripLinks($message);
});
```

### Changing the comment list

filterclient.blog_comment_list

`ClientBlog` an admin sees more

Runs before the comments reach the screen. It runs separately for reply lists too.

Parameters 2

$itemsarrayby linkThe comments to show, with their formatted fields.

$ctxarrayby linkContext: whether this is a reply list, which article, and whether an administrator is looking. ? **When an administrator looks, comments awaiting approval are in the list too.** A listener ignoring that counts or exports unpublished content.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.blog_comment_list', 10, function (&$items, &$ctx) {
    // When an administrator looks, pending comments are in the list too.
    if ($ctx['is_admin'] ?? false) return;

    $items = Acme::hideFlagged($items);
});
```

## Pitfalls

> **The gate states the request, the event the result**
> 
> In the moderation gate pinning is a **toggle** request: you cannot tell from there whether it ends as pinning or unpinning. In the event the value arrives already split in two. A rule assuming the result at the gate misjudges an unpinning.

> **Deleting a root comment takes its replies**
> 
> A zero parent id in the delete event means a **root comment** went and every reply beneath it went too. But **no separate call arrives** for those replies: you must clear the whole branch on your side, or orphan records remain.

## Related Articles

- [Customer Site Hooks](https://dev.wisecp.com/en/hooks-on-the-customer-site)
- [Knowledge Base Display Hooks](https://dev.wisecp.com/en/knowledge-base-display-hooks)
- [How Hooks Work](https://dev.wisecp.com/en/how-hooks-work)
