1<?php 2 3use dokuwiki\ChangeLog\PageChangeLog; 4 5/** 6 * saveWikiText() stores files in pages/, attic/ and adds entries to changelog 7 */ 8class common_saveWikiText_test extends DokuWikiTest { 9 10 /** Delay writes of old revisions by a second. */ 11 public function handle_write(Doku_Event $event, $param) { 12 if ($event->data[3] !== false) { 13 $this->waitForTick(); 14 } 15 } 16 17 /** 18 * assertions against changelog entries and attic after saveWikiText() 19 */ 20 private function checkChangeLogAfterNormalSave( 21 PageChangeLog $pagelog, 22 $expectedRevs, // @param int 23 &$expectedLastEntry // @param array, pass by reference 24 ) { 25 $revisions = $pagelog->getRevisions(-1, 200); 26 $this->assertCount($expectedRevs, $revisions); 27 $this->assertCount($expectedRevs, array_unique($revisions), 'date duplicated in changelog'); 28 // last revision 29 $lastRevInfo = $pagelog->getRevisionInfo($revisions[0]); 30 $expectedLastEntry += $lastRevInfo; 31 $this->assertEquals($expectedLastEntry, $lastRevInfo); 32 // current revision 33 $currentRevInfo = $pagelog->getCurrentRevisionInfo(); 34 $this->assertEquals($currentRevInfo, $lastRevInfo, 'current & last revs should be identical'); 35 // attic 36 $attic = wikiFN($lastRevInfo['id'], $lastRevInfo['date']); 37 $this->assertFileExists($attic, 'file missing in attic'); 38 $files = count(glob(dirname($attic).'/'.noNS($lastRevInfo['id']).'.*')); 39 $this->assertLessThanOrEqual($expectedRevs, $files, 'detectExternalEdit() should not add too often old revs'); 40 } 41 42 /** 43 * assertions against changelog entries and attic after external edit, create or deletion of page 44 */ 45 private function checkChangeLogAfterExternalEdit( 46 PageChangeLog $pagelog, 47 $expectedRevs, // @param int 48 $expectedLastEntry, // @param array 49 &$expectedCurrentEntry // @param array, pass by reference 50 ) { 51 $revisions = $pagelog->getRevisions(-1, 200); 52 $this->assertCount($expectedRevs, $revisions); 53 $this->assertCount($expectedRevs, array_unique($revisions), 'date duplicated in changelog'); 54 // last revision 55 if ($expectedRevs > 0) { 56 $lastRevInfo = $pagelog->getRevisionInfo($revisions[0]); 57 $expectedLastEntry += $lastRevInfo; 58 $this->assertEquals($expectedLastEntry, $lastRevInfo); 59 } else { 60 $this->assertFalse($pagelog->lastRevision(), 'changelog file does not yet exist'); 61 } 62 // current revision 63 $currentRevInfo = $pagelog->getCurrentRevisionInfo(); 64 $this->assertArrayHasKey('timestamp', $currentRevInfo, 'should be external revision'); 65 $expectedCurrentEntry += $currentRevInfo; 66 if ($expectedRevs > 0) { 67 $this->assertEquals($expectedCurrentEntry, $currentRevInfo); 68 69 } 70 // attic 71 $attic = wikiFN($currentRevInfo['id'], $currentRevInfo['date']); 72 $this->assertFileNotExists($attic, 'page does not yet exist in attic'); 73 } 74 75 76 /** 77 * Execute a whole bunch of saves on the same page and check the results 78 * TEST 1 79 * 1.1 create a page 80 * 1.2 save with same content should be ignored 81 * 1.3 update the page with new text 82 * 1.4 add a minor edit (unauthenticated, minor not allowable) 83 * 1.5 add a minor edit (authenticated) 84 * 1.6 delete 85 * 1.7 restore 86 * 1.8 external edit 87 * 1.9 edit and save on top of external edit 88 */ 89 function test_savesequence1() { 90 global $REV; 91 92 $page = 'page'; 93 $file = wikiFN($page); 94 $this->assertFileNotExists($file); 95 96 // 1.1 create a page 97 saveWikiText($page, 'teststring', '1st save', false); 98 clearstatcache(false, $file); 99 $this->assertFileExists($file); 100 $lastmod = filemtime($file); 101 $expectedRevs = 1; 102 $expect = array( 103 'date' => $lastmod, 104 'type' => DOKU_CHANGE_TYPE_CREATE, 105 'sum' => '1st save', 106 'sizechange' => 10, // = strlen('teststring') 107 ); 108 109 $pagelog = new PageChangeLog($page); 110 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 111 112 $this->waitForTick(true); // wait for new revision ID 113 114 // 1.2 save with same content should be ignored 115 saveWikiText($page, 'teststring', '2nd save', false); 116 clearstatcache(false, $file); 117 $this->assertEquals($lastmod, filemtime($file)); 118 119 $pagelog = new PageChangeLog($page); 120 $revisions = $pagelog->getRevisions(-1, 200); 121 $this->assertCount(1, $revisions); 122 123 // 1.3 update the page with new text 124 saveWikiText($page, 'teststring2long', '3rd save', false); 125 clearstatcache(false, $file); 126 $newmod = filemtime($file); 127 $this->assertNotEquals($lastmod, $newmod); 128 $lastmod = $newmod; 129 $expectedRevs = 2; 130 $expect = array( 131 'date' => $lastmod, 132 'type' => DOKU_CHANGE_TYPE_EDIT, 133 'sum' => '3rd save', 134 'sizechange' => 5, 135 ); 136 137 $pagelog = new PageChangeLog($page); 138 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 139 140 $this->waitForTick(); // wait for new revision ID 141 142 // 1.4 add a minor edit (unauthenticated, minor not allowable) 143 saveWikiText($page, 'teststring3long', '4th save', true); 144 clearstatcache(false, $file); 145 $newmod = filemtime($file); 146 $this->assertNotEquals($lastmod, $newmod); 147 $lastmod = $newmod; 148 $expectedRevs = 3; 149 $expect = array( 150 'date' => $lastmod, 151 'type' => DOKU_CHANGE_TYPE_EDIT, 152 'sum' => '4th save', 153 'sizechange' => 0, 154 ); 155 156 $pagelog = new PageChangeLog($page); 157 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 158 159 $this->waitForTick(); // wait for new revision ID 160 161 // 1.5 add a minor edit (authenticated) 162 $_SERVER['REMOTE_USER'] = 'user'; 163 saveWikiText($page, 'teststring4', '5th save', true); 164 clearstatcache(false, $file); 165 $newmod = filemtime($file); 166 $this->assertNotEquals($lastmod, $newmod); 167 $lastmod = $newmod; 168 $expectedRevs = 4; 169 $expect = array( 170 'date' => $lastmod, 171 'type' => DOKU_CHANGE_TYPE_MINOR_EDIT, 172 'sum' => '5th save', 173 'sizechange' => -4, 174 ); 175 176 $pagelog = new PageChangeLog($page); 177 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 178 179 $this->waitForTick(); // wait for new revision ID 180 181 // 1.6 delete 182 saveWikiText($page, '', '6th save', false); 183 clearstatcache(false, $file); 184 $this->assertFileNotExists($file); 185 $expectedRevs = 5; 186 $expect = array( 187 //'date' => $lastmod, // ignore from lastRev assertion, but confirm attic file existence 188 'type' => DOKU_CHANGE_TYPE_DELETE, 189 'sum' => '6th save', 190 'sizechange' => -11, 191 ); 192 193 $pagelog = new PageChangeLog($page); 194 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 195 196 $this->waitForTick(); // wait for new revision ID 197 198 // 1.7 restore 199 $REV = $lastmod; 200 saveWikiText($page, 'teststring4', '7th save', true); 201 clearstatcache(false, $file); 202 $this->assertFileExists($file); 203 $newmod = filemtime($file); 204 $this->assertNotEquals($lastmod, $newmod); 205 $lastmod = $newmod; 206 $expectedRevs = 6; 207 $expect = array( 208 'date' => $lastmod, 209 'type' => DOKU_CHANGE_TYPE_REVERT, 210 'sum' => '7th save', 211 'sizechange' => 11, 212 ); 213 214 $pagelog = new PageChangeLog($page); 215 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 216 $REV = ''; 217 218 $this->waitForTick(); // wait for new revision ID 219 220 // 1.8 external edit 221 file_put_contents($file, 'teststring5 external edit'); 222 clearstatcache(false, $file); 223 $newmod = filemtime($file); 224 $this->assertNotEquals($lastmod, $newmod); 225 $lastmod = $newmod; 226 $expectedRevs = 6; // external edit is not yet in changelog 227 $expectExternal = array( 228 'date' => $lastmod, 229 'type' => DOKU_CHANGE_TYPE_EDIT, 230 'sum' => 'external edit', 231 'sizechange' => 14, 232 ); 233 234 $pagelog = new PageChangeLog($page); 235 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 236 237 $this->waitForTick(); // wait for new revision ID 238 239 // 1.9 save on top of external edit 240 saveWikiText($page, 'teststring6', '8th save', false); 241 clearstatcache(false, $file); 242 $newmod = filemtime($file); 243 $this->assertNotEquals($lastmod, $newmod); 244 $lastmod = $newmod; 245 $expectedRevs = 8; 246 $expect = array( 247 'date' => $lastmod, 248 'type' => DOKU_CHANGE_TYPE_EDIT, 249 'sum' => '8th save', 250 'sizechange' => -14, 251 ); 252 253 $pagelog = new PageChangeLog($page); 254 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 255 } 256 257 /** 258 * Execute a whole bunch of saves on the same page and check the results 259 * using $this->handle_write() in event IO_WIKIPAGE_WRITE 260 * TEST 2 - create a page externally in 2.3, while external edit in Test 1.8 261 * 2.1 create a page 262 * 2.2 delete 263 * 2.3 externally create the page 264 * 2.4 edit and save on top of external edit 265 * 2.5 external edit 266 * 2.6 edit and save on top of external edit, again 267 */ 268 function test_savesequence2() { 269 // add an additional delay when saving files to make sure 270 // nobody relies on the saving happening in the same second 271 /** @var $EVENT_HANDLER \dokuwiki\Extension\EventHandler */ 272 global $EVENT_HANDLER; 273 $EVENT_HANDLER->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'handle_write'); 274 275 $page = 'page2'; 276 $file = wikiFN($page); 277 $this->assertFileNotExists($file); 278 279 // 2.1 create a page 280 saveWikiText($page, 'teststring', 'Test 2, 1st save', false); 281 clearstatcache(false, $file); 282 $this->assertFileExists($file); 283 $lastmod = filemtime($file); 284 $expectedRevs = 1; 285 $expect = array( 286 'date' => $lastmod, 287 'type' => DOKU_CHANGE_TYPE_CREATE, 288 'sum' => 'Test 2, 1st save', 289 'sizechange' => 10, // = strlen('teststring') 290 ); 291 292 $pagelog = new PageChangeLog($page); 293 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 294 295 $this->waitForTick(true); // wait for new revision ID 296 297 // 2.2 delete 298 saveWikiText($page, '', 'Test 2, 2nd save', false); 299 clearstatcache(false, $file); 300 $this->assertFileNotExists($file); 301 $expectedRevs = 2; 302 $expect = array( 303 //'date' => $lastmod, // ignore from lastRev assertion, but confirm attic file existence 304 'type' => DOKU_CHANGE_TYPE_DELETE, 305 'sum' => 'Test 2, 2nd save', 306 'sizechange' => -10, 307 ); 308 309 $pagelog = new PageChangeLog($page); 310 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 311 312 $this->waitForTick(); // wait for new revision ID 313 314 // 2.3 externally create the page 315 file_put_contents($file, 'teststring5'); 316 clearstatcache(false, $file); 317 $lastmod = filemtime($file); 318 $expectedRevs = 2; // external edit is not yet in changelog 319 $expectExternal = array( 320 'date' => $lastmod, 321 'type' => DOKU_CHANGE_TYPE_CREATE, 322 'sum' => 'created - external edit', 323 'sizechange' => 11, 324 ); 325 326 $pagelog = new PageChangeLog($page); 327 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 328 329 $this->waitForTick(); // wait for new revision ID 330 331 // 2.4 save on top of external edit 332 saveWikiText($page, 'teststring6', 'Test 2, 3rd save', false); 333 clearstatcache(false, $file); 334 $newmod = filemtime($file); 335 $this->assertNotEquals($lastmod, $newmod); 336 $lastmod = $newmod; 337 $expectedRevs = 4; // two more revisions now! 338 $expect = array( 339 'date' => $lastmod, 340 'type' => DOKU_CHANGE_TYPE_EDIT, 341 'sum' => 'Test 2, 3rd save', 342 'sizechange' => 0, 343 ); 344 345 $pagelog = new PageChangeLog($page); 346 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 347 348 $this->waitForTick(); // wait for new revision ID 349 350 // 2.5 external edit 351 file_put_contents($file, 'teststring7 external edit2'); 352 clearstatcache(false, $file); 353 $newmod = filemtime($file); 354 $this->assertNotEquals($lastmod, $newmod); 355 $lastmod = $newmod; 356 $expectedRevs = 4; // external edit is not yet in changelog 357 $expectExternal = array( 358 'date' => $lastmod, 359 'type' => DOKU_CHANGE_TYPE_EDIT, 360 'sum' => 'external edit', 361 'sizechange' => 15, 362 ); 363 364 $pagelog = new PageChangeLog($page); 365 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 366 367 $this->waitForTick(); // wait for new revision ID 368 369 // 2.6 save on top of external edit, again 370 saveWikiText($page, 'teststring8', 'Test 2, 4th save', false); 371 clearstatcache(false, $file); 372 $newmod = filemtime($file); 373 $this->assertNotEquals($lastmod, $newmod); 374 $lastmod = $newmod; 375 $expectedRevs = 6; // two more revisions now! 376 $expect = array( 377 'date' => $lastmod, 378 'type' => DOKU_CHANGE_TYPE_EDIT, 379 'sum' => 'Test 2, 4th save', 380 'sizechange' => -15, 381 ); 382 383 $pagelog = new PageChangeLog($page); 384 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 385 } 386 387 /** 388 * Execute a whole bunch of saves on the same page and check the results 389 * TEST 3 - typical page life of bundled page such as wiki:syntax 390 * 3.1 externally create a page 391 * 3.2 external edit 392 * 3.3 edit and save on top of external edit 393 * 3.4 externally delete the page 394 */ 395 function test_savesequence3() { 396 $page = 'page3'; 397 $file = wikiFN($page); 398 399 // 3.1 externally create a page 400 $this->assertFileNotExists($file); 401 file_put_contents($file, 'teststring'); 402 clearstatcache(false, $file); 403 $lastmod = filemtime($file); 404 $expectedRevs = 0; // external edit is not yet in changelog 405 $expect = false; 406 $expectExternal = array( 407 'date' => $lastmod, 408 'type' => DOKU_CHANGE_TYPE_CREATE, 409 'sum' => 'created - external edit', 410 'sizechange' => 10, 411 ); 412 413 $pagelog = new PageChangeLog($page); 414 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 415 416 $this->waitForTick(true); // wait for new revision ID 417 418 // 3.2 external edit (repeated, still no changelog exists) 419 file_put_contents($file, 'teststring external edit'); 420 clearstatcache(false, $file); 421 $newmod = filemtime($file); 422 $this->assertNotEquals($lastmod, $newmod); 423 $lastmod = $newmod; 424 $expectedRevs = 0; // external edit is not yet in changelog 425 $expectExternal = array( 426 'date' => $lastmod, 427 'type' => DOKU_CHANGE_TYPE_CREATE, // not DOKU_CHANGE_TYPE_EDIT 428 'sum' => 'created - external edit', 429 'sizechange' => 24, 430 ); 431 432 $pagelog = new PageChangeLog($page); 433 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 434 435 $this->waitForTick(true); // wait for new revision ID 436 437 // 3.3 save on top of external edit 438 saveWikiText($page, 'teststring1', 'Test 3, first save', false); 439 clearstatcache(false, $file); 440 $newmod = filemtime($file); 441 $this->assertNotEquals($lastmod, $newmod); 442 $lastmod = $newmod; 443 $expectedRevs = 2; // two more revisions now! 444 $expect = array( 445 'date' => $lastmod, 446 'type' => DOKU_CHANGE_TYPE_EDIT, 447 'sum' => 'Test 3, first save', 448 'sizechange' => -13, 449 ); 450 451 $pagelog = new PageChangeLog($page); 452 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 453 454 455 $this->waitForTick(true); // wait for new revision ID 456 457 // 3.4 externally delete the page 458 unlink($file); 459 clearstatcache(false, $file); 460 $expectedRevs = 2; 461 $expectExternal = array( 462 //'date' => $lastmod, 463 'type' => DOKU_CHANGE_TYPE_DELETE, 464 'sum' => 'removed - external edit (Unknown date)', 465 'sizechange' => -11, 466 ); 467 468 $pagelog = new PageChangeLog($page); 469 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 470 } 471 472 /** 473 * Execute a whole bunch of saves on the same page and check the results 474 * TEST 4 - typical page life of bundled page such as wiki:syntax 475 * 4.1 externally create a page 476 * 4.2 edit and save 477 * 4.3 externally edit as a result of a file which has older timestamp than last revision 478 */ 479 function test_savesequence4() { 480 $page = 'page4'; 481 $file = wikiFN($page); 482 483 // 4.1 externally create a page 484 $this->assertFileNotExists($file); 485 file_put_contents($file, 'teststring'); 486 clearstatcache(false, $file); 487 $lastmod = filemtime($file); 488 $expectedRevs = 0; // external edit is not yet in changelog 489 $expect = false; 490 $expectExternal = array( 491 'date' => $lastmod, 492 'type' => DOKU_CHANGE_TYPE_CREATE, 493 'sum' => 'created - external edit', 494 'sizechange' => 10, 495 ); 496 497 $pagelog = new PageChangeLog($page); 498 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 499 500 $this->waitForTick(true); // wait for new revision ID 501 502 // 4.2 edit and save 503 saveWikiText($page, 'teststring1', 'Test 4, first save', false); 504 clearstatcache(false, $file); 505 $newmod = filemtime($file); 506 $this->assertNotEquals($lastmod, $newmod); 507 $lastmod = $newmod; 508 $expectedRevs = 2; // two more revisions now! 509 $expect = array( 510 'date' => $lastmod, 511 'type' => DOKU_CHANGE_TYPE_EDIT, 512 'sum' => 'Test 4, first save', 513 'sizechange' => 1, 514 ); 515 516 $pagelog = new PageChangeLog($page); 517 $this->checkChangeLogAfterNormalSave($pagelog, $expectedRevs, $expect); 518 519 $this->waitForTick(true); // wait for new revision ID 520 521 // 4.3 externally edit as a result of a file which has older timestamp than last revision 522 unlink($file); 523 file_put_contents($file, 'teststring fake 1 hout past'); 524 touch($file, filemtime($file) -3600); // change file modification time to 1 hour past 525 clearstatcache(); 526 $newmod = filemtime($file); 527 $this->assertLessThan($lastmod, $newmod); // file must be older than previous for this test 528 $expectedRevs = 2; // external edit is not yet in changelog 529 $expectExternal = array( 530 'date' => $lastmod + 1, 531 'type' => DOKU_CHANGE_TYPE_EDIT, 532 'sum' => 'external edit (Unknown date)', 533 'sizechange' => 16, 534 ); 535 536 $pagelog = new PageChangeLog($page); 537 $this->checkChangeLogAfterExternalEdit($pagelog, $expectedRevs, $expect, $expectExternal); 538 } 539 540} 541