deep-web-solutions/wordpress-framework-utilities

View on GitHub
src/includes/AdminNotices/Handlers/DismissibleNoticesHandler.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace DeepWebSolutions\Framework\Utilities\AdminNotices\Handlers;

use DeepWebSolutions\Framework\Foundations\Actions\Outputtable\OutputFailureException;
use DeepWebSolutions\Framework\Foundations\PluginAwareInterface;
use DeepWebSolutions\Framework\Foundations\PluginAwareTrait;
use DeepWebSolutions\Framework\Foundations\Storage\StoreInterface;
use DeepWebSolutions\Framework\Helpers\Assets;
use DeepWebSolutions\Framework\Helpers\DataTypes\Strings;
use DeepWebSolutions\Framework\Helpers\HooksHelpersAwareInterface;
use DeepWebSolutions\Framework\Utilities\AdminNotices\AdminNoticeInterface;
use DeepWebSolutions\Framework\Utilities\AdminNotices\Notices\DismissibleAdminNotice;
use DeepWebSolutions\Framework\Utilities\Hooks\HooksService;
use DeepWebSolutions\Framework\Utilities\Hooks\HooksServiceRegisterInterface;
use DeepWebSolutions\Framework\Utilities\Hooks\HooksServiceRegisterTrait;
use Psr\Container\ContainerExceptionInterface;

\defined( 'ABSPATH' ) || exit;

/**
 * Handles dismissible notices.
 *
 * @since   1.0.0
 * @version 1.0.0
 * @author  Antonius Hegyes <a.hegyes@deep-web-solutions.com>
 * @package DeepWebSolutions\WP-Framework\Utilities\AdminNotices\Handlers
 */
class DismissibleNoticesHandler extends SimpleNoticesHandler implements PluginAwareInterface, HooksHelpersAwareInterface, HooksServiceRegisterInterface {
    // region TRAITS

    use PluginAwareTrait;
    use HooksServiceRegisterTrait;

    // endregion

    // region INHERITED METHODS

    /**
     * Returns the ID of the handler as the dismissible notice model class. Since there should be only one handler per type
     * of admin notices, this is safe.
     *
     * @since   1.0.0
     * @version 1.0.0
     *
     * @return  string
     */
    public function get_id(): string {
        return DismissibleAdminNotice::class;
    }

    /**
     * {@inheritDoc}
     *
     * @since   1.0.0
     * @version 1.0.0
     */
    public function register_hooks( HooksService $hooks_service ): void {
        $hooks_service->add_action( 'admin_footer', $this, 'output_dismiss_js' );
        $hooks_service->add_action( 'wp_ajax_' . $this->get_hook_tag( 'dismiss_notice' ), $this, 'handle_ajax_dismiss' );
    }

    // endregion

    // region HOOKS

    /**
     * Output the JS that handles the notice dismiss action.
     *
     * @since   1.0.0
     * @version 1.0.0
     */
    public function output_dismiss_js(): void {
        if ( false === $this->has_output ) {
            return;
        }

        \ob_start();

        ?>

        ( function( $ ) {
            $( '.dws-framework-notice-%plugin_slug%' ).on( 'click.wp-dismiss-notice', '.notice-dismiss', function( e ) {
                var notice = $( this ).closest( '.dws-framework-notice' );
                $.ajax( {
                    url: ajaxurl,
                    method: 'POST',
                    data: {
                        action: '%action%',
                        handle: $( notice ).data( 'handle' ),
                        store: $( notice ).data( 'store' ),
                        _wpnonce: '%nonce%'
                    }
                } );
            } );
        } ) ( jQuery );

        <?php

        $js_script = Strings::replace_placeholders(
            \ob_get_clean(),
            array(
                '%plugin_slug%' => \esc_js( $this->get_plugin()->get_plugin_slug() ),
                '%action%'      => \esc_js( $this->get_hook_tag( 'dismiss_notice' ) ),
                '%nonce%'       => \esc_js( \wp_create_nonce( $this->get_plugin()->get_plugin_safe_slug() . '_dismiss_notice' ) ),
            )
        );

        if ( \function_exists( 'wp_print_inline_script_tag' ) ) {
            \wp_print_inline_script_tag( $js_script );
        } else {
            echo "<script type='text/javascript'>$js_script</script>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        }
    }

    /**
     * Intercepts an AJAX request for dismissing a given notice.
     *
     * @since   1.0.0
     * @version 1.0.0
     *
     * @throws  ContainerExceptionInterface     Thrown when an error occurs while dismissing the notice.
     */
    public function handle_ajax_dismiss(): void {
        if ( \is_user_logged_in() && \check_ajax_referer( $this->get_plugin()->get_plugin_safe_slug() . '_dismiss_notice', false, false ) ) {
            $handle = \sanitize_key( $_POST['handle'] ?? '' );
            $store  = \sanitize_key( $_POST['store'] ?? '' );
            $this->dismiss_notice( $handle, $store );
        }

        \wp_die();
    }

    // endregion

    // region METHODS

    /**
     * Marks a notice as dismissed.
     *
     * @since   1.0.0
     * @version 1.0.0
     *
     * @param   string      $handle         The ID of the notice.
     * @param   string      $store_id       The ID of the store the notice is stored in.
     *
     * @throws  ContainerExceptionInterface     Error while retrieving the entries.
     *
     * @return  bool|null
     */
    public function dismiss_notice( string $handle, string $store_id ): ?bool {
        $store  = $this->get_store_entry( $store_id );
        $notice = $this->get_notice( $store_id, $handle );
        if ( ! $store instanceof StoreInterface || ! \is_a( $notice, DismissibleAdminNotice::class ) ) {
            return null;
        }

        $notice->set_dismissed( true );
        $result = $store->update( $notice );

        return \is_null( $result ) || $result;
    }

    /**
     * Returns whether a given notice is dismissed or not.
     *
     * @since   1.0.0
     * @version 1.0.0
     *
     * @param   string      $handle         The ID of the notice.
     * @param   string      $store_id       The ID of the store the notice is stored in.
     *
     * @throws  ContainerExceptionInterface     Error while retrieving the entries.
     *
     * @return  bool|null
     */
    public function is_dismissed_notice( string $handle, string $store_id ): ?bool {
        $notice = $this->get_notice( $store_id, $handle );
        return \is_a( $notice, DismissibleAdminNotice::class ) ? $notice->is_dismissed() : null;
    }

    /**
     * Returns all dismissed notices in a given store.
     *
     * @since   1.0.0
     * @version 1.0.0
     *
     * @param   string  $store_id   The ID of the store to retrieve the dismissed notices from.
     *
     * @throws  ContainerExceptionInterface     Error while retrieving the entries.
     *
     * @return  DismissibleAdminNotice[]
     */
    public function get_dismissed_notices( string $store_id ): array {
        return \array_filter(
            $this->get_notices( $store_id ),
            fn ( DismissibleAdminNotice $notice ) => $notice->is_dismissed()
        );
    }

    // endregion

    // region INHERITED HELPERS

    /**
     * Manipulate the output of the notice to contain the HTML attributes needed for the AJAX dismiss call.
     *
     * @since   1.0.0
     * @version 1.0.0
     *
     * @param   AdminNoticeInterface    $notice     Notice to output.
     * @param   StoreInterface          $store      Store holding the notice.
     *
     * @return  OutputFailureException|null
     */
    protected function output_notice( AdminNoticeInterface $notice, StoreInterface $store ): ?OutputFailureException {
        \ob_start();

        $result = parent::output_notice( $notice, $store );
        if ( ! \is_null( $result ) ) {
            \ob_end_clean();
            return $result;
        }

        $notice_html = \ob_get_clean();
        $notice_html = Strings::replace_placeholders(
            $notice_html,
            array(
                'dws-framework-notice' => 'dws-framework-notice dws-framework-notice-' . \esc_attr( $this->get_plugin()->get_plugin_slug() ),
                'class='               => 'data-store="' . \esc_attr( $store->get_id() ) . '" class=',
            )
        );
        echo $notice_html; // phpcs:ignore

        return null;
    }

    // endregion
}