This repository has been archived on 2024-09-05. You can view files and clone it, but cannot push or open issues or pull requests.
shimmie2/ext/wiki/main.php

570 lines
20 KiB
PHP
Raw Normal View History

2020-01-26 13:19:35 +00:00
<?php declare(strict_types=1);
class WikiUpdateEvent extends Event
{
2019-05-28 19:27:23 +00:00
/** @var User */
public $user;
2019-05-28 19:27:23 +00:00
/** @var WikiPage */
public $wikipage;
public function __construct(User $user, WikiPage $wikipage)
{
2020-01-26 13:19:35 +00:00
parent::__construct();
$this->user = $user;
$this->wikipage = $wikipage;
}
}
2009-07-28 01:06:51 +00:00
2020-01-29 00:49:21 +00:00
class WikiDeleteRevisionEvent extends Event
{
public $title;
public $revision;
2020-01-29 00:49:21 +00:00
public function __construct($title, $revision)
{
parent::__construct();
$this->title = $title;
$this->revision = $revision;
}
}
2020-01-29 00:49:21 +00:00
class WikiDeletePageEvent extends Event
{
public $title;
2020-01-29 00:49:21 +00:00
public function __construct($title)
{
parent::__construct();
$this->title = $title;
}
}
class WikiUpdateException extends SCoreException
{
2009-08-03 10:04:43 +00:00
}
class WikiPage
{
/** @var int|string */
public $id;
/** @var int */
public $owner_id;
/** @var string */
public $owner_ip;
/** @var string */
public $date;
/** @var string */
public $title;
/** @var int */
public $revision;
/** @var bool */
public $locked;
/** @var string */
public $body;
public function __construct(array $row=null)
{
//assert(!empty($row));
if (!is_null($row)) {
$this->id = (int)$row['id'];
$this->owner_id = (int)$row['owner_id'];
$this->owner_ip = $row['owner_ip'];
$this->date = $row['date'];
$this->title = $row['title'];
$this->revision = (int)$row['revision'];
2020-10-26 16:46:36 +00:00
$this->locked = bool_escape($row['locked']);
$this->body = $row['body'];
}
}
public function get_owner(): User
{
return User::by_id($this->owner_id);
}
public function is_locked(): bool
{
return $this->locked;
}
}
abstract class WikiConfig
{
const TAG_SHORTWIKIS = "shortwikis_on_tags";
}
class Wiki extends Extension
{
2020-01-26 13:19:35 +00:00
/** @var WikiTheme */
protected $theme;
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_bool(WikiConfig::TAG_SHORTWIKIS, false);
}
// Add a block to the Board Config / Setup
public function onSetupBuilding(SetupBuildingEvent $event)
{
$sb = $event->panel->create_new_block("Wiki");
$sb->add_bool_option(WikiConfig::TAG_SHORTWIKIS, "Show shortwiki entry when searching for a single tag: ");
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{
2020-01-26 13:19:35 +00:00
global $database;
2009-08-03 10:04:43 +00:00
2019-11-03 23:43:35 +00:00
if ($this->get_version("ext_wiki_version") < 1) {
$database->create_table("wiki_pages", "
2009-08-03 10:04:43 +00:00
id SCORE_AIPK,
owner_id INTEGER NOT NULL,
owner_ip SCORE_INET NOT NULL,
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
2009-08-03 10:04:43 +00:00
title VARCHAR(255) NOT NULL,
revision INTEGER NOT NULL DEFAULT 1,
locked BOOLEAN NOT NULL DEFAULT FALSE,
2009-08-03 10:04:43 +00:00
body TEXT NOT NULL,
UNIQUE (title, revision),
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
2009-08-03 10:04:43 +00:00
");
2020-10-26 16:46:36 +00:00
$this->set_version("ext_wiki_version", 3);
}
2019-11-03 19:49:52 +00:00
if ($this->get_version("ext_wiki_version") < 2) {
2020-10-25 21:34:52 +00:00
$database->execute("ALTER TABLE wiki_pages ADD COLUMN
2009-08-03 10:04:43 +00:00
locked ENUM('Y', 'N') DEFAULT 'N' NOT NULL AFTER REVISION");
2019-11-03 19:49:52 +00:00
$this->set_version("ext_wiki_version", 2);
}
2020-10-26 16:46:36 +00:00
if ($this->get_version("ext_wiki_version") < 3) {
2020-10-26 17:28:21 +00:00
$database->standardise_boolean("wiki_pages", "locked", true);
2020-10-26 16:46:36 +00:00
$this->set_version("ext_wiki_version", 3);
}
}
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if ($event->page_matches("wiki")) {
if ($event->count_args() == 0 || strlen(trim($event->get_arg(0))) === 0) {
$title = "Index";
} else {
$title = $event->get_arg(0);
}
$content = $this->get_page($title);
$this->theme->display_page($page, $content, $this->get_page("wiki:sidebar"));
} elseif ($event->page_matches("wiki_admin/edit")) {
$content = $this->get_page($_POST['title']);
$this->theme->display_page_editor($page, $content);
} elseif ($event->page_matches("wiki_admin/save")) {
$title = $_POST['title'];
$rev = int_escape($_POST['revision']);
$body = $_POST['body'];
2019-09-29 18:00:51 +00:00
$lock = $user->can(Permissions::WIKI_ADMIN) && isset($_POST['lock']) && ($_POST['lock'] == "on");
if ($this->can_edit($user, $this->get_page($title))) {
$wikipage = $this->get_page($title);
$wikipage->revision = $rev;
$wikipage->body = $body;
$wikipage->locked = $lock;
try {
send_event(new WikiUpdateEvent($user, $wikipage));
$u_title = url_escape($title);
2019-06-19 01:58:28 +00:00
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("wiki/$u_title"));
} catch (WikiUpdateException $e) {
$original = $this->get_page($title);
// @ because arr_diff is full of warnings
$original->body = @$this->arr_diff(
explode("\n", $original->body),
explode("\n", $wikipage->body)
);
$this->theme->display_page_editor($page, $original);
}
} else {
$this->theme->display_permission_denied();
}
} elseif ($event->page_matches("wiki_admin/delete_revision")) {
2019-09-29 18:00:51 +00:00
if ($user->can(Permissions::WIKI_ADMIN)) {
send_event(new WikiDeleteRevisionEvent($_POST["title"], $_POST["revision"]));
$u_title = url_escape($_POST["title"]);
2019-06-19 01:58:28 +00:00
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("wiki/$u_title"));
}
} elseif ($event->page_matches("wiki_admin/delete_all")) {
2019-09-29 18:00:51 +00:00
if ($user->can(Permissions::WIKI_ADMIN)) {
send_event(new WikiDeletePageEvent($_POST["title"]));
$u_title = url_escape($_POST["title"]);
2019-06-19 01:58:28 +00:00
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("wiki/$u_title"));
}
}
}
public function onPageNavBuilding(PageNavBuildingEvent $event)
{
2019-09-29 13:30:55 +00:00
$event->add_nav_link("wiki", new Link('wiki'), "Wiki");
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
2019-09-29 13:30:55 +00:00
if ($event->parent=="wiki") {
$event->add_nav_link("wiki_rules", new Link('wiki/rules'), "Rules");
$event->add_nav_link("wiki_help", new Link('ext_doc/wiki'), "Help");
}
}
public function onWikiUpdate(WikiUpdateEvent $event)
{
global $database;
$wpage = $event->wikipage;
try {
2020-10-25 21:34:52 +00:00
$database->execute(
2019-11-27 12:13:04 +00:00
"
2009-08-03 10:04:43 +00:00
INSERT INTO wiki_pages(owner_id, owner_ip, date, title, revision, locked, body)
2019-11-27 11:22:46 +00:00
VALUES (:owner_id, :owner_ip, now(), :title, :revision, :locked, :body)",
2019-11-27 12:13:04 +00:00
["owner_id"=>$event->user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'],
2020-10-26 16:46:36 +00:00
"title"=>$wpage->title, "revision"=>$wpage->revision, "locked"=>$wpage->locked, "body"=>$wpage->body]
2019-11-27 12:13:04 +00:00
);
} catch (Exception $e) {
throw new WikiUpdateException("Somebody else edited that page at the same time :-(");
}
}
2020-01-29 00:49:21 +00:00
public function onWikiDeleteRevision(WikiDeleteRevisionEvent $event)
{
global $database;
2020-10-25 21:34:52 +00:00
$database->execute(
"DELETE FROM wiki_pages WHERE title=:title AND revision=:rev",
["title"=>$event->title, "rev"=>$event->revision]
);
}
2020-01-29 00:49:21 +00:00
public function onWikiDeletePage(WikiDeletePageEvent $event)
{
global $database;
2020-10-25 21:34:52 +00:00
$database->execute(
"DELETE FROM wiki_pages WHERE title=:title",
["title" => $event->title]
);
}
/**
* See if the given user is allowed to edit the given page.
*/
public static function can_edit(User $user, WikiPage $page): bool
{
// admins can edit everything
2019-09-29 18:00:51 +00:00
if ($user->can(Permissions::WIKI_ADMIN)) {
return true;
}
// anon / user can't ever edit locked pages
if ($page->is_locked()) {
return false;
}
// anon / user can edit if allowed by config
2019-07-09 14:10:21 +00:00
if ($user->can(Permissions::EDIT_WIKI_PAGE)) {
return true;
}
return false;
}
public static function get_page(string $title, int $revision=-1): WikiPage
{
global $database;
// first try and get the actual page
$row = $database->get_row(
2020-02-01 22:44:50 +00:00
"
SELECT *
FROM wiki_pages
WHERE LOWER(title) LIKE LOWER(:title)
2020-02-01 22:44:50 +00:00
ORDER BY revision DESC
2020-02-01 23:04:40 +00:00
",
["title"=>$title]
);
2009-07-28 01:06:51 +00:00
// fall back to wiki:default
if (empty($row)) {
$row = $database->get_row("
2020-02-01 22:44:50 +00:00
SELECT *
FROM wiki_pages
WHERE title LIKE :title
ORDER BY revision DESC
", ["title"=>"wiki:default"]);
// fall further back to manual
if (empty($row)) {
$row = [
"id" => -1,
"owner_ip" => "0.0.0.0",
"date" => "",
"revision" => 0,
"locked" => false,
"body" => "This is a default page for when a page is empty, ".
"it can be edited by editing [[wiki:default]].",
];
}
// correct the default
global $config;
$row["title"] = $title;
$row["owner_id"] = $config->get_int("anon_id", 0);
}
assert(!empty($row));
return new WikiPage($row);
}
/**
Diff implemented in pure php, written from scratch.
Copyright (C) 2003 Daniel Unterberger <diff.phpnet@holomind.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
2020-03-25 11:47:00 +00:00
https://www.gnu.org/licenses/gpl.html
About:
I searched a function to compare arrays and the array_diff()
was not specific enough. It ignores the order of the array-values.
So I reimplemented the diff-function which is found on unix-systems
but this you can use directly in your code and adopt for your needs.
Simply adopt the formatline-function. with the third-parameter of arr_diff()
you can hide matching lines. Hope someone has use for this.
Contact: d.u.diff@holomind.de <daniel unterberger>
**/
private function arr_diff($f1, $f2, $show_equal = 0)
{
$c1 = 0 ; # current line of left
$c2 = 0 ; # current line of right
2020-01-26 13:19:35 +00:00
$max1 = count($f1) ; # maximal lines of left
$max2 = count($f2) ; # maximal lines of right
$outcount = 0; # output counter
2020-01-26 13:19:35 +00:00
$hit1 = []; # hit in left
$hit2 = []; # hit in right
$stop = 0;
$out = "";
while (
$c1 < $max1 # have next line in left
and
$c2 < $max2 # have next line in right
and
($stop++) < 1000 # don-t have more then 1000 ( loop-stopper )
and
$outcount < 20 # output count is less then 20
) {
/**
* is the trimmed line of the current left and current right line
* the same ? then this is a hit (no difference)
*/
if (trim($f1[$c1]) == trim($f2[$c2])) {
/**
* add to output-string, if "show_equal" is enabled
*/
$out .= ($show_equal==1)
2020-01-26 13:19:35 +00:00
? $this->formatline(($c1), ($c2), "=", $f1[ $c1 ])
: "" ;
/**
* increase the out-putcounter, if "show_equal" is enabled
* this ist more for demonstration purpose
*/
if ($show_equal == 1) {
$outcount++ ;
}
/**
* move the current-pointer in the left and right side
*/
$c1 ++;
$c2 ++;
}
/**
* the current lines are different so we search in parallel
* on each side for the next matching pair, we walk on both
* sided at the same time comparing with the current-lines
* this should be most probable to find the next matching pair
* we only search in a distance of 10 lines, because then it
* is not the same function most of the time. other algos
* would be very complicated, to detect 'real' block movements.
*/
else {
$b = "" ;
$s1 = 0 ; # search on left
$s2 = 0 ; # search on right
$found = 0 ; # flag, found a matching pair
$b1 = "" ;
$b2 = "" ;
$fstop = 0 ; # distance of maximum search
#fast search in on both sides for next match.
while (
$found == 0 # search until we find a pair
and
($c1 + $s1 <= $max1) # and we are inside of the left lines
and
($c2 + $s2 <= $max2) # and we are inside of the right lines
and
$fstop++ < 10 # and the distance is lower than 10 lines
) {
/**
* test the left side for a hit
*
* comparing current line with the searching line on the left
* b1 is a buffer, which collects the line which not match, to
* show the differences later, if one line hits, this buffer will
* be used, else it will be discarded later
*/
#hit
if (trim($f1[$c1+$s1]) == trim($f2[$c2])) {
$found = 1 ; # set flag to stop further search
$s2 = 0 ; # reset right side search-pointer
$c2-- ; # move back the current right, so next loop hits
$b = $b1 ; # set b=output (b)uffer
}
#no hit: move on
else {
/**
* prevent finding a line again, which would show wrong results
*
* add the current line to leftbuffer, if this will be the hit
*/
if ($hit1[ ($c1 + $s1) . "_" . ($c2) ] != 1) {
/**
* add current search-line to diffence-buffer
*/
$b1 .= $this->formatline(($c1 + $s1), ($c2), "-", $f1[ $c1+$s1 ]);
/**
* mark this line as 'searched' to prevent doubles.
*/
$hit1[ ($c1 + $s1) . "_" . $c2 ] = 1 ;
}
}
/**
* test the right side for a hit
*
* comparing current line with the searching line on the right
*/
if (trim($f1[$c1]) == trim($f2[$c2+$s2])) {
$found = 1 ; # flag to stop search
$s1 = 0 ; # reset pointer for search
$c1-- ; # move current line back, so we hit next loop
$b = $b2 ; # get the buffered difference
} else {
/**
* prevent to find line again
*/
if ($hit2[ ($c1) . "_" . ($c2 + $s2) ] != 1) {
/**
* add current searchline to buffer
*/
$b2 .= $this->formatline(($c1), ($c2 + $s2), "+", $f2[ $c2+$s2 ]);
/**
* mark current line to prevent double-hits
*/
$hit2[ ($c1) . "_" . ($c2 + $s2) ] = 1;
}
}
/**
* search in bigger distance
*
* increase the search-pointers (satelites) and try again
*/
$s1++ ; # increase left search-pointer
$s2++ ; # increase right search-pointer
}
/**
* add line as different on both arrays (no match found)
*/
if ($found == 0) {
$b .= $this->formatline(($c1), ($c2), "-", $f1[ $c1 ]);
$b .= $this->formatline(($c1), ($c2), "+", $f2[ $c2 ]);
}
/**
* add current buffer to outputstring
*/
$out .= $b;
$outcount++ ; #increase outcounter
$c1++ ; #move currentline forward
$c2++ ; #move currentline forward
/**
* comment the lines are tested quite fast, because
* the current line always moves forward
*/
} /*endif*/
}/*endwhile*/
return $out;
}/*end func*/
/**
* callback function to format the diffence-lines with your 'style'
*/
private function formatline(int $nr1, int $nr2, string $stat, &$value): string
{ #change to $value if problems
if (trim($value) == "") {
return "";
}
switch ($stat) {
case "=":
// return $nr1. " : $nr2 : = ".htmlentities( $value ) ."<br>";
return "$value\n";
break;
case "+":
//return $nr1. " : $nr2 : + <font color='blue' >".htmlentities( $value ) ."</font><br>";
return "+++ $value\n";
break;
case "-":
//return $nr1. " : $nr2 : - <font color='red' >".htmlentities( $value ) ."</font><br>";
return "--- $value\n";
break;
2019-05-28 19:27:23 +00:00
2019-06-14 12:16:58 +00:00
default:
2020-01-26 13:19:35 +00:00
throw new RuntimeException("stat needs to be =, + or -");
}
}
}