# WordPress

> A small WordPress plugin that signs user tokens for the logged-in WordPress user and adds a Nexafon shortcode.

Source: https://docs.nexafone.so/v1/web/wordpress/

The WordPress example is a plugin. It adds the REST route `POST /wp-json/nexafon/v1/user-token`, which signs a user
token for the logged-in WordPress user with the [PHP helper](/v1/server/php/) (`nexafon/nexafon-php`) and accepts only
users with the `nexafon_use` capability sending the REST nonce, and the shortcode `[nexafon_inbox]`, which loads the web
SDK and places the inbox and softphone on any page.

```php title="examples/wordpress/nexafon-example/nexafon-example.php"
<?php

/**
 * Plugin Name: Nexafon example
 * Description: Nexafon's inbox and softphone on any page with the [nexafon_inbox] shortcode, signed in as the WordPress user.
 * Version: 0.1.0
 * Requires PHP: 8.1
 */

declare(strict_types=1);

use Nexafon\Tokens;

if (!defined('ABSPATH')) {
    exit;
}

require_once __DIR__ . '/vendor/autoload.php';

// Settings come from wp-config.php constants or the server's environment, so the secret key never sits in the
// database next to other plugins' options.
function nexafon_example_setting(string $name, string $default = ''): string
{
    if (defined($name)) {
        return (string) constant($name);
    }
    $value = getenv($name);
    return $value === false || $value === '' ? $default : $value;
}

// Only the site's staff act as Nexafon members: customers and subscribers are signed-in users too.
const NEXAFON_EXAMPLE_CAPABILITY = 'nexafon_use';

register_activation_hook(__FILE__, function (): void {
    get_role('administrator')?->add_cap(NEXAFON_EXAMPLE_CAPABILITY);
});

function nexafon_example_can_use(): bool
{
    return current_user_can(NEXAFON_EXAMPLE_CAPABILITY);
}

add_action('rest_api_init', function (): void {
    register_rest_route('nexafon/v1', '/user-token', [
        'methods' => 'POST',
        'permission_callback' => 'nexafon_example_can_use',
        'callback' => function (): WP_REST_Response {
            $user = wp_get_current_user();
            // Nexafon links a new user to the member with this email. WordPress confirms a changed email, but other
            // plugins (WooCommerce's account page, checkout) and admins set it unconfirmed, so the site has to say so.
            $token = Tokens::createUserToken([
                'userId' => 'wp-' . $user->ID,
                'name' => $user->display_name,
                'role' => nexafon_example_setting('NEXAFON_USER_ROLE', 'agent'),
                'email' => $user->user_email,
                'emailVerified' => filter_var(nexafon_example_setting('NEXAFON_EMAILS_VERIFIED'), FILTER_VALIDATE_BOOLEAN),
            ], ['secretKey' => nexafon_example_setting('NEXAFON_SECRET_KEY')]);
            $response = new WP_REST_Response(['token' => $token]);
            $response->header('Cache-Control', 'no-store');
            return $response;
        },
    ]);
});

add_shortcode('nexafon_inbox', function (): string {
    if (!nexafon_example_can_use()) {
        return '';
    }
    $cdn = rtrim(nexafon_example_setting('NEXAFON_EMBED_CDN_URL'), '/');
    wp_enqueue_script('nexafon', $cdn . '/v1/nexafon.js', [], null, ['in_footer' => true]);
    $config = [
        'publishableKey' => nexafon_example_setting('NEXAFON_PUBLISHABLE_KEY'),
        'tokenUrl' => rest_url('nexafon/v1/user-token'),
        'nonce' => wp_create_nonce('wp_rest'),
    ];
    wp_add_inline_script('nexafon', sprintf(
        '(function (c) { Nexafon.init({ publishableKey: c.publishableKey, getUserToken: function () { return fetch(c.tokenUrl, { method: "POST", headers: { "X-WP-Nonce": c.nonce } }).then(function (r) { if (!r.ok) { throw new Error("The token endpoint answered " + r.status) } return r.json() }).then(function (b) { return b.token }) } }) })(%s);',
        wp_json_encode($config)
    ));
    return '<div class="nexafon-example" style="display:grid;gap:16px;grid-template-columns:1fr 340px">'
        . '<nexafon-inbox style="height:640px"></nexafon-inbox><nexafon-softphone></nexafon-softphone></div>';
});
```

Keep the secret key out of the database and the theme: define `NEXAFON_PUBLISHABLE_KEY`, `NEXAFON_SECRET_KEY`,
`NEXAFON_EMBED_CDN_URL` and `NEXAFON_USER_ROLE` in `wp-config.php` or the server’s environment. Add the site’s origin
to the workspace’s embed domains and the publishable key’s allowed origins.

A signed-in WordPress user is not necessarily one of your staff: subscribers and shop customers sign in too. Two rules
keep them out of your workspace:

- **Gate the token route on a capability, not on being signed in.** The plugin checks `nexafon_use`, which activating
  it gives to administrators. Give it to your staff’s roles (`wp cap add editor nexafon_use`), never to subscribers or
  customers.
- **Send `emailVerified: true` only when the site has confirmed the address.** Nexafon matches a user to a member by
  email the first time they sign in, so whoever can set an account’s email to a member’s address can act as that
  member. WordPress confirms an email change made on the profile page, but an admin or a plugin such as WooCommerce
  (account details, checkout) changes it without asking. The plugin sends `true` only when `NEXAFON_EMAILS_VERIFIED`
  is set; without it, Nexafon refuses a user’s first sign-in.

To install it on your own site, copy `nexafon-example/` to `wp-content/plugins/` and run
`composer require nexafon/nexafon-php` in it. The full plugin is `examples/wordpress`.

:::note[Not published yet]
The Nexafon packages are not on npm, Packagist or PyPI yet, and `go get` finds the Go module only once the Nexafon
repository is public and tagged, so installing them by name fails for now. Until then,
[install them from a checkout of the repo](/v1/quickstart/#install-from-a-checkout). The install commands shown here
are the ones to use after release.
:::
