<?php /** * wiki.php - wiki controller * * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>, Rasmus Fuhse <fuhse@data-quest.de> * @license GPL2 or any later version * @since 3.3 */ class Course_WikiController extends AuthenticatedController { protected $allow_nobody = true; protected $with_session = true; protected $_autobind = true; public function before_filter(&$action, &$args) { 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) { if ($page_id === null) { $page_id = $this->range->getConfiguration()->WIKI_STARTPAGE_ID; } Navigation::activateItem('/course/wiki/start'); $this->page = new WikiPage($page_id); $sidebar = Sidebar::Get(); if (!$this->page->isReadable()) { throw new AccessDeniedException(); } if (!$this->page->isNew()) { // Table of Contents/QuickLinks $widget = Sidebar::Get()->addWidget(new ListWidget()); $widget->setTitle(_('QuickLinks')); $quicklinks = WikiPage::findOneBySQL("`name` = 'toc' AND `range_id` = ?", [$this->range->id]); $toc_content = $quicklinks ? '<div class="wikitoc" id="00toc">' . wikiReady($quicklinks['content'], true, $this->range->id) . '</div>' : ''; $toc_content_empty = !trim(strip_tags($toc_content)); if ( (!$quicklinks && $GLOBALS['perm']->have_studip_perm($this->range->getConfiguration()->WIKI_CREATE_PERMISSION, $this->range->id)) || ($quicklinks && $quicklinks->isEditable()) ) { $extra = sprintf( '<a href="%s">%s</a>', URLHelper::getLink('dispatch.php/course/wiki/edit_toc'), $toc_content_empty ? Icon::create('add')->asImg(['title' => _('Erstellen')]) : Icon::create('edit')->asImg(['title' => _('Bearbeiten')]) ); $widget->setExtra($extra); } $element = new WidgetElement($toc_content_empty ? _('Keine QuickLinks vorhanden') : $toc_content); $element->icon = Icon::create('link-intern'); $widget->addElement($element); } $this->edit_perms = $this->range->getConfiguration()->WIKI_CREATE_PERMISSION; if ( $this->edit_perms === 'all' || $GLOBALS['perm']->have_studip_perm($this->edit_perms, $this->range->id) ) { $actions = new ActionsWidget(); $actions->addLink( _('Neue Wiki-Seite anlegen'), $this->new_pageURL($this->page->id), Icon::create('add'), ['data-dialog' => 'width=700'] ); if ($GLOBALS['perm']->have_studip_perm('tutor', $this->range->id)) { $actions->addLink( _('Wiki verwalten'), $this->adminURL(), Icon::create('admin'), ['data-dialog' => 'width=700'] ); } if ($GLOBALS['perm']->have_studip_perm('tutor', $this->range->id)) { //minimum perm tutor necessary $actions->addLink( _('Seiten aus Veranstaltung importieren'), $this->importURL(), Icon::create('import'), ['data-dialog' => 'width=700'] ); } $sidebar->addWidget($actions); } if (!$this->page->isNew()) { //then the wiki is not empty $search = new SearchWidget($this->searchURL()); $search->addNeedle( _('Im Wiki suchen'), 'search', true ); $sidebar->addWidget($search); $sidebar->addWidget($this->getViewsWidget($this->page, 'read')); $exports = new ExportWidget(); $exports->addLink( _('Als PDF exportieren'), $this->pdfURL($this->page->id), Icon::create('file-pdf') ); $sidebar->addWidget($exports); } $startPage = WikiPage::find($this->range->getConfiguration()->WIKI_STARTPAGE_ID); $this->contentbar = ContentBar::get() ->setTOC(CoreWiki::getTOC($this->page)) ->setIcon(Icon::create('wiki')); if (!$this->page->isNew()) { $this->contentbar->setInfo(sprintf( _('Version %1$s, geändert von %2$s <br> am %3$s'), $this->page->versionnumber, sprintf( '<a href="%s">%s</a>', URLHelper::getLink('dispatch.php/profile', ['username' => get_username($this->page['user_id'])]), htmlReady(get_fullname($this->page['user_id'])) ), date('d.m.Y H:i:s', $this->page['chdate']) )); $action_menu = ActionMenu::get(); if ($this->page->isEditable()) { $action_menu->addLink( $this->editURL($this->page), _('Bearbeiten'), Icon::create('edit') ); $action_menu->addLink( $this->pagesettingsURL($this->page->id), _('Seiteneinstellungen'), Icon::create('settings'), ['data-dialog' => 'width=700'] ); if (count($this->page->versions) > 0) { $action_menu->addLink( $this->ask_deletingURL($this->page), _('Seite / Version löschen'), Icon::create('trash'), ['data-dialog' => 'size=auto'] ); } else { $action_menu->addButton( 'delete', _('Seite löschen'), Icon::create('trash'), ['data-confirm' => _('Wollen Sie wirklich die komplette Seite löschen?'), 'form' => 'delete_page'] ); } } $action_menu->addLink( '#', _('Als Vollbild anzeigen'), Icon::create('screen-full'), ['class' => 'fullscreen-trigger hidden-medium-down'] ); $this->contentbar->setActionMenu($action_menu); } } public function pagesettings_action(WikiPage $page) { if (!$page->isEditable()) { throw new AccessDeniedException(); } $options = [ '' => _('Keine') ]; $descendants_ids = array_map( function ($d) { return $d->id; }, $page->getDescendants() ); WikiPage::findEachBySQL( function (WikiPage $p) use ($page, &$options, $descendants_ids) { if ($p->id !== $page->id && !in_array($p->id, $descendants_ids)) { $options[$p->id] = $p->name; } }, 'range_id = ? ORDER BY name', [$this->range->id] ); $groups = [ 'all' => _('Alle'), 'tutor' => _('Lehrende und Tutoren/Tutorinnen'), 'dozent' => _('Nur Lehrende') ]; Statusgruppen::findEachBySQL( function (Statusgruppen $group) use (&$groups) { $groups[$group->id] = $group->name; }, '`range_id` = ? ORDER BY `name`', [$this->range->id] ); $oldname = $page->name; $this->form = \Studip\Forms\Form::fromSORM( $page, [ 'legend' => _('Seiteneinstellung'), 'fields' => [ 'name' => [ 'label' => _('Titel der Seite'), 'required' => true, 'validate' => function ($value, $input) { $page = $input->getContextObject(); if ($value !== $page->name) { $page2 = WikiPage::findOneBySQL('`range_id` = :range_id AND `name` = :name', [ 'range_id' => $page['range_id'], 'name' => $value ]); if ($page2) { return _('Dieser Name ist bereits vergeben.'); } } return true; } ], 'parent_id' => [ 'label' => _('Übergeordnete Seite im Inhaltsverzeichnis'), 'type' => 'select', 'options' => $options ], 'read_permission' => [ 'label' => _('Lesezugriff für'), 'type' => 'select', 'options' => $groups ], 'write_permission' => [ 'label' => _('Schreibzugriff für'), 'type' => 'select', 'options' => $groups ] ] ], $this->pagesettingsURL($page->id) )->addStoreCallback(function ($form, $values) use ($oldname) { if ($values['name'] === $oldname) { return; } $page = $form->getLastPart()->getContextObject(); $other_pages = WikiPage::findBySQL( "`range_id` = :range_id AND `page_id` != :page_id AND `content` LIKE :search", [ 'page_id' => $page->id, 'range_id' => $page['range_id'], 'search' => '%' . $oldname . '%', ] ); foreach ($other_pages as $p2) { $p2['content'] = preg_replace( "/\[\[\s*" . $oldname . "\b/", "[[ " . $values['name'], $p2['content'] ); if ($p2->isDirty()) { $p2['user_id'] = User::findCurrent()->id; $p2->store(); } } })->validate(); if (Request::isPost()) { $this->form->store(); PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.')); if ($page->isReadable()) { $this->redirect($this->pageURL($page->id)); } else { $this->redirect($this->allpagesURL()); } return; } $this->render_form($this->form); } public function ask_deleting_action(WikiPage $page) { if (!$page->isEditable()) { throw new AccessDeniedException(); } PageLayout::setTitle(_('Was genau soll gelöscht werden?')); } public function delete_action(WikiPage $page) { if (!Request::isPost() || !$page->isEditable() || !CSRFProtection::verifyRequest()) { throw new AccessDeniedException(); } $name = $page->name; $page->delete(); PageLayout::postSuccess(sprintf(_('Die Seite %s wurde gelöscht.'), htmlReady($name))); $this->redirect($this->allpagesURL()); } public function deleteversion_action(WikiPage $page, $version_id = null) { if (!Request::isPost() || !$page->isEditable() || !CSRFProtection::verifyRequest()) { throw new AccessDeniedException(); } if ($version_id === null) { $version = $page->versions[0]; if ($version) { $page['name'] = $version['name']; $page['content'] = $version['content']; $page['user_id'] = $version['user_id']; $page['chdate'] = $version['mkdate']; $page->store(); $version->delete(); } else { $page->delete(); } } else { $version = WikiVersion::find($version_id); if ($version['page_id'] === $page->id) { $version->delete(); } } PageLayout::postSuccess(_('Version wurde gelöscht.')); if (Request::get('redirect_to') === 'page') { $this->redirect($this->page($page)); } else { $this->redirect($this->history($page)); } } public function allpages_action() { $this->pages = WikiPage::findBySQL( "`range_id` = ? ORDER BY `name` ASC", [$this->range->id] ); if (count($this->pages) === 0) { $this->redirect($this->pageURL()); return; } Navigation::activateItem('/course/wiki/allpages'); if ($GLOBALS['perm']->have_studip_perm('tutor', $this->range->id)) { $actions = new ActionsWidget(); $actions->addLink( _('Neue Wiki-Seite anlegen'), $this->new_pageURL(), Icon::create('add'), ['data-dialog' => 'width=700'] ); $actions->addLink( _('Wiki verwalten'), $this->adminURL(), Icon::create('admin'), ['data-dialog' => 'width=700'] ); Sidebar::Get()->addWidget($actions); } $search = new SearchWidget($this->searchURL()); $search->addNeedle( _('Im Wiki suchen'), 'search', true ); Sidebar::Get()->addWidget($search); $widget = new ExportWidget(); $widget->addLink( _('Alle Wiki-Seiten als PDF exportieren'), $this->pdf_allpagesURL(), Icon::create('file-pdf') ); Sidebar::Get()->addWidget($widget); } public function admin_action() { if (!$GLOBALS['perm']->have_studip_perm('tutor', $this->range->id)) { throw new AccessDeniedException(); } $this->config = $this->range->getConfiguration(); $this->pages = WikiPage::findBySQL( '`range_id` = ? ORDER BY `name` ASC', [$this->range->id] ); } public function store_course_config_action() { if (!$GLOBALS['perm']->have_studip_perm('tutor', $this->range->id)) { throw new AccessDeniedException(); } CSRFProtection::verifyUnsafeRequest(); $this->config = $this->range->getConfiguration(); $this->config->store('WIKI_STARTPAGE_ID', trim(Request::option('wiki_startpage_id'))); if ( $this->config->WIKI_CREATE_PERMISSION === 'all' || $GLOBALS['perm']->have_studip_perm($this->config->WIKI_CREATE_PERMISSION, Context::getId()) ) { $this->config->store('WIKI_CREATE_PERMISSION', trim(Request::option('wiki_create_permission'))); } if ( $this->config->WIKI_RENAME_PERMISSION === 'all' || $GLOBALS['perm']->have_studip_perm($this->config->WIKI_RENAME_PERMISSION, $this->range->id) ) { $this->config->store('WIKI_RENAME_PERMISSION', trim(Request::option('wiki_rename_permission'))); } PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.')); if (WikiPage::countBySQL('`range_id` = ? ORDER BY `name` ASC', [$this->range->id]) > 0) { $this->redirect($this->allpagesURL()); } else { $this->redirect($this->pageURL()); } } public function edit_action(WikiPage $page = null) { if ($page->isNew() && Request::get('keyword')) { $name = trim(Request::get('keyword')); $page = WikiPage::findOneBySQL('`name` = :name AND `range_id` = :range_id', [ 'name' => $name, 'range_id' => Context::getId() ]); if (!$page) { $page = WikiPage::create([ 'name' => $name, 'range_id' => Context::getId(), 'parent_id' => Request::option('parent_id', $this->range->getConfiguration()->WIKI_STARTPAGE_ID), ]); } $this->redirect($this->editURL($page)); return; } if ($page->isNew() || !$page->isEditable()) { throw new AccessDeniedException(); } Navigation::activateItem('/course/wiki/start'); $user = User::findCurrent(); WikiOnlineEditingUser::deleteBySQL( "`page_id` = :page_id AND `chdate` < UNIX_TIMESTAMP() - :threshold", [ 'page_id' => $page->id, 'threshold' => WikiOnlineEditingUser::$threshold ] ); $pageData = [ 'page_id' => $page->id, 'user_id' => $user->id ]; $online_user = WikiOnlineEditingUser::findOneBySQL( '`page_id` = :page_id AND `user_id` = :user_id', $pageData ); if (!$online_user) { $online_user = WikiOnlineEditingUser::build($pageData); } $editingUsers = WikiOnlineEditingUser::countBySQL( "`page_id` = ? AND `editing` = 1 AND `user_id` != ?", [$page->id, $user->id] ); $online_user->editing = $editingUsers === 0 ? 1 : 0; $online_user->chdate = time(); $online_user->store(); $this->me_online = $online_user; $this->online_users = WikiOnlineEditingUser::findBySQL( "JOIN `auth_user_md5` USING (`user_id`) WHERE `page_id` = ? ORDER BY Nachname, Vorname", [$page->id] ); $startPage = WikiPage::find($this->range->getConfiguration()->WIKI_STARTPAGE_ID); $this->contentbar = ContentBar::get() ->setTOC(CoreWiki::getTOC($page)) ->setIcon(Icon::create('wiki')) ->setInfo(_('Zuletzt gespeichert') .': '. '<studip-date-time :timestamp="Math.floor(lastSaveDate / 1000)" :relative="true"></studip-date-time>'); } public function apply_editing_action(WikiPage $page) { if (!$page->isEditable() || !Request::isPost()) { throw new AccessDeniedException(); } $user = User::findCurrent(); $pageData = [ 'page_id' => $page->id, 'user_id' => $user->id ]; $online_user = WikiOnlineEditingUser::findOneBySQL( '`page_id` = :page_id AND `user_id` = :user_id', $pageData ); if (!$online_user) { $online_user = WikiOnlineEditingUser::build($pageData); } $editingUsers = WikiOnlineEditingUser::countBySQL( "`page_id` = ? AND `editing` = 1 AND `user_id` != ?", [$page->id, $user->id] ); if ($editingUsers > 0) { $online_user->editing_request = 1; } else { $online_user->editing = 1; } $online_user->store(); $output = [ 'me_online' => $online_user->toArray(), 'users' => $page->getOnlineUsers() ]; $this->render_json($output); } public function leave_editing_action(WikiPage $page) { if (!$page->isEditable()) { throw new AccessDeniedException(); } $user = User::findCurrent(); $pageData = [ 'page_id' => $page->id, 'user_id' => $user->id ]; WikiOnlineEditingUser::deleteBySQL( '`page_id` = :page_id AND `user_id` = :user_id', $pageData ); $this->redirect($this->pageURL($page)); } public function delegate_edit_mode_action(WikiPage $page, $user_id) { if (!$page->isEditable() || !Request::isPost()) { throw new AccessDeniedException(); } $user = User::findCurrent(); $pageData = [ 'page_id' => $page->id, 'user_id' => $user->id ]; $online_user_me = WikiOnlineEditingUser::findOneBySQL( '`page_id` = :page_id AND `user_id` = :user_id', $pageData ); if (!$online_user_me->editing) { $this->render_json([ 'error' => 'not_in_edit_mode' ]); } $online_user_them = WikiOnlineEditingUser::findOneBySQL( '`page_id` = :page_id AND `user_id` = :user_id', ['page_id' => $page->id, 'user_id' => $user_id] ); if (!$online_user_them || !$online_user_them->editing_request) { $this->render_json([ 'error' => 'user_not_requested_edit_mode' ]); return; } $online_user_me->editing = 0; $online_user_me->store(); $online_user_them->editing_request = 1; //that will be set to 0 by the user themself $online_user_them->editing = 1; $online_user_them->store(); $this->render_json([ 'message' => 'edit mode delegated' ]); } public function save_action(WikiPage $page) { CSRFProtection::verifyUnsafeRequest(); if (!$page->isEditable()) { throw new AccessDeniedException(); } $page->content = \Studip\Markup::markAsHtml(trim(Request::get('content'))); $user = User::findCurrent(); if ($page->isDirty()) { $page['user_id'] = $user->id; $page->store(); } $pageData = [ 'page_id' => $page->id, 'user_id' => $user->id ]; WikiOnlineEditingUser::deleteBySQL( '`page_id` = :page_id AND `user_id` = :user_id', $pageData ); PageLayout::postSuccess(_('Die Seite wurde gespeichert.')); $this->redirect($this->pageURL($page)); } public function edit_toc_action() { $quicklinks = WikiPage::findOneBySQL( "`name` = 'toc' AND `range_id` = ?", [$this->range->id] ); if (!$quicklinks) { $quicklinks = WikiPage::create([ 'range_id' => $this->range->id, 'name' => 'toc' ]); } $this->redirect($this->editURL($quicklinks)); } public function newpages_action() { 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 `wiki_pages` WHERE `wiki_pages`.`range_id` = :range_id AND `wiki_pages`.`chdate` > :threshold AND `wiki_pages`.`user_id` != :me "); $statement->execute([ 'range_id' => $this->range->id, 'threshold' => $this->last_visit, 'me' => User::findCurrent()->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 = []; } } public function history_action(WikiPage $page) { Navigation::activateItem('/course/wiki/start'); Sidebar::Get()->addWidget($this->getViewsWidget($this->page, 'history')); } public function version_action(WikiVersion $version) { Navigation::activateItem('/course/wiki/start'); Sidebar::Get()->addWidget($this->getViewsWidget($version->page, 'history')); $startPage = WikiPage::find($this->range->getConfiguration()->WIKI_STARTPAGE_ID); $this->contentbar = ContentBar::get() ->setTOC(CoreWiki::getTOC($version->page)) ->setIcon(Icon::create('wiki')) ->setInfo(sprintf( _('Version %1$s vom %2$s'), $version->versionnumber, date('d.m.Y H:i:s', $version['mkdate']) )); } public function blame_action(WikiPage $page) { Navigation::activateItem('/course/wiki/start'); Sidebar::Get()->addWidget($this->getViewsWidget($page, 'blame')); $version = WikiVersion::findOneBySQL( "`page_id` = ? ORDER BY `mkdate` LIMIT 1", [$page->id] ); if (!$version) { $version = $page; } $lines = Studip\Markup::removeHtml($version->content); $lines = explode("\n", str_replace("\r", '', $lines)); $this->line_versions = array_fill(0, count($lines), $version); $this->diffarray = WikiDiffer::toDiffLineArray($version->content, $version->user_id); $differ = new WikiDiffer(); $k = 0; while ($version && !is_a($version, WikiPage::class)) { $version = $version->successor; if ($version) { $diffarray2 = WikiDiffer::toDiffLineArray($version->content, $version->user_id); $newarray = $differ->arr_compare('diff', $this->diffarray, $diffarray2); $this->diffarray = []; //completely rewrite $this->diffarray with newer version foreach ($newarray as $number => $i) { if ($i->status['diff'] !== '-') { $this->diffarray[] = $i; } if ($i->status['diff'] === '+') { $this->line_versions[$number] = $version; } } } $k++; if ($k > 100) { break; } } $this->diffarray[] = null; } public function diff_action(WikiPage $page) { Navigation::activateItem('/course/wiki/start'); Sidebar::Get()->addWidget($this->getViewsWidget($page, 'diff')); $this->diffs = []; $last_version = null; foreach (array_reverse($page->versions->getArrayCopy()) as $version) { if ($last_version === null) { $last_version = $version; $this->diffs[] = [ 'diff' => WikiDiffer::doDiff( Studip\Markup::removeHtml($version->content), '' ), 'version' => $version ]; continue; } $this->diffs[] = [ 'diff' => WikiDiffer::doDiff( Studip\Markup::removeHtml($version->content), Studip\Markup::removeHtml($last_version->content) ), 'version' => $version ]; $last_version = $version; } $this->diffs[] = [ 'diff' => WikiDiffer::doDiff( Studip\Markup::removeHtml($page->content), $last_version !== null ? Studip\Markup::removeHtml($last_version->content) : '' ), 'version' => $page ]; } public function versiondiff_action (WikiPage $page, $version_id = null) { if ($version_id !== null) { $this->version = WikiVersion::find($version_id); } if ( ($this->version && $this->version->page_id != $page->id) || !$page->isReadable() ) { throw new AccessDeniedException(); } PageLayout::setTitle(_('Änderungen dieser Version')); $content = $this->version ? $this->version->content : $page->content; $predecessor = $this->version ? $this->version->predecessor : $page->predecessor; $this->diff = WikiDiffer::doDiff( Studip\Markup::removeHtml($content), Studip\Markup::removeHtml($predecessor ? $predecessor->content : '') ); if (!$this->version) { $this->version = $page; } } public function new_page_action($parent_id = null) { if ( $this->range->getConfiguration()->WIKI_CREATE_PERMISSION !== 'all' && !$GLOBALS['perm']->have_studip_perm($this->range->getConfiguration()->WIKI_CREATE_PERMISSION, $this->range->id) ) { throw new AccessDeniedException(); } $page = new WikiPage(); $page->parent_id = $parent_id ?? $this->range->getConfiguration()->WIKI_STARTPAGE_ID; $parent_id = $parent_id ?? $this->range->getConfiguration()->WIKI_STARTPAGE_ID; PageLayout::setTitle(_('Neue Wikiseite erstellen')); $options = [ '' => _('Keine') ]; WikiPage::findEachBySQL( function (WikiPage $p) use (&$options) { $options[$p->id] = $p->name; }, 'range_id = ? ORDER BY name', [$this->range->id] ); $this->form = \Studip\Forms\Form::fromSORM( $page, [ 'legend' => _('Daten'), 'fields' => [ 'range_id' => [ 'type' => 'no', 'mapper' => function () { return $this->range->id; } ], 'user_id' => [ 'type' => 'no', 'mapper' => function () { return User::findCurrent()->id; } ], 'name' => [ 'required' => true, 'label' => _('Name der Seite'), 'validate' => function ($value, $input) { $name_count = WikiPage::countBySql('`name` = :name AND `range_id` = :range_id', [ 'name' => $value, 'range_id' => $this->range->id ]); if ($name_count === 0) { return true; } else { return _('Name existiert schon.'); } } ], 'parent_id' => [ 'label' => _('Übergeordnete Seite im Inhaltsverzeichnis'), 'type' => 'select', 'options' => $options ], 'autocreate_links' => [ 'label' => _('Den Seitennamen der neuen Seite automatisch in anderen Wikiseiten verlinken.'), 'type' => 'checkbox', 'permission' => WikiPage::countBySql("`range_id` = ?", [$this->range->id]) > 0 ] ] ], $this->allpagesURL() )->addStoreCallback(function ($form, $values) { $page = $form->getLastPart()->getContextObject(); $other_pages = WikiPage::countBySQL( "`range_id` = :range_id AND `page_id` != :page_id", [ 'page_id' => $page->id, 'range_id' => $page->range_id, ] ); if ($other_pages == 0) { $this->range->getConfiguration()->store('WIKI_STARTPAGE_ID', $page->id); } if (Request::bool('autocreate_links')) { $pages = WikiPage::findBySQL( "`range_id` = :range_id AND `content` LIKE :search", [ 'range_id' => $this->range->id, 'search' => '%' . $values['name'] . '%', ] ); foreach ($pages as $page) { $page->content = preg_replace( "/\b" . $values['name'] . "\b/", '[[ ' . $values['name'] . ' ]]', $page->content ); if ($page->isDirty()) { $page['user_id'] = User::findCurrent()->id; $page->store(); } } } } )->setURL($this->new_pageURL($parent_id)) ->validate(); if (Request::isPost()) { $this->form->store(); $this->redirect($this->editURL($page)); } else { $this->render_form($this->form); } } public function search_action() { Navigation::activateItem('/course/wiki/allpages'); if (Request::get('search')) { $statement = DBManager::get()->prepare(" SELECT `wiki_pages`.`page_id`, `wiki_pages`.`name` LIKE :searchterm AS `is_in_name`, `wiki_pages`.`content` LIKE :searchterm AS `is_in_content`, `wiki_versions`.`content` LIKE :searchterm AS `is_in_history`, `wiki_versions`.`name` LIKE :searchterm AS `is_in_old_name`, `wiki_versions`.`version_id` FROM `wiki_pages` LEFT JOIN `statusgruppe_user` ON (`statusgruppe_user`.`statusgruppe_id` = `wiki_pages`.`read_permission`) LEFT JOIN `wiki_versions` ON (`wiki_versions`.`page_id` = `wiki_pages`.`page_id` AND (`wiki_versions`.`content` LIKE :searchterm OR `wiki_versions`.`name` LIKE :searchterm)) WHERE `wiki_pages`.`range_id` = :range_id AND (`wiki_pages`.`name` LIKE :searchterm OR `wiki_pages`.`content` LIKE :searchterm OR `wiki_versions`.`content` LIKE :searchterm OR `wiki_versions`.`name` LIKE :searchterm ) AND ( `wiki_pages`.`read_permission` = 'all' OR `statusgruppe_user`.`user_id` = :user_id OR `wiki_pages`.`read_permission` = :perm OR (`wiki_pages`.`read_permission` = 'tutor' AND :perm = 'dozent') ) ORDER BY `is_in_name` DESC, `is_in_content` DESC, `is_in_old_name` DESC, `is_in_history` DESC "); $search = str_replace(['\\', '_', '%'], ['\\\\', '\\_', '\\%'], Request::get('search')); $perm = $GLOBALS['perm']->get_perm(); if (in_array($perm, ['admin', 'root'])) { $perm = 'dozent'; } $statement->execute([ 'range_id' => $this->range->id, 'searchterm' => '%' . $search . '%', 'perm' => $perm, 'user_id' => User::findCurrent()->id ]); $data = $statement->fetchAll(PDO::FETCH_ASSOC); $this->pages = []; foreach ($data as $row) { if (!isset($this->pages[$row['page_id']])) { $this->pages[$row['page_id']] = [ 'page' => WikiPage::find($row['page_id']), 'is_in_name' => $row['is_in_name'], 'is_in_content' => $row['is_in_content'], 'is_in_history' => $row['is_in_history'], 'is_in_old_name' => $row['is_in_old_name'], 'versions' => [$row['version_id']] ]; } else { $this->pages[$row['page_id']]['versions'][] = $row['version_id']; $this->pages[$row['page_id']]['is_in_name'] = max($this->pages[$row['page_id']]['is_in_name'], $row['is_in_name']); $this->pages[$row['page_id']]['is_in_content'] = max($this->pages[$row['page_id']]['is_in_content'], $row['is_in_content']); $this->pages[$row['page_id']]['is_in_history'] = max($this->pages[$row['page_id']]['is_in_history'], $row['is_in_history']); $this->pages[$row['page_id']]['is_in_old_name'] = max($this->pages[$row['page_id']]['is_in_old_name'], $row['is_in_old_name']); } } } else { $this->redirect($this->pageURL()); } $search = new SearchWidget($this->searchURL()); $search->addNeedle( _('Im Wiki suchen'), 'search', true ); Sidebar::Get()->addWidget($search); } public function searchpage_action(WikiPage $page) { if (!$page->isReadable()) { throw new AccessDeniedException(); } Navigation::activateItem('/course/wiki/allpages'); if (!Request::get('search')) { throw new Exception('No search text.'); } $search = str_replace(['\\', '_', '%'], ['\\\\', '\\_', '\\%'], Request::get('search')); $this->versions = WikiVersion::findBySQL("`page_id` = :page_id AND (`wiki_versions`.`content` LIKE :searchterm OR `wiki_versions`.`name` LIKE :searchterm) ORDER BY `mkdate` DESC ", [ 'page_id' => $page->id, 'searchterm' => '%' . $search . '%' ]); $search = new SearchWidget($this->searchURL()); $search->addNeedle( _('Im Wiki suchen'), 'search', true ); Sidebar::Get()->addWidget($search); } public function pdf_action(WikiPage $page) { if (!$page->isReadable()) { throw new AccessDeniedException(); } $document = new ExportPDF(); $document->SetTitle(_('Wiki: ') . $page->name); $document->setHeaderTitle(sprintf(_('Wiki von "%s"'), $this->range->name)); $document->setHeaderSubtitle(sprintf(_('Seite: %s'), $page->name)); $document->addPage(); $content = $page->content; //remove wiki-links: $content = preg_replace('/\[\[[^|\]]*\|([^]]*)\]\]/', '$1', $content); $content = preg_replace('/\[\[([^|\]]*)\]\]/', '$1', $content); $document->writeHTML($content); $this->render_pdf( $document, Context::getHeaderLine() . ' - ' . $page->name . '.pdf', true ); } public function pdf_allpages_action() { if (!$GLOBALS['perm']->have_studip_perm('user', Context::getId())) { throw new AccessDeniedException(); } $pages = WikiPage::findBySql('`range_id` = ? ORDER BY `name` ASC', [Context::getId()]); $document = new ExportPDF(); $document->SetTitle(_('Wiki: ') . Context::get()->name); $document->setHeaderTitle(sprintf(_('Wiki von "%s"'), Context::get()->name)); foreach ($pages as $page) { if (!$page->isReadable()) { continue; } $document->setHeaderSubtitle(sprintf(_('Seite: %s'), $page->name)); $document->addPage(); // We need the @ in front since TCPDF might throw warning that can lead // to errors viewing the document $content = $page->content; //remove wiki-links: $content = preg_replace('/\[\[[^|\]]*\|([^]]*)\]\]/', '$1', $content); $content = preg_replace('/\[\[([^|\]]*)\]\]/', '$1', $content); //@$document->addContent($content); @$document->writeHTML($content); } $this->render_pdf( $document, Context::getHeaderLine() . '.pdf', true ); } protected function getViewsWidget(WikiPage $page, string $action): ViewsWidget { $views = new ViewsWidget(); $link = $views->addLink( _('Lesen'), $this->pageURL($page) )->setActive($action === 'read'); $link = $views->addLink( _('Seiten-Historie'), $this->historyURL($page) )->setActive($action === 'history'); $link = $views->addLink( _('Änderungsliste'), $this->diffURL($page) )->setActive($action === 'diff'); $link = $views->addLink( _('Text mit Autor/-innenzuordnung'), $this->blameURL($page) )->setActive($action === 'blame'); return $views; } /** * This action is responsible for importing wiki pages into the wiki * of a course from another course. */ public function import_action() { $edit_perms = $this->range->getConfiguration()->WIKI_CREATE_PERMISSION; if ($edit_perms !== 'dozent') { $edit_perms = 'tutor'; } if (!$GLOBALS['perm']->have_studip_perm($edit_perms, $this->range->id)) { throw new AccessDeniedException(_('Sie haben keine Berechtigung, Änderungen an Wiki-Seiten vorzunehmen!')); } if (!$this->range) { PageLayout::postError( _('Die ausgewählte Veranstaltung wurde nicht gefunden!') ); } $this->course_search = new QuickSearch( 'selected_range_id', new MyCoursesSearch( 'Seminar_id', $GLOBALS['perm']->get_perm(), [ 'userid' => User::findCurrent()->id, 'exclude' => [$this->range->id], 'institutes' => array_column(Institute::getMyInstitutes(), 'Institut_id') ], 's.`Seminar_id` IN ( SELECT range_id FROM wiki_pages WHERE range_id = s.`Seminar_id` )' ) ); $this->course_search->fireJSFunctionOnSelect( "function() {jQuery(this).closest('form').submit();}" ); $this->show_wiki_page_form = false; $this->bad_course_search = false; $this->success = false; //The following steps are identical for the search and the import. if (Request::submittedSome('selected_range_id', 'import')) { CSRFProtection::verifyUnsafeRequest(); //Search for wiki pages in the selected course: $this->selected_range_id = Request::option('selected_range_id'); $this->selected_course = Course::find($this->selected_range_id); if (!$this->selected_course) { $this->bad_course_search = true; return; } $this->wiki_pages = WikiPage::findBySQL( "`range_id` = ? ORDER BY `name`", [$this->selected_course->id] ); $this->show_wiki_page_form = true; } //The import required additional functionality: if (Request::submitted('import')) { CSRFProtection::verifyUnsafeRequest(); $this->selected_wiki_page_ids = Request::getArray('selected_wiki_page_ids'); if (!$this->selected_wiki_page_ids) { PageLayout::postInfo(_('Es wurden keine Wiki-Seiten ausgewählt!')); return; } $selected_wiki_pages = []; foreach ($this->selected_wiki_page_ids as $id) { $wiki_page = WikiPage::find($id); if ($wiki_page) { $selected_wiki_pages[] = $wiki_page; } } if (!$selected_wiki_pages) { PageLayout::postError(_('Es wurden keine Wiki-Seiten gefunden!')); return; } $errors = []; $new_ids = []; foreach ($selected_wiki_pages as $selected_page) { if ($selected_page->isReadable()) { $count = WikiPage::countBySql( "`range_id` = :range_id AND `name` = :name", [ 'range_id' => $this->range->id, 'name' => $selected_page['name'] ] ); if ($count === 0) { $new_page = WikiPage::build([ 'range_id' => $this->range->id, 'parent_id'=> null, 'user_id' => $selected_page->user_id, 'name' => $selected_page->name, 'content' => $selected_page->content, 'chdate' => $selected_page->chdate, ]); if (!$new_page->store()) { $errors[] = sprintf( _('Fehler beim Import der Wiki-Seite %s!'), htmlReady($new_page->name) ); } $new_ids[$selected_page->id] = $new_page->id; } } } foreach ($new_ids as $old_page_id => $new_page_id) { $old_page = WikiPage::find($old_page_id); $new_page = WikiPage::find($new_page_id); $new_page->parent_id = $new_ids[$old_page->parent_id] ?? null; $new_page->store(); } if ($errors) { PageLayout::postError( _('Die folgenden Fehler traten beim Import auf:'), $errors ); } else { $this->show_wiki_page_form = false; $this->success = true; PageLayout::postSuccess( ngettext( 'Die Wiki-Seite wurde importiert! Sie ist unter der Ansicht "Alle Seiten" erreichbar.', 'Die Wiki-Seiten wurden importiert! Sie sind unter der Ansicht "Alle Seiten" erreichbar.', count($selected_wiki_pages) ) ); } } } /** * @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; } public function findTextualHits($text, $search, $length = 80) { $content = Studip\Markup::removeHtml($text); $offset = 0; $output = []; // find all occurences while ($offset < mb_strlen($content)) { $pos = mb_stripos($content, Request::get('search'), $offset); if ($pos === false) { break; } $offset = $pos + 1; // show max 200 chars $fragment = ''; $split_fragment = preg_split( '/(' . preg_quote(Request::get('search'), '/') . ')/i', mb_substr($content, max(0, $pos - floor($length / 2)), $length), -1, PREG_SPLIT_DELIM_CAPTURE ); for ($i = 0; $i < count($split_fragment); ++$i) { if ($i % 2) { $fragment .= '<span class="wiki_highlight">'; $fragment .= htmlready($split_fragment[$i], false); $fragment .= '</span>'; } else { $fragment .= htmlready($split_fragment[$i], false); } } $found_in_fragment = (count($split_fragment) - 1) / 2; // number of hits in fragment $output[] = '...' . $fragment . '...'; } return implode('<br>', $output); } }