Merge pull request #3361 from ssahara/revisionHandle3
Hierarchical class Ui\(Revisions,Diff), new class File\PageFile
This commit is contained in:
commit
8a10303f9b
|
@ -195,7 +195,7 @@ abstract class DokuWikiTest extends PHPUnit\Framework\TestCase {
|
|||
* Waits until a new second has passed
|
||||
*
|
||||
* This tried to be clever about the passing of time and return early if possible. Unfortunately
|
||||
* this never worked reliably fo unknown reasons. To avoid flaky tests, this now always simply
|
||||
* this never worked reliably for unknown reasons. To avoid flaky tests, this now always simply
|
||||
* sleeps for a full second on every call.
|
||||
*
|
||||
* @param bool $init no longer used
|
||||
|
|
|
@ -34,6 +34,7 @@ class XmlRpcServerTest extends DokuWikiTest
|
|||
$file = wikiFN($pageName);
|
||||
$timestamp = filemtime($file);
|
||||
$ixrModifiedTime = (new DateTime('@' . $timestamp))->format(DateTime::ATOM);
|
||||
$author = '127.0.0.1'; // read from changelog, $info['user'] or $info['ip']
|
||||
|
||||
$request = <<<EOD
|
||||
<?xml version="1.0"?>
|
||||
|
@ -54,7 +55,7 @@ EOD;
|
|||
<struct>
|
||||
<member><name>name</name><value><string>wiki:dokuwiki</string></value></member>
|
||||
<member><name>lastModified</name><value><dateTime.iso8601>$ixrModifiedTime</dateTime.iso8601></value></member>
|
||||
<member><name>author</name><value><string></string></value></member>
|
||||
<member><name>author</name><value><string>$author</string></value></member>
|
||||
<member><name>version</name><value><int>$timestamp</int></value></member>
|
||||
</struct>
|
||||
</value>
|
||||
|
|
|
@ -35,12 +35,10 @@ class changelog_getrevisionsaround_test extends DokuWikiTest {
|
|||
parent::setup();
|
||||
global $cache_revinfo;
|
||||
$cache =& $cache_revinfo;
|
||||
if(isset($cache['nonexist'])) {
|
||||
unset($cache['nonexist']);
|
||||
}
|
||||
if(isset($cache['mailinglist'])) {
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
unset($cache['nonexist']);
|
||||
unset($cache['mailinglist']);
|
||||
// fix filemtime of page source
|
||||
touch(wikiFN($this->pageid), $this->revsexpected[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,12 +18,8 @@ class changelog_getlastrevisionat_test extends DokuWikiTest {
|
|||
parent::setup();
|
||||
global $cache_revinfo;
|
||||
$cache =& $cache_revinfo;
|
||||
if(isset($cache['nonexist'])) {
|
||||
unset($cache['nonexist']);
|
||||
}
|
||||
if(isset($cache['mailinglist'])) {
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
unset($cache['nonexist']);
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,12 +18,8 @@ class changelog_getrelativerevision_test extends DokuWikiTest {
|
|||
parent::setup();
|
||||
global $cache_revinfo;
|
||||
$cache =& $cache_revinfo;
|
||||
if(isset($cache['nonexist'])) {
|
||||
unset($cache['nonexist']);
|
||||
}
|
||||
if(isset($cache['mailinglist'])) {
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
unset($cache['nonexist']);
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,12 +19,8 @@ class changelog_getrevisionsinfo_test extends DokuWikiTest {
|
|||
parent::setup();
|
||||
global $cache_revinfo;
|
||||
$cache =& $cache_revinfo;
|
||||
if(isset($cache['nonexist'])) {
|
||||
unset($cache['nonexist']);
|
||||
}
|
||||
if(isset($cache['mailinglist'])) {
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
unset($cache['nonexist']);
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,12 +35,10 @@ class changelog_getrevisions_test extends DokuWikiTest {
|
|||
parent::setup();
|
||||
global $cache_revinfo;
|
||||
$cache =& $cache_revinfo;
|
||||
if(isset($cache['nonexist'])) {
|
||||
unset($cache['nonexist']);
|
||||
}
|
||||
if(isset($cache['mailinglist'])) {
|
||||
unset($cache['mailinglist']);
|
||||
}
|
||||
unset($cache['nonexist']);
|
||||
unset($cache['mailinglist']);
|
||||
// fix filemtime of page source
|
||||
touch(wikiFN($this->pageid), $this->revsexpected[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ class changelog_hasrevisions_test extends DokuWikiTest {
|
|||
*/
|
||||
function test_hasrevisions() {
|
||||
$id = 'mailinglist';
|
||||
|
||||
|
||||
$pagelog = new PageChangeLog($id);
|
||||
$result = $pagelog->hasRevisions();
|
||||
$this->assertTrue($result);
|
||||
|
@ -24,7 +24,7 @@ class changelog_hasrevisions_test extends DokuWikiTest {
|
|||
*/
|
||||
function test_norevisions() {
|
||||
$id = 'nonexist';
|
||||
|
||||
|
||||
$pagelog = new PageChangeLog($id);
|
||||
$result = $pagelog->hasRevisions();
|
||||
$this->assertFalse($result);
|
||||
|
|
|
@ -51,7 +51,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
/**
|
||||
* check info keys and values for a non-existent page & admin user
|
||||
*/
|
||||
function test_basic_nonexistentpage(){
|
||||
function test_basic_nonexistentpage() {
|
||||
global $ID,$conf;
|
||||
$ID = 'wiki:start';
|
||||
|
||||
|
@ -66,12 +66,16 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
/**
|
||||
* check info keys and values for a existing page & admin user
|
||||
*/
|
||||
function test_basic_existingpage(){
|
||||
function test_basic_existingpage() {
|
||||
global $ID,$conf;
|
||||
$ID = 'wiki:syntax';
|
||||
$filename = $conf['datadir'].'/wiki/syntax.txt';
|
||||
$rev = filemtime($filename);
|
||||
|
||||
// run once to prepare meta/wiki/syntax.change file for existing page
|
||||
// because pageinfo() set $info['meta']['last_change'] entry
|
||||
pageinfo();
|
||||
|
||||
$info = $this->_get_expected_pageinfo();
|
||||
$info['id'] = 'wiki:syntax';
|
||||
$info['namespace'] = 'wiki';
|
||||
|
@ -80,6 +84,13 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
$info['lastmod'] = $rev;
|
||||
$info['currentrev'] = $rev;
|
||||
$info['meta'] = p_get_metadata($ID);
|
||||
// set from revinfo, $pagelog->getRevisionInfo($info['lastmod'])
|
||||
$info = array_merge($info, array(
|
||||
'ip' => '127.0.0.1',
|
||||
'user' => '',
|
||||
'sum' => 'created - external edit',
|
||||
));
|
||||
$info['editor'] = '127.0.0.1';
|
||||
|
||||
$this->assertEquals($info, pageinfo());
|
||||
}
|
||||
|
@ -87,7 +98,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
/**
|
||||
* check info keys and values for anonymous user
|
||||
*/
|
||||
function test_anonymoususer(){
|
||||
function test_anonymoususer() {
|
||||
global $ID,$conf,$REV;
|
||||
|
||||
unset($_SERVER['REMOTE_USER']);
|
||||
|
@ -106,7 +117,15 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
$info['currentrev'] = $rev;
|
||||
$info['meta'] = p_get_metadata($ID);
|
||||
$info['rev'] = '';
|
||||
// set from revinfo, $pagelog->getRevisionInfo($info['lastmod'])
|
||||
$info = array_merge($info, array(
|
||||
'ip' => '127.0.0.1',
|
||||
'user' => '',
|
||||
'sum' => 'created - external edit',
|
||||
));
|
||||
$info['editor'] = '127.0.0.1';
|
||||
|
||||
// anonymous user
|
||||
$info = array_merge($info, array(
|
||||
'isadmin' => false,
|
||||
'ismanager' => false,
|
||||
|
@ -114,6 +133,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
'client' => '1.2.3.4',
|
||||
));
|
||||
unset($info['userinfo']);
|
||||
|
||||
$this->assertEquals($info, pageinfo());
|
||||
}
|
||||
|
||||
|
@ -121,7 +141,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
* check info keys and values with $REV
|
||||
* (also see $RANGE tests)
|
||||
*/
|
||||
function test_rev(){
|
||||
function test_rev() {
|
||||
global $ID,$conf,$REV;
|
||||
|
||||
$ID = 'wiki:syntax';
|
||||
|
@ -129,7 +149,8 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
$rev = filemtime($filename);
|
||||
$REV = $rev - 100;
|
||||
$ext = '.txt';
|
||||
if($conf['compression']) { //compression in $info['filepath'] determined by wikiFN depends also on if the page exist
|
||||
if ($conf['compression']) {
|
||||
//compression in $info['filepath'] determined by wikiFN depends also on if the page exist
|
||||
$ext .= "." . $conf['compression']; //.gz or .bz2
|
||||
}
|
||||
|
||||
|
@ -148,7 +169,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
/**
|
||||
* check info keys and values with $RANGE
|
||||
*/
|
||||
function test_range(){
|
||||
function test_range() {
|
||||
global $ID,$conf,$REV,$RANGE;
|
||||
|
||||
$ID = 'wiki:syntax';
|
||||
|
@ -164,6 +185,13 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
$info['currentrev'] = $rev;
|
||||
$info['meta'] = p_get_metadata($ID);
|
||||
$info['filepath'] = $filename;
|
||||
// set from revinfo, $pagelog->getRevisionInfo($info['lastmod'])
|
||||
$info = array_merge($info, array(
|
||||
'ip' => '127.0.0.1',
|
||||
'user' => '',
|
||||
'sum' => 'created - external edit',
|
||||
));
|
||||
$info['editor'] = '127.0.0.1';
|
||||
|
||||
// check $RANGE without $REV
|
||||
// expected result $RANGE unchanged
|
||||
|
@ -171,7 +199,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
|
||||
$this->assertEquals($info, pageinfo());
|
||||
$this->assertFalse(isset($REV));
|
||||
$this->assertEquals($range,$RANGE);
|
||||
$this->assertEquals($range, $RANGE);
|
||||
|
||||
// check $RANGE with $REV = current
|
||||
// expected result: $RANGE unchanged, $REV cleared
|
||||
|
@ -180,21 +208,21 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
|
||||
$this->assertEquals($info, pageinfo());
|
||||
$this->assertEquals('',$REV);
|
||||
$this->assertEquals($range,$RANGE);
|
||||
$this->assertEquals($range, $RANGE);
|
||||
|
||||
// check with a real $REV
|
||||
// expected result: $REV and $RANGE are cleared
|
||||
$REV = $rev - 100;
|
||||
|
||||
$this->assertEquals($info, pageinfo());
|
||||
$this->assertEquals('',$REV);
|
||||
$this->assertEquals('',$RANGE);
|
||||
$this->assertEquals('', $REV);
|
||||
$this->assertEquals('', $RANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* test editor entry and external edit
|
||||
*/
|
||||
function test_editor_and_externaledits(){
|
||||
function test_editor_and_externaledits() {
|
||||
global $ID,$conf;
|
||||
$ID = 'wiki:syntax';
|
||||
$filename = $conf['datadir'].'/wiki/syntax.txt';
|
||||
|
@ -216,24 +244,24 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
addLogEntry($rev, $ID);
|
||||
|
||||
$info['meta'] = p_get_metadata($ID);
|
||||
$info['editor'] = $_SERVER['REMOTE_USER'];
|
||||
$info['user'] = $_SERVER['REMOTE_USER'];
|
||||
$info['ip'] = $_SERVER['REMOTE_ADDR'];
|
||||
$info['user'] = $_SERVER['REMOTE_USER'];
|
||||
$info['sum'] = '';
|
||||
$info['editor'] = $info['user'];
|
||||
|
||||
// with an editor ...
|
||||
$this->assertEquals($info, pageinfo());
|
||||
|
||||
// clear the meta['last_change'] value, pageinfo should restore it
|
||||
p_set_metadata($ID,array('last_change' => false));
|
||||
p_set_metadata($ID, array('last_change' => false));
|
||||
|
||||
$this->assertEquals($info, pageinfo());
|
||||
$this->assertEquals($info['meta']['last_change'], p_get_metadata($ID,'last_change'));
|
||||
|
||||
// fake an external edit, pageinfo should clear the last change from meta data
|
||||
// and not return any editor data
|
||||
$now = time()+10;
|
||||
touch($filename,$now);
|
||||
$now = time() + 10;
|
||||
touch($filename, $now);
|
||||
|
||||
$info['lastmod'] = $now;
|
||||
$info['currentrev'] = $now;
|
||||
|
@ -250,12 +278,16 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
/**
|
||||
* check draft
|
||||
*/
|
||||
function test_draft(){
|
||||
function test_draft() {
|
||||
global $ID,$conf;
|
||||
$ID = 'wiki:syntax';
|
||||
$filename = $conf['datadir'].'/wiki/syntax.txt';
|
||||
$rev = filemtime($filename);
|
||||
|
||||
// run once to prepare meta/wiki/syntax.change file for existing page
|
||||
// because pageinfo() set $info['meta']['last_change'] entry
|
||||
pageinfo();
|
||||
|
||||
$info = $this->_get_expected_pageinfo();
|
||||
$info['id'] = 'wiki:syntax';
|
||||
$info['namespace'] = 'wiki';
|
||||
|
@ -264,11 +296,23 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
$info['lastmod'] = $rev;
|
||||
$info['currentrev'] = $rev;
|
||||
$info['meta'] = p_get_metadata($ID);
|
||||
// $info['ip'] = $_SERVER['REMOTE_ADDR'];
|
||||
// $info['user'] = $_SERVER['REMOTE_USER'];
|
||||
// $info['sum'] = '';
|
||||
// $info['editor'] = $info['user'];
|
||||
// set from revinfo, $pagelog->getRevisionInfo($info['lastmod'])
|
||||
$info = array_merge($info, array(
|
||||
'ip' => '127.0.0.1',
|
||||
'user' => '',
|
||||
'sum' => 'external edit',
|
||||
));
|
||||
$info['editor'] = '127.0.0.1';
|
||||
|
||||
// setup a draft, make it more recent than the current page
|
||||
// - pageinfo should recognise it and keep it
|
||||
|
||||
$draft = getCacheName($info['client']."\n".$ID,'.draft');
|
||||
touch($draft,$rev + 10);
|
||||
touch($draft, $rev + 10);
|
||||
|
||||
$info['draft'] = $draft;
|
||||
|
||||
|
@ -287,7 +331,7 @@ class common_pageinfo_test extends DokuWikiTest {
|
|||
/**
|
||||
* check ismobile
|
||||
*/
|
||||
function test_ismobile(){
|
||||
function test_ismobile() {
|
||||
global $ID,$conf;
|
||||
$ID = 'wiki:start';
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
|
||||
/**
|
||||
* saveWikiText() stores files in pages/, attic/ and adds entries to changelog
|
||||
*/
|
||||
class common_saveWikiText_test extends DokuWikiTest {
|
||||
|
||||
/** Delay writes of old revisions by a second. */
|
||||
public function handle_write(Doku_Event $event, $param) {
|
||||
if ($event->data[3] !== false) {
|
||||
|
@ -11,161 +15,266 @@ class common_saveWikiText_test extends DokuWikiTest {
|
|||
}
|
||||
|
||||
/**
|
||||
* Execute a whole bunch of saves on the same page and check the results
|
||||
* assertions against changelog entries and attic after saveWikiText()
|
||||
*/
|
||||
function test_savesequence() {
|
||||
private function checkChangeLogAfterNormalSave(
|
||||
PageChangeLog $pagelog,
|
||||
$expectedRevs, // @param int
|
||||
&$expectedLastEntry, // @param array, pass by reference
|
||||
$expected2ndLastEntry = null // @param array (optional)
|
||||
) {
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertCount($expectedRevs, $revisions);
|
||||
$this->assertCount($expectedRevs, array_unique($revisions), 'date duplicated in changelog');
|
||||
// last revision
|
||||
$lastRevInfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$expectedLastEntry += $lastRevInfo;
|
||||
$this->assertEquals($expectedLastEntry, $lastRevInfo);
|
||||
// current revision
|
||||
$currentRevInfo = $pagelog->getCurrentRevisionInfo();
|
||||
$this->assertEquals($currentRevInfo, $lastRevInfo, 'current & last revs should be identical');
|
||||
// attic
|
||||
$attic = wikiFN($lastRevInfo['id'], $lastRevInfo['date']);
|
||||
$this->assertFileExists($attic, 'file missing in attic');
|
||||
$files = count(glob(dirname($attic).'/'.noNS($lastRevInfo['id']).'.*'));
|
||||
$this->assertLessThanOrEqual($expectedRevs, $files, 'detectExternalEdit() should not add too often old revs');
|
||||
|
||||
// second to last revision (optional), intended to check logline of previous external edits
|
||||
if ($expected2ndLastEntry && count($revisions) > 1) {
|
||||
$prevRevInfo = $pagelog->getRevisionInfo($revisions[1]);
|
||||
unset($expected2ndLastEntry['timestamp']); // drop timestamp key
|
||||
$this->assertEquals($expected2ndLastEntry, $prevRevInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* assertions against changelog entries and attic after external edit, create or deletion of page
|
||||
*/
|
||||
private function checkChangeLogAfterExternalEdit(
|
||||
PageChangeLog $pagelog,
|
||||
$expectedRevs, // @param int
|
||||
$expectedLastEntry, // @param array
|
||||
&$expectedCurrentEntry // @param array, pass by reference
|
||||
) {
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertCount($expectedRevs, $revisions);
|
||||
$this->assertCount($expectedRevs, array_unique($revisions), 'date duplicated in changelog');
|
||||
// last revision
|
||||
if ($expectedRevs > 0) {
|
||||
$lastRevInfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$expectedLastEntry += $lastRevInfo;
|
||||
$this->assertEquals($expectedLastEntry, $lastRevInfo);
|
||||
} else {
|
||||
$this->assertFalse($pagelog->lastRevision(), 'changelog file does not yet exist');
|
||||
}
|
||||
// current revision
|
||||
$currentRevInfo = $pagelog->getCurrentRevisionInfo();
|
||||
$this->assertArrayHasKey('timestamp', $currentRevInfo, 'should be external revision');
|
||||
$expectedCurrentEntry += $currentRevInfo;
|
||||
if ($expectedRevs > 0) {
|
||||
$this->assertEquals($expectedCurrentEntry, $currentRevInfo);
|
||||
|
||||
}
|
||||
// attic
|
||||
$attic = wikiFN($currentRevInfo['id'], $currentRevInfo['date']);
|
||||
$this->assertFileNotExists($attic, 'page does not yet exist in attic');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a whole bunch of saves on the same page and check the results
|
||||
* TEST 1
|
||||
* 1.1 create a page
|
||||
* 1.2 save with same content should be ignored
|
||||
* 1.3 update the page with new text
|
||||
* 1.4 add a minor edit (unauthenticated, minor not allowable)
|
||||
* 1.5 add a minor edit (authenticated)
|
||||
* 1.6 delete
|
||||
* 1.7 restore
|
||||
* 1.8 external edit
|
||||
* 1.9 edit and save on top of external edit
|
||||
*/
|
||||
function test_savesequence1() {
|
||||
global $REV;
|
||||
|
||||
$page = 'page';
|
||||
$file = wikiFN($page);
|
||||
|
||||
// create the page
|
||||
$this->assertFileNotExists($file);
|
||||
saveWikiText($page, 'teststring', 'first save', false);
|
||||
|
||||
// 1.1 create a page
|
||||
saveWikiText($page, 'teststring', '1st save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertFileExists($file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 1;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => '1st save',
|
||||
'sizechange' => 10, // = strlen('teststring')
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(1, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('first save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_CREATE, $revinfo['type']);
|
||||
$this->assertEquals(10, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// save with same content should be ignored
|
||||
saveWikiText($page, 'teststring', 'second save', false);
|
||||
// 1.2 save with same content should be ignored
|
||||
saveWikiText($page, 'teststring', '2nd save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertEquals($lastmod, filemtime($file));
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(1, count($revisions));
|
||||
$this->assertCount(1, $revisions);
|
||||
|
||||
// update the page with new text
|
||||
saveWikiText($page, 'teststring2long', 'third save', false);
|
||||
// 1.3 update the page with new text
|
||||
saveWikiText($page, 'teststring2long', '3rd save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 2;
|
||||
$expectPrev = $expect;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => '3rd save',
|
||||
'sizechange' => 5,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(2, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('third save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(5, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectPrev);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// add a minor edit (unauthenticated)
|
||||
saveWikiText($page, 'teststring3long', 'fourth save', true);
|
||||
// 1.4 add a minor edit (unauthenticated, minor not allowable)
|
||||
saveWikiText($page, 'teststring3long', '4th save', true);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 3;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => '4th save',
|
||||
'sizechange' => 0,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(3, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('fourth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(0, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// add a minor edit (authenticated)
|
||||
// 1.5 add a minor edit (authenticated)
|
||||
$_SERVER['REMOTE_USER'] = 'user';
|
||||
saveWikiText($page, 'teststring4', 'fifth save', true);
|
||||
saveWikiText($page, 'teststring4', '5th save', true);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 4;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_MINOR_EDIT,
|
||||
'sum' => '5th save',
|
||||
'sizechange' => -4,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(4, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('fifth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_MINOR_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(-4, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// delete
|
||||
saveWikiText($page, '', 'sixth save', false);
|
||||
// 1.6 delete
|
||||
saveWikiText($page, '', '6th save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertFileNotExists($file);
|
||||
$expectedRevs = 5;
|
||||
$expect = array(
|
||||
//'date' => $lastmod, // ignore from lastRev assertion, but confirm attic file existence
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'sum' => '6th save',
|
||||
'sizechange' => -11,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(5, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('sixth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_DELETE, $revinfo['type']);
|
||||
$this->assertEquals(-11, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// restore
|
||||
// 1.7 restore
|
||||
$REV = $lastmod;
|
||||
saveWikiText($page, 'teststring4', 'seventh save', true);
|
||||
saveWikiText($page, 'teststring4', '7th save', true);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertFileExists($file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 6;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_REVERT,
|
||||
'sum' => '7th save',
|
||||
'sizechange' => 11,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(6, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('seventh save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_REVERT, $revinfo['type']);
|
||||
$this->assertEquals($REV, $revinfo['extra']);
|
||||
$this->assertEquals(11, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
$REV = '';
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// create external edit
|
||||
file_put_contents($file, 'teststring5');
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// save on top of external edit
|
||||
saveWikiText($page, 'teststring6', 'eigth save', false);
|
||||
// 1.8 external edit
|
||||
file_put_contents($file, 'teststring5 external edit');
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 6; // external edit is not yet in changelog
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'external edit',
|
||||
'sizechange' => 14,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(8, count($revisions)); // two more revisions now!
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('eigth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(0, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[1]);
|
||||
$this->assertEquals('external edit', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(0, $revinfo['sizechange']);
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// 1.9 save on top of external edit
|
||||
saveWikiText($page, 'teststring6', '8th save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 8;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => '8th save',
|
||||
'sizechange' => -14,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a whole bunch of saves on the same page and check the results
|
||||
* using $this->handle_write() in event IO_WIKIPAGE_WRITE
|
||||
* TEST 2 - create a page externally in 2.3, while external edit in Test 1.8
|
||||
* 2.1 create a page
|
||||
* 2.2 delete
|
||||
* 2.3 externally create the page
|
||||
* 2.4 edit and save on top of external edit
|
||||
* 2.5 external edit
|
||||
* 2.6 edit and save on top of external edit, again
|
||||
*/
|
||||
function test_savesequencedeleteexternalrevision() {
|
||||
function test_savesequence2() {
|
||||
// add an additional delay when saving files to make sure
|
||||
// nobody relies on the saving happening in the same second
|
||||
/** @var $EVENT_HANDLER \dokuwiki\Extension\EventHandler */
|
||||
|
@ -174,251 +283,405 @@ class common_saveWikiText_test extends DokuWikiTest {
|
|||
|
||||
$page = 'page2';
|
||||
$file = wikiFN($page);
|
||||
|
||||
// create the page
|
||||
$this->assertFileNotExists($file);
|
||||
saveWikiText($page, 'teststring', 'first save', false);
|
||||
|
||||
// 2.1 create a page
|
||||
saveWikiText($page, 'teststring', 'Test 2, 1st save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertFileExists($file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 1;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'Test 2, 1st save',
|
||||
'sizechange' => 10, // = strlen('teststring')
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(1, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('first save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_CREATE, $revinfo['type']);
|
||||
$this->assertEquals(10, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// delete
|
||||
saveWikiText($page, '', 'second save', false);
|
||||
// 2.2 delete
|
||||
saveWikiText($page, '', 'Test 2, 2nd save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertFileNotExists($file);
|
||||
$expectedRevs = 2;
|
||||
$expect = array(
|
||||
//'date' => $lastmod, // ignore from lastRev assertion, but confirm attic file existence
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'sum' => 'Test 2, 2nd save',
|
||||
'sizechange' => -10,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(2, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('second save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_DELETE, $revinfo['type']);
|
||||
$this->assertEquals(-10, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// create external edit
|
||||
// 2.3 externally create the page
|
||||
file_put_contents($file, 'teststring5');
|
||||
clearstatcache(false, $file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 2; // external edit is not yet in changelog
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'created - external edit',
|
||||
'sizechange' => 11,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// save on top of external edit
|
||||
saveWikiText($page, 'teststring6', 'third save', false);
|
||||
// 2.4 save on top of external edit
|
||||
saveWikiText($page, 'teststring6', 'Test 2, 3rd save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 4; // two more revisions now!
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'Test 2, 3rd save',
|
||||
'sizechange' => 0,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(4, count($revisions)); // two more revisions now!
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('third save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(0, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[1]);
|
||||
$this->assertEquals('external edit', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(11, $revinfo['sizechange']);
|
||||
$this->assertFileExists(wikiFN($page, $revinfo['date']));
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// 2.5 external edit
|
||||
file_put_contents($file, 'teststring7 external edit2');
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 4; // external edit is not yet in changelog
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'external edit',
|
||||
'sizechange' => 15,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// 2.6 save on top of external edit, again
|
||||
saveWikiText($page, 'teststring8', 'Test 2, 4th save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 6; // two more revisions now!
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'Test 2, 4th save',
|
||||
'sizechange' => -15,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a whole bunch of saves on the same page and check the results
|
||||
* TEST 3 - typical page life of bundled page such as wiki:syntax
|
||||
* 3.1 externally create a page
|
||||
* 3.2 external edit
|
||||
* 3.3 edit and save on top of external edit
|
||||
* 3.4 externally delete the page
|
||||
*/
|
||||
function test_saveexternalasfirst() {
|
||||
function test_savesequence3() {
|
||||
$page = 'page3';
|
||||
$file = wikiFN($page);
|
||||
|
||||
// create the page
|
||||
// 3.1 externally create a page
|
||||
$this->assertFileNotExists($file);
|
||||
|
||||
// create external edit
|
||||
file_put_contents($file, 'teststring');
|
||||
clearstatcache(false, $file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 0; // external edit is not yet in changelog
|
||||
$expect = false;
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'created - external edit',
|
||||
'sizechange' => 10,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// save on top of external edit
|
||||
saveWikiText($page, 'teststring6', 'first save', false);
|
||||
// 3.2 external edit (repeated, still no changelog exists)
|
||||
file_put_contents($file, 'teststring external edit');
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 0; // external edit is not yet in changelog
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE, // not DOKU_CHANGE_TYPE_EDIT
|
||||
'sum' => 'created - external edit',
|
||||
'sizechange' => 24,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(2, count($revisions)); // two more revisions now!
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('first save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(1, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[1]);
|
||||
$this->assertEquals('external edit', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(10, $revinfo['sizechange']);
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// 3.3 save on top of external edit
|
||||
saveWikiText($page, 'teststring1', 'Test 3, first save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 2; // two more revisions now!
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'Test 3, first save',
|
||||
'sizechange' => -13,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// 3.4 externally delete the page
|
||||
unlink($file);
|
||||
$expectedRevs = 2;
|
||||
$expectExternal = array(
|
||||
//'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'sum' => 'removed - external edit (Unknown date)',
|
||||
'sizechange' => -11,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a whole bunch of saves on the same page and check the results
|
||||
* TEST 4 - typical page life of bundled page such as wiki:syntax
|
||||
* 4.1 externally create a page
|
||||
* 4.2 edit and save
|
||||
* 4.3 externally edit as a result of a file which has older timestamp than last revision
|
||||
*/
|
||||
function test_savesequenceexternaldeleteedit() {
|
||||
function test_savesequence4() {
|
||||
$page = 'page4';
|
||||
$file = wikiFN($page);
|
||||
|
||||
// create the page
|
||||
// 4.1 externally create a page
|
||||
$this->assertFileNotExists($file);
|
||||
saveWikiText($page, 'teststring', 'first save', false);
|
||||
$this->assertFileExists($file);
|
||||
file_put_contents($file, 'teststring');
|
||||
clearstatcache(false, $file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 0; // external edit is not yet in changelog
|
||||
$expect = false;
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'created - external edit',
|
||||
'sizechange' => 10,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(1, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('first save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_CREATE, $revinfo['type']);
|
||||
$this->assertEquals(10, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
|
||||
// create external delete
|
||||
unlink($file);
|
||||
clearstatcache(false, $file);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// save on top of external delete. save is seen as creation
|
||||
saveWikiText($page, 'teststring6', 'second save', false);
|
||||
// 4.2 edit and save
|
||||
saveWikiText($page, 'teststring1', 'Test 4, first save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 2; // two more revisions now!
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'Test 4, first save',
|
||||
'sizechange' => 1,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(2, count($revisions)); // one more revisions now!
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('second save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_CREATE, $revinfo['type']);
|
||||
$this->assertEquals(11, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[1]);
|
||||
$this->assertEquals('first save', $revinfo['sum']);
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// 4.3 externally edit as a result of a file which has older timestamp than last revision
|
||||
unlink($file);
|
||||
file_put_contents($file, 'teststring fake 1 hout past');
|
||||
touch($file, filemtime($file) -3600); // change file modification time to 1 hour past
|
||||
clearstatcache();
|
||||
$newmod = filemtime($file);
|
||||
$this->assertLessThan($lastmod, $newmod); // file must be older than previous for this test
|
||||
$expectedRevs = 2; // external edit is not yet in changelog
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod + 1,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'external edit (Unknown date)',
|
||||
'sizechange' => 16,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a whole bunch of saves on the same page and check the results
|
||||
* TEST 5 - page creation and deletion
|
||||
* 5.1 create a page
|
||||
* 5.2 external edit
|
||||
* 5.3 edit and save on top of external edit
|
||||
* 5.4 delete
|
||||
* 5.5 create a page, second time
|
||||
* 5.6 externally delete
|
||||
* 5.7 create a page, third time
|
||||
*/
|
||||
function test_savesequencerevert() {
|
||||
global $REV;
|
||||
|
||||
function test_savesequence5() {
|
||||
$page = 'page5';
|
||||
$file = wikiFN($page);
|
||||
|
||||
// create the page
|
||||
$this->assertFileNotExists($file);
|
||||
saveWikiText($page, 'teststring', 'first save', false);
|
||||
|
||||
// 5.1 create a page
|
||||
saveWikiText($page, 'teststring', 'Test 5, 1st save', false);
|
||||
$this->assertFileExists($file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 1;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'Test 5, 1st save',
|
||||
'sizechange' => 10, // = strlen('teststring')
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(1, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('first save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_CREATE, $revinfo['type']);
|
||||
$this->assertEquals(10, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// save with same content should be ignored
|
||||
saveWikiText($page, 'teststring', 'second save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertEquals($lastmod, filemtime($file));
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(1, count($revisions));
|
||||
|
||||
// update the page with new text
|
||||
saveWikiText($page, 'teststring2long', 'third save', false);
|
||||
// 5.2 external edit
|
||||
file_put_contents($file, 'teststring external edit');
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$revertrev = $newmod;
|
||||
$expectedRevs = 1; // external edit is not yet in changelog
|
||||
$expectExternal = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'external edit',
|
||||
'sizechange' => 14,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(2, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('third save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(5, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// add a minor edit (unauthenticated)
|
||||
saveWikiText($page, 'teststring3long', 'fourth save', true);
|
||||
// 5.3 edit and save on top of external edit
|
||||
saveWikiText($page, 'teststring normal edit', 'Test 5, 2nd save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$expectedRevs = 3; // two more revisions now!
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_EDIT,
|
||||
'sum' => 'Test 5, 2nd save',
|
||||
'sizechange' => -2,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(3, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('fourth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(0, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// add a minor edit (authenticated)
|
||||
$_SERVER['REMOTE_USER'] = 'user';
|
||||
saveWikiText($page, 'teststring4', 'fifth save', true);
|
||||
// 5.4 delete
|
||||
saveWikiText($page, '', 'Test 5 3rd save', false);
|
||||
clearstatcache(false, $file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = $newmod;
|
||||
$this->assertFileNotExists($file);
|
||||
$expectedRevs = 4;
|
||||
$expect = array(
|
||||
//'date' => $lastmod, // ignore from lastRev assertion, but confirm attic file existence
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'sum' => 'Test 5 3rd save',
|
||||
'sizechange' => -22,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(4, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('fifth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_MINOR_EDIT, $revinfo['type']);
|
||||
$this->assertEquals(-4, $revinfo['sizechange']);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(); // wait for new revision ID
|
||||
|
||||
// restore
|
||||
$REV = $revertrev;
|
||||
saveWikiText($page, 'teststring2long', 'sixth save', true);
|
||||
// 5.5 create a page, second time
|
||||
$this->assertFileNotExists($file);
|
||||
saveWikiText($page, 'teststring revived', 'Test 5, 4th save', false);
|
||||
$this->assertFileExists($file);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 5;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'Test 5, 4th save',
|
||||
'sizechange' => 18, // = strlen('teststring revived')
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// 5.6 externally delete
|
||||
unlink($file);
|
||||
$this->assertFileNotExists($file);
|
||||
$expectedRevs = 5;
|
||||
$expectExternal = array(
|
||||
//'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'sum' => 'removed - external edit (Unknown date)',
|
||||
'sizechange' => -18,
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
|
||||
$this->waitForTick(true); // wait for new revision ID
|
||||
|
||||
// 5.7 create a page, third time
|
||||
$this->assertFileNotExists($file);
|
||||
saveWikiText($page, 'teststring revived 2', 'Test 5, 5th save', false);
|
||||
clearstatcache(false, $file);
|
||||
$this->assertFileExists($file);
|
||||
$newmod = filemtime($file);
|
||||
$this->assertNotEquals($lastmod, $newmod);
|
||||
$lastmod = filemtime($file);
|
||||
$expectedRevs = 7;
|
||||
$expect = array(
|
||||
'date' => $lastmod,
|
||||
'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'sum' => 'Test 5, 5th save',
|
||||
'sizechange' => 20, // = strlen('teststring revived 2')
|
||||
);
|
||||
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$revisions = $pagelog->getRevisions(-1, 200);
|
||||
$this->assertEquals(5, count($revisions));
|
||||
$revinfo = $pagelog->getRevisionInfo($revisions[0]);
|
||||
$this->assertEquals('sixth save', $revinfo['sum']);
|
||||
$this->assertEquals(DOKU_CHANGE_TYPE_REVERT, $revinfo['type']);
|
||||
$this->assertEquals($REV, $revinfo['extra']);
|
||||
$this->assertEquals(4, $revinfo['sizechange']);
|
||||
$REV = '';
|
||||
$this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect, $expectExternal);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ class Diff extends AbstractAction
|
|||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
(new Ui\Diff())->show();
|
||||
global $INFO;
|
||||
(new Ui\PageDiff($INFO['id']))->preference('showIntro', true)->show();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class Revisions extends AbstractAction
|
|||
/** @inheritdoc */
|
||||
public function tplContent()
|
||||
{
|
||||
global $INPUT;
|
||||
(new Ui\Revisions($INPUT->int('first')))->show();
|
||||
global $INFO, $INPUT;
|
||||
(new Ui\PageRevisions($INFO['id']))->show($INPUT->int('first'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -243,14 +243,11 @@ class Ajax {
|
|||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function callMediadiff() {
|
||||
global $NS;
|
||||
global $INPUT;
|
||||
|
||||
$image = '';
|
||||
if($INPUT->has('image')) $image = cleanID($INPUT->str('image'));
|
||||
$NS = getNS($image);
|
||||
$auth = auth_quickaclcheck("$NS:*");
|
||||
media_diff($image, $NS, $auth, true);
|
||||
(new Ui\MediaDiff($image))->preference('fromAjax', true)->show();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,16 +2,19 @@
|
|||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
use dokuwiki\Logger;
|
||||
|
||||
/**
|
||||
* methods for handling of changelog of pages or media files
|
||||
* ChangeLog Prototype; methods for handling changelog
|
||||
*/
|
||||
abstract class ChangeLog
|
||||
{
|
||||
use ChangeLogTrait;
|
||||
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var int */
|
||||
protected $chunk_size;
|
||||
protected $currentRevision;
|
||||
/** @var array */
|
||||
protected $cache;
|
||||
|
||||
|
@ -32,29 +35,10 @@ abstract class ChangeLog
|
|||
|
||||
$this->id = $id;
|
||||
$this->setChunkSize($chunk_size);
|
||||
|
||||
// set property currentRevision and cache prior to getRevisionInfo($currentRev) call
|
||||
$this->getCurrentRevisionInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set chunk size for file reading
|
||||
* Chunk size zero let read whole file at once
|
||||
*
|
||||
* @param int $chunk_size maximum block size read from file
|
||||
*/
|
||||
public function setChunkSize($chunk_size)
|
||||
{
|
||||
if (!is_numeric($chunk_size)) $chunk_size = 0;
|
||||
|
||||
$this->chunk_size = (int)max($chunk_size, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to changelog
|
||||
*
|
||||
* @return string path to file
|
||||
*/
|
||||
abstract protected function getChangelogFilename();
|
||||
|
||||
/**
|
||||
* Returns path to current page/media
|
||||
*
|
||||
|
@ -63,7 +47,74 @@ abstract class ChangeLog
|
|||
abstract protected function getFilename();
|
||||
|
||||
/**
|
||||
* Get the changelog information for a specific page id and revision (timestamp)
|
||||
* Check whether given revision is the current page
|
||||
*
|
||||
* @param int $rev timestamp of current page
|
||||
* @return bool true if $rev is current revision, otherwise false
|
||||
*/
|
||||
public function isCurrentRevision($rev)
|
||||
{
|
||||
return $rev == $this->currentRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the revision is last revision
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @return bool true if $rev is last revision, otherwise false
|
||||
*/
|
||||
public function isLastRevision($rev = null)
|
||||
{
|
||||
return $rev === $this->lastRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current revision identifer
|
||||
*
|
||||
* The "current" revision means current version of the page or media file. It is either
|
||||
* identical with or newer than the "last" revision, that depends on whether the file
|
||||
* has modified, created or deleted outside of DokuWiki.
|
||||
* The value of identifier can be determined by timestamp as far as the file exists,
|
||||
* otherwise it must be assigned larger than any other revisions to keep them sortable.
|
||||
*
|
||||
* @return int|false revision timestamp
|
||||
*/
|
||||
public function currentRevision()
|
||||
{
|
||||
if (!isset($this->currentRevision)) {
|
||||
// set ChangeLog::currentRevision property
|
||||
$this->getCurrentRevisionInfo();
|
||||
}
|
||||
return $this->currentRevision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last revision identifer, date value of the last entry of the changelog
|
||||
*
|
||||
* @return int|false revision timestamp
|
||||
*/
|
||||
public function lastRevision()
|
||||
{
|
||||
$revs = $this->getRevisions(-1, 1);
|
||||
return empty($revs) ? false : $revs[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save revision info to the cache pool
|
||||
*
|
||||
* @param array $info Revision info structure
|
||||
* @return bool
|
||||
*/
|
||||
protected function cacheRevisionInfo($info)
|
||||
{
|
||||
if (!is_array($info)) return false;
|
||||
//$this->cache[$this->id][$info['date']] ??= $info; // since php 7.4
|
||||
$this->cache[$this->id][$info['date']] = $this->cache[$this->id][$info['date']] ?? $info;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the changelog information for a specific revision (timestamp)
|
||||
*
|
||||
* Adjacent changelog lines are optimistically parsed and cached to speed up
|
||||
* consecutive calls to getRevisionInfo. For large changelog files, only the chunk
|
||||
|
@ -78,13 +129,15 @@ abstract class ChangeLog
|
|||
* - user: user name
|
||||
* - sum: edit summary (or action reason)
|
||||
* - extra: extra data (varies by line type)
|
||||
* - sizechange: change of filesize
|
||||
*
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
public function getRevisionInfo($rev)
|
||||
{
|
||||
$rev = max($rev, 0);
|
||||
$rev = max(0, $rev);
|
||||
if (!$rev) return false;
|
||||
|
||||
// check if it's already in the memory cache
|
||||
if (isset($this->cache[$this->id]) && isset($this->cache[$this->id][$rev])) {
|
||||
|
@ -100,10 +153,8 @@ abstract class ChangeLog
|
|||
|
||||
// parse and cache changelog lines
|
||||
foreach ($lines as $value) {
|
||||
$tmp = parseChangelogLine($value);
|
||||
if ($tmp !== false) {
|
||||
$this->cache[$this->id][$tmp['date']] = $tmp;
|
||||
}
|
||||
$info = $this->parseLogLine($value);
|
||||
$this->cacheRevisionInfo($info);
|
||||
}
|
||||
if (!isset($this->cache[$this->id][$rev])) {
|
||||
return false;
|
||||
|
@ -140,6 +191,9 @@ abstract class ChangeLog
|
|||
$lines = array();
|
||||
$count = 0;
|
||||
|
||||
$logfile = $this->getChangelogFilename();
|
||||
if (!file_exists($logfile)) return $revs;
|
||||
|
||||
$num = max($num, 0);
|
||||
if ($num == 0) {
|
||||
return $revs;
|
||||
|
@ -148,26 +202,22 @@ abstract class ChangeLog
|
|||
if ($first < 0) {
|
||||
$first = 0;
|
||||
} else {
|
||||
if (file_exists($this->getFilename())) {
|
||||
// skip current revision if the page exists
|
||||
$fileLastMod = $this->getFilename();
|
||||
if (file_exists($fileLastMod) && $this->isLastRevision(filemtime($fileLastMod))) {
|
||||
// skip last revision if the page exists
|
||||
$first = max($first + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->getChangelogFilename();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return $revs;
|
||||
}
|
||||
if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) {
|
||||
if (filesize($logfile) < $this->chunk_size || $this->chunk_size == 0) {
|
||||
// read whole file
|
||||
$lines = file($file);
|
||||
$lines = file($logfile);
|
||||
if ($lines === false) {
|
||||
return $revs;
|
||||
}
|
||||
} else {
|
||||
// read chunks backwards
|
||||
$fp = fopen($file, 'rb'); // "file pointer"
|
||||
$fp = fopen($logfile, 'rb'); // "file pointer"
|
||||
if ($fp === false) {
|
||||
return $revs;
|
||||
}
|
||||
|
@ -221,20 +271,17 @@ abstract class ChangeLog
|
|||
$num = max(min(count($lines) - $first, $num), 0);
|
||||
if ($first > 0 && $num > 0) {
|
||||
$lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num);
|
||||
} else {
|
||||
if ($first > 0 && $num == 0) {
|
||||
$lines = array_slice($lines, 0, max(count($lines) - $first, 0));
|
||||
} elseif ($first == 0 && $num > 0) {
|
||||
$lines = array_slice($lines, max(count($lines) - $num, 0));
|
||||
}
|
||||
} elseif ($first > 0 && $num == 0) {
|
||||
$lines = array_slice($lines, 0, max(count($lines) - $first, 0));
|
||||
} elseif ($first == 0 && $num > 0) {
|
||||
$lines = array_slice($lines, max(count($lines) - $num, 0));
|
||||
}
|
||||
|
||||
// handle lines in reverse order
|
||||
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||
$tmp = parseChangelogLine($lines[$i]);
|
||||
if ($tmp !== false) {
|
||||
$this->cache[$this->id][$tmp['date']] = $tmp;
|
||||
$revs[] = $tmp['date'];
|
||||
$info = $this->parseLogLine($lines[$i]);
|
||||
if ($this->cacheRevisionInfo($info)) {
|
||||
$revs[] = $info['date'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,8 +297,10 @@ abstract class ChangeLog
|
|||
* Adjacent changelog lines are optimistically parsed and cached to speed up
|
||||
* consecutive calls to getRevisionInfo.
|
||||
*
|
||||
* @param int $rev revision timestamp used as startdate (doesn't need to be revisionnumber)
|
||||
* @param int $direction give position of returned revision with respect to $rev; positive=next, negative=prev
|
||||
* @param int $rev revision timestamp used as startdate
|
||||
* (doesn't need to be exact revision number)
|
||||
* @param int $direction give position of returned revision with respect to $rev;
|
||||
positive=next, negative=prev
|
||||
* @return bool|int
|
||||
* timestamp of the requested revision
|
||||
* otherwise false
|
||||
|
@ -270,13 +319,13 @@ abstract class ChangeLog
|
|||
list($fp, $lines, $head, $tail, $eof) = $this->readloglines($rev);
|
||||
if (empty($lines)) return false;
|
||||
|
||||
// look for revisions later/earlier then $rev, when founded count till the wanted revision is reached
|
||||
// look for revisions later/earlier than $rev, when founded count till the wanted revision is reached
|
||||
// also parse and cache changelog lines for getRevisionInfo().
|
||||
$revcounter = 0;
|
||||
$relativerev = false;
|
||||
$checkotherchunck = true; //always runs once
|
||||
while (!$relativerev && $checkotherchunck) {
|
||||
$tmp = array();
|
||||
$info = array();
|
||||
//parse in normal or reverse order
|
||||
$count = count($lines);
|
||||
if ($direction > 0) {
|
||||
|
@ -287,14 +336,13 @@ abstract class ChangeLog
|
|||
$step = -1;
|
||||
}
|
||||
for ($i = $start; $i >= 0 && $i < $count; $i = $i + $step) {
|
||||
$tmp = parseChangelogLine($lines[$i]);
|
||||
if ($tmp !== false) {
|
||||
$this->cache[$this->id][$tmp['date']] = $tmp;
|
||||
$info = $this->parseLogLine($lines[$i]);
|
||||
if ($this->cacheRevisionInfo($info)) {
|
||||
//look for revs older/earlier then reference $rev and select $direction-th one
|
||||
if (($direction > 0 && $tmp['date'] > $rev) || ($direction < 0 && $tmp['date'] < $rev)) {
|
||||
if (($direction > 0 && $info['date'] > $rev) || ($direction < 0 && $info['date'] < $rev)) {
|
||||
$revcounter++;
|
||||
if ($revcounter == abs($direction)) {
|
||||
$relativerev = $tmp['date'];
|
||||
$relativerev = $info['date'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +350,7 @@ abstract class ChangeLog
|
|||
|
||||
//true when $rev is found, but not the wanted follow-up.
|
||||
$checkotherchunck = $fp
|
||||
&& ($tmp['date'] == $rev || ($revcounter > 0 && !$relativerev))
|
||||
&& ($info['date'] == $rev || ($revcounter > 0 && !$relativerev))
|
||||
&& !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0));
|
||||
|
||||
if ($checkotherchunck) {
|
||||
|
@ -329,7 +377,7 @@ abstract class ChangeLog
|
|||
*/
|
||||
public function getRevisionsAround($rev1, $rev2, $max = 50)
|
||||
{
|
||||
$max = floor(abs($max) / 2) * 2 + 1;
|
||||
$max = intval(abs($max) / 2) * 2 + 1;
|
||||
$rev1 = max($rev1, 0);
|
||||
$rev2 = max($rev2, 0);
|
||||
|
||||
|
@ -341,8 +389,7 @@ abstract class ChangeLog
|
|||
}
|
||||
} else {
|
||||
//empty right side means a removed page. Look up last revision.
|
||||
$revs = $this->getRevisions(-1, 1);
|
||||
$rev2 = $revs[0];
|
||||
$rev2 = $this->currentRevision();
|
||||
}
|
||||
//collect revisions around rev2
|
||||
list($revs2, $allrevs, $fp, $lines, $head, $tail) = $this->retrieveRevisionsAround($rev2, $max);
|
||||
|
@ -357,171 +404,31 @@ abstract class ChangeLog
|
|||
if (empty($revs1)) $revs1 = array();
|
||||
} else {
|
||||
//revisions overlaps, reuse revisions around rev2
|
||||
$lastrev = array_pop($allrevs); //keep last entry that could be external edit
|
||||
$revs1 = $allrevs;
|
||||
while ($head > 0) {
|
||||
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||
$tmp = parseChangelogLine($lines[$i]);
|
||||
if ($tmp !== false) {
|
||||
$this->cache[$this->id][$tmp['date']] = $tmp;
|
||||
$revs1[] = $tmp['date'];
|
||||
$info = $this->parseLogLine($lines[$i]);
|
||||
if ($this->cacheRevisionInfo($info)) {
|
||||
$revs1[] = $info['date'];
|
||||
$index++;
|
||||
|
||||
if ($index > floor($max / 2)) break 2;
|
||||
if ($index > intval($max / 2)) break 2;
|
||||
}
|
||||
}
|
||||
|
||||
list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1);
|
||||
}
|
||||
sort($revs1);
|
||||
$revs1[] = $lastrev; //push back last entry
|
||||
|
||||
//return wanted selection
|
||||
$revs1 = array_slice($revs1, max($index - floor($max / 2), 0), $max);
|
||||
$revs1 = array_slice($revs1, max($index - intval($max / 2), 0), $max);
|
||||
}
|
||||
|
||||
return array(array_reverse($revs1), array_reverse($revs2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the ID has old revisons
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasRevisions() {
|
||||
$file = $this->getChangelogFilename();
|
||||
return file_exists($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lines from changelog.
|
||||
* If file larger than $chuncksize, only chunck is read that could contain $rev.
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @return array|false
|
||||
* if success returns array(fp, array(changeloglines), $head, $tail, $eof)
|
||||
* where fp only defined for chuck reading, needs closing.
|
||||
* otherwise false
|
||||
*/
|
||||
protected function readloglines($rev)
|
||||
{
|
||||
$file = $this->getChangelogFilename();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fp = null;
|
||||
$head = 0;
|
||||
$tail = 0;
|
||||
$eof = 0;
|
||||
|
||||
if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) {
|
||||
// read whole file
|
||||
$lines = file($file);
|
||||
if ($lines === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// read by chunk
|
||||
$fp = fopen($file, 'rb'); // "file pointer"
|
||||
if ($fp === false) {
|
||||
return false;
|
||||
}
|
||||
$head = 0;
|
||||
fseek($fp, 0, SEEK_END);
|
||||
$eof = ftell($fp);
|
||||
$tail = $eof;
|
||||
|
||||
// find chunk
|
||||
while ($tail - $head > $this->chunk_size) {
|
||||
$finger = $head + floor(($tail - $head) / 2.0);
|
||||
$finger = $this->getNewlinepointer($fp, $finger);
|
||||
$tmp = fgets($fp);
|
||||
if ($finger == $head || $finger == $tail) {
|
||||
break;
|
||||
}
|
||||
$tmp = parseChangelogLine($tmp);
|
||||
$finger_rev = $tmp['date'];
|
||||
|
||||
if ($finger_rev > $rev) {
|
||||
$tail = $finger;
|
||||
} else {
|
||||
$head = $finger;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tail - $head < 1) {
|
||||
// cound not find chunk, assume requested rev is missing
|
||||
fclose($fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = $this->readChunk($fp, $head, $tail);
|
||||
}
|
||||
return array(
|
||||
$fp,
|
||||
$lines,
|
||||
$head,
|
||||
$tail,
|
||||
$eof,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read chunk and return array with lines of given chunck.
|
||||
* Has no check if $head and $tail are really at a new line
|
||||
*
|
||||
* @param resource $fp resource filepointer
|
||||
* @param int $head start point chunck
|
||||
* @param int $tail end point chunck
|
||||
* @return array lines read from chunck
|
||||
*/
|
||||
protected function readChunk($fp, $head, $tail)
|
||||
{
|
||||
$chunk = '';
|
||||
$chunk_size = max($tail - $head, 0); // found chunk size
|
||||
$got = 0;
|
||||
fseek($fp, $head);
|
||||
while ($got < $chunk_size && !feof($fp)) {
|
||||
$tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0));
|
||||
if ($tmp === false) { //error state
|
||||
break;
|
||||
}
|
||||
$got += strlen($tmp);
|
||||
$chunk .= $tmp;
|
||||
}
|
||||
$lines = explode("\n", $chunk);
|
||||
array_pop($lines); // remove trailing newline
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set pointer to first new line after $finger and return its position
|
||||
*
|
||||
* @param resource $fp filepointer
|
||||
* @param int $finger a pointer
|
||||
* @return int pointer
|
||||
*/
|
||||
protected function getNewlinepointer($fp, $finger)
|
||||
{
|
||||
fseek($fp, $finger);
|
||||
$nl = $finger;
|
||||
if ($finger > 0) {
|
||||
fgets($fp); // slip the finger forward to a new line
|
||||
$nl = ftell($fp);
|
||||
}
|
||||
return $nl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether given revision is the current page
|
||||
*
|
||||
* @param int $rev timestamp of current page
|
||||
* @return bool true if $rev is current revision, otherwise false
|
||||
*/
|
||||
public function isCurrentRevision($rev)
|
||||
{
|
||||
return $rev == @filemtime($this->getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an existing revision for a specific date which is
|
||||
* the current one or younger or equal then the date
|
||||
|
@ -531,8 +438,9 @@ abstract class ChangeLog
|
|||
*/
|
||||
public function getLastRevisionAt($date_at)
|
||||
{
|
||||
$fileLastMod = $this->getFilename();
|
||||
//requested date_at(timestamp) younger or equal then modified_time($this->id) => load current
|
||||
if (file_exists($this->getFilename()) && $date_at >= @filemtime($this->getFilename())) {
|
||||
if (file_exists($fileLastMod) && $date_at >= @filemtime($fileLastMod)) {
|
||||
return '';
|
||||
} else {
|
||||
if ($rev = $this->getRelativeRevision($date_at + 1, -1)) { //+1 to get also the requested date revision
|
||||
|
@ -543,51 +451,13 @@ abstract class ChangeLog
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next lines of the changelog of the chunck before head or after tail
|
||||
*
|
||||
* @param resource $fp filepointer
|
||||
* @param int $head position head of last chunk
|
||||
* @param int $tail position tail of last chunk
|
||||
* @param int $direction positive forward, negative backward
|
||||
* @return array with entries:
|
||||
* - $lines: changelog lines of readed chunk
|
||||
* - $head: head of chunk
|
||||
* - $tail: tail of chunk
|
||||
*/
|
||||
protected function readAdjacentChunk($fp, $head, $tail, $direction)
|
||||
{
|
||||
if (!$fp) return array(array(), $head, $tail);
|
||||
|
||||
if ($direction > 0) {
|
||||
//read forward
|
||||
$head = $tail;
|
||||
$tail = $head + floor($this->chunk_size * (2 / 3));
|
||||
$tail = $this->getNewlinepointer($fp, $tail);
|
||||
} else {
|
||||
//read backward
|
||||
$tail = $head;
|
||||
$head = max($tail - $this->chunk_size, 0);
|
||||
while (true) {
|
||||
$nl = $this->getNewlinepointer($fp, $head);
|
||||
// was the chunk big enough? if not, take another bite
|
||||
if ($nl > 0 && $tail <= $nl) {
|
||||
$head = max($head - $this->chunk_size, 0);
|
||||
} else {
|
||||
$head = $nl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//load next chunck
|
||||
$lines = $this->readChunk($fp, $head, $tail);
|
||||
return array($lines, $head, $tail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the $max revisions near to the timestamp $rev
|
||||
*
|
||||
* Ideally, half of retrieved timestamps are older than $rev, another half are newer.
|
||||
* The returned array $requestedrevs may not contain the reference timestamp $rev
|
||||
* when it does not match any revision value recorded in changelog.
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @param int $max maximum number of revisions to be returned
|
||||
* @return bool|array
|
||||
|
@ -602,38 +472,49 @@ abstract class ChangeLog
|
|||
*/
|
||||
protected function retrieveRevisionsAround($rev, $max)
|
||||
{
|
||||
//get lines from changelog
|
||||
list($fp, $lines, $starthead, $starttail, /* $eof */) = $this->readloglines($rev);
|
||||
if (empty($lines)) return false;
|
||||
|
||||
//parse chunk containing $rev, and read forward more chunks until $max/2 is reached
|
||||
$head = $starthead;
|
||||
$tail = $starttail;
|
||||
$revs = array();
|
||||
$aftercount = $beforecount = 0;
|
||||
|
||||
//get lines from changelog
|
||||
list($fp, $lines, $starthead, $starttail, $eof) = $this->readloglines($rev);
|
||||
if (empty($lines)) return false;
|
||||
|
||||
//parse changelog lines in chunk, and read forward more chunks until $max/2 is reached
|
||||
$head = $starthead;
|
||||
$tail = $starttail;
|
||||
while (count($lines) > 0) {
|
||||
foreach ($lines as $line) {
|
||||
$tmp = parseChangelogLine($line);
|
||||
if ($tmp !== false) {
|
||||
$this->cache[$this->id][$tmp['date']] = $tmp;
|
||||
$revs[] = $tmp['date'];
|
||||
if ($tmp['date'] >= $rev) {
|
||||
$info = $this->parseLogLine($line);
|
||||
if ($this->cacheRevisionInfo($info)) {
|
||||
$revs[] = $info['date'];
|
||||
if ($info['date'] >= $rev) {
|
||||
//count revs after reference $rev
|
||||
$aftercount++;
|
||||
if ($aftercount == 1) $beforecount = count($revs);
|
||||
}
|
||||
//enough revs after reference $rev?
|
||||
if ($aftercount > floor($max / 2)) break 2;
|
||||
if ($aftercount > intval($max / 2)) break 2;
|
||||
}
|
||||
}
|
||||
//retrieve next chunk
|
||||
list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1);
|
||||
}
|
||||
if ($aftercount == 0) return false;
|
||||
|
||||
$lasttail = $tail;
|
||||
|
||||
//read additional chuncks backward until $max/2 is reached and total number of revs is equal to $max
|
||||
// add a possible revision of external edit, create or deletion
|
||||
if ($lasttail == $eof && $aftercount <= intval($max / 2) &&
|
||||
count($revs) && !$this->isCurrentRevision($revs[count($revs)-1])
|
||||
) {
|
||||
$revs[] = $this->currentRevision;
|
||||
$aftercount++;
|
||||
}
|
||||
|
||||
if ($aftercount == 0) {
|
||||
//given timestamp $rev is newer than the most recent line in chunk
|
||||
return false; //FIXME: or proceed to collect older revisions?
|
||||
}
|
||||
|
||||
//read more chunks backward until $max/2 is reached and total number of revs is equal to $max
|
||||
$lines = array();
|
||||
$i = 0;
|
||||
if ($aftercount > 0) {
|
||||
|
@ -643,24 +524,138 @@ abstract class ChangeLog
|
|||
list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1);
|
||||
|
||||
for ($i = count($lines) - 1; $i >= 0; $i--) {
|
||||
$tmp = parseChangelogLine($lines[$i]);
|
||||
if ($tmp !== false) {
|
||||
$this->cache[$this->id][$tmp['date']] = $tmp;
|
||||
$revs[] = $tmp['date'];
|
||||
$info = $this->parseLogLine($lines[$i]);
|
||||
if ($this->cacheRevisionInfo($info)) {
|
||||
$revs[] = $info['date'];
|
||||
$beforecount++;
|
||||
//enough revs before reference $rev?
|
||||
if ($beforecount > max(floor($max / 2), $max - $aftercount)) break 2;
|
||||
if ($beforecount > max(intval($max / 2), $max - $aftercount)) break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort($revs);
|
||||
|
||||
//keep only non-parsed lines
|
||||
$lines = array_slice($lines, 0, $i);
|
||||
|
||||
sort($revs);
|
||||
|
||||
//trunk desired selection
|
||||
$requestedrevs = array_slice($revs, -$max, $max);
|
||||
|
||||
return array($requestedrevs, $revs, $fp, $lines, $head, $lasttail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current revision information, considering external edit, create or deletion
|
||||
*
|
||||
* When the file has not modified since its last revision, the infomation of the last
|
||||
* change that had already recorded in the changelog is returned as current change info.
|
||||
* Otherwise, the change infomation since the last revision caused outside DokuWiki
|
||||
* should be returned, which is referred as "external revision".
|
||||
*
|
||||
* The change date of the file can be determined by timestamp as far as the file exists,
|
||||
* however this is not possible when the file has already deleted outside of DokuWiki.
|
||||
* In such case we assign 1 sec before current time() for the external deletion.
|
||||
* As a result, the value of current revision identifier may change each time because:
|
||||
* 1) the file has again modified outside of DokuWiki, or
|
||||
* 2) the value is essentially volatile for deleted but once existed files.
|
||||
*
|
||||
* @return bool|array false when page had never existed or array with entries:
|
||||
* - date: revision identifier (timestamp or last revision +1)
|
||||
* - ip: IPv4 address (127.0.0.1)
|
||||
* - type: log line type
|
||||
* - id: id of page or media
|
||||
* - user: user name
|
||||
* - sum: edit summary (or action reason)
|
||||
* - extra: extra data (varies by line type)
|
||||
* - sizechange: change of filesize
|
||||
* - timestamp: unix timestamp or false (key set only for external edit occurred)
|
||||
*
|
||||
* @author Satoshi Sahara <sahara.satoshi@gmail.com>
|
||||
*/
|
||||
public function getCurrentRevisionInfo()
|
||||
{
|
||||
global $lang;
|
||||
|
||||
if (isset($this->currentRevision)) return $this->getRevisionInfo($this->currentRevision);
|
||||
|
||||
// get revision id from the item file timestamp and chagelog
|
||||
$fileLastMod = $this->getFilename();
|
||||
$fileRev = @filemtime($fileLastMod); // false when the file not exist
|
||||
$lastRev = $this->lastRevision(); // false when no changelog
|
||||
|
||||
if (!$fileRev && !$lastRev) { // has never existed
|
||||
$this->currentRevision = false;
|
||||
return false;
|
||||
} elseif ($fileRev === $lastRev) { // not external edit
|
||||
$this->currentRevision = $lastRev;
|
||||
return $this->getRevisionInfo($lastRev);
|
||||
}
|
||||
|
||||
if (!$fileRev && $lastRev) { // item file does not exist
|
||||
// check consistency against changelog
|
||||
$revInfo = $this->getRevisionInfo($lastRev);
|
||||
if ($revInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
$this->currentRevision = $lastRev;
|
||||
return $this->getRevisionInfo($lastRev);
|
||||
}
|
||||
|
||||
// externally deleted, set revision date as late as possible
|
||||
$revInfo = [
|
||||
'date' => max($lastRev +1, time() -1), // 1 sec before now or new page save
|
||||
'ip' => '127.0.0.1',
|
||||
'type' => DOKU_CHANGE_TYPE_DELETE,
|
||||
'id' => $this->id,
|
||||
'user' => '',
|
||||
'sum' => $lang['deleted'].' - '.$lang['external_edit'].' ('.$lang['unknowndate'].')',
|
||||
'extra' => '',
|
||||
'sizechange' => -io_getSizeFile($this->getFilename($lastRev)),
|
||||
'timestamp' => false,
|
||||
];
|
||||
|
||||
} elseif ($fileRev) { // item file exist
|
||||
// here, file timestamp is different with last revision in changelog
|
||||
$isJustCreated = $lastRev === false || (
|
||||
$fileRev > $lastRev &&
|
||||
$this->getRevisionInfo($lastRev)['type'] == DOKU_CHANGE_TYPE_DELETE
|
||||
);
|
||||
$filesize_new = filesize($this->getFilename());
|
||||
$filesize_old = $isJustCreated ? 0 : io_getSizeFile($this->getFilename($lastRev));
|
||||
$sizechange = $filesize_new - $filesize_old;
|
||||
|
||||
if ($isJustCreated) {
|
||||
$timestamp = $fileRev;
|
||||
$sum = $lang['created'].' - '.$lang['external_edit'];
|
||||
} elseif ($fileRev > $lastRev) {
|
||||
$timestamp = $fileRev;
|
||||
$sum = $lang['external_edit'];
|
||||
} else {
|
||||
// $fileRev is older than $lastRev, that is erroneous/incorrect occurence.
|
||||
$msg = "Warning: current file modification time is older than last revision date";
|
||||
$details = 'File revision: '.$fileRev.' '.strftime("%Y-%m-%d %H:%M:%S", $fileRev)."\n"
|
||||
.'Last revision: '.$lastRev.' '.strftime("%Y-%m-%d %H:%M:%S", $lastRev);
|
||||
Logger::error($msg, $details, $this->getFilename());
|
||||
$timestamp = false;
|
||||
$sum = $lang['external_edit'].' ('.$lang['unknowndate'].')';
|
||||
}
|
||||
|
||||
// externally created or edited
|
||||
$revInfo = [
|
||||
'date' => $timestamp ?: $lastRev +1,
|
||||
'ip' => '127.0.0.1',
|
||||
'type' => $isJustCreated ? DOKU_CHANGE_TYPE_CREATE : DOKU_CHANGE_TYPE_EDIT,
|
||||
'id' => $this->id,
|
||||
'user' => '',
|
||||
'sum' => $sum,
|
||||
'extra' => '',
|
||||
'sizechange' => $sizechange,
|
||||
'timestamp' => $timestamp,
|
||||
];
|
||||
}
|
||||
|
||||
// cache current revision information of external edition
|
||||
$this->currentRevision = $revInfo['date'];
|
||||
$this->cache[$this->id][$this->currentRevision] = $revInfo;
|
||||
return $this->getRevisionInfo($this->currentRevision);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
/**
|
||||
* Provides methods for handling of changelog
|
||||
*/
|
||||
trait ChangeLogTrait
|
||||
{
|
||||
/**
|
||||
* Adds an entry to the changelog file
|
||||
*
|
||||
* @return array added logline as revision info
|
||||
*/
|
||||
abstract public function addLogEntry(array $info, $timestamp = null);
|
||||
|
||||
/**
|
||||
* Parses a changelog line into it's components
|
||||
*
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
*
|
||||
* @param string $line changelog line
|
||||
* @return array|bool parsed line or false
|
||||
*/
|
||||
public static function parseLogLine($line)
|
||||
{
|
||||
$info = explode("\t", rtrim($line, "\n"));
|
||||
if ($info !== false && count($info) > 1) {
|
||||
return $entry = array(
|
||||
'date' => (int)$info[0], // unix timestamp
|
||||
'ip' => $info[1], // IPv4 address (127.0.0.1)
|
||||
'type' => $info[2], // log line type
|
||||
'id' => $info[3], // page id
|
||||
'user' => $info[4], // user name
|
||||
'sum' => $info[5], // edit summary (or action reason)
|
||||
'extra' => $info[6], // extra data (varies by line type)
|
||||
'sizechange' => (isset($info[7]) && $info[7] !== '') ? (int)$info[7] : null, //
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a changelog line from it's components
|
||||
*
|
||||
* @param array $info Revision info structure
|
||||
* @param int $timestamp logline date (optional)
|
||||
* @return string changelog line
|
||||
*/
|
||||
public static function buildLogLine(array &$info, $timestamp = null)
|
||||
{
|
||||
$strip = ["\t", "\n"];
|
||||
$entry = array(
|
||||
'date' => $timestamp ?? $info['date'],
|
||||
'ip' => $info['ip'],
|
||||
'type' => str_replace($strip, '', $info['type']),
|
||||
'id' => $info['id'],
|
||||
'user' => $info['user'],
|
||||
'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $info['sum']), 0, 255),
|
||||
'extra' => str_replace($strip, '', $info['extra']),
|
||||
'sizechange' => $info['sizechange'],
|
||||
);
|
||||
$info = $entry;
|
||||
return $line = implode("\t", $entry) ."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to changelog
|
||||
*
|
||||
* @return string path to file
|
||||
*/
|
||||
abstract protected function getChangelogFilename();
|
||||
|
||||
/**
|
||||
* Checks if the ID has old revisons
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasRevisions()
|
||||
{
|
||||
$logfile = $this->getChangelogFilename();
|
||||
return file_exists($logfile);
|
||||
}
|
||||
|
||||
|
||||
/** @var int */
|
||||
protected $chunk_size;
|
||||
|
||||
/**
|
||||
* Set chunk size for file reading
|
||||
* Chunk size zero let read whole file at once
|
||||
*
|
||||
* @param int $chunk_size maximum block size read from file
|
||||
*/
|
||||
public function setChunkSize($chunk_size)
|
||||
{
|
||||
if (!is_numeric($chunk_size)) $chunk_size = 0;
|
||||
|
||||
$this->chunk_size = (int)max($chunk_size, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lines from changelog.
|
||||
* If file larger than $chuncksize, only chunck is read that could contain $rev.
|
||||
*
|
||||
* When reference timestamp $rev is outside time range of changelog, readloglines() will return
|
||||
* lines in first or last chunk, but they obviously does not contain $rev.
|
||||
*
|
||||
* @param int $rev revision timestamp
|
||||
* @return array|false
|
||||
* if success returns array(fp, array(changeloglines), $head, $tail, $eof)
|
||||
* where fp only defined for chuck reading, needs closing.
|
||||
* otherwise false
|
||||
*/
|
||||
protected function readloglines($rev)
|
||||
{
|
||||
$file = $this->getChangelogFilename();
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fp = null;
|
||||
$head = 0;
|
||||
$tail = 0;
|
||||
$eof = 0;
|
||||
|
||||
if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) {
|
||||
// read whole file
|
||||
$lines = file($file);
|
||||
if ($lines === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// read by chunk
|
||||
$fp = fopen($file, 'rb'); // "file pointer"
|
||||
if ($fp === false) {
|
||||
return false;
|
||||
}
|
||||
$head = 0;
|
||||
fseek($fp, 0, SEEK_END);
|
||||
$eof = ftell($fp);
|
||||
$tail = $eof;
|
||||
|
||||
// find chunk
|
||||
while ($tail - $head > $this->chunk_size) {
|
||||
$finger = $head + intval(($tail - $head) / 2);
|
||||
$finger = $this->getNewlinepointer($fp, $finger);
|
||||
$tmp = fgets($fp);
|
||||
if ($finger == $head || $finger == $tail) {
|
||||
break;
|
||||
}
|
||||
$info = $this->parseLogLine($tmp);
|
||||
$finger_rev = $info['date'];
|
||||
|
||||
if ($finger_rev > $rev) {
|
||||
$tail = $finger;
|
||||
} else {
|
||||
$head = $finger;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tail - $head < 1) {
|
||||
// cound not find chunk, assume requested rev is missing
|
||||
fclose($fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = $this->readChunk($fp, $head, $tail);
|
||||
}
|
||||
return array(
|
||||
$fp,
|
||||
$lines,
|
||||
$head,
|
||||
$tail,
|
||||
$eof,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read chunk and return array with lines of given chunck.
|
||||
* Has no check if $head and $tail are really at a new line
|
||||
*
|
||||
* @param resource $fp resource filepointer
|
||||
* @param int $head start point chunck
|
||||
* @param int $tail end point chunck
|
||||
* @return array lines read from chunck
|
||||
*/
|
||||
protected function readChunk($fp, $head, $tail)
|
||||
{
|
||||
$chunk = '';
|
||||
$chunk_size = max($tail - $head, 0); // found chunk size
|
||||
$got = 0;
|
||||
fseek($fp, $head);
|
||||
while ($got < $chunk_size && !feof($fp)) {
|
||||
$tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0));
|
||||
if ($tmp === false) { //error state
|
||||
break;
|
||||
}
|
||||
$got += strlen($tmp);
|
||||
$chunk .= $tmp;
|
||||
}
|
||||
$lines = explode("\n", $chunk);
|
||||
array_pop($lines); // remove trailing newline
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set pointer to first new line after $finger and return its position
|
||||
*
|
||||
* @param resource $fp filepointer
|
||||
* @param int $finger a pointer
|
||||
* @return int pointer
|
||||
*/
|
||||
protected function getNewlinepointer($fp, $finger)
|
||||
{
|
||||
fseek($fp, $finger);
|
||||
$nl = $finger;
|
||||
if ($finger > 0) {
|
||||
fgets($fp); // slip the finger forward to a new line
|
||||
$nl = ftell($fp);
|
||||
}
|
||||
return $nl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next lines of the changelog of the chunck before head or after tail
|
||||
*
|
||||
* @param resource $fp filepointer
|
||||
* @param int $head position head of last chunk
|
||||
* @param int $tail position tail of last chunk
|
||||
* @param int $direction positive forward, negative backward
|
||||
* @return array with entries:
|
||||
* - $lines: changelog lines of readed chunk
|
||||
* - $head: head of chunk
|
||||
* - $tail: tail of chunk
|
||||
*/
|
||||
protected function readAdjacentChunk($fp, $head, $tail, $direction)
|
||||
{
|
||||
if (!$fp) return array(array(), $head, $tail);
|
||||
|
||||
if ($direction > 0) {
|
||||
//read forward
|
||||
$head = $tail;
|
||||
$tail = $head + intval($this->chunk_size * (2 / 3));
|
||||
$tail = $this->getNewlinepointer($fp, $tail);
|
||||
} else {
|
||||
//read backward
|
||||
$tail = $head;
|
||||
$head = max($tail - $this->chunk_size, 0);
|
||||
while (true) {
|
||||
$nl = $this->getNewlinepointer($fp, $head);
|
||||
// was the chunk big enough? if not, take another bite
|
||||
if ($nl > 0 && $tail <= $nl) {
|
||||
$head = max($head - $this->chunk_size, 0);
|
||||
} else {
|
||||
$head = $nl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//load next chunck
|
||||
$lines = $this->readChunk($fp, $head, $tail);
|
||||
return array($lines, $head, $tail);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
/**
|
||||
* handles changelog of a media file
|
||||
* Class MediaChangeLog; handles changelog of a media file
|
||||
*/
|
||||
class MediaChangeLog extends ChangeLog
|
||||
{
|
||||
|
@ -21,10 +21,40 @@ class MediaChangeLog extends ChangeLog
|
|||
/**
|
||||
* Returns path to current page/media
|
||||
*
|
||||
* @param string|int $rev empty string or revision timestamp
|
||||
* @return string path to file
|
||||
*/
|
||||
protected function getFilename()
|
||||
protected function getFilename($rev = '')
|
||||
{
|
||||
return mediaFN($this->id);
|
||||
return mediaFN($this->id, $rev);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adds an entry to the changelog
|
||||
*
|
||||
* @param array $info Revision info structure of a media file
|
||||
* @param int $timestamp logline date (optional)
|
||||
* @return array revision info of added logline
|
||||
*
|
||||
* @see also addMediaLogEntry() in inc/changelog.php file
|
||||
*/
|
||||
public function addLogEntry(array $info, $timestamp = null)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (isset($timestamp)) unset($this->cache[$this->id][$info['date']]);
|
||||
|
||||
// add changelog lines
|
||||
$logline = $this->buildLogLine($info, $timestamp);
|
||||
io_saveFile(mediaMetaFN($this->id,'.changes'), $logline, $append = true);
|
||||
io_saveFile($conf['media_changelog'], $logline, $append = true); //global changelog cache
|
||||
|
||||
// update cache
|
||||
$this->currentRevision = $info['date'];
|
||||
$this->cache[$this->id][$this->currentRevision] = $info;
|
||||
return $info;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace dokuwiki\ChangeLog;
|
||||
|
||||
/**
|
||||
* handles changelog of a wiki page
|
||||
* Class PageChangeLog; handles changelog of a wiki page
|
||||
*/
|
||||
class PageChangeLog extends ChangeLog
|
||||
{
|
||||
|
@ -21,10 +21,40 @@ class PageChangeLog extends ChangeLog
|
|||
/**
|
||||
* Returns path to current page/media
|
||||
*
|
||||
* @param string|int $rev empty string or revision timestamp
|
||||
* @return string path to file
|
||||
*/
|
||||
protected function getFilename()
|
||||
protected function getFilename($rev = '')
|
||||
{
|
||||
return wikiFN($this->id);
|
||||
return wikiFN($this->id, $rev);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adds an entry to the changelog
|
||||
*
|
||||
* @param array $info Revision info structure of a page
|
||||
* @param int $timestamp logline date (optional)
|
||||
* @return array revision info of added logline
|
||||
*
|
||||
* @see also addLogEntry() in inc/changelog.php file
|
||||
*/
|
||||
public function addLogEntry(array $info, $timestamp = null)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (isset($timestamp)) unset($this->cache[$this->id][$info['date']]);
|
||||
|
||||
// add changelog lines
|
||||
$logline = $this->buildLogLine($info, $timestamp);
|
||||
io_saveFile(metaFN($this->id,'.changes'), $logline, $append = true);
|
||||
io_saveFile($conf['changelog'], $logline, $append = true); //global changelog cache
|
||||
|
||||
// update cache
|
||||
$this->currentRevision = $info['date'];
|
||||
$this->cache[$this->id][$this->currentRevision] = $info;
|
||||
return $info;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\File;
|
||||
|
||||
use dokuwiki\Cache\CacheInstructions;
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Logger;
|
||||
|
||||
/**
|
||||
* Class PageFile : handles wiki text file and its change management for specific page
|
||||
*/
|
||||
class PageFile
|
||||
{
|
||||
protected $id;
|
||||
|
||||
/* @var PageChangeLog $changelog */
|
||||
public $changelog;
|
||||
|
||||
/* @var array $data initial data when event COMMON_WIKIPAGE_SAVE triggered */
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* PageFile constructor.
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->changelog = new PageChangeLog($this->id);
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getPath($rev = '')
|
||||
{
|
||||
return wikiFN($this->id, $rev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw WikiText of the page, considering change type at revision date
|
||||
* similar to function rawWiki($id, $rev = '')
|
||||
*
|
||||
* @param int|false $rev timestamp when a revision of wikitext is desired
|
||||
* @return string
|
||||
*/
|
||||
public function rawWikiText($rev = null)
|
||||
{
|
||||
if ($rev !== null) {
|
||||
$revInfo = $rev ? $this->changelog->getRevisionInfo($rev) : false;
|
||||
return (!$revInfo || $revInfo['type'] == DOKU_CHANGE_TYPE_DELETE)
|
||||
? '' // attic stores complete last page version for a deleted page
|
||||
: io_readWikiPage($this->getPath($rev), $this->id, $rev); // retrieve from attic
|
||||
} else {
|
||||
return io_readWikiPage($this->getPath(), $this->id, '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a wikitext by calling io_writeWikiPage.
|
||||
* Also directs changelog and attic updates.
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
*
|
||||
* @param string $text wikitext being saved
|
||||
* @param string $summary summary of text update
|
||||
* @param bool $minor mark this saved version as minor update
|
||||
* @return array data of event COMMON_WIKIPAGE_SAVE
|
||||
*/
|
||||
public function saveWikiText($text, $summary, $minor = false)
|
||||
{
|
||||
/* Note to developers:
|
||||
This code is subtle and delicate. Test the behavior of
|
||||
the attic and changelog with dokuwiki and external edits
|
||||
after any changes. External edits change the wiki page
|
||||
directly without using php or dokuwiki.
|
||||
*/
|
||||
global $conf;
|
||||
global $lang;
|
||||
global $REV;
|
||||
/* @var Input $INPUT */
|
||||
global $INPUT;
|
||||
|
||||
// prevent recursive call
|
||||
if (isset($this->data)) return;
|
||||
|
||||
$pagefile = $this->getPath();
|
||||
$currentRevision = @filemtime($pagefile); // int or false
|
||||
$currentContent = $this->rawWikiText();
|
||||
$currentSize = file_exists($pagefile) ? filesize($pagefile) : 0;
|
||||
|
||||
// prepare data for event COMMON_WIKIPAGE_SAVE
|
||||
$data = array(
|
||||
'id' => $this->id, // should not be altered by any handlers
|
||||
'file' => $pagefile, // same above
|
||||
'changeType' => null, // set prior to event, and confirm later
|
||||
'revertFrom' => $REV,
|
||||
'oldRevision' => $currentRevision,
|
||||
'oldContent' => $currentContent,
|
||||
'newRevision' => 0, // only available in the after hook
|
||||
'newContent' => $text,
|
||||
'summary' => $summary,
|
||||
'contentChanged' => (bool)($text != $currentContent), // confirm later
|
||||
'changeInfo' => '', // automatically determined by revertFrom
|
||||
'sizechange' => strlen($text) - strlen($currentContent), // TBD
|
||||
);
|
||||
|
||||
// determine tentatively change type and relevant elements of event data
|
||||
if ($data['revertFrom']) {
|
||||
// new text may differ from exact revert revision
|
||||
$data['changeType'] = DOKU_CHANGE_TYPE_REVERT;
|
||||
$data['changeInfo'] = $REV;
|
||||
} elseif (trim($data['newContent']) == '') {
|
||||
// empty or whitespace only content deletes
|
||||
$data['changeType'] = DOKU_CHANGE_TYPE_DELETE;
|
||||
} elseif (!file_exists($pagefile)) {
|
||||
$data['changeType'] = DOKU_CHANGE_TYPE_CREATE;
|
||||
} else {
|
||||
// minor edits allowable only for logged in users
|
||||
$is_minor_change = ($minor && $conf['useacl'] && $INPUT->server->str('REMOTE_USER'));
|
||||
$data['changeType'] = $is_minor_change
|
||||
? DOKU_CHANGE_TYPE_MINOR_EDIT
|
||||
: DOKU_CHANGE_TYPE_EDIT;
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
$data['page'] = $this; // allow event handlers to use this class methods
|
||||
|
||||
$event = new Event('COMMON_WIKIPAGE_SAVE', $data);
|
||||
if (!$event->advise_before()) return;
|
||||
|
||||
// if the content has not been changed, no save happens (plugins may override this)
|
||||
if (!$data['contentChanged']) return;
|
||||
|
||||
// Check whether the pagefile has modified during $event->advise_before()
|
||||
clearstatcache();
|
||||
$fileRev = @filemtime($pagefile);
|
||||
if ($fileRev === $currentRevision) {
|
||||
// pagefile has not touched by plugin's event handler
|
||||
// add a potential external edit entry to changelog and store it into attic
|
||||
$this->detectExternalEdit();
|
||||
$filesize_old = $currentSize;
|
||||
} else {
|
||||
// pagefile has modified by plugin's event handler, confirm sizechange
|
||||
$filesize_old = (
|
||||
$data['changeType'] == DOKU_CHANGE_TYPE_CREATE || (
|
||||
$data['changeType'] == DOKU_CHANGE_TYPE_REVERT && !file_exists($pagefile))
|
||||
) ? 0 : filesize($pagefile);
|
||||
}
|
||||
|
||||
// make change to the current file
|
||||
if ($data['changeType'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
// nothing to do when the file has already deleted
|
||||
if (!file_exists($pagefile)) return;
|
||||
// autoset summary on deletion
|
||||
if (blank($data['summary'])) {
|
||||
$data['summary'] = $lang['deleted'];
|
||||
}
|
||||
// send "update" event with empty data, so plugins can react to page deletion
|
||||
$ioData = array([$pagefile, '', false], getNS($this->id), noNS($this->id), false);
|
||||
Event::createAndTrigger('IO_WIKIPAGE_WRITE', $ioData);
|
||||
// pre-save deleted revision
|
||||
@touch($pagefile);
|
||||
clearstatcache();
|
||||
$data['newRevision'] = $this->saveOldRevision();
|
||||
// remove empty file
|
||||
@unlink($pagefile);
|
||||
$filesize_new = 0;
|
||||
// don't remove old meta info as it should be saved, plugins can use
|
||||
// IO_WIKIPAGE_WRITE for removing their metadata...
|
||||
// purge non-persistant meta data
|
||||
p_purge_metadata($this->id);
|
||||
// remove empty namespaces
|
||||
io_sweepNS($this->id, 'datadir');
|
||||
io_sweepNS($this->id, 'mediadir');
|
||||
} else {
|
||||
// save file (namespace dir is created in io_writeWikiPage)
|
||||
io_writeWikiPage($pagefile, $data['newContent'], $this->id);
|
||||
// pre-save the revision, to keep the attic in sync
|
||||
$data['newRevision'] = $this->saveOldRevision();
|
||||
$filesize_new = filesize($pagefile);
|
||||
}
|
||||
$data['sizechange'] = $filesize_new - $filesize_old;
|
||||
|
||||
$event->advise_after();
|
||||
|
||||
unset($data['page']);
|
||||
|
||||
// adds an entry to the changelog and saves the metadata for the page
|
||||
$logEntry = $this->changelog->addLogEntry([
|
||||
'date' => $data['newRevision'],
|
||||
'ip' => clientIP(true),
|
||||
'type' => $data['changeType'],
|
||||
'id' => $this->id,
|
||||
'user' => $INPUT->server->str('REMOTE_USER'),
|
||||
'sum' => $data['summary'],
|
||||
'extra' => $data['changeInfo'],
|
||||
'sizechange' => $data['sizechange'],
|
||||
]);
|
||||
// update metadata
|
||||
$this->updateMetadata($logEntry);
|
||||
|
||||
// update the purgefile (timestamp of the last time anything within the wiki was changed)
|
||||
io_saveFile($conf['cachedir'].'/purgefile', time());
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current page version is newer than the last entry in the page's changelog.
|
||||
* If so, we assume it has been an external edit and we create an attic copy and add a proper
|
||||
* changelog line.
|
||||
*
|
||||
* This check is only executed when the page is about to be saved again from the wiki,
|
||||
* triggered in @see saveWikiText()
|
||||
*/
|
||||
public function detectExternalEdit()
|
||||
{
|
||||
$revInfo = $this->changelog->getCurrentRevisionInfo();
|
||||
|
||||
// only interested in external revision
|
||||
if (empty($revInfo) || !array_key_exists('timestamp', $revInfo)) return;
|
||||
|
||||
if ($revInfo['type'] != DOKU_CHANGE_TYPE_DELETE && !$revInfo['timestamp']) {
|
||||
// file is older than last revision, that is erroneous/incorrect occurence.
|
||||
// try to change file modification time
|
||||
$fileLastMod = $this->getPath();
|
||||
$wrong_timestamp = filemtime($fileLastMod);
|
||||
if (touch($fileLastMod, $revInfo['date'])) {
|
||||
clearstatcache();
|
||||
$msg = "detectExternalEdit($id): timestamp successfully modified";
|
||||
$details = '('.$wrong_timestamp.' -> '.$revInfo['date'].')';
|
||||
Logger::error($msg, $details, $fileLastMod);
|
||||
} else {
|
||||
// runtime error
|
||||
$msg = "detectExternalEdit($id): page file should be newer than last revision "
|
||||
.'('.filemtime($fileLastMod).' < '. $this->changelog->lastRevision() .')';
|
||||
throw new \RuntimeException($msg);
|
||||
}
|
||||
}
|
||||
|
||||
// keep at least 1 sec before new page save
|
||||
if ($revInfo['date'] == time()) sleep(1); // wait a tick
|
||||
|
||||
// store externally edited file to the attic folder
|
||||
$this->saveOldRevision();
|
||||
// add a changelog entry for externally edited file
|
||||
$revInfo = $this->changelog->addLogEntry($revInfo);
|
||||
// remove soon to be stale instructions
|
||||
$cache = new CacheInstructions($this->id, $this->getPath());
|
||||
$cache->removeCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current version to the attic and returns its revision date
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @return int|string revision timestamp
|
||||
*/
|
||||
public function saveOldRevision()
|
||||
{
|
||||
$oldfile = $this->getPath();
|
||||
if (!file_exists($oldfile)) return '';
|
||||
$date = filemtime($oldfile);
|
||||
$newfile = $this->getPath($date);
|
||||
io_writeWikiPage($newfile, $this->rawWikiText(), $this->id, $date);
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update metadata of changed page
|
||||
*
|
||||
* @param array $logEntry changelog entry
|
||||
*/
|
||||
public function updateMetadata(array $logEntry)
|
||||
{
|
||||
global $INFO;
|
||||
|
||||
list(
|
||||
'date' => $date,
|
||||
'type' => $changeType,
|
||||
'user' => $user,
|
||||
) = $logEntry;
|
||||
|
||||
$wasRemoved = ($changeType === DOKU_CHANGE_TYPE_DELETE);
|
||||
$wasCreated = ($changeType === DOKU_CHANGE_TYPE_CREATE);
|
||||
$wasReverted = ($changeType === DOKU_CHANGE_TYPE_REVERT);
|
||||
$wasMinorEdit = ($changeType === DOKU_CHANGE_TYPE_MINOR_EDIT);
|
||||
|
||||
$createdDate = @filectime($this->getPath());
|
||||
|
||||
if ($wasRemoved) return;
|
||||
|
||||
$oldmeta = p_read_metadata($this->id)['persistent'];
|
||||
$meta = array();
|
||||
|
||||
if ($wasCreated &&
|
||||
(empty($oldmeta['date']['created']) || $oldmeta['date']['created'] === $createdDate)
|
||||
) {
|
||||
// newly created
|
||||
$meta['date']['created'] = $createdDate;
|
||||
if ($user) {
|
||||
$meta['creator'] = $INFO['userinfo']['name'] ?? null;
|
||||
$meta['user'] = $user;
|
||||
}
|
||||
} elseif (($wasCreated || $wasReverted) && !empty($oldmeta['date']['created'])) {
|
||||
// re-created / restored
|
||||
$meta['date']['created'] = $oldmeta['date']['created'];
|
||||
$meta['date']['modified'] = $createdDate; // use the files ctime here
|
||||
$meta['creator'] = $oldmeta['creator'] ?? null;
|
||||
if ($user) {
|
||||
$meta['contributor'][$user] = $INFO['userinfo']['name'] ?? null;
|
||||
}
|
||||
} elseif (!$wasMinorEdit) { // non-minor modification
|
||||
$meta['date']['modified'] = $date;
|
||||
if ($user) {
|
||||
$meta['contributor'][$user] = $INFO['userinfo']['name'] ?? null;
|
||||
}
|
||||
}
|
||||
$meta['last_change'] = $logEntry;
|
||||
p_set_metadata($this->id, $meta);
|
||||
}
|
||||
|
||||
}
|
|
@ -87,7 +87,8 @@ class BulkSubscriptionSender extends SubscriptionSender
|
|||
$n = 0;
|
||||
while (!is_null($rev) && $rev['date'] >= $lastupdate &&
|
||||
($INPUT->server->str('REMOTE_USER') === $rev['user'] ||
|
||||
$rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)) {
|
||||
$rev['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT)
|
||||
) {
|
||||
$pagelog = new PageChangeLog($rev['id']);
|
||||
$rev = $pagelog->getRevisions($n++, 1);
|
||||
$rev = (count($rev) > 0) ? $rev[0] : null;
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace dokuwiki;
|
|||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Sitemap\Mapper;
|
||||
use dokuwiki\Subscriptions\BulkSubscriptionSender;
|
||||
use dokuwiki\ChangeLog\ChangeLog;
|
||||
|
||||
/**
|
||||
* Class TaskRunner
|
||||
|
@ -120,7 +121,7 @@ class TaskRunner
|
|||
$out_lines = [];
|
||||
$old_lines = [];
|
||||
for ($i = 0; $i < count($lines); $i++) {
|
||||
$log = parseChangelogLine($lines[$i]);
|
||||
$log = ChangeLog::parseLogLine($lines[$i]);
|
||||
if ($log === false) {
|
||||
continue; // discard junk
|
||||
}
|
||||
|
|
547
inc/Ui/Diff.php
547
inc/Ui/Diff.php
|
@ -2,502 +2,137 @@
|
|||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Form\Form;
|
||||
use dokuwiki\ChangeLog\ChangeLog;
|
||||
|
||||
/**
|
||||
* DokuWiki Diff Interface
|
||||
* parent class of PageDiff and MediaDiff
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
class Diff extends Ui
|
||||
abstract class Diff extends Ui
|
||||
{
|
||||
protected $text;
|
||||
protected $showIntro;
|
||||
protected $difftype;
|
||||
/* @var string */
|
||||
protected $id; // page id or media id
|
||||
|
||||
/* @var int */
|
||||
protected $oldRev; // timestamp of older revision
|
||||
protected $newRev; // timestamp of newer revision
|
||||
|
||||
/* @var array */
|
||||
protected $preference = [];
|
||||
|
||||
/* @var ChangeLog */
|
||||
protected $changelog; // PageChangeLog or MediaChangeLog object
|
||||
|
||||
/**
|
||||
* Diff Ui constructor
|
||||
*
|
||||
* @param string $text when non-empty: compare with this text with most current version
|
||||
* @param bool $showIntro display the intro text
|
||||
* @param string $difftype diff view type (inline or sidebyside)
|
||||
* @param string $id page id or media id
|
||||
*/
|
||||
public function __construct($text = '', $showIntro = true, $difftype = null)
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->showIntro = $showIntro;
|
||||
|
||||
// determine diff view type
|
||||
if (isset($difftype)) {
|
||||
$this->difftype = $difftype;
|
||||
} else {
|
||||
global $INPUT;
|
||||
global $INFO;
|
||||
$this->difftype = $INPUT->str('difftype') ?: get_doku_pref('difftype', $difftype);
|
||||
if (empty($this->difftype) && $INFO['ismobile']) {
|
||||
$this->difftype = 'inline';
|
||||
}
|
||||
}
|
||||
if ($this->difftype !== 'inline') $this->difftype = 'sidebyside';
|
||||
$this->id = $id;
|
||||
$this->setChangeLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show diff
|
||||
* between current page version and provided $text
|
||||
* or between the revisions provided via GET or POST
|
||||
* set class property changelog
|
||||
*/
|
||||
abstract protected function setChangeLog();
|
||||
|
||||
/**
|
||||
* Prepare revision info of comparison pair
|
||||
*/
|
||||
abstract protected function preProcess();
|
||||
|
||||
/**
|
||||
* Set a pair of revisions to be compared
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @param int $oldRev
|
||||
* @param int $newRev
|
||||
* @return $this
|
||||
*/
|
||||
public function compare($oldRev, $newRev)
|
||||
{
|
||||
if ($oldRev < $newRev) {
|
||||
[$this->oldRev, $this->newRev] = [$oldRev, $newRev];
|
||||
} else {
|
||||
[$this->oldRev, $this->newRev] = [$newRev, $oldRev];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or Sets preference of the Ui\Diff object
|
||||
*
|
||||
* @param string|array $prefs a key name or key-value pair(s)
|
||||
* @param mixed $value value used when the first args is string
|
||||
* @return array|$this
|
||||
*/
|
||||
public function preference($prefs = null, $value = null)
|
||||
{
|
||||
// set
|
||||
if (is_string($prefs) && isset($value)) {
|
||||
$this->preference[$prefs] = $value;
|
||||
return $this;
|
||||
} elseif (is_array($prefs)) {
|
||||
foreach ($prefs as $name => $value) {
|
||||
$this->preference[$name] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
// get
|
||||
return $this->preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle requested revision(s)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show()
|
||||
protected function handle()
|
||||
{
|
||||
global $ID;
|
||||
global $REV;
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
global $INFO;
|
||||
$pagelog = new PageChangeLog($ID);
|
||||
|
||||
/*
|
||||
* Determine requested revision(s)
|
||||
*/
|
||||
// we're trying to be clever here, revisions to compare can be either
|
||||
// given as rev and rev2 parameters, with rev2 being optional. Or in an
|
||||
// array in rev2.
|
||||
$rev1 = $REV;
|
||||
|
||||
$rev2 = $INPUT->ref('rev2');
|
||||
if (is_array($rev2)) {
|
||||
$rev1 = (int) $rev2[0];
|
||||
$rev2 = (int) $rev2[1];
|
||||
|
||||
if (!$rev1) {
|
||||
$rev1 = $rev2;
|
||||
unset($rev2);
|
||||
}
|
||||
} else {
|
||||
$rev2 = $INPUT->int('rev2');
|
||||
// difflink icon click, eg. ?rev=123456789&do=diff
|
||||
if ($INPUT->has('rev')) {
|
||||
$this->oldRev = $INPUT->int('rev');
|
||||
$this->newRev = $this->changelog->currentRevision();
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine left and right revision, its texts and the header
|
||||
*/
|
||||
$r_minor = '';
|
||||
$l_minor = '';
|
||||
|
||||
if ($this->text) { // compare text to the most current revision
|
||||
$l_rev = '';
|
||||
$l_text = rawWiki($ID, '');
|
||||
$l_head = '<a class="wikilink1" href="'. wl($ID) .'">'
|
||||
. $ID .' '. dformat((int) @filemtime(wikiFN($ID))) .'</a> '
|
||||
. $lang['current'];
|
||||
|
||||
$r_rev = '';
|
||||
$r_text = cleanText($this->text);
|
||||
$r_head = $lang['yours'];
|
||||
} else {
|
||||
if ($rev1 && isset($rev2) && $rev2) { // two specific revisions wanted
|
||||
// make sure order is correct (older on the left)
|
||||
if ($rev1 < $rev2) {
|
||||
$l_rev = $rev1;
|
||||
$r_rev = $rev2;
|
||||
} else {
|
||||
$l_rev = $rev2;
|
||||
$r_rev = $rev1;
|
||||
}
|
||||
} elseif ($rev1) { // single revision given, compare to current
|
||||
$r_rev = '';
|
||||
$l_rev = $rev1;
|
||||
} else { // no revision was given, compare previous to current
|
||||
$r_rev = '';
|
||||
$revs = $pagelog->getRevisions(0, 1);
|
||||
$l_rev = $revs[0];
|
||||
$REV = $l_rev; // store revision back in $REV
|
||||
}
|
||||
|
||||
// when both revisions are empty then the page was created just now
|
||||
if (!$l_rev && !$r_rev) {
|
||||
$l_text = '';
|
||||
// submit button with two checked boxes
|
||||
$rev2 = $INPUT->arr('rev2', []);
|
||||
if (count($rev2) > 1) {
|
||||
if ($rev2[0] < $rev2[1]) {
|
||||
[$this->oldRev, $this->newRev] = [$rev2[0], $rev2[1]];
|
||||
} else {
|
||||
$l_text = rawWiki($ID, $l_rev);
|
||||
[$this->oldRev, $this->newRev] = [$rev2[1], $rev2[0]];
|
||||
}
|
||||
$r_text = rawWiki($ID, $r_rev);
|
||||
|
||||
list($l_head, $r_head, $l_minor, $r_minor) = $this->diffHead(
|
||||
$l_rev, $r_rev, null, false, ($this->difftype == 'inline')
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build navigation
|
||||
*/
|
||||
$l_nav = '';
|
||||
$r_nav = '';
|
||||
if (!$this->text) {
|
||||
list($l_nav, $r_nav) = $this->diffNavigation($pagelog, $l_rev, $r_rev);
|
||||
if (!isset($this->oldRev, $this->newRev)) {
|
||||
// no revision was given, compare previous to current
|
||||
$revs = $this->changelog->getRevisions(-1, 2);
|
||||
$this->newRev = $this->changelog->currentRevision();
|
||||
$this->oldRev = ($revs[0] == $this->newRev) ? $revs[1] : $revs[0];
|
||||
}
|
||||
/*
|
||||
* Create diff object and the formatter
|
||||
*/
|
||||
$diff = new \Diff(explode("\n", $l_text), explode("\n", $r_text));
|
||||
|
||||
if ($this->difftype == 'inline') {
|
||||
$diffformatter = new \InlineDiffFormatter();
|
||||
} else {
|
||||
$diffformatter = new \TableDiffFormatter();
|
||||
}
|
||||
/*
|
||||
* Display intro
|
||||
*/
|
||||
if ($this->showIntro) print p_locale_xhtml('diff');
|
||||
|
||||
/*
|
||||
* Display type and exact reference
|
||||
*/
|
||||
if (!$this->text) {
|
||||
print '<div class="diffoptions group">';
|
||||
|
||||
// create the form to select difftype
|
||||
$form = new Form(['action' => wl()]);
|
||||
$form->setHiddenField('id', $ID);
|
||||
$form->setHiddenField('rev2[0]', $l_rev);
|
||||
$form->setHiddenField('rev2[1]', $r_rev);
|
||||
$form->setHiddenField('do', 'diff');
|
||||
$options = array(
|
||||
'sidebyside' => $lang['diff_side'],
|
||||
'inline' => $lang['diff_inline']
|
||||
);
|
||||
$input = $form->addDropdown('difftype', $options, $lang['diff_type'])
|
||||
->val($this->difftype)->addClass('quickselect');
|
||||
$input->useInput(false); // inhibit prefillInput() during toHTML() process
|
||||
$form->addButton('do[diff]', 'Go')->attr('type','submit');
|
||||
print $form->toHTML();
|
||||
|
||||
print '<p>';
|
||||
// link to exactly this view FS#2835
|
||||
print $this->diffViewlink('difflink', $l_rev, ($r_rev ?: $INFO['currentrev']));
|
||||
print '</p>';
|
||||
|
||||
print '</div>'; // .diffoptions
|
||||
}
|
||||
|
||||
/*
|
||||
* Display diff view table
|
||||
*/
|
||||
print '<div class="table">';
|
||||
print '<table class="diff diff_'. $this->difftype .'">';
|
||||
|
||||
//navigation and header
|
||||
if ($this->difftype == 'inline') {
|
||||
if (!$this->text) {
|
||||
print '<tr>'
|
||||
. '<td class="diff-lineheader">-</td>'
|
||||
. '<td class="diffnav">'. $l_nav .'</td>'
|
||||
. '</tr>';
|
||||
print '<tr>'
|
||||
. '<th class="diff-lineheader">-</th>'
|
||||
. '<th '. $l_minor .'>'. $l_head .'</th>'
|
||||
.'</tr>';
|
||||
}
|
||||
print '<tr>'
|
||||
. '<td class="diff-lineheader">+</td>'
|
||||
. '<td class="diffnav">'. $r_nav .'</td>'
|
||||
.'</tr>';
|
||||
print '<tr>'
|
||||
. '<th class="diff-lineheader">+</th>'
|
||||
. '<th '. $r_minor .'>'. $r_head .'</th>'
|
||||
. '</tr>';
|
||||
} else {
|
||||
if (!$this->text) {
|
||||
print '<tr>'
|
||||
. '<td colspan="2" class="diffnav">'. $l_nav .'</td>'
|
||||
. '<td colspan="2" class="diffnav">'. $r_nav .'</td>'
|
||||
. '</tr>';
|
||||
}
|
||||
print '<tr>'
|
||||
. '<th colspan="2" '. $l_minor .'>'. $l_head .'</th>'
|
||||
. '<th colspan="2" '. $r_minor .'>'. $r_head .'</th>'
|
||||
. '</tr>';
|
||||
}
|
||||
|
||||
//diff view
|
||||
print $this->insertSoftbreaks($diffformatter->format($diff));
|
||||
|
||||
print '</table>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get header of diff HTML
|
||||
* Build header of diff HTML
|
||||
*
|
||||
* @param string $l_rev Left revisions
|
||||
* @param string $r_rev Right revision
|
||||
* @param string $id Page id, if null $ID is used
|
||||
* @param bool $media If it is for media files
|
||||
* @param bool $inline Return the header on a single line
|
||||
* @return string[] HTML snippets for diff header
|
||||
* @deprecated 2020-12-31
|
||||
*/
|
||||
public function diffHead($l_rev, $r_rev, $id = null, $media = false, $inline = false)
|
||||
public function buildDiffHead($l_rev, $r_rev)
|
||||
{
|
||||
global $lang;
|
||||
if ($id === null) {
|
||||
global $ID;
|
||||
$id = $ID;
|
||||
}
|
||||
$head_separator = $inline ? ' ' : '<br />';
|
||||
$media_or_wikiFN = $media ? 'mediaFN' : 'wikiFN';
|
||||
$ml_or_wl = $media ? 'ml' : 'wl';
|
||||
$l_minor = $r_minor = '';
|
||||
|
||||
if ($media) {
|
||||
$changelog = new MediaChangeLog($id);
|
||||
} else {
|
||||
$changelog = new PageChangeLog($id);
|
||||
}
|
||||
if (!$l_rev) {
|
||||
$l_head = '—';
|
||||
} else {
|
||||
$l_info = $changelog->getRevisionInfo($l_rev);
|
||||
if ($l_info['user']) {
|
||||
$l_user = '<bdi>'.editorinfo($l_info['user']).'</bdi>';
|
||||
if (auth_ismanager()) $l_user .= ' <bdo dir="ltr">('.$l_info['ip'].')</bdo>';
|
||||
} else {
|
||||
$l_user = '<bdo dir="ltr">'.$l_info['ip'].'</bdo>';
|
||||
}
|
||||
$l_user = '<span class="user">'.$l_user.'</span>';
|
||||
$l_sum = ($l_info['sum']) ? '<span class="sum"><bdi>'.hsc($l_info['sum']).'</bdi></span>' : '';
|
||||
if ($l_info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) $l_minor = 'class="minor"';
|
||||
|
||||
$l_head_title = ($media) ? dformat($l_rev) : $id.' ['.dformat($l_rev).']';
|
||||
$l_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id,"rev=$l_rev").'">'
|
||||
. $l_head_title.'</a></bdi>'.$head_separator.$l_user.' '.$l_sum;
|
||||
}
|
||||
|
||||
if ($r_rev) {
|
||||
$r_info = $changelog->getRevisionInfo($r_rev);
|
||||
if ($r_info['user']) {
|
||||
$r_user = '<bdi>'.editorinfo($r_info['user']).'</bdi>';
|
||||
if (auth_ismanager()) $r_user .= ' <bdo dir="ltr">('.$r_info['ip'].')</bdo>';
|
||||
} else {
|
||||
$r_user = '<bdo dir="ltr">'.$r_info['ip'].'</bdo>';
|
||||
}
|
||||
$r_user = '<span class="user">'.$r_user.'</span>';
|
||||
$r_sum = ($r_info['sum']) ? '<span class="sum"><bdi>'.hsc($r_info['sum']).'</bdi></span>' : '';
|
||||
if ($r_info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) $r_minor = 'class="minor"';
|
||||
|
||||
$r_head_title = ($media) ? dformat($r_rev) : $id.' ['.dformat($r_rev).']';
|
||||
$r_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id,"rev=$r_rev").'">'
|
||||
. $r_head_title.'</a></bdi>'.$head_separator.$r_user.' '.$r_sum;
|
||||
} elseif ($_rev = @filemtime($media_or_wikiFN($id))) {
|
||||
$_info = $changelog->getRevisionInfo($_rev);
|
||||
if ($_info['user']) {
|
||||
$_user = '<bdi>'.editorinfo($_info['user']).'</bdi>';
|
||||
if (auth_ismanager()) $_user .= ' <bdo dir="ltr">('.$_info['ip'].')</bdo>';
|
||||
} else {
|
||||
$_user = '<bdo dir="ltr">'.$_info['ip'].'</bdo>';
|
||||
}
|
||||
$_user = '<span class="user">'.$_user.'</span>';
|
||||
$_sum = ($_info['sum']) ? '<span class="sum"><bdi>'.hsc($_info['sum']).'</span></bdi>' : '';
|
||||
if ($_info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) $r_minor = 'class="minor"';
|
||||
|
||||
$r_head_title = ($media) ? dformat($_rev) : $id.' ['.dformat($_rev).']';
|
||||
$r_head = '<bdi><a class="wikilink1" href="'.$ml_or_wl($id).'">'
|
||||
. $r_head_title.'</a></bdi> '.'('.$lang['current'].')'.$head_separator.$_user.' '.$_sum;
|
||||
}else{
|
||||
$r_head = '— ('.$lang['current'].')';
|
||||
}
|
||||
|
||||
return array($l_head, $r_head, $l_minor, $r_minor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html for revision navigation
|
||||
*
|
||||
* @param PageChangeLog $pagelog changelog object of current page
|
||||
* @param int $l_rev left revision timestamp
|
||||
* @param int $r_rev right revision timestamp
|
||||
* @return string[] html of left and right navigation elements
|
||||
*/
|
||||
protected function diffNavigation($pagelog, $l_rev, $r_rev)
|
||||
{
|
||||
global $INFO, $ID;
|
||||
|
||||
// last timestamp is not in changelog, retrieve timestamp from metadata
|
||||
// note: when page is removed, the metadata timestamp is zero
|
||||
if (!$r_rev) {
|
||||
if (isset($INFO['meta']['last_change']['date'])) {
|
||||
$r_rev = $INFO['meta']['last_change']['date'];
|
||||
} else {
|
||||
$r_rev = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//retrieve revisions with additional info
|
||||
list($l_revs, $r_revs) = $pagelog->getRevisionsAround($l_rev, $r_rev);
|
||||
$l_revisions = array();
|
||||
if (!$l_rev) {
|
||||
//no left revision given, add dummy
|
||||
$l_revisions[0]= array('label' => '', 'attrs' => []);
|
||||
}
|
||||
foreach ($l_revs as $rev) {
|
||||
$info = $pagelog->getRevisionInfo($rev);
|
||||
$l_revisions[$rev] = array(
|
||||
'label' => dformat($info['date']) .' '. editorinfo($info['user'], true) .' '. $info['sum'],
|
||||
'attrs' => ['title' => $rev],
|
||||
);
|
||||
if ($r_rev ? $rev >= $r_rev : false) $l_revisions[$rev]['attrs']['disabled'] = 'disabled';
|
||||
}
|
||||
$r_revisions = array();
|
||||
if (!$r_rev) {
|
||||
//no right revision given, add dummy
|
||||
$r_revisions[0] = array('label' => '', 'attrs' => []);
|
||||
}
|
||||
foreach ($r_revs as $rev) {
|
||||
$info = $pagelog->getRevisionInfo($rev);
|
||||
$r_revisions[$rev] = array(
|
||||
'label' => dformat($info['date']) .' '. editorinfo($info['user'], true) .' '. $info['sum'],
|
||||
'attrs' => ['title' => $rev],
|
||||
);
|
||||
if ($rev <= $l_rev) $r_revisions[$rev]['attrs']['disabled'] = 'disabled';
|
||||
}
|
||||
|
||||
//determine previous/next revisions
|
||||
$l_index = array_search($l_rev, $l_revs);
|
||||
$l_prev = $l_index < count($l_revs) - 1 ? $l_revs[$l_index + 1] : null;
|
||||
$l_next = $l_index > 1 ? $l_revs[$l_index - 1] : null;
|
||||
if ($r_rev) {
|
||||
$r_index = array_search($r_rev, $r_revs);
|
||||
$r_prev = $r_index < count($r_revs) - 1 ? $r_revs[$r_index + 1] : null;
|
||||
$r_next = $r_index > 1 ? $r_revs[$r_index - 1] : null;
|
||||
} else {
|
||||
//removed page
|
||||
if ($l_next) {
|
||||
$r_prev = $r_revs[0];
|
||||
} else {
|
||||
$r_prev = null;
|
||||
}
|
||||
$r_next = null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Left side:
|
||||
*/
|
||||
$l_nav = '';
|
||||
//move back
|
||||
if ($l_prev) {
|
||||
$l_nav .= $this->diffViewlink('diffbothprevrev', $l_prev, $r_prev);
|
||||
$l_nav .= $this->diffViewlink('diffprevrev', $l_prev, $r_rev);
|
||||
}
|
||||
//dropdown
|
||||
$form = new Form(['action' => wl()]);
|
||||
$form->setHiddenField('id', $ID);
|
||||
$form->setHiddenField('difftype', $this->difftype);
|
||||
$form->setHiddenField('rev2[1]', $r_rev);
|
||||
$form->setHiddenField('do', 'diff');
|
||||
$input = $form->addDropdown('rev2[0]', $l_revisions)->val($l_rev)->addClass('quickselect');
|
||||
$input->useInput(false); // inhibit prefillInput() during toHTML() process
|
||||
$form->addButton('do[diff]', 'Go')->attr('type','submit');
|
||||
$l_nav .= $form->toHTML();
|
||||
//move forward
|
||||
if ($l_next && ($l_next < $r_rev || !$r_rev)) {
|
||||
$l_nav .= $this->diffViewlink('diffnextrev', $l_next, $r_rev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Right side:
|
||||
*/
|
||||
$r_nav = '';
|
||||
//move back
|
||||
if ($l_rev < $r_prev) {
|
||||
$r_nav .= $this->diffViewlink('diffprevrev', $l_rev, $r_prev);
|
||||
}
|
||||
//dropdown
|
||||
$form = new Form(['action' => wl()]);
|
||||
$form->setHiddenField('id', $ID);
|
||||
$form->setHiddenField('rev2[0]', $l_rev);
|
||||
$form->setHiddenField('difftype', $this->difftype);
|
||||
$form->setHiddenField('do', 'diff');
|
||||
$input = $form->addDropdown('rev2[1]', $r_revisions)->val($r_rev)->addClass('quickselect');
|
||||
$input->useInput(false); // inhibit prefillInput() during toHTML() process
|
||||
$form->addButton('do[diff]', 'Go')->attr('type','submit');
|
||||
$r_nav .= $form->toHTML();
|
||||
//move forward
|
||||
if ($r_next) {
|
||||
if ($pagelog->isCurrentRevision($r_next)) {
|
||||
//last revision is diff with current page
|
||||
$r_nav .= $this->diffViewlink('difflastrev', $l_rev);
|
||||
} else {
|
||||
$r_nav .= $this->diffViewlink('diffnextrev', $l_rev, $r_next);
|
||||
}
|
||||
} else {
|
||||
$r_nav .= $this->diffViewlink('diffbothnextrev', $l_next, $r_next);
|
||||
}
|
||||
return array($l_nav, $r_nav);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html link to a diff view defined by two revisions
|
||||
*
|
||||
* @param string $linktype
|
||||
* @param int $lrev oldest revision
|
||||
* @param int $rrev newest revision or null for diff with current revision
|
||||
* @return string html of link to a diff view
|
||||
*/
|
||||
protected function diffViewlink($linktype, $lrev, $rrev = null)
|
||||
{
|
||||
global $ID, $lang;
|
||||
if ($rrev === null) {
|
||||
$urlparam = array(
|
||||
'do' => 'diff',
|
||||
'rev' => $lrev,
|
||||
'difftype' => $this->difftype,
|
||||
);
|
||||
} else {
|
||||
$urlparam = array(
|
||||
'do' => 'diff',
|
||||
'rev2[0]' => $lrev,
|
||||
'rev2[1]' => $rrev,
|
||||
'difftype' => $this->difftype,
|
||||
);
|
||||
}
|
||||
return '<a class="'. $linktype .'" href="'. wl($ID, $urlparam) .'" title="'. $lang[$linktype] .'">'
|
||||
. '<span>'. $lang[$linktype] .'</span>'
|
||||
. '</a>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert soft breaks in diff html
|
||||
*
|
||||
* @param string $diffhtml
|
||||
* @return string
|
||||
*/
|
||||
public function insertSoftbreaks($diffhtml)
|
||||
{
|
||||
// search the diff html string for both:
|
||||
// - html tags, so these can be ignored
|
||||
// - long strings of characters without breaking characters
|
||||
return preg_replace_callback('/<[^>]*>|[^<> ]{12,}/', function ($match) {
|
||||
// if match is an html tag, return it intact
|
||||
if ($match[0][0] == '<') return $match[0];
|
||||
// its a long string without a breaking character,
|
||||
// make certain characters into breaking characters by inserting a
|
||||
// word break opportunity (<wbr> tag) in front of them.
|
||||
$regex = <<< REGEX
|
||||
(?(?= # start a conditional expression with a positive look ahead ...
|
||||
&\#?\\w{1,6};) # ... for html entities - we don't want to split them (ok to catch some invalid combinations)
|
||||
&\#?\\w{1,6}; # yes pattern - a quicker match for the html entity, since we know we have one
|
||||
|
|
||||
[?/,&\#;:] # no pattern - any other group of 'special' characters to insert a breaking character after
|
||||
)+ # end conditional expression
|
||||
REGEX;
|
||||
return preg_replace('<'.$regex.'>xu', '\0<wbr>', $match[0]);
|
||||
}, $diffhtml);
|
||||
dbg_deprecated('not used see '. \dokuwiki\Ui\PageDiff::class .'::show()');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,351 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\Ui\MediaRevisions;
|
||||
use dokuwiki\Extension\Event;
|
||||
use dokuwiki\Form\Form;
|
||||
use JpegMeta;
|
||||
|
||||
/**
|
||||
* DokuWiki MediaDiff Interface
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
class MediaDiff extends Diff
|
||||
{
|
||||
/* @var MediaChangeLog */
|
||||
protected $changelog;
|
||||
|
||||
/* @var array */
|
||||
protected $oldRevInfo;
|
||||
protected $newRevInfo;
|
||||
|
||||
/* @var bool */
|
||||
protected $is_img;
|
||||
|
||||
/**
|
||||
* MediaDiff Ui constructor
|
||||
*
|
||||
* @param string $id media id
|
||||
*/
|
||||
public function __construct($id)
|
||||
{
|
||||
if (!isset($id)) {
|
||||
throw new \InvalidArgumentException('media id should not be empty!');
|
||||
}
|
||||
|
||||
// init preference
|
||||
$this->preference['fromAjax'] = false; // see dokuwiki\Ajax::callMediadiff()
|
||||
$this->preference['showIntro'] = false;
|
||||
$this->preference['difftype'] = 'both'; // diff view type: both, opacity or portions
|
||||
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
protected function setChangeLog()
|
||||
{
|
||||
$this->changelog = new MediaChangeLog($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle requested revision(s) and diff view preferences
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
// requested rev or rev2
|
||||
parent::handle();
|
||||
|
||||
// requested diff view type
|
||||
if ($INPUT->has('difftype')) {
|
||||
$this->preference['difftype'] = $INPUT->str('difftype');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare revision info of comparison pair
|
||||
*/
|
||||
protected function preProcess()
|
||||
{
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
// revision info of older file (left side)
|
||||
$this->oldRevInfo = $changelog->getRevisionInfo($this->oldRev);
|
||||
// revision info of newer file (right side)
|
||||
$this->newRevInfo = $changelog->getRevisionInfo($this->newRev);
|
||||
|
||||
$this->is_img = preg_match('/\.(jpe?g|gif|png)$/', $this->id);
|
||||
|
||||
foreach ([&$this->oldRevInfo, &$this->newRevInfo] as &$revInfo) {
|
||||
// use timestamp and '' properly as $rev for the current file
|
||||
$isCurrent = $changelog->isCurrentRevision($revInfo['date']);
|
||||
$revInfo += [
|
||||
'current' => $isCurrent,
|
||||
'rev' => $isCurrent ? '' : $revInfo['date'],
|
||||
];
|
||||
|
||||
// headline in the Diff view navigation
|
||||
$revInfo['navTitle'] = $this->revisionTitle($revInfo);
|
||||
|
||||
if ($this->is_img) {
|
||||
$rev = $revInfo['rev'];
|
||||
$meta = new JpegMeta(mediaFN($this->id, $rev));
|
||||
// get image width and height for the mediamanager preview panel
|
||||
$revInfo['previewSize'] = media_image_preview_size($this->id, $rev, $meta);
|
||||
}
|
||||
}
|
||||
unset($revInfo);
|
||||
|
||||
// re-check image, ensure minimum image width for showImageDiff()
|
||||
$this->is_img = ($this->is_img
|
||||
&& ($this->oldRevInfo['previewSize'][0] ?? 0) >= 30
|
||||
&& ($this->newRevInfo['previewSize'][0] ?? 0) >= 30
|
||||
);
|
||||
// adjust requested diff view type
|
||||
if (!$this->is_img) {
|
||||
$this->preference['difftype'] = 'both';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows difference between two revisions of media
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
$ns = getNS($this->id);
|
||||
$auth = auth_quickaclcheck("$ns:*");
|
||||
|
||||
if ($auth < AUTH_READ || !$this->id || !$conf['mediarevisions']) return '';
|
||||
|
||||
// retrieve form parameters: rev, rev2, difftype
|
||||
$this->handle();
|
||||
// prepare revision info of comparison pair
|
||||
$this->preProcess();
|
||||
|
||||
// display intro
|
||||
if ($this->preference['showIntro']) echo p_locale_xhtml('diff');
|
||||
|
||||
// print form to choose diff view type
|
||||
if ($this->is_img && !$this->preference['fromAjax']) {
|
||||
$this->showDiffViewSelector();
|
||||
echo '<div id="mediamanager__diff" >';
|
||||
}
|
||||
|
||||
switch ($this->preference['difftype']) {
|
||||
case 'opacity':
|
||||
case 'portions':
|
||||
$this->showImageDiff();
|
||||
break;
|
||||
case 'both':
|
||||
default:
|
||||
$this->showFileDiff();
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->is_img && !$this->preference['fromAjax']) {
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print form to choose diff view type
|
||||
* the dropdown is to be added through JavaScript, see lib/scripts/media.js
|
||||
*/
|
||||
protected function showDiffViewSelector()
|
||||
{
|
||||
// use timestamp for current revision
|
||||
[$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['date']];
|
||||
|
||||
echo '<div class="diffoptions group">';
|
||||
|
||||
$form = new Form([
|
||||
'id' => 'mediamanager__form_diffview',
|
||||
'action' => media_managerURL([], '&'),
|
||||
'method' => 'get',
|
||||
'class' => 'diffView',
|
||||
]);
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
$form->setHiddenField('sectok', null);
|
||||
$form->setHiddenField('mediado', 'diff');
|
||||
$form->setHiddenField('rev2[0]', $oldRev);
|
||||
$form->setHiddenField('rev2[1]', $newRev);
|
||||
$form->addTagClose('div');
|
||||
echo $form->toHTML();
|
||||
|
||||
echo '</div>'; // .diffoptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints two images side by side
|
||||
* and slider
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function showImageDiff()
|
||||
{
|
||||
// diff view type: opacity or portions
|
||||
$type = $this->preference['difftype'];
|
||||
|
||||
// use '' for current revision
|
||||
[$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']];
|
||||
|
||||
// adjust image width, right side (newer) has priority
|
||||
$oldRevSize = $this->oldRevInfo['previewSize'];
|
||||
$newRevSize = $this->newRevInfo['previewSize'];
|
||||
if ($oldRevSize != $newRevSize) {
|
||||
if ($newRevSize[0] > $oldRevSize[0]) {
|
||||
$oldRevSize = $newRevSize;
|
||||
}
|
||||
}
|
||||
|
||||
$oldRevSrc = ml($this->id, ['rev' => $oldRev, 'h' => $oldRevSize[1], 'w' => $oldRevSize[0]]);
|
||||
$newRevSrc = ml($this->id, ['rev' => $newRev, 'h' => $oldRevSize[1], 'w' => $oldRevSize[0]]);
|
||||
|
||||
// slider
|
||||
echo '<div class="slider" style="max-width: '.($oldRevSize[0]-20).'px;" ></div>';
|
||||
|
||||
// two images in divs
|
||||
echo '<div class="imageDiff '.$type.'">';
|
||||
echo '<div class="image1" style="max-width: '.$oldRevSize[0].'px;">';
|
||||
echo '<img src="'.$oldRevSrc.'" alt="" />';
|
||||
echo '</div>';
|
||||
echo '<div class="image2" style="max-width: '.$oldRevSize[0].'px;">';
|
||||
echo '<img src="'.$newRevSrc.'" alt="" />';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows difference between two revisions of media file
|
||||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
protected function showFileDiff()
|
||||
{
|
||||
global $lang;
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
$ns = getNS($this->id);
|
||||
$auth = auth_quickaclcheck("$ns:*");
|
||||
|
||||
// use '' for current revision
|
||||
[$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']];
|
||||
|
||||
$oldRevMeta = new JpegMeta(mediaFN($this->id, $oldRev));
|
||||
$newRevMeta = new JpegMeta(mediaFN($this->id, $newRev));
|
||||
|
||||
// display diff view table
|
||||
echo '<div class="table">';
|
||||
echo '<table>';
|
||||
echo '<tr>';
|
||||
echo '<th>'. $this->oldRevInfo['navTitle'] .'</th>';
|
||||
echo '<th>'. $this->newRevInfo['navTitle'] .'</th>';
|
||||
echo '</tr>';
|
||||
|
||||
echo '<tr class="image">';
|
||||
echo '<td>';
|
||||
media_preview($this->id, $auth, $oldRev, $oldRevMeta); // $auth not used in media_preview()?
|
||||
echo '</td>';
|
||||
|
||||
echo '<td>';
|
||||
media_preview($this->id, $auth, $newRev, $newRevMeta);
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
|
||||
echo '<tr class="actions">';
|
||||
echo '<td>';
|
||||
media_preview_buttons($this->id, $auth, $oldRev); // $auth used in media_preview_buttons()
|
||||
echo '</td>';
|
||||
|
||||
echo '<td>';
|
||||
media_preview_buttons($this->id, $auth, $newRev);
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
|
||||
$l_tags = media_file_tags($oldRevMeta);
|
||||
$r_tags = media_file_tags($newRevMeta);
|
||||
// FIXME r_tags-only stuff
|
||||
foreach ($l_tags as $key => $l_tag) {
|
||||
if ($l_tag['value'] != $r_tags[$key]['value']) {
|
||||
$r_tags[$key]['highlighted'] = true;
|
||||
$l_tags[$key]['highlighted'] = true;
|
||||
} elseif (!$l_tag['value'] || !$r_tags[$key]['value']) {
|
||||
unset($r_tags[$key]);
|
||||
unset($l_tags[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
echo '<tr>';
|
||||
foreach (array($l_tags, $r_tags) as $tags) {
|
||||
echo '<td>';
|
||||
|
||||
echo '<dl class="img_tags">';
|
||||
foreach ($tags as $tag) {
|
||||
$value = cleanText($tag['value']);
|
||||
if (!$value) $value = '-';
|
||||
echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
|
||||
echo '<dd>';
|
||||
if ($tag['highlighted']) echo '<strong>';
|
||||
if ($tag['tag'][2] == 'date') {
|
||||
echo dformat($value);
|
||||
} else {
|
||||
echo hsc($value);
|
||||
}
|
||||
if ($tag['highlighted']) echo '</strong>';
|
||||
echo '</dd>';
|
||||
}
|
||||
echo '</dl>';
|
||||
|
||||
echo '</td>';
|
||||
}
|
||||
echo '</tr>';
|
||||
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Revision Title for MediaDiff table headline
|
||||
*
|
||||
* @param array $info Revision info structure of a media file
|
||||
* @return string
|
||||
*/
|
||||
protected function revisionTitle(array $info)
|
||||
{
|
||||
global $lang, $INFO;
|
||||
|
||||
if (isset($info['date'])) {
|
||||
$rev = $info['date'];
|
||||
$title = '<bdi><a class="wikilink1" href="'.ml($this->id, ['rev' => $rev]).'">'
|
||||
. dformat($rev).'</a></bdi>';
|
||||
} else {
|
||||
$rev = false;
|
||||
$title = '—';
|
||||
}
|
||||
if (isset($info['current']) || ($rev && $rev == $INFO['currentrev'])) {
|
||||
$title .= ' ('.$lang['current'].')';
|
||||
}
|
||||
|
||||
// append separator
|
||||
$title .= ($this->preference['difftype'] === 'inline') ? ' ' : '<br />';
|
||||
|
||||
// supplement
|
||||
if (isset($info['date'])) {
|
||||
$objRevInfo = (new MediaRevisions($this->id))->getObjRevInfo($info);
|
||||
$title .= $objRevInfo->editSummary().' '.$objRevInfo->editor();
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\Form\Form;
|
||||
|
||||
/**
|
||||
* DokuWiki MediaRevisions Interface
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
class MediaRevisions extends Revisions
|
||||
{
|
||||
/* @var MediaChangeLog */
|
||||
protected $changelog;
|
||||
|
||||
/**
|
||||
* MediaRevisions Ui constructor
|
||||
*
|
||||
* @param string $id id of media
|
||||
*/
|
||||
public function __construct($id)
|
||||
{
|
||||
if (!$id) {
|
||||
throw new \InvalidArgumentException('media id should not be empty!');
|
||||
}
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
protected function setChangeLog()
|
||||
{
|
||||
$this->changelog = new MediaChangeLog($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a list of Media Revisions in the MediaManager
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
* @author Satoshi Sahara <sahara.satoshi@gmail.com>
|
||||
*
|
||||
* @param int $first skip the first n changelog lines
|
||||
* @return void
|
||||
*/
|
||||
public function show($first = 0)
|
||||
{
|
||||
global $lang;
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
// get revisions, and set correct pagenation parameters (first, hasNext)
|
||||
if ($first === null) $first = 0;
|
||||
$hasNext = false;
|
||||
$revisions = $this->getRevisions($first, $hasNext);
|
||||
|
||||
// create the form
|
||||
$form = new Form([
|
||||
'id' => 'page__revisions', // must not be "media__revisions"
|
||||
'action' => media_managerURL(['image' => $this->id], '&'),
|
||||
'class' => 'changes',
|
||||
]);
|
||||
$form->setHiddenField('mediado', 'diff'); // required for media revisions
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
|
||||
// start listing
|
||||
$form->addTagOpen('ul');
|
||||
foreach ($revisions as $info) {
|
||||
$rev = $info['date'];
|
||||
$info['current'] = $changelog->isCurrentRevision($rev);
|
||||
|
||||
$class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : '';
|
||||
$form->addTagOpen('li')->addClass($class);
|
||||
$form->addTagOpen('div')->addClass('li');
|
||||
|
||||
if (isset($info['current'])) {
|
||||
$form->addCheckbox('rev2[]')->val($rev);
|
||||
} elseif (file_exists(mediaFN($this->id, $rev))) {
|
||||
$form->addCheckbox('rev2[]')->val($rev);
|
||||
} else {
|
||||
$form->addCheckbox('')->val($rev)->attr('disabled','disabled');
|
||||
}
|
||||
$form->addHTML(' ');
|
||||
|
||||
$objRevInfo = $this->getObjRevInfo($info);
|
||||
$html = implode(' ', [
|
||||
$objRevInfo->editDate(), // edit date and time
|
||||
$objRevInfo->difflink(), // link to diffview icon
|
||||
$objRevInfo->itemName(), // name of page or media
|
||||
'<div>',
|
||||
$objRevInfo->editSummary(), // edit summary
|
||||
$objRevInfo->editor(), // editor info
|
||||
$objRevInfo->sizechange(), // size change indicator
|
||||
$objRevInfo->currentIndicator(), // current indicator (only when k=1)
|
||||
'</div>',
|
||||
]);
|
||||
$form->addHTML($html);
|
||||
|
||||
$form->addTagClose('div');
|
||||
$form->addTagClose('li');
|
||||
}
|
||||
$form->addTagClose('ul'); // end of revision list
|
||||
|
||||
// show button for diff view
|
||||
$form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit');
|
||||
|
||||
$form->addTagClose('div'); // close div class=no
|
||||
|
||||
print $form->toHTML('Revisions');
|
||||
|
||||
// provide navigation for pagenated revision list (of pages and/or media files)
|
||||
print $this->navigation($first, $hasNext, function ($n) {
|
||||
return media_managerURL(['first' => $n], '&', false, true);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -35,7 +35,7 @@ class PageConflict extends Ui
|
|||
*/
|
||||
public function show()
|
||||
{
|
||||
global $ID;
|
||||
global $INFO;
|
||||
global $lang;
|
||||
|
||||
// print intro
|
||||
|
@ -44,7 +44,7 @@ class PageConflict extends Ui
|
|||
// create the form
|
||||
$form = new Form(['id' => 'dw__editform']);
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
$form->setHiddenField('id', $ID);
|
||||
$form->setHiddenField('id', $INFO['id']);
|
||||
$form->setHiddenField('wikitext', $this->text);
|
||||
$form->setHiddenField('summary', $this->summary);
|
||||
|
||||
|
@ -56,7 +56,8 @@ class PageConflict extends Ui
|
|||
|
||||
print '<br /><br /><br /><br />';
|
||||
|
||||
(new Diff($this->text, false))->show();
|
||||
// print difference
|
||||
(new PageDiff($INFO['id']))->compareWith($this->text)->preference('showIntro', false)->show();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,554 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\Form\Form;
|
||||
|
||||
/**
|
||||
* DokuWiki PageDiff Interface
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Satoshi Sahara <sahara.satoshi@gmail.com>
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
class PageDiff extends Diff
|
||||
{
|
||||
/* @var PageChangeLog */
|
||||
protected $changelog;
|
||||
|
||||
/* @var array */
|
||||
protected $oldRevInfo;
|
||||
protected $newRevInfo;
|
||||
|
||||
/* @var string */
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* PageDiff Ui constructor
|
||||
*
|
||||
* @param string $id page id
|
||||
*/
|
||||
public function __construct($id = null)
|
||||
{
|
||||
global $INFO;
|
||||
if (!isset($id)) $id = $INFO['id'];
|
||||
|
||||
// init preference
|
||||
$this->preference['showIntro'] = true;
|
||||
$this->preference['difftype'] = 'sidebyside'; // diff view type: inline or sidebyside
|
||||
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
protected function setChangeLog()
|
||||
{
|
||||
$this->changelog = new PageChangeLog($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text to be compared with most current version
|
||||
* when it has been externally edited
|
||||
* exclusively use of the compare($old, $new) method
|
||||
*
|
||||
* @param string $text
|
||||
* @return $this
|
||||
*/
|
||||
public function compareWith($text = null)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
if (isset($text)) {
|
||||
$this->text = $text;
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
// revision info of older file (left side)
|
||||
$this->oldRevInfo = $changelog->getCurrentRevisionInfo() + [
|
||||
'current' => true,
|
||||
'rev' => '',
|
||||
'navTitle' => $this->revisionTitle($changelog->getCurrentRevisionInfo()),
|
||||
'text' => rawWiki($this->id, ''),
|
||||
];
|
||||
|
||||
// revision info of newer file (right side)
|
||||
$this->newRevInfo = [
|
||||
'date' => null,
|
||||
//'ip' => '127.0.0.1',
|
||||
//'type' => DOKU_CHANGE_TYPE_CREATE,
|
||||
'id' => $this->id,
|
||||
//'user' => '',
|
||||
//'sum' => '',
|
||||
//'extra' => '',
|
||||
'sizechange' => strlen($this->text) - io_getSizeFile(wikiFN($this->id, '')),
|
||||
'timestamp' => false,
|
||||
'current' => false,
|
||||
'rev' => false,
|
||||
'navTitle' => $lang['yours'],
|
||||
'text' => cleanText($this->text),
|
||||
];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle requested revision(s) and diff view preferences
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
// requested rev or rev2
|
||||
if (!isset($this->oldRevInfo, $this->newRevInfo)) {
|
||||
parent::handle();
|
||||
}
|
||||
|
||||
// requested diff view type
|
||||
if ($INPUT->has('difftype')) {
|
||||
$this->preference['difftype'] = $INPUT->str('difftype');
|
||||
} else {
|
||||
// read preference from DokuWiki cookie. PageDiff only
|
||||
$mode = get_doku_pref('difftype', $mode = null);
|
||||
if (isset($mode)) $this->preference['difftype'] = $mode;
|
||||
}
|
||||
|
||||
if (!$INPUT->has('rev') && !$INPUT->has('rev2')) {
|
||||
global $INFO, $REV;
|
||||
if ($this->id == $INFO['id'])
|
||||
$REV = $this->oldRev; // store revision back in $REV
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare revision info of comparison pair
|
||||
*/
|
||||
protected function preProcess()
|
||||
{
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
// revision info of older file (left side)
|
||||
$this->oldRevInfo = $changelog->getRevisionInfo($this->oldRev);
|
||||
// revision info of newer file (right side)
|
||||
$this->newRevInfo = $changelog->getRevisionInfo($this->newRev);
|
||||
|
||||
foreach ([&$this->oldRevInfo, &$this->newRevInfo] as &$revInfo) {
|
||||
// use timestamp and '' properly as $rev for the current file
|
||||
$isCurrent = $changelog->isCurrentRevision($revInfo['date']);
|
||||
$revInfo += [
|
||||
'current' => $isCurrent,
|
||||
'rev' => $isCurrent ? '' : $revInfo['date'],
|
||||
];
|
||||
|
||||
// headline in the Diff view navigation
|
||||
$revInfo['navTitle'] = $this->revisionTitle($revInfo);
|
||||
|
||||
if ($revInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
//attic stores complete last page version for a deleted page
|
||||
$revInfo['text'] = '';
|
||||
} else {
|
||||
$revInfo['text'] = rawWiki($this->id, $revInfo['rev']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show diff
|
||||
* between current page version and provided $text
|
||||
* or between the revisions provided via GET or POST
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
if (!isset($this->oldRevInfo, $this->newRevInfo)) {
|
||||
// retrieve form parameters: rev, rev2, difftype
|
||||
$this->handle();
|
||||
// prepare revision info of comparison pair, except PageConfrict or PageDraft
|
||||
$this->preProcess();
|
||||
}
|
||||
|
||||
// create difference engine object
|
||||
$Difference = new \Diff(
|
||||
explode("\n", $this->oldRevInfo['text']),
|
||||
explode("\n", $this->newRevInfo['text'])
|
||||
);
|
||||
|
||||
// build paired navigation
|
||||
[$navOlderRevisions, $navNewerRevisions] = $this->buildRevisionsNavigation();
|
||||
|
||||
// display intro
|
||||
if ($this->preference['showIntro']) echo p_locale_xhtml('diff');
|
||||
|
||||
// print form to choose diff view type, and exact url reference to the view
|
||||
if ($this->newRevInfo['rev'] !== false) {
|
||||
$this->showDiffViewSelector();
|
||||
}
|
||||
|
||||
// assign minor edit checker to the variable
|
||||
$classEditType = function ($info) {
|
||||
return ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? ' class="minor"' : '';
|
||||
};
|
||||
|
||||
// display diff view table
|
||||
echo '<div class="table">';
|
||||
echo '<table class="diff diff_'.$this->preference['difftype'] .'">';
|
||||
|
||||
//navigation and header
|
||||
switch ($this->preference['difftype']) {
|
||||
case 'inline':
|
||||
if ($this->newRevInfo['rev'] !== false) {
|
||||
echo '<tr>'
|
||||
.'<td class="diff-lineheader">-</td>'
|
||||
.'<td class="diffnav">'. $navOlderRevisions .'</td>'
|
||||
.'</tr>';
|
||||
echo '<tr>'
|
||||
.'<th class="diff-lineheader">-</th>'
|
||||
.'<th'.$classEditType($this->oldRevInfo).'>'.$this->oldRevInfo['navTitle'].'</th>'
|
||||
.'</tr>';
|
||||
}
|
||||
echo '<tr>'
|
||||
.'<td class="diff-lineheader">+</td>'
|
||||
.'<td class="diffnav">'. $navNewerRevisions .'</td>'
|
||||
.'</tr>';
|
||||
echo '<tr>'
|
||||
.'<th class="diff-lineheader">+</th>'
|
||||
.'<th'.$classEditType($this->newRevInfo).'>'.$this->newRevInfo['navTitle'].'</th>'
|
||||
.'</tr>';
|
||||
// create formatter object
|
||||
$DiffFormatter = new \InlineDiffFormatter();
|
||||
break;
|
||||
|
||||
case 'sidebyside':
|
||||
default:
|
||||
if ($this->newRevInfo['rev'] !== false) {
|
||||
echo '<tr>'
|
||||
.'<td colspan="2" class="diffnav">'. $navOlderRevisions .'</td>'
|
||||
.'<td colspan="2" class="diffnav">'. $navNewerRevisions .'</td>'
|
||||
.'</tr>';
|
||||
}
|
||||
echo '<tr>'
|
||||
.'<th colspan="2"'.$classEditType($this->oldRevInfo).'>'.$this->oldRevInfo['navTitle'].'</th>'
|
||||
.'<th colspan="2"'.$classEditType($this->newRevInfo).'>'.$this->newRevInfo['navTitle'].'</th>'
|
||||
.'</tr>';
|
||||
// create formatter object
|
||||
$DiffFormatter = new \TableDiffFormatter();
|
||||
break;
|
||||
}
|
||||
|
||||
// output formatted difference
|
||||
echo $this->insertSoftbreaks($DiffFormatter->format($Difference));
|
||||
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Revision Title for PageDiff table headline
|
||||
*
|
||||
* @param array $info Revision info structure of a page
|
||||
* @return string
|
||||
*/
|
||||
protected function revisionTitle(array $info)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
// use designated title when compare current page source with given text
|
||||
if (array_key_exists('date', $info) && is_null($info['date'])) {
|
||||
return $lang['yours'];
|
||||
}
|
||||
|
||||
// revision info may have timestamp key when external edits occurred
|
||||
$info['timestamp'] = $info['timestamp'] ?? true;
|
||||
|
||||
if (isset($info['date'])) {
|
||||
$rev = $info['date'];
|
||||
if ($info['timestamp'] === false) {
|
||||
// exteranlly deleted or older file restored
|
||||
$title = '<bdi><a class="wikilink2" href="'.wl($this->id).'">'
|
||||
. $this->id .' ['. $lang['unknowndate'] .']'.'</a></bdi>';
|
||||
} else {
|
||||
$title = '<bdi><a class="wikilink1" href="'.wl($this->id, ['rev' => $rev]).'">'
|
||||
. $this->id .' ['. dformat($rev) .']'.'</a></bdi>';
|
||||
}
|
||||
} else {
|
||||
$rev = false;
|
||||
$title = '—';
|
||||
}
|
||||
if ($info['current']) {
|
||||
$title .= ' ('.$lang['current'].')';
|
||||
}
|
||||
|
||||
// append separator
|
||||
$title .= ($this->preference['difftype'] === 'inline') ? ' ' : '<br />';
|
||||
|
||||
// supplement
|
||||
if (isset($info['date'])) {
|
||||
$objRevInfo = (new PageRevisions($this->id))->getObjRevInfo($info);
|
||||
$title .= $objRevInfo->editSummary().' '.$objRevInfo->editor();
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print form to choose diff view type, and exact url reference to the view
|
||||
*/
|
||||
protected function showDiffViewSelector()
|
||||
{
|
||||
global $lang;
|
||||
|
||||
// use timestamp for current revision
|
||||
[$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['date']];
|
||||
|
||||
echo '<div class="diffoptions group">';
|
||||
|
||||
// create the form to select difftype
|
||||
$form = new Form(['action' => wl()]);
|
||||
$form->setHiddenField('id', $this->id);
|
||||
$form->setHiddenField('rev2[0]', $oldRev);
|
||||
$form->setHiddenField('rev2[1]', $newRev);
|
||||
$form->setHiddenField('do', 'diff');
|
||||
$options = array(
|
||||
'sidebyside' => $lang['diff_side'],
|
||||
'inline' => $lang['diff_inline'],
|
||||
);
|
||||
$input = $form->addDropdown('difftype', $options, $lang['diff_type'])
|
||||
->val($this->preference['difftype'])
|
||||
->addClass('quickselect');
|
||||
$input->useInput(false); // inhibit prefillInput() during toHTML() process
|
||||
$form->addButton('do[diff]', 'Go')->attr('type','submit');
|
||||
echo $form->toHTML();
|
||||
|
||||
// show exact url reference to the view when it is meaningful
|
||||
echo '<p>';
|
||||
if ($oldRev && $newRev) {
|
||||
// link to exactly this view FS#2835
|
||||
$viewUrl = $this->diffViewlink('difflink', $oldRev, $newRev);
|
||||
}
|
||||
echo $viewUrl ?? '<br />';
|
||||
echo '</p>';
|
||||
|
||||
echo '</div>'; // .diffoptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html for revision navigation
|
||||
*
|
||||
* The navigation consists of older and newer revisions selectors, each
|
||||
* state mutually depends on the selected revision of opposite side.
|
||||
*
|
||||
* @return string[] html of navigation for both older and newer sides
|
||||
*/
|
||||
protected function buildRevisionsNavigation()
|
||||
{
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
if ($this->newRevInfo['rev'] === false) {
|
||||
// no revisions selector for PageConflict or PageDraft
|
||||
return array('', '');
|
||||
}
|
||||
|
||||
// use timestamp for current revision
|
||||
[$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['date']];
|
||||
|
||||
// retrieve revisions with additional info
|
||||
[$oldRevs, $newRevs] = $changelog->getRevisionsAround($oldRev, $newRev);
|
||||
|
||||
// build options for dropdown selector
|
||||
$olderRevisions = $this->buildRevisionOptions('older', $oldRevs);
|
||||
$newerRevisions = $this->buildRevisionOptions('newer', $newRevs);
|
||||
|
||||
// determine previous/next revisions
|
||||
$index = array_search($oldRev, $oldRevs);
|
||||
$oldPrevRev = ($index +1 < count($oldRevs)) ? $oldRevs[$index +1] : false;
|
||||
$oldNextRev = ($index > 0) ? $oldRevs[$index -1] : false;
|
||||
$index = array_search($newRev, $newRevs);
|
||||
$newPrevRev = ($index +1 < count($newRevs)) ? $newRevs[$index +1] : false;
|
||||
$newNextRev = ($index > 0) ? $newRevs[$index -1] : false;
|
||||
|
||||
/*
|
||||
* navigation UI for older revisions / Left side:
|
||||
*/
|
||||
$navOlderRevs = '';
|
||||
// move backward both side: ◀◀
|
||||
if ($oldPrevRev && $newPrevRev)
|
||||
$navOlderRevs .= $this->diffViewlink('diffbothprevrev', $oldPrevRev, $newPrevRev);
|
||||
// move backward left side: ◀
|
||||
if ($oldPrevRev)
|
||||
$navOlderRevs .= $this->diffViewlink('diffprevrev', $oldPrevRev, $newRev);
|
||||
// dropdown
|
||||
$navOlderRevs .= $this->buildDropdownSelector('older', $olderRevisions);
|
||||
// move forward left side: ▶
|
||||
if ($oldNextRev && ($oldNextRev < $newRev))
|
||||
$navOlderRevs .= $this->diffViewlink('diffnextrev', $oldNextRev, $newRev);
|
||||
|
||||
/*
|
||||
* navigation UI for newer revisions / Right side:
|
||||
*/
|
||||
$navNewerRevs = '';
|
||||
// move backward right side: ◀
|
||||
if ($newPrevRev && ($oldRev < $newPrevRev))
|
||||
$navNewerRevs .= $this->diffViewlink('diffprevrev', $oldRev, $newPrevRev);
|
||||
// dropdown
|
||||
$navNewerRevs .= $this->buildDropdownSelector('newer', $newerRevisions);
|
||||
// move forward right side: ▶
|
||||
if ($newNextRev) {
|
||||
if ($changelog->isCurrentRevision($newNextRev)) {
|
||||
$navNewerRevs .= $this->diffViewlink('difflastrev', $oldRev, $newNextRev);
|
||||
} else {
|
||||
$navNewerRevs .= $this->diffViewlink('diffnextrev', $oldRev, $newNextRev);
|
||||
}
|
||||
}
|
||||
// move forward both side: ▶▶
|
||||
if ($oldNextRev && $newNextRev)
|
||||
$navNewerRevs .= $this->diffViewlink('diffbothnextrev', $oldNextRev, $newNextRev);
|
||||
|
||||
return array($navOlderRevs, $navNewerRevs);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare options for dropdwon selector
|
||||
*
|
||||
* @params string $side "older" or "newer"
|
||||
* @params array $revs list of revsion
|
||||
* @return array
|
||||
*/
|
||||
protected function buildRevisionOptions($side, $revs)
|
||||
{
|
||||
global $lang;
|
||||
$changelog =& $this->changelog;
|
||||
$revisions = array();
|
||||
|
||||
// use timestamp for current revision
|
||||
[$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['date']];
|
||||
|
||||
foreach ($revs as $rev) {
|
||||
$info = $changelog->getRevisionInfo($rev);
|
||||
// revision info may have timestamp key when external edits occurred
|
||||
$info['timestamp'] = $info['timestamp'] ?? true;
|
||||
$date = dformat($info['date']);
|
||||
if ($info['timestamp'] === false) {
|
||||
// exteranlly deleted or older file restored
|
||||
$date = preg_replace('/[0-9a-zA-Z]/','_', $date);
|
||||
}
|
||||
$revisions[$rev] = array(
|
||||
'label' => implode(' ', [
|
||||
$date,
|
||||
editorinfo($info['user'], true),
|
||||
$info['sum'],
|
||||
]),
|
||||
'attrs' => ['title' => $rev],
|
||||
);
|
||||
if (($side == 'older' && ($newRev && $rev >= $newRev))
|
||||
||($side == 'newer' && ($rev <= $oldRev))
|
||||
) {
|
||||
$revisions[$rev]['attrs']['disabled'] = 'disabled';
|
||||
}
|
||||
}
|
||||
return $revisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* build Dropdown form for revisions navigation
|
||||
*
|
||||
* @params string $side "older" or "newer"
|
||||
* @params array $options dropdown options
|
||||
* @return string
|
||||
*/
|
||||
protected function buildDropdownSelector($side, $options)
|
||||
{
|
||||
$form = new Form(['action' => wl($this->id)]);
|
||||
$form->setHiddenField('id', $this->id);
|
||||
$form->setHiddenField('do', 'diff');
|
||||
$form->setHiddenField('difftype', $this->preference['difftype']);
|
||||
|
||||
// use timestamp for current revision
|
||||
[$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['date']];
|
||||
|
||||
switch ($side) {
|
||||
case 'older': // left side
|
||||
$form->setHiddenField('rev2[1]', $newRev);
|
||||
$input = $form->addDropdown('rev2[0]', $options)
|
||||
->val($oldRev)->addClass('quickselect');
|
||||
$input->useInput(false); // inhibit prefillInput() during toHTML() process
|
||||
break;
|
||||
case 'newer': // right side
|
||||
$form->setHiddenField('rev2[0]', $oldRev);
|
||||
$input = $form->addDropdown('rev2[1]', $options)
|
||||
->val($newRev)->addClass('quickselect');
|
||||
$input->useInput(false); // inhibit prefillInput() during toHTML() process
|
||||
break;
|
||||
}
|
||||
$form->addButton('do[diff]', 'Go')->attr('type','submit');
|
||||
return $form->toHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html link to a diff view defined by two revisions
|
||||
*
|
||||
* @param string $linktype
|
||||
* @param int $oldRev older revision
|
||||
* @param int $newRev newer revision or null for diff with current revision
|
||||
* @return string html of link to a diff view
|
||||
*/
|
||||
protected function diffViewlink($linktype, $oldRev, $newRev = null)
|
||||
{
|
||||
global $lang;
|
||||
if ($newRev === null) {
|
||||
$urlparam = array(
|
||||
'do' => 'diff',
|
||||
'rev' => $oldRev,
|
||||
'difftype' => $this->preference['difftype'],
|
||||
);
|
||||
} else {
|
||||
$urlparam = array(
|
||||
'do' => 'diff',
|
||||
'rev2[0]' => $oldRev,
|
||||
'rev2[1]' => $newRev,
|
||||
'difftype' => $this->preference['difftype'],
|
||||
);
|
||||
}
|
||||
$attr = array(
|
||||
'class' => $linktype,
|
||||
'href' => wl($this->id, $urlparam, true, '&'),
|
||||
'title' => $lang[$linktype],
|
||||
);
|
||||
return '<a '. buildAttributes($attr) .'><span>'. $lang[$linktype] .'</span></a>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert soft breaks in diff html
|
||||
*
|
||||
* @param string $diffhtml
|
||||
* @return string
|
||||
*/
|
||||
public function insertSoftbreaks($diffhtml)
|
||||
{
|
||||
// search the diff html string for both:
|
||||
// - html tags, so these can be ignored
|
||||
// - long strings of characters without breaking characters
|
||||
return preg_replace_callback('/<[^>]*>|[^<> ]{12,}/', function ($match) {
|
||||
// if match is an html tag, return it intact
|
||||
if ($match[0][0] == '<') return $match[0];
|
||||
// its a long string without a breaking character,
|
||||
// make certain characters into breaking characters by inserting a
|
||||
// word break opportunity (<wbr> tag) in front of them.
|
||||
$regex = <<< REGEX
|
||||
(?(?= # start a conditional expression with a positive look ahead ...
|
||||
&\#?\\w{1,6};) # ... for html entities - we don't want to split them (ok to catch some invalid combinations)
|
||||
&\#?\\w{1,6}; # yes pattern - a quicker match for the html entity, since we know we have one
|
||||
|
|
||||
[?/,&\#;:] # no pattern - any other group of 'special' characters to insert a breaking character after
|
||||
)+ # end conditional expression
|
||||
REGEX;
|
||||
return preg_replace('<'.$regex.'>xu', '\0<wbr>', $match[0]);
|
||||
}, $diffhtml);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,21 +22,21 @@ class PageDraft extends Ui
|
|||
public function show()
|
||||
{
|
||||
global $INFO;
|
||||
global $ID;
|
||||
global $lang;
|
||||
|
||||
$draft = new \dokuwiki\Draft($ID, $INFO['client']);
|
||||
$draft = new \dokuwiki\Draft($INFO['id'], $INFO['client']);
|
||||
$text = $draft->getDraftText();
|
||||
|
||||
// print intro
|
||||
print p_locale_xhtml('draft');
|
||||
|
||||
(new Diff($text, false))->show();
|
||||
// print difference
|
||||
(new PageDiff($INFO['id']))->compareWith($text)->preference('showIntro', false)->show();
|
||||
|
||||
// create the draft form
|
||||
$form = new Form(['id' => 'dw__editform']);
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
$form->setHiddenField('id', $ID);
|
||||
$form->setHiddenField('id', $INFO['id']);
|
||||
$form->setHiddenField('date', $draft->getDraftDate());
|
||||
$form->setHiddenField('wikitext', $text);
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\Form\Form;
|
||||
|
||||
/**
|
||||
* DokuWiki PageRevisions Interface
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
class PageRevisions extends Revisions
|
||||
{
|
||||
/* @var PageChangeLog */
|
||||
protected $changelog;
|
||||
|
||||
/**
|
||||
* PageRevisions Ui constructor
|
||||
*
|
||||
* @param string $id id of page
|
||||
*/
|
||||
public function __construct($id = null)
|
||||
{
|
||||
global $INFO;
|
||||
if (!isset($id)) $id = $INFO['id'];
|
||||
parent::__construct($id);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
protected function setChangeLog()
|
||||
{
|
||||
$this->changelog = new PageChangeLog($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display list of old revisions of the page
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
* @author Satoshi Sahara <sahara.satoshi@gmail.com>
|
||||
*
|
||||
* @param int $first skip the first n changelog lines
|
||||
* @return void
|
||||
*/
|
||||
public function show($first = 0)
|
||||
{
|
||||
global $lang, $REV;
|
||||
$changelog =& $this->changelog;
|
||||
|
||||
// get revisions, and set correct pagenation parameters (first, hasNext)
|
||||
if ($first === null) $first = 0;
|
||||
$hasNext = false;
|
||||
$revisions = $this->getRevisions($first, $hasNext);
|
||||
|
||||
// print intro
|
||||
print p_locale_xhtml('revisions');
|
||||
|
||||
// create the form
|
||||
$form = new Form([
|
||||
'id' => 'page__revisions',
|
||||
'class' => 'changes',
|
||||
]);
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
|
||||
// start listing
|
||||
$form->addTagOpen('ul');
|
||||
foreach ($revisions as $info) {
|
||||
$rev = $info['date'];
|
||||
$info['current'] = $changelog->isCurrentRevision($rev);
|
||||
|
||||
$class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : '';
|
||||
$form->addTagOpen('li')->addClass($class);
|
||||
$form->addTagOpen('div')->addClass('li');
|
||||
|
||||
if (isset($info['current'])) {
|
||||
$form->addCheckbox('rev2[]')->val($rev);
|
||||
} elseif ($rev == $REV) {
|
||||
$form->addCheckbox('rev2[]')->val($rev)->attr('checked','checked');
|
||||
} elseif (page_exists($this->id, $rev)) {
|
||||
$form->addCheckbox('rev2[]')->val($rev);
|
||||
} else {
|
||||
$form->addCheckbox('')->val($rev)->attr('disabled','disabled');
|
||||
}
|
||||
$form->addHTML(' ');
|
||||
|
||||
$objRevInfo = $this->getObjRevInfo($info);
|
||||
$html = implode(' ', [
|
||||
$objRevInfo->editDate(), // edit date and time
|
||||
$objRevInfo->difflink(), // link to diffview icon
|
||||
$objRevInfo->itemName(), // name of page or media
|
||||
$objRevInfo->editSummary(), // edit summary
|
||||
$objRevInfo->editor(), // editor info
|
||||
$objRevInfo->sizechange(), // size change indicator
|
||||
$objRevInfo->currentIndicator(), // current indicator (only when k=1)
|
||||
]);
|
||||
$form->addHTML($html);
|
||||
$form->addTagClose('div');
|
||||
$form->addTagClose('li');
|
||||
}
|
||||
$form->addTagClose('ul'); // end of revision list
|
||||
|
||||
// show button for diff view
|
||||
$form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit');
|
||||
|
||||
$form->addTagClose('div'); // close div class=no
|
||||
|
||||
print $form->toHTML('Revisions');
|
||||
|
||||
// provide navigation for paginated revision list (of pages and/or media files)
|
||||
print $this->navigation($first, $hasNext, function ($n) {
|
||||
return array('do' => 'revisions', 'first' => $n);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\Form\Form;
|
||||
|
||||
|
@ -72,6 +73,10 @@ class Recent extends Ui
|
|||
// start listing of recent items
|
||||
$form->addTagOpen('ul');
|
||||
foreach ($recents as $recent) {
|
||||
// check possible external edition for current page or media
|
||||
$this->checkCurrentRevision($recent);
|
||||
$recent['current'] = true;
|
||||
|
||||
$objRevInfo = $this->getObjRevInfo($recent);
|
||||
$class = ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor': '';
|
||||
$form->addTagOpen('li')->addClass($class);
|
||||
|
@ -140,6 +145,31 @@ class Recent extends Ui
|
|||
return $recents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check possible external deletion for current page or media
|
||||
*
|
||||
* To keep sort order in the recent list, we ignore externally modification.
|
||||
* It is not possible to know when external deletion had happened,
|
||||
* $info['date'] is to be incremented 1 second when such deletion detected.
|
||||
*/
|
||||
protected function checkCurrentRevision(array &$info)
|
||||
{
|
||||
$itemType = strrpos($info['id'], '.') ? 'media' : 'page';
|
||||
if ($itemType == 'page') {
|
||||
$changelog = new PageChangelog($info['id']);
|
||||
} else {
|
||||
$changelog = new MediaChangelog($info['id']);
|
||||
}
|
||||
if (!$changelog->isCurrentRevision($info['date'])) {
|
||||
$currentRevInfo = $changelog->getCurrentRevisionInfo();
|
||||
if ($currentRevInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
// the page or media file was externally deleted
|
||||
$info = array_merge($info, $currentRevInfo);
|
||||
}
|
||||
}
|
||||
unset($changelog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation buttons for Pagenation (prev/next)
|
||||
*
|
||||
|
@ -209,6 +239,8 @@ class Recent extends Ui
|
|||
|
||||
public function __construct(array $info)
|
||||
{
|
||||
$info['item'] = strrpos($info['id'], '.') ? 'media' : 'page';
|
||||
$info['current'] = $info['current'] ?? false;
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
|
@ -216,10 +248,12 @@ class Recent extends Ui
|
|||
public function itemIcon()
|
||||
{
|
||||
$id = $this->info['id'];
|
||||
if (isset($this->info['media'])) {
|
||||
$html = media_printicon($id);
|
||||
} else {
|
||||
$html = '<img class="icon" src="'.DOKU_BASE.'lib/images/fileicons/file.png" alt="'.$id.'" />';
|
||||
switch ($this->info['item']) {
|
||||
case 'media': // media file revision
|
||||
$html = media_printicon($id);
|
||||
break;
|
||||
case 'page': // page revision
|
||||
$html = '<img class="icon" src="'.DOKU_BASE.'lib/images/fileicons/file.png" alt="'.$id.'" />';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
@ -254,14 +288,17 @@ class Recent extends Ui
|
|||
public function itemName()
|
||||
{
|
||||
$id = $this->info['id'];
|
||||
if (isset($this->info['media'])) {
|
||||
$href = media_managerURL(['tab_details'=>'view', 'image'=> $id, 'ns'=> getNS($id)], '&');
|
||||
$class = file_exists(mediaFN($id)) ? 'wikilink1' : 'wikilink2';
|
||||
$html = '<a href="'.$href.'" class="'.$class.'">'.$id.'</a>';
|
||||
} else {
|
||||
$html = html_wikilink(':'.$id, (useHeading('navigation') ? null : $id));
|
||||
switch ($this->info['item']) {
|
||||
case 'media': // media file revision
|
||||
$href = media_managerURL(['tab_details'=>'view', 'image'=> $id, 'ns'=> getNS($id)], '&');
|
||||
$class = file_exists(mediaFN($id)) ? 'wikilink1' : 'wikilink2';
|
||||
$html = '<a href="'.$href.'" class="'.$class.'">'.$id.'</a>';
|
||||
return $html;
|
||||
case 'page': // page revision
|
||||
$html = html_wikilink(':'.$id, (useHeading('navigation') ? null : $id));
|
||||
return $html;
|
||||
}
|
||||
return $html;
|
||||
return '';
|
||||
}
|
||||
|
||||
// icon difflink
|
||||
|
@ -270,18 +307,20 @@ class Recent extends Ui
|
|||
global $lang;
|
||||
$id = $this->info['id'];
|
||||
|
||||
if (isset($this->info['media'])) {
|
||||
$revs = (new MediaChangeLog($id))->getRevisions(0, 1);
|
||||
$diff = (count($revs) && file_exists(mediaFN($id)));
|
||||
if ($diff) {
|
||||
$href = media_managerURL(
|
||||
['tab_details'=>'history', 'mediado'=>'diff', 'image'=> $id, 'ns'=> getNS($id)], '&'
|
||||
);
|
||||
} else {
|
||||
$href = '';
|
||||
}
|
||||
} else {
|
||||
$href = wl($id, "do=diff", false, '&');
|
||||
switch ($this->info['item']) {
|
||||
case 'media': // media file revision
|
||||
$revs = (new MediaChangeLog($id))->getRevisions(0, 1);
|
||||
$diff = (count($revs) && file_exists(mediaFN($id)));
|
||||
if ($diff) {
|
||||
$href = media_managerURL(
|
||||
['tab_details'=>'history', 'mediado'=>'diff', 'image'=> $id, 'ns'=> getNS($id)], '&'
|
||||
);
|
||||
} else {
|
||||
$href = '';
|
||||
}
|
||||
break;
|
||||
case 'page': // page revision
|
||||
$href = wl($id, "do=diff", false, '&');
|
||||
}
|
||||
|
||||
if ($href) {
|
||||
|
@ -300,10 +339,12 @@ class Recent extends Ui
|
|||
{
|
||||
global $lang;
|
||||
$id = $this->info['id'];
|
||||
if (isset($this->info['media'])) {
|
||||
$href = media_managerURL(['tab_details'=>'history', 'image'=> $id, 'ns'=> getNS($id)], '&');
|
||||
} else {
|
||||
$href = wl($id, "do=revisions", false, '&');
|
||||
switch ($this->info['item']) {
|
||||
case 'media': // media file revision
|
||||
$href = media_managerURL(['tab_details'=>'history', 'image'=> $id, 'ns'=> getNS($id)], '&');
|
||||
break;
|
||||
case 'page': // page revision
|
||||
$href = wl($id, "do=revisions", false, '&');
|
||||
}
|
||||
$html = '<a href="'.$href.'" class="revisions_link">'
|
||||
. '<img src="'.DOKU_BASE.'lib/images/history.png" width="12" height="14"'
|
||||
|
|
|
@ -2,193 +2,40 @@
|
|||
|
||||
namespace dokuwiki\Ui;
|
||||
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\ChangeLog\MediaChangeLog;
|
||||
use dokuwiki\Form\Form;
|
||||
use dokuwiki\ChangeLog\ChangeLog;
|
||||
|
||||
/**
|
||||
* DokuWiki Revisions Interface
|
||||
* parent class of PageRevisions and MediaRevisions
|
||||
*
|
||||
* @package dokuwiki\Ui
|
||||
*/
|
||||
class Revisions extends Ui
|
||||
abstract class Revisions extends Ui
|
||||
{
|
||||
protected $first;
|
||||
protected $media_id;
|
||||
/* @var string */
|
||||
protected $id; // page id or media id
|
||||
|
||||
/* @var ChangeLog */
|
||||
protected $changelog; // PageChangeLog or MediaChangeLog object
|
||||
|
||||
/**
|
||||
* Revisions Ui constructor
|
||||
*
|
||||
* @param int $first skip the first n changelog lines
|
||||
* @param bool|string $media_id id of media, or false for current page
|
||||
* @param string $id page id or media id
|
||||
*/
|
||||
public function __construct($first = 0, $media_id = false)
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->first = $first;
|
||||
$this->media_id = $media_id;
|
||||
$this->id = $id;
|
||||
$this->setChangeLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display list of old revisions
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
* @author Satoshi Sahara <sahara.satoshi@gmail.com>
|
||||
*
|
||||
* @return void
|
||||
* set class property changelog
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
global $ID;
|
||||
|
||||
if ($this->media_id) {
|
||||
return $this->showMediaRevisions($this->media_id);
|
||||
} else {
|
||||
return $this->showPageRevisions($ID);
|
||||
}
|
||||
}
|
||||
abstract protected function setChangeLog();
|
||||
|
||||
/**
|
||||
* Display a list of Media Revisions in the MediaManager
|
||||
*
|
||||
* @param string $id media id
|
||||
* @return void
|
||||
*/
|
||||
protected function showMediaRevisions($id)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
// get revisions, and set correct pagenation parameters (first, hasNext)
|
||||
$first = $this->first;
|
||||
$hasNext = false;
|
||||
$revisions = $this->getRevisions($first, $hasNext);
|
||||
|
||||
// create the form
|
||||
$form = new Form([
|
||||
'id' => 'page__revisions', // must not be "media__revisions"
|
||||
'action' => media_managerURL(['image' => $id], '&'),
|
||||
'class' => 'changes',
|
||||
]);
|
||||
$form->setHiddenField('mediado', 'diff'); // required for media revisions
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
|
||||
// start listing
|
||||
$form->addTagOpen('ul');
|
||||
foreach ($revisions as $info) {
|
||||
$rev = $info['date'];
|
||||
$class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : '';
|
||||
$form->addTagOpen('li')->addClass($class);
|
||||
$form->addTagOpen('div')->addClass('li');
|
||||
|
||||
if (isset($info['current'])) {
|
||||
$form->addCheckbox('rev2[]')->val('current');
|
||||
} elseif (file_exists(mediaFN($id, $rev))) {
|
||||
$form->addCheckbox('rev2[]')->val($rev);
|
||||
} else {
|
||||
$form->addCheckbox('')->val($rev)->attr('disabled','disabled');
|
||||
}
|
||||
$form->addHTML(' ');
|
||||
|
||||
$objRevInfo = $this->getObjRevInfo($info);
|
||||
$html = implode(' ', [
|
||||
$objRevInfo->editDate(), // edit date and time
|
||||
$objRevInfo->difflink(), // link to diffview icon
|
||||
$objRevInfo->itemName(), // name of page or media
|
||||
'<div>',
|
||||
$objRevInfo->editSummary(), // edit summary
|
||||
$objRevInfo->editor(), // editor info
|
||||
html_sizechange($info['sizechange']), // size change indicator
|
||||
$objRevInfo->currentIndicator(), // current indicator (only when k=1)
|
||||
'</div>',
|
||||
]);
|
||||
$form->addHTML($html);
|
||||
|
||||
$form->addTagClose('div');
|
||||
$form->addTagClose('li');
|
||||
}
|
||||
$form->addTagClose('ul'); // end of revision list
|
||||
|
||||
// show button for diff view
|
||||
$form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit');
|
||||
|
||||
$form->addTagClose('div'); // close div class=no
|
||||
|
||||
print $form->toHTML('Revisions');
|
||||
|
||||
// provide navigation for pagenated revision list (of pages and/or media files)
|
||||
print $this->htmlNavigation($id, $first, $hasNext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a list of Page Revisions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function showPageRevisions($id)
|
||||
{
|
||||
global $lang;
|
||||
|
||||
// get revisions, and set correct pagenation parameters (first, hasNext)
|
||||
$first = $this->first;
|
||||
$hasNext = false;
|
||||
$revisions = $this->getRevisions($first, $hasNext);
|
||||
|
||||
// print intro
|
||||
print p_locale_xhtml('revisions');
|
||||
|
||||
// create the form
|
||||
$form = new Form([
|
||||
'id' => 'page__revisions',
|
||||
'class' => 'changes',
|
||||
]);
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
|
||||
// start listing
|
||||
$form->addTagOpen('ul');
|
||||
foreach ($revisions as $info) {
|
||||
$rev = $info['date'];
|
||||
$class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : '';
|
||||
$form->addTagOpen('li')->addClass($class);
|
||||
$form->addTagOpen('div')->addClass('li');
|
||||
|
||||
if (page_exists($id, $rev)) {
|
||||
$form->addCheckbox('rev2[]')->val($rev);
|
||||
} else {
|
||||
$form->addCheckbox('')->val($rev)->attr('disabled','disabled');
|
||||
}
|
||||
$form->addHTML(' ');
|
||||
|
||||
$objRevInfo = $this->getObjRevInfo($info);
|
||||
$html = implode(' ', [
|
||||
$objRevInfo->editDate(), // edit date and time
|
||||
$objRevInfo->difflink(), // link to diffview icon
|
||||
$objRevInfo->itemName(), // name of page or media
|
||||
$objRevInfo->editSummary(), // edit summary
|
||||
$objRevInfo->editor(), // editor info
|
||||
$objRevInfo->sizechange(), // size change indicator
|
||||
$objRevInfo->currentIndicator(), // current indicator (only when k=1)
|
||||
]);
|
||||
$form->addHTML($html);
|
||||
$form->addTagClose('div');
|
||||
$form->addTagClose('li');
|
||||
}
|
||||
$form->addTagClose('ul'); // end of revision list
|
||||
|
||||
// show button for diff view
|
||||
$form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit');
|
||||
|
||||
$form->addTagClose('div'); // close div class=no
|
||||
|
||||
print $form->toHTML('Revisions');
|
||||
|
||||
// provide navigation for pagenated revision list (of pages and/or media files)
|
||||
print $this->htmlNavigation($id, $first, $hasNext);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get revisions, and set correct pagenation parameters (first, hasNext)
|
||||
* Get revisions, and set correct pagination parameters (first, hasNext)
|
||||
*
|
||||
* @param int $first
|
||||
* @param bool $hasNext
|
||||
|
@ -197,81 +44,46 @@ class Revisions extends Ui
|
|||
*/
|
||||
protected function getRevisions(&$first, &$hasNext)
|
||||
{
|
||||
global $INFO, $conf;
|
||||
|
||||
if ($this->media_id) {
|
||||
$changelog = new MediaChangeLog($this->media_id);
|
||||
} else {
|
||||
$changelog = new PageChangeLog($INFO['id']);
|
||||
}
|
||||
global $conf;
|
||||
|
||||
$changelog =& $this->changelog;
|
||||
$revisions = [];
|
||||
|
||||
$currentRevInfo = $changelog->getCurrentRevisionInfo();
|
||||
if (!$currentRevInfo) return $revisions;
|
||||
|
||||
$num = $conf['recent'];
|
||||
if ($first == 0) {
|
||||
// add extrenal or existing last revision that is excluded from $changelog->getRevisions()
|
||||
if (array_key_exists('timestamp', $currentRevInfo) || (
|
||||
$currentRevInfo['type'] != DOKU_CHANGE_TYPE_DELETE &&
|
||||
$currentRevInfo['date'] == $changelog->lastRevision() )
|
||||
) {
|
||||
$revisions[] = $currentRevInfo;
|
||||
$num = $num - 1;
|
||||
}
|
||||
}
|
||||
/* we need to get one additional log entry to be able to
|
||||
* decide if this is the last page or is there another one.
|
||||
* see also Ui\Recent::getRecents()
|
||||
*/
|
||||
$revlist = $changelog->getRevisions($first, $conf['recent'] +1);
|
||||
if (count($revlist) == 0 && $first != 0) {
|
||||
$revlist = $changelog->getRevisions($first, $num + 1);
|
||||
if (count($revlist) == 0 && $first > 0) {
|
||||
// resets to zero if $first requested a too high number
|
||||
$first = 0;
|
||||
$revlist = $changelog->getRevisions($first, $conf['recent'] +1);
|
||||
}
|
||||
$exists = ($this->media_id) ? file_exists(mediaFN($this->media_id)) : $INFO['exists'];
|
||||
if ($first === 0 && $exists) {
|
||||
// add current page or media as revision[0]
|
||||
if ($this->media_id) {
|
||||
$rev = filemtime(fullpath(mediaFN($this->media_id)));
|
||||
$changelog->setChunkSize(1024);
|
||||
$revinfo = $changelog->getRevisionInfo($rev) ?: array(
|
||||
'date' => $rev,
|
||||
'ip' => null,
|
||||
'type' => null,
|
||||
'id' => $this->media_id,
|
||||
'user' => null,
|
||||
'sum' => null,
|
||||
'extra' => null,
|
||||
'sizechange' => null,
|
||||
);
|
||||
$revisions[] = $revinfo + array(
|
||||
'media' => true,
|
||||
'current' => true,
|
||||
);
|
||||
} else {
|
||||
if (isset($INFO['meta']['last_change'])) {
|
||||
$type = $INFO['meta']['last_change']['type'];
|
||||
$sizechange = $INFO['meta']['last_change']['sizechange'];
|
||||
} else {
|
||||
$type = $sizechange = null;
|
||||
}
|
||||
|
||||
$revisions[] = array(
|
||||
'date' => $INFO['lastmod'],
|
||||
'ip' => null,
|
||||
'type' => $type,
|
||||
'id' => $INFO['id'],
|
||||
'user' => $INFO['editor'],
|
||||
'sum' => $INFO['sum'],
|
||||
'extra' => null,
|
||||
'sizechange' => $sizechange,
|
||||
'current' => true,
|
||||
);
|
||||
}
|
||||
return $this->getRevisions($first, $hasNext);
|
||||
}
|
||||
|
||||
// decide if this is the last page or is there another one
|
||||
$hasNext = false;
|
||||
if (count($revlist) > $conf['recent']) {
|
||||
if (count($revlist) > $num) {
|
||||
$hasNext = true;
|
||||
array_pop($revlist); // remove one additional log entry
|
||||
}
|
||||
|
||||
// append each revison info array to the revisions
|
||||
foreach ($revlist as $rev) {
|
||||
if ($this->media_id) {
|
||||
$revisions[] = $changelog->getRevisionInfo($rev) + array('media' => true);
|
||||
} else {
|
||||
$revisions[] = $changelog->getRevisionInfo($rev);
|
||||
}
|
||||
$revisions[] = $changelog->getRevisionInfo($rev);
|
||||
}
|
||||
return $revisions;
|
||||
}
|
||||
|
@ -279,12 +91,12 @@ class Revisions extends Ui
|
|||
/**
|
||||
* Navigation buttons for Pagenation (prev/next)
|
||||
*
|
||||
* @param string $id page id or media id
|
||||
* @param int $first
|
||||
* @param bool $hasNext
|
||||
* @return array html
|
||||
* @param callable $callback returns array of hidden fields for the form button
|
||||
* @return string html
|
||||
*/
|
||||
protected function htmlNavigation($id, $first, $hasNext)
|
||||
protected function navigation($first, $hasNext, $callback)
|
||||
{
|
||||
global $conf;
|
||||
|
||||
|
@ -293,20 +105,12 @@ class Revisions extends Ui
|
|||
if ($first > 0) {
|
||||
$first = max($first - $conf['recent'], 0);
|
||||
$html.= '<div class="pagenav-prev">';
|
||||
if ($this->media_id) {
|
||||
$html.= html_btn('newer', $id, "p", media_managerURL(['first' => $first], '&', false, true));
|
||||
} else {
|
||||
$html.= html_btn('newer', $id, "p" ,['do' => 'revisions', 'first' => $first]);
|
||||
}
|
||||
$html.= html_btn('newer', $this->id, "p", $callback($first));
|
||||
$html.= '</div>';
|
||||
}
|
||||
if ($hasNext) {
|
||||
$html.= '<div class="pagenav-next">';
|
||||
if ($this->media_id) {
|
||||
$html.= html_btn('older', $id, "n", media_managerURL(['first' => $last], '&', false, true));
|
||||
} else {
|
||||
$html.= html_btn('older', $id, "n", ['do' => 'revisions', 'first' => $last]);
|
||||
}
|
||||
$html.= html_btn('older', $this->id, "n", $callback($last));
|
||||
$html.= '</div>';
|
||||
}
|
||||
$html.= '</div>';
|
||||
|
@ -319,7 +123,7 @@ class Revisions extends Ui
|
|||
* @param array $info Revision info structure of a page or media file
|
||||
* @return objRevInfo object (anonymous class)
|
||||
*/
|
||||
protected function getObjRevInfo(array $info)
|
||||
public function getObjRevInfo(array $info)
|
||||
{
|
||||
return new class ($info) // anonymous class (objRevInfo)
|
||||
{
|
||||
|
@ -327,9 +131,10 @@ class Revisions extends Ui
|
|||
|
||||
public function __construct(array $info)
|
||||
{
|
||||
if (!isset($info['current'])) {
|
||||
$info['current'] = false;
|
||||
}
|
||||
$info['item'] = strrpos($info['id'], '.') ? 'media' : 'page';
|
||||
$info['current'] = $info['current'] ?? false;
|
||||
// revision info may have timestamp key when external edits occurred
|
||||
$info['timestamp'] = $info['timestamp'] ?? true;
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
|
@ -343,7 +148,13 @@ class Revisions extends Ui
|
|||
// edit date and time of the page or media file
|
||||
public function editDate()
|
||||
{
|
||||
return '<span class="date">'. dformat($this->info['date']) .'</span>';
|
||||
global $lang;
|
||||
$date = dformat($this->info['date']);
|
||||
if ($this->info['timestamp'] === false) {
|
||||
// externally deleted or older file restored
|
||||
$date = preg_replace('/[0-9a-zA-Z]/','_', $date);
|
||||
}
|
||||
return '<span class="date">'. $date .'</span>';
|
||||
}
|
||||
|
||||
// edit summary
|
||||
|
@ -377,30 +188,34 @@ class Revisions extends Ui
|
|||
$id = $this->info['id'];
|
||||
$rev = $this->info['date'];
|
||||
|
||||
if (isset($this->info['media'])) {
|
||||
// media file revision
|
||||
if (isset($this->info['current'])) {
|
||||
$href = media_managerURL(['image'=> $id, 'tab_details'=> 'view'], '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
|
||||
} elseif (file_exists(mediaFN($id, $rev))) {
|
||||
$href = media_managerURL(['image'=> $id, 'tab_details'=> 'view', 'rev'=> $rev], '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
|
||||
} else {
|
||||
$html = $id;
|
||||
}
|
||||
return $html;
|
||||
} else {
|
||||
// page revision
|
||||
$display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
|
||||
if (!$display_name) $display_name = $id;
|
||||
if ($this->info['current'] || page_exists($id, $rev)) {
|
||||
$href = wl($id, "rev=$rev", false, '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink1">'.$display_name.'</a>';
|
||||
} else {
|
||||
$html = $display_name;
|
||||
}
|
||||
return $html;
|
||||
switch ($this->info['item']) {
|
||||
case 'media': // media file revision
|
||||
if ($this->info['current']) {
|
||||
$href = media_managerURL(['image'=> $id, 'tab_details'=> 'view'], '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
|
||||
} elseif (file_exists(mediaFN($id, $rev))) {
|
||||
$href = media_managerURL(['image'=> $id, 'tab_details'=> 'view', 'rev'=> $rev], '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
|
||||
} else {
|
||||
$html = $id;
|
||||
}
|
||||
return $html;
|
||||
case 'page': // page revision
|
||||
$display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
|
||||
if (!$display_name) $display_name = $id;
|
||||
if ($this->info['type'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
// externally deleted or older file restored
|
||||
$href = wl($id, "", false, '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink2">'.$display_name.'</a>';
|
||||
} elseif ($this->info['current'] || page_exists($id, $rev)) {
|
||||
$href = wl($id, "rev=$rev", false, '&');
|
||||
$html = '<a href="'.$href.'" class="wikilink1">'.$display_name.'</a>';
|
||||
} else {
|
||||
$html = $display_name;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// icon difflink
|
||||
|
@ -410,31 +225,31 @@ class Revisions extends Ui
|
|||
$id = $this->info['id'];
|
||||
$rev = $this->info['date'];
|
||||
|
||||
if (isset($this->info['media'])) {
|
||||
// media file revision
|
||||
if (isset($this->info['current']) || !file_exists(mediaFN($id, $rev))) {
|
||||
$html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
|
||||
} else {
|
||||
$href = media_managerURL(['image'=> $id, 'rev'=> $rev, 'mediado'=>'diff'], '&');
|
||||
$html = '<a href="'.$href.'" class="diff_link">'
|
||||
. '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
|
||||
. ' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />'
|
||||
. '</a> ';
|
||||
}
|
||||
return $html;
|
||||
} else {
|
||||
// page revision
|
||||
if ($this->info['current'] || !page_exists($id, $rev)) {
|
||||
$html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
|
||||
} else {
|
||||
$href = wl($id, "rev=$rev,do=diff", false, '&');
|
||||
$html = '<a href="'.$href.'" class="diff_link">'
|
||||
. '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
|
||||
. ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />'
|
||||
. '</a>';
|
||||
}
|
||||
return $html;
|
||||
switch ($this->info['item']) {
|
||||
case 'media': // media file revision
|
||||
if ($this->info['current'] || !file_exists(mediaFN($id, $rev))) {
|
||||
$html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
|
||||
} else {
|
||||
$href = media_managerURL(['image'=> $id, 'rev'=> $rev, 'mediado'=>'diff'], '&');
|
||||
$html = '<a href="'.$href.'" class="diff_link">'
|
||||
. '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
|
||||
. ' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />'
|
||||
. '</a> ';
|
||||
}
|
||||
return $html;
|
||||
case 'page': // page revision
|
||||
if ($this->info['current'] || !page_exists($id, $rev)) {
|
||||
$html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
|
||||
} else {
|
||||
$href = wl($id, "rev=$rev,do=diff", false, '&');
|
||||
$html = '<a href="'.$href.'" class="diff_link">'
|
||||
. '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
|
||||
. ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />'
|
||||
. '</a>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// size change
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*/
|
||||
|
||||
use dokuwiki\ChangeLog\ChangeLog;
|
||||
use dokuwiki\File\PageFile;
|
||||
|
||||
/**
|
||||
* parses a changelog line into it's components
|
||||
*
|
||||
|
@ -15,31 +18,15 @@
|
|||
* @return array|bool parsed line or false
|
||||
*/
|
||||
function parseChangelogLine($line) {
|
||||
$line = rtrim($line, "\n");
|
||||
$tmp = explode("\t", $line);
|
||||
if ($tmp!==false && count($tmp)>1) {
|
||||
$info = array();
|
||||
$info['date'] = (int)$tmp[0]; // unix timestamp
|
||||
$info['ip'] = $tmp[1]; // IPv4 address (127.0.0.1)
|
||||
$info['type'] = $tmp[2]; // log line type
|
||||
$info['id'] = $tmp[3]; // page id
|
||||
$info['user'] = $tmp[4]; // user name
|
||||
$info['sum'] = $tmp[5]; // edit summary (or action reason)
|
||||
$info['extra'] = $tmp[6]; // extra data (varies by line type)
|
||||
if(isset($tmp[7]) && $tmp[7] !== '') { //last item has line-end||
|
||||
$info['sizechange'] = (int) $tmp[7];
|
||||
} else {
|
||||
$info['sizechange'] = null;
|
||||
}
|
||||
return $info;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return ChangeLog::parseLogLine($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry to the changelog and saves the metadata for the page
|
||||
*
|
||||
* Note: timestamp of the change might not be unique especially after very quick
|
||||
* repeated edits (e.g. change checkbox via do plugin)
|
||||
*
|
||||
* @param int $date Timestamp of the change
|
||||
* @param String $id Name of the affected page
|
||||
* @param String $type Type of the change see DOKU_CHANGE_TYPE_*
|
||||
|
@ -53,79 +40,50 @@ function parseChangelogLine($line) {
|
|||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
* @author Esther Brunner <wikidesign@gmail.com>
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @deprecated 2021-11-28
|
||||
*/
|
||||
function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null, $sizechange = null){
|
||||
function addLogEntry(
|
||||
$date,
|
||||
$id,
|
||||
$type = DOKU_CHANGE_TYPE_EDIT,
|
||||
$summary = '',
|
||||
$extra = '',
|
||||
$flags = null,
|
||||
$sizechange = null)
|
||||
{
|
||||
// no more used in DokuWiki core, but left for third-party plugins
|
||||
dbg_deprecated('see '. \dokuwiki\File\PageFile::class .'::saveWikiText()');
|
||||
|
||||
global $conf, $INFO;
|
||||
/** @var Input $INPUT */
|
||||
global $INPUT;
|
||||
|
||||
// check for special flags as keys
|
||||
if (!is_array($flags)) { $flags = array(); }
|
||||
if (!is_array($flags)) $flags = array();
|
||||
$flagExternalEdit = isset($flags['ExternalEdit']);
|
||||
|
||||
$id = cleanid($id);
|
||||
$file = wikiFN($id);
|
||||
$created = @filectime($file);
|
||||
$minor = ($type===DOKU_CHANGE_TYPE_MINOR_EDIT);
|
||||
$wasRemoved = ($type===DOKU_CHANGE_TYPE_DELETE);
|
||||
|
||||
if(!$date) $date = time(); //use current time if none supplied
|
||||
$remote = (!$flagExternalEdit)?clientIP(true):'127.0.0.1';
|
||||
$user = (!$flagExternalEdit)?$INPUT->server->str('REMOTE_USER'):'';
|
||||
if($sizechange === null) {
|
||||
$sizechange = '';
|
||||
} else {
|
||||
$sizechange = (int) $sizechange;
|
||||
}
|
||||
if (!$date) $date = time(); //use current time if none supplied
|
||||
$remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1';
|
||||
$user = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : '';
|
||||
$sizechange = ($sizechange === null) ? '' : (int)$sizechange;
|
||||
|
||||
$strip = array("\t", "\n");
|
||||
$logline = array(
|
||||
// update changelog file and get the added entry that is also to be stored in metadata
|
||||
$pageFile = new PageFile($id);
|
||||
$logEntry = $pageFile->changelog->addLogEntry([
|
||||
'date' => $date,
|
||||
'ip' => $remote,
|
||||
'type' => str_replace($strip, '', $type),
|
||||
'type' => $type,
|
||||
'id' => $id,
|
||||
'user' => $user,
|
||||
'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
|
||||
'extra' => str_replace($strip, '', $extra),
|
||||
'sizechange' => $sizechange
|
||||
);
|
||||
'sum' => $summary,
|
||||
'extra' => $extra,
|
||||
'sizechange' => $sizechange,
|
||||
]);
|
||||
|
||||
$wasCreated = ($type===DOKU_CHANGE_TYPE_CREATE);
|
||||
$wasReverted = ($type===DOKU_CHANGE_TYPE_REVERT);
|
||||
// update metadata
|
||||
if (!$wasRemoved) {
|
||||
$oldmeta = p_read_metadata($id)['persistent'];
|
||||
$meta = array();
|
||||
if (
|
||||
$wasCreated && (
|
||||
empty($oldmeta['date']['created']) ||
|
||||
$oldmeta['date']['created'] === $created
|
||||
)
|
||||
){
|
||||
// newly created
|
||||
$meta['date']['created'] = $created;
|
||||
if ($user){
|
||||
$meta['creator'] = isset($INFO) ? $INFO['userinfo']['name'] : null;
|
||||
$meta['user'] = $user;
|
||||
}
|
||||
} elseif (($wasCreated || $wasReverted) && !empty($oldmeta['date']['created'])) {
|
||||
// re-created / restored
|
||||
$meta['date']['created'] = $oldmeta['date']['created'];
|
||||
$meta['date']['modified'] = $created; // use the files ctime here
|
||||
$meta['creator'] = isset($oldmeta['creator']) ? $oldmeta['creator'] : null;
|
||||
if ($user) $meta['contributor'][$user] = isset($INFO) ? $INFO['userinfo']['name'] : null;
|
||||
} elseif (!$minor) { // non-minor modification
|
||||
$meta['date']['modified'] = $date;
|
||||
if ($user) $meta['contributor'][$user] = isset($INFO) ? $INFO['userinfo']['name'] : null;
|
||||
}
|
||||
$meta['last_change'] = $logline;
|
||||
p_set_metadata($id, $meta);
|
||||
}
|
||||
|
||||
// add changelog lines
|
||||
$logline = implode("\t", $logline)."\n";
|
||||
io_saveFile(metaFN($id,'.changes'),$logline,true); //page changelog
|
||||
io_saveFile($conf['changelog'],$logline,true); //global changelog cache
|
||||
$pageFile->updateMetadata($logEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,48 +107,42 @@ function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extr
|
|||
function addMediaLogEntry(
|
||||
$date,
|
||||
$id,
|
||||
$type=DOKU_CHANGE_TYPE_EDIT,
|
||||
$summary='',
|
||||
$extra='',
|
||||
$flags=null,
|
||||
$type = DOKU_CHANGE_TYPE_EDIT,
|
||||
$summary = '',
|
||||
$extra = '',
|
||||
$flags = null,
|
||||
$sizechange = null)
|
||||
{
|
||||
global $conf;
|
||||
/** @var Input $INPUT */
|
||||
global $INPUT;
|
||||
|
||||
// check for special flags as keys
|
||||
if (!is_array($flags)) $flags = array();
|
||||
$flagExternalEdit = isset($flags['ExternalEdit']);
|
||||
|
||||
$id = cleanid($id);
|
||||
|
||||
if(!$date) $date = time(); //use current time if none supplied
|
||||
$remote = clientIP(true);
|
||||
$user = $INPUT->server->str('REMOTE_USER');
|
||||
if($sizechange === null) {
|
||||
$sizechange = '';
|
||||
} else {
|
||||
$sizechange = (int) $sizechange;
|
||||
}
|
||||
if (!$date) $date = time(); //use current time if none supplied
|
||||
$remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1';
|
||||
$user = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : '';
|
||||
$sizechange = ($sizechange === null) ? '' : (int)$sizechange;
|
||||
|
||||
$strip = array("\t", "\n");
|
||||
$logline = array(
|
||||
// update changelog file and get the added entry
|
||||
$logEntry = (new MediaChangeLog($id, 1024))->addLogEntry([
|
||||
'date' => $date,
|
||||
'ip' => $remote,
|
||||
'type' => str_replace($strip, '', $type),
|
||||
'type' => $type,
|
||||
'id' => $id,
|
||||
'user' => $user,
|
||||
'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
|
||||
'extra' => str_replace($strip, '', $extra),
|
||||
'sizechange' => $sizechange
|
||||
);
|
||||
|
||||
// add changelog lines
|
||||
$logline = implode("\t", $logline)."\n";
|
||||
io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache
|
||||
io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog
|
||||
'sum' => $summary,
|
||||
'extra' => $extra,
|
||||
'sizechange' => $sizechange,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an array of recently changed files using the
|
||||
* changelog
|
||||
* returns an array of recently changed files using the changelog
|
||||
*
|
||||
* The following constants can be used to control which changes are
|
||||
* included. Add them together as needed.
|
||||
|
@ -211,12 +163,12 @@ function addMediaLogEntry(
|
|||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
function getRecents($first,$num,$ns='',$flags=0){
|
||||
function getRecents($first, $num, $ns = '', $flags = 0) {
|
||||
global $conf;
|
||||
$recent = array();
|
||||
$count = 0;
|
||||
|
||||
if(!$num)
|
||||
if (!$num)
|
||||
return $recent;
|
||||
|
||||
// read all recent changes. (kept short)
|
||||
|
@ -228,7 +180,7 @@ function getRecents($first,$num,$ns='',$flags=0){
|
|||
if (!is_array($lines)) {
|
||||
$lines = array();
|
||||
}
|
||||
$lines_position = count($lines)-1;
|
||||
$lines_position = count($lines) - 1;
|
||||
$media_lines_position = 0;
|
||||
$media_lines = array();
|
||||
|
||||
|
@ -237,13 +189,13 @@ function getRecents($first,$num,$ns='',$flags=0){
|
|||
if (!is_array($media_lines)) {
|
||||
$media_lines = array();
|
||||
}
|
||||
$media_lines_position = count($media_lines)-1;
|
||||
$media_lines_position = count($media_lines) - 1;
|
||||
}
|
||||
|
||||
$seen = array(); // caches seen lines, _handleRecent() skips them
|
||||
|
||||
// handle lines
|
||||
while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) {
|
||||
while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >= 0)) {
|
||||
if (empty($rec) && $lines_position >= 0) {
|
||||
$rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen);
|
||||
if (!$rec) {
|
||||
|
@ -274,11 +226,11 @@ function getRecents($first,$num,$ns='',$flags=0){
|
|||
if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
|
||||
$rec = false;
|
||||
}
|
||||
if(--$first >= 0) continue; // skip first entries
|
||||
if (--$first >= 0) continue; // skip first entries
|
||||
$recent[] = $x;
|
||||
$count++;
|
||||
// break when we have enough entries
|
||||
if($count >= $num){ break; }
|
||||
if ($count >= $num) { break; }
|
||||
}
|
||||
return $recent;
|
||||
}
|
||||
|
@ -305,11 +257,11 @@ function getRecents($first,$num,$ns='',$flags=0){
|
|||
* @author Michael Hamann <michael@content-space.de>
|
||||
* @author Ben Coburn <btcoburn@silicodon.net>
|
||||
*/
|
||||
function getRecentsSince($from,$to=null,$ns='',$flags=0){
|
||||
function getRecentsSince($from, $to = null, $ns = '', $flags = 0) {
|
||||
global $conf;
|
||||
$recent = array();
|
||||
|
||||
if($to && $to < $from)
|
||||
if ($to && $to < $from)
|
||||
return $recent;
|
||||
|
||||
// read all recent changes. (kept short)
|
||||
|
@ -318,7 +270,7 @@ function getRecentsSince($from,$to=null,$ns='',$flags=0){
|
|||
} else {
|
||||
$lines = @file($conf['changelog']);
|
||||
}
|
||||
if(!$lines) return $recent;
|
||||
if (!$lines) return $recent;
|
||||
|
||||
// we start searching at the end of the list
|
||||
$lines = array_reverse($lines);
|
||||
|
@ -326,9 +278,9 @@ function getRecentsSince($from,$to=null,$ns='',$flags=0){
|
|||
// handle lines
|
||||
$seen = array(); // caches seen lines, _handleRecent() skips them
|
||||
|
||||
foreach($lines as $line){
|
||||
foreach ($lines as $line) {
|
||||
$rec = _handleRecent($line, $ns, $flags, $seen);
|
||||
if($rec !== false) {
|
||||
if ($rec !== false) {
|
||||
if ($rec['date'] >= $from) {
|
||||
if (!$to || $rec['date'] <= $to) {
|
||||
$recent[] = $rec;
|
||||
|
@ -357,30 +309,30 @@ function getRecentsSince($from,$to=null,$ns='',$flags=0){
|
|||
* @param array $seen listing of seen pages
|
||||
* @return array|bool false or array with info about a change
|
||||
*/
|
||||
function _handleRecent($line,$ns,$flags,&$seen){
|
||||
if(empty($line)) return false; //skip empty lines
|
||||
function _handleRecent($line, $ns, $flags, &$seen) {
|
||||
if (empty($line)) return false; //skip empty lines
|
||||
|
||||
// split the line into parts
|
||||
$recent = parseChangelogLine($line);
|
||||
if ($recent===false) { return false; }
|
||||
$recent = ChangeLog::parseLogLine($line);
|
||||
if ($recent === false) return false;
|
||||
|
||||
// skip seen ones
|
||||
if(isset($seen[$recent['id']])) return false;
|
||||
if (isset($seen[$recent['id']])) return false;
|
||||
|
||||
// skip changes, of only new items are requested
|
||||
if($recent['type']!==DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false;
|
||||
if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false;
|
||||
|
||||
// skip minors
|
||||
if($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
|
||||
if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
|
||||
|
||||
// remember in seen to skip additional sights
|
||||
$seen[$recent['id']] = 1;
|
||||
|
||||
// check if it's a hidden page
|
||||
if(isHiddenPage($recent['id'])) return false;
|
||||
if (isHiddenPage($recent['id'])) return false;
|
||||
|
||||
// filter namespace
|
||||
if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false;
|
||||
if (($ns) && (strpos($recent['id'], $ns.':') !== 0)) return false;
|
||||
|
||||
// exclude subnamespaces
|
||||
if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
|
||||
|
@ -394,10 +346,11 @@ function _handleRecent($line,$ns,$flags,&$seen){
|
|||
if ($recent['perms'] < AUTH_READ) return false;
|
||||
|
||||
// check existance
|
||||
if($flags & RECENTS_SKIP_DELETED){
|
||||
if ($flags & RECENTS_SKIP_DELETED) {
|
||||
$fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
|
||||
if(!file_exists($fn)) return false;
|
||||
if (!file_exists($fn)) return false;
|
||||
}
|
||||
|
||||
return $recent;
|
||||
}
|
||||
|
212
inc/common.php
212
inc/common.php
|
@ -9,6 +9,8 @@
|
|||
use dokuwiki\Cache\CacheInstructions;
|
||||
use dokuwiki\Cache\CacheRenderer;
|
||||
use dokuwiki\ChangeLog\PageChangeLog;
|
||||
use dokuwiki\File\PageFile;
|
||||
use dokuwiki\Logger;
|
||||
use dokuwiki\Subscriptions\PageSubscriptionSender;
|
||||
use dokuwiki\Subscriptions\SubscriberManager;
|
||||
use dokuwiki\Extension\AuthPlugin;
|
||||
|
@ -215,11 +217,12 @@ function pageinfo() {
|
|||
$info['filepath'] = wikiFN($ID);
|
||||
$info['exists'] = file_exists($info['filepath']);
|
||||
$info['currentrev'] = @filemtime($info['filepath']);
|
||||
if($REV) {
|
||||
|
||||
if ($REV) {
|
||||
//check if current revision was meant
|
||||
if($info['exists'] && ($info['currentrev'] == $REV)) {
|
||||
if ($info['exists'] && ($info['currentrev'] == $REV)) {
|
||||
$REV = '';
|
||||
} elseif($RANGE) {
|
||||
} elseif ($RANGE) {
|
||||
//section editing does not work with old revisions!
|
||||
$REV = '';
|
||||
$RANGE = '';
|
||||
|
@ -231,9 +234,8 @@ function pageinfo() {
|
|||
}
|
||||
}
|
||||
$info['rev'] = $REV;
|
||||
if($info['exists']) {
|
||||
$info['writable'] = (is_writable($info['filepath']) &&
|
||||
($info['perm'] >= AUTH_EDIT));
|
||||
if ($info['exists']) {
|
||||
$info['writable'] = (is_writable($info['filepath']) && $info['perm'] >= AUTH_EDIT);
|
||||
} else {
|
||||
$info['writable'] = ($info['perm'] >= AUTH_CREATE);
|
||||
}
|
||||
|
@ -245,41 +247,37 @@ function pageinfo() {
|
|||
|
||||
//who's the editor
|
||||
$pagelog = new PageChangeLog($ID, 1024);
|
||||
if($REV) {
|
||||
if ($REV) {
|
||||
$revinfo = $pagelog->getRevisionInfo($REV);
|
||||
} else {
|
||||
if(!empty($info['meta']['last_change']) && is_array($info['meta']['last_change'])) {
|
||||
if (!empty($info['meta']['last_change']) && is_array($info['meta']['last_change'])) {
|
||||
$revinfo = $info['meta']['last_change'];
|
||||
} else {
|
||||
$revinfo = $pagelog->getRevisionInfo($info['lastmod']);
|
||||
// cache most recent changelog line in metadata if missing and still valid
|
||||
if($revinfo !== false) {
|
||||
if ($revinfo !== false) {
|
||||
$info['meta']['last_change'] = $revinfo;
|
||||
p_set_metadata($ID, array('last_change' => $revinfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
//and check for an external edit
|
||||
if($revinfo !== false && $revinfo['date'] != $info['lastmod']) {
|
||||
if ($revinfo !== false && $revinfo['date'] != $info['lastmod']) {
|
||||
// cached changelog line no longer valid
|
||||
$revinfo = false;
|
||||
$info['meta']['last_change'] = $revinfo;
|
||||
p_set_metadata($ID, array('last_change' => $revinfo));
|
||||
}
|
||||
|
||||
if($revinfo !== false){
|
||||
if ($revinfo !== false) {
|
||||
$info['ip'] = $revinfo['ip'];
|
||||
$info['user'] = $revinfo['user'];
|
||||
$info['sum'] = $revinfo['sum'];
|
||||
// See also $INFO['meta']['last_change'] which is the most recent log line for page $ID.
|
||||
// Use $INFO['meta']['last_change']['type']===DOKU_CHANGE_TYPE_MINOR_EDIT in place of $info['minor'].
|
||||
|
||||
if($revinfo['user']) {
|
||||
$info['editor'] = $revinfo['user'];
|
||||
} else {
|
||||
$info['editor'] = $revinfo['ip'];
|
||||
}
|
||||
}else{
|
||||
$info['editor'] = $revinfo['user'] ?: $revinfo['ip'];
|
||||
} else {
|
||||
$info['ip'] = null;
|
||||
$info['user'] = null;
|
||||
$info['sum'] = null;
|
||||
|
@ -317,7 +315,7 @@ function jsinfo() {
|
|||
*
|
||||
* @return array with info about current media item
|
||||
*/
|
||||
function mediainfo(){
|
||||
function mediainfo() {
|
||||
global $NS;
|
||||
global $IMG;
|
||||
|
||||
|
@ -1244,45 +1242,11 @@ function con($pre, $text, $suf, $pretty = false) {
|
|||
* wiki, triggered in @see saveWikiText()
|
||||
*
|
||||
* @param string $id the page ID
|
||||
* @deprecated 2021-11-28
|
||||
*/
|
||||
function detectExternalEdit($id) {
|
||||
global $lang;
|
||||
|
||||
$fileLastMod = wikiFN($id);
|
||||
$lastMod = @filemtime($fileLastMod); // from page
|
||||
$pagelog = new PageChangeLog($id, 1024);
|
||||
$lastRev = $pagelog->getRevisions(-1, 1); // from changelog
|
||||
$lastRev = (int) (empty($lastRev) ? 0 : $lastRev[0]);
|
||||
|
||||
if(!file_exists(wikiFN($id, $lastMod)) && file_exists($fileLastMod) && $lastMod >= $lastRev) {
|
||||
// add old revision to the attic if missing
|
||||
saveOldRevision($id);
|
||||
// add a changelog entry if this edit came from outside dokuwiki
|
||||
if($lastMod > $lastRev) {
|
||||
$fileLastRev = wikiFN($id, $lastRev);
|
||||
$revinfo = $pagelog->getRevisionInfo($lastRev);
|
||||
if(empty($lastRev) || !file_exists($fileLastRev) || $revinfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
$filesize_old = 0;
|
||||
} else {
|
||||
$filesize_old = io_getSizeFile($fileLastRev);
|
||||
}
|
||||
$filesize_new = filesize($fileLastMod);
|
||||
$sizechange = $filesize_new - $filesize_old;
|
||||
|
||||
addLogEntry(
|
||||
$lastMod,
|
||||
$id,
|
||||
DOKU_CHANGE_TYPE_EDIT,
|
||||
$lang['external_edit'],
|
||||
'',
|
||||
array('ExternalEdit' => true),
|
||||
$sizechange
|
||||
);
|
||||
// remove soon to be stale instructions
|
||||
$cache = new CacheInstructions($id, $fileLastMod);
|
||||
$cache->removeCache();
|
||||
}
|
||||
}
|
||||
dbg_deprecated(\dokuwiki\File\PageFile::class .'::detectExternalEdit()');
|
||||
(new PageFile($id))->detectExternalEdit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1298,117 +1262,19 @@ function detectExternalEdit($id) {
|
|||
* @param bool $minor mark this saved version as minor update
|
||||
*/
|
||||
function saveWikiText($id, $text, $summary, $minor = false) {
|
||||
/* Note to developers:
|
||||
This code is subtle and delicate. Test the behavior of
|
||||
the attic and changelog with dokuwiki and external edits
|
||||
after any changes. External edits change the wiki page
|
||||
directly without using php or dokuwiki.
|
||||
*/
|
||||
global $conf;
|
||||
global $lang;
|
||||
global $REV;
|
||||
/* @var Input $INPUT */
|
||||
global $INPUT;
|
||||
|
||||
// prepare data for event
|
||||
$svdta = array();
|
||||
$svdta['id'] = $id;
|
||||
$svdta['file'] = wikiFN($id);
|
||||
$svdta['revertFrom'] = $REV;
|
||||
$svdta['oldRevision'] = @filemtime($svdta['file']);
|
||||
$svdta['newRevision'] = 0;
|
||||
$svdta['newContent'] = $text;
|
||||
$svdta['oldContent'] = rawWiki($id);
|
||||
$svdta['summary'] = $summary;
|
||||
$svdta['contentChanged'] = ($svdta['newContent'] != $svdta['oldContent']);
|
||||
$svdta['changeInfo'] = '';
|
||||
$svdta['changeType'] = DOKU_CHANGE_TYPE_EDIT;
|
||||
$svdta['sizechange'] = null;
|
||||
|
||||
// select changelog line type
|
||||
if($REV) {
|
||||
$svdta['changeType'] = DOKU_CHANGE_TYPE_REVERT;
|
||||
$svdta['changeInfo'] = $REV;
|
||||
} else if(!file_exists($svdta['file'])) {
|
||||
$svdta['changeType'] = DOKU_CHANGE_TYPE_CREATE;
|
||||
} else if(trim($text) == '') {
|
||||
// empty or whitespace only content deletes
|
||||
$svdta['changeType'] = DOKU_CHANGE_TYPE_DELETE;
|
||||
// autoset summary on deletion
|
||||
if(blank($svdta['summary'])) {
|
||||
$svdta['summary'] = $lang['deleted'];
|
||||
}
|
||||
} else if($minor && $conf['useacl'] && $INPUT->server->str('REMOTE_USER')) {
|
||||
//minor edits only for logged in users
|
||||
$svdta['changeType'] = DOKU_CHANGE_TYPE_MINOR_EDIT;
|
||||
}
|
||||
|
||||
$event = new Event('COMMON_WIKIPAGE_SAVE', $svdta);
|
||||
if(!$event->advise_before()) return;
|
||||
|
||||
// if the content has not been changed, no save happens (plugins may override this)
|
||||
if(!$svdta['contentChanged']) return;
|
||||
|
||||
detectExternalEdit($id);
|
||||
|
||||
if(
|
||||
$svdta['changeType'] == DOKU_CHANGE_TYPE_CREATE ||
|
||||
($svdta['changeType'] == DOKU_CHANGE_TYPE_REVERT && !file_exists($svdta['file']))
|
||||
) {
|
||||
$filesize_old = 0;
|
||||
} else {
|
||||
$filesize_old = filesize($svdta['file']);
|
||||
}
|
||||
if($svdta['changeType'] == DOKU_CHANGE_TYPE_DELETE) {
|
||||
// Send "update" event with empty data, so plugins can react to page deletion
|
||||
$data = array(array($svdta['file'], '', false), getNS($id), noNS($id), false);
|
||||
Event::createAndTrigger('IO_WIKIPAGE_WRITE', $data);
|
||||
// pre-save deleted revision
|
||||
@touch($svdta['file']);
|
||||
clearstatcache();
|
||||
$svdta['newRevision'] = saveOldRevision($id);
|
||||
// remove empty file
|
||||
@unlink($svdta['file']);
|
||||
$filesize_new = 0;
|
||||
// don't remove old meta info as it should be saved, plugins can use
|
||||
// IO_WIKIPAGE_WRITE for removing their metadata...
|
||||
// purge non-persistant meta data
|
||||
p_purge_metadata($id);
|
||||
// remove empty namespaces
|
||||
io_sweepNS($id, 'datadir');
|
||||
io_sweepNS($id, 'mediadir');
|
||||
} else {
|
||||
// save file (namespace dir is created in io_writeWikiPage)
|
||||
io_writeWikiPage($svdta['file'], $svdta['newContent'], $id);
|
||||
// pre-save the revision, to keep the attic in sync
|
||||
$svdta['newRevision'] = saveOldRevision($id);
|
||||
$filesize_new = filesize($svdta['file']);
|
||||
}
|
||||
$svdta['sizechange'] = $filesize_new - $filesize_old;
|
||||
|
||||
$event->advise_after();
|
||||
|
||||
addLogEntry(
|
||||
$svdta['newRevision'],
|
||||
$svdta['id'],
|
||||
$svdta['changeType'],
|
||||
$svdta['summary'],
|
||||
$svdta['changeInfo'],
|
||||
null,
|
||||
$svdta['sizechange']
|
||||
);
|
||||
// get COMMON_WIKIPAGE_SAVE event data
|
||||
$data = (new PageFile($id))->saveWikiText($text, $summary, $minor);
|
||||
|
||||
// send notify mails
|
||||
notify($svdta['id'], 'admin', $svdta['oldRevision'], $svdta['summary'], $minor, $svdta['newRevision']);
|
||||
notify($svdta['id'], 'subscribers', $svdta['oldRevision'], $svdta['summary'], $minor, $svdta['newRevision']);
|
||||
|
||||
// update the purgefile (timestamp of the last time anything within the wiki was changed)
|
||||
io_saveFile($conf['cachedir'].'/purgefile', time());
|
||||
list('oldRevision' => $rev, 'newRevision' => $new_rev, 'summary' => $summary) = $data;
|
||||
notify($id, 'admin', $rev, $summary, $minor, $new_rev);
|
||||
notify($id, 'subscribers', $rev, $summary, $minor, $new_rev);
|
||||
|
||||
// if useheading is enabled, purge the cache of all linking pages
|
||||
if(useHeading('content')) {
|
||||
if (useHeading('content')) {
|
||||
$pages = ft_backlinks($id, true);
|
||||
foreach($pages as $page) {
|
||||
foreach ($pages as $page) {
|
||||
$cache = new CacheRenderer($page, wikiFN($page), 'xhtml');
|
||||
$cache->removeCache();
|
||||
}
|
||||
|
@ -1416,21 +1282,17 @@ function saveWikiText($id, $text, $summary, $minor = false) {
|
|||
}
|
||||
|
||||
/**
|
||||
* moves the current version to the attic and returns its
|
||||
* revision date
|
||||
* moves the current version to the attic and returns its revision date
|
||||
*
|
||||
* @author Andreas Gohr <andi@splitbrain.org>
|
||||
*
|
||||
* @param string $id page id
|
||||
* @return int|string revision timestamp
|
||||
* @deprecated 2021-11-28
|
||||
*/
|
||||
function saveOldRevision($id) {
|
||||
$oldf = wikiFN($id);
|
||||
if(!file_exists($oldf)) return '';
|
||||
$date = filemtime($oldf);
|
||||
$newf = wikiFN($id, $date);
|
||||
io_writeWikiPage($newf, rawWiki($id), $id, $date);
|
||||
return $date;
|
||||
dbg_deprecated(\dokuwiki\File\PageFile::class .'::saveOldRevision()');
|
||||
return (new PageFile($id))->saveOldRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1438,7 +1300,7 @@ function saveOldRevision($id) {
|
|||
*
|
||||
* @param string $id The changed page
|
||||
* @param string $who Who to notify (admin|subscribers|register)
|
||||
* @param int|string $rev Old page revision
|
||||
* @param int|string $rev Old page revision
|
||||
* @param string $summary What changed
|
||||
* @param boolean $minor Is this a minor edit?
|
||||
* @param string[] $replace Additional string substitutions, @KEY@ to be replaced by value
|
||||
|
@ -1453,20 +1315,20 @@ function notify($id, $who, $rev = '', $summary = '', $minor = false, $replace =
|
|||
global $INPUT;
|
||||
|
||||
// decide if there is something to do, eg. whom to mail
|
||||
if($who == 'admin') {
|
||||
if(empty($conf['notify'])) return false; //notify enabled?
|
||||
if ($who == 'admin') {
|
||||
if (empty($conf['notify'])) return false; //notify enabled?
|
||||
$tpl = 'mailtext';
|
||||
$to = $conf['notify'];
|
||||
} elseif($who == 'subscribers') {
|
||||
if(!actionOK('subscribe')) return false; //subscribers enabled?
|
||||
if($conf['useacl'] && $INPUT->server->str('REMOTE_USER') && $minor) return false; //skip minors
|
||||
} elseif ($who == 'subscribers') {
|
||||
if (!actionOK('subscribe')) return false; //subscribers enabled?
|
||||
if ($conf['useacl'] && $INPUT->server->str('REMOTE_USER') && $minor) return false; //skip minors
|
||||
$data = array('id' => $id, 'addresslist' => '', 'self' => false, 'replacements' => $replace);
|
||||
Event::createAndTrigger(
|
||||
'COMMON_NOTIFY_ADDRESSLIST', $data,
|
||||
array(new SubscriberManager(), 'notifyAddresses')
|
||||
);
|
||||
$to = $data['addresslist'];
|
||||
if(empty($to)) return false;
|
||||
if (empty($to)) return false;
|
||||
$tpl = 'subscr_single';
|
||||
} else {
|
||||
return false; //just to be safe
|
||||
|
|
35
inc/html.php
35
inc/html.php
|
@ -297,12 +297,17 @@ function html_locked() {
|
|||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*
|
||||
* @param int $first skip the first n changelog lines
|
||||
* @param bool|string $media_id id of media, or false for current page
|
||||
* @param string $media_id id of media, or empty for current page
|
||||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_revisions($first=0, $media_id = false) {
|
||||
dbg_deprecated(\dokuwiki\Ui\Revisions::class .'::show()');
|
||||
(new dokuwiki\Ui\Revisions($first, $media_id))->show();
|
||||
function html_revisions($first = 0, $media_id = '') {
|
||||
dbg_deprecated(\dokuwiki\Ui\PageRevisions::class .'::show()');
|
||||
if ($media_id) {
|
||||
(new dokuwiki\Ui\MediaRevisions($media_id))->show($first);
|
||||
} else {
|
||||
global $INFO;
|
||||
(new dokuwiki\Ui\PageRevisions($INFO['id']))->show($first);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -494,7 +499,7 @@ function html_backlinks() {
|
|||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_diff_head($l_rev, $r_rev, $id = null, $media = false, $inline = false) {
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\Diff::class .'::diffHead()');
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\PageDiff::class .'::buildDiffHead()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -509,8 +514,12 @@ function html_diff_head($l_rev, $r_rev, $id = null, $media = false, $inline = fa
|
|||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_diff($text = '', $intro = true, $type = null) {
|
||||
dbg_deprecated(\dokuwiki\Ui\Diff::class .'::show()');
|
||||
(new dokuwiki\Ui\Diff($text, $intro, $type))->show();
|
||||
dbg_deprecated(\dokuwiki\Ui\PageDiff::class .'::show()');
|
||||
global $INFO;
|
||||
(new dokuwiki\Ui\PageDiff($INFO['id']))->compareWith($text)->preference([
|
||||
'showIntro' => $intro,
|
||||
'difftype' => $type,
|
||||
])->show();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -524,7 +533,7 @@ function html_diff($text = '', $intro = true, $type = null) {
|
|||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_diff_navigation($pagelog, $type, $l_rev, $r_rev) {
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\Diff::class .'::diffNavigation()');
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\PageDiff::class .'::buildRevisionsNavigation()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -538,7 +547,7 @@ function html_diff_navigation($pagelog, $type, $l_rev, $r_rev) {
|
|||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_diff_navigationlink($difftype, $linktype, $lrev, $rrev = null) {
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\Diff::class .'::diffViewlink()');
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\PageDiff::class .'::diffViewlink()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -549,8 +558,8 @@ function html_diff_navigationlink($difftype, $linktype, $lrev, $rrev = null) {
|
|||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_insert_softbreaks($diffhtml) {
|
||||
dbg_deprecated(\dokuwiki\Ui\Diff::class .'::insertSoftbreaks()');
|
||||
return (new dokuwiki\Ui\Diff())->insertSoftbreaks($diffhtml);
|
||||
dbg_deprecated(\dokuwiki\Ui\PageDiff::class .'::insertSoftbreaks()');
|
||||
return (new dokuwiki\Ui\PageDiff)->insertSoftbreaks($diffhtml);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -563,8 +572,8 @@ function html_insert_softbreaks($diffhtml) {
|
|||
* @deprecated 2020-07-18
|
||||
*/
|
||||
function html_conflict($text, $summary) {
|
||||
dbg_deprecated(\dokuwiki\Ui\Conflict::class .'::show()');
|
||||
(new dokuwiki\Ui\Conflict($text, $summary))->show();
|
||||
dbg_deprecated(\dokuwiki\Ui\PageConflict::class .'::show()');
|
||||
(new dokuwiki\Ui\PageConflict($text, $summary))->show();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -226,6 +226,7 @@ $lang['created'] = 'created';
|
|||
$lang['restored'] = 'old revision restored (%s)';
|
||||
$lang['external_edit'] = 'external edit';
|
||||
$lang['summary'] = 'Edit summary';
|
||||
$lang['unknowndate'] = 'Unknown date';
|
||||
$lang['noflash'] = 'The <a href="http://get.adobe.com/flashplayer">Adobe Flash Plugin</a> is needed to display this content.';
|
||||
$lang['download'] = 'Download Snippet';
|
||||
$lang['tools'] = 'Tools';
|
||||
|
|
|
@ -199,7 +199,7 @@ $lang['current'] = '現在';
|
|||
$lang['yours'] = 'あなたのバージョン';
|
||||
$lang['diff'] = '現在のリビジョンとの差分を表示';
|
||||
$lang['diff2'] = '選択したリビジョン間の差分を表示';
|
||||
$lang['difflink'] = 'この比較画面にリンクする';
|
||||
$lang['difflink'] = 'この比較画面へのリンク';
|
||||
$lang['diff_type'] = '差分の表示方法:';
|
||||
$lang['diff_inline'] = 'インライン';
|
||||
$lang['diff_side'] = '横に並べる';
|
||||
|
|
260
inc/media.php
260
inc/media.php
|
@ -567,11 +567,11 @@ function media_upload_finish($fn_tmp, $fn, $id, $imime, $overwrite, $move = 'mov
|
|||
* @param string $id
|
||||
* @return int - revision date
|
||||
*/
|
||||
function media_saveOldRevision($id){
|
||||
function media_saveOldRevision($id) {
|
||||
global $conf, $lang;
|
||||
|
||||
$oldf = mediaFN($id);
|
||||
if(!file_exists($oldf)) return '';
|
||||
if (!file_exists($oldf)) return '';
|
||||
$date = filemtime($oldf);
|
||||
if (!$conf['mediarevisions']) return $date;
|
||||
|
||||
|
@ -592,9 +592,9 @@ function media_saveOldRevision($id){
|
|||
}
|
||||
}
|
||||
|
||||
$newf = mediaFN($id,$date);
|
||||
$newf = mediaFN($id, $date);
|
||||
io_makeFileDir($newf);
|
||||
if(copy($oldf, $newf)) {
|
||||
if (copy($oldf, $newf)) {
|
||||
// Set the correct permission here.
|
||||
// Always chmod media because they may be saved with different permissions than expected from the php umask.
|
||||
// (Should normally chmod to $conf['fperm'] only if $conf['fperm'] is set.)
|
||||
|
@ -997,10 +997,10 @@ function media_tab_history($image, $ns, $auth=null) {
|
|||
|
||||
if ($auth >= AUTH_READ && $image) {
|
||||
if ($do == 'diff'){
|
||||
media_diff($image, $ns, $auth);
|
||||
(new dokuwiki\Ui\MediaDiff($image))->show(); //media_diff($image, $ns, $auth);
|
||||
} else {
|
||||
$first = $INPUT->int('first');
|
||||
html_revisions($first, $image);
|
||||
(new dokuwiki\Ui\MediaRevisions($image))->show($first);
|
||||
}
|
||||
} else {
|
||||
echo '<div class="nothing">'.$lang['media_perm_read'].'</div>'.NL;
|
||||
|
@ -1017,7 +1017,7 @@ function media_tab_history($image, $ns, $auth=null) {
|
|||
*
|
||||
* @author Kate Arzamastseva <pshns@ukr.net>
|
||||
*/
|
||||
function media_preview($image, $auth, $rev='', $meta=false) {
|
||||
function media_preview($image, $auth, $rev = '', $meta = false) {
|
||||
|
||||
$size = media_image_preview_size($image, $rev, $meta);
|
||||
|
||||
|
@ -1041,7 +1041,7 @@ function media_preview($image, $auth, $rev='', $meta=false) {
|
|||
echo '<img src="'.$src.'" alt="" style="max-width: '.$size[0].'px;" />';
|
||||
echo '</a>';
|
||||
|
||||
echo '</div>'.NL;
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1052,12 +1052,12 @@ function media_preview($image, $auth, $rev='', $meta=false) {
|
|||
*
|
||||
* @param string $image media id
|
||||
* @param int $auth permission level
|
||||
* @param string|int $rev revision timestamp, or empty string
|
||||
* @param int|string $rev revision timestamp, or empty string
|
||||
*/
|
||||
function media_preview_buttons($image, $auth, $rev = '') {
|
||||
global $lang, $conf;
|
||||
|
||||
echo '<ul class="actions">'.DOKU_LF;
|
||||
echo '<ul class="actions">';
|
||||
|
||||
if ($auth >= AUTH_DELETE && !$rev && file_exists(mediaFN($image))) {
|
||||
|
||||
|
@ -1071,7 +1071,7 @@ function media_preview_buttons($image, $auth, $rev = '') {
|
|||
$form->addTagClose('div');
|
||||
echo '<li>';
|
||||
echo $form->toHTML();
|
||||
echo '</li>'.DOKU_LF;
|
||||
echo '</li>';
|
||||
}
|
||||
|
||||
$auth_ow = (($conf['mediarevisions']) ? AUTH_UPLOAD : AUTH_DELETE);
|
||||
|
@ -1087,7 +1087,7 @@ function media_preview_buttons($image, $auth, $rev = '') {
|
|||
$form->addTagClose('div');
|
||||
echo '<li>';
|
||||
echo $form->toHTML();
|
||||
echo '</li>'.DOKU_LF;
|
||||
echo '</li>';
|
||||
}
|
||||
|
||||
if ($auth >= AUTH_UPLOAD && $rev && $conf['mediarevisions'] && file_exists(mediaFN($image, $rev))) {
|
||||
|
@ -1104,10 +1104,10 @@ function media_preview_buttons($image, $auth, $rev = '') {
|
|||
$form->addTagClose('div');
|
||||
echo '<li>';
|
||||
echo $form->toHTML();
|
||||
echo '</li>'.DOKU_LF;
|
||||
echo '</li>';
|
||||
}
|
||||
|
||||
echo '</ul>'.DOKU_LF;
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1118,16 +1118,18 @@ function media_preview_buttons($image, $auth, $rev = '') {
|
|||
* @param int|string $rev
|
||||
* @param JpegMeta|bool $meta
|
||||
* @param int $size
|
||||
* @return array|false
|
||||
* @return array
|
||||
*/
|
||||
function media_image_preview_size($image, $rev, $meta, $size = 500) {
|
||||
if (!preg_match("/\.(jpe?g|gif|png)$/", $image) || !file_exists(mediaFN($image, $rev))) return false;
|
||||
function media_image_preview_size($image, $rev, $meta = false, $size = 500) {
|
||||
if (!preg_match("/\.(jpe?g|gif|png)$/", $image)
|
||||
|| !file_exists($filename = mediaFN($image, $rev))
|
||||
) return array();
|
||||
|
||||
$info = getimagesize(mediaFN($image, $rev));
|
||||
$info = getimagesize($filename);
|
||||
$w = (int) $info[0];
|
||||
$h = (int) $info[1];
|
||||
|
||||
if($meta && ($w > $size || $h > $size)){
|
||||
if ($meta && ($w > $size || $h > $size)) {
|
||||
$ratio = $meta->getResizeRatio($size, $size);
|
||||
$w = floor($w * $ratio);
|
||||
$h = floor($h * $ratio);
|
||||
|
@ -1145,10 +1147,10 @@ function media_image_preview_size($image, $rev, $meta, $size = 500) {
|
|||
* @param string $alt alternative value
|
||||
* @return string
|
||||
*/
|
||||
function media_getTag($tags,$meta,$alt=''){
|
||||
if($meta === false) return $alt;
|
||||
function media_getTag($tags, $meta = false, $alt = '') {
|
||||
if (!$meta) return $alt;
|
||||
$info = $meta->getField($tags);
|
||||
if($info == false) return $alt;
|
||||
if (!$info) return $alt;
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
@ -1163,19 +1165,19 @@ function media_getTag($tags,$meta,$alt=''){
|
|||
function media_file_tags($meta) {
|
||||
// load the field descriptions
|
||||
static $fields = null;
|
||||
if(is_null($fields)){
|
||||
if (is_null($fields)) {
|
||||
$config_files = getConfigFiles('mediameta');
|
||||
foreach ($config_files as $config_file) {
|
||||
if(file_exists($config_file)) include($config_file);
|
||||
if (file_exists($config_file)) include($config_file);
|
||||
}
|
||||
}
|
||||
|
||||
$tags = array();
|
||||
|
||||
foreach($fields as $key => $tag){
|
||||
foreach ($fields as $key => $tag) {
|
||||
$t = array();
|
||||
if (!empty($tag[0])) $t = array($tag[0]);
|
||||
if(isset($tag[3]) && is_array($tag[3])) $t = array_merge($t,$tag[3]);
|
||||
if (isset($tag[3]) && is_array($tag[3])) $t = array_merge($t,$tag[3]);
|
||||
$value = media_getTag($t, $meta);
|
||||
$tags[] = array('tag' => $tag, 'value' => $value);
|
||||
}
|
||||
|
@ -1234,65 +1236,10 @@ function media_details($image, $auth, $rev='', $meta=false) {
|
|||
* @param int $auth permission level
|
||||
* @param bool $fromajax
|
||||
* @return false|null|string
|
||||
* @deprecated 2020-12-31
|
||||
*/
|
||||
function media_diff($image, $ns, $auth, $fromajax = false) {
|
||||
global $conf;
|
||||
global $INPUT;
|
||||
|
||||
if ($auth < AUTH_READ || !$image || !$conf['mediarevisions']) return '';
|
||||
|
||||
$rev1 = $INPUT->int('rev');
|
||||
|
||||
$rev2 = $INPUT->ref('rev2');
|
||||
if(is_array($rev2)){
|
||||
$rev1 = (int) $rev2[0];
|
||||
$rev2 = (int) $rev2[1];
|
||||
|
||||
if(!$rev1){
|
||||
$rev1 = $rev2;
|
||||
unset($rev2);
|
||||
}
|
||||
}else{
|
||||
$rev2 = $INPUT->int('rev2');
|
||||
}
|
||||
|
||||
if ($rev1 && !file_exists(mediaFN($image, $rev1))) $rev1 = false;
|
||||
if ($rev2 && !file_exists(mediaFN($image, $rev2))) $rev2 = false;
|
||||
|
||||
if($rev1 && $rev2){ // two specific revisions wanted
|
||||
// make sure order is correct (older on the left)
|
||||
if($rev1 < $rev2){
|
||||
$l_rev = $rev1;
|
||||
$r_rev = $rev2;
|
||||
}else{
|
||||
$l_rev = $rev2;
|
||||
$r_rev = $rev1;
|
||||
}
|
||||
}elseif($rev1){ // single revision given, compare to current
|
||||
$r_rev = '';
|
||||
$l_rev = $rev1;
|
||||
}else{ // no revision was given, compare previous to current
|
||||
$r_rev = '';
|
||||
$medialog = new MediaChangeLog($image);
|
||||
$revs = $medialog->getRevisions(0, 1);
|
||||
if (file_exists(mediaFN($image, $revs[0]))) {
|
||||
$l_rev = $revs[0];
|
||||
} else {
|
||||
$l_rev = '';
|
||||
}
|
||||
}
|
||||
|
||||
// prepare event data
|
||||
$data = array();
|
||||
$data[0] = $image;
|
||||
$data[1] = $l_rev;
|
||||
$data[2] = $r_rev;
|
||||
$data[3] = $ns;
|
||||
$data[4] = $auth;
|
||||
$data[5] = $fromajax;
|
||||
|
||||
// trigger event
|
||||
return Event::createAndTrigger('MEDIA_DIFF', $data, '_media_file_diff', true);
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\MediaDiff::class .'::show()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1300,13 +1247,10 @@ function media_diff($image, $ns, $auth, $fromajax = false) {
|
|||
*
|
||||
* @param array $data event data
|
||||
* @return false|null
|
||||
* @deprecated 2020-12-31
|
||||
*/
|
||||
function _media_file_diff($data) {
|
||||
if(is_array($data) && count($data)===6) {
|
||||
media_file_diff($data[0], $data[1], $data[2], $data[3], $data[4], $data[5]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\MediaDiff::class .'::show()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1320,120 +1264,10 @@ function _media_file_diff($data) {
|
|||
* @param string $ns
|
||||
* @param int $auth permission level
|
||||
* @param bool $fromajax
|
||||
* @deprecated 2020-12-31
|
||||
*/
|
||||
function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax) {
|
||||
global $lang;
|
||||
global $INPUT;
|
||||
|
||||
$l_meta = new JpegMeta(mediaFN($image, $l_rev));
|
||||
$r_meta = new JpegMeta(mediaFN($image, $r_rev));
|
||||
|
||||
$is_img = preg_match('/\.(jpe?g|gif|png)$/', $image);
|
||||
if ($is_img) {
|
||||
$l_size = media_image_preview_size($image, $l_rev, $l_meta);
|
||||
$r_size = media_image_preview_size($image, $r_rev, $r_meta);
|
||||
$is_img = ($l_size && $r_size && ($l_size[0] >= 30 || $r_size[0] >= 30));
|
||||
|
||||
$difftype = $INPUT->str('difftype');
|
||||
|
||||
if (!$fromajax) {
|
||||
$form = new Form([
|
||||
'id' => 'mediamanager__form_diffview',
|
||||
'action' => media_managerURL([], '&'),
|
||||
'method' => 'get',
|
||||
'class' => 'diffView',
|
||||
]);
|
||||
$form->addTagOpen('div')->addClass('no');
|
||||
$form->setHiddenField('sectok', null);
|
||||
$form->setHiddenField('mediado', 'diff');
|
||||
$form->setHiddenField('rev2[0]', $l_rev);
|
||||
$form->setHiddenField('rev2[1]', $r_rev);
|
||||
echo $form->toHTML();
|
||||
|
||||
echo NL.'<div id="mediamanager__diff" >'.NL;
|
||||
}
|
||||
|
||||
if ($difftype == 'opacity' || $difftype == 'portions') {
|
||||
media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $difftype);
|
||||
if (!$fromajax) echo '</div>';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
list($l_head, $r_head) = (new dokuwiki\Ui\Diff)->diffHead($l_rev, $r_rev, $image, true);
|
||||
|
||||
?>
|
||||
<div class="table">
|
||||
<table>
|
||||
<tr>
|
||||
<th><?php echo $l_head; ?></th>
|
||||
<th><?php echo $r_head; ?></th>
|
||||
</tr>
|
||||
<?php
|
||||
|
||||
echo '<tr class="image">';
|
||||
echo '<td>';
|
||||
media_preview($image, $auth, $l_rev, $l_meta);
|
||||
echo '</td>';
|
||||
|
||||
echo '<td>';
|
||||
media_preview($image, $auth, $r_rev, $r_meta);
|
||||
echo '</td>';
|
||||
echo '</tr>'.NL;
|
||||
|
||||
echo '<tr class="actions">';
|
||||
echo '<td>';
|
||||
media_preview_buttons($image, $auth, $l_rev);
|
||||
echo '</td>';
|
||||
|
||||
echo '<td>';
|
||||
media_preview_buttons($image, $auth, $r_rev);
|
||||
echo '</td>';
|
||||
echo '</tr>'.NL;
|
||||
|
||||
$l_tags = media_file_tags($l_meta);
|
||||
$r_tags = media_file_tags($r_meta);
|
||||
// FIXME r_tags-only stuff
|
||||
foreach ($l_tags as $key => $l_tag) {
|
||||
if ($l_tag['value'] != $r_tags[$key]['value']) {
|
||||
$r_tags[$key]['highlighted'] = true;
|
||||
$l_tags[$key]['highlighted'] = true;
|
||||
} else if (!$l_tag['value'] || !$r_tags[$key]['value']) {
|
||||
unset($r_tags[$key]);
|
||||
unset($l_tags[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
echo '<tr>';
|
||||
foreach(array($l_tags,$r_tags) as $tags){
|
||||
echo '<td>'.NL;
|
||||
|
||||
echo '<dl class="img_tags">';
|
||||
foreach($tags as $tag){
|
||||
$value = cleanText($tag['value']);
|
||||
if (!$value) $value = '-';
|
||||
echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
|
||||
echo '<dd>';
|
||||
if ($tag['highlighted']) {
|
||||
echo '<strong>';
|
||||
}
|
||||
if ($tag['tag'][2] == 'date') echo dformat($value);
|
||||
else echo hsc($value);
|
||||
if ($tag['highlighted']) {
|
||||
echo '</strong>';
|
||||
}
|
||||
echo '</dd>';
|
||||
}
|
||||
echo '</dl>'.NL;
|
||||
|
||||
echo '</td>';
|
||||
}
|
||||
echo '</tr>'.NL;
|
||||
|
||||
echo '</table>'.NL;
|
||||
echo '</div>'.NL;
|
||||
|
||||
if ($is_img && !$fromajax) echo '</div>';
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\MediaDiff::class .'::showFileDiff()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1448,32 +1282,10 @@ function media_file_diff($image, $l_rev, $r_rev, $ns, $auth, $fromajax) {
|
|||
* @param array $l_size array with width and height
|
||||
* @param array $r_size array with width and height
|
||||
* @param string $type
|
||||
* @deprecated 2020-12-31
|
||||
*/
|
||||
function media_image_diff($image, $l_rev, $r_rev, $l_size, $r_size, $type) {
|
||||
if ($l_size != $r_size) {
|
||||
if ($r_size[0] > $l_size[0]) {
|
||||
$l_size = $r_size;
|
||||
}
|
||||
}
|
||||
|
||||
$l_more = array('rev' => $l_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
|
||||
$r_more = array('rev' => $r_rev, 'h' => $l_size[1], 'w' => $l_size[0]);
|
||||
|
||||
$l_src = ml($image, $l_more);
|
||||
$r_src = ml($image, $r_more);
|
||||
|
||||
// slider
|
||||
echo '<div class="slider" style="max-width: '.($l_size[0]-20).'px;" ></div>'.NL;
|
||||
|
||||
// two images in divs
|
||||
echo '<div class="imageDiff ' . $type . '">'.NL;
|
||||
echo '<div class="image1" style="max-width: '.$l_size[0].'px;">';
|
||||
echo '<img src="'.$l_src.'" alt="" />';
|
||||
echo '</div>'.NL;
|
||||
echo '<div class="image2" style="max-width: '.$l_size[0].'px;">';
|
||||
echo '<img src="'.$r_src.'" alt="" />';
|
||||
echo '</div>'.NL;
|
||||
echo '</div>'.NL;
|
||||
dbg_deprecated('see '. \dokuwiki\Ui\MediaDiff::class .'::showImageDiff()');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -266,14 +266,14 @@ function sectionID($title,&$check) {
|
|||
* @param bool $date_at
|
||||
* @return bool exists?
|
||||
*/
|
||||
function page_exists($id,$rev='',$clean=true, $date_at=false) {
|
||||
if($rev !== '' && $date_at) {
|
||||
function page_exists($id, $rev = '', $clean = true, $date_at = false) {
|
||||
if ($rev !== '' && $date_at) {
|
||||
$pagelog = new PageChangeLog($id);
|
||||
$pagelog_rev = $pagelog->getLastRevisionAt($rev);
|
||||
if($pagelog_rev !== false)
|
||||
if ($pagelog_rev !== false)
|
||||
$rev = $pagelog_rev;
|
||||
}
|
||||
return file_exists(wikiFN($id,$rev,$clean));
|
||||
return file_exists(wikiFN($id, $rev, $clean));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -508,18 +508,17 @@ function resolve_id($ns,$id,$clean=true){
|
|||
* @param int|string $rev
|
||||
* @param bool $date_at
|
||||
*/
|
||||
function resolve_mediaid($ns,&$page,&$exists,$rev='',$date_at=false){
|
||||
$page = resolve_id($ns,$page);
|
||||
if($rev !== '' && $date_at){
|
||||
function resolve_mediaid($ns, &$page, &$exists, $rev = '', $date_at = false) {
|
||||
$page = resolve_id($ns, $page);
|
||||
if ($rev !== '' && $date_at) {
|
||||
$medialog = new MediaChangeLog($page);
|
||||
$medialog_rev = $medialog->getLastRevisionAt($rev);
|
||||
if($medialog_rev !== false) {
|
||||
if ($medialog_rev !== false) {
|
||||
$rev = $medialog_rev;
|
||||
}
|
||||
}
|
||||
|
||||
$file = mediaFN($page,$rev);
|
||||
$exists = file_exists($file);
|
||||
$exists = file_exists(mediaFN($page, $rev));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -533,7 +532,7 @@ function resolve_mediaid($ns,&$page,&$exists,$rev='',$date_at=false){
|
|||
* @param string $rev
|
||||
* @param bool $date_at
|
||||
*/
|
||||
function resolve_pageid($ns,&$page,&$exists,$rev='',$date_at=false ){
|
||||
function resolve_pageid($ns, &$page, &$exists, $rev = '', $date_at = false) {
|
||||
global $conf;
|
||||
global $ID;
|
||||
$exists = false;
|
||||
|
@ -545,56 +544,57 @@ function resolve_pageid($ns,&$page,&$exists,$rev='',$date_at=false ){
|
|||
|
||||
//keep hashlink if exists then clean both parts
|
||||
if (strpos($page,'#')) {
|
||||
list($page,$hash) = explode('#',$page,2);
|
||||
list($page,$hash) = explode('#', $page, 2);
|
||||
} else {
|
||||
$hash = '';
|
||||
}
|
||||
$hash = cleanID($hash);
|
||||
$page = resolve_id($ns,$page,false); // resolve but don't clean, yet
|
||||
$page = resolve_id($ns, $page, false); // resolve but don't clean, yet
|
||||
|
||||
// get filename (calls clean itself)
|
||||
if($rev !== '' && $date_at) {
|
||||
if ($rev !== '' && $date_at) {
|
||||
$pagelog = new PageChangeLog($page);
|
||||
$pagelog_rev = $pagelog->getLastRevisionAt($rev);
|
||||
if($pagelog_rev !== false)//something found
|
||||
$rev = $pagelog_rev;
|
||||
if ($pagelog_rev !== false)//something found
|
||||
$rev = $pagelog_rev;
|
||||
}
|
||||
$file = wikiFN($page,$rev);
|
||||
|
||||
// if ends with colon or slash we have a namespace link
|
||||
if(in_array(substr($page,-1), array(':', ';')) ||
|
||||
($conf['useslash'] && substr($page,-1) == '/')){
|
||||
if(page_exists($page.$conf['start'],$rev,true,$date_at)){
|
||||
if (in_array(substr($page, -1), array(':', ';')) ||
|
||||
($conf['useslash'] && substr($page,-1) == '/')
|
||||
) {
|
||||
if (page_exists($page.$conf['start'], $rev, true, $date_at)) {
|
||||
// start page inside namespace
|
||||
$page = $page.$conf['start'];
|
||||
$exists = true;
|
||||
}elseif(page_exists($page.noNS(cleanID($page)),$rev,true,$date_at)){
|
||||
} elseif (page_exists($page.noNS(cleanID($page)), $rev, true, $date_at)) {
|
||||
// page named like the NS inside the NS
|
||||
$page = $page.noNS(cleanID($page));
|
||||
$exists = true;
|
||||
}elseif(page_exists($page,$rev,true,$date_at)){
|
||||
} elseif (page_exists($page, $rev, true, $date_at)) {
|
||||
// page like namespace exists
|
||||
$page = $page;
|
||||
$exists = true;
|
||||
}else{
|
||||
} else {
|
||||
// fall back to default
|
||||
$page = $page.$conf['start'];
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
//check alternative plural/nonplural form
|
||||
if(!file_exists($file)){
|
||||
if( $conf['autoplural'] ){
|
||||
if(substr($page,-1) == 's'){
|
||||
$try = substr($page,0,-1);
|
||||
}else{
|
||||
if (!file_exists($file)) {
|
||||
if ($conf['autoplural']) {
|
||||
if (substr($page, -1) == 's') {
|
||||
$try = substr($page, 0, -1);
|
||||
} else {
|
||||
$try = $page.'s';
|
||||
}
|
||||
if(page_exists($try,$rev,true,$date_at)){
|
||||
$page = $try;
|
||||
if (page_exists($try, $rev, true, $date_at)) {
|
||||
$page = $try;
|
||||
$exists = true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
$exists = true;
|
||||
}
|
||||
}
|
||||
|
@ -603,7 +603,7 @@ function resolve_pageid($ns,&$page,&$exists,$rev='',$date_at=false ){
|
|||
$page = cleanID($page);
|
||||
|
||||
//add hash if any
|
||||
if(!empty($hash)) $page .= '#'.$hash;
|
||||
if (!empty($hash)) $page .= '#'.$hash;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1988,10 +1988,10 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
|
|||
* @access protected
|
||||
* @return string revision ('' for current)
|
||||
*/
|
||||
protected function _getLastMediaRevisionAt($media_id){
|
||||
if(!$this->date_at || media_isexternal($media_id)) return '';
|
||||
$pagelog = new MediaChangeLog($media_id);
|
||||
return $pagelog->getLastRevisionAt($this->date_at);
|
||||
protected function _getLastMediaRevisionAt($media_id) {
|
||||
if (!$this->date_at || media_isexternal($media_id)) return '';
|
||||
$changelog = new MediaChangeLog($media_id);
|
||||
return $changelog->getLastRevisionAt($this->date_at);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
Loading…
Reference in New Issue