Статья размещена автором Бетке Сергей Сергеевич

Пишем плагин для WordPress: ищем framework

Писать плагин с нуля полезно лишь для того, чтобы понять, как устроен WordPress, но не более. Для того, чтобы Ваш плагин был оптимальным с точки зрения производительности front-end, читаемым и сопровождаемым, следует воспользоваться framework’ом, выбором которого мы и займёмся в этой статье.

На запрос “wordpress plugin framework” google выдаёт массу ссылок, с которых мы и начнём.

Плагин wordpress-plugin-framework

Да, так бывает. Есть целый плагин для создания плагинов. Но последнее обновление – в 2008 году. Разработчик рекомендует почитать WordPress Plugin Framework User Manual. Читаем.

Позволю себе немного плагиата, но в итоге файл плагина будет выглядеть приблизительно так:

<?php 
/** 
 *     ---------------       DO NOT DELETE!!!     --------------- 
 *  
 *     Plugin Name:  My Test Plugin 
 *     Plugin URI:   http://code.google.com/p/wordpress-plugin-framework/w/edit/WordpressPluginFrameworkUserManual 
 *     Description:  A simple test plugin used to demonstrate the WordPress Plugin Framework. 
 *     Version:      0.01 
 *     Author:       Double Black Design 
 *     Author URI:   http://www.doubleblackdesign.com 
 * 
 *     ---------------       DO NOT DELETE!!!     --------------- 
 * 
 *    This is the required license information for a WordPress plugin. 
 * 
 *    Copyright 2007  Keith Huster  (email : husterk@doubleblackdesign.com) 
 * 
 *    This program is free software; you can redistribute it and/or modify 
 *    it under the terms of the GNU General Public License as published by 
 *    the Free Software Foundation; either version 2 of the License, or 
 *    (at your option) any later version. 
 * 
 *    This program is distributed in the hope that it will be useful, 
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 *    GNU General Public License for more details. 
 * 
 *    You should have received a copy of the GNU General Public License 
 *    along with this program; if not, write to the Free Software 
 *    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 * 
 *     ---------------       DO NOT DELETE!!!     --------------- 
 */ 
 
 
/** 
 * Include the WordPressPluginFramework. 
 */ 
require_once( "wordpress-plugin-framework.php" );  
 
 
 
/** 
 * MyTestPlugin - Simple plugin class used to demonstrate the WordPressPluginFramework. 
 * 
 * @package my-test-plugin 
 * @since {WP 2.3}  
 * @author Keith Huster 
 */ 
class MyTestPlugin extends MyTestPlugin_WordpressPluginFramework 
{ 
  /** 
   * HTML_DisplayPluginHelloWorldBlock() - Displays the "Hello World!" content block. 
   * 
   * This function generates the markup required to display the specified content block. 
   * 
   * @param void      None. 
   *  
   * @return void     None.       
   *  
   * @access private  Access via internal callback only. 
   * @since {WP 2.3} 
   * @author Keith Huster 
   */ 
   function HTML_DisplayPluginHelloWorldBlock() 
   { 
      ?> 
      <p>Hello World!</p> 
      <?php 
   } 
} 
 
/** 
 * Demonstration of creating a plugin utilizing the WordPressPlugin Framework. 
 */ 
