Merge pull request #515 from ArthurHoaro/template-feeds
Refactor RSS feeds generation, and do it through templates
This commit is contained in:
commit
f66a1990e5
16 changed files with 859 additions and 339 deletions
279
application/FeedBuilder.php
Normal file
279
application/FeedBuilder.php
Normal file
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* FeedBuilder class.
|
||||
*
|
||||
* Used to build ATOM and RSS feeds data.
|
||||
*/
|
||||
class FeedBuilder
|
||||
{
|
||||
/**
|
||||
* @var string Constant: RSS feed type.
|
||||
*/
|
||||
public static $FEED_RSS = 'rss';
|
||||
|
||||
/**
|
||||
* @var string Constant: ATOM feed type.
|
||||
*/
|
||||
public static $FEED_ATOM = 'atom';
|
||||
|
||||
/**
|
||||
* @var string Default language if the locale isn't set.
|
||||
*/
|
||||
public static $DEFAULT_LANGUAGE = 'en-en';
|
||||
|
||||
/**
|
||||
* @var int Number of links to display in a feed by default.
|
||||
*/
|
||||
public static $DEFAULT_NB_LINKS = 50;
|
||||
|
||||
/**
|
||||
* @var LinkDB instance.
|
||||
*/
|
||||
protected $linkDB;
|
||||
|
||||
/**
|
||||
* @var string RSS or ATOM feed.
|
||||
*/
|
||||
protected $feedType;
|
||||
|
||||
/**
|
||||
* @var array $_SERVER.
|
||||
*/
|
||||
protected $serverInfo;
|
||||
|
||||
/**
|
||||
* @var array $_GET.
|
||||
*/
|
||||
protected $userInput;
|
||||
|
||||
/**
|
||||
* @var boolean True if the user is currently logged in, false otherwise.
|
||||
*/
|
||||
protected $isLoggedIn;
|
||||
|
||||
/**
|
||||
* @var boolean Use permalinks instead of direct links if true.
|
||||
*/
|
||||
protected $usePermalinks;
|
||||
|
||||
/**
|
||||
* @var boolean true to hide dates in feeds.
|
||||
*/
|
||||
protected $hideDates;
|
||||
|
||||
/**
|
||||
* @var string PubSub hub URL.
|
||||
*/
|
||||
protected $pubsubhubUrl;
|
||||
|
||||
/**
|
||||
* @var string server locale.
|
||||
*/
|
||||
protected $locale;
|
||||
|
||||
/**
|
||||
* @var DateTime Latest item date.
|
||||
*/
|
||||
protected $latestDate;
|
||||
|
||||
/**
|
||||
* Feed constructor.
|
||||
*
|
||||
* @param LinkDB $linkDB LinkDB instance.
|
||||
* @param string $feedType Type of feed.
|
||||
* @param array $serverInfo $_SERVER.
|
||||
* @param array $userInput $_GET.
|
||||
* @param boolean $isLoggedIn True if the user is currently logged in, false otherwise.
|
||||
*/
|
||||
public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn)
|
||||
{
|
||||
$this->linkDB = $linkDB;
|
||||
$this->feedType = $feedType;
|
||||
$this->serverInfo = $serverInfo;
|
||||
$this->userInput = $userInput;
|
||||
$this->isLoggedIn = $isLoggedIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build data for feed templates.
|
||||
*
|
||||
* @return array Formatted data for feeds templates.
|
||||
*/
|
||||
public function buildData()
|
||||
{
|
||||
// Optionally filter the results:
|
||||
$linksToDisplay = $this->linkDB->filterSearch($this->userInput);
|
||||
|
||||
$nblinksToDisplay = $this->getNbLinks(count($linksToDisplay));
|
||||
|
||||
// Can't use array_keys() because $link is a LinkDB instance and not a real array.
|
||||
$keys = array();
|
||||
foreach ($linksToDisplay as $key => $value) {
|
||||
$keys[] = $key;
|
||||
}
|
||||
|
||||
$pageaddr = escape(index_url($this->serverInfo));
|
||||
$linkDisplayed = array();
|
||||
for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) {
|
||||
$linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr);
|
||||
}
|
||||
|
||||
$data['language'] = $this->getTypeLanguage();
|
||||
$data['pubsubhub_url'] = $this->pubsubhubUrl;
|
||||
$data['last_update'] = $this->getLatestDateFormatted();
|
||||
$data['show_dates'] = !$this->hideDates || $this->isLoggedIn;
|
||||
// Remove leading slash from REQUEST_URI.
|
||||
$data['self_link'] = $pageaddr . escape(ltrim($this->serverInfo['REQUEST_URI'], '/'));
|
||||
$data['index_url'] = $pageaddr;
|
||||
$data['usepermalinks'] = $this->usePermalinks === true;
|
||||
$data['links'] = $linkDisplayed;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a feed item (one per shaare).
|
||||
*
|
||||
* @param array $link Single link array extracted from LinkDB.
|
||||
* @param string $pageaddr Index URL.
|
||||
*
|
||||
* @return array Link array with feed attributes.
|
||||
*/
|
||||
protected function buildItem($link, $pageaddr)
|
||||
{
|
||||
$link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']);
|
||||
// Check for both signs of a note: starting with ? and 7 chars long.
|
||||
if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
|
||||
$link['url'] = $pageaddr . $link['url'];
|
||||
}
|
||||
if ($this->usePermalinks === true) {
|
||||
$permalink = '<a href="'. $link['url'] .'" title="Direct link">Direct link</a>';
|
||||
} else {
|
||||
$permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>';
|
||||
}
|
||||
$link['description'] = format_description($link['description']) . PHP_EOL .'<br>— '. $permalink;
|
||||
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
|
||||
if ($this->feedType == self::$FEED_RSS) {
|
||||
$link['iso_date'] = $date->format(DateTime::RSS);
|
||||
} else {
|
||||
$link['iso_date'] = $date->format(DateTime::ATOM);
|
||||
}
|
||||
|
||||
// Save the more recent item.
|
||||
if (empty($this->latestDate) || $this->latestDate < $date) {
|
||||
$this->latestDate = $date;
|
||||
}
|
||||
|
||||
$taglist = array_filter(explode(' ', $link['tags']), 'strlen');
|
||||
uasort($taglist, 'strcasecmp');
|
||||
$link['taglist'] = $taglist;
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign PubSub hub URL.
|
||||
*
|
||||
* @param string $pubsubhubUrl PubSub hub url.
|
||||
*/
|
||||
public function setPubsubhubUrl($pubsubhubUrl)
|
||||
{
|
||||
$this->pubsubhubUrl = $pubsubhubUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to use permalinks instead of direct links.
|
||||
*
|
||||
* @param boolean $usePermalinks true to force permalinks.
|
||||
*/
|
||||
public function setUsePermalinks($usePermalinks)
|
||||
{
|
||||
$this->usePermalinks = $usePermalinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to hide timestamps in feeds.
|
||||
*
|
||||
* @param boolean $hideDates true to enable.
|
||||
*/
|
||||
public function setHideDates($hideDates)
|
||||
{
|
||||
$this->hideDates = $hideDates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the locale. Used to show feed language.
|
||||
*
|
||||
* @param string $locale The locale (eg. 'fr_FR.UTF8').
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->locale = strtolower($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language according to the feed type, based on the locale:
|
||||
*
|
||||
* - RSS format: en-us (default: 'en-en').
|
||||
* - ATOM format: fr (default: 'en').
|
||||
*
|
||||
* @return string The language.
|
||||
*/
|
||||
public function getTypeLanguage()
|
||||
{
|
||||
// Use the locale do define the language, if available.
|
||||
if (! empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) {
|
||||
$length = ($this->feedType == self::$FEED_RSS) ? 5 : 2;
|
||||
return str_replace('_', '-', substr($this->locale, 0, $length));
|
||||
}
|
||||
return ($this->feedType == self::$FEED_RSS) ? 'en-en' : 'en';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the latest item date found according to the feed type.
|
||||
*
|
||||
* Return an empty string if invalid DateTime is passed.
|
||||
*
|
||||
* @return string Formatted date.
|
||||
*/
|
||||
protected function getLatestDateFormatted()
|
||||
{
|
||||
if (empty($this->latestDate) || !$this->latestDate instanceof DateTime) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$type = ($this->feedType == self::$FEED_RSS) ? DateTime::RSS : DateTime::ATOM;
|
||||
return $this->latestDate->format($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of link to display according to 'nb' user input parameter.
|
||||
*
|
||||
* If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS.
|
||||
* If 'nb' is set to 'all', display all filtered links (max parameter).
|
||||
*
|
||||
* @param int $max maximum number of links to display.
|
||||
*
|
||||
* @return int number of links to display.
|
||||
*/
|
||||
public function getNbLinks($max)
|
||||
{
|
||||
if (empty($this->userInput['nb'])) {
|
||||
return self::$DEFAULT_NB_LINKS;
|
||||
}
|
||||
|
||||
if ($this->userInput['nb'] == 'all') {
|
||||
return $max;
|
||||
}
|
||||
|
||||
$intNb = intval($this->userInput['nb']);
|
||||
if (! is_int($intNb) || $intNb == 0) {
|
||||
return self::$DEFAULT_NB_LINKS;
|
||||
}
|
||||
|
||||
return $intNb;
|
||||
}
|
||||
}
|
|
@ -341,17 +341,71 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
}
|
||||
|
||||
/**
|
||||
* Filter links.
|
||||
* Returns the shaare corresponding to a smallHash.
|
||||
*
|
||||
* @param string $type Type of filter.
|
||||
* @param mixed $request Search request, string or array.
|
||||
* @param string $request QUERY_STRING server parameter.
|
||||
*
|
||||
* @return array $filtered array containing permalink data.
|
||||
*
|
||||
* @throws LinkNotFoundException if the smallhash is malformed or doesn't match any link.
|
||||
*/
|
||||
public function filterHash($request)
|
||||
{
|
||||
$request = substr($request, 0, 6);
|
||||
$linkFilter = new LinkFilter($this->_links);
|
||||
return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of articles for a given day.
|
||||
*
|
||||
* @param string $request day to filter. Format: YYYYMMDD.
|
||||
*
|
||||
* @return array list of shaare found.
|
||||
*/
|
||||
public function filterDay($request) {
|
||||
$linkFilter = new LinkFilter($this->_links);
|
||||
return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter links according to search parameters.
|
||||
*
|
||||
* @param array $filterRequest Search request content. Supported keys:
|
||||
* - searchtags: list of tags
|
||||
* - searchterm: term search
|
||||
* @param bool $casesensitive Optional: Perform case sensitive filter
|
||||
* @param bool $privateonly Optional: Returns private links only if true.
|
||||
*
|
||||
* @return array filtered links
|
||||
* @return array filtered links, all links if no suitable filter was provided.
|
||||
*/
|
||||
public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false)
|
||||
public function filterSearch($filterRequest = array(), $casesensitive = false, $privateonly = false)
|
||||
{
|
||||
// Filter link database according to parameters.
|
||||
$searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
|
||||
$searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
|
||||
|
||||
// Search tags + fullsearch.
|
||||
if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) {
|
||||
$type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
|
||||
$request = array($searchtags, $searchterm);
|
||||
}
|
||||
// Search by tags.
|
||||
elseif (! empty($searchtags)) {
|
||||
$type = LinkFilter::$FILTER_TAG;
|
||||
$request = $searchtags;
|
||||
}
|
||||
// Fulltext search.
|
||||
elseif (! empty($searchterm)) {
|
||||
$type = LinkFilter::$FILTER_TEXT;
|
||||
$request = $searchterm;
|
||||
}
|
||||
// Otherwise, display without filtering.
|
||||
else {
|
||||
$type = '';
|
||||
$request = '';
|
||||
}
|
||||
|
||||
$linkFilter = new LinkFilter($this->_links);
|
||||
return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class LinkFilter
|
|||
* Filter links according to parameters.
|
||||
*
|
||||
* @param string $type Type of filter (eg. tags, permalink, etc.).
|
||||
* @param string $request Filter content.
|
||||
* @param mixed $request Filter content.
|
||||
* @param bool $casesensitive Optional: Perform case sensitive filter if true.
|
||||
* @param bool $privateonly Optional: Only returns private links if true.
|
||||
*
|
||||
|
@ -110,6 +110,8 @@ class LinkFilter
|
|||
* @param string $smallHash permalink hash.
|
||||
*
|
||||
* @return array $filtered array containing permalink data.
|
||||
*
|
||||
* @throws LinkNotFoundException if the smallhash doesn't match any link.
|
||||
*/
|
||||
private function filterSmallHash($smallHash)
|
||||
{
|
||||
|
@ -121,6 +123,11 @@ class LinkFilter
|
|||
return $filtered;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($filtered)) {
|
||||
throw new LinkNotFoundException();
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
|
@ -318,3 +325,8 @@ class LinkFilter
|
|||
return array_filter(explode(' ', trim($tagsOut)), 'strlen');
|
||||
}
|
||||
}
|
||||
|
||||
class LinkNotFoundException extends Exception
|
||||
{
|
||||
protected $message = 'The link you are trying to reach does not exist or has been deleted.';
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@ class Router
|
|||
|
||||
public static $PAGE_DAILY = 'daily';
|
||||
|
||||
public static $PAGE_FEED_ATOM = 'atom';
|
||||
|
||||
public static $PAGE_FEED_RSS = 'rss';
|
||||
|
||||
public static $PAGE_TOOLS = 'tools';
|
||||
|
||||
public static $PAGE_CHANGEPASSWORD = 'changepasswd';
|
||||
|
@ -49,7 +53,7 @@ class Router
|
|||
* @param array $get $_SERVER['GET'].
|
||||
* @param bool $loggedIn true if authenticated user.
|
||||
*
|
||||
* @return self::page found.
|
||||
* @return string page found.
|
||||
*/
|
||||
public static function findPage($query, $get, $loggedIn)
|
||||
{
|
||||
|
@ -79,6 +83,14 @@ class Router
|
|||
return self::$PAGE_DAILY;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_FEED_ATOM)) {
|
||||
return self::$PAGE_FEED_ATOM;
|
||||
}
|
||||
|
||||
if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) {
|
||||
return self::$PAGE_FEED_RSS;
|
||||
}
|
||||
|
||||
// At this point, only loggedin pages.
|
||||
if (!$loggedIn) {
|
||||
return self::$PAGE_LINKLIST;
|
||||
|
|
|
@ -137,7 +137,7 @@ class Updater
|
|||
*/
|
||||
public function updateMethodRenameDashTags()
|
||||
{
|
||||
$linklist = $this->linkDB->filter();
|
||||
$linklist = $this->linkDB->filterSearch();
|
||||
foreach ($linklist as $link) {
|
||||
$link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
|
||||
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
|
||||
|
|
|
@ -63,14 +63,22 @@ function endsWith($haystack, $needle, $case=true)
|
|||
|
||||
/**
|
||||
* Htmlspecialchars wrapper
|
||||
* Support multidimensional array of strings.
|
||||
*
|
||||
* @param string $str the string to escape.
|
||||
* @param mixed $input Data to escape: a single string or an array of strings.
|
||||
*
|
||||
* @return string escaped.
|
||||
*/
|
||||
function escape($str)
|
||||
function escape($input)
|
||||
{
|
||||
return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false);
|
||||
if (is_array($input)) {
|
||||
$out = array();
|
||||
foreach($input as $key => $value) {
|
||||
$out[$key] = escape($value);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
return htmlspecialchars($input, ENT_COMPAT, 'UTF-8', false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,7 +234,7 @@ function space2nbsp($text)
|
|||
*
|
||||
* @return string formatted description.
|
||||
*/
|
||||
function format_description($description, $redirector) {
|
||||
function format_description($description, $redirector = false) {
|
||||
return nl2br(space2nbsp(text2clickable($description, $redirector)));
|
||||
}
|
||||
|
||||
|
|
392
index.php
392
index.php
|
@ -154,6 +154,7 @@ if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
|
|||
require_once 'application/ApplicationUtils.php';
|
||||
require_once 'application/Cache.php';
|
||||
require_once 'application/CachedPage.php';
|
||||
require_once 'application/FeedBuilder.php';
|
||||
require_once 'application/FileUtils.php';
|
||||
require_once 'application/HttpUtils.php';
|
||||
require_once 'application/LinkDB.php';
|
||||
|
@ -637,6 +638,29 @@ class pageBuilder
|
|||
$this->tpl->assign($what,$where);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign an array of data to the template builder.
|
||||
*
|
||||
* @param array $data Data to assign.
|
||||
*
|
||||
* @return false if invalid data.
|
||||
*/
|
||||
public function assignAll($data)
|
||||
{
|
||||
// Lazy initialization
|
||||
if ($this->tpl === false) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if (empty($data) || !is_array($data)){
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$this->assign($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Render a specific page (using a template).
|
||||
// e.g. pb.renderPage('picwall')
|
||||
public function renderPage($page)
|
||||
|
@ -658,232 +682,6 @@ class pageBuilder
|
|||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Output the last N links in RSS 2.0 format.
|
||||
function showRSS()
|
||||
{
|
||||
header('Content-Type: application/rss+xml; charset=utf-8');
|
||||
|
||||
// $usepermalink : If true, use permalink instead of final link.
|
||||
// User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=rss&permalinks
|
||||
// Also enabled through a config option
|
||||
$usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
|
||||
|
||||
// Cache system
|
||||
$query = $_SERVER["QUERY_STRING"];
|
||||
$cache = new CachedPage(
|
||||
$GLOBALS['config']['PAGECACHE'],
|
||||
page_url($_SERVER),
|
||||
startsWith($query,'do=rss') && !isLoggedIn()
|
||||
);
|
||||
$cached = $cache->cachedVersion();
|
||||
if (! empty($cached)) {
|
||||
echo $cached;
|
||||
exit;
|
||||
}
|
||||
|
||||
// If cached was not found (or not usable), then read the database and build the response:
|
||||
$LINKSDB = new LinkDB(
|
||||
$GLOBALS['config']['DATASTORE'],
|
||||
isLoggedIn(),
|
||||
$GLOBALS['config']['HIDE_PUBLIC_LINKS'],
|
||||
$GLOBALS['redirector']
|
||||
);
|
||||
// Read links from database (and filter private links if user it not logged in).
|
||||
|
||||
// Optionally filter the results:
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm)
|
||||
);
|
||||
}
|
||||
elseif ($searchtags) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||
}
|
||||
elseif ($searchterm) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||
}
|
||||
else {
|
||||
$linksToDisplay = $LINKSDB;
|
||||
}
|
||||
|
||||
$nblinksToDisplay = 50; // Number of links to display.
|
||||
// In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
|
||||
if (!empty($_GET['nb'])) {
|
||||
$nblinksToDisplay = $_GET['nb'] == 'all' ? count($linksToDisplay) : max(intval($_GET['nb']), 1);
|
||||
}
|
||||
|
||||
$pageaddr = escape(index_url($_SERVER));
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">';
|
||||
echo '<channel><title>'.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>';
|
||||
echo '<description>Shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n\n";
|
||||
if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
|
||||
{
|
||||
echo '<!-- PubSubHubbub Discovery -->';
|
||||
echo '<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" xmlns="http://www.w3.org/2005/Atom" />';
|
||||
echo '<link rel="self" href="'.$pageaddr.'?do=rss" xmlns="http://www.w3.org/2005/Atom" />';
|
||||
echo '<!-- End Of PubSubHubbub Discovery -->';
|
||||
}
|
||||
$i=0;
|
||||
$keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // No, I can't use array_keys().
|
||||
while ($i<$nblinksToDisplay && $i<count($keys))
|
||||
{
|
||||
$link = $linksToDisplay[$keys[$i]];
|
||||
$guid = $pageaddr.'?'.smallHash($link['linkdate']);
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$absurl = $link['url'];
|
||||
if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute
|
||||
if ($usepermalinks===true)
|
||||
echo '<item><title>'.$link['title'].'</title><guid isPermaLink="true">'.$guid.'</guid><link>'.$guid.'</link>';
|
||||
else
|
||||
echo '<item><title>'.$link['title'].'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$absurl.'</link>';
|
||||
if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) {
|
||||
echo '<pubDate>'.escape($date->format(DateTime::RSS))."</pubDate>\n";
|
||||
}
|
||||
if ($link['tags']!='') // Adding tags to each RSS entry (as mentioned in RSS specification)
|
||||
{
|
||||
foreach(explode(' ',$link['tags']) as $tag) { echo '<category domain="'.$pageaddr.'">'.$tag.'</category>'."\n"; }
|
||||
}
|
||||
|
||||
// Add permalink in description
|
||||
$descriptionlink = '(<a href="'.$guid.'">Permalink</a>)';
|
||||
// If user wants permalinks first, put the final link in description
|
||||
if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
|
||||
if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
|
||||
echo '<description><![CDATA['.
|
||||
format_description($link['description'], $GLOBALS['redirector']) .
|
||||
$descriptionlink . ']]></description>' . "\n</item>\n";
|
||||
$i++;
|
||||
}
|
||||
echo '</channel></rss><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
|
||||
|
||||
$cache->cache(ob_get_contents());
|
||||
ob_end_flush();
|
||||
exit;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Output the last N links in ATOM format.
|
||||
function showATOM()
|
||||
{
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
|
||||
// $usepermalink : If true, use permalink instead of final link.
|
||||
// User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=atom&permalinks
|
||||
$usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
|
||||
|
||||
// Cache system
|
||||
$query = $_SERVER["QUERY_STRING"];
|
||||
$cache = new CachedPage(
|
||||
$GLOBALS['config']['PAGECACHE'],
|
||||
page_url($_SERVER),
|
||||
startsWith($query,'do=atom') && !isLoggedIn()
|
||||
);
|
||||
$cached = $cache->cachedVersion();
|
||||
if (!empty($cached)) {
|
||||
echo $cached;
|
||||
exit;
|
||||
}
|
||||
|
||||
// If cached was not found (or not usable), then read the database and build the response:
|
||||
// Read links from database (and filter private links if used it not logged in).
|
||||
$LINKSDB = new LinkDB(
|
||||
$GLOBALS['config']['DATASTORE'],
|
||||
isLoggedIn(),
|
||||
$GLOBALS['config']['HIDE_PUBLIC_LINKS'],
|
||||
$GLOBALS['redirector']
|
||||
);
|
||||
|
||||
// Optionally filter the results:
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm)
|
||||
);
|
||||
}
|
||||
elseif ($searchtags) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||
}
|
||||
elseif ($searchterm) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||
}
|
||||
else {
|
||||
$linksToDisplay = $LINKSDB;
|
||||
}
|
||||
|
||||
$nblinksToDisplay = 50; // Number of links to display.
|
||||
// In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
|
||||
if (!empty($_GET['nb'])) {
|
||||
$nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max(intval($_GET['nb']), 1);
|
||||
}
|
||||
|
||||
$pageaddr=escape(index_url($_SERVER));
|
||||
$latestDate = '';
|
||||
$entries='';
|
||||
$i=0;
|
||||
$keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // No, I can't use array_keys().
|
||||
while ($i<$nblinksToDisplay && $i<count($keys))
|
||||
{
|
||||
$link = $linksToDisplay[$keys[$i]];
|
||||
$guid = $pageaddr.'?'.smallHash($link['linkdate']);
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$iso8601date = $date->format(DateTime::ISO8601);
|
||||
$latestDate = max($latestDate, $iso8601date);
|
||||
$absurl = $link['url'];
|
||||
if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute
|
||||
$entries.='<entry><title>'.$link['title'].'</title>';
|
||||
if ($usepermalinks===true)
|
||||
$entries.='<link href="'.$guid.'" /><id>'.$guid.'</id>';
|
||||
else
|
||||
$entries.='<link href="'.$absurl.'" /><id>'.$guid.'</id>';
|
||||
|
||||
if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) {
|
||||
$entries.='<updated>'.escape($iso8601date).'</updated>';
|
||||
}
|
||||
|
||||
// Add permalink in description
|
||||
$descriptionlink = '(<a href="'.$guid.'">Permalink</a>)';
|
||||
// If user wants permalinks first, put the final link in description
|
||||
if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
|
||||
if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
|
||||
|
||||
$entries .= '<content type="html"><![CDATA['.
|
||||
format_description($link['description'], $GLOBALS['redirector']) .
|
||||
$descriptionlink . "]]></content>\n";
|
||||
if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification)
|
||||
{
|
||||
foreach(explode(' ',$link['tags']) as $tag)
|
||||
{ $entries.='<category scheme="'.$pageaddr.'" term="'.$tag.'" />'."\n"; }
|
||||
}
|
||||
$entries.="</entry>\n";
|
||||
$i++;
|
||||
}
|
||||
$feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">';
|
||||
$feed.='<title>'.$GLOBALS['title'].'</title>';
|
||||
if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.escape($latestDate).'</updated>';
|
||||
$feed.='<link rel="self" href="'.escape(server_url($_SERVER).$_SERVER["REQUEST_URI"]).'" />';
|
||||
if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
|
||||
{
|
||||
$feed.='<!-- PubSubHubbub Discovery -->';
|
||||
$feed.='<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" />';
|
||||
$feed.='<!-- End Of PubSubHubbub Discovery -->';
|
||||
}
|
||||
$feed.='<author><name>'.$pageaddr.'</name><uri>'.$pageaddr.'</uri></author>';
|
||||
$feed.='<id>'.$pageaddr.'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
|
||||
$feed.=$entries;
|
||||
$feed.='</feed><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
|
||||
echo $feed;
|
||||
|
||||
$cache->cache(ob_get_contents());
|
||||
ob_end_flush();
|
||||
exit;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Daily RSS feed: 1 RSS entry per day giving all the links on that day.
|
||||
// Gives the last 7 days (which have links).
|
||||
|
@ -1018,7 +816,7 @@ function showDaily($pageBuilder)
|
|||
}
|
||||
|
||||
try {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_DAY, $day);
|
||||
$linksToDisplay = $LINKSDB->filterDay($day);
|
||||
} catch (Exception $exc) {
|
||||
error_log($exc);
|
||||
$linksToDisplay = array();
|
||||
|
@ -1164,24 +962,7 @@ function renderPage()
|
|||
if ($targetPage == Router::$PAGE_PICWALL)
|
||||
{
|
||||
// Optionally filter the results:
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$links = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm)
|
||||
);
|
||||
}
|
||||
elseif ($searchtags) {
|
||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||
}
|
||||
elseif ($searchterm) {
|
||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||
}
|
||||
else {
|
||||
$links = $LINKSDB;
|
||||
}
|
||||
|
||||
$links = $LINKSDB->filterSearch($_GET);
|
||||
$linksToDisplay = array();
|
||||
|
||||
// Get only links which have a thumbnail.
|
||||
|
@ -1260,6 +1041,49 @@ function renderPage()
|
|||
showDaily($PAGE);
|
||||
}
|
||||
|
||||
// ATOM and RSS feed.
|
||||
if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) {
|
||||
$feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM;
|
||||
header('Content-Type: application/'. $feedType .'+xml; charset=utf-8');
|
||||
|
||||
// Cache system
|
||||
$query = $_SERVER['QUERY_STRING'];
|
||||
$cache = new CachedPage(
|
||||
$GLOBALS['config']['PAGECACHE'],
|
||||
page_url($_SERVER),
|
||||
startsWith($query,'do='. $targetPage) && !isLoggedIn()
|
||||
);
|
||||
$cached = $cache->cachedVersion();
|
||||
if (false && !empty($cached)) {
|
||||
echo $cached;
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate data.
|
||||
$feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn());
|
||||
$feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
|
||||
$feedGenerator->setHideDates($GLOBALS['config']['HIDE_TIMESTAMPS'] && !isLoggedIn());
|
||||
$feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']);
|
||||
if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) {
|
||||
$feedGenerator->setPubsubhubUrl($GLOBALS['config']['PUBSUBHUB_URL']);
|
||||
}
|
||||
$data = $feedGenerator->buildData();
|
||||
|
||||
// Process plugin hook.
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
$pluginManager->executeHooks('render_feed', $data, array(
|
||||
'loggedin' => isLoggedIn(),
|
||||
'target' => $targetPage,
|
||||
));
|
||||
|
||||
// Render the template.
|
||||
$PAGE->assignAll($data);
|
||||
$PAGE->renderPage('feed.'. $feedType);
|
||||
$cache->cache(ob_get_contents());
|
||||
ob_end_flush();
|
||||
exit;
|
||||
}
|
||||
|
||||
// Display openseach plugin (XML)
|
||||
if ($targetPage == Router::$PAGE_OPENSEARCH) {
|
||||
header('Content-Type: application/xml; charset=utf-8');
|
||||
|
@ -1511,9 +1335,9 @@ function renderPage()
|
|||
|
||||
// Delete a tag:
|
||||
if (isset($_POST['deletetag']) && !empty($_POST['fromtag'])) {
|
||||
$needle=trim($_POST['fromtag']);
|
||||
$needle = trim($_POST['fromtag']);
|
||||
// True for case-sensitive tag search.
|
||||
$linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true);
|
||||
$linksToAlter = $LINKSDB->filterSearch(array('searchtags' => $needle), true);
|
||||
foreach($linksToAlter as $key=>$value)
|
||||
{
|
||||
$tags = explode(' ',trim($value['tags']));
|
||||
|
@ -1528,9 +1352,9 @@ function renderPage()
|
|||
|
||||
// Rename a tag:
|
||||
if (isset($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag'])) {
|
||||
$needle=trim($_POST['fromtag']);
|
||||
$needle = trim($_POST['fromtag']);
|
||||
// True for case-sensitive tag search.
|
||||
$linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true);
|
||||
$linksToAlter = $LINKSDB->filterSearch(array('searchtags' => $needle), true);
|
||||
foreach($linksToAlter as $key=>$value)
|
||||
{
|
||||
$tags = explode(' ',trim($value['tags']));
|
||||
|
@ -1966,60 +1790,32 @@ function importFile()
|
|||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Template for the list of links (<div id="linklist">)
|
||||
// This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
|
||||
/**
|
||||
* Template for the list of links (<div id="linklist">)
|
||||
* This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
|
||||
*
|
||||
* @param pageBuilder $PAGE pageBuilder instance.
|
||||
* @param LinkDB $LINKSDB LinkDB instance.
|
||||
*/
|
||||
function buildLinkList($PAGE,$LINKSDB)
|
||||
{
|
||||
// Filter link database according to parameters.
|
||||
// Used in templates
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape(trim($_GET['searchterm'])) : '';
|
||||
$privateonly = !empty($_SESSION['privateonly']) ? true : false;
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
|
||||
// Search tags + fullsearch.
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm),
|
||||
false,
|
||||
$privateonly
|
||||
);
|
||||
}
|
||||
// Search by tags.
|
||||
elseif (! empty($searchtags)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG,
|
||||
$searchtags,
|
||||
false,
|
||||
$privateonly
|
||||
);
|
||||
}
|
||||
// Fulltext search.
|
||||
elseif (! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TEXT,
|
||||
$searchterm,
|
||||
false,
|
||||
$privateonly
|
||||
);
|
||||
}
|
||||
// Detect smallHashes in URL.
|
||||
elseif (! empty($_SERVER['QUERY_STRING'])
|
||||
&& preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING'])
|
||||
) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_HASH,
|
||||
substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6)
|
||||
);
|
||||
|
||||
if (count($linksToDisplay) == 0) {
|
||||
$PAGE->render404('The link you are trying to reach does not exist or has been deleted.');
|
||||
// Smallhash filter
|
||||
if (! empty($_SERVER['QUERY_STRING'])
|
||||
&& preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) {
|
||||
try {
|
||||
$linksToDisplay = $LINKSDB->filterHash($_SERVER['QUERY_STRING']);
|
||||
} catch (LinkNotFoundException $e) {
|
||||
$PAGE->render404($e->getMessage());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
// Otherwise, display without filtering.
|
||||
else {
|
||||
$linksToDisplay = $LINKSDB->filter('', '', false, $privateonly);
|
||||
} else {
|
||||
// Filter links according search parameters.
|
||||
$privateonly = !empty($_SESSION['privateonly']);
|
||||
$linksToDisplay = $LINKSDB->filterSearch($_GET, false, $privateonly);
|
||||
}
|
||||
|
||||
// ---- Handle paging.
|
||||
|
@ -2584,8 +2380,6 @@ function resizeImage($filepath)
|
|||
}
|
||||
|
||||
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
|
||||
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
|
||||
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
|
||||
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=dailyrss')) { showDailyRSS(); exit; }
|
||||
if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE'];
|
||||
renderPage();
|
||||
|
|
|
@ -323,3 +323,28 @@ function hook_demo_plugin_delete_link($data)
|
|||
exit('You can not delete a YouTube link. Don\'t ask.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute render_feed hook.
|
||||
* Called with ATOM and RSS feed.
|
||||
*
|
||||
* Special data keys:
|
||||
* - _PAGE_: current page
|
||||
* - _LOGGEDIN_: true/false
|
||||
*
|
||||
* @param array $data data passed to plugin
|
||||
*
|
||||
* @return array altered $data.
|
||||
*/
|
||||
function hook_demo_plugin_render_feed($data)
|
||||
{
|
||||
foreach ($data['links'] as &$link) {
|
||||
if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) {
|
||||
$link['description'] .= ' - ATOM Feed' ;
|
||||
}
|
||||
elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) {
|
||||
$link['description'] .= ' - RSS Feed';
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
|
212
tests/FeedBuilderTest.php
Normal file
212
tests/FeedBuilderTest.php
Normal file
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
|
||||
require_once 'application/FeedBuilder.php';
|
||||
require_once 'application/LinkDB.php';
|
||||
|
||||
/**
|
||||
* FeedBuilderTest class.
|
||||
*
|
||||
* Unit tests for FeedBuilder.
|
||||
*/
|
||||
class FeedBuilderTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var string locale Basque (Spain).
|
||||
*/
|
||||
public static $LOCALE = 'eu_ES';
|
||||
|
||||
/**
|
||||
* @var string language in RSS format.
|
||||
*/
|
||||
public static $RSS_LANGUAGE = 'eu-es';
|
||||
|
||||
/**
|
||||
* @var string language in ATOM format.
|
||||
*/
|
||||
public static $ATOM_LANGUAGUE = 'eu';
|
||||
|
||||
protected static $testDatastore = 'sandbox/datastore.php';
|
||||
|
||||
public static $linkDB;
|
||||
|
||||
public static $serverInfo;
|
||||
|
||||
/**
|
||||
* Called before every test method.
|
||||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$refLinkDB = new ReferenceLinkDB();
|
||||
$refLinkDB->write(self::$testDatastore);
|
||||
self::$linkDB = new LinkDB(self::$testDatastore, true, false);
|
||||
self::$serverInfo = array(
|
||||
'HTTPS' => 'Off',
|
||||
'SERVER_NAME' => 'host.tld',
|
||||
'SERVER_PORT' => '80',
|
||||
'SCRIPT_NAME' => '/index.php',
|
||||
'REQUEST_URI' => '/index.php?do=feed',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test GetTypeLanguage().
|
||||
*/
|
||||
public function testGetTypeLanguage()
|
||||
{
|
||||
$feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$this->assertEquals(self::$ATOM_LANGUAGUE, $feedBuilder->getTypeLanguage());
|
||||
$feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$this->assertEquals(self::$RSS_LANGUAGE, $feedBuilder->getTypeLanguage());
|
||||
$feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false);
|
||||
$this->assertEquals('en', $feedBuilder->getTypeLanguage());
|
||||
$feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false);
|
||||
$this->assertEquals('en-en', $feedBuilder->getTypeLanguage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with RSS feed.
|
||||
*/
|
||||
public function testRSSBuildData()
|
||||
{
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_RSS, self::$serverInfo, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$data = $feedBuilder->buildData();
|
||||
// Test headers (RSS)
|
||||
$this->assertEquals(self::$RSS_LANGUAGE, $data['language']);
|
||||
$this->assertEmpty($data['pubsubhub_url']);
|
||||
$this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $data['last_update']);
|
||||
$this->assertEquals(true, $data['show_dates']);
|
||||
$this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']);
|
||||
$this->assertEquals('http://host.tld/', $data['index_url']);
|
||||
$this->assertFalse($data['usepermalinks']);
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
|
||||
// Test first link (note link)
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
$this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
|
||||
$this->assertEquals('http://host.tld/?WDWyig', $link['url']);
|
||||
$this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $link['iso_date']);
|
||||
$this->assertContains('Stallman has a beard', $link['description']);
|
||||
$this->assertContains('Permalink', $link['description']);
|
||||
$this->assertContains('http://host.tld/?WDWyig', $link['description']);
|
||||
$this->assertEquals(1, count($link['taglist']));
|
||||
$this->assertEquals('stuff', $link['taglist'][0]);
|
||||
|
||||
// Test URL with external link.
|
||||
$this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']);
|
||||
|
||||
// Test multitags.
|
||||
$this->assertEquals(5, count($data['links']['20141125_084734']['taglist']));
|
||||
$this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with ATOM feed (test only specific to ATOM).
|
||||
*/
|
||||
public function testAtomBuildData()
|
||||
{
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('2015-03-10T11:46:51+01:00', $link['iso_date']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with search criteria.
|
||||
*/
|
||||
public function testBuildDataFiltered()
|
||||
{
|
||||
$criteria = array(
|
||||
'searchtags' => 'stuff',
|
||||
'searchterm' => 'beard',
|
||||
);
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(1, count($data['links']));
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with nb limit.
|
||||
*/
|
||||
public function testBuildDataCount()
|
||||
{
|
||||
$criteria = array(
|
||||
'nb' => '1',
|
||||
);
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(1, count($data['links']));
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with permalinks on.
|
||||
*/
|
||||
public function testBuildDataPermalinks()
|
||||
{
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$feedBuilder->setUsePermalinks(true);
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
$this->assertTrue($data['usepermalinks']);
|
||||
// First link is a permalink
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
$this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
|
||||
$this->assertEquals('http://host.tld/?WDWyig', $link['url']);
|
||||
$this->assertContains('Direct link', $link['description']);
|
||||
$this->assertContains('http://host.tld/?WDWyig', $link['description']);
|
||||
// Second link is a direct link
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114633', $link['linkdate']);
|
||||
$this->assertEquals('http://host.tld/?kLHmZg', $link['guid']);
|
||||
$this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
|
||||
$this->assertContains('Direct link', $link['description']);
|
||||
$this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with hide dates settings.
|
||||
*/
|
||||
public function testBuildDataHideDates()
|
||||
{
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$feedBuilder->setHideDates(true);
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
$this->assertFalse($data['show_dates']);
|
||||
|
||||
// Show dates while logged in
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, true);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$feedBuilder->setHideDates(true);
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
$this->assertTrue($data['show_dates']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buildData with hide dates settings.
|
||||
*/
|
||||
public function testBuildDataPubsubhub()
|
||||
{
|
||||
$feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
|
||||
$feedBuilder->setLocale(self::$LOCALE);
|
||||
$feedBuilder->setPubsubhubUrl('http://pubsubhub.io');
|
||||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
$this->assertEquals('http://pubsubhub.io', $data['pubsubhub_url']);
|
||||
}
|
||||
}
|
|
@ -17,8 +17,20 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
|
|||
{
|
||||
// datastore to test write operations
|
||||
protected static $testDatastore = 'sandbox/datastore.php';
|
||||
|
||||
/**
|
||||
* @var ReferenceLinkDB instance.
|
||||
*/
|
||||
protected static $refDB = null;
|
||||
|
||||
/**
|
||||
* @var LinkDB public LinkDB instance.
|
||||
*/
|
||||
protected static $publicLinkDB = null;
|
||||
|
||||
/**
|
||||
* @var LinkDB private LinkDB instance.
|
||||
*/
|
||||
protected static $privateLinkDB = null;
|
||||
|
||||
/**
|
||||
|
@ -335,9 +347,10 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
|
|||
public function testFilterString()
|
||||
{
|
||||
$tags = 'dev cartoon';
|
||||
$request = array('searchtags' => $tags);
|
||||
$this->assertEquals(
|
||||
2,
|
||||
count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
|
||||
count(self::$privateLinkDB->filterSearch($request, true, false))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -347,9 +360,10 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
|
|||
public function testFilterArray()
|
||||
{
|
||||
$tags = array('dev', 'cartoon');
|
||||
$request = array('searchtags' => $tags);
|
||||
$this->assertEquals(
|
||||
2,
|
||||
count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
|
||||
count(self::$privateLinkDB->filterSearch($request, true, false))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -360,14 +374,48 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
|
|||
public function testHiddenTags()
|
||||
{
|
||||
$tags = '.hidden';
|
||||
$request = array('searchtags' => $tags);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
|
||||
count(self::$privateLinkDB->filterSearch($request, true, false))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
0,
|
||||
count(self::$publicLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
|
||||
count(self::$publicLinkDB->filterSearch($request, true, false))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filterHash() with a valid smallhash.
|
||||
*/
|
||||
public function testFilterHashValid()
|
||||
{
|
||||
$request = smallHash('20150310_114651');
|
||||
$this->assertEquals(
|
||||
1,
|
||||
count(self::$publicLinkDB->filterHash($request))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filterHash() with an invalid smallhash.
|
||||
*
|
||||
* @expectedException LinkNotFoundException
|
||||
*/
|
||||
public function testFilterHashInValid1()
|
||||
{
|
||||
$request = 'blabla';
|
||||
self::$publicLinkDB->filterHash($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test filterHash() with an empty smallhash.
|
||||
*
|
||||
* @expectedException LinkNotFoundException
|
||||
*/
|
||||
public function testFilterHashInValid()
|
||||
{
|
||||
self::$publicLinkDB->filterHash('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
*/
|
||||
protected static $linkFilter;
|
||||
|
||||
protected static $NB_LINKS_REFDB = 7;
|
||||
|
||||
/**
|
||||
* Instanciate linkFilter with ReferenceLinkDB data.
|
||||
*/
|
||||
|
@ -29,7 +27,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
public function testFilter()
|
||||
{
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
ReferenceLinkDB::$NB_LINKS_TOTAL,
|
||||
count(self::$linkFilter->filter('', ''))
|
||||
);
|
||||
|
||||
|
@ -40,12 +38,12 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
);
|
||||
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
ReferenceLinkDB::$NB_LINKS_TOTAL,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, ''))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
ReferenceLinkDB::$NB_LINKS_TOTAL,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, ''))
|
||||
);
|
||||
}
|
||||
|
@ -167,13 +165,12 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
|
||||
/**
|
||||
* No link for this hash
|
||||
*
|
||||
* @expectedException LinkNotFoundException
|
||||
*/
|
||||
public function testFilterUnknownSmallHash()
|
||||
{
|
||||
$this->assertEquals(
|
||||
0,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah'))
|
||||
);
|
||||
self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -383,7 +380,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
))
|
||||
);
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
ReferenceLinkDB::$NB_LINKS_TOTAL,
|
||||
count(self::$linkFilter->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
''
|
||||
|
|
|
@ -236,9 +236,9 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
|
|||
$refDB = new ReferenceLinkDB();
|
||||
$refDB->write(self::$testDatastore);
|
||||
$linkDB = new LinkDB(self::$testDatastore, true, false);
|
||||
$this->assertEmpty($linkDB->filter(LinkFilter::$FILTER_TAG, 'exclude'));
|
||||
$this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
|
||||
$updater = new Updater(array(), self::$configFields, $linkDB, true);
|
||||
$updater->updateMethodRenameDashTags();
|
||||
$this->assertNotEmpty($linkDB->filter(LinkFilter::$FILTER_TAG, 'exclude'));
|
||||
$this->assertNotEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
*/
|
||||
class ReferenceLinkDB
|
||||
{
|
||||
public static $NB_LINKS_TOTAL = 7;
|
||||
|
||||
private $_links = array();
|
||||
private $_publicCount = 0;
|
||||
private $_privateCount = 0;
|
||||
|
@ -13,6 +15,15 @@ class ReferenceLinkDB
|
|||
*/
|
||||
function __construct()
|
||||
{
|
||||
$this->addLink(
|
||||
'Link title: @website',
|
||||
'?WDWyig',
|
||||
'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.',
|
||||
0,
|
||||
'20150310_114651',
|
||||
'stuff'
|
||||
);
|
||||
|
||||
$this->addLink(
|
||||
'Free as in Freedom 2.0 @website',
|
||||
'https://static.fsf.org/nosvn/faif-2.0.pdf',
|
||||
|
@ -22,15 +33,6 @@ class ReferenceLinkDB
|
|||
'free gnu software stallman -exclude stuff'
|
||||
);
|
||||
|
||||
$this->addLink(
|
||||
'Link title: @website',
|
||||
'local',
|
||||
'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.',
|
||||
0,
|
||||
'20150310_114651',
|
||||
'stuff'
|
||||
);
|
||||
|
||||
$this->addLink(
|
||||
'MediaGoblin',
|
||||
'http://mediagoblin.org/',
|
||||
|
|
|
@ -22,9 +22,12 @@
|
|||
<input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault"> All new links are private by default</label></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top"><b>Enable RSS Permalinks</b></td>
|
||||
<td valign="top"><b>RSS direct links</b></td>
|
||||
<td>
|
||||
<input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/><label for="enableRssPermalinks"> Switches the RSS feed URLs between full URLs and shortlinks. Enabling it will show a permalink in the description, and the feed item will be linked to the absolute URL. Disabling it swaps this behaviour around (permalink in title and link in description). RSS Permalinks are currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}</b></label>
|
||||
<input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/>
|
||||
<label for="enableRssPermalinks">
|
||||
Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}.</b>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
40
tpl/feed.atom.html
Normal file
40
tpl/feed.atom.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>{$pagetitle}</title>
|
||||
<subtitle>Shaared links</subtitle>
|
||||
{if="$show_dates"}
|
||||
<updated>{$last_update}</updated>
|
||||
{/if}
|
||||
<link rel="self" href="{$self_link}#" />
|
||||
{if="!empty($pubsubhub_url)"}
|
||||
<!-- PubSubHubbub Discovery -->
|
||||
<link rel="hub" href="{$pubsubhub_url}#" />
|
||||
<!-- End Of PubSubHubbub Discovery -->
|
||||
{/if}
|
||||
<author>
|
||||
<name>{$index_url}</name>
|
||||
<uri>{$index_url}</uri>
|
||||
</author>
|
||||
<id>{$index_url}</id>
|
||||
<generator>Shaarli</generator>
|
||||
{loop="links"}
|
||||
<entry>
|
||||
<title>{$value.title}</title>
|
||||
{if="$usepermalinks"}
|
||||
<link href="{$value.guid}#" />
|
||||
{else}
|
||||
<link href="{$value.url}#" />
|
||||
{/if}
|
||||
<id>{$value.guid}</id>
|
||||
{if="$show_dates"}
|
||||
<updated>{$value.iso_date}</updated>
|
||||
{/if}
|
||||
<content type="html" xml:lang="{$language}">
|
||||
<![CDATA[{$value.description}]]>
|
||||
</content>
|
||||
{loop="$value.taglist"}
|
||||
<category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
|
||||
{/loop}
|
||||
</entry>
|
||||
{/loop}
|
||||
</feed>
|
34
tpl/feed.rss.html
Normal file
34
tpl/feed.rss.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{$pagetitle}</title>
|
||||
<link>{$index_url}</link>
|
||||
<description>Shaared links</description>
|
||||
<language>{$language}</language>
|
||||
<copyright>{$index_url}</copyright>
|
||||
<generator>Shaarli</generator>
|
||||
<atom:link rel="self" href="{$self_link}" />
|
||||
{if="!empty($pubsubhub_url)"}
|
||||
<!-- PubSubHubbub Discovery -->
|
||||
<atom:link rel="hub" href="{$pubsubhub_url}" />
|
||||
{/if}
|
||||
{loop="links"}
|
||||
<item>
|
||||
<title>{$value.title}</title>
|
||||
<guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid>
|
||||
{if="$usepermalinks"}
|
||||
<link>{$value.guid}</link>
|
||||
{else}
|
||||
<link>{$value.url}</link>
|
||||
{/if}
|
||||
{if="$show_dates"}
|
||||
<pubDate>{$value.iso_date}</pubDate>
|
||||
{/if}
|
||||
<description><![CDATA[{$value.description}]]></description>
|
||||
{loop="$value.taglist"}
|
||||
<category domain="{$index_url}?searchtags=">{$value}</category>
|
||||
{/loop}
|
||||
</item>
|
||||
{/loop}
|
||||
</channel>
|
||||
</rss>
|
Loading…
Reference in a new issue