Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A wiki for phpBB with version history, side-by-side compare, and a moderation qu
- Edits can wait for approval: moderators get a **Pending articles** list with Approve and Disapprove. Approving a version older than the live one is blocked, so a stale draft can't overwrite newer content.
- Moderators can take an article offline or delete it, and articles can be made sticky or set to redirect.
- A contributions page for every member, at `/wiki/contributions/<username>`, lists every version they saved across all articles, newest first. Author names on articles and in the version history link to it. Versions waiting for approval, and those of switched-off articles, show only to moderators and to the author.
- Files can be uploaded to an article by members who hold the **Can upload files to articles** permission. They are checked against the board's allowed file types and size limit, stored outside public view under `store/phpbbmodders_wiki/`, and read only through the wiki, which checks that the reader may see the article. In the text, `[[File:Diagram.png]]` shows an uploaded image and `[[File:notes.zip|the notes]]` links to any other file. The member who uploaded a file, or a moderator who may delete versions, can remove it, and deleting an article deletes its files.
- Notifications when an edit is waiting for approval.
- The Wiki link in the board header shows the Wikipedia icon by default. Under **Extensions > Wiki > Settings** in the ACP the icon can be changed to any [Font Awesome](https://fontawesome.com/v4.7.0/icons/) icon name, or left blank for no icon.
- A **Start discussion** button on each article creates its discussion topic in a forum chosen under **Extensions > Wiki > Settings** in the ACP, and links the topic to the article. It shows to members who hold the "Can change the discussion topic for an article" permission and may post in that forum, and stays hidden until a forum is chosen.
Expand Down
22 changes: 22 additions & 0 deletions config/services.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
parameters:
tables.wiki.article: '%core.table_prefix%wiki_article'
tables.wiki.file: '%core.table_prefix%wiki_file'

services:
_defaults:
Expand Down Expand Up @@ -36,6 +37,23 @@ services:
- '%core.root_path%'
- '%core.php_ext%'

phpbbmodders.wiki.files:
class: phpbbmodders\wiki\wiki\files
arguments:
- '@auth'
- '@cache'
- '@config'
- '@dbal.conn'
- '@files.factory'
- '@controller.helper'
- '@request'
- '@template'
- '@user'
- '%tables.wiki.file%'
- '%tables.wiki.article%'
- '%core.root_path%'
- '%core.php_ext%'

phpbbmodders.wiki.main:
class: phpbbmodders\wiki\controller\main
arguments:
Expand All @@ -49,6 +67,7 @@ services:
- '@phpbbmodders.wiki.compare'
- '@phpbbmodders.wiki.view'
- '@phpbbmodders.wiki.discussion'
- '@phpbbmodders.wiki.files'
- '%core.root_path%'
- '%core.php_ext%'
phpbbmodders.wiki.listener:
Expand Down Expand Up @@ -98,6 +117,7 @@ services:
- '@controller.helper'
- '@user'
- '%tables.wiki.article%'
- '@phpbbmodders.wiki.files'
phpbbmodders.wiki.view:
class: phpbbmodders\wiki\wiki\view
arguments:
Expand All @@ -113,6 +133,7 @@ services:
- '%core.php_ext%'
- '@phpbbmodders.wiki.links'
- '@phpbbmodders.wiki.discussion'
- '@phpbbmodders.wiki.files'
phpbbmodders.wiki.discussion:
class: phpbbmodders\wiki\wiki\discussion
arguments:
Expand All @@ -136,6 +157,7 @@ services:
- '@controller.helper'
- '@language'
- '@phpbbmodders.wiki.links'
- '@phpbbmodders.wiki.files'
tags:
- { name: event.listener }

Expand Down
16 changes: 15 additions & 1 deletion controller/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class main
/** @var \phpbbmodders\wiki\wiki\discussion */
protected $discussion;

/** @var \phpbbmodders\wiki\wiki\files */
protected $files;

/** @var string phpbb_root_path */
protected $phpbb_root_path;

Expand All @@ -61,10 +64,11 @@ class main
* @param \phpbbmodders\wiki\wiki\compare $compare Diff Wiki object
* @param \phpbbmodders\wiki\wiki\view $view View Wiki object
* @param \phpbbmodders\wiki\wiki\discussion $discussion Start discussion object
* @param \phpbbmodders\wiki\wiki\files $files Uploaded files object
* @param string $phpbb_root_path
* @param string $php_ext
*/
public function __construct(\phpbb\auth\auth $auth, \phpbb\controller\helper $helper, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, \phpbbmodders\wiki\wiki\delete $delete, \phpbbmodders\wiki\wiki\edit $edit, \phpbbmodders\wiki\wiki\compare $compare, \phpbbmodders\wiki\wiki\view $view, \phpbbmodders\wiki\wiki\discussion $discussion, $phpbb_root_path, $php_ext)
public function __construct(\phpbb\auth\auth $auth, \phpbb\controller\helper $helper, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, \phpbbmodders\wiki\wiki\delete $delete, \phpbbmodders\wiki\wiki\edit $edit, \phpbbmodders\wiki\wiki\compare $compare, \phpbbmodders\wiki\wiki\view $view, \phpbbmodders\wiki\wiki\discussion $discussion, \phpbbmodders\wiki\wiki\files $files, $phpbb_root_path, $php_ext)
{
$this->auth = $auth;
$this->helper = $helper;
Expand All @@ -76,6 +80,7 @@ public function __construct(\phpbb\auth\auth $auth, \phpbb\controller\helper $he
$this->compare = $compare;
$this->view = $view;
$this->discussion = $discussion;
$this->files = $files;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
}
Expand Down Expand Up @@ -149,6 +154,15 @@ public function article($article)
case 'start_discussion':
return $this->discussion->start($article);

case 'upload':
return $this->files->upload($article);

case 'file':
return $this->files->serve($article, $id);

case 'delete_file':
return $this->files->delete($article, $id);

default:
return $this->view->view_article($article, $id);
}
Expand Down
16 changes: 0 additions & 16 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,3 @@ BBCode comfortably.

A one-click "Restore this version" in the version history. Today the only
way is to approve the old version again.

## File uploads

Let editors upload images and files to an article, the way posts take
attachments. phpBB's attachment system is built around posts and private
messages, so the wiki either needs its own upload handling or a careful
reuse of phpBB's file upload classes with its own table.

- Uploads belong to an article and survive across its versions; deleting
an article deletes them.
- Reuse the board's allowed file types, size limits and quota where
possible, with a separate permission to upload.
- Serve files through the extension's own controller, which checks the
reader's wiki permissions, never straight from a public folder.
- An uploaded image can be placed in the article text, for example with
`[[File:Diagram.png]]`.
4 changes: 4 additions & 0 deletions event/listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ public function permissions($event)
'lang' => 'ACL_U_WIKI_EDIT_TOPIC',
'cat' => 'wiki'
),
'u_wiki_upload' => array(
'lang' => 'ACL_U_WIKI_UPLOAD',
'cat' => 'wiki'
),
'u_wiki_set_sticky' => array(
'lang' => 'ACL_U_WIKI_SET_STICKY',
'cat' => 'wiki'
Expand Down
71 changes: 69 additions & 2 deletions event/wikilink_listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ class wikilink_listener implements EventSubscriberInterface
/** @var \phpbbmodders\wiki\wiki\links */
protected $links;

/** @var \phpbbmodders\wiki\wiki\files */
protected $files;

/**
* @param \phpbb\controller\helper $helper Controller helper object
* @param \phpbb\language\language $language Language object
* @param \phpbbmodders\wiki\wiki\links $links Wiki links service
*/
public function __construct(\phpbb\controller\helper $helper, \phpbb\language\language $language, \phpbbmodders\wiki\wiki\links $links)
public function __construct(\phpbb\controller\helper $helper, \phpbb\language\language $language, \phpbbmodders\wiki\wiki\links $links, \phpbbmodders\wiki\wiki\files $files)
{
$this->files = $files;
$this->helper = $helper;
$this->language = $language;
$this->links = $links;
Expand Down Expand Up @@ -72,6 +76,18 @@ public function configure_wikilink($event)
return;
}

// [[File:name]] and [[File:name|alt text]] embed one of the article's uploaded files.
// It is added first, and WIKILINK leaves "File:" names to it.
$file_tag = $configurator->tags->add('WIKIFILE');
$file_tag->attributes->add('name');
$file_tag->attributes->add('alt')->required = false;
$file_tag->template = '<span class="wikifile" data-wiki-file="{@name}" data-wiki-alt="{@alt}"></span>';

$configurator->Preg->match(
'/\[\[\s*File:\s*(?<name>' . self::NAME_PATTERN . '?)\s*(?:\|\s*(?<alt>[^\[\]\n]{1,255}?)\s*)?\]\]/',
'WIKIFILE'
);

$tag = $configurator->tags->add('WIKILINK');
$tag->attributes->add('name');
$tag->attributes->add('text')->required = false;
Expand All @@ -80,7 +96,7 @@ public function configure_wikilink($event)
. '<xsl:otherwise><xsl:value-of select="@name"/></xsl:otherwise></xsl:choose></a>';

$configurator->Preg->match(
'/\[\[\s*(?<name>' . self::NAME_PATTERN . '?)\s*(?:\|\s*(?<text>[^\[\]\n]{1,255}?)\s*)?\]\]/',
'/\[\[\s*(?!File:)(?<name>' . self::NAME_PATTERN . '?)\s*(?:\|\s*(?<text>[^\[\]\n]{1,255}?)\s*)?\]\]/',
'WIKILINK'
);
}
Expand All @@ -95,6 +111,13 @@ public function configure_wikilink($event)
public function link_wikilinks($event)
{
$html = $event['html'];

if (strpos($html, 'class="wikifile"') !== false)
{
$html = $this->embed_files($html);
$event['html'] = $html;
}

if (strpos($html, 'class="wikilink"') === false)
{
return;
Expand Down Expand Up @@ -123,4 +146,48 @@ public function link_wikilinks($event)
. htmlspecialchars($this->language->lang('WIKI_LINK_NEW_ARTICLE'), ENT_COMPAT, 'UTF-8') . '">';
}, $html);
}

/**
* Replace each [[File:name]] placeholder with the file's image or link.
*
* Files are looked up among those of the article being rendered. Anywhere else
* (a forum post, a preview with no article) and for names that were never
* uploaded, the text stays as plain text.
*
* @param string $html
* @return string
*/
protected function embed_files($html)
{
$pattern = '/<span class="wikifile" data-wiki-file="([^"]*)" data-wiki-alt="([^"]*)"><\/span>/';
if (!preg_match_all($pattern, $html, $matches))
{
return $html;
}

$article = $this->files->get_context();
$names = array_map(function ($name) {
return htmlspecialchars_decode($name, ENT_QUOTES);
}, $matches[1]);
$found = ($article !== null) ? $this->files->get_files_by_name($article, $names) : array();

return preg_replace_callback($pattern, function ($match) use ($article, $found) {
$name = htmlspecialchars_decode($match[1], ENT_QUOTES);
$alt = ($match[2] !== '') ? $match[2] : $match[1];

if (!isset($found[$name]))
{
return '<span class="wikifile wikifile-missing" title="' . htmlspecialchars($this->language->lang('WIKI_FILE_MISSING', $name), ENT_COMPAT, 'UTF-8') . '">' . $alt . '</span>';
}

$url = $this->files->get_url($article, $found[$name]['file_id']);

if ($found[$name]['is_image'])
{
return '<img class="postimage wikifile-image" src="' . $url . '" alt="' . $alt . '">';
}

return '<a class="wikifile-link" href="' . $url . '" download>' . $alt . '</a>';
}, $html);
}
}
10 changes: 10 additions & 0 deletions language/de/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@
'CONTRIBUTION_LIVE' => 'Aktive Version',
'CONTRIBUTION_REPLACED' => 'Durch eine neuere Version ersetzt',
'CONTRIBUTION_PENDING' => 'Wartet auf Freigabe oder inaktiv',
'WIKI_FILES' => 'Dateien',
'WIKI_NO_FILES' => 'Zu diesem Artikel wurden keine Dateien hochgeladen.',
'WIKI_UPLOAD_FILE' => 'Datei hochladen',
'WIKI_DELETE_FILE' => 'Datei löschen',
'WIKI_FILE_UPLOADED' => 'Die Datei „%s“ wurde hochgeladen.',
'WIKI_FILE_EXISTS' => 'Dieser Artikel hat bereits eine Datei namens „%s“. Lösche sie zuerst oder benenne die neue Datei um.',
'WIKI_FILE_DELETED' => 'Die Datei „%s“ wurde gelöscht.',
'WIKI_FILE_NOT_FOUND' => 'Die Datei existiert nicht.',
'WIKI_CONFIRM_DELETE_FILE' => 'Bist du sicher, dass du die Datei „%s“ löschen möchtest?',
'WIKI_FILE_MISSING' => 'Die Datei „%s“ wurde nicht zu diesem Artikel hochgeladen.',
'START_DISCUSSION' => 'Diskussion starten',
'DISCUSSION_READ_ARTICLE' => 'Artikel lesen',
'DISCUSSION_NO_FORUM' => 'Für das Wiki wurde kein Diskussionsforum eingerichtet.',
Expand Down
1 change: 1 addition & 0 deletions language/de/permissions_wiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'ACL_M_WIKI_DELETE_ARTICLE' => 'Kann einen kompletten Artikel löschen',
'ACL_M_WIKI_VIEW_INACTIVE' => 'Kann inaktive Artikel anschauen',
'ACL_U_WIKI_EDIT_TOPIC' => 'Kann das Diskussionsthema zu einem Artikel ändern',
'ACL_U_WIKI_UPLOAD' => 'Kann Dateien zu Artikeln hochladen',
'ACL_U_WIKI_SET_ACTIVE' => 'Kann die aktive Version eines Artikels ändern',
'ACL_U_WIKI_SET_STICKY' => 'Kann einen Artikel als wichtig markieren',
'ACL_U_WIKI_SET_REDIRECT' => 'Kann eine Weiterleitung in einem Artikel einrichten',
Expand Down
10 changes: 10 additions & 0 deletions language/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@
'CONTRIBUTION_LIVE' => 'Live version',
'CONTRIBUTION_REPLACED' => 'Replaced by a newer version',
'CONTRIBUTION_PENDING' => 'Awaiting approval or inactive',
'WIKI_FILES' => 'Files',
'WIKI_NO_FILES' => 'No files have been uploaded to this article.',
'WIKI_UPLOAD_FILE' => 'Upload file',
'WIKI_DELETE_FILE' => 'Delete file',
'WIKI_FILE_UPLOADED' => 'The file “%s” has been uploaded.',
'WIKI_FILE_EXISTS' => 'This article already has a file called “%s”. Delete it first or rename the new one.',
'WIKI_FILE_DELETED' => 'The file “%s” has been deleted.',
'WIKI_FILE_NOT_FOUND' => 'The file does not exist.',
'WIKI_CONFIRM_DELETE_FILE' => 'Are you sure you want to delete the file “%s”?',
'WIKI_FILE_MISSING' => 'The file “%s” has not been uploaded to this article.',
'START_DISCUSSION' => 'Start discussion',
'DISCUSSION_READ_ARTICLE' => 'Read the article',
'DISCUSSION_NO_FORUM' => 'No discussion forum has been set up for the Wiki.',
Expand Down
1 change: 1 addition & 0 deletions language/en/permissions_wiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'ACL_U_WIKI_VERSIONS' => 'Can view versions of an article',
'ACL_U_WIKI_DELETE' => 'Can delete versions of an article',
'ACL_U_WIKI_EDIT_TOPIC' => 'Can change the discussion topic for an article',
'ACL_U_WIKI_UPLOAD' => 'Can upload files to articles',
'ACL_U_WIKI_SET_ACTIVE' => 'Can change the active version of an article',
'ACL_U_WIKI_SET_STICKY' => 'Can mark an article as important',
'ACL_U_WIKI_SET_REDIRECT' => 'Can set up a redirect on an article',
Expand Down
66 changes: 66 additions & 0 deletions migrations/add_file_uploads.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
*
* Wiki extension for the phpBB Forum Software package
*
* @copyright (c) 2026, phpBB Modders, https://www.phpbbmodders.com/
* @license GNU General Public License, version 2 (GPL-2.0)
*
*/

namespace phpbbmodders\wiki\migrations;

/**
* Adds the table that records an article's uploaded files, and the permission to upload them.
*/
class add_file_uploads extends \phpbb\db\migration\migration
{
public static function depends_on()
{
return array(
'\phpbbmodders\wiki\migrations\add_discussion_forum',
);
}

public function update_schema()
{
return array(
'add_tables' => array(
$this->table_prefix . 'wiki_file' => array(
'COLUMNS' => array(
'file_id' => array('UINT', null, 'auto_increment'),
// The article's URL name: files belong to the article, not to one version of it
'article_url' => array('VCHAR:255', ''),
// The name people see and write in [[File:name]]
'file_name' => array('VCHAR:255', ''),
// The name on disk, which is random and has no extension
'physical_name' => array('VCHAR:255', ''),
'mimetype' => array('VCHAR:100', ''),
'file_size' => array('UINT', 0),
'is_image' => array('BOOL', 0),
'user_id' => array('UINT', 0),
'file_time' => array('TIMESTAMP', 0),
),
'PRIMARY_KEY' => 'file_id',
),
),
);
}

public function revert_schema()
{
return array(
'drop_tables' => array(
$this->table_prefix . 'wiki_file',
),
);
}

public function update_data()
{
return array(
// Nobody has it until an administrator gives it
array('permission.add', array('u_wiki_upload', true)),
);
}
}
Loading
Loading