# StorePress Admin Utils

StorePress Admin Utils is a comprehensive PHP library for WordPress that simplifies the creation of admin interfaces for plugins. It provides a structured, object-oriented approach to building settings pages, managing plugin updates, handling rollbacks, and displaying administrative notices.

## Core Features

## Settings Framework

The library's cornerstone is its powerful settings framework, which allows developers to create complex settings pages with multiple tabs and a wide variety of field types.

### Field Types

It supports a rich set of field types including `text`, `unit`, `textarea`, `checkbox`, `toggle`,`radio`, `select`, `color`, `number`, and more advanced fields like `toggle` switches and `group` fields.

### Structure

Settings are organized into sections and tabs, providing a clean and intuitive user experience. The framework handles the rendering of the entire settings page, including the navigation tabs, fields, and save/reset buttons.

### Data Management 

It streamlines the process of saving, retrieving, and deleting plugin options from the database. It also includes mechanisms for data sanitization and validation.

## Plugin Updater

StorePress Admin Utils includes a robust module for managing plugin updates from a custom, non-WordPress.org server.

### Custom Update Server

Developers can specify a URL to their own update server in the plugin's header. The library then communicates with this server to check for new versions.

### Update Process

It handles the entire update process, from checking for new versions and displaying update notifications in the WordPress admin to downloading and installing the new plugin package. The `README.md` file provides a clear example of how to set up the server-side endpoint to respond to update requests.

## Plugin Rollback

A key feature is the ability to roll back a plugin to a previous version.

## Simple DI Container

Simple DI Container with singleton and factory support.

### Rollback UI

It adds a "Rollback" link to the plugin's action links on the plugins page. This leads to a dedicated page where the user can select a previous version to install.

### Version Management

The rollback functionality is tied into the update server, which must provide a list of available versions and their corresponding package URLs.

## REST API Integration

The settings framework can automatically expose plugin settings via the WordPress REST API.

### Endpoints

It creates REST API endpoints for fetching settings, allowing for headless WordPress implementations or integration with other applications.

### Configuration

Developers can easily enable or disable this feature and customize the API namespace and version.

## Upgrade & Compatibility Notices

The library provides a class for managing admin notices, which is particularly useful for handling compatibility issues between a primary plugin and its extensions. 
It can display notices in the admin area and on the plugins page if an incompatible version of an extension is detected.

## Usage and Implementation

To use StorePress Admin Utils, developers typically extend the core classes provided by the library, such as `Settings`, `Updater`, and `Upgrade_Notice`. By implementing the abstract methods in these classes, developers can configure the library to suit their plugin's specific needs.

## Installation

```shell
composer require storepress/admin-utils
```

## Usage

### Plugin entry file `plugin-example.php`

```php
<?php
  /**
  Plugin Name: Plugin Example
  Plugin URI: https://storepress.com/plugins/plugin-example/
  Description: Example Plugin.
  Author: Emran Ahmed
  Version: 1.0.0
  Tested up to: 6.3
  Author URI: https://storepress.com/emran/
  Update URI: https://update.example.com/
  */
  
  defined( 'ABSPATH' ) || die( 'Keep Silent' );
  
  use StorePress\Example\Plugin;
  
  define('EXAMPLE_PLUGIN_FILE', __FILE__);
  
  // Include the main class.
  if ( ! class_exists( Plugin::class, false ) ) {
    require_once __DIR__ . '/includes/Plugin.php';
  }
  
  /**
   * Plugin class init
   *
   * @return Plugin|PluginPro
   */
  function plugin_example() {
  
    if ( function_exists( 'plugin_example_pro') ){
      return plugin_example_pro();
    }
  
    return Plugin::instance();
  }
  
  add_action( 'plugins_loaded', 'plugin_example' );
```

### Sample `includes/functions.php` file

```php
<?php
	namespace StorePress\Example;
	
	use StorePress\Example\Containers\Container;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	function get_container(): Container {
		return Container::instance();
	}
	
	function get_plugin_file(): string {
		return constant( 'EXAMPLE_PLUGIN_FILE' );
	}
	
	function get_pro_plugin_file(): string {
		
		if( defined( 'EXAMPLE_PLUGIN_PRO_FILE' ) ) {
			return constant( 'EXAMPLE_PLUGIN_PRO_FILE' );
		}
		
		return 'example-plugin-pro/example-plugin-pro.php';
	}


```

### Sample `includes/Plugin.php` class file

