Merge pull request #275 from shaarli/plugin-proposition

Plugin proposition
This commit is contained in:
Arthur 2015-11-08 13:29:32 +01:00
commit fd006c630b
61 changed files with 13270 additions and 484 deletions

3
.gitignore vendored
View File

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

18
COPYING
View File

@ -1,4 +1,4 @@
Files: *
Files: *
License: zlib/libpng
Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
(c) 2011-2015 Alexandre Alapetite <alexandre@alapetite.fr>
@ -31,9 +31,9 @@ Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
Files: inc/reset.css
License: BSD (http://opensource.org/licenses/BSD-3-Clause)
Copyright: (c) 2010, Yahoo! Inc.
Copyright: (c) 2010, Yahoo! Inc.
Files: images/calendar.png, images/edit_icon.png, images/feed-icon-14x14.png, images/private.png, images/private_16x16.png, images/private_16x16_active.png, images/qrcode.png, images/tag_blue.png
Files: images/calendar.png, images/edit_icon.png, images/feed-icon-14x14.png, images/private.png, images/private_16x16.png, images/private_16x16_active.png, images/tag_blue.png
License: CC-BY (http://creativecommons.org/licenses/by/3.0/)
Copyright: (c) 2014 Yusuke Kamiyamane
Source: http://p.yusukekamiyamane.com/
@ -59,10 +59,6 @@ Files: inc/blazy*.js
License: MIT License (http://opensource.org/licenses/MIT)
Copyright: (C) Bjoern Klinggaard - @bklinggaard - http://dinbror.dk/blazy
Files: inc/qr.js
License: GPLv3 License (http://opensource.org/licenses/gpl-3.0)
Copyright: (C) 2014 Alasdair Mercer, http://neocotic.com, https://github.com/neocotic/qr.js
Files: inc/rain.tpl.class.php
Copyright: 2011-2012, Federico Ulfo <rainelemental@gmail.com>
2011-2012, The Rain Team <hello@raintm.com>
@ -72,6 +68,10 @@ Files: inc/awesomplete*
License: MIT License (http://opensource.org/licenses/MIT)
Copyright: (C) 2015 Lea Verou - https://github.com/LeaVerou/awesomplete
Files: plugins/wallabag/wallabag.png
License: MIT License (http://opensource.org/licenses/MIT)
Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag
----------------------------------------------------
ZLIB/LIBPNG LICENSE
@ -80,10 +80,10 @@ In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would
be appreciated but is not required.

View File

@ -13,8 +13,8 @@
# - enable in php.ini
BIN = vendor/bin
PHP_SOURCE = index.php application tests
PHP_COMMA_SOURCE = index.php,application,tests
PHP_SOURCE = index.php application tests plugins
PHP_COMMA_SOURCE = index.php,application,tests,plugins
all: static_analysis_summary test

263
application/Config.php Executable file → Normal file
View File

@ -1,129 +1,134 @@
<?php
/**
* Functions related to configuration management.
*/
/**
* Re-write configuration file according to given array.
* Requires mandatory fields listed in $MANDATORY_FIELDS.
*
* @param array $config contains all configuration fields.
* @param bool $isLoggedIn true if user is logged in.
*
* @return void
*
* @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
* @throws UnauthorizedConfigException: user is not authorize to change configuration.
* @throws Exception: an error occured while writing the new config file.
*/
function writeConfig($config, $isLoggedIn)
{
// These fields are required in configuration.
$MANDATORY_FIELDS = array(
'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
'redirector', 'disablesessionprotection', 'privateLinkByDefault'
);
if (!isset($config['config']['CONFIG_FILE'])) {
throw new MissingFieldConfigException('CONFIG_FILE');
}
// Only logged in user can alter config.
if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
throw new UnauthorizedConfigException();
}
// Check that all mandatory fields are provided in $config.
foreach ($MANDATORY_FIELDS as $field) {
if (!isset($config[$field])) {
throw new MissingFieldConfigException($field);
}
}
$configStr = '<?php '. PHP_EOL;
$configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
$configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
$configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
// Store all $config['config']
foreach ($config['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
}
$configStr .= '?>';
if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
|| strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
) {
throw new Exception(
'Shaarli could not create the config file.
Please make sure Shaarli has the right to write in the folder is it installed in.'
);
}
}
/**
* Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.
* ==> if user is loggedIn, merge its content with config.php, then delete options.php.
*
* @param array $config contains all configuration fields.
* @param bool $isLoggedIn true if user is logged in.
*
* @return void
*/
function mergeDeprecatedConfig($config, $isLoggedIn)
{
$config_file = $config['config']['CONFIG_FILE'];
if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {
include $config['config']['DATADIR'].'/options.php';
// Load GLOBALS into config
foreach ($GLOBALS as $key => $value) {
$config[$key] = $value;
}
$config['config']['CONFIG_FILE'] = $config_file;
writeConfig($config, $isLoggedIn);
unlink($config['config']['DATADIR'].'/options.php');
}
}
/**
* Exception used if a mandatory field is missing in given configuration.
*/
class MissingFieldConfigException extends Exception
{
public $field;
/**
* Construct exception.
*
* @param string $field field name missing.
*/
public function __construct($field)
{
$this->field = $field;
$this->message = 'Configuration value is required for '. $this->field;
}
}
/**
* Exception used if an unauthorized attempt to edit configuration has been made.
*/
class UnauthorizedConfigException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'You are not authorized to alter config.';
}
}
<?php
/**
* Functions related to configuration management.
*/
/**
* Re-write configuration file according to given array.
* Requires mandatory fields listed in $MANDATORY_FIELDS.
*
* @param array $config contains all configuration fields.
* @param bool $isLoggedIn true if user is logged in.
*
* @return void
*
* @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
* @throws UnauthorizedConfigException: user is not authorize to change configuration.
* @throws Exception: an error occured while writing the new config file.
*/
function writeConfig($config, $isLoggedIn)
{
// These fields are required in configuration.
$MANDATORY_FIELDS = array(
'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
'redirector', 'disablesessionprotection', 'privateLinkByDefault'
);
if (!isset($config['config']['CONFIG_FILE'])) {
throw new MissingFieldConfigException('CONFIG_FILE');
}
// Only logged in user can alter config.
if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
throw new UnauthorizedConfigException();
}
// Check that all mandatory fields are provided in $config.
foreach ($MANDATORY_FIELDS as $field) {
if (!isset($config[$field])) {
throw new MissingFieldConfigException($field);
}
}
$configStr = '<?php '. PHP_EOL;
$configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
$configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
$configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
$configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
$configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
// Store all $config['config']
foreach ($config['config'] as $key => $value) {
$configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
}
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
) {
throw new Exception(
'Shaarli could not create the config file.
Please make sure Shaarli has the right to write in the folder is it installed in.'
);
}
}
/**
* Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.
* ==> if user is loggedIn, merge its content with config.php, then delete options.php.
*
* @param array $config contains all configuration fields.
* @param bool $isLoggedIn true if user is logged in.
*
* @return void
*/
function mergeDeprecatedConfig($config, $isLoggedIn)
{
$config_file = $config['config']['CONFIG_FILE'];
if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {
include $config['config']['DATADIR'].'/options.php';
// Load GLOBALS into config
foreach ($GLOBALS as $key => $value) {
$config[$key] = $value;
}
$config['config']['CONFIG_FILE'] = $config_file;
writeConfig($config, $isLoggedIn);
unlink($config['config']['DATADIR'].'/options.php');
}
}
/**
* Exception used if a mandatory field is missing in given configuration.
*/
class MissingFieldConfigException extends Exception
{
public $field;
/**
* Construct exception.
*
* @param string $field field name missing.
*/
public function __construct($field)
{
$this->field = $field;
$this->message = 'Configuration value is required for '. $this->field;
}
}
/**
* Exception used if an unauthorized attempt to edit configuration has been made.
*/
class UnauthorizedConfigException extends Exception
{
/**
* Construct exception.
*/
public function __construct()
{
$this->message = 'You are not authorized to alter config.';
}
}

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;
}
}

View File

@ -405,12 +405,12 @@ h1 {
}
*/
.linkdate, .linkarchive {
.linkdate {
font-size:8pt;
color:#888;
}
.linkdate a, .linkarchive a {
.linkdate a {
color:#E28E3F;
}
@ -451,12 +451,12 @@ a.qrcode img {
color: #F57900;
}
.linkdate, .linkarchive {
.linkdate {
font-size: 8pt;
color: #888;
}
.linkdate a, .linkarchive a {
.linkdate a {
background-image: url('../images/calendar.png');
padding: 2px 0 3px 20px;
background-repeat: no-repeat;
@ -1126,3 +1126,8 @@ div.dailyNoEntry {
.center {
text-align: center;
}
ul.errors {
color: red;
float: left;
}

278
index.php
View File

@ -45,9 +45,17 @@ $GLOBALS['config']['RAINTPL_TPL'] = 'tpl/' ; // Raintpl template directory (keep
$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
// 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']['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']['WALLABAG_URL'] = 'https://demo.wallabag.org/';
// -----------------------------------------------------------------------------------------------
define('shaarli_version', '0.5.4');
// http://server.com/x/shaarli --> /shaarli/
@ -75,6 +83,8 @@ require_once 'application/TimeZone.php';
require_once 'application/Url.php';
require_once 'application/Utils.php';
require_once 'application/Config.php';
require_once 'application/PluginManager.php';
require_once 'application/Router.php';
// Ensure the PHP version is supported
try {
@ -119,6 +129,9 @@ include "inc/rain.tpl.class.php"; //include Rain TPL
raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template 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.
@ -566,28 +579,43 @@ class pageBuilder
function __construct()
{
$this->tpl=false;
$this->tpl = false;
}
/**
* Initialize all default tpl tags.
*/
private function initialize()
{
$this->tpl = new RainTPL;
$this->tpl->assign('newversion',escape(checkUpdate()));
$this->tpl->assign('feedurl',escape(index_url($_SERVER)));
$searchcrits=''; // Search criteria
if (!empty($_GET['searchtags'])) $searchcrits.='&searchtags='.urlencode($_GET['searchtags']);
elseif (!empty($_GET['searchterm'])) $searchcrits.='&searchterm='.urlencode($_GET['searchterm']);
$this->tpl->assign('searchcrits',$searchcrits);
$this->tpl->assign('source',index_url($_SERVER));
$this->tpl->assign('version',shaarli_version);
$this->tpl->assign('scripturl',index_url($_SERVER));
$this->tpl->assign('pagetitle','Shaarli');
$this->tpl->assign('privateonly',!empty($_SESSION['privateonly'])); // Show only private links?
if (!empty($GLOBALS['title'])) $this->tpl->assign('pagetitle',$GLOBALS['title']);
if (!empty($GLOBALS['titleLink'])) $this->tpl->assign('titleLink',$GLOBALS['titleLink']);
if (!empty($GLOBALS['pagetitle'])) $this->tpl->assign('pagetitle',$GLOBALS['pagetitle']);
$this->tpl->assign('shaarlititle',empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title'] );
return;
$this->tpl->assign('newversion', escape(checkUpdate()));
$this->tpl->assign('feedurl', escape(index_url($_SERVER)));
$searchcrits = ''; // Search criteria
if (!empty($_GET['searchtags'])) {
$searchcrits .= '&searchtags=' . urlencode($_GET['searchtags']);
}
elseif (!empty($_GET['searchterm'])) {
$searchcrits .= '&searchterm=' . urlencode($_GET['searchterm']);
}
$this->tpl->assign('searchcrits', $searchcrits);
$this->tpl->assign('source', index_url($_SERVER));
$this->tpl->assign('version', shaarli_version);
$this->tpl->assign('scripturl', index_url($_SERVER));
$this->tpl->assign('pagetitle', 'Shaarli');
$this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
if (!empty($GLOBALS['title'])) {
$this->tpl->assign('pagetitle', $GLOBALS['title']);
}
if (!empty($GLOBALS['titleLink'])) {
$this->tpl->assign('titleLink', $GLOBALS['titleLink']);
}
if (!empty($GLOBALS['pagetitle'])) {
$this->tpl->assign('pagetitle', $GLOBALS['pagetitle']);
}
$this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']);
if (!empty($GLOBALS['plugins']['errors'])) {
$this->tpl->assign('plugin_errors', $GLOBALS['plugins']['errors']);
}
}
// The following assign() method is basically the same as RainTPL (except that it's lazy)
@ -962,16 +990,31 @@ function showDaily()
$fill[$index]+=$length;
}
$PAGE = new pageBuilder;
$PAGE->assign('linksToDisplay',$linksToDisplay);
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('cols', $columns);
$PAGE->assign('day',linkdate2timestamp($day.'_000000'));
$PAGE->assign('previousday',$previousday);
$PAGE->assign('nextday',$nextday);
$data = array(
'linksToDisplay' => $linksToDisplay,
'linkcount' => count($LINKSDB),
'cols' => $columns,
'day' => linkdate2timestamp($day.'_000000'),
'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');
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)
@ -983,12 +1026,36 @@ function renderPage()
$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.
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
$token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful.
$PAGE = new pageBuilder;
$PAGE->assign('token',$token);
$PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
$PAGE->renderPage('loginform');
@ -1004,7 +1071,7 @@ function renderPage()
}
// -------- Picture wall
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=picwall'))
if ($targetPage == Router::$PAGE_PICWALL)
{
// Optionally filter the results:
$links=array();
@ -1027,15 +1094,22 @@ function renderPage()
}
}
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('linksToDisplay',$linksToDisplay);
$data = array(
'linkcount' => count($LINKSDB),
'linksToDisplay' => $linksToDisplay,
);
$pluginManager->executeHooks('render_picwall', $data, array('loggedin' => isLoggedIn()));
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('picwall');
exit;
}
// -------- Tag cloud
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tagcloud'))
if ($targetPage == Router::$PAGE_TAGCLOUD)
{
$tags= $LINKSDB->allTags();
@ -1049,9 +1123,17 @@ function renderPage()
{
$tagList[$key] = array('count'=>$value,'size'=>log($value, 15) / log($maxcount, 30) * (22-6) + 6);
}
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('tags',$tagList);
$data = array(
'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');
exit;
}
@ -1164,32 +1246,36 @@ function renderPage()
header('Location: ?do=login&post=');
exit;
}
showLinkList($PAGE, $LINKSDB);
if (isset($_GET['edit_link'])) {
header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
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.
}
// -------- All other functions are reserved for the registered user:
// -------- 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;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('pageabsaddr',index_url($_SERVER));
$data = array(
'linkcount' => count($LINKSDB),
'pageabsaddr' => index_url($_SERVER),
);
$pluginManager->executeHooks('render_tools', $data);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('tools');
exit;
}
// -------- 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 (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
@ -1220,7 +1306,6 @@ function renderPage()
}
else // show the change password form.
{
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken());
$PAGE->renderPage('changepassword');
@ -1229,7 +1314,7 @@ function renderPage()
}
// -------- 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']) )
{
@ -1265,7 +1350,6 @@ function renderPage()
}
else // Show the configuration form.
{
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken());
$PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] );
@ -1279,11 +1363,10 @@ function renderPage()
}
// -------- 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']))
{
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken());
$PAGE->assign('tags', $LINKSDB->allTags());
@ -1328,9 +1411,8 @@ function renderPage()
}
// -------- 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->renderPage('addlink');
exit;
@ -1349,6 +1431,9 @@ function renderPage()
$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));
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->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
pubsubhub();
@ -1386,6 +1471,9 @@ function renderPage()
// - confirmation is handled by JavaScript
// - we are protected from XSRF by the token.
$linkdate=$_POST['lf_linkdate'];
$pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]);
unset($LINKSDB[$linkdate]);
$LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk
@ -1427,13 +1515,20 @@ function renderPage()
{
$link = $LINKSDB[$_GET['edit_link']]; // Read database
if (!$link) { header('Location: ?'); exit; } // Link not found in database.
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('link',$link);
$PAGE->assign('link_is_new',false);
$PAGE->assign('token',getToken()); // XSRF protection.
$PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''));
$PAGE->assign('tags', $LINKSDB->allTags());
$data = array(
'linkcount' => count($LINKSDB),
'link' => $link,
'link_is_new' => false,
'token' => getToken(),
'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
'tags' => $LINKSDB->allTags(),
);
$pluginManager->executeHooks('render_editlink', $data);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('editlink');
exit;
}
@ -1497,24 +1592,30 @@ function renderPage()
);
}
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('link',$link);
$PAGE->assign('link_is_new',$link_is_new);
$PAGE->assign('token',getToken()); // XSRF protection.
$PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''));
$PAGE->assign('source',(isset($_GET['source']) ? $_GET['source'] : ''));
$PAGE->assign('tags', $LINKSDB->allTags());
$data = array(
'linkcount' => count($LINKSDB),
'link' => $link,
'link_is_new' => $link_is_new,
'token' => getToken(), // XSRF protection.
'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
'tags' => $LINKSDB->allTags(),
);
$pluginManager->executeHooks('render_editlink', $data);
foreach ($data as $key => $value) {
$PAGE->assign($key, $value);
}
$PAGE->renderPage('editlink');
exit;
}
// -------- 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']))
{
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->renderPage('export');
exit;
@ -1566,9 +1667,8 @@ HTML;
}
// -------- 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('token',getToken());
$PAGE->assign('maxfilesize',getMaxFileSize());
@ -1577,9 +1677,7 @@ HTML;
}
// -------- Otherwise, simply display search form and links:
$PAGE = new pageBuilder;
buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
$PAGE->renderPage('linklist');
showLinkList($PAGE, $LINKSDB);
exit;
}
@ -1750,7 +1848,7 @@ function buildLinkList($PAGE,$LINKSDB)
$taglist = explode(' ',$link['tags']);
uasort($taglist, 'strcasecmp');
$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.
strlen($link["url"]) === 7) {
$link["url"] = index_url($_SERVER) . $link["url"];
@ -1770,18 +1868,28 @@ function buildLinkList($PAGE,$LINKSDB)
$token = ''; if (isLoggedIn()) $token=getToken();
// Fill all template fields.
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('previous_page_url',$previous_page_url);
$PAGE->assign('next_page_url',$next_page_url);
$PAGE->assign('page_current',$page);
$PAGE->assign('page_max',$pagecount);
$PAGE->assign('result_count',count($linksToDisplay));
$PAGE->assign('search_type',$search_type);
$PAGE->assign('search_crits',$search_crits);
$PAGE->assign('redirector',empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); // Optional redirector URL.
$PAGE->assign('token',$token);
$PAGE->assign('links',$linkDisp);
$PAGE->assign('tags', $LINKSDB->allTags());
$data = array(
'linkcount' => count($LINKSDB),
'previous_page_url' => $previous_page_url,
'next_page_url' => $next_page_url,
'page_current' => $page,
'page_max' => $pagecount,
'result_count' => count($linksToDisplay),
'search_type' => $search_type,
'search_crits' => $search_crits,
'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL.
'token' => $token,
'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;
}

28
plugins/TODO.md Normal file
View File

@ -0,0 +1,28 @@
https://github.com/shaarli/Shaarli/issues/181 - Add Disqus or Isso comments box on a permalink page
* http://posativ.org/isso/
* install debian package https://packages.debian.org/sid/isso
* configure server http://posativ.org/isso/docs/configuration/server/
* configure client http://posativ.org/isso/docs/configuration/client/
* http://posativ.org/isso/docs/quickstart/ and add `<script data-isso="//comments.example.tld/" src="//comments.example.tld/js/embed.min.js"></script>` to includes.html template; then add `<section id="isso-thread"></section>` in the linklist template where you want the comments (in the linklist_plugins loop for example)
Problem: by default, Isso thread ID is guessed from the current url (only one thread per page).
if we want multiple threads on a single page (shaarli linklist), we must use : the `data-isso-id` client config,
with data-isso-id being the permalink of an item.
`<section data-isso-id="aH7klxW" id="isso-thread"></section>`
`data-isso-id: Set a custom thread id, defaults to current URI.`
Problem: feature is currently broken https://github.com/posativ/isso/issues/27
Another option, only display isso threads when current URL is a permalink (`\?(A-Z|a-z|0-9|-){7}`) (only show thread
when displaying only this link), and just display a "comments" button on each linklist item. Optionally show the comment
count on each item using the API (http://posativ.org/isso/docs/extras/api/#get-comment-count). API requests can be done
by raintpl `{function` or client-side with js. The former should be faster if isso and shaarli are on ther same server.
Showing all full isso threads in the linklist would destroy layout
-----------------------------------------------------------
http://www.git-attitude.fr/2014/11/04/git-rerere/ for the merge

View File

@ -0,0 +1,4 @@
#addlink_toolbar {
display: inline;
margin: 0 0 0 25px;
}

View File

@ -0,0 +1,6 @@
<div id="addlink_toolbar">
<form method="GET" action="" name="addform" class="addform">
<input type="text" name="post" placeholder="URI">
<input type="submit" value="Add link" class="bigbutton">
</form>
</div>

View File

@ -0,0 +1,38 @@
<?php
/**
* Plugin addlink_toolbar.
* Adds the addlink input on the linklist page.
*/
/**
* When linklist is displayed, add play videos to header's toolbar.
*
* @param array $data - header data.
*
* @return mixed - header data with addlink toolbar item.
*/
function hook_addlink_toolbar_render_header($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) {
$data['fields_toolbar'][] = file_get_contents(PluginManager::$PLUGINS_PATH . '/addlink_toolbar/addlink_toolbar.html');
}
return $data;
}
/**
* When link list is displayed, include markdown CSS.
*
* @param array $data - includes data.
*
* @return mixed - includes data with markdown CSS file added.
*/
function hook_addlink_toolbar_render_includes($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) {
$data['css_files'][] = PluginManager::$PLUGINS_PATH . '/addlink_toolbar/addlink_toolbar.css';
}
return $data;
}

View File

@ -0,0 +1 @@
<span><a href="https://web.archive.org/web/%s"><img width="13" height="13" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" /></a></span>

View File

@ -0,0 +1,25 @@
<?php
/**
* Plugin Archive.org.
*
* Add an icon in the link list for archive.org.
*/
/**
* Add archive.org icon to link_plugin when rendering linklist.
*
* @param mixed $data - linklist data.
*
* @return mixed - linklist data with archive.org plugin.
*/
function hook_archiveorg_render_linklist($data)
{
$archive_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/archiveorg/archiveorg.html');
foreach ($data['links'] as &$value) {
$archive = sprintf($archive_html, $value['url']);
$value['link_plugin'][] = $archive;
}
return $data;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

View File

@ -0,0 +1,7 @@
.linktitle a {
color: red;
}
.upper_plugin_demo {
float: left;
}

View File

@ -0,0 +1 @@
console.log("I love the smell of napalm in the morning.");

View File

@ -0,0 +1,317 @@
<?php
/**
* Demo Plugin.
*
* This plugin try to cover Shaarli's plugin API entirely.
* Can be used by plugin developper to make their own.
*/
/*
* RENDER HEADER, INCLUDES, FOOTER
*
* Those hooks are called at every page rendering.
* You can filter its execution by checking _PAGE_ value
* and check user status with _LOGGEDIN_.
*/
/**
* Hook render_header.
* Executed on every page redering.
*
* Template placeholders:
* - buttons_toolbar
* - fields_toolbar
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_header($data)
{
// Only execute when linklist is rendered.
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
// If loggedin
if ($data['_LOGGEDIN_'] === true) {
// Buttons in toolbar
$data['buttons_toolbar'][] = '<li><a href="#">DEMO_buttons_toolbar</a></li>';
}
// Fields in toolbar
$data['fields_toolbar'][] = 'DEMO_fields_toolbar';
}
return $data;
}
/**
* Hook render_includes.
* Executed on every page redering.
*
* Template placeholders:
* - css_files
*
* Data:
* - _PAGE_: current page
* - _LOGGEDIN_: true/false
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_includes($data)
{
// List of plugin's CSS files.
// Note that you just need to specify CSS path.
$data['css_files'][] = PluginManager::$PLUGINS_PATH . '/demo_plugin/custom_demo.css';
return $data;
}
/**
* Hook render_footer.
* Executed on every page redering.
*
* Template placeholders:
* - text
* - js_files
*
* Data:
* - _PAGE_: current page
* - _LOGGEDIN_: true/false
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_footer($data)
{
// footer text
$data['text'][] = 'Shaarli is now enhanced by the awesome demo_plugin.';
// List of plugin's JS files.
// Note that you just need to specify CSS path.
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/demo_plugin/demo_plugin.js';
return $data;
}
/*
* SPECIFIC PAGES
*/
/**
* Hook render_linklist.
*
* Template placeholders:
* - action_plugin: next to 'private only' button.
* - plugin_start_zone: page start
* - plugin_end_zone: page end
* - link_plugin: icons below each links.
*
* Data:
* - _LOGGEDIN_: true/false
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_linklist($data)
{
// action_plugin
$data['action_plugin'][] = '<div class="upper_plugin_demo"><a href="?up" title="Uppercase!">←</a></div>';
if (isset($_GET['up'])) {
// Manipulate link data
foreach ($data['links'] as &$value) {
$value['description'] = strtoupper($value['description']);
$value['title'] = strtoupper($value['title']);
}
}
// link_plugin (for each link)
foreach ($data['links'] as &$value) {
$value['link_plugin'][] = ' DEMO \o/';
}
// plugin_start_zone
$data['plugin_start_zone'][] = '<center>BEFORE</center>';
// plugin_start_zone
$data['plugin_end_zone'][] = '<center>AFTER</center>';
return $data;
}
/**
* Hook render_editlink.
*
* Template placeholders:
* - field_plugin: add link fields after tags.
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_editlink($data)
{
// Load HTML into a string
$html = file_get_contents(PluginManager::$PLUGINS_PATH .'/demo_plugin/field.html');
// replace value in HTML if it exists in $data
if (!empty($data['link']['stuff'])) {
$html = sprintf($html, $data['link']['stuff']);
} else {
$html = sprintf($html, '');
}
// field_plugin
$data['edit_link_plugin'][] = $html;
return $data;
}
/**
* Hook render_tools.
*
* Template placeholders:
* - tools_plugin: after other tools.
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_tools($data)
{
// field_plugin
$data['tools_plugin'][] = 'tools_plugin';
return $data;
}
/**
* Hook render_picwall.
*
* Template placeholders:
* - plugin_start_zone: page start.
* - plugin_end_zone: page end.
*
* Data:
* - _LOGGEDIN_: true/false
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_picwall($data)
{
// plugin_start_zone
$data['plugin_start_zone'][] = '<center>BEFORE</center>';
// plugin_end_zone
$data['plugin_end_zone'][] = '<center>AFTER</center>';
return $data;
}
/**
* Hook render_tagcloud.
*
* Template placeholders:
* - plugin_start_zone: page start.
* - plugin_end_zone: page end.
*
* Data:
* - _LOGGEDIN_: true/false
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_tagcloud($data)
{
// plugin_start_zone
$data['plugin_start_zone'][] = '<center>BEFORE</center>';
// plugin_end_zone
$data['plugin_end_zone'][] = '<center>AFTER</center>';
return $data;
}
/**
* Hook render_daily.
*
* Template placeholders:
* - plugin_start_zone: page start.
* - plugin_end_zone: page end.
*
* Data:
* - _LOGGEDIN_: true/false
*
* @param array $data data passed to plugin
*
* @return array altered $data.
*/
function hook_demo_plugin_render_daily($data)
{
// plugin_start_zone
$data['plugin_start_zone'][] = '<center>BEFORE</center>';
// plugin_end_zone
$data['plugin_end_zone'][] = '<center>AFTER</center>';
// Manipulate columns data
foreach ($data['cols'] as &$value) {
foreach ($value as &$value2) {
$value2['formatedDescription'] .= ' ಠ_ಠ';
}
}
// Add plugin content at the end of each link
foreach ($data['cols'] as &$value) {
foreach ($value as &$value2) {
$value2['link_plugin'][] = 'DEMO';
}
}
return $data;
}
/*
* DATA SAVING HOOK.
*/
/**
* Hook savelink.
*
* Triggered when a link is save (new or edit).
* All new links now contain a 'stuff' value.
*
* @param array $data contains the new link data.
*
* @return array altered $data.
*/
function hook_demo_plugin_save_link($data)
{
// Save stuff added in editlink field
if (!empty($_POST['lf_stuff'])) {
$data['stuff'] = escape($_POST['lf_stuff']);
}
return $data;
}
/**
* Hook delete_link.
*
* Triggered when a link is deleted.
*
* @param array $data contains the link to be deleted.
*
* @return array altered data.
*/
function hook_demo_plugin_delete_link($data)
{
if (strpos($data['url'], 'youtube.com') !== false) {
exit('You can not delete a YouTube link. Don\'t ask.');
}
}

2
plugins/demo_plugin/field.html Executable file
View File

@ -0,0 +1,2 @@
<label for="lf_stuff"><i>Demo Stuff</i></label><br>
<input type="text" name="lf_stuff" id="lf_stuff" value="%s" class="lf_input"><br>

View File

@ -0,0 +1,71 @@
### ► Play Videos plugin for Shaarli
This plugin adds a `► Play Videos` button to [Shaarli](https://github.com/shaarli/Shaarli)'s toolbar. Click this button to play all videos on the page in an overlay HTML5 player. Nice for continuous stream of music, documentaries, talks...
This uses code from https://zaius.github.io/youtube_playlist/ and is currently only compatible with Youtube videos.
![](https://cdn.mediacru.sh/D_izf0zjAtxy.png)
#### Installation and setup
Place the files in the `tpl/plugins/playvideos/` directory of your Shaarli.
This is a default Shaarli plugin, you just have to enable it.
To enable the plugin, add `playvideos` to the `TOOLBAR_PLUGINS` config option in your `index.php` or `data/options.php`. Example:
$GLOBALS['config']['TOOLBAR_PLUGINS'] = array('aplugins', 'anotherone', 'playvideos');
#### Troubleshooting
If your server has [Content Security Policy](http://content-security-policy.com/) headers enabled, this may prevent the script from loading fully. You should relax the CSP in your server settings. Example CSP rule for apache2:
`Header set Content-Security-Policy "script-src 'self' 'unsafe-inline' https://www.youtube.com https://s.ytimg.com 'unsafe-eval'"`
### License
```
File: youtube_playlist.js
Copyright: (c) 2010-2014, David Kelso <david@kelso.id.au>
License: The ISC License (http://opensource.org/licenses/ISC)
Files: jquery*.js
License: MIT License (http://opensource.org/licenses/MIT)
Copyright: (C) jQuery Foundation and other contributors, https://jquery.com/download/
-----------------------------------------------------
The ISC License (http://opensource.org/licenses/ISC)
Copyright (c) 2010-2014, David Kelso (david at kelso dot id dot au)
Copyright (c) 2010-2014, nodiscc (nodiscc at gmail dot com)
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE
----------------------------------------------------
MIT LICENSE
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.
----------------------------------------------------
```

10346
plugins/playvideos/jquery-1.11.2.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<a href="#" id="playvideos">► Play Videos</a>

View File

@ -0,0 +1,40 @@
<?php
/**
* Plugin PlayVideos
*
* Add a button in the toolbar allowing to watch all videos.
* Note: this plugin adds jQuery.
*/
/**
* When linklist is displayed, add play videos to header's toolbar.
*
* @param array $data - header data.
*
* @return mixed - header data with playvideos toolbar item.
*/
function hook_playvideos_render_header($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
$data['buttons_toolbar'][] = file_get_contents(PluginManager::$PLUGINS_PATH . '/playvideos/playvideos.html');
}
return $data;
}
/**
* When linklist is displayed, include playvideos JS files.
*
* @param array $data - footer data.
*
* @return mixed - footer data with playvideos JS files added.
*/
function hook_playvideos_render_footer($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/jquery-1.11.2.min.js';
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/playvideos/youtube_playlist.js';
}
return $data;
}

View File

@ -0,0 +1,196 @@
var run_playideos = (function () {
var e, n, t, o, r, i = [].indexOf || function (e) {
for (var n = 0, t = this.length; n < t; n++) {
if (n in this && this[n] === e) return n
}
return -1
};
if (!window.console) {
window.console = {
log: function () {}
}
}
n = {
shadow: {
"background-color": "black",
position: "fixed",
left: 0,
top: 0,
width: "100%",
height: "100%",
"z-index": 1e3,
opacity: .8
},
player_box: {
position: "fixed",
left: "50%",
top: "50%",
width: 640,
height: 480,
"margin-left": -320,
"margin-top": -240,
"z-index": 1001
},
prev_button: {
"float": "left"
},
next_button: {
"float": "right"
}
};
t = function (e, n) {
var t, o, r;
r = document.createElement("script");
r.src = e;
o = document.getElementsByTagName("head")[0];
t = false;
r.onload = r.onreadystatechange = function () {
var e, i;
e = !this.readyState || (i = this.readyState) === "loaded" || i === "complete";
if (!t && e) {
t = true;
n();
r.onload = r.onreadystatechange = null;
return o.removeChild(r)
}
};
return o.appendChild(r)
};
e = function (e) {
var t, o, r, a, u, l, d, c, f, p, s, y, h, g, v, m, w;
e.getScript("//www.youtube.com/iframe_api");
d = [];
w = new RegExp("https?://(www.)?youtube.com/");
e('a[href^="http"]').each(function () {
var n;
if (!e(this).attr("href").match(w)) {
return
}
n = this.href.replace(/^.*v=/, "").replace(/\&.*$/, "");
if (i.call(d, n) < 0) {
return d.push(n)
}
});
console.log("video ids", d);
c = 0;
y = null;
g = "playlist_player";
f = function () {
console.log("Playing", c, d[c]);
return y.loadVideoById(d[c])
};
p = function () {
c++;
if (c >= d.length) {
c -= d.length
}
return f()
};
s = function () {
c--;
if (c < 0) {
c += d.length
}
return f()
};
l = function () {
e("#shadow, #player_box").remove();
return e(document).unbind("keyup.player")
};
e(document).bind("keyup.player", function (e) {
if (e.keyCode === 27) {
l()
}
if (e.keyCode === 39) {
p()
}
if (e.keyCode === 37) {
return s()
}
});
u = e("<div />", {
id: "shadow",
css: n.shadow,
click: l
});
r = e("<div />", {
id: "player_box",
css: n.player_box
});
o = e("<div />", {
id: g
});
a = e("<a />", {
href: "javascript:;",
text: "previous",
css: n.prev_button,
click: s
});
t = e("<a />", {
href: "javascript:;",
text: "next",
css: n.next_button,
click: p
});
r.append(o).append(a).append(t);
e("body").append(u).append(r);
v = function (e) {
console.log("player ready");
return e.target.playVideo()
};
h = function (e) {
var n, t;
n = {
2: "invalid video id",
5: "video not supported in html5",
100: "video removed or private",
101: "video not embedable",
150: "video not embedable"
};
t = n[e.data] || "unknown error";
console.log("Error", t);
d.splice(c, 1);
if (c >= d.length) {
c = 0
}
return f()
};
m = function (e) {
if (e.data === YT.PlayerState.ENDED) {
return p()
}
};
return window.onYouTubeIframeAPIReady = function () {
return y = new YT.Player(g, {
height: "390",
width: "640",
videoId: d[0],
events: {
onReady: v,
onError: h,
onStateChange: m
}
})
}
};
o = false;
if (typeof jQuery !== "undefined" && jQuery !== null && jQuery.fn && jQuery.fn.jquery) {
r = jQuery.fn.jquery.split(".");
if (r.length === 3 && parseInt(r[1]) > 3) {
console.log("using in page jquery version", jQuery.fn.jquery);
e(jQuery);
o = true
}
}
if (!o) {
t("plugins/playvideos/jquery-1.11.2.min.js", function () {
return e(jQuery.noConflict(true))
})
}
});
var input = document.querySelector('#playvideos');
input.addEventListener('click', function()
{
run_playideos();
});

View File

@ -0,0 +1,5 @@
<div class="linkqrcode">
<a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s">
<img src="%s/qrcode/qrcode.png" width="13" height="13" title="QR-Code">
</a>
</div>

41
plugins/qrcode/qrcode.php Normal file
View File

@ -0,0 +1,41 @@
<?php
/**
* Plugin qrcode
* Add QRCode containing URL for each links.
* Display a QRCode icon in link list.
*/
/**
* Add qrcode icon to link_plugin when rendering linklist.
*
* @param array $data - linklist data.
*
* @return mixed - linklist data with qrcode plugin.
*/
function hook_qrcode_render_linklist($data)
{
$qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html');
foreach ($data['links'] as &$value) {
$qrcode = sprintf($qrcode_html, $value['url'], $value['url'], PluginManager::$PLUGINS_PATH);
$value['link_plugin'][] = $qrcode;
}
return $data;
}
/**
* When linklist is displayed, include qrcode JS files.
*
* @param array $data - footer data.
*
* @return mixed - footer data with qrcode JS files added.
*/
function hook_qrcode_render_footer($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
$data['js_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/shaarli-qrcode.js';
}
return $data;
}

View File

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

View File

@ -0,0 +1,56 @@
// Show the QR-Code of a permalink (when the QR-Code icon is clicked).
function showQrCode(caller,loading)
{
// Dynamic javascript lib loading: We only load qr.js if the QR code icon is clicked:
if (typeof(qr) == 'undefined') // Load qr.js only if not present.
{
if (!loading) // If javascript lib is still loading, do not append script to body.
{
var element = document.createElement("script");
element.src = "plugins/qrcode/qr-1.1.3.min.js";
document.body.appendChild(element);
}
setTimeout(function() { showQrCode(caller,true);}, 200); // Retry in 200 milliseconds.
return false;
}
// Remove previous qrcode if present.
removeQrcode();
// Build the div which contains the QR-Code:
var element = document.createElement('div');
element.id="permalinkQrcode";
// Make QR-Code div commit sepuku when clicked:
if ( element.attachEvent ){
element.attachEvent('onclick', 'this.parentNode.removeChild(this);' );
} else {
// Damn IE
element.setAttribute('onclick', 'this.parentNode.removeChild(this);' );
}
// Build the QR-Code:
var image = qr.image({size: 8,value: caller.dataset.permalink});
if (image)
{
element.appendChild(image);
element.innerHTML += "<br>Click to close";
caller.parentNode.appendChild(element);
}
else
{
element.innerHTML = "Your browser does not seem to be HTML5 compatible.";
}
return false;
}
// Remove any displayed QR-Code
function removeQrcode()
{
var elem = document.getElementById("permalinkQrcode");
if (elem) {
elem.parentNode.removeChild(elem);
}
return false;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

View File

@ -0,0 +1,3 @@
<?php
$GLOBALS['plugins']['READITYOUSELF_URL'] = 'http://someurl.com';

View File

@ -0,0 +1 @@
<span><a href="%s?url=%s"><img width="13" height="13" src="%s/readityourself/book-open.png" title="Read with Readityourself" /></a></span>

View File

@ -0,0 +1,43 @@
<?php
/**
* Plugin readityourself
*/
// If we're talking about https://github.com/memiks/readityourself
// it seems kinda dead.
// Not tested.
// don't raise unnecessary warnings
if (is_file(PluginManager::$PLUGINS_PATH . '/readityourself/config.php')) {
include PluginManager::$PLUGINS_PATH . '/readityourself/config.php';
}
if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) {
$GLOBALS['plugins']['errors'][] = 'Readityourself plugin error: '.
'Please define "$GLOBALS[\'plugins\'][\'READITYOUSELF_URL\']" '.
'in "plugins/readityourself/config.php" or in your Shaarli config.php file.';
}
/**
* Add readityourself icon to link_plugin when rendering linklist.
*
* @param mixed $data - linklist data.
*
* @return mixed - linklist data with readityourself plugin.
*/
function hook_readityourself_render_linklist($data)
{
if (!isset($GLOBALS['plugins']['READITYOUSELF_URL'])) {
return $data;
}
$readityourself_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/readityourself/readityourself.html');
foreach ($data['links'] as &$value) {
$readityourself = sprintf($readityourself_html, $GLOBALS['plugins']['READITYOUSELF_URL'], $value['url'], PluginManager::$PLUGINS_PATH);
$value['link_plugin'][] = $readityourself;
}
return $data;
}

View File

@ -0,0 +1,29 @@
## Save to Wallabag plugin for Shaarli
For each link in your Shaarli, adds a button to save the target page in your [wallabag](https://www.wallabag.org/).
### Installation/configuration
Clone this repository inside your `tpl/plugins/` directory, or download the archive and unpack it there.
The directory structure should look like:
```
└── tpl
└── plugins
   └── wallabag
   ├── README.md
   ├── wallabag.html
   └── wallabag.png
```
To enable the plugin, add `'wallabag'` to your list of enabled plugins in `data/options.php` (`PLUGINS` array)
. This should look like:
```
$GLOBALS['config']['PLUGINS'] = array('qrcode', 'any_other_plugin', 'wallabag')
```
Then, set the `WALLABAG_URL` variable in `data/options.php` pointing to your wallabag URL. Example:
```
$GLOBALS['config']['WALLABAG_URL'] = 'http://demo.wallabag.org' ; //Base URL of your wallabag installation
```

View File

@ -0,0 +1,3 @@
<?php
$GLOBALS['plugins']['WALLABAG_URL'] = 'https://demo.wallabag.org/';

View File

@ -0,0 +1 @@
<span><a href="%s/?plainurl=%s" target="_blank"><img width="13" height="13" src="%s/wallabag/wallabag.png" title="Save to wallabag" /></a></span>

View File

@ -0,0 +1,39 @@
<?php
/**
* Plugin Wallabag.
*/
// don't raise unnecessary warnings
if (is_file(PluginManager::$PLUGINS_PATH . '/wallabag/config.php')) {
include PluginManager::$PLUGINS_PATH . '/wallabag/config.php';
}
if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) {
$GLOBALS['plugins']['errors'][] = 'Wallabag plugin error: '.
'Please define "$GLOBALS[\'plugins\'][\'WALLABAG_URL\']" '.
'in "plugins/wallabag/config.php" or in your Shaarli config.php file.';
}
/**
* Add wallabag icon to link_plugin when rendering linklist.
*
* @param mixed $data - linklist data.
*
* @return mixed - linklist data with wallabag plugin.
*/
function hook_wallabag_render_linklist($data)
{
if (!isset($GLOBALS['plugins']['WALLABAG_URL'])) {
return $data;
}
$wallabag_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/wallabag/wallabag.html');
foreach ($data['links'] as &$value) {
$wallabag = sprintf($wallabag_html, $GLOBALS['plugins']['WALLABAG_URL'], $value['url'], PluginManager::$PLUGINS_PATH);
$value['link_plugin'][] = $wallabag;
}
return $data;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

354
tests/ConfigTest.php Executable file → Normal file
View File

@ -1,177 +1,177 @@
<?php
/**
* Config' tests
*/
require_once 'application/Config.php';
/**
* Unitary tests for Shaarli config related functions
*/
class ConfigTest extends PHPUnit_Framework_TestCase
{
// Configuration input set.
private static $_configFields;
/**
* Executed before each test.
*/
public function setUp()
{
self::$_configFields = array(
'login' => 'login',
'hash' => 'hash',
'salt' => 'salt',
'timezone' => 'Europe/Paris',
'title' => 'title',
'titleLink' => 'titleLink',
'redirector' => '',
'disablesessionprotection' => false,
'privateLinkByDefault' => false,
'config' => array(
'CONFIG_FILE' => 'tests/config.php',
'DATADIR' => 'tests',
'config1' => 'config1data',
'config2' => 'config2data',
)
);
}
/**
* Executed after each test.
*
* @return void
*/
public function tearDown()
{
if (is_file(self::$_configFields['config']['CONFIG_FILE'])) {
unlink(self::$_configFields['config']['CONFIG_FILE']);
}
}
/**
* Test writeConfig function, valid use case, while being logged in.
*/
public function testWriteConfig()
{
writeConfig(self::$_configFields, true);
include self::$_configFields['config']['CONFIG_FILE'];
$this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);
$this->assertEquals(self::$_configFields['hash'], $GLOBALS['hash']);
$this->assertEquals(self::$_configFields['salt'], $GLOBALS['salt']);
$this->assertEquals(self::$_configFields['timezone'], $GLOBALS['timezone']);
$this->assertEquals(self::$_configFields['title'], $GLOBALS['title']);
$this->assertEquals(self::$_configFields['titleLink'], $GLOBALS['titleLink']);
$this->assertEquals(self::$_configFields['redirector'], $GLOBALS['redirector']);
$this->assertEquals(self::$_configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);
$this->assertEquals(self::$_configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);
$this->assertEquals(self::$_configFields['config']['config1'], $GLOBALS['config']['config1']);
$this->assertEquals(self::$_configFields['config']['config2'], $GLOBALS['config']['config2']);
}
/**
* Test writeConfig option while logged in:
* 1. init fields.
* 2. update fields, add new sub config, add new root config.
* 3. rewrite config.
* 4. check result.
*/
public function testWriteConfigFieldUpdate()
{
writeConfig(self::$_configFields, true);
self::$_configFields['title'] = 'ok';
self::$_configFields['config']['config1'] = 'ok';
self::$_configFields['config']['config_new'] = 'ok';
self::$_configFields['new'] = 'should not be saved';
writeConfig(self::$_configFields, true);
include self::$_configFields['config']['CONFIG_FILE'];
$this->assertEquals('ok', $GLOBALS['title']);
$this->assertEquals('ok', $GLOBALS['config']['config1']);
$this->assertEquals('ok', $GLOBALS['config']['config_new']);
$this->assertFalse(isset($GLOBALS['new']));
}
/**
* Test writeConfig function with an empty array.
*
* @expectedException MissingFieldConfigException
*/
public function testWriteConfigEmpty()
{
writeConfig(array(), true);
}
/**
* Test writeConfig function with a missing mandatory field.
*
* @expectedException MissingFieldConfigException
*/
public function testWriteConfigMissingField()
{
unset(self::$_configFields['login']);
writeConfig(self::$_configFields, true);
}
/**
* Test writeConfig function while being logged out, and there is no config file existing.
*/
public function testWriteConfigLoggedOutNoFile()
{
writeConfig(self::$_configFields, false);
}
/**
* Test writeConfig function while being logged out, and a config file already exists.
*
* @expectedException UnauthorizedConfigException
*/
public function testWriteConfigLoggedOutWithFile()
{
file_put_contents(self::$_configFields['config']['CONFIG_FILE'], '');
writeConfig(self::$_configFields, false);
}
/**
* Test mergeDeprecatedConfig while being logged in:
* 1. init a config file.
* 2. init a options.php file with update value.
* 3. merge.
* 4. check updated value in config file.
*/
public function testMergeDeprecatedConfig()
{
// init
writeConfig(self::$_configFields, true);
$configCopy = self::$_configFields;
$invert = !$configCopy['privateLinkByDefault'];
$configCopy['privateLinkByDefault'] = $invert;
// Use writeConfig to create a options.php
$configCopy['config']['CONFIG_FILE'] = 'tests/options.php';
writeConfig($configCopy, true);
$this->assertTrue(is_file($configCopy['config']['CONFIG_FILE']));
// merge configs
mergeDeprecatedConfig(self::$_configFields, true);
// make sure updated field is changed
include self::$_configFields['config']['CONFIG_FILE'];
$this->assertEquals($invert, $GLOBALS['privateLinkByDefault']);
$this->assertFalse(is_file($configCopy['config']['CONFIG_FILE']));
}
/**
* Test mergeDeprecatedConfig while being logged in without options file.
*/
public function testMergeDeprecatedConfigNoFile()
{
writeConfig(self::$_configFields, true);
mergeDeprecatedConfig(self::$_configFields, true);
include self::$_configFields['config']['CONFIG_FILE'];
$this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);
}
}
<?php
/**
* Config' tests
*/
require_once 'application/Config.php';
/**
* Unitary tests for Shaarli config related functions
*/
class ConfigTest extends PHPUnit_Framework_TestCase
{
// Configuration input set.
private static $configFields;
/**
* Executed before each test.
*/
public function setUp()
{
self::$configFields = array(
'login' => 'login',
'hash' => 'hash',
'salt' => 'salt',
'timezone' => 'Europe/Paris',
'title' => 'title',
'titleLink' => 'titleLink',
'redirector' => '',
'disablesessionprotection' => false,
'privateLinkByDefault' => false,
'config' => array(
'CONFIG_FILE' => 'tests/config.php',
'DATADIR' => 'tests',
'config1' => 'config1data',
'config2' => 'config2data',
)
);
}
/**
* Executed after each test.
*
* @return void
*/
public function tearDown()
{
if (is_file(self::$configFields['config']['CONFIG_FILE'])) {
unlink(self::$configFields['config']['CONFIG_FILE']);
}
}
/**
* Test writeConfig function, valid use case, while being logged in.
*/
public function testWriteConfig()
{
writeConfig(self::$configFields, true);
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
$this->assertEquals(self::$configFields['hash'], $GLOBALS['hash']);
$this->assertEquals(self::$configFields['salt'], $GLOBALS['salt']);
$this->assertEquals(self::$configFields['timezone'], $GLOBALS['timezone']);
$this->assertEquals(self::$configFields['title'], $GLOBALS['title']);
$this->assertEquals(self::$configFields['titleLink'], $GLOBALS['titleLink']);
$this->assertEquals(self::$configFields['redirector'], $GLOBALS['redirector']);
$this->assertEquals(self::$configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);
$this->assertEquals(self::$configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);
$this->assertEquals(self::$configFields['config']['config1'], $GLOBALS['config']['config1']);
$this->assertEquals(self::$configFields['config']['config2'], $GLOBALS['config']['config2']);
}
/**
* Test writeConfig option while logged in:
* 1. init fields.
* 2. update fields, add new sub config, add new root config.
* 3. rewrite config.
* 4. check result.
*/
public function testWriteConfigFieldUpdate()
{
writeConfig(self::$configFields, true);
self::$configFields['title'] = 'ok';
self::$configFields['config']['config1'] = 'ok';
self::$configFields['config']['config_new'] = 'ok';
self::$configFields['new'] = 'should not be saved';
writeConfig(self::$configFields, true);
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals('ok', $GLOBALS['title']);
$this->assertEquals('ok', $GLOBALS['config']['config1']);
$this->assertEquals('ok', $GLOBALS['config']['config_new']);
$this->assertFalse(isset($GLOBALS['new']));
}
/**
* Test writeConfig function with an empty array.
*
* @expectedException MissingFieldConfigException
*/
public function testWriteConfigEmpty()
{
writeConfig(array(), true);
}
/**
* Test writeConfig function with a missing mandatory field.
*
* @expectedException MissingFieldConfigException
*/
public function testWriteConfigMissingField()
{
unset(self::$configFields['login']);
writeConfig(self::$configFields, true);
}
/**
* Test writeConfig function while being logged out, and there is no config file existing.
*/
public function testWriteConfigLoggedOutNoFile()
{
writeConfig(self::$configFields, false);
}
/**
* Test writeConfig function while being logged out, and a config file already exists.
*
* @expectedException UnauthorizedConfigException
*/
public function testWriteConfigLoggedOutWithFile()
{
file_put_contents(self::$configFields['config']['CONFIG_FILE'], '');
writeConfig(self::$configFields, false);
}
/**
* Test mergeDeprecatedConfig while being logged in:
* 1. init a config file.
* 2. init a options.php file with update value.
* 3. merge.
* 4. check updated value in config file.
*/
public function testMergeDeprecatedConfig()
{
// init
writeConfig(self::$configFields, true);
$configCopy = self::$configFields;
$invert = !$configCopy['privateLinkByDefault'];
$configCopy['privateLinkByDefault'] = $invert;
// Use writeConfig to create a options.php
$configCopy['config']['CONFIG_FILE'] = 'tests/options.php';
writeConfig($configCopy, true);
$this->assertTrue(is_file($configCopy['config']['CONFIG_FILE']));
// merge configs
mergeDeprecatedConfig(self::$configFields, true);
// make sure updated field is changed
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals($invert, $GLOBALS['privateLinkByDefault']);
$this->assertFalse(is_file($configCopy['config']['CONFIG_FILE']));
}
/**
* Test mergeDeprecatedConfig while being logged in without options file.
*/
public function testMergeDeprecatedConfigNoFile()
{
writeConfig(self::$configFields, true);
mergeDeprecatedConfig(self::$configFields, true);
include self::$configFields['config']['CONFIG_FILE'];
$this->assertEquals(self::$configFields['login'], $GLOBALS['login']);
}
}

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 $pluginPath
*/
private static $pluginPath = 'tests/plugins';
/**
* Test plugin.
* @var string $pluginName
*/
private static $pluginName = 'test';
/**
* Test plugin loading and hook execution.
*
* @return void
*/
public function testPlugin()
{
$pluginManager = PluginManager::getInstance();
PluginManager::$PLUGINS_PATH = self::$pluginPath;
$pluginManager->load(array(self::$pluginName));
$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 Normal 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)
);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* PlugQrcodeTest.php
*/
require_once 'plugins/qrcode/qrcode.php';
require_once 'application/Router.php';
/**
* Class PlugQrcodeTest
* Unit test for the QR-Code plugin
*/
class PlugQrcodeTest extends PHPUnit_Framework_TestCase
{
/**
* Reset plugin path
*/
function setUp() {
PluginManager::$PLUGINS_PATH = 'plugins';
}
/**
* Test render_linklist hook.
*/
function testQrcodeLinklist()
{
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
'links' => array(
array(
'url' => $str,
)
)
);
$data = hook_qrcode_render_linklist($data);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
$this->assertEquals($str, $link['url']);
// plugin data
$this->assertEquals(1, count($link['link_plugin']));
$this->assertNotFalse(strpos($link['link_plugin'][0], $str));
}
/**
* Test render_footer hook.
*/
function testQrcodeFooter()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data = hook_qrcode_render_footer($data);
$this->assertEquals($str, $data[$str]);
$this->assertEquals(1, count($data['js_files']));
$data = array($str => $str);
$data['_PAGE_'] = $str;
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('js_files', $data);
}
}

View File

@ -0,0 +1,100 @@
<?php
/**
* PluginPlayvideosTest.php
*/
require_once 'plugins/addlink_toolbar/addlink_toolbar.php';
require_once 'application/Router.php';
/**
* Class PluginAddlinkTest
* Unit test for the Addlink toolbar plugin
*/
class PluginAddlinkTest extends PHPUnit_Framework_TestCase
{
/**
* Reset plugin path.
*/
function setUp()
{
PluginManager::$PLUGINS_PATH = 'plugins';
}
/**
* Test render_header hook while logged in.
*/
function testAddlinkHeaderLoggedIn()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data['_LOGGEDIN_'] = true;
$data = hook_addlink_toolbar_render_header($data);
$this->assertEquals($str, $data[$str]);
$this->assertEquals(1, count($data['fields_toolbar']));
$data = array($str => $str);
$data['_PAGE_'] = $str;
$data['_LOGGEDIN_'] = true;
$data = hook_addlink_toolbar_render_header($data);
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('fields_toolbar', $data);
}
/**
* Test render_header hook while logged out.
*/
function testAddlinkHeaderLoggedOut()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data['_LOGGEDIN_'] = false;
$data = hook_addlink_toolbar_render_header($data);
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('fields_toolbar', $data);
}
/**
* Test render_includes hook while logged in.
*/
function testAddlinkIncludesLoggedIn()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data['_LOGGEDIN_'] = true;
$data = hook_addlink_toolbar_render_includes($data);
$this->assertEquals($str, $data[$str]);
$this->assertEquals(1, count($data['css_files']));
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = $str;
$data['_LOGGEDIN_'] = true;
$data = hook_addlink_toolbar_render_includes($data);
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('css_files', $data);
}
/**
* Test render_includes hook.
* Should not affect css files while logged out.
*/
function testAddlinkIncludesLoggedOut()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data['_LOGGEDIN_'] = false;
$data = hook_addlink_toolbar_render_includes($data);
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('css_files', $data);
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* PluginArchiveorgTest.php
*/
require_once 'plugins/archiveorg/archiveorg.php';
/**
* Class PlugQrcodeTest
* Unit test for the QR-Code plugin
*/
class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
{
/**
* Reset plugin path
*/
function setUp()
{
PluginManager::$PLUGINS_PATH = 'plugins';
}
/**
* Test render_linklist hook.
*/
function testArchiveorgLinklist()
{
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
'links' => array(
array(
'url' => $str,
)
)
);
$data = hook_archiveorg_render_linklist($data);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
$this->assertEquals($str, $link['url']);
// plugin data
$this->assertEquals(1, count($link['link_plugin']));
$this->assertNotFalse(strpos($link['link_plugin'][0], $str));
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* PluginPlayvideosTest.php
*/
require_once 'plugins/playvideos/playvideos.php';
require_once 'application/Router.php';
/**
* Class PluginPlayvideosTest
* Unit test for the PlayVideos plugin
*/
class PluginPlayvideosTest extends PHPUnit_Framework_TestCase
{
/**
* Reset plugin path
*/
function setUp()
{
PluginManager::$PLUGINS_PATH = 'plugins';
}
/**
* Test render_linklist hook.
*/
function testPlayvideosHeader()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data = hook_playvideos_render_header($data);
$this->assertEquals($str, $data[$str]);
$this->assertEquals(1, count($data['buttons_toolbar']));
$data = array($str => $str);
$data['_PAGE_'] = $str;
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('buttons_toolbar', $data);
}
/**
* Test render_footer hook.
*/
function testPlayvideosFooter()
{
$str = 'stuff';
$data = array($str => $str);
$data['_PAGE_'] = Router::$PAGE_LINKLIST;
$data = hook_playvideos_render_footer($data);
$this->assertEquals($str, $data[$str]);
$this->assertEquals(2, count($data['js_files']));
$data = array($str => $str);
$data['_PAGE_'] = $str;
$this->assertEquals($str, $data[$str]);
$this->assertArrayNotHasKey('js_files', $data);
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* PluginReadityourselfTest.php.php
*/
require_once 'plugins/readityourself/readityourself.php';
/**
* Class PluginWallabagTest
* Unit test for the Wallabag plugin
*/
class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
{
/**
* Reset plugin path
*/
function setUp()
{
PluginManager::$PLUGINS_PATH = 'plugins';
}
/**
* Test render_linklist hook.
*/
function testReadityourselfLinklist()
{
$GLOBALS['plugins']['READITYOUSELF_URL'] = 'value';
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
'links' => array(
array(
'url' => $str,
)
)
);
$data = hook_readityourself_render_linklist($data);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
$this->assertEquals($str, $link['url']);
// plugin data
$this->assertEquals(1, count($link['link_plugin']));
$this->assertNotFalse(strpos($link['link_plugin'][0], $str));
}
/**
* Test without config: nothing should happened.
*/
function testReadityourselfLinklistWithoutConfig()
{
unset($GLOBALS['plugins']['READITYOUSELF_URL']);
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
'links' => array(
array(
'url' => $str,
)
)
);
$data = hook_readityourself_render_linklist($data);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
$this->assertEquals($str, $link['url']);
// plugin data
$this->assertArrayNotHasKey('link_plugin', $link);
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* PluginWallabagTest.php.php
*/
require_once 'plugins/wallabag/wallabag.php';
/**
* Class PluginWallabagTest
* Unit test for the Wallabag plugin
*/
class PluginWallabagTest extends PHPUnit_Framework_TestCase
{
/**
* Reset plugin path
*/
function setUp()
{
PluginManager::$PLUGINS_PATH = 'plugins';
}
/**
* Test render_linklist hook.
*/
function testWallabagLinklist()
{
$GLOBALS['plugins']['WALLABAG_URL'] = 'value';
$str = 'http://randomstr.com/test';
$data = array(
'title' => $str,
'links' => array(
array(
'url' => $str,
)
)
);
$data = hook_wallabag_render_linklist($data);
$link = $data['links'][0];
// data shouldn't be altered
$this->assertEquals($str, $data['title']);
$this->assertEquals($str, $link['url']);
// plugin data
$this->assertEquals(1, count($link['link_plugin']));
$this->assertNotFalse(strpos($link['link_plugin'][0], $str));
}
}

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;
}

View File

@ -38,6 +38,7 @@
<input type="checkbox" name="updateCheck" id="updateCheck" {if="!empty($GLOBALS['config']['ENABLE_UPDATECHECK'])"}checked{/if}/>
<label for="updateCheck">&nbsp;Notify me when a new release is ready</label></td>
</tr>
<tr><td></td><td class="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr>
</table>
</form>

View File

@ -2,18 +2,43 @@
<html>
<head>{include="includes"}</head>
<body>
<div id="pageheader">{include="page.header"}</div>
<div id="pageheader">
{include="page.header"}
</div>
<div class="daily">
<div class="dailyAbout">
All links of one day<br>in a single page.<br>
{if="$previousday"} <a href="?do=daily&amp;day={$previousday}"><b>&lt;</b>Previous day</a>{else}<b>&lt;</b>Previous day{/if}
-
{if="$nextday"}<a href="?do=daily&amp;day={$nextday}">Next day<b>&gt;</b></a>{else}Next day<b>&gt;</b>{/if}
<br><br>
<a href="?do=dailyrss" title="1 RSS entry per day"><img src="images/feed-icon-14x14.png#" alt="rss_feed">Daily RSS Feed</a>
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div class="dailyTitle"><img src="../images/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left"> The Daily Shaarli <img src="../images/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right"></div>
<div class="dailyDate"><span class="nomobile">&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;</span> {function="strftime('%A %d, %B %Y', $day)"} <span class="nomobile">&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;</span></div>
<div class="dailyAbout">
All links of one day<br>in a single page.<br>
{if="$previousday"} <a href="?do=daily&amp;day={$previousday}"><b>&lt;</b>Previous day</a>{else}<b>&lt;</b>Previous day{/if}
-
{if="$nextday"}<a href="?do=daily&amp;day={$nextday}">Next day<b>&gt;</b></a>{else}Next day<b>&gt;</b>{/if}
<br>
{loop="$daily_about_plugin"}
{$value}
{/loop}
<br>
<a href="?do=dailyrss" title="1 RSS entry per day"><img src="images/feed-icon-14x14.png#" alt="rss_feed">Daily RSS Feed</a>
</div>
<div class="dailyTitle">
<img src="../images/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left">
The Daily Shaarli
<img src="../images/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right">
</div>
<div class="dailyDate">
<span class="nomobile">&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;</span>
{function="strftime('%A %d, %B %Y', $day)"}
<span class="nomobile">&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;</span>
</div>
<div class="clear"></div>
{if="$linksToDisplay"}
@ -47,6 +72,12 @@
<div class="dailyEntryThumbnail">{$link.thumbnail}</div>
{/if}
<div class="dailyEntryDescription">{$link.formatedDescription}</div>
<div class="dailyEntryFooter">
{loop="$link.link_plugin"}
{$value}
{/loop}
</div>
</div>
{/loop}
</div>
@ -55,6 +86,14 @@
{else}
<div class="dailyNoEntry">No articles on this day.</div>
{/if}
<div class="clear"></div>
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
<div id="closing"><img src="../images/squiggle_closing.png" width="66" height="61" alt="-"></div>
</div>
{include="page.footer"}

View File

@ -21,6 +21,11 @@
<label for="lf_tags"><i>Tags</i></label><br>
<input type="text" name="lf_tags" id="lf_tags" value="{$link.tags}" class="lf_input"
data-list="{loop="$tags"}{$key}, {/loop}" data-multiple autocomplete="off" ><br>
{loop="$edit_link_plugin"}
{$value}
{/loop}
{if="($link_is_new && $GLOBALS['privateLinkByDefault']==true) || $link.private == true"}
<input type="checkbox" checked="checked" name="lf_private" id="lf_private">
&nbsp;<label for="lf_private"><i>Private</i></label><br>

View File

@ -8,3 +8,6 @@
<link type="text/css" rel="stylesheet" href="../inc/reset.css" />
<link type="text/css" rel="stylesheet" href="../inc/shaarli.css" />
{if="is_file('inc/user.css')"}<link type="text/css" rel="stylesheet" href="../inc/user.css" />{/if}
{loop="$plugins_includes.css_files"}
<link type="text/css" rel="stylesheet" href="{$value}#"/>
{/loop}

View File

@ -17,6 +17,9 @@
</datalist>
<input type="submit" value="Search" class="bigbutton">
</form>
{loop="$plugins_header.fields_toolbar"}
{$value}
{/loop}
</div>
</div>
@ -24,6 +27,12 @@
{include="linklist.paging"}
<div id="plugin_zone_start_linklist" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
{if="count($links)==0"}
<div id="searchcriteria">Nothing found.</i></div>
{else}
@ -40,7 +49,7 @@
<ul>
{loop="links"}
<li{if="$value.class"} class="{$value.class}"{/if}>
<a id="{$value.linkdate|smallHash}"></a>
<a id="{$value.shorturl}"></a>
<div class="thumbnail">{$value.url|thumbnail}</div>
<div class="linkcontainer">
{if="isLoggedIn()"}
@ -56,89 +65,38 @@
{if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"}
<span class="linkdate" title="Permalink"><a href="?{$value.linkdate|smallHash}">{function="strftime('%c', $value.timestamp)"} - permalink</a> - </span>
{else}
<span class="linkdate" title="Short link here"><a href="?{$value.linkdate|smallHash}">permalink</a> - </span>
<span class="linkdate" title="Short link here"><a href="?{$value.shorturl}">permalink</a> - </span>
{/if}
{if="$GLOBALS['config']['ARCHIVE_ORG']"}
<span class="linkarchive"><a href="https://web.archive.org/web/{$value.url}">archive</a> - </span>
{/if}
<div class="linkqrcode"><a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d={$scripturl|urlencode}%3F{$value.linkdate|smallHash}"
onclick="return showQrCode(this);" class="qrcode" data-permalink="{$scripturl}?{$value.linkdate|smallHash}">
<img src="images/qrcode.png#" alt="QR-Code" title="{function="strftime('%c', $value.timestamp)"}"></a></div> -
{loop="$value.link_plugin"}
<span>{$value}</span> -
{/loop}
<a href="{$value.url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br>
{if="$value.tags"}
<div class="linktaglist">
{loop="value.taglist"}<span class="linktag" title="Add tag"><a href="?addtag={$value|urlencode}">{$value}</a></span> {/loop}
</div>
{/if}
</div>
</li>
{/loop}
</ul>
<div id="plugin_zone_end_linklist" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
{include="linklist.paging"}
</div>
{include="page.footer"}
<script>
// Remove any displayed QR-Code
function remove_qrcode()
{
var elem = document.getElementById("permalinkQrcode");
if (elem) elem.parentNode.removeChild(elem);
return false;
}
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
// Show the QR-Code of a permalink (when the QR-Code icon is clicked).
function showQrCode(caller,loading)
{
if( !isCanvasSupported() ) return true;
// Dynamic javascript lib loading: We only load qr.js if the QR code icon is clicked:
if (typeof(qr)=='undefined') // Load qr.js only if not present.
{
loading = typeof loading !== 'undefined' ? loading : false;
if (!loading) // If javascript lib is still loading, do not append script to body.
{
var element = document.createElement("script");
element.src = "inc/qr-1.1.3.min.js";
document.body.appendChild(element);
}
setTimeout(function() { showQrCode(caller,true);}, 200); // Retry in 200 milliseconds.
return false;
}
// Remove previous qrcode if present.
remove_qrcode();
// Build the div which contains the QR-Code:
var element = document.createElement('div');
element.id="permalinkQrcode";
// Make QR-Code div commit sepuku when clicked:
element.addEventListener('click', remove_qrcode ); // Works on every canvas supported browser
// Build the QR-Code:
var image = qr.image({size: 8,value: caller.getAttribute('data-permalink')});
if (image)
{
element.appendChild(image);
element.innerHTML+= "<br>Click to close";
caller.parentNode.appendChild(element);
}
else
{
element.innerHTML="Your browser does not seem to be HTML5 compatible.";
}
return false;
}
</script>
<script src="inc/awesomplete.min.js#"></script>
</body>
</html>

View File

@ -8,8 +8,13 @@
<img src="images/private_16x16.png#" width="16" height="16" title="Click to see only private links" alt="Click to see only private links">
{/if}
</a>
</div>
{/if}
{loop="$action_plugin"}
{$value}
{/loop}
<div class="paging_linksperpage">
Links per page: <a href="?linksperpage=20">20</a> <a href="?linksperpage=50">50</a> <a href="?linksperpage=100">100</a>
<form method="GET" class="linksperpage"><input type="text" name="linksperpage" size="2"></form>

View File

@ -1,5 +1,8 @@
<div id="footer">
<b><a href="https://github.com/shaarli/Shaarli">Shaarli</a></b> - The personal, minimalist, super-fast, no-database delicious clone by the <a href="https://github.com/shaarli/Shaarli">Shaarli</a> community - <a href="doc/Home.html">Help/documentation</a>
{loop="$plugins_footer.text"}
{$value}
{/loop}
</div>
{if="$newversion"}
<div id="newversion"><span id="version_id">&#x25CF;</span> Shaarli {$newversion} is <a href="https://github.com/shaarli/Shaarli/releases">available</a>.</div>
@ -7,3 +10,7 @@
{if="isLoggedIn()"}
<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
{/if}
{loop="$plugins_footer.js_files"}
<script src="{$value}#"></script>
{/loop}

View File

@ -11,7 +11,7 @@
<a href="{$titleLink}">{$shaarlititle}</a>
</span>
</li>
{if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"}
{ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
{else}
@ -33,10 +33,20 @@
<li><a href="?do=tagcloud">Tag cloud</a></li>
<li><a href="?do=picwall{$searchcrits}">Picture wall</a></li>
<li><a href="?do=daily">Daily</a></li>
{loop="$plugins_header.buttons_toolbar"}
{$value}
{/loop}
{/if}
</ul>
</div>
{if="!empty($plugin_errors) && isLoggedIn()"}
<ul class="errors">
{loop="plugin_errors"}
<li>{$value}</li>
{/loop}
</ul>
{/if}
<div class="clear"></div>

View File

@ -5,15 +5,34 @@
</head>
<body>
<div id="pageheader">{include="page.header"}</div>
<div id="plugin_zone_start_picwall" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div class="center">
<div id="picwall_container">
{loop="linksToDisplay"}
<div class="picwall_pictureframe">
{$value.thumbnail}<a href="{$value.url}"><span class="info">{$value.title}</span></a>
{loop="$value.picwall_plugin"}
{$value}
{/loop}
</div>
{/loop}
</div>
</div>
<div class="clear"></div>
<div id="plugin_zone_end_picwall" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
{include="page.footer"}
<script>

View File

@ -4,9 +4,25 @@
<body>
<div id="pageheader">{include="page.header"}</div>
<div class="center">
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
{loop="$plugin_start_zone"}
{$value}
{/loop}
</div>
<div id="cloudtag">
{loop="tags"}
<span class="count">{$value.count}</span><a href="?searchtags={$key|urlencode}" style="font-size:{$value.size}pt;">{$key}</a>
<span class="count">{$value.count}</span>
<a href="?searchtags={$key|urlencode}" style="font-size:{$value.size}pt;">{$key}</a>
{loop="$value.tag_plugin"}
{$value}
{/loop}
{/loop}
</div>
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
{loop="$plugin_end_zone"}
{$value}
{/loop}
</div>
</div>

View File

@ -13,6 +13,9 @@
<a class="smallbutton" onclick="alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');return false;" href="javascript:javascript:(function(){var%20url%20=%20location.href;var%20title%20=%20document.title%20||%20url;window.open('{$pageabsaddr}?post='%20+%20encodeURIComponent(url)+'&amp;title='%20+%20encodeURIComponent(title)+'&amp;description='%20+%20encodeURIComponent(document.getSelection())+'&amp;source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1');})();"><b>✚Shaare link</b></a> <a href="#" style="clear:none;"><span>&#x21D0; Drag this link to your bookmarks toolbar (or right-click it and choose Bookmark This Link....).<br>&nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Shaare link" button in any page you want to share.</span></a><br><br>
<a class="smallbutton" onclick="alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');return false;" href="?private=1&post="><b>✚Add Note</b></a> <a href="#" style="clear:none;"><span>&#x21D0; Drag this link to your bookmarks toolbar (or right-click it and choose Bookmark This Link....).<br>&nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Add Note" button anytime to start composing a (default private) Note (text post) to your Shaarli.</span></a><br><br>
<a class="smallbutton" onclick="activateFirefoxSocial(this)"><b>✚Add to Firefox social</b></a> <a href="#" style="clear:none;"><span>&#x21D0; Click on this button to add Shaarli to the "Share this page" button in Firefox.</span></a><br><br>
{loop="$tools_plugin"}
{$value}
{/loop}
<div class="clear"></div>
<script>