# booktics/1.0.29/core/extensions/controllers/extension-controller.php

Booktics – Appointment Booking Calendar for Service Businesses, version 1.0.29. 312 lines.

- Page: https://pluginprobe.com/plugins/booktics/1.0.29/code/core/extensions/controllers/extension-controller.php
- Raw: https://pluginprobe.com/plugins/booktics/1.0.29/raw/core/extensions/controllers/extension-controller.php
- Modified: 2026-10-11T10:01:32+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/booktics/1.0.29/code/core/extensions/controllers/extension-controller.php#L10-L20`.

```php
<?php

namespace Booktics\Extensions\Controllers;

use Booktics\Abstracts\Base_Rest_Controller;
use Booktics\Extensions\Plugin_Manager;
use Arraytics\ToolsSdk\PluginManager as SdkPluginManager;
use WP_HTTP_Response;

class Extension_Controller extends Base_Rest_Controller {
    /**
     * Store namespace of api
     *
     * @var string
     */
    protected $namespace = 'booktics/v1';

    /**
     * Store base url
     *
     * @var string
     */
    protected $base = 'extensions';

    /**
     *
     *
     * @return void
     */
    public function register_routes(): void {
        register_rest_route(
            $this->namespace, $this->base, array(
				array(
					'methods'             => \WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array(
						$this,
						'get_items_permissions_check',
					),
				),
            )
        );

        register_rest_route(
            $this->namespace, $this->base, array(
				array(
					'methods'             => \WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array(
						$this,
						'update_item_permissions_check',
					),
				),
            )
        );
    }

    /**
     * Check if a given request has access to get items
     *
     * @param $request
     *
     * @return bool|\WP_Error
     */
    public function get_items_permissions_check( $request ) {
        return current_user_can( 'manage_options' );
    }

    /**
     * Get all extensions
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     */
    public function get_items( $request ) {
        $type = ! empty( $request['type'] ) ? sanitize_key( $request['type'] ) : 'addon';

        $extensions = array_values(
            array_filter(
                Plugin_Manager::extensions(),
                function ( $extension ) use ( $type ) {
                    return $extension['type'] === $type;
                }
            )
        );

        $extensions = array_map(
            function ( $extension ) {
                $extension['status'] = 'install';
                if ( SdkPluginManager::is_installed( $extension['slug'] ) ) {
                    $extension['status'] = 'activate';
                }
                if ( SdkPluginManager::is_activated( $extension['slug'] ) ) {
                    $extension['status'] = 'deactivate';
                }

                return $extension;
            }, $extensions
        );

        return $this->response( $extensions );
    }

    /**
     * Check if a given request has access to get items
     *
     * @param $request
     *
     * @return bool|\WP_Error
     */
    public function update_item_permissions_check( $request ) {
        return current_user_can( 'manage_options' );
    }

    /**
     * Enable or disable extension
     *
     * @param $request
     *
     * @return  WP_HTTP_Response
     */
    public function update_item( $request ) {
        $input_data = json_decode( $request->get_body(), true );
        $name       = $input_data['name'];
        $status     = $input_data['status'];

        $statuses   = array( 'off', 'on', 'install', 'activate', 'deactivate' );
        $extensions = Plugin_Manager::extensions();
        if ( ! $name ) {
            return $this->error( __( 'Please enter extension name', 'booktics' ) );
        }

        if ( ! $status ) {
            return $this->error( __( 'Please enter status', 'booktics' ) );
        }

        if ( ! in_array( $status, $statuses ) ) {
            return $this->error( __( 'Please enter status on/off', 'booktics' ) );
        }

        if ( ! isset( $extensions[ $name ] ) ) {
            return $this->error( __( 'Invalid extension', 'booktics' ) );
        }
        $extension = $extensions[ $name ];

        // Strict, so a "1" or "true" string never counts as agreement.
        $aisentic_consent = 'aisentic' === $name && true === ( $input_data['consent'] ?? null );
        $aisentic_email   = '';

        // The Ask AI setup dialog sends an edited email. Reject a bad one before
        // anything is installed.
        if ( $aisentic_consent && isset( $input_data['email'] ) ) {
            $aisentic_email = sanitize_email( (string) $input_data['email'] );

            if ( ! is_email( $aisentic_email ) ) {
                return $this->error( __( 'Enter a valid email address.', 'booktics' ), 400 );
            }
        }

        if ( 'addon' === $extension['type'] ) {
            $result = null;

            if ( in_array( $status, array( 'install', 'activate' ) ) ) {
                $result = Plugin_Manager::install_plugin( $extension['slug'] );
                if ( $result ) {
                    $result = Plugin_Manager::activate_plugin( $extension['slug'] );
                }
            }

            if ( 'deactivate' === $status && Plugin_Manager::is_activated( $extension['slug'] ) ) {
                $result = Plugin_Manager::deactivate_plugin( $extension['slug'] );
            }

            if ( is_wp_error( $result ) ) {
                return $this->error( __( 'Extension installation error', 'booktics' ) );
            }

            if ( ! $result ) {
                 // translators: %s is the action performed on the extension (install, activate, deactivate, etc.)
                return $this->error( sprintf( __( 'Extension couldn\'t %s', 'booktics' ), $status ) );
            }
        }

        if ( 'plugin' === $extension['type'] ) {
            $result = null;

            // Only people who could do this by hand from the Plugins screen.
            if ( 'install' === $status && ! current_user_can( 'install_plugins' ) ) {
                return $this->error( __( 'You are not allowed to install plugins.', 'booktics' ), 403 );
            }

            if ( 'install' !== $status && ! current_user_can( 'activate_plugins' ) ) {
                return $this->error( __( 'You are not allowed to manage plugins.', 'booktics' ), 403 );
            }

            if ( 'install' === $status ) {
                // Prefer the extension's own download_link (e.g. GitHub release) so a
                // non-wordpress.org URL is not shadowed by the SDK's hardcoded wp.org zip.
                $result = ! empty( $extension['download_link'] )
                    ? Plugin_Manager::install_plugin( $extension['slug'] )
                    : SdkPluginManager::install_plugin( $extension['slug'] );
                if ( $result ) {
                    $result = SdkPluginManager::activate_plugin( $extension['slug'] );
                }
            } elseif ( 'activate' === $status ) {
                $result = SdkPluginManager::activate_plugin( $extension['slug'] );
            } elseif ( 'deactivate' === $status ) {
                $result = SdkPluginManager::deactivate_plugin( $extension['slug'] );
            }

            if ( is_wp_error( $result ) ) {
                return $this->error( __( 'Plugin operation error', 'booktics' ) );
            }

            if ( false === $result ) {
                // translators: %s is the action performed on the plugin (install, activate, deactivate, etc.)
                return $this->error( sprintf( __( 'Plugin couldn\'t %s', 'booktics' ), $status ) );
            }
        }

        $response = Plugin_Manager::extensions()[ $name ];

        // Nothing reaches Aisentic's provider on activation alone. The handshake only
        // runs when the caller ticked the consent box and the plugin really is active.
        if ( 'aisentic' === $name
            && in_array( $status, array( 'install', 'activate' ), true )
            && $aisentic_consent
            && SdkPluginManager::is_activated( $extension['slug'] ) ) {
            // Snapshot before the handshake so the caller can tell a fresh
            // registration (tokens just granted) from re-activating a site that
            // was already connected (no new tokens).
            $was_registered = booktics_aisentic_is_registered();

            $this->register_aisentic_site(
                $aisentic_email,
                sanitize_text_field( (string) ( $input_data['account_name'] ?? '' ) )
            );

            $is_registered = booktics_aisentic_is_registered();

            $response['aisentic_registered'] = $is_registered;

            // True only when this request is what connected the site, so the
            // "150K tokens added" message never fires on a plain re-activation.
            $response['aisentic_newly_registered'] = $is_registered && ! $was_registered;
        }

        $response['status'] = 'install';
        if ( SdkPluginManager::is_installed( $response['slug'] ) ) {
            $response['status'] = 'activate';
        }
        if ( SdkPluginManager::is_activated( $response['slug'] ) ) {
            $response['status'] = 'deactivate';
        }

        return $this->response( $response, __( 'Successfully updated', 'booktics' ) );
    }

    /**
     * Record consent and hand the site identity to Aisentic.
     *
     * Only ever called from behind the consent gate in update_item(). The action is
     * fire and forget: Aisentic owns the provider call, so a provider outage cannot
     * fail the activation the user actually asked for.
     *
     * @param string $email Email the user confirmed in the Ask AI dialog. Empty uses the business email.
     * @param string $name  Account name sent by the Ask AI dialog. Empty uses the business name.
     *
     * @return void
     */
    private function register_aisentic_site( string $email = '', string $name = '' ): void {
        // Older Aisentic builds ship no listener, so the handshake would go nowhere.
        if ( ! class_exists( 'Aisentic\Api\Services\Registration_Service' ) ) {
            return;
        }

        $identity = booktics_aisentic_identity();

        if ( $email ) {
            $identity['email'] = $email;
        }

        if ( $name ) {
            $identity['name'] = $name;
        }

        // Fail closed. An account cannot be minted without a real address.
        if ( empty( $identity['email'] ) ) {
            return;
        }

        update_option(
            'booktics_aisentic_consent', array(
				'agreed'  => true,
				'time'    => gmdate( 'c' ),
				'user_id' => get_current_user_id(),
				'email'   => $identity['email'],
            )
        );

        /**
         * Fires once the admin has opted in to sharing site details with Aisentic.
         *
         * @param string $name     Business name, falling back to the site title.
         * @param string $email    Business email, falling back to the admin email.
         * @param string $site_url Site URL.
         */
        do_action( 'booktics/aisentic/register_site', $identity['name'], $identity['email'], $identity['site_url'] );
    }
}

```
