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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ An article
- 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.
- **Start from a template.** A template whose name starts with `Start ` (for example `Template:Start Extension`) is offered when someone creates a new article: a **Start from** choice above the edit form lists them by the rest of the name, and **Use** fills the text box with the template's text (without its `<noinclude>` parts) to edit and save as the new article. The choice appears only for an article that does not exist yet, and only when at least one such template exists. Nothing is included: the text is copied, so later edits to the template do not change articles made from it.
- **Start from a template.** A template whose name starts with `Start ` (for example `Template:Start Extension`) is offered when someone creates a new article: a **Start from** choice above the edit form lists them by the rest of the name, and **Use** fills the text box with the template's text (without its `<noinclude>` parts) to edit and save as the new article. The choice appears only for an article that does not exist yet. Nothing is included: the text is copied, so later edits to the template do not change articles made from it.
- A Start template that uses parameters (`{{{version|1.0}}}`, `{{{1}}}`) asks for their values first: choosing it shows one box per parameter, filled with its default, and **Fill in** writes the values into the text. A box left empty is left out.
- **or copy an article** beside the choice fills the text box with another article's live text, to start from instead. Its uploaded files are not copied, so `[[File:...]]` lines in it refer to the other article's files until they are uploaded again.
- Like headings and tables, this works in wiki articles only; in a forum post `{{Name}}` stays plain text. The first letter of a template name is not case-sensitive (`{{out of date}}` finds `Template:Out of date`).
- **Parser functions** decide what a template shows. Only the part that is chosen is worked out, so the branch that is not taken can hold anything.

