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 @@ -68,6 +68,7 @@ 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.
- **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.
- 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
2 changes: 2 additions & 0 deletions config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ services:
- '@template'
- '@user'
- '@phpbbmodders.wiki.links'
- '@phpbbmodders.wiki.templates'
- '%tables.wiki.article%'

phpbbmodders.wiki.contributions:
Expand Down Expand Up @@ -140,6 +141,7 @@ services:
- '%core.root_path%'
- '%core.php_ext%'
- '@phpbbmodders.wiki.watch'
- '@phpbbmodders.wiki.templates'
phpbbmodders.wiki.compare:
class: phpbbmodders\wiki\wiki\compare
arguments:
Expand Down
29 changes: 8 additions & 21 deletions controller/template_list.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class template_list
/** @var \phpbbmodders\wiki\wiki\links */
protected $links;

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

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

Expand All @@ -55,10 +58,12 @@ class template_list
* @param \phpbb\template\template $template
* @param \phpbb\user $user
* @param \phpbbmodders\wiki\wiki\links $links
* @param \phpbbmodders\wiki\wiki\templates $templates
* @param string $article_table
*/
public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\controller\helper $helper, \phpbb\pagination $pagination, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, \phpbbmodders\wiki\wiki\links $links, $article_table)
public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\controller\helper $helper, \phpbb\pagination $pagination, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, \phpbbmodders\wiki\wiki\links $links, \phpbbmodders\wiki\wiki\templates $templates, $article_table)
{
$this->templates = $templates;
$this->auth = $auth;
$this->config = $config;
$this->db = $db;
Expand All @@ -71,24 +76,6 @@ public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config
$this->article_table = $article_table;
}

/**
* SQL condition for the live version of a template: approved, and not
* replaced by a newer approved version.
*
* @return string
*/
protected function live_template_where()
{
return 'a.article_approved = 1
AND a.article_url ' . $this->db->sql_like_expression(\phpbbmodders\wiki\wiki\templates::PREFIX . $this->db->get_any_char()) . '
AND NOT EXISTS (
SELECT 1 FROM ' . $this->article_table . ' b
WHERE b.article_url = a.article_url
AND b.article_approved = 1
AND (b.article_last_edit > a.article_last_edit
OR (b.article_last_edit = a.article_last_edit AND b.article_id > a.article_id)))';
}

