Plugin system - CORE

see shaarli/Shaarli#275
This commit is contained in:
ArthurHoaro 2015-07-15 11:42:15 +02:00
parent 38bedfbbcd
commit 6fc14d5303
8 changed files with 1190 additions and 197 deletions

3
.gitignore vendored
View file

@ -21,3 +21,6 @@ coverage
tests/datastore.php tests/datastore.php
tests/dummycache/ tests/dummycache/
phpmd.html phpmd.html
# Ignore user plugin configuration
plugins/*/config.php

7
application/Config.php Executable file → Normal file
View file

@ -56,7 +56,12 @@ function writeConfig($config, $isLoggedIn)
foreach ($config['config'] as $key => $value) { foreach ($config['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL; $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) if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
|| strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0 || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0

View file

@ -0,0 +1,184 @@
<?php
/**
* Class PluginManager
*
* Use to manage, load and execute plugins.
*
* Using Singleton design pattern.
*/
class PluginManager
{
/**
* PluginManager singleton instance.
* @var PluginManager $instance
*/
private static $instance;
/**
* List of authorized plugins from configuration file.
* @var array $authorizedPlugins
*/
private $authorizedPlugins;
/**
* List of loaded plugins.
* @var array $loadedPlugins
*/
private $loadedPlugins = array();
/**
* Plugins subdirectory.
* @var string $PLUGINS_PATH
*/
public static $PLUGINS_PATH = 'plugins';
/**
* Private constructor: new instances not allowed.
*/
private function __construct()
{
}
/**
* Cloning isn't allowed either.
*
* @return void
*/
private function __clone()
{
}
/**
* Return existing instance of PluginManager, or create it.
*
* @return PluginManager instance.
*/
public static function getInstance()
{
if (!(self::$instance instanceof self)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Load plugins listed in $authorizedPlugins.
*
* @param array $authorizedPlugins Names of plugin authorized to be loaded.
*
* @return void
*/
public function load($authorizedPlugins)
{
$this->authorizedPlugins = $authorizedPlugins;
$dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR);
$dirnames = array_map('basename', $dirs);
foreach ($this->authorizedPlugins as $plugin) {
$index = array_search($plugin, $dirnames);
// plugin authorized, but its folder isn't listed
if ($index === false) {
continue;
}
try {
$this->loadPlugin($dirs[$index], $plugin);
}
catch (PluginFileNotFoundException $e) {
error_log($e->getMessage());
}
}
}
/**
* 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.
*
* @return void
*/
public function executeHooks($hook, &$data, $params = array())
{
if (!empty($params['target'])) {
$data['_PAGE_'] = $params['target'];
}
if (isset($params['loggedin'])) {
$data['_LOGGEDIN_'] = $params['loggedin'];
}
foreach ($this->loadedPlugins as $plugin) {
$hookFunction = $this->buildHookName($hook, $plugin);
if (function_exists($hookFunction)) {
$data = call_user_func($hookFunction, $data);
}
}
}
/**
* Load a single plugin from its files.
* Add them in $loadedPlugins if successful.
*
* @param string $dir plugin's directory.
* @param string $pluginName plugin's name.
*
* @return void
* @throws PluginFileNotFoundException - plugin files not found.
*/
private 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_once $pluginFilePath;
$this->loadedPlugins[] = $pluginName;
}
/**
* Construct normalize hook name for a specific plugin.
*
* Format:
* hook_<plugin_name>_<hook_name>
*
* @param string $hook hook name.
* @param string $pluginName plugin name.
*
* @return string - plugin's hook name.
*/
public function buildHookName($hook, $pluginName)
{
return 'hook_' . $pluginName . '_' . $hook;
}
}
/**
* Class PluginFileNotFoundException
*
* Raise when plugin files can't be found.
*/
class PluginFileNotFoundException extends Exception
{
/**
* Construct exception with plugin name.
* Generate message.
*
* @param string $pluginName name of the plugin not found
*/
public function __construct($pluginName)
{
$this->message = 'Plugin "'. $pluginName .'" files not found.';
}
}

