Skip to content
Snippets Groups Projects
Commit 6d414414 authored by Rasmus Fuhse's avatar Rasmus Fuhse Committed by Jan-Hendrik Willms
Browse files

Resolve "Wiki ab 5.5: Fehler nach dem Merge im Main"

Closes #3652

Merge request studip/studip!2535
parent 47fd0029
No related branches found
No related tags found
No related merge requests found
......@@ -18,6 +18,9 @@ class Course_WikiController extends AuthenticatedController
parent::before_filter($action, $args);
object_set_visit_module('wiki');
$this->range = Context::get();
$this->plugin = PluginManager::getInstance()->getPlugin('CoreWiki');
PageLayout::setTitle(Navigation::getItem('/course/wiki')->getTitle());
}
public function page_action($page_id = null)
......@@ -26,7 +29,6 @@ class Course_WikiController extends AuthenticatedController
$page_id = $this->range->getConfiguration()->WIKI_STARTPAGE_ID;
}
Navigation::activateItem('/course/wiki/start');
PageLayout::setTitle(Navigation::getItem('/course/wiki')->getTitle());
$this->page = new WikiPage($page_id);
......@@ -547,60 +549,51 @@ class Course_WikiController extends AuthenticatedController
Navigation::activateItem('/course/wiki/listnew');
$this->limit = Config::get()->ENTRIES_PER_PAGE;
$this->last_visit = object_get_visit($this->range->id, $this->plugin->getPluginId());
$statement = DBManager::get()->prepare("
SELECT COUNT(*) FROM (
SELECT `wiki_pages`.`page_id` AS `id`,
0 AS `is_version`,
`wiki_pages`.`chdate` AS `timestamp`
FROM `wiki_pages`
WHERE `wiki_pages`.`range_id` = :range_id
UNION
SELECT `wiki_versions`.`version_id` AS `id`,
1 AS `is_version`,
`wiki_versions`.`mkdate` AS `timestamp`
FROM `wiki_versions`
JOIN `wiki_pages` USING (`page_id`)
WHERE `wiki_pages`.`range_id` = :range_id
) AS `all_entries`
");
$statement->execute([
'range_id' => $this->range->id
]);
$this->num_entries = $statement->fetch(PDO::FETCH_COLUMN);
$this->page = Request::int('page', 0);
$statement = DBManager::get()->prepare("
SELECT `wiki_pages`.`page_id` AS `id`,
0 AS `is_version`,
`wiki_pages`.`chdate` AS `timestamp`
SELECT COUNT(*)
FROM `wiki_pages`
WHERE `wiki_pages`.`range_id` = :range_id
UNION
SELECT `wiki_versions`.`version_id` AS `id`,
1 AS `is_version`,
`wiki_versions`.`mkdate` AS `timestamp`
FROM `wiki_versions`
JOIN `wiki_pages` USING (`page_id`)
WHERE `wiki_pages`.`range_id` = :range_id
ORDER BY `timestamp` DESC
LIMIT :offset, :limit
AND `wiki_pages`.`chdate` > :threshold
AND `wiki_pages`.`user_id` != :me
");
$statement->execute([
'range_id' => $this->range->id,
'offset' => Request::int('page', 0) * $this->limit,
'limit' => $this->limit
'threshold' => $this->last_visit,
'me' => User::findCurrent()->id
]);
$this->versions = [];
foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) {
if ($row['is_version']) {
$this->versions[] = WikiVersion::find($row['id']);
} else {
$this->versions[] = WikiPage::find($row['id']);
}
$this->num_entries = $statement->fetch(PDO::FETCH_COLUMN);
$this->pagenumber = Request::int('page', 0);
$this->sort = Request::option('sort', 'chdate');
if (!in_array($this->sort, ['name', 'chdate'])) {
$this->sort = 'chdate';
}
$this->sort_asc = Request::bool('sort_asc', $this->sort === 'name');
if ($this->num_entries > 0) {
$statement = DBManager::get()->prepare("
SELECT `wiki_pages`.*
FROM `wiki_pages`
WHERE `wiki_pages`.`range_id` = :range_id
AND `wiki_pages`.`chdate` > :threshold
AND `wiki_pages`.`user_id` != :me
ORDER BY `wiki_pages`.`{$this->sort}` " . ($this->sort_asc ? 'ASC' : 'DESC') . "
LIMIT :offset, :limit
");
$statement->execute([
'range_id' => $this->range->id,
'threshold' => $this->last_visit,
'offset' => $this->pagenumber * $this->limit,
'limit' => $this->limit,
'me' => User::findCurrent()->id
]);
$this->pages = array_map(
fn($p) => WikiPage::buildExisting($p),
$statement->fetchAll(PDO::FETCH_ASSOC)
);
} else {
$this->pages = [];
}
}
......@@ -1113,4 +1106,24 @@ class Course_WikiController extends AuthenticatedController
}
}
}
/**
* @see https://stackoverflow.com/a/7475502/982902
*/
public function findLongestCommonSubstring(string $str0, string $str1, bool $from_end = false): int
{
if ($from_end) {
$str0 = implode('', array_reverse(mb_str_split($str0, 1)));
$str1 = implode('', array_reverse(mb_str_split($str1, 1)));
}
$length = mb_strlen(
mb_strcut(
$str0,
0,
strspn($str0 ^ $str1, "\0")
)
);
return $from_end ? mb_strlen($str0) - $length : $length;
}
}
<table class="default sortable-table" data-sortlist="[[3, 1]]">
<?php
/**
* @var Course_WikiController $controller
* @var string $sort
* @var bool $sort_asc
* @var WikiPage[]|null $pages
* @var int $last_visit
*
* @var int $num_entries
* @var int $limit
* @var int $pagenumber
*/
?>
<table class="default">
<caption>
<?= _('Letzte Änderungen') ?>
</caption>
<colgroup>
<col style="min-width: 120px;">
<col>
<col style="min-width: 150px;">
<col>
</colgroup>
<thead>
<tr>
<th data-sort="text"><?= _('Seitenname') ?></th>
<th data-sort="false"><?= _('Text') ?></th>
<th data-sort="text"><?= _('Autor/-in') ?></th>
<th data-sort="text"><?= _('Datum') ?></th>
<tr class="sortable">
<th <? if ($sort === 'name') echo 'class="' . ($sort_asc ? 'sortasc' : 'sortdesc') . '"'; ?>>
<a href="<?= $controller->newpages(['sort' => 'name', 'sort_asc' => $sort !== 'name' || !$sort_asc ? 1 : 0]) ?>">
<?= _('Seitenname') ?>
</a>
</th>
<th><?= _('Text') ?></th>
<th><?= _('Autor/-in') ?></th>
<th <? if ($sort === 'chdate') echo 'class="' . ($sort_asc ? 'sortasc' : 'sortdesc') . '"'; ?>>
<a href="<?= $controller->newpages(['sort' => 'chdate', 'sort_asc' => $sort === 'chdate' && !$sort_asc ? 1 : 0]) ?>">
<?= _('Datum') ?>
</a>
</th>
</tr>
</thead>
<tbody>
<? foreach (array_reverse($versions) as $version) : ?>
<?= $this->render_partial('course/wiki/versioncompare', ['version' => $version]) ?>
<? endforeach ?>
<? if (count($pages) === 0): ?>
<tr>
<td colspan="4">
<?= _('Keine Seiten wurden seit Ihrem letzten Besuch verändert.') ?>
</td>
</tr>
<? endif ?>
<? foreach ($pages as $page) : ?>
<tr>
<td>
<a href="<?= $controller->page($page) ?>">
<?= htmlReady($page->name) ?>
</a>
</td>
<td>
<?
$authors = [$page->user_id => $page->user];
$oldcontent = "";
$oldversion = $page;
while ($oldversion = $oldversion->predecessor) {
if ($oldversion->mkdate >= $last_visit && $oldversion->user_id !== User::findCurrent()->id) {
$oldcontent = $oldversion->content;
if (!isset($authors[$oldversion->user_id])) {
$authors[$oldversion->user_id] = $oldversion->user;
}
} else {
break;
}
}
$oldcontent = strip_tags(wikiReady($oldcontent));
$content = strip_tags(wikiReady($page->content));
$commonFromStart = $controller->findLongestCommonSubstring($content, $oldcontent);
$commonFromEnd = $controller->findLongestCommonSubstring($content, $oldcontent, true);
$content = mb_substr($content, $commonFromStart, $commonFromEnd);
$oldcontent = mb_substr($oldcontent, $commonFromStart, $commonFromEnd);
if ($content) {
echo htmlReady(mila($content, 300), true, true);
} elseif ($oldcontent) {
echo _('Gelöscht') . ': ' . htmlReady($oldcontent, true, true);
}
?>
</td>
<td>
<ul class="wiki_authors">
<? foreach ($authors as $user) : ?>
<li>
<? if ($user): ?>
<a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $user->username]) ?>"
style="background-image: url(<?= Avatar::getAvatar($user->id)->getURL(Avatar::SMALL) ?>)"
>
<?= htmlReady($user->getFullName()) ?>
</a>
<? else: ?>
<?= _('unbekannt') ?>
<? endif; ?>
</li>
<? endforeach ?>
</ul>
</td>
<td><?= strftime('%x %X', $page->chdate) ?></td>
</tr>
<? endforeach ?>
</tbody>
<? if ($num_entries > $limit) : ?>
<tfoot>
<tr>
<td colspan="4" class="actions">
<?= Pagination::create($num_entries, $page, $limit)->asLinks() ?>
<?= Pagination::create($num_entries, $pagenumber, $limit)->asLinks() ?>
</td>
</tr>
</tfoot>
......
<tr>
<td data-sort-value="<?= htmlReady(is_a($version, WikiPage::class) ? $version->name : $version->page->name) ?>">
<a href="<?= is_a($version, WikiPage::class) ? $controller->page($version) : $controller->version($version) ?>">
<?= htmlReady(is_a($version, WikiPage::class) ? $version->name : $version->page->name) ?>
</a>
</td>
<td>
<?
$oldversion = $version->predecessor ? $version->predecessor->content : '';
$oldcontent = strip_tags(wikiReady($oldversion));
$content = strip_tags(wikiReady($version->content));
while ($content && $oldcontent && $content[0] == $oldcontent[0]) {
$content = substr($content, 1);
$oldcontent = substr($oldcontent, 1);
}
while ($content && $oldcontent && $content[strlen($content) - 1] == $oldcontent[strlen($oldcontent) - 1]) {
$content = substr($content, 0, -1);
$oldcontent = substr($oldcontent, 0, -1);
}
if ($content) {
echo nl2br(htmlReady(mila($content, 300)));
} elseif ($oldcontent) {
echo _('Gelöscht') . ': ' . nl2br(htmlReady($oldcontent));
} else {
echo nl2br(strip_tags(wikiReady(mila($version->content, 300))));
}
?></td>
<? $user = User::find($version->user_id) ?>
<td data-sort-value="<?= htmlReady($user ? $user->getFullName() : _('unbekannt')) ?>">
<?
if ($user) {
echo Avatar::getAvatar($user->id)->getImageTag(Avatar::SMALL);
echo ' ';
echo htmlReady($user->getFullName());
} else {
echo _('unbekannt');
}
?></td>
<td data-sort-value="<?= htmlReady(is_a($version, WikiPage::class) ? $version->chdate : $version->mkdate) ?>">
<? $chdate = is_a($version, WikiPage::class) ? $version->chdate : $version->mkdate ?>
<?= $chdate > 0 ? date('d.m.Y H:i:s', $chdate) : _('unbekannt') ?>
</td>
</tr>
......@@ -2,16 +2,17 @@
/**
* @var WikiPage|WikiVersion $version
* @var Course_WikiController $controller
* @var string $diff
*/
?>
<h3>
<a href="<?= is_a($version, WikiPage::class) ? $controller->page($version) : $controller->version($version) ?>">
<? $chdate = is_a($version, WikiPage::class) ? $version->chdate : $version->mkdate ?>
<?= sprintf(
_('Version %1$s, geändert von %2$s am %3$s.'),
htmlReady($version->versionnumber),
_('Version %1$u, geändert von %2$s am %3$s.'),
$version->versionnumber,
htmlReady($version->user ? $version->user->getFullName() : _('unbekannt')),
$chdate > 0 ? date('d.m.Y H:i:s', $chdate) : _('unbekannt')) ?>
$chdate ? strftime('%x %X', $chdate) : _('unbekannt')) ?>
</a>
</h3>
<div class="wiki_diffs">
......
......@@ -22,9 +22,12 @@
* @property int|null $mkdate database column
* @property User|null $user belongs_to User
* @property Course $course belongs_to Course
* @property-read mixed $parent additional field
* @property-read mixed $children additional field
* @property-read mixed $config additional field
* @property WikiVersion[]|SimpleORMapCollection $versions
* @property WikiOnlineEditingUser[]|SimpleORMapCollection $onlineeditingusers
* @property-read WikiPage $parent additional field
* @property-read WikiPage[] $children additional field
* @property-read WikiVersion|null $predecessor additional field
* @property-read int $versionnumber additional field
*/
class WikiPage extends SimpleORMap implements PrivacyObject
{
......@@ -57,25 +60,25 @@ class WikiPage extends SimpleORMap implements PrivacyObject
];
$config['additional_fields']['parent'] = [
'get' => function ($page) {
return \WikiPage::find($page->parent_id);
'get' => function (WikiPage $page): ?WikiPage {
return self::find($page->parent_id);
}
];
$config['additional_fields']['children'] = [
'get' => function ($page) {
'get' => function (WikiPage $page): array {
return self::findBySQL('parent_id = ?', [
$page->id
]);
}
];
$config['additional_fields']['predecessor'] = [
'get' => function ($page) {
'get' => function (WikiPage $page): ?WikiVersion {
return $page->versions ? $page->versions[0] : null;
}
];
$config['additional_fields']['versionnumber'] = [
'get' => function ($page) {
'get' => function (WikiPage $page): int {
return count($page->versions) + 1;
}
];
......@@ -92,7 +95,7 @@ class WikiPage extends SimpleORMap implements PrivacyObject
$this->user_id = User::findCurrent()->id;
if (
!$this->isNew()
&& $this->content['content'] !== $this->content_db['content']
&& $this->content['content'] !== $this->content_db['content']
&& (
$this->content_db['user_id'] !== $this->content['user_id']
|| $this->content_db['chdate'] < time() - 60 * 30
......@@ -121,7 +124,7 @@ class WikiPage extends SimpleORMap implements PrivacyObject
/**
* Returns whether this page is visible to the given user.
* @param mixed $user User object or id
* @param string|null $user_id User id
* @return boolean indicating whether the page is visible
*/
public function isReadable(?string $user_id = null): bool
......@@ -166,7 +169,7 @@ class WikiPage extends SimpleORMap implements PrivacyObject
/**
* Returns whether this page is editable to the given user.
* @param string $user_id the ID of the user
* @param string|null $user_id the ID of the user
* @return boolean indicating whether the page is editable
*/
public function isEditable(?string $user_id = null): bool
......@@ -203,7 +206,7 @@ class WikiPage extends SimpleORMap implements PrivacyObject
* @param string $range_id Course id
* @return WikiPage
*/
public static function getStartPage($range_id)
public static function getStartPage($range_id): WikiPage
{
$page_id = CourseConfig::get($range_id)->WIKI_STARTPAGE_ID;
......@@ -212,7 +215,6 @@ class WikiPage extends SimpleORMap implements PrivacyObject
}
$page = new WikiPage();
$pagename = _('Startseite');
$page->content = _('Dieses Wiki ist noch leer.');
if ($page->isEditable()) {
$page->content .= ' ' . _("Bearbeiten Sie es!\nNeue Seiten oder Links werden einfach durch Eingeben von [nop][[Wikinamen]][/nop] in doppelten eckigen Klammern angelegt.");
......@@ -244,11 +246,11 @@ class WikiPage extends SimpleORMap implements PrivacyObject
/**
* Tests if a given Wikipage name (keyword) is a valid ancestor for this page.
*
* @param string ancestor Wikipage name to be tested to be an ancestor
* @param string $ancestor Wikipage name to be tested to be an ancestor
* @return boolean true if ok, false if not
*
*/
public function isValidAncestor($ancestor)
public function isValidAncestor($ancestor): bool
{
if ($this->name === 'WikiWikiWeb' || $this->name === $ancestor) {
return false;
......@@ -267,10 +269,10 @@ class WikiPage extends SimpleORMap implements PrivacyObject
/**
* Retrieve an array of all descending WikiPages (recursive).
*
* @return array Array of all descendant WikiPages
* @return WikiPage[] Array of all descendant WikiPages
*
*/
public function getDescendants()
public function getDescendants(): array
{
$descendants = [];
......@@ -281,6 +283,9 @@ class WikiPage extends SimpleORMap implements PrivacyObject
return $descendants;
}
/**
* @return array
*/
public function getOnlineUsers(): array
{
$users = [];
......
......@@ -171,6 +171,10 @@ $grid-gap: 0;
#{"--"}group-color-7: $petrol;
#{"--"}group-color-8: $brown;
#{"--"}avatar-small: $avatar-small;
#{"--"}avatar-medium: $avatar-medium;
#{"--"}avatar-normal: $avatar-normal;
#{"--"}transition-duration: $transition-duration;
#{"--"}transition-duration-slow: $transition-duration-slow;
......
......@@ -185,3 +185,18 @@ article.studip.wiki {
.wiki_highlight {
background-color: var(--yellow);
}
ul.wiki_authors {
list-style-type: none;
padding: 0;
li {
margin-bottom: 5px;
}
a {
background-position: left top;
background-repeat: no-repeat;
background-size: var(--avatar-small);
display: block;
min-height: var(--avatar-small);
padding-left: calc(var(--avatar-small) + 1ex);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment