はじまりの大地

This commit is contained in:
miteruzo
2024-07-08 03:32:47 +09:00
commit c616a96f53
7749 changed files with 478270 additions and 0 deletions
+232
View File
@@ -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);
}
}
+161
View File
@@ -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'];
}
}
+172
View File
@@ -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;
}
}
+189
View File
@@ -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);
}
}
+215
View File
@@ -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(), '')));
}
}