diff --git a/CHANGELOG.md b/CHANGELOG.md index 3505d089b0846f2e2f6f9e2bbde5a73c0237cc37..d501ec22e222cd1f782e842a4470c0a268ab91ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## [1.4.0] - 2019-09-26 +### Added +- SlimPlugin - abstract class with only most important plugin elements +- AbstractPlugin - docs and cleaning +- Activateable - interface to tag plugin to hook into activation hook +- Deactivateable - interface to tag plugin to hook into deactivation hook +- Conditional - interface to tag classes that should be instantiated/hooked only in given state +### Changed +- WordpressFilterStorage - store plugin using WordPress filter system +- target blank in default plugin links +### Fixed +- Fixed assets and plugin url issues + ## [1.3.4] - 2019-09-26 ### Fixed - Gitlab.ci diff --git a/src/BuildDirector/LegacyBuildDirector.php b/src/BuildDirector/LegacyBuildDirector.php index 90e01620403535ff1d204cb9222cbc4be5e97177..5357a35d3b241a981ee5d69789d53e3d486e71de 100644 --- a/src/BuildDirector/LegacyBuildDirector.php +++ b/src/BuildDirector/LegacyBuildDirector.php @@ -34,4 +34,4 @@ class LegacyBuildDirector { public function get_plugin() { return $this->builder->get_plugin(); } -} \ No newline at end of file +} diff --git a/src/Builder/AbstractBuilder.php b/src/Builder/AbstractBuilder.php index 592c969e1c31e68243ade2e929314424866be3dc..e42fa5bec31e6326fe7333cdeada10eb003eab73 100644 --- a/src/Builder/AbstractBuilder.php +++ b/src/Builder/AbstractBuilder.php @@ -61,4 +61,4 @@ abstract class AbstractBuilder { */ public function set_helper( $helper ) { } -} \ No newline at end of file +} diff --git a/src/Builder/InfoActivationBuilder.php b/src/Builder/InfoActivationBuilder.php index 7b646f31a378cfcdd28f263ad33d644b8d50e9b6..10d9f32dd74abd2cda486404c4e870eeb7f8cf99 100644 --- a/src/Builder/InfoActivationBuilder.php +++ b/src/Builder/InfoActivationBuilder.php @@ -79,4 +79,4 @@ class InfoActivationBuilder extends AbstractBuilder { return $this->plugin; } -} \ No newline at end of file +} diff --git a/src/Builder/InfoBuilder.php b/src/Builder/InfoBuilder.php index d4d44c8b83e7e27310775556ff10560eb89cc446..1adb8f6c30d5b477c71ba1e3198bbcb517ec5c49 100644 --- a/src/Builder/InfoBuilder.php +++ b/src/Builder/InfoBuilder.php @@ -55,4 +55,4 @@ class InfoBuilder extends AbstractBuilder { public function get_plugin() { return $this->plugin; } -} \ No newline at end of file +} diff --git a/src/Plugin/AbstractPlugin.php b/src/Plugin/AbstractPlugin.php index e3809c43d4024d87600b1ea26f5843802e6cf730..20ee0261d5285ac285155f25bde6e8204cdf85bc 100644 --- a/src/Plugin/AbstractPlugin.php +++ b/src/Plugin/AbstractPlugin.php @@ -3,31 +3,50 @@ namespace WPDesk\PluginBuilder\Plugin; /** - * Base plugin class for WP Desk plugins. + * Base plugin with most basic functionalities used by every WPDesk plugin. * - * ************************************************************* - * * Important! This class should be not modified! * - * * This class is loaded at startup from first loaded plugin! * - * ************************************************************* * - * @author Grzegorz, Dyszczo + * Known issues: * + * The class name is too generic but can't be changed as it would introduce a major incompatibility for most of the plugins. + * The $plugin_url, $docs_url and most other fields should be removed as they only litter the place but for compatibility reasons we can't do it right now. + * Hook methods should be moved to external classes but for compatibility reasons we can't do it right now. */ -abstract class AbstractPlugin implements \WPDesk_Translable { +abstract class AbstractPlugin extends SlimPlugin { - /** @var \WPDesk_Plugin_Info */ + /** + * Most info about plugin internals. + * + * @var \WPDesk_Plugin_Info + */ protected $plugin_info; - /** @var string */ + /** + * Unique string for this plugin in [a-z_]+ format. + * + * @var string + */ protected $plugin_namespace; - /** @var string */ + /** + * Absolute URL to the plugin dir. + * + * @var string + */ protected $plugin_url; - /** @var string */ + /** + * Absolute URL to the plugin docs. + * + * @var string + */ protected $docs_url; - /** @var string */ + /** + * Absolute URL to the plugin settings url. + * + * @var string + */ protected $settings_url; /** @@ -45,34 +64,35 @@ abstract class AbstractPlugin implements \WPDesk_Translable { public function __construct( $plugin_info ) { $this->plugin_info = $plugin_info; $this->plugin_namespace = strtolower( $plugin_info->get_plugin_dir() ); - } - - public function init() { + $this->plugin_url = $this->plugin_info->get_plugin_url(); $this->init_base_variables(); - $this->hooks(); } + /** + * Initialize internal state of the plugin. + * + * @return void + * @deprecated Just use __construct to initialize plugin internal state. + * + */ public function init_base_variables() { - $this->plugin_url = plugin_dir_url( $this->plugin_info->get_plugin_dir() ); } /** + * Initializes plugin external state. + * + * The plugin internal state is initialized in the constructor and the plugin should be internally consistent after creation. + * The external state includes hooks execution, communication with other plugins, integration with WC etc. + * * @return void */ - protected function hooks() { - add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] ); - - add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ] ); - - add_action( 'plugins_loaded', [ $this, 'load_plugin_text_domain' ] ); - add_filter( 'plugin_action_links_' . plugin_basename( $this->get_plugin_file_path() ), [ - $this, - 'links_filter' - ] ); - + public function init() { + $this->hooks(); } /** + * Returns absolute path to the plugin dir. + * * @return string */ public function get_plugin_file_path() { @@ -80,82 +100,118 @@ abstract class AbstractPlugin implements \WPDesk_Translable { } /** - * @return $this + * Returns plugin text domain. + * + * @return string */ - public function get_plugin() { - return $this; + public function get_text_domain() { + return $this->plugin_info->get_text_domain(); } /** - * @return void + * Returns unique string for plugin in [a-z_]+ format. Can be used as plugin id in various places like plugin slug etc. + * + * @return string */ - public function load_plugin_text_domain() { - load_plugin_textdomain( $this->get_text_domain(), false, $this->get_namespace() . '/lang/' ); + public function get_namespace() { + return $this->plugin_namespace; } /** + * Returns plugin absolute URL. + * * @return string */ - public function get_text_domain() { - return $this->plugin_info->get_text_domain(); + public function get_plugin_url() { + return esc_url( trailingslashit( $this->plugin_url ) ); } /** + * Returns plugin absolute URL to dir with front end assets. + * * @return string */ - public function get_namespace() { - return $this->plugin_namespace; - } - public function get_plugin_assets_url() { return esc_url( trailingslashit( $this->get_plugin_url() . 'assets' ) ); } /** + * @return $this + * @deprecated For backward compatibility. * - * @return string */ - public function get_plugin_url() { - return esc_url( trailingslashit( $this->plugin_url ) ); + public function get_plugin() { + return $this; + } + + /** + * Integrate with WordPress and with other plugins using action/filter system. + * + * @return void + */ + protected function hooks() { + add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] ); + add_action( 'wp_enqueue_scripts', [ $this, 'wp_enqueue_scripts' ] ); + add_action( 'plugins_loaded', [ $this, 'load_plugin_text_domain' ] ); + add_filter( 'plugin_action_links_' . plugin_basename( $this->get_plugin_file_path() ), [ + $this, + 'links_filter' + ] ); + } + /** + * Initialize plugin test domain. This is a hook function. Do not execute directly. + * + * @return void + */ + public function load_plugin_text_domain() { + load_plugin_textdomain( $this->get_text_domain(), false, $this->get_namespace() . '/lang/' ); } + /** + * Append JS scripts in the WordPress admin panel. This is a hook function. Do not execute directly. + * + * @return void + */ public function admin_enqueue_scripts() { } + /** + * Append JS scripts in WordPress. This is a hook function. Do not execute directly. + * + * @return void + */ public function wp_enqueue_scripts() { } /** - * action_links function. - * - * @access public + * Initialize plugin admin links. This is a hook function. Do not execute directly. * - * @param mixed $links + * @param string[] $links * - * @return array + * @return string[] */ public function links_filter( $links ) { $support_link = get_locale() === 'pl_PL' ? 'https://www.wpdesk.pl/support/' : 'https://www.wpdesk.net/support'; - if( $this->support_url ) { + if ( $this->support_url ) { $support_link = $this->support_url; } $plugin_links = [ - '<a href="' . $support_link . '">' . __( 'Support', $this->get_text_domain() ) . '</a>', + '<a target="_blank" href="' . $support_link . '">' . __( 'Support', $this->get_text_domain() ) . '</a>', ]; $links = array_merge( $plugin_links, $links ); if ( $this->docs_url ) { $plugin_links = [ - '<a href="' . $this->docs_url . '">' . __( 'Docs', $this->get_text_domain() ) . '</a>', + '<a target="_blank" href="' . $this->docs_url . '">' . __( 'Docs', $this->get_text_domain() ) . '</a>', ]; $links = array_merge( $plugin_links, $links ); } if ( $this->settings_url ) { $plugin_links = [ - '<a href="' . $this->settings_url . '">' . __( 'Settings', $this->get_text_domain() ) . '</a>', + '<a target="_blank" href="' . $this->settings_url . '">' . __( 'Settings', $this->get_text_domain() ) . '</a>', ]; $links = array_merge( $plugin_links, $links ); } diff --git a/src/Plugin/Activateable.php b/src/Plugin/Activateable.php new file mode 100644 index 0000000000000000000000000000000000000000..9e084717f11cdf08ad4b27b3a12433d64f6123e5 --- /dev/null +++ b/src/Plugin/Activateable.php @@ -0,0 +1,21 @@ +<?php + + +namespace WPDesk\PluginBuilder\Plugin; + +/** + * Tag the plugin with this ingterface to hook it to the WordPress activation hook. + * + * Note: works from plugin flow ^2.2. + * + * @package WPDesk\PluginBuilder\Plugin + */ +interface Activateable { + + /** + * Plugin activated in WordPress. Do not execute directly. + * + * @return void + */ + public function activate(); +} diff --git a/src/Plugin/ActivationAware.php b/src/Plugin/ActivationAware.php index f9cdac6938e424db972b4110875d352a7c567fb7..6fa0c798e3ec47db325e7d9cd81db9ea54778ae2 100644 --- a/src/Plugin/ActivationAware.php +++ b/src/Plugin/ActivationAware.php @@ -3,7 +3,7 @@ namespace WPDesk\PluginBuilder\Plugin; /** - * It means that this class is should know about subscription activation + * It means that this class is should know about SUBSCRIPTION activation * * @package WPDesk\PluginBuilder\Plugin */ diff --git a/src/Plugin/Conditional.php b/src/Plugin/Conditional.php new file mode 100644 index 0000000000000000000000000000000000000000..c30e6daf87ecc3c347f4ad0a280cc7c8543c2a64 --- /dev/null +++ b/src/Plugin/Conditional.php @@ -0,0 +1,20 @@ +<?php + + +namespace WPDesk\PluginBuilder\Plugin; + +/** + * Something that can be instantiated/hooked conditionally. + * + * @see https://github.com/mwpd/basic-scaffold/blob/master/src/Infrastructure/Conditional.php by Alain Schlesser + * + * @package WPDesk\PluginBuilder\Plugin + */ +interface Conditional { + /** + * Check whether the conditional object is currently needed. + * + * @return bool Whether the conditional object is needed. + */ + public static function is_needed(); +} diff --git a/src/Plugin/Deactivateable.php b/src/Plugin/Deactivateable.php new file mode 100644 index 0000000000000000000000000000000000000000..6d7f5786e0be9c01cb60e04bd05c3d876c5ac77b --- /dev/null +++ b/src/Plugin/Deactivateable.php @@ -0,0 +1,21 @@ +<?php + + +namespace WPDesk\PluginBuilder\Plugin; + +/** + * Tag the plugin with this ingterface to hook it to the WordPress deactivation hook. + * + * Note: works from plugin flow ^2.2. + * + * @package WPDesk\PluginBuilder\Plugin + */ +interface Deactivateable { + + /** + * Plugin deactivate in WordPress. Do not execute directly. + * + * @return void + */ + public function deactivate(); +} diff --git a/src/Plugin/HookableParent.php b/src/Plugin/HookableParent.php index 5aca3784d46ede75c9938c0248f5fd0c8c9b605f..667060567026a630c5a4bcb8554d4b02f3943517 100644 --- a/src/Plugin/HookableParent.php +++ b/src/Plugin/HookableParent.php @@ -45,9 +45,15 @@ trait HookableParent { protected function hooks_on_hookable_objects() { /** @var Hookable $hookable_object $hookable_object */ foreach ( $this->hookable_objects as $hookable_object ) { - $hookable_object->hooks(); + if ($hookable_object instanceof Conditional) { + if ($hookable_object::is_needed()) { + $hookable_object->hooks(); + } + } else { + $hookable_object->hooks(); + } } } -} \ No newline at end of file +} diff --git a/src/Plugin/PluginAccess.php b/src/Plugin/PluginAccess.php index 96e0ada648f0d5edd01eedeb16abb5b34ec50558..ebb9d3fd0a4f5ef10e90eab7b6843c027b77bff7 100644 --- a/src/Plugin/PluginAccess.php +++ b/src/Plugin/PluginAccess.php @@ -31,4 +31,4 @@ trait PluginAccess { return $this->plugin; } -} \ No newline at end of file +} diff --git a/src/Plugin/SlimPlugin.php b/src/Plugin/SlimPlugin.php new file mode 100644 index 0000000000000000000000000000000000000000..cf95f3d7dc131367c5197aa669c7fe31851a1ac6 --- /dev/null +++ b/src/Plugin/SlimPlugin.php @@ -0,0 +1,20 @@ +<?php + +namespace WPDesk\PluginBuilder\Plugin; + +/** + * Most clean plugin class with only most important details. + */ +abstract class SlimPlugin implements \WPDesk_Translatable { + + /** + * Initializes plugin external state. + * + * The plugin internal state is initialized in the constructor and the plugin should be internally consistent after creation. + * The external state includes hooks execution, communication with other plugins, integration with WC etc. + * + * @return void + */ + abstract public function init(); +} + diff --git a/src/Storage/StorageFactory.php b/src/Storage/StorageFactory.php index 8a1422f53e033dc1fc3b1da9cf1dee71a302f23a..89f5f56f2d2c6ad16e85b828af21061f0f811033 100644 --- a/src/Storage/StorageFactory.php +++ b/src/Storage/StorageFactory.php @@ -8,7 +8,7 @@ class StorageFactory { * @return PluginStorage */ public function create_storage() { - return new StaticStorage(); + return new WordpressFilterStorage(); } } diff --git a/src/Storage/WordpressFilterStorage.php b/src/Storage/WordpressFilterStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..d8c564abd1de999128ffb2824d826241a3869d33 --- /dev/null +++ b/src/Storage/WordpressFilterStorage.php @@ -0,0 +1,44 @@ +<?php + +namespace WPDesk\PluginBuilder\Storage; + +use WPDesk\PluginBuilder\Plugin\AbstractPlugin; + +/** + * Can store plugin instances in WordPress filter system. + * + * @package WPDesk\PluginBuilder\Storage + */ +class WordpressFilterStorage implements PluginStorage { + const STORAGE_FILTER_NAME = 'wpdesk_plugin_instances'; + + /** + * @param string $class + * @param AbstractPlugin $object + */ + public function add_to_storage( $class, $object ) { + add_filter( self::STORAGE_FILTER_NAME, function ( $plugins ) use ( $class, $object ) { + if ( isset( $plugins[ $class ] ) ) { + throw new Exception\ClassAlreadyExists( "Class {$class} already exists" ); + } + $plugins[ $class ] = $object; + + return $plugins; + } ); + + } + + /** + * @param string $class + * + * @return AbstractPlugin + */ + public function get_from_storage( $class ) { + $plugins = apply_filters( self::STORAGE_FILTER_NAME, [] ); + if ( isset( $plugins[ $class ] ) ) { + return $plugins[ $class ]; + } + throw new Exception\ClassNotExists( "Class {$class} not exists in storage" ); + } +} +