# notificationx/trunk/includes/Core/Rest/Posts.php

NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner &amp; Floating Notification Bar, version trunk. 349 lines.

- Page: https://pluginprobe.com/plugins/notificationx/trunk/code/includes/Core/Rest/Posts.php
- Raw: https://pluginprobe.com/plugins/notificationx/trunk/raw/includes/Core/Rest/Posts.php
- Modified: 2026-08-27T12:11:04+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/notificationx/trunk/code/includes/Core/Rest/Posts.php#L10-L20`.

```php
<?php

namespace NotificationX\Core\Rest;

use FluentForm\Framework\Database\Query\Expression;
use NotificationX\Core\Database;
use NotificationX\Core\PostType;
use NotificationX\Core\REST;
use NotificationX\Extensions\ExtensionFactory;
use NotificationX\Extensions\GlobalFields;
use NotificationX\GetInstance;
use NotificationX\NotificationX;
use WP_REST_Controller;
use WP_REST_Response;
use WP_REST_Server;
use WP_Error;

/**
 * @method static Posts get_instance($args = null)
 */
class Posts extends WP_REST_Controller {
    /**
     * Instance of NotificationX
     *
     * @var NotificationX
     */
    use GetInstance;

    /**
     * Post type.
     *
     * @since 4.7.0
     * @var string
     */
    protected $post_type;

    /**
     * Constructor.
     *
     * @since 4.7.0
     *
     * @param string $post_type Post type.
     */
    public function __construct() {
        $this->namespace = 'notificationx/v1';
        $this->rest_base = 'nx';
        add_action('rest_api_init', [$this, 'register_routes']);
    }

    /**
     * Registers the routes for the objects of the controller.
     *
     * @since 4.7.0
     *
     * @see register_rest_route()
     */
    public function register_routes() {
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base,
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_items'),
                    'permission_callback' => array($this, 'get_items_permissions_check'),
                    // 'args'                => $this->get_collection_params(),
                ),
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array($this, 'create_item'),
                    'permission_callback' => array($this, 'create_item_permissions_check'),
                    // 'args'                => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE),
                ),
                // 'schema' => array($this, 'get_public_item_schema'),
            )
        );

        // $schema = $this->get_item_schema();
        $get_item_args = array(
            'context' => $this->get_context_param(array('default' => 'view')),
        );

        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/(?P<id>[\d]+)',
            array(
                'args' => array(
                    'id' => array(
                        'description' => __('Unique identifier for the object.', 'notificationx'),
                        'type'        => 'integer',
                    ),
                ),
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array($this, 'get_item'),
                    'permission_callback' => array($this, 'get_item_permissions_check'),
                    // 'args'                => $get_item_args,
                ),
                array(
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => array($this, 'update_item'),
                    'permission_callback' => array($this, 'update_item_permissions_check'),
                    // 'args'                => $this->get_endpoint_args_for_item_schema(WP_REST_Server::EDITABLE),
                ),
                array(
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => array($this, 'delete_item'),
                    'permission_callback' => array($this, 'delete_item_permissions_check'),
                    'args'                => array(
                        'force' => array(
                            'type'        => 'boolean',
                            'default'     => false,
                            'description' => __('Whether to bypass Trash and force deletion.', 'notificationx'),
                        ),
                    ),
                ),
                // 'schema' => array($this, 'get_public_item_schema'),
            )
        );
    }

    /**
     * Checks if a given request has access to read posts.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
     */
    public function get_items_permissions_check($request) {
        return current_user_can('read_notificationx');
    }

    /**
     * Checks if a given request has access to read post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
     */
    public function get_item_permissions_check($request) {
        $params = $request->get_params();
        if( !empty( $params['source'] ) ) {
            return current_user_can('read_notificationx');
        }
        return current_user_can('read_notificationx');
    }


    public function get_items($request) {
       $params         = $request->get_params();
       $status         = !empty($params['status']) ? $params['status'] : "all";
       $page           = !empty($params['page']) ? intval( $params['page'] ) : 1;
       $per_page       = !empty($params['per_page']) ? intval( $params['per_page'] ) : 20;
       $search_keyword = !empty($params['s']) ? sanitize_text_field($params['s']) : '';
       $start_from     = ($page - 1) * $per_page;
       $query = Database::get_instance()->query()
                ->from('nx_posts a')
                ->join('nx_stats b', 'b.nx_id', '=', 'a.nx_id')
                ->group_by('a.nx_id')
                ->order_by('a.updated_at', 'DESC')
                ->select('a.*, SUM(b.clicks) clicks, SUM(b.views) views');
        if ($status !== 'all') {
            $query->where('enabled', $status == 'enabled' ? true : false);
        }
        if( $search_keyword ) {
            global $wpdb;
            // esc_like() so `%` and `_` typed into the search box match themselves
            // instead of acting as wildcards; the pattern is bound as a value by the
            // query builder.
            $like = '%' . $wpdb->esc_like( $search_keyword ) . '%';
            $query->where(function($query) use ($like) {
                $query->where('title', 'LIKE', $like)
                      ->orWhere( 'a.nx_id', 'LIKE', $like );
            });
        }
        
        $query->offset($start_from)
                ->limit($per_page);
        $posts = $query->get();
        $posts = PostType::get_instance()->__get_posts( $posts, '*' );

        // Add entries count for popup notifications
        global $wpdb;
        $entries_table = $wpdb->prefix . 'nx_entries';
        // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16.
        $entries_counts = $wpdb->get_results(
            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16.
            "SELECT nx_id, COUNT(*) as entries_count
             FROM {$entries_table}
             WHERE source IN ('popup_notification', 'exit_intent_custom')
             GROUP BY nx_id",
            ARRAY_A
        );
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        // Create a lookup array for entries counts
        $entries_lookup = [];
        foreach ($entries_counts as $entry) {
            $entries_lookup[$entry['nx_id']] = $entry['entries_count'];
        }

        // Add entries count to posts
        foreach ($posts as $key => $post) {
            if ($post['source'] === 'popup_notification') {
                $posts[$key]['entries'] = isset($entries_lookup[$post['nx_id']]) ? $entries_lookup[$post['nx_id']] : 0;
            }
        }
        
        $total_posts = Database::get_instance()->get_post(Database::$table_posts, [], 'count(*) AS total');
        $enabled     = Database::get_instance()->get_post(Database::$table_posts, ['enabled' => true], 'count(*) AS total');
        $disabled    = Database::get_instance()->get_post(Database::$table_posts, ['enabled' => false], 'count(*) AS total');

        return [
            'total'          => $total_posts['total'],
            'enabled'        => $enabled['total'],
            'disabled'       => $disabled['total'],
            'search_keyword' => $search_keyword,
            'posts'          => $posts,
        ];

    }

    /**
     * Retrieves a single post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     */
    public function get_item($request) {
        PostType::get_instance()->set_context( 'edit' );
        return PostType::get_instance()->get_post( absint( $request['id'] ) );
    }

    /**
     * Checks if a given request has access to create a post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
     */
    public function create_item_permissions_check($request) {
        if (!empty($request['id'])) {
            return new WP_Error(
                'rest_post_exists',
                __('Cannot create existing post.', 'notificationx'),
                array('status' => 400)
            );
        }

        return current_user_can('edit_notificationx');
    }

    /**
     * Creates a single post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     */
    public function create_item($request) {
        if (!empty($request['nx_id'])) {
            return new WP_Error(
                'rest_post_exists',
                __('Cannot create existing post.', 'notificationx'),
                array('status' => 400)
            );
        }

        // $prepared_post = $this->prepare_item_for_database($request);

        // if (is_wp_error($prepared_post)) {
        //     return $prepared_post;
        // }

        $params = $request->get_params();
        return PostType::get_instance()->save_post($params);
    }

    /**
     * Checks if a given request has access to update a post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
     */
    public function update_item_permissions_check($request) {
        $params = $request->get_params();
        if( !empty( $params['source'] ) ) {
            return current_user_can('edit_notificationx');
        }
        return current_user_can('edit_notificationx');
    }

    /**
     * Updates a single post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     */
    public function update_item($request) {
        $params = $request->get_params();
        return PostType::get_instance()->save_post($params);
    }

    /**
     * Checks if a given request has access to delete a post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
     */
    public function delete_item_permissions_check($request) {
        // if ($post && !$this->check_delete_permission($post)) {
        //     return new WP_Error(
        //         'rest_cannot_delete',
        //         __('Sorry, you are not allowed to delete this post.'),
        //         array('status' => rest_authorization_required_code())
        //     );
        // }
        return current_user_can('edit_notificationx');
    }

    /**
     * Deletes a single post.
     *
     * @since 4.7.0
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     */
    public function delete_item($request) {
        if(PostType::get_instance()->delete_post($request['id'])){
            wp_send_json_success();
        }
        wp_send_json_error();
    }

}

```
