Skip to content

Commit

Permalink
Plugin system - CORE
Browse files Browse the repository at this point in the history
I retrieved @nodiscc plugin system proposed in #164 and changed it to create PHP plugin system. It relies on hooks triggered by certain actions (only template rendering for now).

**It's only a proposition, let me know what you think of it**.

  * I think that an 'only template' plugin system might be too restrictive, and doesn't allow a lot of extension.
  * I raised concerns in #44  and don't blend too well with user made templates.
  * @nodiscc lacks of time to finish this.
  * I'd like to see 0.9beta release one day. :)

PluginManager class includes enabled plugin PHP files at loading and register their hook function.

When we want to trigger a hook, 'PluginManager::executeHooks()' is called, eventually with rendering data. It will call every plugin function registered under the standardized name:

    hook_<plugin_name>_<hook_name>

Rendering data can be altered and/or completed.

This is exactly what @nodiscc did.

Templates contain plugin display at specific location, which are populated by the plugin functions.

Here is what's has been done:

  * hook_render_linklist: when linklist is rendered, all links data passed to plugins. It allows plugins to alter link rendering (such as Markdown parsing). They can also add a linklist icon for every link (QRCode, etc.)
  * hook_render_header: every page builder triggers this hook. Plugins can add specific data to header if the current page is concerned (toolbar).
  * hook_render_footer: : every page builder triggers this hook. Plugins can add specific data to header if the current page is concerned (JS file).
  * hook_render_includes: : every page builder triggers this hook. Plugins can add specific data to header if the current page is concerned (CSS file).

We can easily add hooks to whatever is pertinent (link add, picwal rendering, etc.).

  * Strong documentation, especially for plugin developers.
  * Unit tests for PluginManger and Router.
  * Test this heavily.

Later:

  * finish Markdown plugin.
  * Add a plugin page in administration.
  • Loading branch information
ArthurHoaro committed Jul 15, 2015
1 parent 874f858 commit 59ed397
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 44 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ composer.lock
coverage
tests/datastore.php
phpmd.html

# Ignore user plugin configuration
plugins/*/config.php
7 changes: 6 additions & 1 deletion application/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ function writeConfig($config, $isLoggedIn)
foreach ($config['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
}
$configStr .= '?>';

if (isset($config['plugins'])) {
foreach ($config['plugins'] as $key => $value) {
$configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL;
}
}

if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
|| strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
Expand Down
118 changes: 118 additions & 0 deletions application/Plugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

/**
* Class PluginManager
*
* Use to manage, load and execute plugins.
*/
class PluginManager
{
/**
* @var array - list of authorized plugins from configuration file.
*/
public static $AUTHORIZED_PLUGINS = array();

/**
* @var array - list of loaded plugins.
*/
private static $_LOADED_PLUGINS = array();

/**
* @var string - plugins subdirectory.
*/
public static $PLUGINS_PATH = 'plugins';

/**
* Load plugins listed in $AUTHORIZED_PLUGINS.
*/
public static function load() {
$dirs = glob(self::$PLUGINS_PATH . '/*' , GLOB_ONLYDIR);
$dirnames = array_map('basename', $dirs);
foreach (self::$AUTHORIZED_PLUGINS as $plugin) {
$index = array_search($plugin, $dirnames);
if ($index !== false) {
try {
self::loadPlugin($dirs[$index], $plugin);
}
catch (PluginFileNotFoundException $e) {
error_log($e->getMessage());
}
}
}
}

/**
* Load a single plugin from its files.
* Add them in $__LOADED_PLUGINS if successful.
*
* @param string $dir - plugin's directory.
* @param string $pluginName - plugin's name.
* @throws PluginFileNotFoundException - plugin files not found.
*/
private static function loadPlugin($dir, $pluginName) {
if (!is_dir($dir)) {
throw new PluginFileNotFoundException($pluginName);
}

$pluginFilePath = $dir . '/' . $pluginName . '.php';
if (!is_file($pluginFilePath)) {
throw new PluginFileNotFoundException($pluginName);
}

include $pluginFilePath;

self::$_LOADED_PLUGINS[] = $pluginName;
}

/**
* Execute all plugins registered hook.
*
* @param string $hook - name of the hook to trigger.
* @param array $data - list of data to manipulate passed by reference.
* @param array $params - additional parameters such as page target for common templates.
*/
public static function executeHooks($hook, &$data, $params = array()) {
if (!empty($params['target'])) {
$data['_PAGE_'] = $params['target'];
}

if (isset($params['loggedin'])) {
$data['_LOGGEDIN_'] = $params['loggedin'];
}

foreach (self::$_LOADED_PLUGINS as $plugin) {
$hookFunction = self::buildHookName($hook, $plugin);

if (function_exists($hookFunction)) {
$data = call_user_func($hookFunction, $data);
}
}
}

/**
* Construct normalize hook name for a specific plugin.
*
* Format:
* hook_<plugin_name>_<hook_name>
*
* @param $hook - hook name.
* @param $pluginName - plugin name.
*
* @return string - plugin's hook name.
*/
public static function buildHookName($hook, $pluginName) {
return 'hook_' . $pluginName . '_' . $hook;
}
}

/**
* Class PluginFileNotFoundException
*
* Raise when plugin files can't be found.
*/
class PluginFileNotFoundException extends Exception
{
public function __construct($pluginName) {
$this->message = 'Plugin "'. $pluginName .'" files not found.';
}
}
100 changes: 100 additions & 0 deletions application/Router.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

/**
* Class Router
*
* (only displayable pages here)
*/
class Router {

public static $PAGE_LOGIN = 'login';

public static $PAGE_PICWALL = 'picwall';

public static $PAGE_TAGCLOUD = 'tagcloud';

public static $PAGE_TOOLS = 'tools';

public static $PAGE_CHANGEPASSWORD = 'changepasswd';

public static $PAGE_CONFIGURE = 'configure';

public static $PAGE_CHANGETAG = 'changetag';

public static $PAGE_ADDLINK = 'addlink';

public static $PAGE_EDITLINK = 'edit_link';

public static $PAGE_EXPORT = 'export';

public static $PAGE_IMPORT = 'import';

public static $PAGE_LINKLIST = 'linklist';

/**
* Reproducing renderPage() if hell, to avoid regression.
*
*
* This highlights how bad this needs to be rewrite,
* but let's focus on plugins for now.
*
* @param string $query $_SERVER['QUERY_STRING'].
* @param array $get $_SERVER['GET'].
* @param bool $loggedIn true if authenticated user.
*
* @return self::page found.
*/
public static function findPage($query, $get, $loggedIn) {

if (isset($query) && startswith($query, 'do='. self::$PAGE_LOGIN)) {
return self::$PAGE_LOGIN;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_PICWALL)) {
return self::$PAGE_PICWALL;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_TAGCLOUD)) {
return self::$PAGE_TAGCLOUD;
}

// At this point, only loggedin pages.
if (!$loggedIn) {
return self::$PAGE_LINKLIST;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_TOOLS)) {
return self::$PAGE_TOOLS;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) {
return self::$PAGE_CHANGEPASSWORD;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_CONFIGURE)) {
return self::$PAGE_CONFIGURE;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_CHANGETAG)) {
return self::$PAGE_CHANGETAG;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_ADDLINK)) {
return self::$PAGE_ADDLINK;
}

if (isset($get['edit_link']) || isset($get['post'])) {
return self::$PAGE_EDITLINK;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_EXPORT)) {
return self::$PAGE_EXPORT;
}

if (isset($query) && startswith($query, 'do='. self::$PAGE_IMPORT)) {
return self::$PAGE_IMPORT;
}

return self::$PAGE_LINKLIST;
}
}
Loading

0 comments on commit 59ed397

Please sign in to comment.