```php
<?php
	/**
	 * Plugin Initialization Class File.
	 *
	 * Handles plugin bootstrap, dependency loading, and service provider initialization.
	 *
	 * @package    StorePress/Example
	 * @since      1.0.0
	 * @version    1.0.0
	 */

	declare( strict_types=1 );

	namespace StorePress\Example;

	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\Example\ServiceProviders\ProCompatibilityServiceProvider;
	use StorePress\Example\ServiceProviders\ServiceProviders;
	use StorePress\Example\ServiceProviders\SettingsServiceProvider;
	use StorePress\Example\ServiceProviders\DeactivationServiceProvider;
	use StorePress\Example\ServiceProviders\UpdaterServiceProvider;
	
	/**
	 * Plugin Initialization Class.
	 *
	 * Bootstraps the plugin by loading vendor autoloaders, registering the service provider,
	 * and initializing hooks. Uses the singleton pattern to ensure only one instance exists.
	 *
	 * @name Plugin
	 */
	class Plugin {

		// =====================================================================
		// Properties
		// =====================================================================

		/**
		 * Plugin file path.
		 *
		 * Stores the absolute path to the main plugin file.
		 *
		 * @var string
		 */
		protected string $plugin_file;

		// =====================================================================
		// Singleton Instance
		// =====================================================================

		/**
		 * Return singleton instance of the Init class.
		 *
		 * The instance will be created if it does not exist yet.
		 *
		 * @param string $plugin_file The absolute path to the main plugin file.
		 *
		 * @return self The singleton instance.
		 *
		 * @since 1.0.0
		 *
		 * @example
		 * // Get or create the singleton instance.
		 * $init = Init::instance( __FILE__ );
		 *
		 * @example
		 * // Access from global function.
		 * function plugin_b(): Init {
		 *     return Init::instance( __FILE__ );
		 * }
		 */
		public static function instance(): self {
			static $instance = null;
			return $instance ??= new self();
		}

		// =====================================================================
		// Constructor
		// =====================================================================

		/**
		 * Initialize the plugin.
		 *
		 * Loads vendor autoloaders and functions, registers the service provider,
		 * boots the service provider, and runs initialization hooks.
		 *
		 */
		public function __construct() {

			$this->includes();

			$this->hooks();
			
			$this->init();
		}

		// =====================================================================
		// File Loading Methods
		// =====================================================================

		/**
		 * Load required files.
		 *
		 * Includes the vendor autoload_packages.php file if it exists and the
		 * plugin functions.php file for utility functions.
		 *
		 * @return void
		 *
		 * @since 1.0.0
		 *
		 * @see Init::get_plugin_file() Gets the plugin file path.
		 *
		 * @example
		 * // Files loaded:
		 * // - vendor/autoload_packages.php (if exists)
		 * // - includes/functions.php
		 */
		public function includes(): void {
			$vendor_path = untrailingslashit( plugin_dir_path( $this->get_plugin_file() ) ) . '/vendor';

			if ( file_exists( $vendor_path . '/autoload_packages.php' ) ) {
				require_once $vendor_path . '/autoload_packages.php';
			}

			require_once __DIR__ . '/functions.php';
		}

		// =====================================================================
		// Getter Methods
		// =====================================================================

		/**
		 * Get the plugin file path.
		 *
		 * Returns the absolute path to the main plugin file, useful for
		 * deriving plugin directory, URL, basename, and version.
		 *
		 * @return string The absolute path to the main plugin file.
		 *
		 * @since 1.0.0
		 *
		 * @example
		 * // Get the plugin file path.
		 * $file = $init->get_plugin_file();
		 * // Returns: /path/to/wp-content/plugins/plugin-b/plugin-b.php
		 *
		 * @example
		 * // Derive plugin directory.
		 * $dir = plugin_dir_path( $init->get_plugin_file() );
		 *
		 * @example
		 * // Derive plugin URL.
		 * $url = plugin_dir_url( $init->get_plugin_file() );
		 *
		 * @example
		 * // Get plugin basename.
		 * $basename = plugin_basename( $init->get_plugin_file() );
		 * // Returns: plugin-b/plugin-b.php
		 */
		public function get_plugin_file(): string {
			return constant( 'EXAMPLE_PLUGIN_FILE' );
		}

		// =====================================================================
		// Hook Methods
		// =====================================================================

		/**
		 * Register WordPress hooks.
		 *
		 * Hook into WordPress actions and filters for plugin functionality.
		 * This method is intended to be extended with additional hook registrations
		 * as the plugin grows.
		 *
		 * @return void
		 *
		 * @since 1.0.0
		 *
		 * @example
		 * // Override in subclass or extend with hooks:
		 * public function hooks(): void {
		 *     add_action( 'init', array( $this, 'register_post_types' ) );
		 *     add_filter( 'plugin_action_links', array( $this, 'add_settings_link' ) );
		 * }
		 */
		public function hooks(): void {}

		// =====================================================================
		// Service Container Methods
		// =====================================================================
		
		public function get_service_providers(): array {
			return array(
				UpdaterServiceProvider::class=>UpdaterServiceProvider::class,
				DeactivationServiceProvider::class=>DeactivationServiceProvider::class,
				SettingsServiceProvider::class=>SettingsServiceProvider::class,
				ProCompatibilityServiceProvider::class=>ProCompatibilityServiceProvider::class
			);
		}

		public function service_providers(): ServiceProviders {
			return ServiceProviders::instance( $this->get_service_providers() );
		}
	}
```

### Sample `ServiceProviders` class

```php
<?php
	
	declare( strict_types=1 );
	
	namespace StorePress\Example\ServiceProviders;
	
	use StorePress\AdminUtils\Traits\SingletonTrait;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	class ServiceProviders {
		
		use SingletonTrait;
		
		protected $service_providers = array();
		
		public function __construct( $service_providers ) {
			$this->service_providers = $service_providers;
		    $this->init();
		}
		
		public function get_providers(): array {
			return $this->service_providers;
		}
		
		private function init(): void {
			$providers = $this->get_providers();
			
			
			foreach ( $providers as $provider ) {
				$provider::instance();
				$provider::instance()->register();
				$provider::instance()->boot();
			}
			
		}
	}
```

### `AbstractSettings` class usages example

```php
<?php
	
	namespace StorePress\Example\Integrations;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\AdminUtils\Abstracts\AbstractSettings;
	
	class AdminPage extends AbstractSettings {
		
		public function settings_id(): string {
			return 'example-plugin-settings';
		}
		
		public function get_default_sidebar(): void {
			echo 'Hello from default sidebar';
			
			// include_once $this->get_templates_path() . '/sidebar.php'
			
		}
		
		public function localize_strings(): array {
			return array(
				'unsaved_warning_text'            => 'The changes you made will be lost if you navigate away from this page.',
				'reset_warning_text'              => 'Are you sure to reset?',
				'reset_button_text'               => 'Reset All',
				'settings_link_text'              => 'Settings',
				'settings_error_message_text'     => 'Settings not saved',
				'settings_updated_message_text'   => 'Settings Saved',
				'settings_deleted_message_text'   => 'Settings Reset',
				'settings_tab_not_available_text' => 'Settings Tab is not available.',
			);
		}
		

		// Adding custom scripts.
    public function enqueue_scripts(): void {
        parent::enqueue_scripts();
        if ( $this->has_field_type( 'wc-enhanced-select' ) ) {
            wp_enqueue_style( 'woocommerce_admin_styles' );
            wp_enqueue_script( 'wc-enhanced-select' );
        }
    }
    
    // Adding Custom TASK: 
    public function get_custom_action_uri(): string {
       return wp_nonce_url( $this->get_settings_uri( array( 'action' => 'custom-action' ) ), $this->get_nonce_action() );
    }
    
    // Task: 02
    public function process_actions($current_action): void{
    
        parent::process_actions($current_action);
      
        if ( 'custom-action' === $current_action ) {
          $this->process_action_custom();
        }
    }
    
    // Task: 03
    public function process_action_custom(): void{
        check_admin_referer( $this->get_nonce_action() );
        
        
        
        // Process your task.
        
        
        
        wp_safe_redirect( $this->get_action_uri( array( 'message' => 'custom-action-done' ) ) ); 
        exit;
    }
    
    // Task: 04
    public function settings_messages(): void{
      
      parent::settings_messages();
      
      $message = $this->get_message_query_arg_value();
      
      if ( 'custom-action-done' === $message ) {
          $this->add_settings_message( 'Custom action done successfully.' );
      }
      
      if ( 'custom-action-fail' === $message ) {
          $this->add_settings_message( 'Custom action failed.', 'error' );
      }
    }
	}
```

```php
<?php
	
	namespace StorePress\Example\Services;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\AdminUtils\Traits\SingletonTrait;
	use StorePress\Example\Integrations\AdminPage;
	
	/**
	 * Admin Menu Class.
	 *
	 * @name Settings
	 */
	
	class Settings extends AdminPage {
		
		use SingletonTrait;
		
		public function add_settings(): array {
			return array(
				'general' => 'General',
				
				//'pure' => 'Pure',
				'basic'   => array(
					'name'     => 'Basic',
					'sidebar'  => 25,
				),
				
				'advance' => array(
					'name'     => 'Advanced',
					'icon'     => 'dashicons dashicons-analytics',
					'sidebar'  => false,
					'hidden'   => false,
					'external' => false,
				),
				'rest' => 'Rest',
			);
		}
		
		// Naming Convention: add_<TAB ID>_settings_page()
    public function add_basic_settings_page() {
        echo 'custom page ui';
    }
		
		public function add_general_settings_fields() {
			return array(
				array(
					'type'        => 'section',
					'title'       => 'Section title',
					'description' => 'Section description',
				),
				
				array(
					'id'          => 'field-text-mn',
					'type'        => 'text',
					'title'       => 'Input Type text',
					'description' => 'Input Description',
					'placeholder' => 'Placeholder',
					'default'     => 'text field default',
					'suffix'      => 'px',
					'required'    => true,
					'add_tag'     => array('PRO'),
					// 'condition' => array('selector'=>$this->get_field_selector('grps')),
					// 'html_datalist' => array('yes','no'),
					// 'show_in_rest'    => array( 'name' => 'custom_rest_id' ),
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				
				array(
					'id'          => 'grps',
					'type'        => 'toggle',
					'title'       => 'Show Grpups',
					'default'     => 'no',
					'tooltip'      => 'Textarea Help tooltip',
					'required'    => true,
					'add_tag'    => array('NEW', '#d63639'),
					// 'options'=>array('key'=> 'value','key2'=> 'value2')
				),
				
				
				array(
					'condition'=>array('selector'=>$this->get_field_selector('grps')),
					'id'          => 'input_group',
					'type'        => 'group',
					// 'show_in_rest'        => false,
					'title'       => 'Input text 01 general',
					'description' => 'Input desc of 01',
					'tooltip'      => 'Input Group Help Tooltip',
					'fields'      => array(
						
						array(
							'id'          => 'inputaxxx',
							'type'        => 'text',
							'title'       => 'Single Toggle',
							'placeholder' => 'Abcd',
							'default'     => 'yes',
							'html_datalist'=>array('yes','no'),
							'class'=>array('large-text'),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						
						array(
							'id'          => 'field-color',
							'type'        => 'color',
							'title'       => 'Input Type Color',
							'description' => 'Input Type Color',
							'placeholder' => 'Placeholder',
							'default'     => '#ffccff',
							'html_datalist'=>array('#dddddd','#eeeeee'),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
							// 'required'    => true,
							//'class'       => array( 'large-text', 'code', 'custom-class' )
						),
						
						array(
							'id'          => 'inputakk',
							'type'        => 'unit',
							'title'       => 'Single Unit',
							'placeholder' => '',
							'default'     => '20px',
							'units'=>     array('%','px'),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						
						array(
							'id'          => 'inputas',
							'type'        => 'toggle',
							'title'       => 'Single Toggle',
							'placeholder' => 'Abcd',
							'default'     => 'no',
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
							//'options'=>array('X', 'Y', 'Z'),
						),
						array(
							'id'          => 'input2',
							'type'        => 'checkbox',
							'title'       => 'Single Checkbox',
							'placeholder' => 'Abcd',
							'default'     => 'yes',
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						array(
							//'show_in_rest'    => false,
							'condition'=>array('selector'=>$this->get_group_field_selector('input_group','inputas')),
							'id'                => 'input5',
							'type'              => 'number',
							'title'             => 'Width WW',
							'description'       => 'Input desc of 01',
							'placeholder'       => 'Abcd',
							'default'           => '100',
							'suffix'            => 'x',
							'sanitize_callback' => 'absint',
							'html_attributes'   => array( 'min' => 10 ),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						array(
							//'show_in_rest'    => false,
							'id'              => 'input45',
							'type'            => 'textarea',
							'title'           => 'Width',
							'description'     => 'Input desc of 01',
							'placeholder'     => 'Abcd',
							'default'         => '100',
							'suffix'          => 'x',
							'html_attributes' => array( 'min' => 10 ),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						
						
						array(
							'id'              => 'inputse2xx',
							'type'            => 'select',
							'title'           => 'Int value',
							'description'     => 'Input desc of 01<code>rxxx</code>',
							// 'default'     => array( 'home3', 'home1' ),
							// 'multiple'    => true,
							'default'         => '2',
							'class'=>array('x', 'y'),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
							'html_attributes' => array( 'data-demo' => true ),
							'options'         => array(
								'1' => 'Home One',
								'2' => 'Home Two',
								'3' => 'Home three',
							)
						),
						
						array(
							'id'          => 'input_wi',
							'type'        => 'radio',
							'title'       => 'Width X',
							'placeholder' => 'Abcd',
							'default'     => 'y',
							'options'     => array(
								'x' => 'Home X',
								'y' => 'Home Y',
								'z' => 'Home Z',
							),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						array(
							'id'      => 'input_wx',
							'type'    => 'checkbox',
							'title'   => 'Multi Checkbox',
							'default' => array( 'y', 'z' ),
							'options' => array(
								'x' => 'Home X',
								'y' => 'Home Y',
								'z' => 'Home Z',
							),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
						
						array(
							'id'      => 'input_wewdsf',
							'type'    => 'toggle',
							'title'   => 'Multi Toggle Checkbox',
							'default' => array( 'y', 'z' ),
							'options' => array(
								'x' => 'Home X',
								'y' => 'Home Y',
								'z' => 'Home Z',
							),
							'tooltip'      => 'Textarea Help tooltip',
							'required'    => true,
						),
					),
				),
				
				////////////
				array(
					'id'           => 'field-password',
					'type'         => 'password',
					'title'        => 'Input Type Password',
					'description'  => 'Input Description',
					'placeholder'  => 'Placeholder',
					'default'      => 'text field default',
					//'suffix'       => 'px',
					'required'     => true,
					'tooltip'      => 'Password Help tooltip',
					'condition'=>array('selector'=>$this->get_field_selector('field-textarea')),
					
					//'show_in_rest' => array( 'name' => 'custom_rest_id' ),
					//'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'           => 'field-textarea',
					'type'         => 'textarea',
					'title'        => 'Input Type text',
					'description'  => 'Input Description',
					'placeholder'  => 'Placeholder',
					'default'      => '',
					'suffix'       => 'px',
					'required'     => true,
					'tooltip'      => 'Textarea Help tooltip',
					//'show_in_rest' => array( 'name' => 'custom_rest_id' ),
					//'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-text',
					'type'        => 'text',
					'title'       => 'Input Type text',
					'description' => 'Input Description',
					'placeholder' => 'Placeholder',
					'default'     => 'text field default',
					'suffix'      => 'px',
					'required'    => true,
					// 'html_datalist'=>array('yes','no'),
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-number',
					'type'        => 'number',
					'title'       => 'Input Type Number',
					'description' => 'Input Type Number',
					'placeholder' => '',
					'default'     => '1',
					'suffix'      => 'px',
					// 'required'    => true,
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				///////
				
				array(
					'id'          => 'field-color',
					'type'        => 'color',
					'title'       => 'Input Type Color',
					'description' => 'Input Type Color',
					'placeholder' => 'Placeholder',
					'default'     => '#ffccff',
					'html_datalist'=>array('#dddddd','#eeeeee'),
					// 'required'    => true,
					// 'show_in_rest'    => 'fieldColor',
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-radio',
					'type'        => 'radio',
					'title'       => 'Input Type Radio',
					'description' => 'Input Type Radio',
					'placeholder' => 'Placeholder',
					'default'     => 'y',
					'options'     => array(
						'x' => 'Home X',
						'y' => 'Home Y',
						'z' => 'Home Z',
					)
					// 'required'    => true,
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				// license
				array(
					'id'          => 'license',
					'type'        => 'code',
					'title'       => 'License',
					'private'     => true,
					'show_in_rest' => false,
					'description' => 'Input for license',
					'placeholder' => 'xxxx-xxxx-xxxx',
					//'class'       => 'code'
				),
				
				array(
					'id'          => 'input-single-check',
					'type'        => 'checkbox',
					'title'       => 'Single Checkbox Full',
					'description' => 'Single Checkbox Full desc',
					'default'     => 'yes',
					'full_width'=>true,
					'add_tag'    => array('NEW', '#d63639'),
				),
				
				array(
					'id'          => 'input-single-toggle',
					'type'        => 'toggle',
					'title'       => 'Single Toggle Full',
					'description' => 'Single Checkbox Full desc',
					'default'     => 'yes',
					// 'full_width'=>true
				),
				
				array(
					'id'      => 'input-multi-check',
					'type'    => 'checkbox',
					'title'   => 'Multiple Checkbox Full',
					'description' => 'Multiple Checkbox  Full desc',
					'default' => 'yes',
					'options' => array(
						'x'     => 'Home X',
						'y'     => 'Home Y',
						'new'   => array(
							'label'       => 'New',
							'description' => 'New Item',
						),
						'z'     => 'Home Z',
						'alpha' => 'Alpha',
						'yes' => 'YES',
					
					),
				),
				
				array(
					'id'      => 'input-multi-check-toggle',
					'type'    => 'toggle',
					'title'   => 'Multiple Toggle Full',
					'default' => 'yes',
					'options' => array(
						'x'     => 'Home X',
						'new'   => array(
							'label'       => 'New',
							'description' => 'New Item',
						),
						'y'     => 'Home Y',
						'z'     => 'Home Z',
						'alpha' => 'Alpha',
					)
				),
				
				array(
					'id'          => 'inputr',
					'type'        => 'radio',
					'title'       => 'Input text 01 general',
					'description' => 'Input desc of 01',
					'default'     => 'home2',
					'options'     => array(
						'home'  => 'Home One',
						'home2' => 'Home Twos',
					)
				),
				
				array(
					'id'          => 'inputc',
					'type'        => 'checkbox',
					'title'       => 'Input text 01 general single checkbox',
					'description' => 'Input desc of 01',
					'default'     => 'home3',
					'options'     => array(
						'home1' => 'Home One',
						'home3' => 'Home 3',
						'home2' => 'Home 2',
					)
				),
				
				array(
					'id'              => 'inputse',
					'type'            => 'wc-enhanced-select',
					'title'           => '2 Input text 01 general single selectbox',
					'description'     => 'Input desc of 01<code>xxx</code>',
					// 'default'     => array( 'home3', 'home1' ),
					// 'multiple'    => true,
					'default'         => 'home3',
					'class'=>array('x', 'y'),
					'html_attributes' => array( 'data-demo' => true ),
					'options'         => array(
						'home1' => 'Home One',
						'home3' => 'Home 3',
						'home2' => 'Home 2',
					)
				),
				
				array(
					'id'              => 'inputse2xx',
					'type'            => 'wc-enhanced-select',
					'title'           => 'Int value',
					'description'     => 'Input desc of 01<code>rxxx</code>',
					// 'default'     => array( 'home3', 'home1' ),
					// 'multiple'    => true,
					'default'         => '2',
					'class'=>array('x', 'y'),
					'html_attributes' => array( 'data-demo' => true ),
					'options'         => array(
						'1' => 'Home One',
						'2' => 'Home Two',
						'3' => 'Home three',
					)
				),
				
				array(
					'id'          => 'input2',
					'type'        => 'text',
					'title'       => 'Input text 02',
					'description' => 'Input desc of 02',
					'default'     => '',
					'placeholder' => 'Abcd 02'
				),
				
				array(
					'id'          => 'inputunit',
					'type'        => 'unit',
					'title'       => 'Input text unit',
					'description' => 'Input desc of unit',
					'default'     => '10px',
					'html_attributes' => array( 'min' => 0, 'max'=>100, 'step'=>5 ),
					'units'=>array('px', '%', 'em', 'rem'),
					'condition'=>array('selector'=>$this->get_field_selector('input2')),
				),
				
				array(
					'type'        => 'section',
					'title'       => 'Section 02',
					'description' => 'Section of 02',
				),
			);
		}
		
		public function add_rest_settings_fields() {
			return array(
				array(
					'type'        => 'section',
					'title'       => 'Section rest',
					'description' => 'Section description',
				),
				
				array(
					'id'           => 'field-textarea-x',
					'type'         => 'textarea',
					'title'        => 'Input Type text',
					'description'  => 'Input Description',
					'placeholder'  => 'Placeholder',
					'default'      => 'text field default',
					'suffix'       => 'px',
					'required'     => true,
					'add_tag'    => array('NEW'),
					// 'show_in_rest' => array( 'name' => 'custom_rest_id' ),
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-text-x',
					'type'        => 'text',
					'title'       => 'Input Type text',
					'description' => 'Input Description',
					'placeholder' => 'Placeholder',
					// 'default'     => 'text field default',
					'suffix'      => 'px',
					// 'required'    => true,
					'add_tag'    => array('PRO', '#d63639'),
					// 'show_in_rest'    => array('name'=>'fieldTextX'),
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-text-x-dep',
					'type'        => 'unit',
					'title'       => 'Input Type Unit extra comp',
					'description' => 'Input Type Number',
					'placeholder' => '',
					'default'     => '10px',
					'html_attributes' => array( 'min' => 0, 'max'=>100, 'step'=>5 ),
					'units'=>array('px', '%', 'em', 'rem'),
					'condition'=>array('selector'=> $this->get_field_selector('field-text-x')),
					// 'required'    => true,
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-text-x-dep-text',
					'type'        => 'text',
					'title'       => 'Input Type Unit extra comp 2',
					'description' => 'Input Type Number',
					'placeholder' => '',
					// 'default'     => '',
					'condition'=>array('selector'=>$this->get_field_selector('field-text-x')),
					// 'required'    => true,
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				
				
				array(
					'id'          => 'field-number-x',
					'type'        => 'number',
					'title'       => 'Input Type Number',
					'description' => 'Input Type Number',
					'placeholder' => '',
					'default'     => '1',
					'suffix'      => 'px',
					'add_tag'    => array('BETA', '#000'),
					// 'required'    => true,
					'show_in_rest'    => array('name'=>'fieldNumberX'),
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-extra2-x',
					'type'        => 'number',
					'title'       => 'Input Type Number extra',
					'description' => 'Input Type Number',
					'placeholder' => '',
					'default'     => '1',
					'suffix'      => 'px',
					'show_in_rest'    => 'fieldExtraX',
					// 'required'    => true,
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
				array(
					'id'          => 'field-extra2-unit',
					'type'        => 'unit',
					'title'       => 'Input Type Unit extra',
					'description' => 'Input Type Number',
					'placeholder' => '',
					'default'     => '10px',
					'show_in_rest'    => 'fieldExtra2Unit',
					'html_attributes' => array( 'min' => 0, 'max'=>100, 'step'=>5 ),
					'units'=>array('px', '%', 'em', 'rem'),
					// 'required'    => true,
					// 'class'       => array( 'large-text', 'code', 'custom-class' )
				),
				
			);
		}
	}
```

