Index: trunk/extensions/SemanticFormsToolbar/Body.php |
— | — | @@ -0,0 +1,109 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class SemanticFormsToolbar { |
| 5 | + |
| 6 | + /** |
| 7 | + * Get the HTML to display a form. |
| 8 | + * @param $form Mixed: Title object of the form to use, or a string form definition. |
| 9 | + * @param $data Array: Associative array as would be POSTed to the form, to prefill |
| 10 | + * @param $existing_wikitext String: The wikitext to preload the form with. |
| 11 | + * @return HTML form output |
| 12 | + */ |
| 13 | + public static function getFormHTML( $form, $data = array(), $existing_wikitext = '' ) { |
| 14 | + |
| 15 | + $printer = new SFFormPrinter; |
| 16 | + |
| 17 | + if ( $form instanceof Title ) { |
| 18 | + $article = new Article( $form ); |
| 19 | + $form = $article->getContent(); |
| 20 | + } |
| 21 | + |
| 22 | + global $wgRequest; |
| 23 | + |
| 24 | + if ( count($data) ) { |
| 25 | + // Process into arrays and what not |
| 26 | + $data = self::convertFlatToArray( $data ); |
| 27 | + |
| 28 | + $oldRequest = $wgRequest; |
| 29 | + $wgRequest = new FauxRequest( $data ); |
| 30 | + } |
| 31 | + |
| 32 | + $output = $printer->formHTML( $form, count($data) > 0, |
| 33 | + strlen($existing_wikitext)>0, null, $existing_wikitext ); |
| 34 | + list( $form_text, $javascript_text, $data_text, $form_page_title, |
| 35 | + $generated_page_name ) = $output; |
| 36 | + |
| 37 | + if ( count($data) ) { |
| 38 | + $wgRequest = $oldRequest; |
| 39 | + } |
| 40 | + |
| 41 | + return $form_text; |
| 42 | + } |
| 43 | + |
| 44 | + /** |
| 45 | + * Get the wikitext output from a form. |
| 46 | + * @param $form Mixed: Title object of the form to use, or a String form definition. |
| 47 | + * @param $data Array: Associative array as would be POSTed to the form. |
| 48 | + * @return Wikitext |
| 49 | + */ |
| 50 | + public static function getWikitext( $form, $data = array() ) { |
| 51 | + |
| 52 | + $printer = new SFFormPrinter; |
| 53 | + |
| 54 | + if ( $form instanceof Title ) { |
| 55 | + $article = new Article( $form ); |
| 56 | + $form = $article->getContent(); |
| 57 | + } |
| 58 | + |
| 59 | + global $wgRequest; |
| 60 | + |
| 61 | + // Process into arrays and what not |
| 62 | + $data = self::convertFlatToArray( $data ); |
| 63 | + |
| 64 | + $oldRequest = $wgRequest; |
| 65 | + $wgRequest = new FauxRequest( $data ); |
| 66 | + |
| 67 | + $output = $printer->formHTML( $form, count($data) > 0, false ); |
| 68 | + list( $form_text, $javascript_text, $data_text, $form_page_title, |
| 69 | + $generated_page_name ) = $output; |
| 70 | + |
| 71 | + $wgRequest = $oldRequest; |
| 72 | + |
| 73 | + return $data_text; |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * Converts a flat array which implements arrays using square brackets to a |
| 78 | + * proper array. |
| 79 | + * @param $data Array: Input array, with sub-items declared with square brackets in keys. |
| 80 | + * @return Array: Output array, with sub-items declared with sub-arrays. |
| 81 | + */ |
| 82 | + protected static function convertFlatToArray( $data ) { |
| 83 | + $didReplace = true; |
| 84 | + |
| 85 | + while ( $didReplace ) { |
| 86 | + $didReplace = false; |
| 87 | + $output = array(); |
| 88 | + foreach( $data as $key => $value ) { |
| 89 | + if ( $key[strlen($key)-1] == ']' && strpos( $key, '[' ) >= 0 ) { |
| 90 | + $start = strrpos($key, '['); |
| 91 | + $subkey = substr( $key, $start + 1, -1 ); |
| 92 | + $newkey = substr( $key, 0, $start ); |
| 93 | + |
| 94 | + if ( isset( $output[$newkey] ) ) { |
| 95 | + $output[$newkey][$subkey] = $value; |
| 96 | + } else { |
| 97 | + $output[$newkey] = array( $subkey => $value ); |
| 98 | + } |
| 99 | + $didReplace = true; |
| 100 | + } else { |
| 101 | + $output[$key] = $value; |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + $data = $output; |
| 106 | + } |
| 107 | + |
| 108 | + return $data; |
| 109 | + } |
| 110 | +} |
Index: trunk/extensions/SemanticFormsToolbar/SemanticFormsToolbar.php |
— | — | @@ -0,0 +1,49 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +if ( !defined( 'MEDIAWIKI' ) ) |
| 5 | + die(); |
| 6 | + |
| 7 | +$wgExtensionCredits['other'][] = array( |
| 8 | + 'path' => __FILE__, |
| 9 | + 'name' => 'Semantic Forms Toolbar', |
| 10 | + 'author' => array( 'Andrew Garrett' ), |
| 11 | + 'descriptionmsg' => 'sft-desc', |
| 12 | +); |
| 13 | + |
| 14 | +$wgAutoloadClasses['SemanticFormsToolbar'] = dirname(__FILE__).'/Body.php'; |
| 15 | +$wgAutoloadClasses['ApiSemanticFormsToolbar'] = dirname(__FILE__).'/APISemanticFormsToolbar.php'; |
| 16 | + |
| 17 | +// Semantic Forms Toolbar |
| 18 | +$wgAPIModules['sftoolbar'] = 'ApiSemanticFormsToolbar'; |
| 19 | + |
| 20 | +$wgHooks['EditPage::showEditForm:initial'][] = 'efSFTEditForm'; |
| 21 | + |
| 22 | +// Resource modules for 1.17+ |
| 23 | +$wgResourceModules += array( |
| 24 | + 'ext.sftoolbar' => array( |
| 25 | + 'scripts' => 'ext.sftoolbar.js', |
| 26 | + 'localBasePath' => dirname( __FILE__ ) . '/modules', |
| 27 | + 'remoteExtPath' => 'SemanticFormsToolbar/modules', |
| 28 | + 'group' => 'ext.sftoolbar', |
| 29 | + ), |
| 30 | + 'ext.sftoolbar.json' => array( |
| 31 | + 'scripts' => 'ext.sftoolbar.json.js', |
| 32 | + 'localBasePath' => dirname( __FILE__ ) . '/modules', |
| 33 | + 'remoteExtPath' => 'SemanticFormsToolbar/modules', |
| 34 | + 'group' => 'ext.sftoolbar', |
| 35 | + ), |
| 36 | +); |
| 37 | + |
| 38 | +function efSFTEditForm() { |
| 39 | + global $wgOut; |
| 40 | + |
| 41 | + if ( is_callable( array($wgOut, 'addModules') ) ) { |
| 42 | + $wgOut->addModules( array('ext.sftoolbar', 'ext.sftoolbar.json') ); |
| 43 | + } else { |
| 44 | + $prefix = $wgScriptPath . '/SemanticFormsToolbar/modules/'; |
| 45 | + $wgOut->addScriptFile( $prefix.'ext.sftoolbar.json.js' ); |
| 46 | + $wgOut->addScriptFile( $prefix.'ext.sftoolbar.js' ); |
| 47 | + } |
| 48 | + |
| 49 | + return true; |
| 50 | +} |
Index: trunk/extensions/SemanticFormsToolbar/modules/ext.sftoolbar.js |
— | — | @@ -0,0 +1,230 @@ |
| 2 | +// sftoolbar.js |
| 3 | +// Main JavaScript for Semantic Forms Toolbar items. |
| 4 | +// Provides a template for toolbar dialogs that use Semantic Forms. |
| 5 | +// |
| 6 | +// Simple usage: |
| 7 | +// $j('#wpTextbox1').wikiEditor( 'addDialog', {'test-form' : sfToolbar.getDialog('Form:Test' ) } ) |
| 8 | +// |
| 9 | +// $j('#wpTextbox1').wikiEditor( 'addToToolbar', |
| 10 | +// { 'section' : 'main', 'group' : 'insert', 'tools' : { |
| 11 | +// 'test-dialog' : { 'label' : 'Test form', 'type' : 'button', |
| 12 | +// 'icon' : 'insert-reference.png', |
| 13 | +// 'action' : { |
| 14 | +// 'type' : 'dialog', 'module' : 'test-form' |
| 15 | +// } |
| 16 | +// } } } ); |
| 17 | + |
| 18 | +sfToolbar = { |
| 19 | + 'template' : { |
| 20 | + 'form' : undefined, |
| 21 | + |
| 22 | + 'titleMsg' : 'sftoolbar-dialog-title', |
| 23 | + |
| 24 | + 'id' : 'sftoolbar-dialog', |
| 25 | + |
| 26 | + 'html' : '<form class="sftoolbar-dialog-form sftoolbar-loading"></div>', |
| 27 | + |
| 28 | + 'init' : function() { |
| 29 | + }, |
| 30 | + |
| 31 | + 'dialog' : { |
| 32 | + 'width' : 500, |
| 33 | + 'class' : 'sftoolbar-dialog', |
| 34 | + 'buttons': { /* |
| 35 | + 'sftoolbar-insert' : function() { |
| 36 | + var citation = $j('#citetool-dialog-textbox').val(); |
| 37 | + var wikitext = '<ref name="'+citation+'">{{Cite|'+citation+'}}</ref>'; |
| 38 | + var context = $j(this).data('context'); |
| 39 | + |
| 40 | + if ( typeof context == undefined || !context ) { |
| 41 | + return; |
| 42 | + } |
| 43 | + |
| 44 | + $j(this).dialog('close'); |
| 45 | + $j('#citetool-dialog-textbox').val(''); |
| 46 | + $j('#citetool-dialog-preview').html(''); |
| 47 | + |
| 48 | + $j.wikiEditor.modules.toolbar.fn.doAction( |
| 49 | + context, { |
| 50 | + 'type' : 'replace', |
| 51 | + 'options' : { |
| 52 | + 'pre' : wikitext |
| 53 | + } |
| 54 | + }, $j(this) ); |
| 55 | + } // sftoolbar-insert |
| 56 | + */}, // buttons |
| 57 | + |
| 58 | + 'open' : function() { |
| 59 | + var context = $(this).data( 'context' ); |
| 60 | + |
| 61 | + var selection = context.$textarea.textSelection( 'getSelection' ); |
| 62 | + |
| 63 | + // If we have a selection, and it's a template, |
| 64 | + // reload the form |
| 65 | + selection = selection.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 66 | + if ( selection.substr(0,2) != '{{' || |
| 67 | + selection.substr( selection.length - 2 ) != '}}' ) |
| 68 | + { |
| 69 | + selection = ''; |
| 70 | + } |
| 71 | + |
| 72 | + // Request HTML from the API. |
| 73 | + var form = $j(this).find('.sftoolbar-dialog-form').attr('sft_form'); |
| 74 | + var dialogBox = $j(this).find('.sftoolbar-dialog-form'); |
| 75 | + $j(this).addClass('sftoolbar-loading'); |
| 76 | + |
| 77 | + var context = $(this).data( 'context' ); |
| 78 | + var request = { |
| 79 | + 'action' : 'sftoolbar', |
| 80 | + 'form' : form, |
| 81 | + 'output' : 'html', |
| 82 | + 'input-wikitext' : selection |
| 83 | + }; |
| 84 | + $j(this).data('form',form); |
| 85 | + |
| 86 | + var saveAction = function() { |
| 87 | + var formArray = $j('.sftoolbar-dialog-form').formToArray(); |
| 88 | + var data = {}; |
| 89 | + |
| 90 | + $j.each(formArray, function() { |
| 91 | + data[this.name] = this.value; |
| 92 | + } ); |
| 93 | + |
| 94 | + var saveRequest = { |
| 95 | + 'action' : 'sftoolbar', |
| 96 | + 'form' : form, |
| 97 | + 'output' : 'wikitext', |
| 98 | + 'input-data' : JSON.stringify(data) |
| 99 | + }; |
| 100 | + |
| 101 | + sfToolbar.apiRequest( saveRequest, function(data) { |
| 102 | + var wikitext = data.sftoolbar.wikitext; |
| 103 | + |
| 104 | + wikitext = wikitext.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); |
| 105 | + |
| 106 | + $j.wikiEditor.modules.toolbar.fn.doAction( |
| 107 | + context, { |
| 108 | + 'type' : 'replace', |
| 109 | + 'options' : { |
| 110 | + 'pre' : wikitext |
| 111 | + } |
| 112 | + }, $j(this) ); |
| 113 | + |
| 114 | + dialogBox.parent().dialog('close'); |
| 115 | + |
| 116 | + dialogBox.html(''); |
| 117 | + } ); |
| 118 | + |
| 119 | + return false; |
| 120 | + }; |
| 121 | + |
| 122 | + sfToolbar.apiRequest( request, function(data) { |
| 123 | + var html = $j(data.sftoolbar.html); |
| 124 | + |
| 125 | + html.find('#wpSave').attr('id', 'sftoolbar-save'); |
| 126 | + html.find('#wpPreview').remove(); |
| 127 | + html.find('#wpDiff').remove(); |
| 128 | + html.find('.editHelp').remove(); |
| 129 | + |
| 130 | + html.find('#sftoolbar-save').click( saveAction ); |
| 131 | + |
| 132 | + $j('.sftoolbar-dialog-form').html(html).removeClass('sftoolbar-loading'); |
| 133 | + } ); |
| 134 | + } |
| 135 | + } // dialog |
| 136 | + }, // template |
| 137 | + |
| 138 | + 'apiRequest' : function( request, callback ) { |
| 139 | + request.format = 'json'; |
| 140 | + |
| 141 | + var path = wgScriptPath+'/api'+wgScriptExtension; |
| 142 | + $j.post( path, request, |
| 143 | + function(data) { |
| 144 | + if ( callback ) { |
| 145 | + callback(data); |
| 146 | + } |
| 147 | + }, 'json' ); |
| 148 | + }, // apiRequest |
| 149 | + |
| 150 | + 'getDialog' : function(form) { |
| 151 | + var dialog = $j.extend( {}, sfToolbar.template, { 'form' : form } ); |
| 152 | + |
| 153 | + dialog.html = $j(dialog.html); |
| 154 | + dialog.html.attr('sft_form', form); |
| 155 | + |
| 156 | + dialog.html = $j('<div/>').append(dialog.html).html(); |
| 157 | + |
| 158 | + return dialog; |
| 159 | + } |
| 160 | + |
| 161 | +}; //sfToolbar |
| 162 | + |
| 163 | +// Shamelessly stolen from the jQuery form plugin, which is licensed under the GPL. |
| 164 | +// http://jquery.malsup.com/form/#download |
| 165 | +$.fn.formToArray = function() { |
| 166 | + var a = []; |
| 167 | + if (this.length == 0) return a; |
| 168 | + |
| 169 | + var form = this[0]; |
| 170 | + var els = form.elements; |
| 171 | + if (!els) return a; |
| 172 | + for(var i=0, max=els.length; i < max; i++) { |
| 173 | + var el = els[i]; |
| 174 | + var n = el.name; |
| 175 | + if (!n) continue; |
| 176 | + |
| 177 | + var v = $.fieldValue(el, true); |
| 178 | + if (v && v.constructor == Array) { |
| 179 | + for(var j=0, jmax=v.length; j < jmax; j++) |
| 180 | + a.push({name: n, value: v[j]}); |
| 181 | + } |
| 182 | + else if (v !== null && typeof v != 'undefined') |
| 183 | + a.push({name: n, value: v}); |
| 184 | + } |
| 185 | + |
| 186 | + if (form.clk) { |
| 187 | + // input type=='image' are not found in elements array! handle it here |
| 188 | + var $input = $(form.clk), input = $input[0], n = input.name; |
| 189 | + if (n && !input.disabled && input.type == 'image') { |
| 190 | + a.push({name: n, value: $input.val()}); |
| 191 | + a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y}); |
| 192 | + } |
| 193 | + } |
| 194 | + return a; |
| 195 | +}; |
| 196 | + |
| 197 | +/** |
| 198 | + * Returns the value of the field element. |
| 199 | + */ |
| 200 | +$.fieldValue = function(el, successful) { |
| 201 | + var n = el.name, t = el.type, tag = el.tagName.toLowerCase(); |
| 202 | + if (typeof successful == 'undefined') successful = true; |
| 203 | + |
| 204 | + if (successful && (!n || el.disabled || t == 'reset' || t == 'button' || |
| 205 | + (t == 'checkbox' || t == 'radio') && !el.checked || |
| 206 | + (t == 'submit' || t == 'image') && el.form && el.form.clk != el || |
| 207 | + tag == 'select' && el.selectedIndex == -1)) |
| 208 | + return null; |
| 209 | + |
| 210 | + if (tag == 'select') { |
| 211 | + var index = el.selectedIndex; |
| 212 | + if (index < 0) return null; |
| 213 | + var a = [], ops = el.options; |
| 214 | + var one = (t == 'select-one'); |
| 215 | + var max = (one ? index+1 : ops.length); |
| 216 | + for(var i=(one ? index : 0); i < max; i++) { |
| 217 | + var op = ops[i]; |
| 218 | + if (op.selected) { |
| 219 | + var v = op.value; |
| 220 | + if (!v) // extra pain for IE... |
| 221 | + v = (op.attributes && op.attributes['value'] && |
| 222 | + !(op.attributes['value'].specified)) |
| 223 | + ? op.text : op.value; |
| 224 | + if (one) return v; |
| 225 | + a.push(v); |
| 226 | + } |
| 227 | + } |
| 228 | + return a; |
| 229 | + } |
| 230 | + return el.value; |
| 231 | +}; |
Index: trunk/extensions/SemanticFormsToolbar/modules/ext.sftoolbar.json.js |
— | — | @@ -0,0 +1,480 @@ |
| 2 | +/* |
| 3 | + http://www.JSON.org/json2.js |
| 4 | + 2011-02-23 |
| 5 | + |
| 6 | + Public Domain. |
| 7 | + |
| 8 | + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. |
| 9 | + |
| 10 | + See http://www.JSON.org/js.html |
| 11 | + |
| 12 | + |
| 13 | + This code should be minified before deployment. |
| 14 | + See http://javascript.crockford.com/jsmin.html |
| 15 | + |
| 16 | + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO |
| 17 | + NOT CONTROL. |
| 18 | + |
| 19 | + |
| 20 | + This file creates a global JSON object containing two methods: stringify |
| 21 | + and parse. |
| 22 | + |
| 23 | + JSON.stringify(value, replacer, space) |
| 24 | + value any JavaScript value, usually an object or array. |
| 25 | + |
| 26 | + replacer an optional parameter that determines how object |
| 27 | + values are stringified for objects. It can be a |
| 28 | + function or an array of strings. |
| 29 | + |
| 30 | + space an optional parameter that specifies the indentation |
| 31 | + of nested structures. If it is omitted, the text will |
| 32 | + be packed without extra whitespace. If it is a number, |
| 33 | + it will specify the number of spaces to indent at each |
| 34 | + level. If it is a string (such as '\t' or ' '), |
| 35 | + it contains the characters used to indent at each level. |
| 36 | + |
| 37 | + This method produces a JSON text from a JavaScript value. |
| 38 | + |
| 39 | + When an object value is found, if the object contains a toJSON |
| 40 | + method, its toJSON method will be called and the result will be |
| 41 | + stringified. A toJSON method does not serialize: it returns the |
| 42 | + value represented by the name/value pair that should be serialized, |
| 43 | + or undefined if nothing should be serialized. The toJSON method |
| 44 | + will be passed the key associated with the value, and this will be |
| 45 | + bound to the value |
| 46 | + |
| 47 | + For example, this would serialize Dates as ISO strings. |
| 48 | + |
| 49 | + Date.prototype.toJSON = function (key) { |
| 50 | + function f(n) { |
| 51 | + // Format integers to have at least two digits. |
| 52 | + return n < 10 ? '0' + n : n; |
| 53 | + } |
| 54 | + |
| 55 | + return this.getUTCFullYear() + '-' + |
| 56 | + f(this.getUTCMonth() + 1) + '-' + |
| 57 | + f(this.getUTCDate()) + 'T' + |
| 58 | + f(this.getUTCHours()) + ':' + |
| 59 | + f(this.getUTCMinutes()) + ':' + |
| 60 | + f(this.getUTCSeconds()) + 'Z'; |
| 61 | + }; |
| 62 | + |
| 63 | + You can provide an optional replacer method. It will be passed the |
| 64 | + key and value of each member, with this bound to the containing |
| 65 | + object. The value that is returned from your method will be |
| 66 | + serialized. If your method returns undefined, then the member will |
| 67 | + be excluded from the serialization. |
| 68 | + |
| 69 | + If the replacer parameter is an array of strings, then it will be |
| 70 | + used to select the members to be serialized. It filters the results |
| 71 | + such that only members with keys listed in the replacer array are |
| 72 | + stringified. |
| 73 | + |
| 74 | + Values that do not have JSON representations, such as undefined or |
| 75 | + functions, will not be serialized. Such values in objects will be |
| 76 | + dropped; in arrays they will be replaced with null. You can use |
| 77 | + a replacer function to replace those with JSON values. |
| 78 | + JSON.stringify(undefined) returns undefined. |
| 79 | + |
| 80 | + The optional space parameter produces a stringification of the |
| 81 | + value that is filled with line breaks and indentation to make it |
| 82 | + easier to read. |
| 83 | + |
| 84 | + If the space parameter is a non-empty string, then that string will |
| 85 | + be used for indentation. If the space parameter is a number, then |
| 86 | + the indentation will be that many spaces. |
| 87 | + |
| 88 | + Example: |
| 89 | + |
| 90 | + text = JSON.stringify(['e', {pluribus: 'unum'}]); |
| 91 | + // text is '["e",{"pluribus":"unum"}]' |
| 92 | + |
| 93 | + |
| 94 | + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); |
| 95 | + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' |
| 96 | + |
| 97 | + text = JSON.stringify([new Date()], function (key, value) { |
| 98 | + return this[key] instanceof Date ? |
| 99 | + 'Date(' + this[key] + ')' : value; |
| 100 | + }); |
| 101 | + // text is '["Date(---current time---)"]' |
| 102 | + |
| 103 | + |
| 104 | + JSON.parse(text, reviver) |
| 105 | + This method parses a JSON text to produce an object or array. |
| 106 | + It can throw a SyntaxError exception. |
| 107 | + |
| 108 | + The optional reviver parameter is a function that can filter and |
| 109 | + transform the results. It receives each of the keys and values, |
| 110 | + and its return value is used instead of the original value. |
| 111 | + If it returns what it received, then the structure is not modified. |
| 112 | + If it returns undefined then the member is deleted. |
| 113 | + |
| 114 | + Example: |
| 115 | + |
| 116 | + // Parse the text. Values that look like ISO date strings will |
| 117 | + // be converted to Date objects. |
| 118 | + |
| 119 | + myData = JSON.parse(text, function (key, value) { |
| 120 | + var a; |
| 121 | + if (typeof value === 'string') { |
| 122 | + a = |
| 123 | +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); |
| 124 | + if (a) { |
| 125 | + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], |
| 126 | + +a[5], +a[6])); |
| 127 | + } |
| 128 | + } |
| 129 | + return value; |
| 130 | + }); |
| 131 | + |
| 132 | + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { |
| 133 | + var d; |
| 134 | + if (typeof value === 'string' && |
| 135 | + value.slice(0, 5) === 'Date(' && |
| 136 | + value.slice(-1) === ')') { |
| 137 | + d = new Date(value.slice(5, -1)); |
| 138 | + if (d) { |
| 139 | + return d; |
| 140 | + } |
| 141 | + } |
| 142 | + return value; |
| 143 | + }); |
| 144 | + |
| 145 | + |
| 146 | + This is a reference implementation. You are free to copy, modify, or |
| 147 | + redistribute. |
| 148 | +*/ |
| 149 | + |
| 150 | +/*jslint evil: true, strict: false, regexp: false */ |
| 151 | + |
| 152 | +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, |
| 153 | + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, |
| 154 | + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, |
| 155 | + lastIndex, length, parse, prototype, push, replace, slice, stringify, |
| 156 | + test, toJSON, toString, valueOf |
| 157 | +*/ |
| 158 | + |
| 159 | + |
| 160 | +// Create a JSON object only if one does not already exist. We create the |
| 161 | +// methods in a closure to avoid creating global variables. |
| 162 | + |
| 163 | +var JSON; |
| 164 | +if (!JSON) { |
| 165 | + JSON = {}; |
| 166 | +} |
| 167 | + |
| 168 | +(function () { |
| 169 | + "use strict"; |
| 170 | + |
| 171 | + function f(n) { |
| 172 | + // Format integers to have at least two digits. |
| 173 | + return n < 10 ? '0' + n : n; |
| 174 | + } |
| 175 | + |
| 176 | + if (typeof Date.prototype.toJSON !== 'function') { |
| 177 | + |
| 178 | + Date.prototype.toJSON = function (key) { |
| 179 | + |
| 180 | + return isFinite(this.valueOf()) ? |
| 181 | + this.getUTCFullYear() + '-' + |
| 182 | + f(this.getUTCMonth() + 1) + '-' + |
| 183 | + f(this.getUTCDate()) + 'T' + |
| 184 | + f(this.getUTCHours()) + ':' + |
| 185 | + f(this.getUTCMinutes()) + ':' + |
| 186 | + f(this.getUTCSeconds()) + 'Z' : null; |
| 187 | + }; |
| 188 | + |
| 189 | + String.prototype.toJSON = |
| 190 | + Number.prototype.toJSON = |
| 191 | + Boolean.prototype.toJSON = function (key) { |
| 192 | + return this.valueOf(); |
| 193 | + }; |
| 194 | + } |
| 195 | + |
| 196 | + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
| 197 | + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
| 198 | + gap, |
| 199 | + indent, |
| 200 | + meta = { // table of character substitutions |
| 201 | + '\b': '\\b', |
| 202 | + '\t': '\\t', |
| 203 | + '\n': '\\n', |
| 204 | + '\f': '\\f', |
| 205 | + '\r': '\\r', |
| 206 | + '"' : '\\"', |
| 207 | + '\\': '\\\\' |
| 208 | + }, |
| 209 | + rep; |
| 210 | + |
| 211 | + |
| 212 | + function quote(string) { |
| 213 | + |
| 214 | +// If the string contains no control characters, no quote characters, and no |
| 215 | +// backslash characters, then we can safely slap some quotes around it. |
| 216 | +// Otherwise we must also replace the offending characters with safe escape |
| 217 | +// sequences. |
| 218 | + |
| 219 | + escapable.lastIndex = 0; |
| 220 | + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { |
| 221 | + var c = meta[a]; |
| 222 | + return typeof c === 'string' ? c : |
| 223 | + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
| 224 | + }) + '"' : '"' + string + '"'; |
| 225 | + } |
| 226 | + |
| 227 | + |
| 228 | + function str(key, holder) { |
| 229 | + |
| 230 | +// Produce a string from holder[key]. |
| 231 | + |
| 232 | + var i, // The loop counter. |
| 233 | + k, // The member key. |
| 234 | + v, // The member value. |
| 235 | + length, |
| 236 | + mind = gap, |
| 237 | + partial, |
| 238 | + value = holder[key]; |
| 239 | + |
| 240 | +// If the value has a toJSON method, call it to obtain a replacement value. |
| 241 | + |
| 242 | + if (value && typeof value === 'object' && |
| 243 | + typeof value.toJSON === 'function') { |
| 244 | + value = value.toJSON(key); |
| 245 | + } |
| 246 | + |
| 247 | +// If we were called with a replacer function, then call the replacer to |
| 248 | +// obtain a replacement value. |
| 249 | + |
| 250 | + if (typeof rep === 'function') { |
| 251 | + value = rep.call(holder, key, value); |
| 252 | + } |
| 253 | + |
| 254 | +// What happens next depends on the value's type. |
| 255 | + |
| 256 | + switch (typeof value) { |
| 257 | + case 'string': |
| 258 | + return quote(value); |
| 259 | + |
| 260 | + case 'number': |
| 261 | + |
| 262 | +// JSON numbers must be finite. Encode non-finite numbers as null. |
| 263 | + |
| 264 | + return isFinite(value) ? String(value) : 'null'; |
| 265 | + |
| 266 | + case 'boolean': |
| 267 | + case 'null': |
| 268 | + |
| 269 | +// If the value is a boolean or null, convert it to a string. Note: |
| 270 | +// typeof null does not produce 'null'. The case is included here in |
| 271 | +// the remote chance that this gets fixed someday. |
| 272 | + |
| 273 | + return String(value); |
| 274 | + |
| 275 | +// If the type is 'object', we might be dealing with an object or an array or |
| 276 | +// null. |
| 277 | + |
| 278 | + case 'object': |
| 279 | + |
| 280 | +// Due to a specification blunder in ECMAScript, typeof null is 'object', |
| 281 | +// so watch out for that case. |
| 282 | + |
| 283 | + if (!value) { |
| 284 | + return 'null'; |
| 285 | + } |
| 286 | + |
| 287 | +// Make an array to hold the partial results of stringifying this object value. |
| 288 | + |
| 289 | + gap += indent; |
| 290 | + partial = []; |
| 291 | + |
| 292 | +// Is the value an array? |
| 293 | + |
| 294 | + if (Object.prototype.toString.apply(value) === '[object Array]') { |
| 295 | + |
| 296 | +// The value is an array. Stringify every element. Use null as a placeholder |
| 297 | +// for non-JSON values. |
| 298 | + |
| 299 | + length = value.length; |
| 300 | + for (i = 0; i < length; i += 1) { |
| 301 | + partial[i] = str(i, value) || 'null'; |
| 302 | + } |
| 303 | + |
| 304 | +// Join all of the elements together, separated with commas, and wrap them in |
| 305 | +// brackets. |
| 306 | + |
| 307 | + v = partial.length === 0 ? '[]' : gap ? |
| 308 | + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : |
| 309 | + '[' + partial.join(',') + ']'; |
| 310 | + gap = mind; |
| 311 | + return v; |
| 312 | + } |
| 313 | + |
| 314 | +// If the replacer is an array, use it to select the members to be stringified. |
| 315 | + |
| 316 | + if (rep && typeof rep === 'object') { |
| 317 | + length = rep.length; |
| 318 | + for (i = 0; i < length; i += 1) { |
| 319 | + if (typeof rep[i] === 'string') { |
| 320 | + k = rep[i]; |
| 321 | + v = str(k, value); |
| 322 | + if (v) { |
| 323 | + partial.push(quote(k) + (gap ? ': ' : ':') + v); |
| 324 | + } |
| 325 | + } |
| 326 | + } |
| 327 | + } else { |
| 328 | + |
| 329 | +// Otherwise, iterate through all of the keys in the object. |
| 330 | + |
| 331 | + for (k in value) { |
| 332 | + if (Object.prototype.hasOwnProperty.call(value, k)) { |
| 333 | + v = str(k, value); |
| 334 | + if (v) { |
| 335 | + partial.push(quote(k) + (gap ? ': ' : ':') + v); |
| 336 | + } |
| 337 | + } |
| 338 | + } |
| 339 | + } |
| 340 | + |
| 341 | +// Join all of the member texts together, separated with commas, |
| 342 | +// and wrap them in braces. |
| 343 | + |
| 344 | + v = partial.length === 0 ? '{}' : gap ? |
| 345 | + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : |
| 346 | + '{' + partial.join(',') + '}'; |
| 347 | + gap = mind; |
| 348 | + return v; |
| 349 | + } |
| 350 | + } |
| 351 | + |
| 352 | +// If the JSON object does not yet have a stringify method, give it one. |
| 353 | + |
| 354 | + if (typeof JSON.stringify !== 'function') { |
| 355 | + JSON.stringify = function (value, replacer, space) { |
| 356 | + |
| 357 | +// The stringify method takes a value and an optional replacer, and an optional |
| 358 | +// space parameter, and returns a JSON text. The replacer can be a function |
| 359 | +// that can replace values, or an array of strings that will select the keys. |
| 360 | +// A default replacer method can be provided. Use of the space parameter can |
| 361 | +// produce text that is more easily readable. |
| 362 | + |
| 363 | + var i; |
| 364 | + gap = ''; |
| 365 | + indent = ''; |
| 366 | + |
| 367 | +// If the space parameter is a number, make an indent string containing that |
| 368 | +// many spaces. |
| 369 | + |
| 370 | + if (typeof space === 'number') { |
| 371 | + for (i = 0; i < space; i += 1) { |
| 372 | + indent += ' '; |
| 373 | + } |
| 374 | + |
| 375 | +// If the space parameter is a string, it will be used as the indent string. |
| 376 | + |
| 377 | + } else if (typeof space === 'string') { |
| 378 | + indent = space; |
| 379 | + } |
| 380 | + |
| 381 | +// If there is a replacer, it must be a function or an array. |
| 382 | +// Otherwise, throw an error. |
| 383 | + |
| 384 | + rep = replacer; |
| 385 | + if (replacer && typeof replacer !== 'function' && |
| 386 | + (typeof replacer !== 'object' || |
| 387 | + typeof replacer.length !== 'number')) { |
| 388 | + throw new Error('JSON.stringify'); |
| 389 | + } |
| 390 | + |
| 391 | +// Make a fake root object containing our value under the key of ''. |
| 392 | +// Return the result of stringifying the value. |
| 393 | + |
| 394 | + return str('', {'': value}); |
| 395 | + }; |
| 396 | + } |
| 397 | + |
| 398 | + |
| 399 | +// If the JSON object does not yet have a parse method, give it one. |
| 400 | + |
| 401 | + if (typeof JSON.parse !== 'function') { |
| 402 | + JSON.parse = function (text, reviver) { |
| 403 | + |
| 404 | +// The parse method takes a text and an optional reviver function, and returns |
| 405 | +// a JavaScript value if the text is a valid JSON text. |
| 406 | + |
| 407 | + var j; |
| 408 | + |
| 409 | + function walk(holder, key) { |
| 410 | + |
| 411 | +// The walk method is used to recursively walk the resulting structure so |
| 412 | +// that modifications can be made. |
| 413 | + |
| 414 | + var k, v, value = holder[key]; |
| 415 | + if (value && typeof value === 'object') { |
| 416 | + for (k in value) { |
| 417 | + if (Object.prototype.hasOwnProperty.call(value, k)) { |
| 418 | + v = walk(value, k); |
| 419 | + if (v !== undefined) { |
| 420 | + value[k] = v; |
| 421 | + } else { |
| 422 | + delete value[k]; |
| 423 | + } |
| 424 | + } |
| 425 | + } |
| 426 | + } |
| 427 | + return reviver.call(holder, key, value); |
| 428 | + } |
| 429 | + |
| 430 | + |
| 431 | +// Parsing happens in four stages. In the first stage, we replace certain |
| 432 | +// Unicode characters with escape sequences. JavaScript handles many characters |
| 433 | +// incorrectly, either silently deleting them, or treating them as line endings. |
| 434 | + |
| 435 | + text = String(text); |
| 436 | + cx.lastIndex = 0; |
| 437 | + if (cx.test(text)) { |
| 438 | + text = text.replace(cx, function (a) { |
| 439 | + return '\\u' + |
| 440 | + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
| 441 | + }); |
| 442 | + } |
| 443 | + |
| 444 | +// In the second stage, we run the text against regular expressions that look |
| 445 | +// for non-JSON patterns. We are especially concerned with '()' and 'new' |
| 446 | +// because they can cause invocation, and '=' because it can cause mutation. |
| 447 | +// But just to be safe, we want to reject all unexpected forms. |
| 448 | + |
| 449 | +// We split the second stage into 4 regexp operations in order to work around |
| 450 | +// crippling inefficiencies in IE's and Safari's regexp engines. First we |
| 451 | +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we |
| 452 | +// replace all simple value tokens with ']' characters. Third, we delete all |
| 453 | +// open brackets that follow a colon or comma or that begin the text. Finally, |
| 454 | +// we look to see that the remaining characters are only whitespace or ']' or |
| 455 | +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. |
| 456 | + |
| 457 | + if (/^[\],:{}\s]*$/ |
| 458 | + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') |
| 459 | + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') |
| 460 | + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { |
| 461 | + |
| 462 | +// In the third stage we use the eval function to compile the text into a |
| 463 | +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity |
| 464 | +// in JavaScript: it can begin a block or an object literal. We wrap the text |
| 465 | +// in parens to eliminate the ambiguity. |
| 466 | + |
| 467 | + j = eval('(' + text + ')'); |
| 468 | + |
| 469 | +// In the optional fourth stage, we recursively walk the new structure, passing |
| 470 | +// each name/value pair to a reviver function for possible transformation. |
| 471 | + |
| 472 | + return typeof reviver === 'function' ? |
| 473 | + walk({'': j}, '') : j; |
| 474 | + } |
| 475 | + |
| 476 | +// If the text is not JSON parseable, then a SyntaxError is thrown. |
| 477 | + |
| 478 | + throw new SyntaxError('JSON.parse'); |
| 479 | + }; |
| 480 | + } |
| 481 | +}()); |
Index: trunk/extensions/SemanticFormsToolbar/APISemanticFormsToolbar.php |
— | — | @@ -0,0 +1,64 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class ApiSemanticFormsToolbar extends ApiBase { |
| 5 | + public function __construct( $main, $action ) { |
| 6 | + parent::__construct( $main, $action ); |
| 7 | + } |
| 8 | + |
| 9 | + public function execute() { |
| 10 | + $params = $this->extractRequestParams(); |
| 11 | + |
| 12 | + $result = array(); |
| 13 | + |
| 14 | + $form = Title::newFromText( $params['form'] ); |
| 15 | + |
| 16 | + if ( $params['input-data'] ) { |
| 17 | + $data = json_decode($params['input-data']); |
| 18 | + } else { |
| 19 | + $data = array(); |
| 20 | + } |
| 21 | + |
| 22 | + $wikitext = $params['input-wikitext']; |
| 23 | + |
| 24 | + if ( in_array( 'wikitext', $params['output'] ) ) { |
| 25 | + $result['wikitext'] = SemanticFormsToolbar::getWikitext( $form, $data ); |
| 26 | + } |
| 27 | + |
| 28 | + if ( in_array('html', $params['output']) ) { |
| 29 | + $result['html'] = SemanticFormsToolbar::getFormHTML( $form, $data, $wikitext ); |
| 30 | + } |
| 31 | + |
| 32 | + $this->getResult()->addValue( null, 'sftoolbar', $result ); |
| 33 | + } |
| 34 | + |
| 35 | + public function getAllowedParams() { |
| 36 | + return array( |
| 37 | + 'form' => array( |
| 38 | + ApiBase::PARAM_REQUIRED => true, |
| 39 | + ), |
| 40 | + 'input-wikitext' => null, |
| 41 | + 'input-data' => null, |
| 42 | + 'output' => array( |
| 43 | + ApiBase::PARAM_DFLT => 'html', |
| 44 | + ApiBase::PARAM_TYPE => array( |
| 45 | + 'html', |
| 46 | + 'wikitext', |
| 47 | + ), |
| 48 | + ApiBase::PARAM_ISMULTI => true, |
| 49 | + ), |
| 50 | + ); |
| 51 | + } |
| 52 | + |
| 53 | + protected function getParamDescription() { |
| 54 | + return array( |
| 55 | + 'form' => 'The form in question', |
| 56 | + 'input-wikitext' => 'The existing wikitext', |
| 57 | + 'input-data' => 'JSON-encoded associative array of stuff that would be POSTed to the form', |
| 58 | + 'output' => 'The output type, form HTML or generated wikitext', |
| 59 | + ); |
| 60 | + } |
| 61 | + |
| 62 | + public function getVersion() { |
| 63 | + return '1'; |
| 64 | + } |
| 65 | +} |