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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ A wiki for phpBB with version history, side-by-side compare, and a moderation qu
- Tables, written as HTML in an article: `<table>`, `<caption>`, `<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<th>` and `<td>`, with `colspan` and `rowspan` on a cell, and `class="infobox"` on a table, which floats it at the right of the text as an infobox (full width on a narrow screen). BBCode and `[[links]]` work inside cells. Any other attribute (`style`, `onclick`, any other `class`) is dropped, and any other element (`script`, `iframe`) stays plain text. Like headings, tables work in wiki articles only; in a forum post `<table>` stays plain text. After upgrading, disable and enable the extension so the board rebuilds its cache.
- Boxes the reader opens and closes: `<details><summary>Title</summary>...</details>` shows just the title until it is clicked, with no JavaScript; `<details open>` starts it opened, and `<details class="navbox">` gives it the look of Wikipedia's navigation boxes (a shaded title bar over a table of links). Any other attribute is dropped. Wiki articles only, like tables.
- Centred text: `<center>...</center>` in an article centres what it holds (BBCode, links and templates work inside), as on MediaWiki. Its attributes are dropped. It works in wiki articles only, like tables.
- HTML entities written in an article, such as `&nbsp;`, `&#32;` or `&mdash;`, show as the characters they stand for, as on MediaWiki, in the text and in headings, link texts and link targets. `&lt;b&gt;` shows as "<b>", never as markup, and an unknown entity stays as written. Like headings, this works in wiki articles only; a forum post shows entities as written. After upgrading, disable and enable the extension so the board rebuilds its cache. Articles saved earlier show their entities as written until they are saved again or reparsed with `php bin/phpbbcli.php reparser:reparse phpbbmodders_wiki_article`.
- HTML entities written in an article, such as `&nbsp;`, `&#32;` or `&mdash;`, show as the characters they stand for, as on MediaWiki, in the text and in headings, link texts and link targets. `&lt;b&gt;` shows as "<b>", never as markup, and an unknown entity stays as written. Like headings, this works in wiki articles only; a forum post shows entities as written. After upgrading, disable and enable the extension so the board rebuilds its cache. Articles saved earlier are reparsed in the background after the update (see [Templates](#templates)), and then show their entities as characters.
- Templates: `{{Name}}`, `{{Name|first|second}}` and `{{Name|key=value}}` in an article include the live version of the article `Template:Name`, so a notice or infobox is written once and edited in one place. See [Templates](#templates).
- Starter templates: install ready-made hatnotes, message boxes and text helpers adapted from Wikipedia from the ACP, and optionally be told when new ones are available. See [Starter templates](#starter-templates).
- Categories: `[[Category:Name]]` in an article puts it in a category. The article lists its categories at the bottom, `/wiki/Category:Name` lists the articles in a category, and `/wiki/categories` lists every category. See [Categories](#categories).
Expand Down Expand Up @@ -80,6 +80,7 @@ An article
- A template can give table rows or cells (a text that starts with `<tr>`, `<tbody>`, `<td>` and so on), to be called between the `<table>` tags of an article: `<table>{{Row|a|1}}{{Row|b|2}}</table>`.
- Limits, so one article cannot slow the board down: at most 500 template calls written in an article, 2000 calls and functions in all (including those inside templates), and about 500,000 characters of output. Past a limit the rest of the calls show "Too many templates" where they stand.
- A template can include other templates, ten levels deep at most; a template that includes itself, directly or through another template, shows an error where the loop is (it cannot stop its own recursion with `{{#if:}}`, as on Wikipedia). A call to a template that does not exist is a red link to `Template:Name`.
- A call can hold other calls in its values, as in `{{A|{{B|{{C}}}}}}`, and so can a value's default (`{{{name|{{B}}}}}`), 8 levels deep. **Template nesting depth** under **Extensions > Wiki > Settings** changes that, from 3 to 20; deeper calls show as written. After a change, the saved articles are updated in the background over the next few page views.
- The text of a template is parsed like article text, so it can use BBCode, `[[links]]`, headings and tables, and nothing an article could not.
- The page of a template shows its documentation (what is inside `<noinclude>`, with its calls worked out, so it can show examples), then **Template text**: the text an article gets, as it is written and not worked out, with how many articles use it. `<includeonly>` text is left off the page. **What links here** lists the articles that use the template.
- **Templates** at `/wiki/templates`, linked from the overview, lists every template A to Z with its short description (the article's description field), how many articles use it, and when it was last edited. A **How to use templates** box above the list explains `{{Name}}`, values and the Start templates, plus, for members who can edit, how to make one. Under each template, **Preview and how to write it** opens the call to write, with a placeholder for each value it takes, and its documentation as a preview. Templates are left out of the overview's article lists.
Expand Down Expand Up @@ -122,7 +123,7 @@ An article
A call that cannot be worked out, such as `{{#expr: 1/0}}`, shows a red message where it stands. An unknown `{{#function:}}` does too.

The arguments of a function are trimmed of spaces and line breaks before it works on them.
- After upgrading, disable and enable the extension so the board rebuilds its cache. Articles saved earlier hold `{{...}}` as plain text until they are saved again or reparsed with `php bin/phpbbcli.php reparser:reparse phpbbmodders_wiki_article`.
- After upgrading, disable and enable the extension so the board rebuilds its cache. The saved articles are then reparsed in the background, a batch on each of the next few page views, so `{{...}}` written before the update becomes a call. To do it at once instead, run `php bin/phpbbcli.php reparser:reparse phpbbmodders_wiki_article`.

### Starter templates

Expand All @@ -145,7 +146,7 @@ Write `[[Category:Name]]` anywhere in a wiki article to put it in a category. Th
- Only the live version of an article counts. A forum post keeps `[[Category:Name]]` as plain text, like the rest of the wiki markup.
- A template page counts for the categories it names outside `<includeonly>`, so `<noinclude>[[Category:Infobox templates]]</noinclude>` puts the template in that category. A category inside `<includeonly>` puts every article that uses the template in it, as on MediaWiki: `{{Stub}}` can put its articles in "Stubs". When a template changes, the articles that use it are worked out again.
- Which articles are in which category is kept in a table, worked out whenever an article's live version changes. After the update that adds it, a cron task fills it for the articles the board already has, a batch at a time on ordinary page views; until it is done, the category pages read the articles' text as before (without templates' categories, sub-categories or sort keys).
- After upgrading, disable and enable the extension so the board rebuilds its cache. Articles saved before sort keys keep the key they were written with only once they are saved again or reparsed. Articles saved before this build keep `[[Category:Name]]` as text until they are saved again or reparsed with `php bin/phpbbcli.php reparser:reparse phpbbmodders_wiki_article`.
- After upgrading, disable and enable the extension so the board rebuilds its cache. Articles saved before sort keys keep the key they were written with only once they are saved again or reparsed. Articles saved before categories existed are reparsed in the background after the update (see [Templates](#templates)), and then join their categories.

## Search

Expand Down
21 changes: 20 additions & 1 deletion acp/main_module.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
namespace phpbbmodders\wiki\acp;

/**
* The Wiki settings page: the header link's icon, and which forum "Start discussion" creates topics in.
* The Wiki settings page: the header link's icon, edit conflicts, the feed, how deep template calls
* may nest, and which forum "Start discussion" creates topics in.
*/
class main_module
{
Expand Down Expand Up @@ -57,7 +58,12 @@ public function main($id, $mode)
$auto_watch = $request->variable('wiki_auto_watch', 0) ? 1 : 0;
$edit_conflict = $request->variable('wiki_edit_conflict', '');
$lock_minutes = $request->variable('wiki_edit_lock_minutes', 0);
$nesting = $request->variable('wiki_template_nesting', 0);

if ($nesting < \phpbbmodders\wiki\wiki\templates::CALL_NESTING_MIN || $nesting > \phpbbmodders\wiki\wiki\templates::CALL_NESTING_MAX)
{
trigger_error($language->lang('WIKI_TEMPLATE_NESTING_INVALID', \phpbbmodders\wiki\wiki\templates::CALL_NESTING_MIN, \phpbbmodders\wiki\wiki\templates::CALL_NESTING_MAX) . adm_back_link($this->u_action), E_USER_WARNING);
}
if (!\phpbbmodders\wiki\wiki\edit_lock::valid_mode($edit_conflict))
{
trigger_error($language->lang('WIKI_EDIT_CONFLICT_INVALID') . adm_back_link($this->u_action), E_USER_WARNING);
Expand Down Expand Up @@ -101,6 +107,15 @@ public function main($id, $mode)
$config->set('wiki_edit_conflict', $edit_conflict);
$config->set('wiki_edit_lock_minutes', $lock_minutes);

// A new depth changes how articles are read: rebuild the parser, and reparse the
// saved articles in the background so they follow it
if ($nesting !== $phpbb_container->get('phpbbmodders.wiki.templates')->nesting())
{
$config->set('wiki_template_nesting', $nesting);
$phpbb_container->get('text_formatter.cache')->invalidate();
$phpbb_container->get('phpbbmodders.wiki.article_reparse')->queue();
}

trigger_error($language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action));
}

Expand All @@ -123,6 +138,10 @@ public function main($id, $mode)
'WIKI_EDIT_LOCK_MINUTES' => $edit_lock->minutes(),
'WIKI_EDIT_LOCK_MINUTES_MIN' => \phpbbmodders\wiki\wiki\edit_lock::MINUTES_MIN,
'WIKI_EDIT_LOCK_MINUTES_MAX' => \phpbbmodders\wiki\wiki\edit_lock::MINUTES_MAX,
'WIKI_TEMPLATE_NESTING' => $phpbb_container->get('phpbbmodders.wiki.templates')->nesting(),
'WIKI_TEMPLATE_NESTING_MIN' => \phpbbmodders\wiki\wiki\templates::CALL_NESTING_MIN,
'WIKI_TEMPLATE_NESTING_MAX' => \phpbbmodders\wiki\wiki\templates::CALL_NESTING_MAX,
'S_WIKI_REPARSE_PENDING' => $phpbb_container->get('phpbbmodders.wiki.article_reparse')->pending(),
'S_WIKI_FEED_OPTIONS' => $feed_options,
'S_WIKI_AUTO_WATCH' => !isset($config['wiki_auto_watch']) || !empty($config['wiki_auto_watch']),
'WIKI_ICON' => isset($config['wiki_icon']) ? $config['wiki_icon'] : \phpbbmodders\wiki\wiki\icon::DEFAULT_ICON,
Expand Down
4 changes: 4 additions & 0 deletions adm/style/acp_wiki_settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ <h1>{L_ACP_WIKI_SETTINGS}</h1>
<dt><label for="wiki_edit_lock_minutes">{L_WIKI_EDIT_LOCK_MINUTES}{L_COLON}</label><br><span>{L_WIKI_EDIT_LOCK_MINUTES_EXPLAIN}</span></dt>
<dd><input id="wiki_edit_lock_minutes" type="number" min="{WIKI_EDIT_LOCK_MINUTES_MIN}" max="{WIKI_EDIT_LOCK_MINUTES_MAX}" name="wiki_edit_lock_minutes" value="{WIKI_EDIT_LOCK_MINUTES}"></dd>
</dl>
<dl>
<dt><label for="wiki_template_nesting">{L_WIKI_TEMPLATE_NESTING}{L_COLON}</label><br><span>{L_WIKI_TEMPLATE_NESTING_EXPLAIN}</span><!-- IF S_WIKI_REPARSE_PENDING --><br><span><strong>{L_WIKI_REPARSE_PENDING}</strong></span><!-- ENDIF --></dt>
<dd><input id="wiki_template_nesting" type="number" min="{WIKI_TEMPLATE_NESTING_MIN}" max="{WIKI_TEMPLATE_NESTING_MAX}" name="wiki_template_nesting" value="{WIKI_TEMPLATE_NESTING}"></dd>
</dl>
<dl>
<dt><label for="wiki_feed_format">{L_WIKI_FEED_FORMAT}{L_COLON}</label><br><span>{L_WIKI_FEED_FORMAT_EXPLAIN}</span></dt>
<dd><select id="wiki_feed_format" name="wiki_feed_format">{S_WIKI_FEED_OPTIONS}</select></dd>
Expand Down
14 changes: 14 additions & 0 deletions config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ services:
- [set_name, [cron.task.phpbbmodders.wiki.category_members_build]]
tags:
- { name: cron.task }
# Reparsing the saved articles after the way markup is read has changed
phpbbmodders.wiki.article_reparse:
class: phpbbmodders\wiki\wiki\article_reparse
arguments:
- '@config'
- '@phpbbmodders.wiki.text_reparser.article_text'
phpbbmodders.wiki.cron.task.article_reparse:
class: phpbbmodders\wiki\cron\task\article_reparse
arguments:
- '@phpbbmodders.wiki.article_reparse'
calls:
- [set_name, [cron.task.phpbbmodders.wiki.article_reparse]]
tags:
- { name: cron.task }
# Per-article edit protection: which groups may edit an article, and the page that sets it
phpbbmodders.wiki.protection:
class: phpbbmodders\wiki\wiki\protection
Expand Down
49 changes: 49 additions & 0 deletions cron/task/article_reparse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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\cron\task;

/**
* Reparses the saved articles a batch at a time on ordinary page views, after an update or a
* change of the template nesting depth (see wiki\article_reparse), then stops until the next one.
*/
class article_reparse extends \phpbb\cron\task\base
{
/** @var \phpbbmodders\wiki\wiki\article_reparse */
protected $article_reparse;

/**
* @param \phpbbmodders\wiki\wiki\article_reparse $article_reparse
*/
public function __construct(\phpbbmodders\wiki\wiki\article_reparse $article_reparse)
{
$this->article_reparse = $article_reparse;
}

public function run()
{
$this->article_reparse->run_batch();
}

public function is_runnable()
{
return $this->article_reparse->pending();
}

/**
* Every request may take the next batch until all are done.
*
* @return bool
*/
public function should_run()
{
return true;
}
}
10 changes: 6 additions & 4 deletions docs/TODO/todo-wiki-markup.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,15 @@ string functions `#len`, `#pos`, `#rpos`, `#sub`, `#replace` and `#explode`, `#t
`NAMESPACE` and `SITENAME`, and the `CURRENT...`, `LOCAL...` and `REVISION...` words (see `wiki/templates.php`,
`wiki/expression.php` and `wiki/text_tools.php`). Calls are worked out as text, before the article
is parsed, so a parameter can be another call and only the chosen branch of a function is worked
out. Still to do:
out. A call written in an article, and a parameter's default value, may hold other calls 8 levels
deep, set from 3 to 20 under Extensions > Wiki > Settings (the pattern costs under 1 ms on large
articles even at 20); deeper calls stay text. After a change, and after the update that brought it,
a cron task reparses the saved articles a batch at a time (see `wiki/article_reparse.php`): a
migration cannot, as the wiki's listeners are not loaded while the extension is enabled, and a
reparse then strips every article's wiki markup. Still to do:

- Not planned: `#invoke` (Lua modules). `plural` uses the English rule and `gender` always gives the
neutral form, since phpBB keeps no gender.
- Deeper nesting: a template call holds other calls in its parameters only three levels deep, and a
default value holds only one level of `{{template}}`; anything deeper stays text. Lift both limits,
keeping a cap so one article still cannot slow the board down.
- Only if needed: a cache of rendered articles, cleared when a template changes. Expansion runs on
every view. Measured on a MySQL board: about 1 ms for each template call written in an article (a
table of 400 calls to a template took about 400 ms longer than the same table written out), one
Expand Down
Binary file modified docs/images/wiki-acp-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions event/wikilink_listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ class wikilink_listener implements EventSubscriberInterface

const NAME_PATTERN = self::NAME_CLASS . '{1,255}';

/** How many {{templates}} deep a template call may hold others in its parameters. */
const TEMPLATE_NESTING = 3;

/** HTML elements a wiki article may use to write a table. */
const TABLE_ELEMENTS = array('table', 'caption', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td');
Expand Down Expand Up @@ -96,14 +94,15 @@ public static function getSubscribedEvents()
* The pattern for {{Name|parameters}}. s9e cannot read a recursive pattern, so
* templates inside the braces are matched down to a fixed depth.
*
* @param int $nesting How many levels of calls a call may hold
* @return string
*/
protected static function template_pattern()
protected static function template_pattern($nesting)
{
// Anything but a brace, a {{{parameter}}}, or a brace on its own
$plain = '[^{}]++|\\{\\{\\{[^{}]*+\\}\\}\\}|\\{(?![{])|\\}(?![}])';
$inner = '(?:' . $plain . ')*?';
for ($level = 0; $level < self::TEMPLATE_NESTING; $level++)
for ($level = 0; $level < \phpbbmodders\wiki\wiki\templates::nesting_in_range($nesting); $level++)
{
$inner = '(?:' . $plain . '|\\{\\{(?![{])' . $inner . '\\}\\})*?';
}
Expand Down Expand Up @@ -193,7 +192,8 @@ public function configure_wikilink($event)
// be, so a template can give a table row
$template->template = '<template class="wikitemplate" data-wiki-template="{@name}" data-wiki-call="{@call}"></template>';

$configurator->Preg->match(self::template_pattern(), 'WIKITEMPLATE');
// The board's nesting setting; changing it rebuilds this configuration
$configurator->Preg->match(self::template_pattern($this->templates->nesting()), 'WIKITEMPLATE');

$tag = $configurator->tags->add('WIKILINK');
foreach (array('name' => 'decode_entities_in_name', 'section' => 'decode_entities', 'text' => 'decode_entities') as $attribute => $decode)
Expand Down
7 changes: 6 additions & 1 deletion language/en/acp_wiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@
'WIKI_EDIT_LOCK_MINUTES' => 'Minutes an editor holds an article',
'WIKI_EDIT_LOCK_MINUTES_EXPLAIN' => 'How long the notice names an editor, or the lock lasts, after their last sign of work. It is extended every few minutes while their edit form is open, and when they preview, so it only runs out once they have left.',

'WIKI_ONLY_BBCODES' => 'BBCodes only for the wiki',
'WIKI_TEMPLATE_NESTING' => 'Template nesting depth',
'WIKI_TEMPLATE_NESTING_EXPLAIN' => 'How many levels of templates a template call may hold inside it, as in {{A|{{B|{{C}}}}}}. Deeper calls show as plain text. The default, 8, covers most templates copied from Wikipedia. After a change, saved articles are updated in the background over the next few page views.',
'WIKI_TEMPLATE_NESTING_INVALID' => 'The template nesting depth must be a number from %1$d to %2$d.',
'WIKI_REPARSE_PENDING' => 'Articles are still being updated for the last change.',

'WIKI_ONLY_BBCODES' => 'BBCodes only for the wiki',
'WIKI_ONLY_BBCODES_EXPLAIN' => 'Tick a custom BBCode to use it only in wiki articles. It then stops working in posts, private messages and signatures, and its button shows only in the wiki editor. Text saved before you tick it keeps its formatting until it is edited. Custom BBCodes are the ones added on the <a href="%s">Posting 禄 BBCodes</a> page of the ACP.',
'WIKI_ONLY_BBCODE' => 'Only in wiki articles',
'WIKI_ONLY_BBCODES_NONE' => 'This board has no custom BBCodes yet. Add them on the <a href="%s">Posting 禄 BBCodes</a> page of the ACP.',
Expand Down
Loading
Loading