はじまりの大地
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Feed;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
class FeedCreator
|
||||
{
|
||||
/** @var \UniversalFeedCreator */
|
||||
protected $feed;
|
||||
|
||||
/** @var FeedCreatorOptions */
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @param FeedCreatorOptions $options
|
||||
*/
|
||||
public function __construct(FeedCreatorOptions $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
|
||||
$this->feed = new \UniversalFeedCreator();
|
||||
$this->feed->title = $this->options->get('title');
|
||||
$this->feed->description = $this->options->get('subtitle');
|
||||
$this->feed->link = DOKU_URL;
|
||||
$this->feed->syndicationURL = DOKU_URL . 'feed.php';
|
||||
$this->feed->cssStyleSheet = DOKU_URL . 'lib/exe/css.php?s=feed';
|
||||
|
||||
$this->initLogo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the feed
|
||||
*
|
||||
* @return string The raw XML for the feed
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
switch ($this->options->get('feed_mode')) {
|
||||
case 'list':
|
||||
$items = $this->fetchItemsFromNamespace();
|
||||
break;
|
||||
case 'search':
|
||||
$items = $this->fetchItemsFromSearch();
|
||||
break;
|
||||
case 'recent':
|
||||
$items = $this->fetchItemsFromRecentChanges();
|
||||
break;
|
||||
default:
|
||||
$items = $this->fetchItemsFromPlugin();
|
||||
}
|
||||
|
||||
$eventData = [
|
||||
'rss' => $this->feed,
|
||||
'data' => &$items,
|
||||
'opt' => &$this->options->options,
|
||||
];
|
||||
$event = new Event('FEED_DATA_PROCESS', $eventData);
|
||||
if ($event->advise_before(false)) {
|
||||
foreach ($items as $item) {
|
||||
$this->createAndAddItem($item);
|
||||
}
|
||||
}
|
||||
$event->advise_after();
|
||||
|
||||
return $this->feed->createFeed($this->options->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the raw data, create feed item and add it to the feed
|
||||
*
|
||||
* @param array|string $data raw item data
|
||||
* @return \FeedItem
|
||||
* @triggers FEED_ITEM_ADD
|
||||
*/
|
||||
protected function createAndAddItem($data)
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$data = ['id' => $data];
|
||||
}
|
||||
|
||||
if (($data['mode'] ?? '') == 'media' || isset($data['media'])) {
|
||||
$data['id'] = $data['media'] ?? $data['id'];
|
||||
$proc = new FeedMediaProcessor($data);
|
||||
} else {
|
||||
$proc = new FeedPageProcessor($data);
|
||||
}
|
||||
|
||||
$item = new \FeedItem();
|
||||
$item->title = $proc->getTitle();
|
||||
if ($this->options->get('show_summary') && $proc->getSummary()) {
|
||||
$item->title .= ' - ' . $proc->getSummary();
|
||||
}
|
||||
$item->date = $proc->getRev();
|
||||
[$item->authorEmail, $item->author] = $proc->getAuthor();
|
||||
$item->link = $proc->getURL($this->options->get('link_to'));
|
||||
$item->description = $proc->getBody($this->options->get('item_content'));
|
||||
|
||||
$evdata = [
|
||||
'item' => $item,
|
||||
'opt' => &$this->options->options,
|
||||
'ditem' => &$data,
|
||||
'rss' => $this->feed,
|
||||
];
|
||||
|
||||
$evt = new Event('FEED_ITEM_ADD', $evdata);
|
||||
if ($evt->advise_before()) {
|
||||
$this->feed->addItem($item);
|
||||
}
|
||||
$evt->advise_after();
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all pages from a namespace
|
||||
*
|
||||
* @todo this currently does not honor the rss_media setting and only ever lists pages
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchItemsFromNamespace()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$ns = ':' . cleanID($this->options->get('namespace'));
|
||||
$ns = utf8_encodeFN(str_replace(':', '/', $ns));
|
||||
|
||||
$data = [];
|
||||
$search_opts = [
|
||||
'depth' => 1,
|
||||
'pagesonly' => true,
|
||||
'listfiles' => true
|
||||
];
|
||||
search(
|
||||
$data,
|
||||
$conf['datadir'],
|
||||
'search_universal',
|
||||
$search_opts,
|
||||
$ns,
|
||||
$lvl = 1,
|
||||
$this->options->get('sort')
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the result of a full text search to the feed object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchItemsFromSearch()
|
||||
{
|
||||
if (!actionOK('search')) throw new \RuntimeException('search is disabled');
|
||||
if (!$this->options->get('search_query')) return [];
|
||||
|
||||
$data = ft_pageSearch($this->options->get('search_query'), $poswords);
|
||||
return array_keys($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add recent changed pages to the feed object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchItemsFromRecentChanges()
|
||||
{
|
||||
global $conf;
|
||||
$flags = 0;
|
||||
if (!$this->options->get('show_deleted')) $flags += RECENTS_SKIP_DELETED;
|
||||
if (!$this->options->get('show_minor')) $flags += RECENTS_SKIP_MINORS;
|
||||
if ($this->options->get('only_new')) $flags += RECENTS_ONLY_CREATION;
|
||||
if ($this->options->get('content_type') == 'media' && $conf['mediarevisions']) {
|
||||
$flags += RECENTS_MEDIA_CHANGES;
|
||||
}
|
||||
if ($this->options->get('content_type') == 'both' && $conf['mediarevisions']) {
|
||||
$flags += RECENTS_MEDIA_PAGES_MIXED;
|
||||
}
|
||||
|
||||
return getRecents(0, $this->options->get('items'), $this->options->get('namespace'), $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add items from a plugin to the feed object
|
||||
*
|
||||
* @triggers FEED_MODE_UNKNOWN
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchItemsFromPlugin()
|
||||
{
|
||||
$eventData = [
|
||||
'opt' => $this->options->options,
|
||||
'data' => [],
|
||||
];
|
||||
$event = new Event('FEED_MODE_UNKNOWN', $eventData);
|
||||
if ($event->advise_before(true)) {
|
||||
throw new \RuntimeException('unknown feed mode');
|
||||
}
|
||||
$event->advise_after();
|
||||
|
||||
return $eventData['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a logo to the feed
|
||||
*
|
||||
* Looks at different possible candidates for a logo and adds the first one
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initLogo()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$this->feed->image = new \FeedImage();
|
||||
$this->feed->image->title = $conf['title'];
|
||||
$this->feed->image->link = DOKU_URL;
|
||||
$this->feed->image->url = tpl_getMediaFile([
|
||||
':wiki:logo.svg',
|
||||
':logo.svg',
|
||||
':wiki:logo.png',
|
||||
':logo.png',
|
||||
':wiki:logo.jpg',
|
||||
':logo.jpg',
|
||||
':wiki:favicon.ico',
|
||||
':favicon.ico',
|
||||
':wiki:dokuwiki.svg',
|
||||
':wiki:dokuwiki-128.png',
|
||||
'images/favicon.ico'
|
||||
], true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Feed;
|
||||
|
||||
use dokuwiki\Extension\Event;
|
||||
|
||||
/**
|
||||
* Hold the options for feed generation
|
||||
*/
|
||||
class FeedCreatorOptions
|
||||
{
|
||||
/** @var array[] supported feed types */
|
||||
protected $types = [
|
||||
'rss' => [
|
||||
'name' => 'RSS0.91',
|
||||
'mime' => 'text/xml; charset=utf-8',
|
||||
],
|
||||
'rss1' => [
|
||||
'name' => 'RSS1.0',
|
||||
'mime' => 'text/xml; charset=utf-8',
|
||||
],
|
||||
'rss2' => [
|
||||
'name' => 'RSS2.0',
|
||||
'mime' => 'text/xml; charset=utf-8',
|
||||
],
|
||||
'atom' => [
|
||||
'name' => 'ATOM0.3',
|
||||
'mime' => 'application/xml; charset=utf-8',
|
||||
],
|
||||
'atom1' => [
|
||||
'name' => 'ATOM1.0',
|
||||
'mime' => 'application/atom+xml; charset=utf-8',
|
||||
],
|
||||
];
|
||||
|
||||
/** @var array[] the set options */
|
||||
public $options = [
|
||||
'type' => 'rss',
|
||||
'feed_mode' => 'recent',
|
||||
'link_to' => 'page',
|
||||
'item_content' => 'diff',
|
||||
'namespace' => '',
|
||||
'items' => 15,
|
||||
'show_minor' => false,
|
||||
'show_deleted' => false,
|
||||
'show_summary' => false,
|
||||
'only_new' => false,
|
||||
'sort' => 'natural',
|
||||
'search_query' => '',
|
||||
'content_type' => 'pages',
|
||||
'guardmail' => 'none',
|
||||
'title' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize the options from the request, falling back to config defaults
|
||||
*
|
||||
* @triggers FEED_OPTS_POSTPROCESS
|
||||
* @param array $options additional options to set (for testing)
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
global $conf;
|
||||
global $INPUT;
|
||||
|
||||
$this->options['type'] = $INPUT->valid(
|
||||
'type',
|
||||
array_keys($this->types),
|
||||
$conf['rss_type']
|
||||
);
|
||||
// we only support 'list', 'search', 'recent' but accept anything so plugins can take over
|
||||
$this->options['feed_mode'] = $INPUT->str('mode', 'recent');
|
||||
$this->options['link_to'] = $INPUT->valid(
|
||||
'linkto',
|
||||
['diff', 'page', 'rev', 'current'],
|
||||
$conf['rss_linkto']
|
||||
);
|
||||
$this->options['item_content'] = $INPUT->valid(
|
||||
'content',
|
||||
['abstract', 'diff', 'htmldiff', 'html'],
|
||||
$conf['rss_content']
|
||||
);
|
||||
$this->options['namespace'] = $INPUT->filter('cleanID')->str('ns');
|
||||
$this->options['items'] = max(0, $INPUT->int('num', $conf['recent']));
|
||||
$this->options['show_minor'] = $INPUT->bool('minor');
|
||||
$this->options['show_deleted'] = $conf['rss_show_deleted'];
|
||||
$this->options['show_summary'] = $conf['rss_show_summary'];
|
||||
$this->options['only_new'] = $INPUT->bool('onlynewpages');
|
||||
$this->options['sort'] = $INPUT->valid(
|
||||
'sort',
|
||||
['natural', 'date'],
|
||||
'natural'
|
||||
);
|
||||
$this->options['search_query'] = $INPUT->str('q');
|
||||
$this->options['content_type'] = $INPUT->valid(
|
||||
'view',
|
||||
['pages', 'media', 'both'],
|
||||
$conf['rss_media']
|
||||
);
|
||||
$this->options['guardmail'] = $conf['mailguard'];
|
||||
$this->options['title'] = $conf['title'];
|
||||
if ($this->options['namespace']) {
|
||||
$this->options['title'] .= ' - ' . $this->options['namespace'];
|
||||
}
|
||||
$this->options['subtitle'] = $conf['tagline'];
|
||||
|
||||
$this->options = array_merge($this->options, $options);
|
||||
|
||||
// initialization finished, let plugins know
|
||||
$eventData = [
|
||||
'opt' => &$this->options,
|
||||
];
|
||||
Event::createAndTrigger('FEED_OPTS_POSTPROCESS', $eventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache key to use for a feed with these options
|
||||
*
|
||||
* Does not contain user or host specific information yet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheKey()
|
||||
{
|
||||
return implode('', array_values($this->options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a feed option by name
|
||||
*
|
||||
* @param string $option The name of the option
|
||||
* @param mixed $default default value if option is not set (should usually not happen)
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($option, $default = null)
|
||||
{
|
||||
return $this->options[$option] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the feed type for UniversalFeedCreator
|
||||
*
|
||||
* This returns the apropriate type for UniversalFeedCreator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->types[$this->options['type']]['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the feed mime type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMimeType()
|
||||
{
|
||||
return $this->types[$this->options['type']]['mime'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Feed;
|
||||
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Accept more or less arbitrary data to represent data to later construct a feed item from.
|
||||
* Provide lazy loading accessors to all the data we need for feed generation.
|
||||
*/
|
||||
abstract class FeedItemProcessor
|
||||
{
|
||||
/** @var string This page's ID */
|
||||
protected $id;
|
||||
|
||||
/** @var array bag of holding */
|
||||
protected $data;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $data Needs to have at least an 'id' key
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
if (!isset($data['id'])) throw new RuntimeException('Missing ID');
|
||||
$this->id = cleanID($data['id']);
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the revision timestamp of this page
|
||||
*
|
||||
* If the input gave us a revision, date or lastmodified already, we trust that it is correct.
|
||||
*
|
||||
* Note: we only handle most current revisions in feeds, so the revision is usually just the
|
||||
* lastmodifed timestamp of the page file. However, if the item does not exist, we need to
|
||||
* determine the revision from the changelog.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRev()
|
||||
{
|
||||
if ($this->data['rev'] ?? 0) return $this->data['rev'];
|
||||
|
||||
if (isset($this->data['date'])) {
|
||||
$this->data['rev'] = (int)$this->data['date'];
|
||||
}
|
||||
|
||||
if (isset($this->data['lastmodified'])) {
|
||||
$this->data['rev'] = (int)$this->data['lastmodified'];
|
||||
}
|
||||
|
||||
return $this->data['rev'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the URL for the feed item based on the link_to option
|
||||
*
|
||||
* @param string $linkto The link_to option
|
||||
* @return string URL
|
||||
*/
|
||||
abstract public function getURL($linkto);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->data['title'] ?? noNS($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the body of the feed item based on the item_content option
|
||||
*
|
||||
* @param string $content The item_content option
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getBody($content);
|
||||
|
||||
/**
|
||||
* Get the change summary for this item if any
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
return (string)($this->data['sum'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the author info for this item
|
||||
*
|
||||
* @return string[] [email, author]
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
global $conf;
|
||||
global $auth;
|
||||
|
||||
$user = $this->data['user'] ?? '';
|
||||
$author = 'Anonymous';
|
||||
$email = 'anonymous@undisclosed.example.com';
|
||||
|
||||
if (!$user) return [$email, $author];
|
||||
$author = $user;
|
||||
$email = $user . '@undisclosed.example.com';
|
||||
|
||||
if ($conf['useacl'] && $auth instanceof AuthPlugin) {
|
||||
$userInfo = $auth->getUserData($user);
|
||||
if ($userInfo) {
|
||||
switch ($conf['showuseras']) {
|
||||
case 'username':
|
||||
case 'username_link':
|
||||
$author = $userInfo['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [$email, $author];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the categories for this item
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
abstract public function getCategory();
|
||||
|
||||
|
||||
/**
|
||||
* Clean HTML for the use in feeds
|
||||
*
|
||||
* @param string $html
|
||||
* @return string
|
||||
*/
|
||||
protected function cleanHTML($html)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
// no TOC in feeds
|
||||
$html = preg_replace('/(<!-- TOC START -->).*(<!-- TOC END -->)/s', '', $html);
|
||||
|
||||
// add alignment for images
|
||||
$html = preg_replace('/(<img .*?class="medialeft")/s', '\\1 align="left"', $html);
|
||||
$html = preg_replace('/(<img .*?class="mediaright")/s', '\\1 align="right"', $html);
|
||||
|
||||
// make URLs work when canonical is not set, regexp instead of rerendering!
|
||||
if (!$conf['canonical']) {
|
||||
$base = preg_quote(DOKU_REL, '/');
|
||||
$html = preg_replace(
|
||||
'/(<a href|<img src)="(' . $base . ')/s',
|
||||
'$1="' . DOKU_URL,
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Feed;
|
||||
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\File\MediaFile;
|
||||
use dokuwiki\Ui\Media\Display;
|
||||
|
||||
class FeedMediaProcessor extends FeedItemProcessor
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function getURL($linkto)
|
||||
{
|
||||
switch ($linkto) {
|
||||
case 'page':
|
||||
$opt = [
|
||||
'image' => $this->getId(),
|
||||
'ns' => getNS($this->getId()),
|
||||
'rev' => $this->getRev()
|
||||
];
|
||||
break;
|
||||
case 'rev':
|
||||
$opt = [
|
||||
'image' => $this->getId(),
|
||||
'ns' => getNS($this->getId()),
|
||||
'rev' => $this->getRev(),
|
||||
'tab_details' => 'history'
|
||||
];
|
||||
break;
|
||||
case 'current':
|
||||
$opt = [
|
||||
'image' => $this->getId(),
|
||||
'ns' => getNS($this->getId())
|
||||
];
|
||||
break;
|
||||
case 'diff':
|
||||
default:
|
||||
$opt = [
|
||||
'image' => $this->getId(),
|
||||
'ns' => getNS($this->getId()),
|
||||
'rev' => $this->getRev(),
|
||||
'tab_details' => 'history',
|
||||
'media_do' => 'diff'
|
||||
];
|
||||
}
|
||||
|
||||
return media_managerURL($opt, '&', true);
|
||||
}
|
||||
|
||||
public function getBody($content)
|
||||
{
|
||||
switch ($content) {
|
||||
case 'diff':
|
||||
case 'htmldiff':
|
||||
$prev = $this->getPrev();
|
||||
|
||||
if ($prev) {
|
||||
if ($this->isExisting()) {
|
||||
$src1 = new MediaFile($this->getId(), $prev);
|
||||
$src2 = new MediaFile($this->getId());
|
||||
} else {
|
||||
$src1 = new MediaFile($this->getId(), $prev);
|
||||
$src2 = null;
|
||||
}
|
||||
} else {
|
||||
$src1 = null;
|
||||
$src2 = new MediaFile($this->getId());
|
||||
}
|
||||
return $this->createDiffTable($src1, $src2);
|
||||
|
||||
case 'abstract':
|
||||
case 'html':
|
||||
default:
|
||||
$src = new Display(new MediaFile($this->getId()));
|
||||
return $this->cleanHTML($src->getPreviewHtml(500, 500));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @todo read exif keywords
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return (array)getNS($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the revision timestamp of this page
|
||||
*
|
||||
* Note: we only handle most current revisions in feeds, so the revision is usually just the
|
||||
* lastmodifed timestamp of the page file. However, if the page does not exist, we need to
|
||||
* determine the revision from the changelog.
|
||||
* @return int
|
||||
*/
|
||||
public function getRev()
|
||||
{
|
||||
$rev = parent::getRev();
|
||||
if ($rev) return $rev;
|
||||
|
||||
if (media_exists($this->id)) {
|
||||
$this->data['rev'] = filemtime(mediaFN($this->id));
|
||||
$this->data['exists'] = true;
|
||||
} else {
|
||||
$this->loadRevisions();
|
||||
}
|
||||
return $this->data['rev'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous revision timestamp of this page
|
||||
*
|
||||
* @return int|null The previous revision or null if there is none
|
||||
*/
|
||||
public function getPrev()
|
||||
{
|
||||
if ($this->data['prev'] ?? 0) return $this->data['prev'];
|
||||
$this->loadRevisions();
|
||||
return $this->data['prev'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this page exist?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExisting()
|
||||
{
|
||||
if (!isset($this->data['exists'])) {
|
||||
$this->data['exists'] = media_exists($this->id);
|
||||
}
|
||||
return $this->data['exists'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the current and previous revision from the changelog
|
||||
* @return void
|
||||
*/
|
||||
protected function loadRevisions()
|
||||
{
|
||||
$changelog = new MediaChangeLog($this->id);
|
||||
$revs = $changelog->getRevisions(0, 2); // FIXME check that this returns the current one correctly
|
||||
if (!isset($this->data['rev'])) {
|
||||
// prefer an already set date, only set if missing
|
||||
// it should usally not happen that neither is available
|
||||
$this->data['rev'] = $revs[0] ?? 0;
|
||||
}
|
||||
// a previous revision might not exist
|
||||
$this->data['prev'] = $revs[1] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a table showing the two media files
|
||||
*
|
||||
* @param MediaFile|null $src1
|
||||
* @param MediaFile|null $src2
|
||||
* @return string
|
||||
*/
|
||||
protected function createDiffTable($src1, $src2)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
$content = '<table>';
|
||||
$content .= '<tr>';
|
||||
$content .= '<th width="50%">' . ($src1 ? $src1->getRev() : '') . '</th>';
|
||||
$content .= '<th width="50%">' . $lang['current'] . '</th>';
|
||||
$content .= '</tr>';
|
||||
$content .= '<tr>';
|
||||
|
||||
$content .= '<td align="center">';
|
||||
if ($src1) {
|
||||
$display = new Display($src1);
|
||||
$display->getPreviewHtml(300, 300);
|
||||
}
|
||||
$content .= '</td>';
|
||||
|
||||
$content .= '<td align="center">';
|
||||
if ($src2) {
|
||||
$display = new Display($src2);
|
||||
$display->getPreviewHtml(300, 300);
|
||||
}
|
||||
$content .= '</td>';
|
||||
|
||||
$content .= '</tr>';
|
||||
$content .= '</table>';
|
||||
|
||||
return $this->cleanHTML($content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace dokuwiki\Feed;
|
||||
|
||||
use Diff;
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use TableDiffFormatter;
|
||||
use UnifiedDiffFormatter;
|
||||
|
||||
/**
|
||||
* Accept more or less arbitrary data to represent a page and provide lazy loading accessors
|
||||
* to all the data we need for feed generation.
|
||||
*/
|
||||
class FeedPageProcessor extends FeedItemProcessor
|
||||
{
|
||||
/** @var array[] metadata */
|
||||
protected $meta;
|
||||
|
||||
// region data processors
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getURL($linkto)
|
||||
{
|
||||
switch ($linkto) {
|
||||
case 'page':
|
||||
$opt = ['rev' => $this->getRev()];
|
||||
break;
|
||||
case 'rev':
|
||||
$opt = ['rev' => $this->getRev(), 'do' => 'revisions'];
|
||||
break;
|
||||
case 'current':
|
||||
$opt = [];
|
||||
break;
|
||||
case 'diff':
|
||||
default:
|
||||
$opt = ['rev' => $this->getRev(), 'do' => 'diff'];
|
||||
}
|
||||
|
||||
return wl($this->getId(), $opt, true, '&');
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getBody($content)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
switch ($content) {
|
||||
case 'diff':
|
||||
$diff = $this->getDiff();
|
||||
// note: diff output must be escaped, UnifiedDiffFormatter provides plain text
|
||||
$udf = new UnifiedDiffFormatter();
|
||||
return "<pre>\n" . hsc($udf->format($diff)) . "\n</pre>";
|
||||
|
||||
case 'htmldiff':
|
||||
$diff = $this->getDiff();
|
||||
// note: no need to escape diff output, TableDiffFormatter provides 'safe' html
|
||||
$tdf = new TableDiffFormatter();
|
||||
$content = '<table>';
|
||||
$content .= '<tr><th colspan="2" width="50%">' . dformat($this->getPrev()) . '</th>';
|
||||
$content .= '<th colspan="2" width="50%">' . $lang['current'] . '</th></tr>';
|
||||
$content .= $tdf->format($diff);
|
||||
$content .= '</table>';
|
||||
return $content;
|
||||
|
||||
case 'html':
|
||||
if ($this->isExisting()) {
|
||||
$html = p_wiki_xhtml($this->getId(), '', false);
|
||||
} else {
|
||||
$html = p_wiki_xhtml($this->getId(), $this->getRev(), false);
|
||||
}
|
||||
return $this->cleanHTML($html);
|
||||
|
||||
case 'abstract':
|
||||
default:
|
||||
return $this->getAbstract();
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getCategory()
|
||||
{
|
||||
$meta = $this->getMetaData();
|
||||
return (array)($meta['subject'] ?? (string)getNS($this->getId()));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region data accessors
|
||||
|
||||
/**
|
||||
* Get the page abstract
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAbstract()
|
||||
{
|
||||
if (!isset($this->data['abstract'])) {
|
||||
$meta = $this->getMetaData();
|
||||
if (isset($meta['description']['abstract'])) {
|
||||
$this->data['abstract'] = (string)$meta['description']['abstract'];
|
||||
} else {
|
||||
$this->data['abstract'] = '';
|
||||
}
|
||||
}
|
||||
return $this->data['abstract'];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getRev()
|
||||
{
|
||||
$rev = parent::getRev();
|
||||
if ($rev) return $rev;
|
||||
|
||||
if (page_exists($this->id)) {
|
||||
$this->data['rev'] = filemtime(wikiFN($this->id));
|
||||
$this->data['exists'] = true;
|
||||
} else {
|
||||
$this->loadRevisions();
|
||||
}
|
||||
return $this->data['rev'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous revision timestamp of this page
|
||||
*
|
||||
* @return int|null The previous revision or null if there is none
|
||||
*/
|
||||
public function getPrev()
|
||||
{
|
||||
if ($this->data['prev'] ?? 0) return $this->data['prev'];
|
||||
$this->loadRevisions();
|
||||
return $this->data['prev'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this page exist?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExisting()
|
||||
{
|
||||
if (!isset($this->data['exists'])) {
|
||||
$this->data['exists'] = page_exists($this->id);
|
||||
}
|
||||
return $this->data['exists'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of this page
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
global $conf;
|
||||
if (!isset($this->data['title'])) {
|
||||
if ($conf['useheading']) {
|
||||
$this->data['title'] = p_get_first_heading($this->id);
|
||||
} else {
|
||||
$this->data['title'] = noNS($this->id);
|
||||
}
|
||||
}
|
||||
return $this->data['title'];
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* Get the metadata of this page
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
protected function getMetaData()
|
||||
{
|
||||
if (!isset($this->meta)) {
|
||||
$this->meta = (array)p_get_metadata($this->id);
|
||||
}
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the current and previous revision from the changelog
|
||||
* @return void
|
||||
*/
|
||||
protected function loadRevisions()
|
||||
{
|
||||
$changelog = new PageChangeLog($this->id);
|
||||
$revs = $changelog->getRevisions(0, 2); // FIXME check that this returns the current one correctly
|
||||
if (!isset($this->data['rev'])) {
|
||||
// prefer an already set date, only set if missing
|
||||
// it should usally not happen that neither is available
|
||||
$this->data['rev'] = $revs[0] ?? 0;
|
||||
}
|
||||
// a previous revision might not exist
|
||||
$this->data['prev'] = $revs[1] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a diff between this and the previous revision
|
||||
*
|
||||
* @return Diff
|
||||
*/
|
||||
protected function getDiff()
|
||||
{
|
||||
$prev = $this->getPrev();
|
||||
|
||||
if ($prev) {
|
||||
return new Diff(
|
||||
explode("\n", rawWiki($this->getId(), $prev)),
|
||||
explode("\n", rawWiki($this->getId(), ''))
|
||||
);
|
||||
}
|
||||
return new Diff([''], explode("\n", rawWiki($this->getId(), '')));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user