Index: trunk/extensions/SemanticForms/includes/SF_FormUtils.php |
— | — | @@ -9,412 +9,33 @@ |
10 | 10 | */ |
11 | 11 | |
12 | 12 | class SFFormUtils { |
| 13 | + static function setGlobalJSVariables( &$vars ) { |
| 14 | + global $sfgAdderButtons, $sfgRemoverButtons; |
| 15 | + global $sfgAutocompleteMappings, $sfgAutocompleteDataTypes, $sfgAutocompleteValues; |
| 16 | + global $sfgAutocompleteOnAllChars, $sfgComboBoxInputs, $sfgAutogrowInputs; |
| 17 | + global $sfgJSValidationCalls, $sfgShowOnSelectCalls; |
13 | 18 | |
14 | | - /** |
15 | | - * All the Javascript calls to validate both the type of each |
16 | | - * form field and their presence, for mandatory fields |
17 | | - */ |
18 | | - static function validationJavascript() { |
19 | | - global $sfgJSValidationCalls; |
20 | | - |
21 | | - $form_errors_header = Xml::escapeJsString( wfMsg( 'sf_formerrors_header' ) ); |
22 | | - $blank_error_str = Xml::escapeJsString( wfMsg( 'sf_blank_error' ) ); |
23 | | - $bad_url_error_str = Xml::escapeJsString( wfMsg( 'sf_bad_url_error' ) ); |
24 | | - $bad_email_error_str = Xml::escapeJsString( wfMsg( 'sf_bad_email_error' ) ); |
25 | | - $bad_number_error_str = Xml::escapeJsString( wfMsg( 'sf_bad_number_error' ) ); |
26 | | - $bad_integer_error_str = Xml::escapeJsString( wfMsg( 'sf_bad_integer_error' ) ); |
27 | | - $bad_date_error_str = Xml::escapeJsString( wfMsg( 'sf_bad_date_error' ) ); |
28 | | - |
29 | | - $javascript_text = <<<END |
30 | | - |
31 | | -function validate_mandatory_field(field_id, info_id) { |
32 | | - var field = document.getElementById(field_id); |
33 | | - // if there's nothing at that field ID, ignore it - it's probably |
34 | | - // a hidden field |
35 | | - if (field == null) { |
| 19 | + $vars['sfgRemoveText'] = wfMsg( 'sf_formedit_remove' ); |
| 20 | + $vars['sfgAdderButtons'] = $sfgAdderButtons; |
| 21 | + $vars['sfgRemoverButtons'] = $sfgRemoverButtons; |
| 22 | + $vars['autocompleteOnAllChars'] = $sfgAutocompleteOnAllChars; |
| 23 | + $vars['sfgAutocompleteMappings'] = $sfgAutocompleteMappings; |
| 24 | + $vars['sfgAutocompleteValues'] = $sfgAutocompleteValues; |
| 25 | + $vars['sfgAutocompleteDataTypes'] = $sfgAutocompleteDataTypes; |
| 26 | + $vars['sfgComboBoxInputs'] = $sfgComboBoxInputs; |
| 27 | + $vars['sfgAutogrowInputs'] = $sfgAutogrowInputs; |
| 28 | + $vars['sfgFormErrorsHeader'] = Xml::escapeJsString( wfMsg( 'sf_formerrors_header' ) ); |
| 29 | + $vars['sfgBlankErrorStr'] = Xml::escapeJsString( wfMsg( 'sf_blank_error' ) ); |
| 30 | + $vars['sfgBadURLErrorStr'] = Xml::escapeJsString( wfMsg( 'sf_bad_url_error' ) ); |
| 31 | + $vars['sfgBadEmailErrorStr'] = Xml::escapeJsString( wfMsg( 'sf_bad_email_error' ) ); |
| 32 | + $vars['sfgBadNumberErrorStr'] = Xml::escapeJsString( wfMsg( 'sf_bad_number_error' ) ); |
| 33 | + $vars['sfgBadIntegerErrorStr'] = Xml::escapeJsString( wfMsg( 'sf_bad_integer_error' ) ); |
| 34 | + $vars['sfgBadDateErrorStr'] = Xml::escapeJsString( wfMsg( 'sf_bad_date_error' ) ); |
| 35 | + $vars['sfgJSValidationCalls'] = $sfgJSValidationCalls; |
| 36 | + $vars['sfgShowOnSelectCalls'] = $sfgShowOnSelectCalls; |
36 | 37 | return true; |
37 | 38 | } |
38 | | - if (field.value.replace(/\s+/, '') == '') { |
39 | | - var info_span = document.getElementById(info_id); |
40 | | - if ( info_span == null ) { |
41 | | - alert ("no info span found at " + info_id + "!"); |
42 | | - } else { |
43 | | - info_span.innerHTML = "$blank_error_str"; |
44 | | - } |
45 | | - return false; |
46 | | - } else { |
47 | | - return true; |
48 | | - } |
49 | | -} |
50 | 39 | |
51 | | -// special handling for radiobuttons, because what's being checked |
52 | | -// is the first radiobutton, which has value of "None" |
53 | | -function validate_mandatory_radiobutton(none_button_id, info_id) { |
54 | | - none_button = document.getElementById(none_button_id); |
55 | | - if (none_button && none_button.checked) { |
56 | | - info_span = document.getElementById(info_id); |
57 | | - info_span.innerHTML = "$blank_error_str"; |
58 | | - return false; |
59 | | - } else { |
60 | | - return true; |
61 | | - } |
62 | | -} |
63 | | - |
64 | | -function validate_mandatory_combobox(field_id, info_id) { |
65 | | - var field = jQuery('input#' + field_id); |
66 | | - // if there's nothing at that field ID, ignore it - it's probably |
67 | | - // a hidden field |
68 | | - if (field == null) { |
69 | | - return true; |
70 | | - } |
71 | | - // FIXME |
72 | | - // field.val() unfortunately doesn't work in IE - it just returns |
73 | | - // "undefined". For now, if that happens, just exit |
74 | | - var value = field.val(); |
75 | | - if (value == undefined) { |
76 | | - alert(field.html()); |
77 | | - return true; |
78 | | - } |
79 | | - if (value.replace(/\s+/, '') == '') { |
80 | | - var info_span = document.getElementById(info_id); |
81 | | - info_span.innerHTML = "$blank_error_str"; |
82 | | - return false; |
83 | | - } else { |
84 | | - return true; |
85 | | - } |
86 | | -} |
87 | | - |
88 | | -function validate_mandatory_checkboxes(field_id, info_id) { |
89 | | - // get all checkboxes - the "field_id" in this case is the span |
90 | | - // surrounding all the checkboxes |
91 | | - var checkboxes = jQuery('span#' + field_id + " > span > input"); |
92 | | - var all_unchecked = true; |
93 | | - for (var i = 0; i < checkboxes.length; i++) { |
94 | | - if (checkboxes[i].checked) { |
95 | | - all_unchecked = false; |
96 | | - } |
97 | | - } |
98 | | - if (all_unchecked) { |
99 | | - info_span = document.getElementById(info_id); |
100 | | - info_span.innerHTML = "$blank_error_str"; |
101 | | - return false; |
102 | | - } else { |
103 | | - return true; |
104 | | - } |
105 | | -} |
106 | | - |
107 | | -// validate a mandatory field that exists across multiple instances of |
108 | | -// a template - we have to find each one, matching on the pattern of its |
109 | | -// ID, and validate it |
110 | | -function validate_multiple_mandatory_fields(field_num) { |
111 | | - var num_errors = 0; |
112 | | - elems = document.getElementsByTagName("*"); |
113 | | - var field_pattern = new RegExp('input_(.*)_' + field_num); |
114 | | - for (var i = 0; i < elems.length; i++) { |
115 | | - id = elems[i].id; |
116 | | - if (matches = field_pattern.exec(id)) { |
117 | | - instance_num = matches[1]; |
118 | | - var input_name = "input_" + instance_num + "_" + field_num; |
119 | | - var info_name = "info_" + instance_num + "_" + field_num; |
120 | | - if (! validate_mandatory_field(input_name, info_name)) { |
121 | | - num_errors += 1; |
122 | | - } |
123 | | - } |
124 | | - } |
125 | | - return (num_errors == 0); |
126 | | -} |
127 | | - |
128 | | -function validate_field_type(field_id, type, info_id) { |
129 | | - field = document.getElementById(field_id); |
130 | | - if (type != 'date' && field.value == '') { |
131 | | - return true; |
132 | | - } else { |
133 | | - if (type == 'URL') { |
134 | | - // code borrowed from http://snippets.dzone.com/posts/show/452 |
135 | | - var url_regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; |
136 | | - if (url_regexp.test(field.value)) { |
137 | | - return true; |
138 | | - } else { |
139 | | - info_span = document.getElementById(info_id); |
140 | | - info_span.innerHTML = "$bad_url_error_str"; |
141 | | - return false; |
142 | | - } |
143 | | - } else if (type == 'email') { |
144 | | - // code borrowed from http://javascript.internet.com/forms/email-validation---basic.html |
145 | | - var email_regexp = /^\s*\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,6})+\s*$/; |
146 | | - if (email_regexp.test(field.value)) { |
147 | | - return true; |
148 | | - } else { |
149 | | - info_span = document.getElementById(info_id); |
150 | | - info_span.innerHTML = "$bad_email_error_str"; |
151 | | - return false; |
152 | | - } |
153 | | - } else if (type == 'number') { |
154 | | - if (field.value.match(/^\s*\-?[\d\.,]+\s*$/)) { |
155 | | - return true; |
156 | | - } else { |
157 | | - info_span = document.getElementById(info_id); |
158 | | - info_span.innerHTML = "$bad_number_error_str"; |
159 | | - return false; |
160 | | - } |
161 | | - } else if (type == 'date') { |
162 | | - // validate only if day and year fields are both filled in |
163 | | - day_field = document.getElementById(field_id + "_day"); |
164 | | - year_field = document.getElementById(field_id + "_year"); |
165 | | - if (day_field.value == '' || year_field.value == '') { |
166 | | - return true; |
167 | | - } else if (day_field.value.match(/^\d+$/) && |
168 | | - day_field.value <= 31) { |
169 | | - // no year validation, since it can also include |
170 | | - // 'BC' and possibly other non-number strings |
171 | | - return true; |
172 | | - } else { |
173 | | - info_span = document.getElementById(info_id); |
174 | | - info_span.innerHTML = "$bad_date_error_str"; |
175 | | - return false; |
176 | | - } |
177 | | - } else { |
178 | | - return true; |
179 | | - } |
180 | | - } |
181 | | -} |
182 | | - |
183 | | -// same as validate_multiple_mandatory_fields(), but for type validation |
184 | | -function validate_type_of_multiple_fields(field_num, type) { |
185 | | - var num_errors = 0; |
186 | | - elems = document.getElementsByTagName("*"); |
187 | | - var field_pattern = new RegExp('input_(.*)_' + field_num); |
188 | | - for (var i = 0; i < elems.length; i++) { |
189 | | - id = elems[i].id; |
190 | | - if (matches = field_pattern.exec(id)) { |
191 | | - instance_num = matches[1]; |
192 | | - var input_name = "input_" + instance_num + "_" + field_num; |
193 | | - var info_name = "info_" + instance_num + "_" + field_num; |
194 | | - if (! validate_field_type(input_name, type, info_name)) { |
195 | | - num_errors += 1; |
196 | | - } |
197 | | - } |
198 | | - } |
199 | | - return (num_errors == 0); |
200 | | -} |
201 | | - |
202 | | - |
203 | | -function validate_all() { |
204 | | - var num_errors = 0; |
205 | | - |
206 | | -END; |
207 | | - foreach ( $sfgJSValidationCalls as $function_call ) { |
208 | | - $javascript_text .= " if (! $function_call) num_errors += 1;\n"; |
209 | | - } |
210 | | - $javascript_text .= <<<END |
211 | | - if (num_errors > 0) { |
212 | | - // add error header, if it's not there already |
213 | | - if (! document.getElementById("form_error_header")) { |
214 | | - var errorMsg = document.createElement('div'); |
215 | | - errorMsg.innerHTML = "<div id=\"form_error_header\" class=\"warningMessage\" style=\"font-size: medium\">$form_errors_header</div>"; |
216 | | - document.getElementById("contentSub").appendChild(errorMsg); |
217 | | - } |
218 | | - scroll(0, 0); |
219 | | - } |
220 | | - return (num_errors == 0); |
221 | | -} |
222 | | - |
223 | | -END; |
224 | | - return $javascript_text; |
225 | | - } |
226 | | - |
227 | | - static function instancesJavascript() { |
228 | | - $remove_text = wfMsg( 'sf_formedit_remove' ); |
229 | | - $javascript_text = <<<END |
230 | | - |
231 | | -var num_elements = 0; |
232 | | - |
233 | | -function addInstance(starter_div_id, main_div_id, tab_index) |
234 | | -{ |
235 | | - var starter_div = document.getElementById(starter_div_id); |
236 | | - var main_div = document.getElementById(main_div_id); |
237 | | - num_elements++; |
238 | | - |
239 | | - //Create the new instance |
240 | | - var new_div = starter_div.cloneNode(true); |
241 | | - var div_id = 'div_gen_' + num_elements; |
242 | | - new_div.className = 'multipleTemplate'; |
243 | | - new_div.id = div_id; |
244 | | - new_div.style.display = 'block'; |
245 | | - |
246 | | - // make internal ID unique for the relevant divs and spans, and replace |
247 | | - // the [num] index in the element names with an actual unique index |
248 | | - var children = new_div.getElementsByTagName('*'); |
249 | | - // this array is needed to counteract an IE bug |
250 | | - var orig_children = starter_div.getElementsByTagName('*'); |
251 | | - var fancybox_ids = new Array(); |
252 | | - var x; |
253 | | - for (x = 0; x < children.length; x++) { |
254 | | - if (children[x].name) |
255 | | - children[x].name = children[x].name.replace(/\[num\]/g, '[' + num_elements + ']'); |
256 | | - if (children[x].id) |
257 | | - children[x].id = children[x].id |
258 | | - .replace(/input_/g, 'input_' + num_elements + '_') |
259 | | - .replace(/info_/g, 'info_' + num_elements + '_') |
260 | | - .replace(/div_/g, 'div_' + num_elements + '_'); |
261 | | - if (children[x].href) |
262 | | - children[x].href = children[x].href |
263 | | - .replace(/input_/g, 'input_' + num_elements + '_'); |
264 | | - if (children[x].id.match("^fancybox")) { |
265 | | - fancybox_ids.push(children[x].id); |
266 | | - } |
267 | | - |
268 | | - // for dropdowns, copy over selectedIndex from original div, |
269 | | - // to get around a bug in IE |
270 | | - if (children[x].type == 'select-one') { |
271 | | - children[x].selectedIndex = orig_children[x].selectedIndex; |
272 | | - } |
273 | | - } |
274 | | - if (children[x]) { |
275 | | - //We clone the last object |
276 | | - if (children[x].href) { |
277 | | - children[x].href = children[x].href |
278 | | - .replace(/input_/g, 'input_' + num_elements + '_') |
279 | | - .replace(/info_/g, 'info_' + num_elements + '_') |
280 | | - .replace(/div_/g, 'div_' + num_elements + '_'); |
281 | | - } |
282 | | - } |
283 | | - // Since we clone the first object and we have uploadable field |
284 | | - // we must replace the input_ in order to let the printer return |
285 | | - // the value into the right field |
286 | | - //Create remove button |
287 | | - var remove_button = document.createElement('input'); |
288 | | - remove_button.type = 'button'; |
289 | | - remove_button.value = "$remove_text"; |
290 | | - remove_button.tabIndex = tab_index; |
291 | | - remove_button.onclick = removeInstanceEventHandler(div_id); |
292 | | - new_div.appendChild(remove_button); |
293 | | - |
294 | | - //Add the new instance |
295 | | - main_div.appendChild(new_div); |
296 | | - attachAutocompleteToAllFields(new_div); |
297 | | - |
298 | | - // For each 'upload file' link in this latest instance, |
299 | | - // add a call to fancybox() |
300 | | - for (x = 0; x < fancybox_ids.length; x++) { |
301 | | - jQuery("#" + fancybox_ids[x]).fancybox({ |
302 | | - 'width' : '75%', |
303 | | - 'height' : '75%', |
304 | | - 'autoScale' : false, |
305 | | - 'transitionIn' : 'none', |
306 | | - 'transitionOut' : 'none', |
307 | | - 'type' : 'iframe', |
308 | | - 'overlayColor' : '#222', |
309 | | - 'overlayOpacity' : '0.8' |
310 | | - }); |
311 | | - } |
312 | | -} |
313 | | - |
314 | | -function removeInstanceEventHandler(this_div_id) |
315 | | -{ |
316 | | - return function() { |
317 | | - removeInstance(this_div_id); |
318 | | - }; |
319 | | -} |
320 | | - |
321 | | -function removeInstance(div_id) { |
322 | | - var olddiv = document.getElementById(div_id); |
323 | | - var parent = olddiv.parentNode; |
324 | | - parent.removeChild(olddiv); |
325 | | -} |
326 | | - |
327 | | -END; |
328 | | - return $javascript_text; |
329 | | - } |
330 | | - |
331 | | - static function autocompletionJavascript() { |
332 | | - global $wgScriptPath, $wgOut, $smwgScriptPath, $smwgJQueryIncluded; |
333 | | - global $sfgAutocompleteOnAllChars; |
334 | | - |
335 | | - if ( !$smwgJQueryIncluded ) { |
336 | | - $wgOut->addScriptFile( "$smwgScriptPath/libs/jquery-1.4.2.min.js" ); |
337 | | - $smwgJQueryIncluded = true; |
338 | | - } |
339 | | - |
340 | | - // set a Javascript variable so that the matcher knows |
341 | | - // whether to match on characters anywhere within each string, |
342 | | - // or just (as is the default) the beginning of each word |
343 | | - if ( $sfgAutocompleteOnAllChars ) { |
344 | | - $autocompleteOnAllCharsStr = 'true'; |
345 | | - } else { |
346 | | - $autocompleteOnAllCharsStr = 'false'; |
347 | | - } |
348 | | - |
349 | | - $javascript_text = <<<END |
350 | | -var autocompleteOnAllChars = $autocompleteOnAllCharsStr; |
351 | | -var autocompletemappings = new Array(); |
352 | | -var autocompletestrings = new Array(); |
353 | | -var autocompletedatatypes = new Array(); |
354 | | - |
355 | | -//Activate autocomplete functionality for every field on the document |
356 | | -function attachAutocompleteToAllDocumentFields() |
357 | | -{ |
358 | | - var forms = document.getElementsByTagName("form"); |
359 | | - var x; |
360 | | - for (x = 0; x < forms.length; x++) { |
361 | | - if (forms[x].name == "createbox") { |
362 | | - attachAutocompleteToAllFields(forms[x]); |
363 | | - } |
364 | | - } |
365 | | -} |
366 | | - |
367 | | -//Activate autocomplete functionality for every field under the specified element |
368 | | -function attachAutocompleteToAllFields(base) |
369 | | -{ |
370 | | - var inputs = base.getElementsByTagName("input"); |
371 | | - var y; |
372 | | - for (y = 0; y < inputs.length; y++) { |
373 | | - attachAutocompleteToField(inputs[y].id); |
374 | | - } |
375 | | - // don't forget the textareas |
376 | | - inputs = base.getElementsByTagName("textarea"); |
377 | | - for (y = 0; y < inputs.length; y++) { |
378 | | - attachAutocompleteToField(inputs[y].id); |
379 | | - } |
380 | | -} |
381 | | - |
382 | | -//Activate autocomplete functionality for the specified field |
383 | | -function attachAutocompleteToField(input_id) |
384 | | -{ |
385 | | - //Check input id for the proper format, to ensure this is for SF |
386 | | - if (input_id.substr(0,6) == 'input_') |
387 | | - { |
388 | | - //Extract the field ID number from the input field |
389 | | - var field_num = parseInt(input_id.substring(input_id.lastIndexOf('_') + 1, input_id.length),10); |
390 | | - //Add the autocomplete string, if a mapping exists. |
391 | | - var field_string = autocompletemappings[field_num]; |
392 | | - if (field_string) { |
393 | | - var div_id = input_id.replace(/input_/g, 'div_'); |
394 | | - var field_values = new Array(); |
395 | | - field_values = field_string.split(','); |
396 | | - var delimiter = null; |
397 | | - var data_source = field_values[0]; |
398 | | - if (field_values[1] == 'list') { |
399 | | - delimiter = ","; |
400 | | - if (field_values[2] != null) { |
401 | | - delimiter = field_values[2]; |
402 | | - } |
403 | | - } |
404 | | - if (autocompletestrings[field_string] != null) { |
405 | | - sf_autocomplete(input_id, div_id, autocompletestrings[field_string], null, null, delimiter, data_source); |
406 | | - } else { |
407 | | - sf_autocomplete(input_id, div_id, null, "{$wgScriptPath}/api.php", autocompletedatatypes[field_string], delimiter, data_source); |
408 | | - } |
409 | | - } |
410 | | - } |
411 | | -} |
412 | | - |
413 | | -jQuery.event.add(window, "load", attachAutocompleteToAllDocumentFields); |
414 | | - |
415 | | -END; |
416 | | - return $javascript_text; |
417 | | - } |
418 | | - |
419 | 40 | static function hiddenFieldHTML( $input_name, $cur_value ) { |
420 | 41 | $input = self::buttonHTML( array( |
421 | 42 | 'type' => 'hidden', |
— | — | @@ -456,21 +77,6 @@ |
457 | 78 | return $additional_template_text; |
458 | 79 | } |
459 | 80 | |
460 | | - /** |
461 | | - * Helper function, for versions of MediaWiki that don't have |
462 | | - * Xml::expandAttributes (i.e., before 1.13) |
463 | | - */ |
464 | | - static function expandAttributes( $attribs ) { |
465 | | - if ( method_exists( 'Xml', 'expandAttributes' ) ) { |
466 | | - return Xml::expandAttributes( $attribs ); |
467 | | - } else { |
468 | | - $out = ''; |
469 | | - foreach ( $attribs as $name => $val ) |
470 | | - $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"'; |
471 | | - return $out; |
472 | | - } |
473 | | - } |
474 | | - |
475 | 81 | static function summaryInputHTML( $is_disabled, $label = null, $attr = array() ) { |
476 | 82 | global $sfgTabIndex; |
477 | 83 | |
— | — | @@ -478,7 +84,7 @@ |
479 | 85 | if ( $label == null ) |
480 | 86 | $label = wfMsg( 'summary' ); |
481 | 87 | $disabled_text = ( $is_disabled ) ? " disabled" : ""; |
482 | | - $attr = self::expandAttributes( $attr ); |
| 88 | + $attr = Xml::expandAttributes( $attr ); |
483 | 89 | $text = <<<END |
484 | 90 | <span id='wpSummaryLabel'><label for='wpSummary'>$label</label></span> |
485 | 91 | <input tabindex="$sfgTabIndex" type='text' value="" name='wpSummary' id='wpSummary' maxlength='200' size='60'$disabled_text$attr/> |
— | — | @@ -496,7 +102,7 @@ |
497 | 103 | $label = wfMsgExt( 'minoredit', array( 'parseinline' ) ); |
498 | 104 | $accesskey = wfMsg( 'accesskey-minoredit' ); |
499 | 105 | $tooltip = wfMsg( 'tooltip-minoredit' ); |
500 | | - $attr = self::expandAttributes( $attr ); |
| 106 | + $attr = Xml::expandAttributes( $attr ); |
501 | 107 | $text = <<<END |
502 | 108 | <input tabindex="$sfgTabIndex" type="checkbox" value="" name="wpMinoredit" accesskey="$accesskey" id="wpMinoredit"$disabled_text$attr/> |
503 | 109 | <label for="wpMinoredit" title="$tooltip">$label</label> |
— | — | @@ -527,7 +133,7 @@ |
528 | 134 | $label = wfMsgExt( 'watchthis', array( 'parseinline' ) ); |
529 | 135 | $accesskey = htmlspecialchars( wfMsg( 'accesskey-watch' ) ); |
530 | 136 | $tooltip = htmlspecialchars( wfMsg( 'tooltip-watch' ) ); |
531 | | - $attr = self::expandAttributes( $attr ); |
| 137 | + $attr = Xml::expandAttributes( $attr ); |
532 | 138 | $text = <<<END |
533 | 139 | <input tabindex="$sfgTabIndex" type="checkbox" name="wpWatchthis" accesskey="$accesskey" id='wpWatchthis'$checked_text$disabled_text$attr/> |
534 | 140 | <label for="wpWatchthis" title="$tooltip">$label</label> |