diff --git a/README.md b/README.md index 9787137..8a69e12 100644 --- a/README.md +++ b/README.md @@ -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 ``, 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. `` 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 `` 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 `` 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. diff --git a/docs/TODO.md b/docs/TODO.md index 086756a..b301aa8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -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 diff --git a/docs/images/wiki-start-values.png b/docs/images/wiki-start-values.png new file mode 100644 index 0000000..bae66d7 Binary files /dev/null and b/docs/images/wiki-start-values.png differ diff --git a/language/en/common.php b/language/en/common.php index 89e84cc..be4991d 100644 --- a/language/en/common.php +++ b/language/en/common.php @@ -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.', diff --git a/styles/prosilver/template/article_edit.html b/styles/prosilver/template/article_edit.html index 4e82ba4..2b1b1da 100644 --- a/styles/prosilver/template/article_edit.html +++ b/styles/prosilver/template/article_edit.html @@ -29,7 +29,9 @@

{L_SOURCES}


+

{START_ERROR}

+ + + +

+ +
+

{L_WIKI_START_VALUES_EXPLAIN}

+
+ +
+
+
+
+ +
+

+ + + +

+
+
diff --git a/tests/unit/templates_test.php b/tests/unit/templates_test.php index 4f3d683..578f0fb 100644 --- a/tests/unit/templates_test.php +++ b/tests/unit/templates_test.php @@ -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 diff --git a/wiki/edit.php b/wiki/edit.php index 57abc21..21e5a3a 100644 --- a/wiki/edit.php +++ b/wiki/edit.php @@ -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 @@ -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. * diff --git a/wiki/templates.php b/wiki/templates.php index 4642b34..95513cc 100644 --- a/wiki/templates.php +++ b/wiki/templates.php @@ -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. *