if( !$myTestPlugin ) 
{ 
  // Create a new instance of My Test Plugin class and initialize the plugin. 
  // NOTE: Initialize the plugin in "Debug Mode". 
  $myTestPlugin = new MyTestPlugin(); 
  $myTestPlugin->Initialize( 'Test Plugin for the WordPress Plugin Framework', '1.00', my-test-plugin', 'my-test-plugin', true ); 
 
  // Add the plugin options and initialize the plugin. 
  $myTestPlugin->AddOption( $myTestPlugin->OPTION_TYPE_TEXTBOX, 'myTextboxOption', 'Hello!', 'Simple textbox option for your plugin.' ); 
  $myTestPlugin->RegisterOptions( __FILE__ ); 
 
  // Add the administration page content blocks and register the page. 
  $myTestPlugin->AddAdministrationPageBlock( 'block-hello-world', 'Hello World', $myTestPlugin->CONTENT_BLOCK_TYPE_SIDEBAR, array( $myTestPlugin, 'HTML_DisplayPluginHelloWorldBlock' ) ); 
 
  // Add the requested "About This Plugin" web links. 
  $donateLink = 'http://www.mytestplugin.com/donate'; 
  $homepageLink = 'http://www.mytestplugin.com'; 
  $supportForumLink = 'http://www.mytestplugin.com/support'; 
  $myTestPlugin->AddAboutThisPluginLinks( $donateLink, $homepageLink, $supportForumLink ); 
 
  // Register the plugin administration page with the WordPress core. 
  $myTestPlugin->RegisterAdministrationPage( $myTestPlugin->PARENT_MENU_PLUGINS, $myTestPlugin->RIGHTS_REQUIRED_ADMIN, 'My Test Plugin', 'My Test Plugin Options Page', 'my-test-plugin-options' ); 
}

Уже сразу могу сказать, чем мне не понравился этот framework:

  • код, необходимый только в консоли, грузится и в frontend, что нехорошо;
  • подход больше процедурно-ориентированный, нежели объектно-ориентированный. Обратите внимание на “создание” опций плагина – и Вы поймёте, о чём я говорю.

xavisys WordPress plugin framework

Несколько отличающийся фреймворк, но:

  • код, необходимый только в консоли, грузится и в frontend, что нехорошо;
  • фреймворк излишне закрытый

Пример кода можно будет увидеть в следующем разделе.

Плагин WordPress Plugin Framework Reloaded

По сути – развитие предыдущих проектов. Последнее обновление уже в середине 2010 года.

Скачал, установил, активировал. Со слов автора показательным примером использования фреймворка является сам плагин. Вот его код:

<?php
/*
Plugin Name: WordPress Plugin Framework Reloaded
Version: 0.2.1
Description: Based on Xavisys Plugin Framework, WordPress Plugin Framework and Simple Plugin Framework. Like it? <a href="http://smsh.me/7kit" target="_blank" title="Paypal Website"><strong>Donate</strong></a> | <a href="http://www.amazon.co.uk/wishlist/2NQ1MIIVJ1DFS" target="_blank" title="Amazon Wish List">Amazon Wishlist</a>
Donate link: http://smsh.me/7kit
Author: Lopo Lencastre de Almeida - iPublicis.com
Author URI: http://ipublicis.com/
Plugin URI: http://wordpress.org/extend/plugins/wordpress-pluging-framework-reloaded/
License: GNU LGPL v3 or later

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/**
 * Load the framework file
 */
require_once( 'framework-reloaded.php' );

/**
 * Where you put all your class code
 */
class wpfrMain extends WordPressPluginFrameworkReloaded {
    /**
     * @var wpfrMain - Static property to hold our singleton instance
     */
    static $instance = false;
    
    protected function _init() {

        /**
         * Definition of global values
         */
        $this->_hook = 'wpfReloaded';
        $this->_pluginDir = str_replace(basename( __FILE__),"",plugin_basename(__FILE__));
        $this->_file = plugin_basename(__FILE__);
        $this->_slug = untrailingslashit( $this->_pluginDir );
        $this->_appLogoFile = plugin_dir_url( __FILE__ ).'/images/ipublicis-logo-32.png';
        $this->_pageTitle = "Wordpress Plugin Framework Reloaded";
        $this->_menuTitle = "WPF Reloaded";
        $this->_accessLevel = 'manage_options';
        $this->_optionGroup = 'wpfr-options';
        $this->_optionNames = array('wpfr');
        $this->_optionCallbacks = array();
        $this->_appFeed = 'http://w3.ipublicis.com/newswire/ipublicis/feed';
        $this->_donationID = '7kit';
        $this->_wishlistID = 'A7HJYTOILQO5';
        $this->_contactURL = 'http://w3.ipublicis.com/contact-us';
        $this->_dashboardWidget = array(     'inc' => 'iPublicis!COM',
                                                                    'url' => 'http://w3.ipublicis.com/',
                                                                    'rss' => 'http://w3.ipublicis.com/rss.xml', 
                                                                    'ico' => plugin_dir_url( __FILE__ ).'/images/ipublicis-logo-32.png'  );
        $this->_sidebarNews = array(  false, false );

        /**
         * Add filters and actions
         */
        register_activation_hook( $this->_file, array( $this, 'activate' ) );
        register_deactivation_hook( $this->_file, array( $this, 'deactivate' ) );
    }

    protected function _postSettingsInit() {
        $this->activate();
    }

    public function addOptionsMetaBoxes() {
            add_meta_box( $this->_slug . '-about-us', __('About ', 'framework-reloaded') . $this->_pageTitle, array($this, 'aboutWPFRMetaBox'), $this->_slug . '-about-us', 'main');
            add_meta_box( $this->_slug . '-feed', __('Plugin Latest News.', 'framework-reloaded'), array($this, 'appFeedMetaBox'),  $this->_slug.'-about-us', 'main');
    }

    public function registerOptionsPage() {
        if ( $current_user->ID == 1 || current_user_can( $this->_accessLevel ) ) {
            if( is_callable( array( $this, 'about_page' ) ) )  {
                add_options_page(    $this->_pageTitle, $this->_menuTitle, $this->_accessLevel, 
                                                $this->_hook . '-options', array( $this, 'about_page' ), $this->_appLogoFile ); 
            }
        }
    }

    /**
     * Function to instantiate our class and make it a singleton
     */
    public static function getInstance() {
        if ( !self::$instance ) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    public function activate() {
        $wpfr_version = "0.2";
        add_option( 'wpfr_version', $wpfr_version );
    }

    public function deactivate() {
        if( get_option('wpfr_version') ) {
            delete_option( 'wpfr_version' );
        }
    }
    
} /* END OF CLASS */

// Instantiate our class
$wpfReloaded = wpfrMain::getInstance();

?>

Уже красивее, но наследство явно сказывается – callback явно задаётся, вместо виртуальных методов и полиморфизма, да и не только. Недостатки прежние:

  • код, необходимый только в консоли, грузится и в frontend, что нехорошо;
  • по-прежнему наряду с ООП присутствуют элементы процедурного программирования.

Sanity WordPress plugin Framework

Данный фреймворк несёт некоторые хорошие идеи в себе, не более. Он достаточно оптимален, за счёт разделения представления (шаблонов html кода) и кода в разные файлы. Достаточно любопытная концепция шаблонов, ничего удивительного, но вполне красиво и оптимально, на мой взгляд.

К сожалению, это и всё, что можно о нём сказать. Собственно, весь фреймворк на момент написания статьи представлен ниже:

<?php
class SanityPluginFramework {
    
    // Container variables
    var $view = '';
    var $data = array();
    var $wpdb;
    var $nonce;
    
    // Assets to load
    var $admin_css = array();
    var $admin_js = array();
    var $plugin_css = array();
    var $plugin_js = array();
    
    // Paths
    var $css_path = 'css';
    var $js_path = 'js';
    var $plugin_dir = '';
    var $plugin_dir_name = '';

    // AJAX actions
    var $ajax_actions = array(
        'admin' => array(),
        'plugin' => array()
    );
    
    function __construct($here = __FILE__) {
        global $wpdb;
        $this->add_ajax_actions();
        $this->wpdb = $wpdb;
        $this->plugin_dir = WP_PLUGIN_DIR.'/'.basename(dirname($here));
        $this->plugin_dir_name = basename(dirname($here));
        $this->css_path = WP_PLUGIN_URL.'/'.$this->plugin_dir_name.'/css/';
        $this->js_path = WP_PLUGIN_URL.'/'.$this->plugin_dir_name.'/js/';
        add_action('wp_loaded', array(&$this, 'create_nonce'));
        if(!empty($this->admin_css) || !empty($this->admin_js) ) {
            add_action('admin_enqueue_scripts', array(&$this, 'load_admin_scripts'));
        }
        if(!empty($this->plugin_css) || !empty($this->plugin_js) ) {
            // TODO: enqueue plugin scripts
        }
    }
    
        /*
        *        load_admin_scripts()
        *        =====================
        *        Loads admin-facing CSS and JS.
        */
    function load_admin_scripts() {
        foreach($this->admin_css as $css) {
            wp_enqueue_style($css, $this->css_path.$css.'.css');
        }
        foreach($this->admin_js as $js) {
            wp_enqueue_script($js, $this->js_path.$js.'.js');
        }
    }

        /*
        *        load_plugin_scripts()
        *        =====================
        *        Loads front-facing CSS and JS.
        */
        function load_plugin_scripts() {
            foreach($this->plugin_css as $css) {
          wp_enqueue_style($css, $this->css_path.$css.'.css');
      }
      foreach($this->plugin_js as $js) {
          wp_enqueue_script($js, $this->js_path.$js.'.js');
      }
        }

        /*
        *        create_nonce()
        *        ==============
        *        A security feature that Sanity presumes you should use. Please
        *        refer to: http://codex.wordpress.org/WordPress_Nonces
        */
        function create_nonce() {
            $this->nonce = wp_create_nonce('sanity-nonce');
        }

        /*
        *        add_ajax_actions()
        *        ==================
        *        Loops through $this->ajax_actions['admin'] and $this->ajax_actions['plugin'] and
        *        registers ajax actions. This makes the actions available in the client plugin.
        */
        function add_ajax_actions() {
            if(!empty($this->ajax_actions['admin'])) {
                foreach($this->ajax_actions['admin'] as $action) {
                    add_action("wp_ajax_$action", array(&$this, $action));
                }
            }
            if(!empty($this->ajax_actions['plugin'])) {
                foreach($this->ajax_actions['plugin'] as $action) {
                    add_action("wp_ajax_nopriv_$action", array(&$this, $action));
                }
            }                
        }
    
        /*
        *        render($view)
        *        =============
        *        Loads a view from within the /plugin/views folder. Keep in mind
        *        that any data you need should be passed through the $this->data array.
        *        A few examples:
        *
        *            Load /Plugin/views/example.php
        *            $this->render('example');
        *
        *            Load /Plugin/views/subfolder/example.php
        *            $this->render('subfolder/example);
        *
        */
    function render($view) {
        $template_path = $this->plugin_dir.'/views/'.$view.'.php';
        ob_start();
        include($template_path);
        $output = ob_get_clean();
        return $output;
    }
    
}
?>

Да, здесь только начало, как такового фреймворка, по сути, нет.

YD WordPress plugins framework

Этот framework уже требуется PHP5, что объяснимо. Последнее обновление – конец 2010 года. Скачал я его через консоль wordpress, активировать его невозможно, имем ошибку. Причина – архив не содержит всех необходимых файлов. Итого – в репозитории wordpress этот фреймворк “битый”. Скачать его (фреймворк) можно только в репозитории WordPress с каким-нибудь плагином с префиксом YD.

Пример плагина:

<?php
/**
 * @package YD_Wordpress-plugins-framework
 * @author Yann Dubois
 * @version 0.1.0
 */

/*
 Plugin Name: YD WordPress Plugins Framework
 Plugin URI: http://www.yann.com/en/wp-plugins/yd-wordpress-plugins-framework
 Description: An object oriented PHP framework for building WordPress plugins and widgets. | Funded by <a href="http://www.abc.fr">ABC.FR</a>
 Version: 0.1.0
 Author: Yann Dubois
 Author URI: http://www.yann.com/
 License: GPL2
 */

/**
 * @copyright 2010  Yann Dubois  ( email : yann _at_ abc.fr )
 *
 *  Original development of this plugin was kindly funded by http://www.abc.fr
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
 Revision 0.1.0:
 - Original beta release
 */

include_once( 'inc/yd-widget-framework.inc.php' );

/**
 * 
 * Just fill up necessary settings in the configuration array
 * to create a new custom plugin instance...
 * 
 */
$junk = new YD_Plugin( 
    array(
        'name'                 => 'YD Plugin',
        'version'            => '0.1.0',
        'has_option_page'    => false,
        'has_shortcode'        => false,
        'has_widget'        => false,
        'widget_class'        => '',
        'has_cron'            => false,
        'crontab'            => array(
            'daily'            => array( 'YD_MiscWidget', 'daily_update' ),
            'hourly'        => array( 'YD_MiscWidget', 'hourly_update' )
        ),
        'has_stylesheet'    => false,
        'stylesheet_file'    => 'css/yd.css',
        'has_translation'    => false,
        'translation_domain'=> '', // must be copied in the widget class!!!
        'translations'        => array(
            array( 'English', 'Yann Dubois', 'http://www.yann.com/' ),
            array( 'French', 'Yann Dubois', 'http://www.yann.com/' )
        ),        
        'initial_funding'    => array( 'Yann.com', 'http://www.yann.com' ),
        'additional_funding'=> array(),
        'form_blocks'        => array(
            'block1' => array( 
                'test'    => 'text' 
            )
        ),
        'option_field_labels'=>array(
                'test'    => 'Test label'
        ),
        'option_defaults'    => array(
                'test'        => 'whatever'
        ),
        'form_add_actions'    => array(
                'Manually run hourly process'    => array( 'YD_MiscWidget', 'hourly_update' ),
                'Check latest'                    => array( 'YD_MiscWidget', 'check_update' )
        ),
        'has_cache'            => false,
        'option_page_text'    => 'This plugin has no option... yet!',
        'backlinkware_text' => 'Features Plugin developed using YD Plugin Framework',
        'plugin_file'        => __FILE__        
     )
);

/**
 * 
 * You must specify a unique class name
 * to avoid collision with other plugins...
 * 
 */
class YD_MiscWidget extends YD_Widget {
    
    function do_things( $op ) {
        // do things
        $option_key = 'yd-plugin';
        $options = get_option( $option_key );
        
        $op->error_msg .= 'Great.';
        $op->update_msg .= 'Cool.';
        
        update_option( 'YD_P_last_action', time() );
    }
    
    function hourly_update( $op ) {
        if( !$op || !is_object( $op ) ) {
            $op = new YD_OptionPage(); //dummy object
        }
        self::do_things( &$op );
        update_option( 'YD_P_hourly', time() );
    }
    
    function daily_update( $op ) {
        if( !$op || !is_object( $op ) ) {
            $op = new YD_OptionPage(); //dummy object
        }
        self::do_things( &$op );
        update_option( 'YD_P_daily', time() );
    }
    
    function check_update( $op ) {
        $op->update_msg .= '<p>';
        if( $last = get_option( 'YD_P_daily' ) ) {
            $op->update_msg .= 'Last daily action was on: ' 
                . date( DATE_RSS, $last ) . '<br/>';
        } else { 
            $op->update_msg .= 'No daily action yet.<br/>';
        }
        if( $last = get_option( 'YD_P_hourly' ) ) {
            $op->update_msg .= 'Last hourly action was on: ' 
                . date( DATE_RSS, $last ) . '<br/>';
        } else { 
            $op->update_msg .= 'No hourly action yet.<br/>';
        }
        if( $last = get_option( 'YD_P_last_action' ) ) {
            $op->update_msg .= 'Last completed action was on: ' 
                . date( DATE_RSS, $last ) . '<br/>';
        } else { 
            $op->update_msg .= 'No recorded action yet.<br/>';
        }
        $op->update_msg .= '</p>';
    }
}
?>

Концепция опять не слишком близка к ООП. Я бы даже сказал – дальше, чем у предыдущих проектов. По сути – это фреймворк под конкретного разработчика, практически. Учитывая отсутствие документации и цивилизованного общедоступного версионного хранилища – я бы не стал Вам рекомендовать использовать этот фреймворк.

WordPress OO plugin framework

Пожалуй, один из самых свежих фреймворков. Последнее изменение – 13 июля 2011 года.

 

Пишем плагин для WordPress: ищем framework

К сожалению по данному фреймворку сказать могу немного. Выложил выше видео от автора с примером использования фреймворка. Доступен фреймворк здесь: https://github.com/Sitebase/wpframework.

Пример плагина:

<?php

/*
Plugin Name: Plugin Example
Plugin URI: http://www.sitebase.be
Description: This is an example plugin created with the WordPress Framework by Sitebase
Author: Sitebase
Version: 1.0
Requires at least: 2.8
Author URI: http://www.sitebase.be
*/
    
// Include library
if(!class_exists('WpFramework_Base_0_5')) include "library/wp-framework/Base.php";
if(!class_exists('WpFramework_Vo_Form')) include_once "library/wp-framework/Vo/Form.php";

class PluginExample extends WpFramework_Base_0_5 {
        
        const NAME = 'Plugin Example';
        const NAME_SLUG = 'plugin-example';
    
        /**
         * Constructor
         * 
         * @return void
         */
        public function __construct(){
            
            // Call parent constructor
            parent::__construct();

            // Define form handlers
            $this->load(array('Abstract', 'NotEmpty', 'Integer', 'FileSize'), 'WpFramework_Validators_');
            $validators['firstname'][] = new WpFramework_Validators_NotEmpty(__('This field is required'));
            $validators['lastname'][] = new WpFramework_Validators_NotEmpty(__('This field is required'));
            $validators['age'][] = new WpFramework_Validators_Integer(__('Make sure this field is between 10 and 99.'));
            $validators['age'][] = new WpFramework_Validators_NotEmpty(__('This field is required.'));
            $validators['avatar'][] = new WpFramework_Validators_FileSize(__('Maximum 200'), 100000);
            $this->add_form_handler('save-settings', $validators, array(&$this, 'save_settings'));
            
        }
        
        /**
         * Add item to admin menu
         *
         * @return void
         */
        public function action_admin_menu(){
            $plugin_page = $this->add_options_page('Framework Options', 'Framework Example', self::USER_LEVEL_SUBSCRIBER, self::NAME_SLUG, array(&$this, "admin_page_show"));
        }
        
        /**
         * Load stylesheet
         * 
         * @return void
         */
        public function action_admin_print_styles() {
            if($_GET['page'] == self::NAME_SLUG) {
                $this->enqueue_style('wpframeworktest-style',  $this->plugin_url . '/assets/style.css', null, '1.0'); 
            }
        }
        
        /**
         * Load javascript
         * 
         * @return void
         */
        public function action_admin_print_scripts() {
            if($_GET['page'] == self::NAME_SLUG) {
                $this->enqueue_script('jquery');
                $this->enqueue_script('wpframeworktest-style',  $this->plugin_url . '/assets/script.js', array("jquery"), '1.0'); 
            }
        }
        
        /**
         * Load settings page & handle form submit
         *
         * @return void
         */
        public function admin_page_show(){

            // Add selected page
            // @todo In framework
            $data['page'] = isset($_GET['tab']) && in_array($_GET['tab'], array("settings", "help")) ? $_GET['tab'] : "settings";

            $data['options'] = $this->get_option( self::NAME_SLUG );

            // Validate fields and trigger form handler
            //$data['validation'] = $this->handle_form();
            $data['wpform'] = $this->auto_handle_forms($data['options']);

            // Make sure the data is secure to display
            $clean_data = $this->clean_display_array($data);
        
            // Load view
            // The tab to show is handled in the index.php view
            $this->load_view($this->plugin_path . "/views/plugin-index.php", $clean_data);
        }
        
        /**
         * Handle save settings
         *
         * @param array $data
         * @return void
         */
        public function save_settings(&$form){
            $this->save_option(self::NAME_SLUG, $form->getFields());
        }
        
        /**
         * Add a settings link to the plugin overview page
         * 
         * @param array $links
         * @return array
         */
        public function filter_plugin_action_links($links){
            $settings_link = '<a href="options-general.php?page=' . self::NAME_SLUG . '">Settings</a>'; 
              array_unshift($links, $settings_link); 
            return $links;
        }
        
        /**
         * Add some content to the footer
         * 
         * @return void
         */
        public function action_get_footer() {
            $options = $this->get_option(self::NAME_SLUG);
            echo 'My name is ' . $options['firstname'] . ' ' . $options['lastname'];
            if(is_numeric($options['age'])) {
                echo ' and my age is ' . $options['age'];
            }
            echo '.';
        }
        
        /**
         * Delete option when the plugin is deactivated
         * Clean up your garbage!
         * 
         * @return void
         */
        public function deactivate() {
            $this->delete_option( self::NAME_SLUG );
        }

}
    
$_GLOBALS['plugin-example'] = new PluginExample();

Пока только положительные моменты вижу:

  • код консоли грузится, по сути, только с консолью;
  • также разделено “представление” (view) и код. “Представления” подгружается из отдельных файлов только тогда, когда требуется (что также позволяет сделать код и прозрачнее, и понятнее, и самое главное – производительнее);
  • подход именно ООП, что мне очень по душе;
  • введена иерархия валидаторов для форм (для параметров в консоли), грузятся валидаторы правда несколько раньше, чем требуется, на мой взгляд. Да и во frontend им делать нечего, а они и туда грузятся.

Но в общем и целом рекомендую к использованию.

NerdyIsBack Plugin Framework

Ещё один фреймворк. Приведу сразу пример, там одни комментарии, но они всё и пояснят нам.

<?php
/*
Plugin Name: Name Of The Plugin
Plugin URI: http://URI_Of_Page_Describing_Plugin_and_Updates
Description: A brief description of the Plugin.
Version: The Plugin's Version Number, e.g.: 1.0
Author: Name Of The Plugin Author
Author URI: http://URI_Of_The_Plugin_Author
License: A "Slug" license name e.g. MIT
*/

/*    Copyright (c) 2010 AUTHOR NAME

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/

/**
 * Writing a WordPress Plugin on the NerdyIsBack Plugin Framework
 * By: David Beveridge <davidjbeveridge@gmail.com>, framework author
 * 
 * STANDARD PROCEDURE
 * 
 * As usual, go ahead and edit the header in this file so that WordPress know it's supposed to be
 * a plugin.  Don't forget to change the copyright notice to your name or company or whatever.
 * Also, since you're using my framework, you could mention my name, maybe?  Not strictly necessary,
 * but everyone like being appreciated...
 * 
 * SANITY CHECK
 * 
 * First of all, did you install the NerdyIsBack Plugin Framework? Moreover, did
 * you remember to activate the plugin?  If not, go do that right now so we don't
 * get a bunch of fatal errors when we activate this one.  Also, check the documentaion
 * to make sure you installed everything right.  You might want to include
 * that in YOUR documentation, btw.  Oh, and indicating somewhere that the
 * NerdyIsBack Plugin Framework is a dependency might be a good idea.
 * 
 * INTRO
 * 
 * Ok, let's talk a little about how this all works, then.  You may have noticed that
 * there are several subdirectories in this directory.  Hopefully, their names are
 * somewhat self-explanatory; if you're not familiar with MVC Architecture, I'm not sure
 * I'm qualified to teach it to you, but I'll brief you.  If you already know this, skip to
 * DIRECTORY STRUCTURE to save some time. MVC stands for Model-View-Controller; it's
 * a design pattern (actually, it's an architecture, or a specific combination of design
 * patterns) designed to separate your application (in this case, a WordPress plugin) into
 * very modular pieces.
 * 
 *     -Models are supposed to encapsulate data access, whether it's with a database,
 *      a filesystem, or an external API.
 *     -Views are what the User gets.  With web applications, you're likely to write your views
 *   in HTML/PHP, but there are cases where you'll want to use other formats like XML, JSON,
 *   or maybe something more complicated like PDF.
 *  -Controllers are responsible for controlling the application flow and user interaction.
 *   Please, try to control your surprise.  Controllers find out what the user is requesting,
 *   get data, do some processing, magic happens, etc., and then they load views and spit them
 *   back out at the user.
 * 
 * Once you wrap you mind around the concept, application development and maintanence become a
 * breeze.
 * 
 * DIRECTORY STRUCTURE
 * 
 * All that being said, this plugin has some directories your should know about:
 *         /controller
 *             This is where your Controller classes will live
 *         /lib
 *             This directory is for your code libraries.  Maybe you have one that does DB access
 *             or something?
 *             /vendor
 *                 This is where you put code libraries you didn't write.  Maybe you downloaded Smarty,
 *                 or some PEAR libraries, or something; I don't know.
 *         /model
 *             This is where your data model classes will live.
 *         /plugin
 *             This is where your plugin's resources will live:
 *             /css
 *                 CSS you want to use
 *             /js
 *                 JavaScript files you want to use
 *             /images
 *                 images you want to use
 *         /view
 *             This is where views (of any type) live.  I recommend you add some subdirectories to break
 *             things up a little more so you don't end up with a big ol' mess.
 * 
 * CONVENTIONS
 * 
 * To preface: I wrote several conventions into the NerdyIsBack Plugin Framework, mostly for ease of use,
 * and also to prevent you from having to create a large configuration file to make things work.  This may
 * be worked into a future release (j/k).  I have tried not to be terribly restrictive with any conventions,
 * nor to too strongly deviate from accepted norms in convention or practice among OO developers, though
 * I expect there will always be some contention on this manner; if you don't like these conventions, feel
 * free to create your own branch.
 * 
 * I use a few conventions in the framework you might like (or hate):
 * 
 * Class names are all UpperCamelCased
 * Object (instance) names are lowerCamelCased
 * Static methods use_underscores_in_their_names().  I'm not sure I remembered to adhere to this...
 * Files containing class definitions are named like ClassName.class.php because they're special.
 * Method/Function names are lowerCamelCased
 * Variable names are lowerCamelCased
 * private and protected members (vars and methods) are _prefixed() with an $_underscore.
 * Constants (declared in-class with const or otherwise defined()) SHOULD_BE_UPPERCASE_AND_USE_UNDERSCORES
 * I think tabs are easier to read than spaces, so I use tabs.
 * I prefer same-line curly braces( { ).  This means I write blocks like this:
 *         if( condition ) {     <--- Curly brace goes here
 *         ...
 *      }
 * I put things in as many different files as possible, in case I want to access them from somewhere else.
 * I don't use Hungarian Notation because PHP's typing is too loose to care.
 * Comments are really good.
 * @todo JDoc-style comments are even better.
 * 
 * None of the above is a requirement, but it may be a qualifier for gold stars, brownie points, and riding
 * in the front seat.
 * 
 * In order for your plugin to work with the NerdyIsBack Plugin Framework, you MUST stick to the default
 * directory structure and follow the following naming conventions:
 * 
 * The name of your plugin's base class can be literaly anthing you want, but you MUST remember the name
 * you pass into the NIB_Plugin::instance() method.  This name will be the prefix that is used to instantiate
 * all of your classes.  Having such a prefix helps prevent namespace pollution.  Could we fix this by using
 * Namespaces? You bet, but then everyone would have to run >= PHP 5.3, so we are going to use prefixes.
 * 
 * Controllers and Models all get prefixed with something like 'MyPlugin_', where MyPlugin is the instance
 * name you passed to NIB_Plugin::instance().  They should also be suffixed with either '_Controller' or 
 * '_Model', respectively.  The name of the controller or model is just the name, though, as is this the
 * case with the file containing that class.  For example, if I want the 'Default' controller, it will be
 * in controllers/Default.php and the class name will be 'MyPlugin_Default_Controller'.
 * 
 * GETTING STARTED
 * 
 * I know, I know, you've been reading (or skipping) for over 100 lines and we're not started yet. So, what do
 * we need to do first?  Let's start by requiring all our dependencies.  This includes the class you will
 * be writing to encapsulate your plugin.  You should have this class in the 'plugin/' directory.  You
 * don't need to worry about including any of the NerdyIsBack Plugin Framework classes, as those should already
 * be loaded. 
 */

require_once(dirname(__FILE__).'/lib/MyPlugin.class.php');

/**
 * Since this is the simples plugin on the planet so far, that's the only dependency I have, but if you have
 * others, you should go ahead and include them, too.
 * 
 * Now, if you hadn't figured it out yet, I'm planning on using "MyPlugin" as the instance name, and therefore,
 * the prefix for everything under the sun.  Go ahead and open up lib/MyPlugin.class.php.  You can read the
 * comments I left in there, too.  How hard is it to write a plugin?  It's as easy as:
 */

NIB_Plugin(
    'MyPlugin', /* Instance Name/Prefix */
    'MyPlugin', /* Class name*/
    dirname(plugin_basename(__FILE__)) /* the name of the folder containing this plugin. */
);

/**
 * Wasn't that easy?  Now, technically, our plugin works, but it doesn't really do anything.  We should probably
 * figure out some way to interact with our users, don't you think?
 * 
 * CONTROLLERS
 * 
 * If we're going to interact with users, we're going to need a Controller.  Writing your first controller is really
 * easy; just create a new file in the controller subdirectory with a name like MyController.php, where
 * MyController is the controller name you want to use.  In that file, declare a new class, called
 * MyPlugin_MyController_Controller (remember that MyPlugin is our plugin's instance name), and make sure that it
 * extends NIB_Controller--otherwise it won't work correctly.  You'll also need to write two
 * methods: __construct() and index().  __construct() is the class's constructor (duh) and index() is the method
 * called if no other method name is passed.
 * 
 * __construct() needs to call parent::__construct() so it can take full advantage of NIB_Controller.  You can also
 * do anything else you want to in your constructor.  Maybe this constrctor needs access-protection for all of its
 * methods? You can just write it in the constructor instead of doing it in every method.  Maybe you have a
 * composite view that is used by some or all of your methods? I'd use the contstructor to store an instance of it
 * as a private member so you can access it from all the other methods.  Remember, don't repeat yourself; it's
 * redundant.  Remember, don't repeat yourself; it's redundant.
 * 
 * You might want to implement index() a little different, too.  The default implementation just throws a
 * warning telling you to reimplement the method.
 * 
 * So you've written a controller; now what?  How do you get in touch with the user?
 * 
 * For reference, and to better see how Controllers and Views interact, check out controller/Default.php
 * 
 * VIEWS
 * 
 * I have to say, I'm a little proud of the way I've done views here.  My implementation has two different
 * kinds of views: NIB_View and NIB_CompositeView (you can roll your own, too).  NIB_View objects just load
 * a template file and shove some variables into it.  This may well be sufficient for what you're doing.  If
 * so, kudos.  If not, try NIB_CompositeView.  It can have children.  And if any of them is a NIB_CompositView,
 * they can have children, too.  You can have your own little view family--or big view family.  And when you
 * attach a NIB_CompositeView, it takes care of the heavy lifting involded in display, too. Oh, did I mention
 * that you don't have to worry about variable scope? If a parent and a child template both have a variable named
 * $title, you don't have to worry about which is which--whatever you passed in is what you get out.  Take a look
 * at controller/Default and pay close attention to the constructor--it loads a composite view, and then index
 * attaches another view to it later.
 * 
 * Open up view/default/index.php and take a look at our template.  Pretty simple, no?  I've passed in one
 * variable, $title, which we display inside some markup.
 * 
 * Now take a look at view/wp/admin.php.  This template loads the WordPress Administration panel, so hang on to
 * it.  You may notice that right after it loads admin-header.php, it has a method call, $this->renderChildren().
 * All that method does is queues the NIB_CompositeView object to put the output from the children there.  If
 * you don't call $this->renderChildren, you won't get the output from the child views.
 * 
 * MODELS
 * 
 * So, we know how to catch a client request with a Controller and how to talk back with a view, but what if
 * we need data?  Short answer: write a Model.
 * 
 * I've left Models pretty much up to you to write.  I don't think that tightly coupling Models to a database
 * table is very useful in the context of WordPress, so if you want to couple them together, you can either
 * use good-old-fashioned SQL calls, or use a library like ActiveRecord (http://www.phpactiverecord.org/).
 * I personally don't care what you do, except to ask that you make your model conform to the directory structure
 * and naming conventions, and call the parent constructor.  You can have a look at model/Message.php for
 * an over-simplified example of a Model, and controller/Default.php's message() method to see it interact
 * with a controller.
 * 
 * TYING IT ALL TOGETHER
 * 
 * Ok, so now you've got all your Models, Views, and Controllers written, you've done unit testing, you've
 * consumed enough coffee to permanently change the color of your teeth and you're litterally shaking with
 * anticipation to get your plugin finished. Or maybe that was a mild heart attack... oh, well.  Everything
 * about your plugin is perfect--immaculate, even.  Except for one gaping problem: you don't have any way of
 * getting users to your controller.  You, sir (or madam), need to tie into the WordPress Administration panel's
 * menu system.  Well, wouldn't you know it, I thought of that too.  There is a class included with the
 * NerdyIsBack Plugin Framework called NIB_WordPress_Menu, and it's already included if you rememberd to activate
 * the NIB Plugin Framework plugin (there's a mouthful, right?).  You'll probably want to create some menu items,
 * and you should do that in this very file (or whatever file you're using as the plugin's main file.
 * 
 */

$MyPluginMenu = new NIB_WordPress_Menu(
    'MyPlugin',    // Menu title
    NIB_Plugin::instance('PostTypes')->adminURL('Default','index')    // URL, generated by a helper method.
);

$MyPluginMenu->addSubmenu(
    'User',    // Submenu Title
    NIB_Plugin::instance('PostTypes')->adminURL('Default','user','testuser') // URL, generated by helper method
);

/**
 * You may be saying to yourself: "David, you poluted the global scope! Why didn't you do that in a a function
 * or a singleton or something?"  You're absolutely right.  I just don't care very much, because this is a
 * tutorial.  Feel free to fix that.
 * 
 * So, what did we do?  We created an NIB_WordPress_Menu object and then added a submenu item to it.  Can you do
 * this same thing with a built-in WordPress function? Yes, but that would mean looking at the docs, so let's not.
 * NIB_WordPress_Menu::__construct() also takes parameters for capability, image, and position, which you might
 * want to look at (especially capability). Ditto for NIB_WordPress_Menu::addSubmenu().
 * 
 * There's a couple more notes you might wanna take, here.  First of all, did you notice how I made URL's?  I
 * used a helper method from the MyPlugin instance.  I defined it in NIB_Plugin, so don't worry about not
 * having defined it yourself.  This creates URL's that use NIB Plugin Framework's point-of-entry, wp-admin/nib.php.
 * Btw, you might wanna make sure your WP install actually has that file in the right place--sometimes the
 * NIB Plugin Framework plugin can't copy the file in there and you'll have to do it yourself.  There are two
 * separate helper methods for URL's: adminURL() and URL().  The difference?  adminURL() generates the
 * relative URI's necessary for the WP Admin Panel's menu system; URL() generates absolute URL's.
 * 
 * You should also take note of how I referenced the MyPlugin instance: I never store it in a variable.
 * NIB_Plugin::instance() remembers that I have an instance with that name stored already and just returns it,
 * so I can use the NIB_Plugin::instance() method to act on the plugin object.  Pretty handy, huh?
 * 
 * CONCLUSION
 * 
 * That's a really, really ridiculously basic tutorial on writing a plugin with the NerdyIsBack Plugin Framework.
 * Hopefullly, this will save all of us a bunch of hassle writing more complex plugins for WordPress, and more
 * imporantly, help cut down on the spaghetti-code I see in most of the plugins I've worked with (not that I'm
 * holding my breath).  If you have comments or free coffee/beer, drop me a line on my website, NerdyIsBack
 * (http://www.nerdyisback.com).
 * 
 * ADDITIONAL RESOURCES
 * 
 * WordPress Plugin Development:
 * 
 * WordPress.org: http://www.wordpress.org
 * WordPress Plugin API: http://codex.wordpress.org/Plugin_API
 * WP Plugin API Action Reference: http://codex.wordpress.org/Plugin_API/Action_Reference
 * WP Plugin API Filter Reference: http://codex.wordpress.org/Plugin_API/Filter_Reference
 * 
 * For more information about programming, design patterns, etc.:
 * 
 * The PHP Architect's Guide to PHP Design Patterns (by Jason E. Sweat): http://www.amazon.com/PHP-Architects-Guide-Design-Patterns/dp/0973589825
 * Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four book): http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
 * 
 * NOTES
 * 
 * Special Thanks to Benjamin West, who loaned me his copy of Design Patterns and has been an invaluable resource.
 * 
 */

Впечатление также резко положительное. Больше смогу сказать по результатам использования.

UFL WordPress plugin framework

И ещё один фреймворк. На этот раз – от университета Флориды. Расскажу о нём в следующий раз.

Ну и наконец,

Zend Framework WordPress plugin

Что такое Zend Framework, я думаю, никому объяснять не надо. Поэтому ожидания от Zend framework для плагинов WordPress соответствующие. Но ожидания нас обманули, к сожалению. По ссылке Вы сможете скачать не фреймворк для написания плагинов, а плагин, который облегчает использование Zend Framework в коде плагинов, обеспечивая автозагрузку кода классов Zend и разделение файлов фреймворка без необходимости их дублирования несколькими плагинами. Да, сам по себе он полезен, но к теме статьи никакого отношения не имеет, к сожалению.

Проверку в плагинах нам предлагают делать следующим образом:

function my_plugin_check_zend_framework()
{
  // if the ZF plugin is successfully loaded this constant is set to true
  if (defined('WP_ZEND_FRAMEWORK') && constant('WP_ZEND_FRAMEWORK')) {
    return true;
  }
  // you can also check if ZF is available on the system
  $paths = explode(PATH_SEPARATOR, get_include_path());
  foreach ($paths as $path) {
    if (file_exists("$path/Zend/Loader.php")) {
      define('WP_ZEND_FRAMEWORK', true);
      return true;
    }
  }
  // nothing found, you may advice the user to install the ZF plugin
  define('WP_ZEND_FRAMEWORK', false);
}
 
add_action('init', 'my_plugin_check_zend_framework');

На этом пока всё. Точнее – не так. Это пока всё, что я нашёл. И из всего многообразия мне пока понравился OO Framework, который и попробую на практике – попробую переписать p3chat plugin с использованием указанного фреймворка. Но – это уже тема следующей статьи.

Отзывы » (2)

  1. Hello! Just want to say thank you for this interesting article! =) Peace, Joy.

  2. ArtCreator:

    Интересная статья но не для чайника по ходу…( все ровно спасибо.Рекомендация: после каждой статьи сделать ссылку «Дальше» - по последовательности. Так будет удобнее мне кажется но это чисто личное мнение

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка