Krinkle has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/49840


Change subject: (DRAFT) initial commit
......................................................................

(DRAFT) initial commit

Change-Id: Icf305892a9512545a63f5a5280cc0d340c61585f
---
A EXAMPLE.js
A TemplateData.hooks.php
A TemplateData.i18n.php
A TemplateData.php
A TemplateDataItem.php
A api/ApiQueryTemplateData.php
A resources/ext.templateData.css
7 files changed, 664 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/TemplateData 
refs/changes/40/49840/1

diff --git a/EXAMPLE.js b/EXAMPLE.js
new file mode 100644
index 0000000..9674252
--- /dev/null
+++ b/EXAMPLE.js
@@ -0,0 +1,152 @@
+/*
+       Specification for the JSON descriptor as used in the
+       TemplateData extension for MediaWiki.
+
+       Author: Timo Tijhof
+       Author: Trevor Parscal
+       Latest version: https://gist.github.com/Krinkle/a47844a677d76c815998
+
+@structure {Object} Root
+       @property {InterfaceText} [description]
+       @property {Object} params Contains all parameters.
+        Keyed by parameter name, contains #Param objects.
+       @property {Object} sets Groups of parameters that should be used
+        together. Groups may overlap with each other, though this is not 
recommended.
+        Keyed by an internal id, contains #Set objects.
+
+@structure {Object} Param
+       @property {InterfaceText} [label] Defaults to key of object in 
`Root.params`. 
+       @property {boolean} [required=false]
+       @property {InterfaceText} [description]
+       @property {boolean|string} [deprecated=false] Tooltip for the user 
detailing
+        the intend for the deprecated parameters.
+       @property {Array} [aliases] List of aliases.
+        An alias is an alternative name for the parameter that may be used 
instead
+        (not in addition) to the primary name. Aliases are not documented in a
+        separate Param object. If they need more information, they should be 
in their
+        own property marked "deprecated".
+       @property {string} [inherits] Key to another object in `Root.params`.
+        The current Param object will inherit from that one, with local 
properties
+        overriding the inherited ones.
+       @property {string} [default] The default value or description thereof.
+       @property {Type} [type] The type of the expected parameter value.
+
+@structure {Object} Set
+       @property {InterfaceText} [label] Defaults to key of object in 
`Root.sets`.
+       @property {Array} params A subset of the parameter's names that belong 
to this set.
+
+@structure {string} Type
+       One of the following:
+       - string
+         Any textual value.
+       - number
+         Any numerical value (without decimal points or thousand separators).
+       - wikipage
+         A valid MediaWiki page name for the current wiki. Doesn't have to 
exist,
+         but if not, should be a valid page name to create.
+       - wikiuser
+         The username of an account on the current wiki (regardless of whether
+         that user has an edit count or a user page).
+
+@structure {string|Object} InterfaceText
+       A free-form string (no wikitext) in the content-language of the wiki, 
or,
+       an object containing those strings keyed by language code.
+
+ */
+
+/**
+ * Template:Unsigned
+ * Example for
+ *     {{unsigned|JohnDoe|2012-10-18}}
+ *     {{unsigned|user=JohnDoe|year=2012|month=10|day=18|comment=blabla}}
+ */
+{
+       "params": {
+               "user": {
+                       "label": "Username",
+                       "required:": true,
+                       "description": "User name of person who forgot to sign 
their comment.",
+                       "aliases": ["1"]
+               },
+               "date": {
+                       "label": {
+                               "en": "Date"
+                       },
+                       "description": {
+                               "en": "Timestamp of when the comment was 
posted, in YYYY-MM-DD format."
+                       },
+                       "aliases": ["2"]
+               },
+               "year": {
+                       "label": "Year"
+               },
+               "month": {
+                       "label": "Month"
+               },
+               "day": {
+                       "label": "Day"
+               },
+               "comment": {
+                       "required": false
+               }
+       },
+       "sets": {
+               "date": {
+                       "label": "Date",
+                       "params": ["year", "month", "day"]
+               }
+       }
+}
+
+/**
+ * Template:TemplateBox
+ * Example for:
+ *     {{TemplateBox|1d=..|2d=..|10d=..}}
+ */
+{
+       "description": "Document the documenter.",
+       "params": {
+               "1d": {
+                       "label": "Param 1",
+                       "description": "Description of the template parameter",
+                       "type": "string"
+
+               },
+               "2d": {
+                       "label": "Param 2",
+                       "inherits": "1d"
+               },
+               "3d": {
+                       "label": "Param 3",
+                       "inherits": "1d"
+               },
+               "4d": {
+                       "label": "Param 4",
+                       "inherits": "1d"
+               },
+               "5d": {
+                       "label": "Param 5",
+                       "inherits": "1d"
+               },
+               "6d": {
+                       "label": "Param 6",
+                       "inherits": "1d"
+               },
+               "7d": {
+                       "label": "Param 7",
+                       "inherits": "1d"
+               },
+               "8d": {
+                       "label": "Param 8",
+                       "inherits": "1d"
+               },
+               "9d": {
+                       "label": "Param 9",
+                       "inherits": "1d"
+               },
+               "10d": {
+                       "label": "Param 10",
+                       "inherits": "1d"
+               }
+       }
+}
diff --git a/TemplateData.hooks.php b/TemplateData.hooks.php
new file mode 100644
index 0000000..d9b7d95
--- /dev/null
+++ b/TemplateData.hooks.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Hooks for TemplateInfo extension
+ *
+ * @file
+ * @ingroup Extensions
+ */
+
+class TemplateDataHooks {
+
+       /**
+        * Register parser hooks
+        */
+       public static function onParserFirstCallInit( &$parser ) {
+               $parser->setHook( 'templatedata', array( 'TemplateDataHooks', 
'render' ) );
+               return true;
+       }
+
+       /**
+        * @param Page &$page
+        * @param User &$user
+        * @param Content &$content
+        * @param string &$summary
+        * @param $minor
+        * @param bool|null $watchthis
+        * @param $sectionanchor
+        * @param &$flags
+        * @param Status &$status
+        */
+       public static function onPageContentSave( &$page, &$user, &$content, 
&$summary, $minor,
+               $watchthis, $sectionanchor, &$flags, &$status
+       ) {
+
+               // The PageContentSave hook provides raw $text, but not $parser 
because at this stage
+               // the page is not actually parsed yet. Which means we can't 
know whether self::render()
+               // got a valid tag or not. Looking at $text directly is not a 
solution either as
+               // it may not be in the current page (it can be transcluded).
+               // Since there is no later hook that allows aborting the save 
and showing an error,
+               // we will have to trigger the parser ourselves.
+               // Fortunately this causes no overhead since the below (copied 
from WikiPage::doEditContent,
+               // right after this hook is ran) has guards that lazy-init and 
return early if called again
+               // later by the real WikiPage.
+
+               $editInfo = $page->prepareContentForEdit( $content, null, 
$user, $serialisation_format = null );
+
+               if ( isset( $editInfo->output->ext_templatedata_status ) ) {
+                       $validation = 
$editInfo->output->ext_templatedata_status;
+                       if ( !$validation->isOK() ) {
+                               // Abort edit, show error message from 
TemplateDataItem::getStatus
+                               $status->merge( $validation );
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Parser hook for <templatedata>.
+        * If there is any JSON provided, render the template documentation on 
the page.
+        *
+        * @param string $input: The content of the tag.
+        * @param array $args: The attributes of the tag.
+        * @param Parser $parser: Parser instance available to render
+        *  wikitext into html, or parser methods.
+        * @param PPFrame $frame: Can be used to see what template parameters 
("{{{1}}}", etc.) this hook was used with.
+        *
+        * @return string: HTML to insert in the page.
+        */
+       public static function render( $input, $args, $parser, $frame ) {
+               // If this call is contained in a transcluded page or template, 
display nothing.
+               // TODO: Why?
+               if ( $input === '' || !$frame->title->equals( 
$parser->getTitle() ) ) {
+                       return;
+               }
+
+               $ti = TemplateDataItem::newFromJSON( $input );
+
+               $status = $ti->getStatus();
+               if ( !$status->isOK() ) {
+                       $parser->getOutput()->ext_templatedata_status = $status;
+                       return '<div class="error">' . $status->getHtml() . 
'</div>';
+               }
+
+               $parser->getOutput()->setProperty( 'templatedata', 
$ti->getJSON() );
+
+               $parser->getOutput()->addModules( 'ext.templateData' );
+
+               return $ti->getHtml();
+       }
+}
diff --git a/TemplateData.i18n.php b/TemplateData.i18n.php
new file mode 100644
index 0000000..9d523c4
--- /dev/null
+++ b/TemplateData.i18n.php
@@ -0,0 +1,37 @@
+<?php
+$messages = array();
+
+/** English
+ * @author Timo Tijhof 
+ */
+$messages['en'] = array(
+
+       // Special:Version
+       'templatedata-desc' => 'Implement data storage for template parameters 
(using JSON).',
+
+       // Error message for edit page
+       'templatedata-invalid-parse' => 'SyntaxError in JSON.',
+       'templatedata-invalid-type' => 'Property "$1" is expected to be of type 
"$2".',
+       'templatedata-invalid-missing' => 'Required property "$1" not found.',
+       'templatedata-invalid-unknown' => 'Unexpected property "$1".',
+       'templatedata-invalid-value' => 'Invalid value for property "$1".',
+);
+
+/** Message documentation (Message documentation)
+ * @author Timo Tijhof 
+ */
+$messages['qqq'] = array(
+       'templatedata-desc' => '{{desc}}',
+       'templatedata-invalid-type' => 'Error message when a property is of the 
wrong type.
+* $1: Name of property
+* $2: Expected type of property
+ ',
+       'templatedata-invalid-missing' => 'Error message when a required 
property is not found.
+* $1: Name of name
+* $2: Type of property',
+       'templatedata-invalid-unknown' => 'Error message when an unknown 
property is found.
+* $1: Name of property',
+       'templatedata-invalid-value' => 'Error message when a property that 
cannot contain free-form text has an invalid value.
+* $1: Name of property',
+);
+
diff --git a/TemplateData.php b/TemplateData.php
new file mode 100644
index 0000000..dcce1f4
--- /dev/null
+++ b/TemplateData.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * TemplateInfo extension.
+ *
+ * @file
+ * @ingroup Extensions
+ */
+
+if ( version_compare( $wgVersion, '1.20', '<' ) ) {
+       echo "Extension:TemplateInfo requires MediaWiki 1.20 or higher.\n";
+       exit( 1 );
+}
+
+$wgExtensionCredits['parserhook'][] = array(
+       'path'          => __FILE__,
+       'name'          => 'TemplateData',
+       'author'        => array( 'Timo Tijhof' ),
+       'version'        => '0.1.0',
+       'url'            => 
'https://www.mediawiki.org/wiki/Extension:TemplateData',
+       'descriptionmsg' => 'templatedata-desc',
+);
+
+/* Setup */
+
+$dir = __DIR__;
+
+// Register files
+$wgExtensionMessagesFiles['TemplateData'] = $dir . '/TemplateData.i18n.php';
+$wgAutoloadClasses['TemplateDataHooks'] = $dir . '/TemplateData.hooks.php';
+$wgAutoloadClasses['TemplateDataItem'] = $dir . '/TemplateDataItem.php';
+$wgAutoloadClasses['ApiQueryTemplateData'] = $dir . 
'/api/ApiQueryTemplateData.php';
+
+// Register hooks
+$wgHooks['ParserFirstCallInit'][] = 'TemplateDataHooks::onParserFirstCallInit';
+$wgHooks['PageContentSave'][] = 'TemplateDataHooks::onPageContentSave';
+
+// Register API actions
+$wgAPIPropModules['templatedata'] = 'ApiQueryTemplateData';
+
+// Register page_props
+$wgPageProps['templatedata'] = 'Content of &lt;templatedata&gt; tag';
+
+// Register modules
+$wgResourceModules['ext.templateData'] = array(
+       'styles' => 'resources/ext.templateData.css1',
+       'localBasePath' => $dir,
+       'remoteExtPath' => 'TemplateData',
+);
diff --git a/TemplateDataItem.php b/TemplateDataItem.php
new file mode 100644
index 0000000..cebf152
--- /dev/null
+++ b/TemplateDataItem.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * TemplateDataItem class
+ *
+ * @file
+ * @ingroup Extensions
+ */
+
+class TemplateDataItem {
+       /**
+        * @var stdClass
+        */
+       private $data;
+
+       /**
+        * @var Status: Cache of TemplateInfo::validate
+        */
+       private $status;
+
+       /**
+        * @param string $json
+        * @return TemplateInfo
+        */
+       public static function newFromJSON( $json ) {
+               $ti = new self( json_decode( $json ) );
+               $status = $ti->parse();
+
+               // TODO: Normalise `params.*.description` to a plain object.
+
+               if ( !$status->isOK() ) {
+                       // Don't save invalid data, clear it.
+                       $ti->data = new stdClass();
+               }
+               $ti->status = $status;
+               return $ti;
+       }
+
+       /**
+        * Parse the data, normalise it and validate it.
+        *
+        * Specification for the JSON object:
+        *
+        * @structure root
+        * @property {string} [description]
+        * @property {Object} params Contains all parameters. Keyed by 
parameter name, contains Param objects.
+        *
+        * @structure Param
+        * @property {string} [inherits]
+        * @property {boolean} [required=false]
+        * @property {string|Object} [description] Free-form description of 
this parameter.
+        * @property {boolean|string} [deprecated=false] Tooltip for the user 
detailing the intended
+        *  action on deprecated parameters.
+        * @property {Array} [aliases] List of aliases. An alias is an 
alternative name for the parameter
+        *  that may be used instead (not in addition) to the primary name.
+        *  Aliases are not documented in the params object further. If they
+        *  need more information, they should be in their own property as 
"deprecated". 
+        * @property {string} [default] The default value or description 
thereof.
+        * 
+        * Example for {{unsigned|JohnDoe|2012-10-18}}, 
{{unsigned|user=JohnDoe|date=2012-10-18}}
+        * <templatedata>
+        * {
+        *     "params": {
+        *         "user": {
+        *             "required:": true,
+        *             "description": "User name of person who forgot to sign 
their comment.",
+        *             "aliases": ["1"]
+        *         },
+        *         "date": {
+        *             "description": "Timestamp of when the comment was 
posted, in YYYY-MM-DD format.",
+        *             "aliases": ["2"]
+        *         }
+        *     }
+        * }
+        * </templatedata>
+        * @return Status
+        */
+       private function parse() {
+               $data = $this->data;
+
+               if ( $data === null ) {
+                       return Status::newFatal( 'templatedata-invalid-parse' );
+               }
+
+               if ( !is_object( $data ) ) {
+                       return Status::newFatal( 'templatedata-invalid-type', 
'templatedata', 'object' );
+               }
+
+               foreach ( $data as $key => $value ) {
+                       if ( !in_array( $key, array( 'params', 'description' ) 
) ) {
+                               return Status::newFatal( 
'templatedata-invalid-unknown', $key );
+                       }
+               }
+
+               if ( !isset( $data->params ) ) {
+                       return Status::newFatal( 
'templatedata-invalid-missing', 'params', 'object' );
+               }
+
+               if ( !is_object( $data->params ) ) {
+                       return Status::newFatal( 'templatedata-invalid-type', 
'params', 'object' );
+               }
+
+               if ( isset( $data->description ) ) {
+                       if ( !is_object( $data->params ) ) {
+                               return Status::newFatal( 
'templatedata-invalid-type', 'params', 'object' );
+                       }
+               } else {
+                       $data->description = '';
+               }
+
+               foreach ( $data->params as $paramName => $paramObj ) {
+                       if ( !is_object( $paramObj ) ) {
+                               return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName, 'object' );
+                       }
+
+                       foreach ( $paramObj as $key => $value ) {
+                               if ( !in_array( $key, array(
+                                               'required',
+                                               'description',
+                                               'deprecated',
+                                               'aliases',
+                                               'clones',
+                                               'default',
+                               ) ) ) {
+                                       return Status::newFatal( 
'templatedata-invalid-unknown', $key );
+                               }
+                       }
+
+                       if ( isset( $paramObj->required ) ) {
+                               if ( !is_bool( $paramObj->required ) ) {
+                                       return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName . '.required', 'boolean' );
+                               }
+                       } else {
+                               $paramObj->required = false;
+                       }
+
+                       if ( isset( $paramObj->description ) ) {
+                               if ( !is_object( $paramObj->description ) && 
!is_string( $paramObj->description ) ) {
+                                       // TODO: Also validate that if it is an 
object, the keys are valid lang codes
+                                       // and the values strings.
+                                       return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName . '.description', 
'string|object' );
+                               }
+                       } else {
+                               $paramObj->description = '';
+                       }
+
+                       if ( isset( $paramObj->deprecated ) ) {
+                               if ( $paramObj->deprecated === false || 
is_string( $paramObj->deprecated ) ) ) {
+                                       return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName . '.deprecated', 
'boolean|string' );
+                               }
+                       } else {
+                               $paramObj->deprecated = false;
+                       }
+
+                       if ( isset( $paramObj->aliases ) ) {
+                               if ( !is_array( $paramObj->aliases ) ) {
+                                       // TODO: Validate the array values.
+                                       return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName . '.aliases', 'array' );
+                               }
+                       } else {
+                               $paramObj->aliases = array();
+                       }
+
+                       if ( isset( $paramObj->clones ) ) {
+                               if ( !is_array( $paramObj->clones ) ) {
+                                       // TODO: Validate the array values.
+                                       return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName . '.clones', 'array' );
+                               }
+                       } else {
+                               $paramObj->clones = array();
+                       }
+
+                       if ( isset( $paramObj->default ) ) {
+                               if ( !is_string( $paramObj->default ) ) {
+                                       return Status::newFatal( 
'templatedata-invalid-type', 'params.' . $paramName . '.default', 'string' );
+                               }
+                       } else {
+                               $paramObj->default = '';
+                       }
+               }
+
+               return Status::newGood();
+       }
+
+       public function getStatus() {
+               return $this->status;
+       }
+
+       public function getJSON() {
+               return json_encode( $this->data );
+       }
+
+       public function getHtml() {
+               $data = $this->data;
+               $html = 
+                       Html::openElement( 'div', array( 'mw-templatedata-wrap' 
) )
+                       . Html::element( 'p', array( 'mw-templatedata-desc' ), 
$data->description )
+                       . '<table class="wikitable sortable 
mw-templatedata-params">'
+                       . '<caption>Template parameters</caption>'
+                       . '<thead><tr>'
+                       . '<th>Name</th>'
+                       . '<th>Description</th>'
+                       . '<th>Default</th>'
+                       . '<th>Status</th>'
+                       . '</tr></thead>'
+                       . '<tbody>'
+                       ;
+               foreach ( $data->params as $paramName => $paramObj ) {
+                       $description = '';
+                       $default = '';
+                       $html .= '<tr>'
+                        . Html::element( 'th', array(), $paramName )
+                        // Description
+                        . Html::rawElement( 'td', array(
+                               'class' => array(
+                                       'mw-templatedata-field-empty' => 
$paramObj->description === '' && $paramObj->deprecated === false
+                               )
+                        ), $paramObj->description !== '' ? 
$paramObj->description : 'no description' )
+                        // Default
+                        . Html::element( 'td', array(
+                               'class' => array(
+                                       'mw-templatedata-field-empty' => 
$paramObj->default === ''
+                               )
+                        ), $paramObj->default !== '' ? $paramObj->default : 
'empty' )
+                        // Status
+                        . Html::element( 'td', array(),
+                               $paramObj->deprecated ? 'deprecated' : (
+                                       $paramObj->required ? 'required' : 
'optional'
+                               )
+                        )
+                        . '</tr>';
+               }
+               $html .= '</tbody></table>'
+                       . Html::closeElement( 'div' );
+
+               return $html;
+       }
+
+       private function __construct( stdClass $data = null ) {
+               $this->data = $data;
+       }
+
+}
diff --git a/api/ApiQueryTemplateData.php b/api/ApiQueryTemplateData.php
new file mode 100644
index 0000000..fcc8302
--- /dev/null
+++ b/api/ApiQueryTemplateData.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Implement the 'templatedata' query module in the API.
+ * Format JSON only.
+ *
+ * @ingroup API
+ * @emits error.code templatedata-corrupt
+ */
+class ApiQueryTemplateData extends ApiQueryBase {
+
+       public function __construct( $query, $module ) {
+               parent::__construct( $query, $module, 'td' );
+       }
+
+       public function execute() {
+               $params = $this->extractRequestParams();
+               $titles = $this->getPageSet()->getGoodTitles(); // page_id => 
Title object
+
+               if ( !count( $titles ) ) {
+                       return;
+               }
+
+               $this->addTables( 'page_props' );
+               $this->addFields( array( 'pp_page', 'pp_value' ) );
+               $this->addWhere( array(
+                       'pp_page' => array_keys( $titles ),
+                       'pp_propname' => 'templatedata'
+               ) );
+               $this->addOption( 'ORDER BY', 'pp_page' );
+
+               if ( $params['continue'] !== null ) {
+                       $fromid = intval( $params['continue'] );
+                       $this->addWhere( "pp_page >= $fromid" );
+               }
+
+               $res = $this->select( __METHOD__ );
+               foreach ( $res as $row ) {
+                       $rawData = $row->pp_value;
+                       $data = json_decode( $rawData );
+
+                       if ( !$data ) {
+                               $this->dieUsage( 'Database data is corrupted.', 
'templatedata-corrupt' );
+                       }
+                       $value = array();
+                       ApiResult::setContent( $value, $data->params, 'params' 
);
+
+                       $fit = $this->addPageSubItems( $row->pp_page, $value );
+
+                       if ( !$fit ) {
+                               $this->setContinueEnumParameter( 'continue', 
$row->pp_page );
+                               break;
+                       }
+               }
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'continue' => null,
+               );
+       }
+
+       public function getParamDescription() {
+               return array(
+                       'continue' => 'When more results are available, use 
this to continue',
+               );
+       }
+
+       public function getDescription() {
+               return 'Data stored by the TemplateData extension 
(https://www.mediawiki.org/Extension:TemplateData)';
+       }
+
+       // getPossibleErrors() is provided by ApiQueryBase
+
+       protected function getExamples() {
+               return array(
+                       
'api.php?action=query&prop=templatedata&titles=Template:Stub|Template:Example',
+               );
+       }
+}
diff --git a/resources/ext.templateData.css b/resources/ext.templateData.css
new file mode 100644
index 0000000..00ad7f6
--- /dev/null
+++ b/resources/ext.templateData.css
@@ -0,0 +1,16 @@
+.mw-templatedata-wrap {
+
+}
+
+.mw-templatedata-desc {
+
+}
+
+.mw-templatedata-params {
+
+}
+
+.mw-templatedata-field-empty {
+       font-style: italic;
+       color: #333;
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/49840
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icf305892a9512545a63f5a5280cc0d340c61585f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/TemplateData
Gerrit-Branch: master
Gerrit-Owner: Krinkle <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to