Expand Down
5 changes: 2 additions & 3 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ separate admin page. See the README. An encyclopedia-style skeleton, `Template:S
[`phpbbmodders/wiki-templates`](https://github.com/phpbbmodders/wiki-templates), and boards
install it from the ACP like the other starter templates.

Not built:

- Starting from an existing article, or a template that asks for values first.
Also built (10/15/2026): a Start template with `{{{parameters}}}` asks for their values first,
and a new article can start as a copy of another article's live text. Nothing more is planned here.

## Categories

Expand Down
Binary file added docs/images/wiki-start-values.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions language/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@
'WIKI_START_FROM' => 'Start from',
'WIKI_START_BLANK' => 'A blank article',
'WIKI_START_USE' => 'Use',
'WIKI_START_COPY' => 'Copy an article',
'WIKI_START_COPY_OR' => 'or copy an article',
'WIKI_START_COPY_PLACEHOLDER' => 'Article name',
'WIKI_START_COPY_NOT_FOUND' => 'There is no live article called “%s” to copy.',
'WIKI_START_VALUES_EXPLAIN' => 'This template takes some values. Fill them in, and they are written into the new article’s text; a box left empty is left out.',
'WIKI_START_VALUE_NUMBERED' => 'Value %d',
'WIKI_START_FILL' => 'Fill in',
'WIKI_CATEGORIES' => 'Categories',
'WIKI_CATEGORIES_EXPLAIN' => 'Groups of related articles, A to Z. Open a category to see every article in it.',
'WIKI_CATEGORIES_NONE' => 'No article is in a category yet.',
Expand Down
23 changes: 23 additions & 0 deletions styles/prosilver/template/article_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,41 @@ <h3>{L_SOURCES}</h3><br>

<!-- IF S_START_PICKER -->
<form method="get" action="{U_START_ACTION}" class="wiki-start-from">
<!-- IF START_ERROR --><p class="error">{START_ERROR}</p><!-- ENDIF -->
<p>
<!-- IF .start_options -->
<label for="start_from">{L_WIKI_START_FROM}{L_COLON}</label>
<select name="start_from" id="start_from">
<option value="">{L_WIKI_START_BLANK}</option>
<!-- BEGIN start_options -->
<option value="{start_options.NAME}"<!-- IF start_options.S_SELECTED --> selected="selected"<!-- ENDIF -->>{start_options.LABEL}</option>
<!-- END start_options -->
</select>
<!-- ENDIF -->
<label for="copy_from"><!-- IF .start_options -->{L_WIKI_START_COPY_OR}<!-- ELSE -->{L_WIKI_START_COPY}<!-- ENDIF -->{L_COLON}</label>
<input type="text" name="copy_from" id="copy_from" value="{START_COPY_FROM}" size="25" maxlength="255" class="inputbox autowidth" placeholder="{L_WIKI_START_COPY_PLACEHOLDER}">
<input type="hidden" name="action" value="edit">
<input type="submit" value="{L_WIKI_START_USE}" class="button2">
</p>
</form>
<!-- IF S_START_VALUES -->
<form method="get" action="{U_START_ACTION}" class="wiki-start-from wiki-start-values">
<p>{L_WIKI_START_VALUES_EXPLAIN}</p>
<fieldset class="fields1">
<!-- BEGIN start_values -->
<dl>
<dt><label for="start_value_{start_values.S_ROW_COUNT}">{start_values.LABEL}{L_COLON}</label></dt>
<dd><input type="text" name="start_values[{start_values.NAME}]" id="start_value_{start_values.S_ROW_COUNT}" value="{start_values.DEFAULT}" size="45" class="inputbox autowidth"></dd>
</dl>
<!-- END start_values -->
</fieldset>
<p>
<!-- BEGIN start_options --><!-- IF start_options.S_SELECTED --><input type="hidden" name="start_from" value="{start_options.NAME}"><!-- ENDIF --><!-- END start_options -->
<input type="hidden" name="action" value="edit">
<input type="submit" value="{L_WIKI_START_FILL}" class="button1">
</p>
</form>
<!-- ENDIF -->
<!-- ENDIF -->

<form id="postform" method="post" action="{U_ACTION}">
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/templates_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public function test_bars_inside_links_and_templates_belong_to_them()
$this->assertSame(array('x' => '{{Y|z}}'), $named);
}

public function test_parameter_defaults_list_each_parameter_once_with_its_default()
{
$text = "Version {{{version|1.0}}} of {{{1}}}, by {{{author}}}. Again {{{version}}}. {{{note|see {{Main|x}}}}}";
$this->assertSame(array('version' => '1.0', '1' => '', 'author' => '', 'note' => ''), templates::parameter_defaults($text));
$this->assertSame(array(), templates::parameter_defaults('No parameters here.'));
$this->assertSame(array('a' => 'later'), templates::parameter_defaults('{{{a}}} {{{a|later}}}'), 'a default written later still counts');
}

public function test_runs_of_closing_braces_do_not_end_a_parameter_early()
{
// }}}}}}}} closes {{{1}}}, {{{w| and {{#if:, so the bar after the next call belongs to it
Expand Down
103 changes: 98 additions & 5 deletions wiki/edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -707,13 +707,53 @@ public function break_lock($article)
protected function offer_start_templates($article)
{
$options = $this->templates->start_templates();
if (empty($options))
$chosen = $this->request->variable('start_from', '', true);
$copy_from = trim(htmlspecialchars_decode($this->request->variable('copy_from', '', true), ENT_COMPAT));
$text = null;
$error = '';

if ($copy_from !== '')
{
return;
// Another article's live text, to edit into this one
$text = $this->copy_text($copy_from);
if ($text === null)
{
$error = $this->user->lang('WIKI_START_COPY_NOT_FOUND', htmlspecialchars($copy_from, ENT_COMPAT, 'UTF-8'));
}
}
else if ($chosen !== '')
{
$source = $this->templates->start_text($chosen);
$parameters = ($source === null) ? array() : \phpbbmodders\wiki\wiki\templates::parameter_defaults($source);
if ($source !== null && $parameters && !$this->request->is_set('start_values'))
{
// A Start template that takes values asks for them first
$this->ask_start_values($parameters);
}
else if ($source !== null)
{
$values = array();
foreach ($this->request->variable('start_values', array('' => ''), true) as $name => $value)
{
$values[htmlspecialchars_decode((string) $name, ENT_COMPAT)] = htmlspecialchars_decode($value, ENT_COMPAT);
}
$positional = $named = array();
foreach (array_keys($parameters) as $name)
{
$value = isset($values[$name]) ? $values[$name] : '';
if (ctype_digit((string) $name))
{
$positional[(int) $name] = $value;
}
else
{
$named[$name] = $value;
}
}
$text = $parameters ? \phpbbmodders\wiki\wiki\templates::substitute($source, $positional, $named) : $source;
}
}

$chosen = $this->request->variable('start_from', '', true);
$text = ($chosen === '') ? null : $this->templates->start_text($chosen);
if ($text !== null)
{
// The text box takes text escaped as decode_message() leaves it
Expand All @@ -725,16 +765,69 @@ protected function offer_start_templates($article)
$this->template->assign_block_vars('start_options', array(
'NAME' => $option['name'],
'LABEL' => $option['label'],
'S_SELECTED' => ($text !== null && $option['name'] === \phpbbmodders\wiki\wiki\templates::normalise_name($chosen)),
'S_SELECTED' => ($chosen !== '' && $option['name'] === \phpbbmodders\wiki\wiki\templates::normalise_name($chosen)),
));
}

$this->template->assign_vars(array(
'S_START_PICKER' => true,
'START_COPY_FROM' => htmlspecialchars($copy_from, ENT_COMPAT, 'UTF-8'),
'START_ERROR' => $error,
'U_START_ACTION' => $this->helper->route('phpbbmodders_wiki_article', array('article' => $article)),
));
}

/**
* Show the form that asks for a Start template's values, one box per parameter, filled
* with the default the template writes for it.
*
* @param array $parameters Names with their defaults
* @return void
*/
protected function ask_start_values(array $parameters)
{
foreach ($parameters as $name => $default)
{
$this->template->assign_block_vars('start_values', array(
'NAME' => htmlspecialchars((string) $name, ENT_COMPAT, 'UTF-8'),
'LABEL' => ctype_digit((string) $name) ? $this->user->lang('WIKI_START_VALUE_NUMBERED', (int) $name) : htmlspecialchars((string) $name, ENT_COMPAT, 'UTF-8'),
'DEFAULT' => htmlspecialchars($default, ENT_COMPAT, 'UTF-8'),
));
}
$this->template->assign_var('S_START_VALUES', true);
}

/**
* The live text of an article, as an editor would see it, to start a new article from.
*
* @param string $name The article's name, as in its address
* @return string|null Null when it has no live version, or the viewer may not read the wiki
*/
protected function copy_text($name)
{
if (!$this->auth->acl_get('u_wiki_view'))
{
return null;
}

$sql = 'SELECT article_text, bbcode_uid
FROM ' . $this->article_table . "
WHERE article_url = '" . $this->db->sql_escape($name) . "'
AND article_approved = 1
ORDER BY article_last_edit DESC, article_id DESC";
$result = $this->db->sql_query_limit($sql, 1);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!$row)
{
return null;
}

$edit = generate_text_for_edit($row['article_text'], $row['bbcode_uid'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES | OPTION_FLAG_LINKS);

return htmlspecialchars_decode($edit['text'], ENT_COMPAT);
}

/**
* Make a version live, then tell the members watching the article.
*
Expand Down
31 changes: 31 additions & 0 deletions wiki/templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,37 @@ public function page_view($article_text, $bbcode_uid)
return array($html, trim(self::transclusion_text($raw)));
}

/**
* The parameters a text uses, with the default written for each: for the form that asks
* for a Start template's values. A default that holds other calls is not offered.
*
* @param string $text
* @return array Names, in the order first written, with their defaults ('' for none)
*/
public static function parameter_defaults($text)
{
$defaults = array();
preg_match_all('#\{\{\{\s*([^{}|]+?)\s*(?:\|([^{}]*))?\}\}\}#', $text, $matches, PREG_SET_ORDER);
foreach ($matches as $match)
{
$name = $match[1];
if (!isset($defaults[$name]) || ($defaults[$name] === '' && isset($match[2])))
{
$defaults[$name] = isset($match[2]) ? trim($match[2]) : '';
}
}
// Parameters whose default holds a call are still asked for, without a default
foreach (self::parameters($text) as $name)
{
if (!isset($defaults[$name]))
{
$defaults[$name] = '';
}
}

return $defaults;
}

/**
* The parameters a template's text uses, in the order they first appear.
*
Expand Down
Loading