/**
* The templates page.
*
Expand All @@ -105,7 +92,7 @@ public function page()

$sql = 'SELECT COUNT(a.article_id) AS total
FROM ' . $this->article_table . ' a
WHERE ' . $this->live_template_where();
WHERE ' . $this->templates->live_where('a', \phpbbmodders\wiki\wiki\templates::PREFIX);
$result = $this->db->sql_query($sql);
$total = (int) $this->db->sql_fetchfield('total');
$this->db->sql_freeresult($result);
Expand All @@ -115,7 +102,7 @@ public function page()

$sql = 'SELECT a.article_id, a.article_title, a.article_url, a.article_description, a.article_last_edit
FROM ' . $this->article_table . ' a
WHERE ' . $this->live_template_where() . '
WHERE ' . $this->templates->live_where('a', \phpbbmodders\wiki\wiki\templates::PREFIX) . '
ORDER BY a.article_title ASC, a.article_id ASC';
$result = $this->db->sql_query_limit($sql, $per_page, $start);
while ($row = $this->db->sql_fetchrow($result))
Expand Down
38 changes: 12 additions & 26 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,18 @@ Ideas not yet built, practical and speculative alike.

## Article creation templates

Right now a new article always starts blank. Offer a starting-point picker
when creating an article:

- **Blank** — today's behavior, an empty edit form.
- **Generic (Wikipedia-style)** — pre-fill the message body with a common
encyclopedia-article skeleton (e.g. an intro paragraph placeholder,
`Overview`, `History`, `References` section headings) so new articles
don't start from a truly empty box.

The template picker lives on the edit page itself (not the overview page's
quick-create box, which still just takes you straight to a blank edit form
and lets you pick there).

Templates are admin-configurable: admins can add new templates beyond the
built-in "Blank" and "Generic" ones, via an ACP page.

Still needs deciding before implementation:

- What a template is allowed to contain (plain text with placeholders?
BBCode? anything the message parser accepts?) and how that's authored in
the ACP form.
- What sanitization that admin-authored template content needs before it's
inserted into the edit form's message body — it's admin-authored rather
than end-user input, but still ends up in content regular members see and
can build on, so it shouldn't get a free pass on the same checks normal
article content goes through.
Built: when someone creates a new article, a **Start from** choice on the edit page
offers every template whose name starts with `Start ` (for example
`Template:Start Extension`) and fills the text box with its text. The templates are
ordinary wiki pages, so they have versions, approval and permissions, and there is no
separate admin page. See the README.

Not built:

- A starter skeleton that comes with the extension (an encyclopedia-style article with
`Overview`, `History` and `References` headings). It would fit the starter-templates
repository below better than code in the extension.
- Starting from an existing article, or a template that asks for values first.

## Move/rename an article

Expand Down
1 change: 0 additions & 1 deletion docs/TODO/todo-wiki-markup.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ out. Still to do:
functions per article. Add the cache only if articles with hundreds of calls become common.
- A template call holds other calls in its parameters three levels deep, and a default value holds
one level of `{{template}}`; deeper nesting stays text.
- A "Start from" choice for templates in the article picker.

A set of ready-made templates, adapted from Wikipedia's, is planned as a separate
repository: [Starter templates repository](todo-wiki-templates-repo.md).
Expand Down
3 changes: 3 additions & 0 deletions language/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@
'WIKI_NOW_WATCHING' => 'You are now watching this article. You will be notified when a new version goes live.',
'WIKI_NO_LONGER_WATCHING' => 'You are no longer watching this article.',
'WIKI_RECENT_CHANGES' => 'Recent changes',
'WIKI_START_FROM' => 'Start from',
'WIKI_START_BLANK' => 'A blank article',
'WIKI_START_USE' => 'Use',
'WIKI_TEMPLATES' => 'Templates',
'WIKI_TEMPLATES_EXPLAIN' => 'Every template of the wiki, A to Z. A template is an article called “Template:Name”; write {{Name}} in an article to use it.',
'WIKI_TEMPLATES_NONE' => 'There are no templates yet.',
Expand Down
16 changes: 16 additions & 0 deletions styles/prosilver/template/article_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ <h3>{L_SOURCES}</h3><br>
<!-- ENDIF -->
<!-- ENDIF -->

<!-- IF S_START_PICKER -->
<form method="get" action="{U_START_ACTION}" class="wiki-start-from">
<p>
<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>
<input type="hidden" name="action" value="edit">
<input type="submit" value="{L_WIKI_START_USE}" class="button2">
</p>
</form>
<!-- ENDIF -->

<form id="postform" method="post" action="{U_ACTION}">
<div class="panel" id="postingbox">
<div class="inner">
Expand Down
5 changes: 5 additions & 0 deletions styles/prosilver/theme/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,8 @@ pre.wiki-template-source {
background-color: rgba(128, 128, 128, 0.08);
font-family: monospace;
}

/* "Start from" above the edit form of a new article */
form.wiki-start-from p {
margin: 0.4em 0 0.8em;
}
13 changes: 13 additions & 0 deletions tests/unit/parser_functions_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,19 @@ public function test_rows_and_cells_are_parsed_inside_a_table_that_is_taken_off_
$this->assertSame('<tr><td>x</td></tr>', $class::unwrap_table("<table>\n<tr><td>x</td></tr>\n</table>\n", '<table>'), 'a row keeps its own tr');
}

public function test_a_new_article_can_start_from_a_start_template_only()
{
$this->templates->pages['Template:Start Extension'] = "<noinclude>Docs</noinclude>== Overview ==\nText [b]bold[/b] {{{1}}}";

$this->assertSame("== Overview ==\nText [b]bold[/b] {{{1}}}", $this->templates->start_text('Start Extension'));
$this->assertSame("== Overview ==\nText [b]bold[/b] {{{1}}}", $this->templates->start_text('Template:Start Extension'));
$this->assertSame("== Overview ==\nText [b]bold[/b] {{{1}}}", $this->templates->start_text('start Extension'));
$this->assertNull($this->templates->start_text('Start Nothing'), 'no such page');
$this->assertNull($this->templates->start_text('Hello'), 'a template that is not a start template is not offered');
$this->assertNull($this->templates->start_text('Main Page'));
$this->assertNull($this->templates->start_text(''));
}