105
application/Router.php Normal file
View file

@ -0,0 +1,105 @@
<?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)
{
$loggedIn = ($loggedIn === true) ? true : false;
if (empty($query) && !isset($get['edit_link']) && !isset($get['post'])) {
return self::$PAGE_LINKLIST;
}
if (startswith($query, 'do='. self::$PAGE_LOGIN) && $loggedIn === false) {
return self::$PAGE_LOGIN;
}
if (startswith($query, 'do='. self::$PAGE_PICWALL)) {
return self::$PAGE_PICWALL;
}
if (startswith($query, 'do='. self::$PAGE_TAGCLOUD)) {
return self::$PAGE_TAGCLOUD;
}
// At this point, only loggedin pages.
if (!$loggedIn) {
return self::$PAGE_LINKLIST;
}
if (startswith($query, 'do='. self::$PAGE_TOOLS)) {
return self::$PAGE_TOOLS;
}
if (startswith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) {
return self::$PAGE_CHANGEPASSWORD;
}
if (startswith($query, 'do='. self::$PAGE_CONFIGURE)) {
return self::$PAGE_CONFIGURE;
}
if (startswith($query, 'do='. self::$PAGE_CHANGETAG)) {
return self::$PAGE_CHANGETAG;
}
if (startswith($query, 'do='. self::$PAGE_ADDLINK)) {
return self::$PAGE_ADDLINK;
}
if (isset($get['edit_link']) || isset($get['post'])) {
return self::$PAGE_EDITLINK;
}
if (startswith($query, 'do='. self::$PAGE_EXPORT)) {
return self::$PAGE_EXPORT;
}
if (startswith($query, 'do='. self::$PAGE_IMPORT)) {
return self::$PAGE_IMPORT;
}
return self::$PAGE_LINKLIST;
}
}

230
index.php
View file

@ -45,9 +45,18 @@
$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli. $GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli.
$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours $GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours
// Note: You must have publisher.php in the same directory as Shaarli index.php // Note: You must have publisher.php in the same directory as Shaarli index.php
$GLOBALS['config']['ARCHIVE_ORG'] = false; // For each link, add a link to an archived version on archive.org
$GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true; // Enable RSS permalinks by default. This corresponds to the default behavior of shaarli before this was added as an option. $GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true; // Enable RSS permalinks by default. This corresponds to the default behavior of shaarli before this was added as an option.
$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false; $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
//$GLOBALS['config']['ENABLED_PLUGINS'] = array(
// 'qrcode', 'archiveorg', 'readityourself', 'demo_plugin', 'playvideos',
// 'wallabag', 'markdown', 'addlink_toolbar',
//);
// Warning: order matters.
$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode');
// Default plugins, default config - will be overriden by config.php and then plugin's config.php file.
$GLOBALS['plugins']['READITYOUSELF_URL'] = 'http://someurl.com';
$GLOBALS['plugins']['WALLABAG_URL'] = 'https://demo.wallabag.org/';
// ----------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------
define('shaarli_version', '0.5.4'); define('shaarli_version', '0.5.4');
// http://server.com/x/shaarli --> /shaarli/ // http://server.com/x/shaarli --> /shaarli/
@ -75,6 +84,8 @@
require_once 'application/Url.php'; require_once 'application/Url.php';
require_once 'application/Utils.php'; require_once 'application/Utils.php';
require_once 'application/Config.php'; require_once 'application/Config.php';
require_once 'application/PluginManager.php';
require_once 'application/Router.php';
// Ensure the PHP version is supported // Ensure the PHP version is supported
try { try {
@ -119,6 +130,9 @@
raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory
$pluginManager = PluginManager::getInstance();
$pluginManager->load($GLOBALS['config']['ENABLED_PLUGINS']);
ob_start(); // Output buffering for the page cache. ob_start(); // Output buffering for the page cache.
@ -962,16 +976,31 @@ function showDaily()
$fill[$index]+=$length; $fill[$index]+=$length;
} }
$PAGE = new pageBuilder; $PAGE = new pageBuilder;
$PAGE->assign('linksToDisplay',$linksToDisplay); $data = array(
$PAGE->assign('linkcount',count($LINKSDB)); 'linksToDisplay' => $linksToDisplay,
$PAGE->assign('cols', $columns); 'linkcount' => count($LINKSDB),
$PAGE->assign('day',linkdate2timestamp($day.'_000000')); 'cols' => $columns,
$PAGE->assign('previousday',$previousday); 'day' => linkdate2timestamp($day.'_000000'),
$PAGE->assign('nextday',$nextday); 'previousday' => $previousday,
'nextday' => $nextday,
);
$pluginManager = PluginManager::getInstance();
$pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('daily'); $PAGE->renderPage('daily');
exit; exit;
} }
// Renders the linklist
function showLinkList($PAGE, $LINKSDB) {
buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
$PAGE->renderPage('linklist');
}
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------
// Render HTML page (according to URL parameters and user rights) // Render HTML page (according to URL parameters and user rights)
@ -983,12 +1012,36 @@ function renderPage()
$GLOBALS['config']['HIDE_PUBLIC_LINKS'] $GLOBALS['config']['HIDE_PUBLIC_LINKS']
); );
$PAGE = new pageBuilder;
// Determine which page will be rendered.
$query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
$targetPage = Router::findPage($query, $_GET, isLoggedIn());
// Call plugin hooks for header, footer and includes, specifying which page will be rendered.
// Then assign generated data to RainTPL.
$common_hooks = array(
'header',
'footer',
'includes',
);
$pluginManager = PluginManager::getInstance();
foreach($common_hooks as $name) {
$plugin_data = array();
$pluginManager->executeHooks('render_' . $name, $plugin_data,
array(
'target' => $targetPage,
'loggedin' => isLoggedIn()
)
);
$PAGE->assign('plugins_' . $name, $plugin_data);
}
// -------- Display login form. // -------- Display login form.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=login')) if ($targetPage == Router::$PAGE_LOGIN)
{ {
if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli
$token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful. $token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful.
$PAGE = new pageBuilder;
$PAGE->assign('token',$token); $PAGE->assign('token',$token);
$PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
$PAGE->renderPage('loginform'); $PAGE->renderPage('loginform');
@ -1004,7 +1057,7 @@ function renderPage()
} }
// -------- Picture wall // -------- Picture wall
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=picwall')) if ($targetPage == Router::$PAGE_PICWALL)
{ {
// Optionally filter the results: // Optionally filter the results:
$links=array(); $links=array();
@ -1027,15 +1080,22 @@ function renderPage()
} }
} }
$PAGE = new pageBuilder; $data = array(
$PAGE->assign('linkcount',count($LINKSDB)); 'linkcount' => count($LINKSDB),
$PAGE->assign('linksToDisplay',$linksToDisplay); 'linksToDisplay' => $linksToDisplay,
);
$pluginManager->executeHooks('render_picwall', $data, array('loggedin' => isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('picwall'); $PAGE->renderPage('picwall');
exit; exit;
} }
// -------- Tag cloud // -------- Tag cloud
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tagcloud')) if ($targetPage == Router::$PAGE_TAGCLOUD)
{ {
$tags= $LINKSDB->allTags(); $tags= $LINKSDB->allTags();
@ -1049,9 +1109,17 @@ function renderPage()
{ {
$tagList[$key] = array('count'=>$value,'size'=>log($value, 15) / log($maxcount, 30) * (22-6) + 6); $tagList[$key] = array('count'=>$value,'size'=>log($value, 15) / log($maxcount, 30) * (22-6) + 6);
} }
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $data = array(
$PAGE->assign('tags',$tagList); 'linkcount' => count($LINKSDB),
'tags' => $tagList,
);
$pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('tagcloud'); $PAGE->renderPage('tagcloud');
exit; exit;
} }
@ -1164,32 +1232,36 @@ function renderPage()
header('Location: ?do=login&post='); header('Location: ?do=login&post=');
exit; exit;
} }
showLinkList($PAGE, $LINKSDB);
if (isset($_GET['edit_link'])) { if (isset($_GET['edit_link'])) {
header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
exit; exit;
} }
$PAGE = new pageBuilder;
buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
$PAGE->renderPage('linklist');
exit; // Never remove this one! All operations below are reserved for logged in user. exit; // Never remove this one! All operations below are reserved for logged in user.
} }
// -------- All other functions are reserved for the registered user: // -------- All other functions are reserved for the registered user:
// -------- Display the Tools menu if requested (import/export/bookmarklet...) // -------- Display the Tools menu if requested (import/export/bookmarklet...)
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tools')) if ($targetPage == Router::$PAGE_TOOLS)
{ {
$PAGE = new pageBuilder; $data = array(
$PAGE->assign('linkcount',count($LINKSDB)); 'linkcount' => count($LINKSDB),
$PAGE->assign('pageabsaddr',index_url($_SERVER)); 'pageabsaddr' => index_url($_SERVER),
);
$pluginManager->executeHooks('render_tools', $data);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('tools'); $PAGE->renderPage('tools');
exit; exit;
} }
// -------- User wants to change his/her password. // -------- User wants to change his/her password.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=changepasswd')) if ($targetPage == Router::$PAGE_CHANGEPASSWORD)
{ {
if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.'); if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.');
if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
@ -1220,7 +1292,6 @@ function renderPage()
} }
else // show the change password form. else // show the change password form.
{ {
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken()); $PAGE->assign('token',getToken());
$PAGE->renderPage('changepassword'); $PAGE->renderPage('changepassword');
@ -1229,7 +1300,7 @@ function renderPage()
} }
// -------- User wants to change configuration // -------- User wants to change configuration
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=configure')) if ($targetPage == Router::$PAGE_CONFIGURE)
{ {
if (!empty($_POST['title']) ) if (!empty($_POST['title']) )
{ {
@ -1265,7 +1336,6 @@ function renderPage()
} }
else // Show the configuration form. else // Show the configuration form.
{ {
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken()); $PAGE->assign('token',getToken());
$PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] ); $PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] );
@ -1279,11 +1349,10 @@ function renderPage()
} }
// -------- User wants to rename a tag or delete it // -------- User wants to rename a tag or delete it
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=changetag')) if ($targetPage == Router::$PAGE_CHANGETAG)
{ {
if (empty($_POST['fromtag'])) if (empty($_POST['fromtag']))
{ {
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken()); $PAGE->assign('token',getToken());
$PAGE->assign('tags', $LINKSDB->allTags()); $PAGE->assign('tags', $LINKSDB->allTags());
@ -1328,9 +1397,8 @@ function renderPage()
} }
// -------- User wants to add a link without using the bookmarklet: Show form. // -------- User wants to add a link without using the bookmarklet: Show form.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=addlink')) if ($targetPage == Router::$PAGE_ADDLINK)
{ {
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $PAGE->assign('linkcount',count($LINKSDB));
$PAGE->renderPage('addlink'); $PAGE->renderPage('addlink');
exit; exit;
@ -1349,6 +1417,9 @@ function renderPage()
$link = array('title'=>trim($_POST['lf_title']),'url'=>$url,'description'=>trim($_POST['lf_description']),'private'=>(isset($_POST['lf_private']) ? 1 : 0), $link = array('title'=>trim($_POST['lf_title']),'url'=>$url,'description'=>trim($_POST['lf_description']),'private'=>(isset($_POST['lf_private']) ? 1 : 0),
'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags)); 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags));
if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title. if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title.
$pluginManager->executeHooks('save_link', $link);
$LINKSDB[$linkdate] = $link; $LINKSDB[$linkdate] = $link;
$LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
pubsubhub(); pubsubhub();
@ -1382,6 +1453,9 @@ function renderPage()
// - confirmation is handled by JavaScript // - confirmation is handled by JavaScript
// - we are protected from XSRF by the token. // - we are protected from XSRF by the token.
$linkdate=$_POST['lf_linkdate']; $linkdate=$_POST['lf_linkdate'];
$pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]);
unset($LINKSDB[$linkdate]); unset($LINKSDB[$linkdate]);
$LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk
@ -1423,13 +1497,20 @@ function renderPage()
{ {
$link = $LINKSDB[$_GET['edit_link']]; // Read database $link = $LINKSDB[$_GET['edit_link']]; // Read database
if (!$link) { header('Location: ?'); exit; } // Link not found in database. if (!$link) { header('Location: ?'); exit; } // Link not found in database.
$PAGE = new pageBuilder; $data = array(
$PAGE->assign('linkcount',count($LINKSDB)); 'linkcount' => count($LINKSDB),
$PAGE->assign('link',$link); 'link' => $link,
$PAGE->assign('link_is_new',false); 'link_is_new' => false,
$PAGE->assign('token',getToken()); // XSRF protection. 'token' => getToken(),
$PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : '')); 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
$PAGE->assign('tags', $LINKSDB->allTags()); 'tags' => $LINKSDB->allTags(),
);
$pluginManager->executeHooks('render_editlink', $data);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('editlink'); $PAGE->renderPage('editlink');
exit; exit;
} }
@ -1493,24 +1574,30 @@ function renderPage()
); );
} }
$PAGE = new pageBuilder; $data = array(
$PAGE->assign('linkcount',count($LINKSDB)); 'linkcount' => count($LINKSDB),
$PAGE->assign('link',$link); 'link' => $link,
$PAGE->assign('link_is_new',$link_is_new); 'link_is_new' => $link_is_new,
$PAGE->assign('token',getToken()); // XSRF protection. 'token' => getToken(), // XSRF protection.
$PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '')); 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
$PAGE->assign('source',(isset($_GET['source']) ? $_GET['source'] : '')); 'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
$PAGE->assign('tags', $LINKSDB->allTags()); 'tags' => $LINKSDB->allTags(),
);
$pluginManager->executeHooks('render_editlink', $data);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('editlink'); $PAGE->renderPage('editlink');
exit; exit;
} }
// -------- Export as Netscape Bookmarks HTML file. // -------- Export as Netscape Bookmarks HTML file.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=export')) if ($targetPage == Router::$PAGE_EXPORT)
{ {
if (empty($_GET['what'])) if (empty($_GET['what']))
{ {
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $PAGE->assign('linkcount',count($LINKSDB));
$PAGE->renderPage('export'); $PAGE->renderPage('export');
exit; exit;
@ -1562,9 +1649,8 @@ function renderPage()
} }
// -------- Show upload/import dialog: // -------- Show upload/import dialog:
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=import')) if ($targetPage == Router::$PAGE_IMPORT)
{ {
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB)); $PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken()); $PAGE->assign('token',getToken());
$PAGE->assign('maxfilesize',getMaxFileSize()); $PAGE->assign('maxfilesize',getMaxFileSize());
@ -1573,9 +1659,7 @@ function renderPage()
} }
// -------- Otherwise, simply display search form and links: // -------- Otherwise, simply display search form and links:
$PAGE = new pageBuilder; showLinkList($PAGE, $LINKSDB);
buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
$PAGE->renderPage('linklist');
exit; exit;
} }
@ -1746,7 +1830,7 @@ function buildLinkList($PAGE,$LINKSDB)
$taglist = explode(' ',$link['tags']); $taglist = explode(' ',$link['tags']);
uasort($taglist, 'strcasecmp'); uasort($taglist, 'strcasecmp');
$link['taglist']=$taglist; $link['taglist']=$taglist;
$link['shorturl'] = smallHash($link['linkdate']);
if ($link["url"][0] === '?' && // Check for both signs of a note: starting with ? and 7 chars long. I doubt that you'll post any links that look like this. if ($link["url"][0] === '?' && // Check for both signs of a note: starting with ? and 7 chars long. I doubt that you'll post any links that look like this.
strlen($link["url"]) === 7) { strlen($link["url"]) === 7) {
$link["url"] = index_url($_SERVER) . $link["url"]; $link["url"] = index_url($_SERVER) . $link["url"];
@ -1766,18 +1850,28 @@ function buildLinkList($PAGE,$LINKSDB)
$token = ''; if (isLoggedIn()) $token=getToken(); $token = ''; if (isLoggedIn()) $token=getToken();
// Fill all template fields. // Fill all template fields.
$PAGE->assign('linkcount',count($LINKSDB)); $data = array(
$PAGE->assign('previous_page_url',$previous_page_url); 'linkcount' => count($LINKSDB),
$PAGE->assign('next_page_url',$next_page_url); 'previous_page_url' => $previous_page_url,
$PAGE->assign('page_current',$page); 'next_page_url' => $next_page_url,
$PAGE->assign('page_max',$pagecount); 'page_current' => $page,
$PAGE->assign('result_count',count($linksToDisplay)); 'page_max' => $pagecount,
$PAGE->assign('search_type',$search_type); 'result_count' => count($linksToDisplay),
$PAGE->assign('search_crits',$search_crits); 'search_type' => $search_type,
$PAGE->assign('redirector',empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); // Optional redirector URL. 'search_crits' => $search_crits,
$PAGE->assign('token',$token); 'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL.
$PAGE->assign('links',$linkDisp); 'token' => $token,
$PAGE->assign('tags', $LINKSDB->allTags()); 'links' => $linkDisp,
'tags' => $LINKSDB->allTags(),
);
$pluginManager = PluginManager::getInstance();
$pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
return; return;
} }

66
tests/PluginManagerTest.php Executable file
View file

@ -0,0 +1,66 @@
<?php
/**
* Plugin Manager tests
*/
require_once 'application/PluginManager.php';
/**
* Unit tests for Plugins
*/
class PluginManagerTest extends PHPUnit_Framework_TestCase
{
/**
* Path to tests plugin.
* @var string $_PLUGIN_PATH
*/
private static $_PLUGIN_PATH = 'tests/plugins';
/**
* Test plugin.
* @var string $_PLUGIN_NAME
*/
private static $_PLUGIN_NAME = 'test';
/**
* Test plugin loading and hook execution.
*
* @return void
*/
public function testPlugin()
{
$pluginManager = PluginManager::getInstance();
PluginManager::$PLUGINS_PATH = self::$_PLUGIN_PATH;
$pluginManager->load(array(self::$_PLUGIN_NAME));
$this->assertTrue(function_exists('hook_test_random'));
$data = array(0 => 'woot');
$pluginManager->executeHooks('random', $data);
$this->assertEquals('woot', $data[1]);
$data = array(0 => 'woot');
$pluginManager->executeHooks('random', $data, array('target' => 'test'));
$this->assertEquals('page test', $data[1]);
$data = array(0 => 'woot');
$pluginManager->executeHooks('random', $data, array('loggedin' => true));
$this->assertEquals('loggedin', $data[1]);
}
/**
* Test missing plugin loading.
*
* @return void
*/
public function testPluginNotFound()
{
$pluginManager = PluginManager::getInstance();
$pluginManager->load(array());
$pluginManager->load(array('nope', 'renope'));
}
}

515
tests/RouterTest.php Executable file
View file

@ -0,0 +1,515 @@
<?php
/**
* Router tests
*/
require_once 'application/Router.php';
/**
* Unit tests for Router
*/
class RouterTest extends PHPUnit_Framework_TestCase
{
/**
* Test findPage: login page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageLoginValid()
{
$this->assertEquals(
Router::$PAGE_LOGIN,
Router::findPage('do=login', array(), false)
);
$this->assertEquals(
Router::$PAGE_LOGIN,
Router::findPage('do=login', array(), 1)
);
$this->assertEquals(
Router::$PAGE_LOGIN,
Router::findPage('do=login&stuff', array(), false)
);
}
/**
* Test findPage: login page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageLoginInvalid()
{
$this->assertNotEquals(
Router::$PAGE_LOGIN,
Router::findPage('do=login', array(), true)
);
$this->assertNotEquals(
Router::$PAGE_LOGIN,
Router::findPage('do=other', array(), false)
);
}
/**
* Test findPage: picwall page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPagePicwallValid()
{
$this->assertEquals(
Router::$PAGE_PICWALL,
Router::findPage('do=picwall', array(), false)
);
$this->assertEquals(
Router::$PAGE_PICWALL,
Router::findPage('do=picwall', array(), true)
);
}
/**
* Test findPage: picwall page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPagePicwallInvalid()
{
$this->assertEquals(
Router::$PAGE_PICWALL,
Router::findPage('do=picwall&stuff', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_PICWALL,
Router::findPage('do=other', array(), false)
);
}
/**
* Test findPage: tagcloud page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageTagcloudValid()
{
$this->assertEquals(
Router::$PAGE_TAGCLOUD,
Router::findPage('do=tagcloud', array(), false)
);
$this->assertEquals(
Router::$PAGE_TAGCLOUD,
Router::findPage('do=tagcloud', array(), true)
);
$this->assertEquals(
Router::$PAGE_TAGCLOUD,
Router::findPage('do=tagcloud&stuff', array(), false)
);
}
/**
* Test findPage: tagcloud page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageTagcloudInvalid()
{
$this->assertNotEquals(
Router::$PAGE_TAGCLOUD,
Router::findPage('do=other', array(), false)
);
}
/**
* Test findPage: linklist page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageLinklistValid()
{
$this->assertEquals(
Router::$PAGE_LINKLIST,
Router::findPage('', array(), true)
);
$this->assertEquals(
Router::$PAGE_LINKLIST,
Router::findPage('whatever', array(), true)
);
$this->assertEquals(
Router::$PAGE_LINKLIST,
Router::findPage('whatever', array(), false)
);
$this->assertEquals(
Router::$PAGE_LINKLIST,
Router::findPage('do=tools', array(), false)
);
}
/**
* Test findPage: tools page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageToolsValid()
{
$this->assertEquals(
Router::$PAGE_TOOLS,
Router::findPage('do=tools', array(), true)
);
$this->assertEquals(
Router::$PAGE_TOOLS,
Router::findPage('do=tools&stuff', array(), true)
);
}
/**
* Test findPage: tools page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageToolsInvalid()
{
$this->assertNotEquals(
Router::$PAGE_TOOLS,
Router::findPage('do=tools', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_TOOLS,
Router::findPage('do=tools', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_TOOLS,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: changepasswd page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageChangepasswdValid()
{
$this->assertEquals(
Router::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd', array(), true)
);
$this->assertEquals(
Router::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd&stuff', array(), true)
);
}
/**
* Test findPage: changepasswd page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageChangepasswdInvalid()
{
$this->assertNotEquals(
Router::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_CHANGEPASSWORD,
Router::findPage('do=changepasswd', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_CHANGEPASSWORD,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: configure page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageConfigureValid()
{
$this->assertEquals(
Router::$PAGE_CONFIGURE,
Router::findPage('do=configure', array(), true)
);
$this->assertEquals(
Router::$PAGE_CONFIGURE,
Router::findPage('do=configure&stuff', array(), true)
);
}
/**
* Test findPage: configure page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageConfigureInvalid()
{
$this->assertNotEquals(
Router::$PAGE_CONFIGURE,
Router::findPage('do=configure', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_CONFIGURE,
Router::findPage('do=configure', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_CONFIGURE,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: changetag page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageChangetagValid()
{
$this->assertEquals(
Router::$PAGE_CHANGETAG,
Router::findPage('do=changetag', array(), true)
);
$this->assertEquals(
Router::$PAGE_CHANGETAG,
Router::findPage('do=changetag&stuff', array(), true)
);
}
/**
* Test findPage: changetag page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageChangetagInvalid()
{
$this->assertNotEquals(
Router::$PAGE_CHANGETAG,
Router::findPage('do=changetag', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_CHANGETAG,
Router::findPage('do=changetag', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_CHANGETAG,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: addlink page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageAddlinkValid()
{
$this->assertEquals(
Router::$PAGE_ADDLINK,
Router::findPage('do=addlink', array(), true)
);
$this->assertEquals(
Router::$PAGE_ADDLINK,
Router::findPage('do=addlink&stuff', array(), true)
);
}
/**
* Test findPage: addlink page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageAddlinkInvalid()
{
$this->assertNotEquals(
Router::$PAGE_ADDLINK,
Router::findPage('do=addlink', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_ADDLINK,
Router::findPage('do=addlink', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_ADDLINK,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: export page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageExportValid()
{
$this->assertEquals(
Router::$PAGE_EXPORT,
Router::findPage('do=export', array(), true)
);
$this->assertEquals(
Router::$PAGE_EXPORT,
Router::findPage('do=export&stuff', array(), true)
);
}
/**
* Test findPage: export page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageExportInvalid()
{
$this->assertNotEquals(
Router::$PAGE_EXPORT,
Router::findPage('do=export', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_EXPORT,
Router::findPage('do=export', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_EXPORT,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: import page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageImportValid()
{
$this->assertEquals(
Router::$PAGE_IMPORT,
Router::findPage('do=import', array(), true)
);
$this->assertEquals(
Router::$PAGE_IMPORT,
Router::findPage('do=import&stuff', array(), true)
);
}
/**
* Test findPage: import page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageImportInvalid()
{
$this->assertNotEquals(
Router::$PAGE_IMPORT,
Router::findPage('do=import', array(), 1)
);
$this->assertNotEquals(
Router::$PAGE_IMPORT,
Router::findPage('do=import', array(), false)
);
$this->assertNotEquals(
Router::$PAGE_IMPORT,
Router::findPage('do=other', array(), true)
);
}
/**
* Test findPage: editlink page output.
* Valid: page should be return.
*
* @return void
*/
public function testFindPageEditlinkValid()
{
$this->assertEquals(
Router::$PAGE_EDITLINK,
Router::findPage('whatever', array('edit_link' => 1), true)
);
$this->assertEquals(
Router::$PAGE_EDITLINK,
Router::findPage('', array('edit_link' => 1), true)
);
$this->assertEquals(
Router::$PAGE_EDITLINK,
Router::findPage('whatever', array('post' => 1), true)
);
$this->assertEquals(
Router::$PAGE_EDITLINK,
Router::findPage('whatever', array('post' => 1, 'edit_link' => 1), true)
);
}
/**
* Test findPage: editlink page output.
* Invalid: page shouldn't be return.
*
* @return void
*/
public function testFindPageEditlinkInvalid()
{
$this->assertNotEquals(
Router::$PAGE_EDITLINK,
Router::findPage('whatever', array('edit_link' => 1), false)
);
$this->assertNotEquals(
Router::$PAGE_EDITLINK,
Router::findPage('whatever', array('edit_link' => 1), 1)
);
$this->assertNotEquals(
Router::$PAGE_EDITLINK,
Router::findPage('whatever', array(), true)
);
}
}

21
tests/plugins/test/test.php Executable file
View file

@ -0,0 +1,21 @@
<?php
/**
* Hook for test.
*
* @param array $data - data passed to plugin.
*
* @return mixed altered data.
*/
function hook_test_random($data)
{
if (isset($data['_PAGE_']) && $data['_PAGE_'] == 'test') {
$data[1] = 'page test';
} else if (isset($data['_LOGGEDIN_']) && $data['_LOGGEDIN_'] === true) {
$data[1] = 'loggedin';
} else {
$data[1] = $data[0];
}
return $data;
}