### `SettingsServiceProvider` class example.

```php
<?php
	
	declare( strict_types=1 );

	namespace StorePress\Example\ServiceProviders;

	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\AdminUtils\Abstracts\AbstractServiceProvider;
	use StorePress\AdminUtils\Traits\SingletonTrait;
	use StorePress\Example\Containers\Container;
	use StorePress\Example\Services\Settings;
	
	class SettingsServiceProvider extends AbstractServiceProvider {
		
		use SingletonTrait;
		
		public function get_container(): Container {
			return Container::instance();
		}
		
		public function register(): void {
			
			$this->get_container()->register(
				Settings::class,
				function () {
					return Settings::instance();
				}
			);
		}
		
		public function boot(): void {
			$this->get_container()->get( Settings::class );
		}
	}
```

### Settings REST API

- URL will be: `/wp-json/<get_page_slug>/<rest_api_version>/<rest_api_base>`
- Default IS: `/wp-json/<get_page_slug>/<rest_api_version>/<rest_api_base>`
- Example: `/wp-json/plugin-example/v1/settings`

### WordPress Data Store Usages example

```js
import { select } from '@wordpress/data';

// For a single record (no ID needed if your endpoint returns one object)
const settings = select( 'core' ).getEntityRecord( '<get_menu_slug>', '<get_page_slug>' );
const settings = wp.data.select( 'core' ).getEntityRecord( '<get_menu_slug>', '<get_page_slug>' );

// Or use the resolver hook in a component
import { useEntityRecord } from '@wordpress/core-data';

function MyComponent() {
  const { record, isResolving } = useEntityRecord( 'storepress', '<get_page_slug>' );

  if ( isResolving ) return <p>Loading...</p>;

  return <div>{ record?.['field-text'] }</div>;
}
```

NOTE: If menu_slug return as sub-menu like:

```php
public function get_menu_slug(): string {
		return 'edit.php?post_type=wporg_product';
}
```

It will create like:

```js
select( 'core' ).getEntityRecord( '<show_in_rest>', '<rest_api_base>' );
select( 'core' ).getEntityRecord( '<get_page_slug>/<rest_api_version>', '<rest_api_base>' );
```

NOTE: You can override methods `show_in_rest()`, `rest_api_base()`, `get_menu_slug()`, `get_page_slug()` from `Settings` class or in `AdminPage` class.


- See: [@wordpress/core-data](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-core-data/)


### Settings Section data structure

```php
<?php
array(
    'type'        => 'section',
    'title'       => 'Section Title',
    'description' => 'Section Description',
)
```

### Field data options

```php
<?php

array(
    'id'          => 'input3', // Field ID.
    'type'        => 'text', // text, unit, password, toggle, code, small-text, tiny-text, large-text, textarea, email, url, number, color, select, wc-enhanced-select, radio, checkbox
    'title'       => 'Input Label',
    
    // Optional.
    'full_width' => true, // To make field full width. Just remove this key if do not want to use.
    
    'add_tag' => "PRO", // Add TAG
    'add_tag' => array("PRO", 'BACKGROUND COLOR HEX CODE'), // Add PRO Label
    'add_tag' => array("BETA", 'BACKGROUND COLOR HEX CODE', 'TEXT COLOR HEX CODE'), // Add PRO Label
    
    'description' => 'Input field description',
    
    'default'       => 'Hello World', //  default value can be string or array
    'default'       => array('x','y'), //  default value can be string or array
    
    'placeholder' => '' // Placeholder
    'suffix'      => '' // Field suffix.
    'html_attributes' => array('min' => 10) // Custom html attributes.
    'html_datalist'   => array('value 1', 'value 2') // HTML Datalist for suggestion.
    'required'    => true, // If field is required and cannot be empty.
    'private'     => true, // Private field does not delete from db during reset all action trigger.
    'multiple'    => true, // for select box 
    'class'       => array( 'large-text', 'code', 'custom-class' ),
    'tooltip'     => 'Textarea Help tooltip',
    'condition'   => array( 'selector'=>$this->get_field_selector('input2') ), // Conditional field, show or hide based on other input value.
    'condition'   => array( 'selector'=>$this->get_field_selector('input2'), 'value'=>'hello' ),
    'units'       => array('px', '%', 'em', 'rem'), // For unit type

    'sanitize_callback'=>'absint', // Use custom sanitize function. Default is: sanitize_text_field.
    'show_in_rest'    => true, // Hide from rest api field. Default is: true
    'show_in_rest'    => 'custom_rest_id', // Change field id on rest api.
    'show_in_rest'    => array( 'name'=>'custom_rest_id' ), // Change field id on rest api.
    'show_in_rest'    => array( 'name'=>'custom_rest_id', 'schema'=>array() ), // Add input schema for REST Api. See: https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/
    // Options array for select, radio and checkbox [key=>value]
    // If checkbox have no options or value, default will be yes|no
    'options' => array(
        'x' => 'Home X',
        'y' => 'Home Y',
        'z' => 'Home Z',
        'new'   => array(
            'label' => 'New',
            'description' => 'New Item',
        ),
    )
),
```

### `AbstractProPluginInCompatibility`  class usages example

- Show notice for incompatible pro or extended plugin.

```php
<?php
	
	namespace StorePress\Example\Integrations;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\AdminUtils\Abstracts\AbstractProPluginInCompatibility;
	use StorePress\AdminUtils\Traits\SingletonTrait;
	use function StorePress\Example\get_pro_plugin_file;
	
	class ProPluginInCompatibility extends AbstractProPluginInCompatibility {
		
		use SingletonTrait;
		
		public function compatible_version(): string {
			return '3.0.0';
		}
		
		public function pro_plugin_file(): string {
			return get_pro_plugin_file(); // OR FILE CONSTANCE OF PRO PLUGIN FILE.
		}
		
		public function localize_notice_format(): string {
			// translators: 1: Extended Plugin Name. 2: Extended Plugin Version. 3: Extended Plugin Compatible Version.
			return 'You are using an incompatible version of <strong>%1$s - (%2$s)</strong>. Please upgrade to version <strong>%3$s</strong> or upper.';
		}
	}
```

### `AbstractUpdater` class usages example

- NOTE: Update server and plugin **SHOULD NOT** be in same WordPress setup.

- You must add `Update URI:` on plugin file header to perform update.

```php
<?php
/**
 * Plugin Name: Plugin Example
 * Tested up to: 6.4.1
 * Update URI: https://update.example.com/
*/
```

```php
<?php
<?php
	
	namespace StorePress\Example\Integrations;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\AdminUtils\Abstracts\AbstractUpdater;
	use StorePress\AdminUtils\Traits\SingletonTrait;
	use function StorePress\Example\get_container;
	
	
	/**
	 * Updater Class.
	 *
	 * @name Updater
	 */
	
	class Updater extends AbstractUpdater {
		
		use SingletonTrait;
		
		public function license_key(): string {
			// return get_container()->get( Settings::class )->get_option( 'license' );
			return 'hello';
		}
		
		public function product_id(): int {
			return 123450;
		}
		
		public function update_server_path(): string {
			return '/storepress-admin-utils/wp-json/plugin-updater/v1/check-update';
		}
		
		public function localize_strings(): array {
			
			$name = $this->get_plugin_name();
			
			return array(
				'license_key_empty_message'     => 'License key is not available.',
				'check_update_link_text'        => sprintf('Check Update %s', $name),
				'rollback_changelog_title'      => 'Changelog',
				'rollback_action_running'       => 'Rolling back',
				'rollback_action_button'        => sprintf('Rollback %s', $name),
				'rollback_cancel_button'        => 'Cancel',
				'rollback_current_version'      => 'Current version',
				'rollback_last_updated'         => 'Last updated %s ago.',
				'rollback_view_changelog'       => sprintf('View Changelog for %s', $name),
				'rollback_page_title'           => sprintf( 'Rollback Plugin %s', $name),
				'rollback_link_text'            => sprintf('Rollback %s', $name),
				'rollback_failed'               => 'Rollback failed.',
				'rollback_success'              => 'Rollback success: %s rolled back to version %s.',
				'rollback_plugin_not_available' => 'Plugin is not available.',
				'rollback_no_access'            => 'Sorry, you are not allowed to rollback plugins for this site.',
				'rollback_not_available'        => 'Rollback is not available for plugin: %s',
				'rollback_no_target_version'    => 'Plugin version not selected.',
			);
		}
		
		// If you need to send additional arguments to update server.
		// Check get_request_args() method.
		public function additional_request_args(): array {
			return array( 'host'=> $this->get_client_hostname() );
		}
	}
```

## `AbstractDeactivationFeedback`  class usages example

```php
<?php
	
	namespace StorePress\Example\Integrations;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\AdminUtils\Abstracts\AbstractDeactivationFeedback;
	use StorePress\AdminUtils\Traits\SingletonTrait;
	use function StorePress\Example\get_container;
	
	/**
	 * Changelog Dialog Class.
	 *
	 * @name DeactivationFeedback
	 */
	
	class DeactivationFeedback extends AbstractDeactivationFeedback {
		
		use SingletonTrait;
		
		/**
		 * Get deactivation title.
		 *
		 * @return string
		 */
		public function title(): string {
			return 'QUICK FEEDBACK';
		}
		
		public function sub_title(): string {
			return 'May we have a little info about why you are deactivating?';
		}
		
		/**
		 * Set API URL to send feedback.
		 *
		 * @return string
		 * @example https://example.com/wp-json/__NAMESPACE__/v1/deactivate
		 */
		public function api_url(): string {
			return 'http://sites.local/storepress-admin-utils/wp-json/feedback/v1/deactivate';
		}
		
		/**
		 * Get saved settings data.
		 *
		 * @return array<string, mixed>
		 */
		public function options(): array {
			// return get_container()->get( Settings::class )->get_options();
			return array();
		}
		
		public function get_buttons(): array {
			
			return array(
				array(
					'type'       => 'button',
					'label'      => __( 'Send feedback & Deactivate' ),
					'attributes' => array(
						'disabled'        => true,
						'type'            => 'submit',
						'data-action'     => 'submit',
						'data-label'      => __( 'Send feedback & Deactivate' ),
						'data-processing' => __( 'Deactivate...' ),
						'class'           => array( 'button', 'button-primary' ),
					),
					'spinner'    => true,
				),
				array(
					'type'       => 'link',
					'label'      => __( 'Skip & Deactivate' ),
					'attributes' => array(
						'href'  => '#',
						'class' => array( 'skip-deactivate' ),
					),
				),
			);
		}
		
		public function get_reasons(): array {
			$current_user = wp_get_current_user();
			$name = $this->get_plugin_name();
			
			return array(
				'temporary_deactivation' => array(
					'title'             => esc_html__( 'It\'s a temporary deactivation.', 'woo-variation-swatches' ),
				),
				
				'dont_know_about' => array(
					'title'             => esc_html__( 'I couldn\'t understand how to make it work.', 'woo-variation-swatches' ),
					'message'             => sprintf( 'Its Plugin %s.', $name),
				),
				
				'no_longer_needed' => array(
					'title'             => esc_html__( 'I no longer need the plugin.', 'woo-variation-swatches' ),
				),
				
				'found_a_better_plugin' => array(
					'title'             => esc_html__( 'I found a better plugin.', 'woo-variation-swatches' ),
					'input' => array(
						'placeholder'=>esc_html__( 'Please let us know which one', 'woo-variation-swatches' ),
					),
				),
				
				'broke_site_layout' => array(
					'title'             => __( 'The plugin <strong>broke my layout</strong> or some functionality.', 'woo-variation-swatches' ),
					'message'           => __( '<a target="_blank" href="https://getwooplugins.com/tickets/">Please open a support ticket</a>, we will fix it immediately.', 'woo-variation-swatches' ),
				),
				
				'plugin_setup_help' => array(
					'title'             => __( 'I need someone to <strong>setup this plugin.</strong>', 'woo-variation-swatches' ),
					'input' => array(
						'placeholder'=>esc_html__( 'Your email address.', 'woo-variation-swatches' ),
						'value'=>sanitize_email( $current_user->user_email )
					),
					'message'             => __( 'Please provide your email address to contact with you <br>and help you to set up and configure this plugin.', 'woo-variation-swatches' ),
				),
				
				'plugin_config_too_complicated' => array(
					'title'             => __( 'The plugin is <strong>too complicated to configure.</strong>', 'woo-variation-swatches' ),
					'message'             => __( '<a target="_blank" href="https://getwooplugins.com/documentation/woocommerce-variation-swatches/">Have you checked our documentation?</a>.', 'woo-variation-swatches' ),
				),
				
				'need_specific_feature' => array(
					'title'             => esc_html__( 'I need specific feature that you don\'t support.', 'woo-variation-swatches' ),
					
					'input' => array(
						'placeholder'=>esc_html__( 'Please share with us.', 'woo-variation-swatches' ),
					),
				),
				
				'other' => array(
					'title'             => esc_html__( 'Other', 'woo-variation-swatches' ),
					'input' => array(
						'placeholder'=>esc_html__( 'Please share the reason', 'woo-variation-swatches' ),
					),
				)
			);
		}
		
		/**
		 * Dialog width.  - Optional.
		 *
		 * @return string
		 */
		/*public function get_dialog_width(): string {
			return ''; // 600px
		}*/
	}
```

## `ServiceContainer` class usages example

```php
<?php

	declare( strict_types=1 );

	namespace StorePress\Example\Containers;

	defined( 'ABSPATH' ) || die( 'Keep Silent' );

	use StorePress\AdminUtils\ServiceContainers\ServiceContainer;
	use StorePress\AdminUtils\Traits\SingletonTrait;

	/**
	 * Dependency Injection Container Class.
	 *
	 *
	 * @name Container
	 */
	class Container extends ServiceContainer {
		use SingletonTrait;
	}
```


## Preparing Pro `plugin-pro/plugin-pro.php` version example

```php
<?php
	/*
	Plugin Name: Plugin Example Pro
	Plugin URI: https://storepress.com/plugins/plugin-example-pro/
	Description: This is not a plugin for test admin utilities.
	Author: Emran Ahmed
	Version: 1.0.0
	Tested up to: 6.3
	Author URI: https://storepress.com/emran/
	Update URI: https://update.example.com/
	*/
	
	/**
	 * Bootstrap the plugin.
	 */
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	use StorePress\Example\PluginPro;
	
	define('PLUGIN_EXAMPLE_PRO_FILE', __FILE__);
	
	function plugin_example_pro() {
		
		// Include the main class.
		if ( ! class_exists( PluginPro::class, false ) ) {
			require_once __DIR__ . '/includes/PluginPro.php';
		}
		
		return PluginPro::instance();
	}
```

## Preparing `PluginPro` class

```php
<?php

	declare( strict_types=1 );

	namespace StorePress\Example;
	
	use StorePress\Example\ServiceProviders\DeactivationServiceProviderPro;
	use StorePress\Example\ServiceProviders\SettingsServiceProvider;
	use StorePress\Example\ServiceProviders\SettingsServiceProviderPro;
	
	defined( 'ABSPATH' ) || die( 'Keep Silent' );
	
	class PluginPro extends Plugin {
		
		public static function instance(): self {
			static $instance = null;
			return $instance ??= new self();
		}

		public function includes(): void {
			
			parent::includes();
			
			$vendor_path = untrailingslashit( plugin_dir_path( $this->get_pro_plugin_file() ) ) . '/vendor';

			if ( file_exists( $vendor_path . '/autoload_packages.php' ) ) {
				require_once $vendor_path . '/autoload_packages.php';
			}
		}

		// =====================================================================
		// Getter Methods
		// =====================================================================
		
		public function get_service_providers(): array {
			
			$current_providers = parent::get_service_providers();
			
			$available_providers = array(
				SettingsServiceProvider::class=>SettingsServiceProviderPro::class,
				DeactivationServiceProviderPro::class=>DeactivationServiceProviderPro::class
			);
			
			return array_replace( $current_providers, $available_providers );
		}
		
		public function get_pro_plugin_file(): string {
			return constant( 'PLUGIN_EXAMPLE_PRO_FILE' );
		}
		
	}
```

## Preparing Update Server

```php
<?php

// Based on Plugin Header Update URI:  
// https://update.example.com/wp-json/plugin-updater/v1/check-update
add_action( 'rest_api_init', function () {
    register_rest_route( 'plugin-updater/v1', '/check-update', [
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'updater_get_plugin',
        'permission_callback' => '__return_true',
    ] );
} );

/**
 * @param WP_REST_Request $request REST request instance.
 *
 * @return WP_REST_Response|WP_Error WP_REST_Response instance if the plugin was found,
 *                                    WP_Error if the plugin isn't found.
 *                                   
 */
function updater_get_plugin( WP_REST_Request $request ) {
    
    $params = $request->get_params();
            
    $type        = $request->get_param( 'type' ); // plugins
    $plugin_name = $request->get_param( 'name' ); // plugin-dir/plugin-name.php
    $license_key = $request->get_param( 'license_key' ); // plugin
    $product_id  = $request->get_param( 'product_id' ); // plugin
    $args        = (array) $request->get_param( 'args' ); // plugin additional arguments.
    
    
    /**
     * $data [
     *
     *     'description'=>'',
     * 
     *     'active_installs'=>'1000',
     *
     *     'faq'=>'',
     * 
     *     'changelog'=>'',
     *
     *     'new_version'=>'x.x.x', // * REQUIRED
     * 
		 *     'banners'=>['low'=>'https://ps.w.org/woocommerce/assets/banner-772x250.png', 'high'=>'https://ps.w.org/woocommerce/assets/banner-1544x500.png'],
		 *
		 *     'banners_rtl'=>[],
		 *
		 *     Using SVG Icon Recommended.
		 *
		 *     'icons'=>[ 'svg' => 'https://ps.w.org/woocommerce/assets/icon.svg', '2x'  => 'https://ps.w.org/woocommerce/assets/icon-256x256.png', '1x'  => 'https://ps.w.org/woocommerce/assets/icon-128x128.png' ], // icons.
		 *  
		 *     'screenshots'=>[['src'=>'', 'caption'=>'' ], ['src'=>'', 'caption'=>''], ['src'=>'', 'caption'=>'']],
     *
     *     'last_updated'=>'2023-11-11 3:24pm GMT+6',
     *
     *     'upgrade_notice'=>'',
     * 
     *     'upgrade_notice'=>['1.1.0'=>'Notice for this version', '1.2.0'=>'Notice for 1.2.0 version'],
     *
     *     'package'=>'https://plugin-server.com/plugin-2.0.0.zip', // * REQUIRED ABSOLUTE URL
     *
     *     'tested'=>'x.x.x', // WP testes Version
     *
     *     'requires'=>'x.x.x', // Minimum Required WP
     *
     *     'requires_php'=>'x.x.x', // Minimum Required PHP
     *
     *     'requires_plugins'=> ['woocommerce'], // Requires Plugins
     *
     *     'versions'=> [  '1.0.0' => 'https://plugin-server.com/plugin-1.0.0.zip', '2.0.0' => 'https://plugin-server.com/plugin-2.0.0.zip' ], // Available versions
     *
     *     'preview_link'=>'', // Plugin Preview Link
     * 
     *     'allow_rollback'=>'yes', // yes | no // * REQUIRED for ROLLBACK
     *
     * ]
     */
    
    $data = array(
        'new_version'    => '1.3.4',
        'last_updated'   => '2023-12-12 09:58pm GMT+6',
        'package'        =>'https://updater.example.com/plugin.zip', // After license verified.
        'upgrade_notice' => 'Its Fine',
        'changelog'      =>'Change log text',
        'versions'       => [  
                '1.0.0' => 'https://plugin-server.com/plugin-1.0.0.zip', 
                '2.0.0' => 'https://plugin-server.com/plugin-2.0.0.zip' 
        ], // Available versions
       'allow_rollback'=>'yes', // yes | no // * REQUIRED for ROLLBACK
    );
    
    return rest_ensure_response( $data );
}
```

## Preparing Feedback Server

```php
<?php

// Sample API:  
// https://state.example.com/wp-json/feedback/v1/deactivate
add_action( 'rest_api_init', function () {
    register_rest_route( 'feedback/v1', '/deactivate', [
        'methods'             => WP_REST_Server::CREATABLE,
        'callback'            => 'store_deactivate_data',
        'permission_callback' => '__return_true',
    ] );
} );

/**
 * @param WP_REST_Request $request REST request instance.
 *
 * @return WP_REST_Response|WP_Error WP_REST_Response instance if the plugin was found,
 *                                    WP_Error if the plugin isn't found.
 *                                   
 */
 
function store_deactivate_data( WP_REST_Request $request ) {
    
    $params = $request->get_params();
            
    $feedback  = (array) $request->get_param( 'feedback' );
    $wordpress = (array) $request->get_param( 'wordpress' );
    $theme     = (array) $request->get_param( 'theme' );
    $plugins   = (array) $request->get_param( 'plugins' );
    $server    = (array) $request->get_param( 'server' );
    
    // Save data
    
    return rest_ensure_response( true );
}
```

## Best Practices to write plugin based on this package

1. **Use Singleton Pattern** - All services should use `SingletonTrait`
3. **Register Before Boot** - Always register services before booting
4. **Lazy Loading** - Services are only instantiated when resolved
5. **Single Responsibility** - Each service handles one concern
6. **Extend Base Classes** - Use abstract classes from AdminUtils