public function test_a_flood_of_calls_is_cut_off()
{
$out = $this->run_text(str_repeat('{{#if: x | a }}', 3000));
Expand Down
52 changes: 50 additions & 2 deletions wiki/edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class edit extends \phpbbmodders\wiki\wiki\functions
/** @var \phpbbmodders\wiki\wiki\watch */
protected $watch;

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

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

Expand Down Expand Up @@ -73,10 +76,12 @@ class edit extends \phpbbmodders\wiki\wiki\functions
* @param string $article_table
* @param string $phpbb_root_path
* @param string $php_ext

* @param \phpbbmodders\wiki\wiki\watch $watch Watched articles object
* @param \phpbbmodders\wiki\wiki\templates $templates Wiki templates object
*/
public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\controller\helper $helper, \phpbb\notification\manager $notification_manager, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, $article_table, $phpbb_root_path, $php_ext, \phpbbmodders\wiki\wiki\watch $watch)
public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\controller\helper $helper, \phpbb\notification\manager $notification_manager, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user, $article_table, $phpbb_root_path, $php_ext, \phpbbmodders\wiki\wiki\watch $watch, \phpbbmodders\wiki\wiki\templates $templates)
{
$this->templates = $templates;
$this->watch = $watch;
$this->auth = $auth;
$this->config = $config;
Expand Down Expand Up @@ -333,10 +338,53 @@ public function edit_article($article)
$this->message_parser->message = $this->data['article_text'];
$this->message_parser->decode_message($this->data['bbcode_uid']);

if (empty($this->data['article_id']))
{
$this->offer_start_templates($article);
}

$this->display_edit_form(false);
}
return $this->helper->render('article_edit.html', $this->user->lang('EDIT_WIKI'));
}
/**
* For a new article: offer the "Template:Start ..." pages to start from, and fill the
* text box with the one that was chosen.
*
* @param string $article The new article's name, as in its address
* @return void
*/
protected function offer_start_templates($article)
{
$options = $this->templates->start_templates();
if (empty($options))
{
return;
}

$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
$this->message_parser->message = htmlspecialchars($text, ENT_COMPAT, 'UTF-8');
}

foreach ($options as $option)
{
$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)),
));
}

$this->template->assign_vars(array(
'S_START_PICKER' => true,
'U_START_ACTION' => $this->helper->route('phpbbmodders_wiki_article', array('article' => $article)),
));
}

/**
* Make a version live, then tell the members watching the article.
*
Expand Down
70 changes: 70 additions & 0 deletions wiki/templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class templates
/** Article names of templates start with this. */
const PREFIX = 'Template:';

/** The name of a template a new article can start from begins with this, after the prefix. */
const START_PREFIX = 'Start ';

/** @var \phpbb\db\driver\driver_interface */
protected $db;

Expand Down Expand Up @@ -787,6 +790,73 @@ public static function transclusion_text($text)
return preg_replace('#</?includeonly>#i', '', $text);
}

/**
* SQL condition for the live version of a template page: approved, and not replaced
* by a newer approved version.
*
* @param string $alias The alias of the article table in the query
* @param string $name_prefix What the article's name starts with, "Template:" for all templates
* @return string
*/
public function live_where($alias, $name_prefix)
{
return $alias . '.article_approved = 1
AND ' . $alias . '.article_url ' . $this->db->sql_like_expression($name_prefix . $this->db->get_any_char()) . '
AND NOT EXISTS (
SELECT 1 FROM ' . $this->article_table . ' b
WHERE b.article_url = ' . $alias . '.article_url
AND b.article_approved = 1
AND (b.article_last_edit > ' . $alias . '.article_last_edit
OR (b.article_last_edit = ' . $alias . '.article_last_edit AND b.article_id > ' . $alias . '.article_id)))';
}

/**
* The templates a new article can start from: the live ones called "Template:Start ...".
*
* @return array Rows with the name (for example "Start Extension"), a label (the rest of the name) and the description
*/
public function start_templates()
{
$sql = 'SELECT a.article_url, a.article_title, a.article_description
FROM ' . $this->article_table . ' a
WHERE ' . $this->live_where('a', self::PREFIX . self::START_PREFIX) . '
ORDER BY a.article_title ASC, a.article_id ASC';
$result = $this->db->sql_query($sql);
$templates = array();
while ($row = $this->db->sql_fetchrow($result))
{
$name = substr($row['article_url'], strlen(self::PREFIX));
$templates[] = array(
'name' => $name,
'label' => substr($name, strlen(self::START_PREFIX)),
'description' => $row['article_description'],
);
}
$this->db->sql_freeresult($result);

return $templates;
}

/**
* The text a new article starts with, from one of the "Template:Start ..." pages: the
* text of the page without its <noinclude> parts, as it is written.
*
* @param string $name The template's name, for example "Start Extension"
* @return string|null Null when there is no such start template
*/
public function start_text($name)
{
$name = self::normalise_name($name);
if (strpos($name, self::START_PREFIX) !== 0)
{
return null;
}

$source = $this->source(self::PREFIX . $name);

return ($source === null) ? null : trim(self::transclusion_text($source));
}

/**
* What to show on the page of a template: its documentation, which is what stands
* inside <noinclude> (worked out like an article, so it can show examples), and
Expand Down
Loading