diff --git a/README.md b/README.md index 8ae6040..708da04 100644 --- a/README.md +++ b/README.md @@ -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 ``, 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. +- **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. - 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/config/services.yml b/config/services.yml index 0522f6a..87fd76e 100644 --- a/config/services.yml +++ b/config/services.yml @@ -50,6 +50,7 @@ services: - '@template' - '@user' - '@phpbbmodders.wiki.links' + - '@phpbbmodders.wiki.templates' - '%tables.wiki.article%' phpbbmodders.wiki.contributions: @@ -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: diff --git a/controller/template_list.php b/controller/template_list.php index b349984..8fdc7b9 100644 --- a/controller/template_list.php +++ b/controller/template_list.php @@ -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; @@ -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; @@ -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. * @@ -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); @@ -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)) diff --git a/docs/TODO.md b/docs/TODO.md index dde55a5..beb4d24 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -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 diff --git a/docs/TODO/todo-wiki-markup.md b/docs/TODO/todo-wiki-markup.md index ac73152..f72f252 100644 --- a/docs/TODO/todo-wiki-markup.md +++ b/docs/TODO/todo-wiki-markup.md @@ -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). diff --git a/language/en/common.php b/language/en/common.php index de79016..482b4e0 100644 --- a/language/en/common.php +++ b/language/en/common.php @@ -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.', diff --git a/styles/prosilver/template/article_edit.html b/styles/prosilver/template/article_edit.html index 0bc3d20..aef2837 100644 --- a/styles/prosilver/template/article_edit.html +++ b/styles/prosilver/template/article_edit.html @@ -27,6 +27,22 @@

{L_SOURCES}


+ +
+

+ + + + +

+
+ +
diff --git a/styles/prosilver/theme/style.css b/styles/prosilver/theme/style.css index 5c4977e..2bf4a28 100644 --- a/styles/prosilver/theme/style.css +++ b/styles/prosilver/theme/style.css @@ -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; +} diff --git a/tests/unit/parser_functions_test.php b/tests/unit/parser_functions_test.php index fb4b415..df98945 100644 --- a/tests/unit/parser_functions_test.php +++ b/tests/unit/parser_functions_test.php @@ -312,6 +312,19 @@ public function test_rows_and_cells_are_parsed_inside_a_table_that_is_taken_off_ $this->assertSame('x', $class::unwrap_table("\n\n
x
\n", ''), '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'] = "Docs== 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)); diff --git a/wiki/edit.php b/wiki/edit.php index 21f787e..25659c2 100644 --- a/wiki/edit.php +++ b/wiki/edit.php @@ -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; @@ -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; @@ -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. * diff --git a/wiki/templates.php b/wiki/templates.php index e3c99ba..9b31567 100644 --- a/wiki/templates.php +++ b/wiki/templates.php @@ -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; @@ -787,6 +790,73 @@ public static function transclusion_text($text) return preg_replace('##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 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 (worked out like an article, so it can show examples), and