Index: trunk/extensions/EtherpadLite/EtherpadLite.php |
— | — | @@ -64,7 +64,7 @@ |
65 | 65 | 'path' => __FILE__, |
66 | 66 | 'name' => 'EtherpadLite', |
67 | 67 | 'author' => array( 'Thomas Gries' ), |
68 | | - 'version' => '1.09 20120218', |
| 68 | + 'version' => '1.10 20120219', |
69 | 69 | 'url' => 'https://www.mediawiki.org/wiki/Extension:EtherpadLite', |
70 | 70 | 'descriptionmsg' => 'etherpadlite-desc', |
71 | 71 | ); |
— | — | @@ -72,192 +72,212 @@ |
73 | 73 | $dir = dirname( __FILE__ ) . '/'; |
74 | 74 | |
75 | 75 | $wgExtensionMessagesFiles['EtherpadLite'] = $dir . 'EtherpadLite.i18n.php'; |
76 | | -$wgHooks['ParserFirstCallInit'][] = 'wfEtherpadLiteParserInit'; |
| 76 | +$wgHooks['ParserFirstCallInit'][] = 'EtherpadLite::EtherpadLiteParserInit'; |
77 | 77 | |
78 | | -# Define a default Etherpad Lite server Url and base path |
79 | | -# this server is used unless a distinct server is defined by id="..." |
80 | | -$wgEtherpadLiteDefaultPadUrl = "http://beta.etherpad.org/p/"; |
81 | | -$wgEtherpadLiteDefaultWidth = "300px"; |
82 | | -$wgEtherpadLiteDefaultHeight = "200px"; |
83 | | -$wgEtherpadLiteMonospacedFont = false; |
84 | | -$wgEtherpadLiteShowControls = true; |
85 | | -$wgEtherpadLiteShowLineNumbers = true; |
86 | | -$wgEtherpadLiteShowChat = true; |
87 | | -$wgEtherpadLiteShowAuthorColors = true; |
| 78 | +class EtherpadLite { |
88 | 79 | |
89 | | -# Whitelist of allowed Etherpad Lite server Urls |
90 | | -# |
91 | | -# If there are items in the array, and the user supplied URL is not in the array, |
92 | | -# the url will not be allowed (proposed in bug 27768 for Extension:RSS) |
93 | | -# Attention: |
94 | | -# Urls are case-sensitively tested against values in the array. |
95 | | -# They must exactly match including any trailing "/" character. |
96 | | -# |
97 | | -# Warning: Allowing all urls (not setting a whitelist) |
98 | | -# may be a security concern. |
99 | | -# |
100 | | -# an empty or non-existent array means: no whitelist defined |
101 | | -# this is the default: an empty whitelist. No servers are allowed by default. |
102 | | -$wgEtherpadLiteUrlWhitelist = array(); |
103 | | -# |
104 | | -# include "*" if you expressly want to allow all urls (you should not do this) |
105 | | -# $wgEtherpadLiteUrlWhitelist = array( "*" ); |
| 80 | + # Define a default Etherpad Lite server Url and base path |
| 81 | + # this server is used unless a distinct server is defined by id="..." |
| 82 | + public $wgEtherpadLiteDefaultPadUrl = "http://beta.etherpad.org/p/"; |
106 | 83 | |
107 | | -# https://www.mediawiki.org/wiki/Manual:Tag_extensions |
108 | | -function wfEtherpadLiteParserInit( $parser ) { |
109 | | - global $wgEtherpadLitePadsOnThisPage; |
110 | | - $wgEtherpadLitePadsOnThisPage = array(); |
111 | | - $parser->setHook('eplite', 'wfEtherpadLiteRender'); |
112 | | - return true; |
113 | | -} |
| 84 | + public $wgEtherpadLiteDefaultWidth = "300px"; |
| 85 | + public $wgEtherpadLiteDefaultHeight = "200px"; |
| 86 | + public $wgEtherpadLiteMonospacedFont = false; |
| 87 | + public $wgEtherpadLiteShowControls = true; |
| 88 | + public $wgEtherpadLiteShowLineNumbers = true; |
| 89 | + public $wgEtherpadLiteShowChat = true; |
| 90 | + public $wgEtherpadLiteShowAuthorColors = true; |
114 | 91 | |
115 | | -function wfEtherpadLiteRender( $input, $args, $parser, $frame ) { |
| 92 | + # Whitelist of allowed Etherpad Lite server Urls |
| 93 | + # |
| 94 | + # If there are items in the array, and the user supplied URL is not in the array, |
| 95 | + # the url will not be allowed (proposed in bug 27768 for Extension:RSS) |
| 96 | + # Attention: |
| 97 | + # Urls are case-sensitively tested against values in the array. |
| 98 | + # They must exactly match including any trailing "/" character. |
| 99 | + # |
| 100 | + # Warning: Allowing all urls (not setting a whitelist) |
| 101 | + # may be a security concern. |
| 102 | + # |
| 103 | + # an empty or non-existent array means: no whitelist defined |
| 104 | + # this is the default: an empty whitelist. No servers are allowed by default. |
116 | 105 | |
117 | | - global $wgUser; |
118 | | - global $wgEtherpadLiteDefaultPadUrl, $wgEtherpadLiteDefaultWidth, $wgEtherpadLiteDefaultHeight, |
119 | | - $wgEtherpadLiteMonospacedFont, $wgEtherpadLiteShowControls, $wgEtherpadLiteShowLineNumbers, |
120 | | - $wgEtherpadLiteShowChat, $wgEtherpadLiteShowAuthorColors, $wgEtherpadLiteUrlWhitelist, |
121 | | - $wgEtherpadLitePadsOnThisPage; |
| 106 | + public $wgEtherpadLiteUrlWhitelist = array(); |
| 107 | + |
| 108 | + # include "*" if you expressly want to allow all urls (you should not do this) |
| 109 | + # $wgEtherpadLiteUrlWhitelist = array( "*" ); |
122 | 110 | |
123 | | - # check the user input |
| 111 | + /** |
| 112 | + * Tell the parser how to handle <eplite> elements |
| 113 | + * https://www.mediawiki.org/wiki/Manual:Tag_extensions |
| 114 | + * @param $parser Parser Object |
| 115 | + */ |
| 116 | + static function EtherpadLiteParserInit( $parser ) { |
124 | 117 | |
125 | | - # undefined id= attributes are replaced by id="" and result |
126 | | - # in Etherpad Lite server showing its entry page - where you can open a new pad. |
127 | | - $args['id'] = ( isset( $args['id'] ) ) ? $args['id'] : ""; |
| 118 | + global $wgEtherpadLitePadsOnThisPage; |
128 | 119 | |
129 | | - $args['height'] = ( isset( $args['height'] ) ) ? $args['height'] : $wgEtherpadLiteDefaultHeight; |
130 | | - $args['width'] = ( isset( $args['width'] ) ) ? $args['width'] : $wgEtherpadLiteDefaultWidth; |
| 120 | + $wgEtherpadLitePadsOnThisPage = array(); |
| 121 | + $parser->setHook( 'eplite', array( __CLASS__, 'EtherpadLiteRender' ) ); |
| 122 | + |
| 123 | + return true; |
| 124 | + |
| 125 | + } |
| 126 | + |
| 127 | + static function EtherpadLiteRender( $input, $args, $parser, $frame ) { |
| 128 | + |
| 129 | + global $wgUser; |
| 130 | + global $wgEtherpadLiteDefaultPadUrl, $wgEtherpadLiteDefaultWidth, $wgEtherpadLiteDefaultHeight, |
| 131 | + $wgEtherpadLiteMonospacedFont, $wgEtherpadLiteShowControls, $wgEtherpadLiteShowLineNumbers, |
| 132 | + $wgEtherpadLiteShowChat, $wgEtherpadLiteShowAuthorColors, $wgEtherpadLiteUrlWhitelist, |
| 133 | + $wgEtherpadLitePadsOnThisPage; |
| 134 | + |
| 135 | + # check the user input |
| 136 | + |
| 137 | + # undefined id= attributes are replaced by id="" and result |
| 138 | + # in Etherpad Lite server showing its entry page - where you can open a new pad. |
| 139 | + $args['id'] = ( isset( $args['id'] ) ) ? $args['id'] : ""; |
| 140 | + |
| 141 | + $args['height'] = ( isset( $args['height'] ) ) ? $args['height'] : $wgEtherpadLiteDefaultHeight; |
| 142 | + $args['width'] = ( isset( $args['width'] ) ) ? $args['width'] : $wgEtherpadLiteDefaultWidth; |
131 | 143 | |
132 | | - $useMonospaceFont = wfBoolToStr( |
133 | | - ( ( isset( $args['monospaced-font'] ) ) ? filter_var( $args['monospaced-font'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteMonospacedFont ) |
134 | | - ); |
| 144 | + $useMonospaceFont = wfBoolToStr( |
| 145 | + ( ( isset( $args['monospaced-font'] ) ) ? filter_var( $args['monospaced-font'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteMonospacedFont ) |
| 146 | + ); |
135 | 147 | |
136 | | - $showControls = wfBoolToStr( |
137 | | - ( ( isset( $args['show-controls'] ) ) ? filter_var( $args['show-controls'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowControls ) |
138 | | - ); |
| 148 | + $showControls = wfBoolToStr( |
| 149 | + ( ( isset( $args['show-controls'] ) ) ? filter_var( $args['show-controls'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowControls ) |
| 150 | + ); |
139 | 151 | |
140 | | - $showLineNumbers = wfBoolToStr( |
141 | | - ( ( isset( $args['show-linenumbers'] ) ) ? filter_var( $args['show-linenumbers'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowLineNumbers ) |
142 | | - ); |
| 152 | + $showLineNumbers = wfBoolToStr( |
| 153 | + ( ( isset( $args['show-linenumbers'] ) ) ? filter_var( $args['show-linenumbers'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowLineNumbers ) |
| 154 | + ); |
143 | 155 | |
144 | | - $showChat = wfBoolToStr( |
145 | | - ( ( isset( $args['show-chat'] ) ) ? filter_var( $args['show-chat'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowChat ) |
146 | | - ); |
| 156 | + $showChat = wfBoolToStr( |
| 157 | + ( ( isset( $args['show-chat'] ) ) ? filter_var( $args['show-chat'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowChat ) |
| 158 | + ); |
147 | 159 | |
148 | | - $noColors = wfBoolToStr( |
149 | | - ! ( ( isset( $args['show-colors'] ) ) ? filter_var( $args['show-colors'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowAuthorColors ) |
150 | | - ); |
| 160 | + $noColors = wfBoolToStr( |
| 161 | + ! ( ( isset( $args['show-colors'] ) ) ? filter_var( $args['show-colors'], FILTER_VALIDATE_BOOLEAN ) : $wgEtherpadLiteShowAuthorColors ) |
| 162 | + ); |
151 | 163 | |
152 | | - # src= is the pad server base url and is user input in <eplite src= > tag from MediaWiki page |
153 | | - # id= is the pad name (also known as pad id) and is user input in <eplite id= > tag from MediaWiki page |
| 164 | + # src= is the pad server base url and is user input in <eplite src= > tag from MediaWiki page |
| 165 | + # id= is the pad name (also known as pad id) and is user input in <eplite id= > tag from MediaWiki page |
154 | 166 | |
155 | | - $src = ( isset( $args['src'] ) ) ? $args['src'] : $wgEtherpadLiteDefaultPadUrl; |
156 | | - # Sanitizer::cleanUrl just does some normalization, somewhat not needed. |
157 | | - $src = Sanitizer::cleanUrl( $src ); |
| 167 | + $src = ( isset( $args['src'] ) ) ? $args['src'] : $wgEtherpadLiteDefaultPadUrl; |
| 168 | + # Sanitizer::cleanUrl just does some normalization, somewhat not needed. |
| 169 | + $src = Sanitizer::cleanUrl( $src ); |
158 | 170 | |
159 | | - switch ( true ) { |
| 171 | + switch ( true ) { |
160 | 172 | |
161 | | - # disallow because there is no whitelist or emtpy whitelist |
162 | | - case ( !isset( $wgEtherpadLiteUrlWhitelist ) |
163 | | - || !is_array( $wgEtherpadLiteUrlWhitelist ) |
164 | | - || ( count( $wgEtherpadLiteUrlWhitelist ) === 0 ) ): |
165 | | - return wfEtherpadLiteError( 'etherpadlite-empty-whitelist', |
166 | | - $src |
167 | | - ); |
168 | | - break; |
| 173 | + # disallow because there is no whitelist or emtpy whitelist |
| 174 | + case ( !isset( $wgEtherpadLiteUrlWhitelist ) |
| 175 | + || !is_array( $wgEtherpadLiteUrlWhitelist ) |
| 176 | + || ( count( $wgEtherpadLiteUrlWhitelist ) === 0 ) ): |
| 177 | + return EtherpadLite::EtherpadLiteError( 'etherpadlite-empty-whitelist', |
| 178 | + $src |
| 179 | + ); |
| 180 | + break; |
169 | 181 | |
170 | | - # allow |
171 | | - case ( in_array( "*", $wgEtherpadLiteUrlWhitelist ) ): |
172 | | - case ( in_array( $src, $wgEtherpadLiteUrlWhitelist ) ): |
173 | | - break; |
| 182 | + # allow |
| 183 | + case ( in_array( "*", $wgEtherpadLiteUrlWhitelist ) ): |
| 184 | + case ( in_array( $src, $wgEtherpadLiteUrlWhitelist ) ): |
| 185 | + break; |
174 | 186 | |
175 | | - # otherwise disallow |
176 | | - case ( !in_array( $src, $wgEtherpadLiteUrlWhitelist ) ): |
177 | | - default: |
178 | | - $listOfAllowed = $parser->getFunctionLang()->listToText( $wgEtherpadLiteUrlWhitelist ); |
179 | | - $numberAllowed = $parser->getFunctionLang()->formatNum( count( $wgEtherpadLiteUrlWhitelist ) ); |
180 | | - return wfEtherpadLiteError( 'etherpadlite-url-is-not-whitelisted', |
181 | | - array( $src, $listOfAllowed, $numberAllowed ) |
182 | | - ); |
183 | | - } |
| 187 | + # otherwise disallow |
| 188 | + case ( !in_array( $src, $wgEtherpadLiteUrlWhitelist ) ): |
| 189 | + default: |
| 190 | + $listOfAllowed = $parser->getFunctionLang()->listToText( $wgEtherpadLiteUrlWhitelist ); |
| 191 | + $numberAllowed = $parser->getFunctionLang()->formatNum( count( $wgEtherpadLiteUrlWhitelist ) ); |
| 192 | + return EtherpadLite::EtherpadLiteError( 'etherpadlite-url-is-not-whitelisted', |
| 193 | + array( $src, $listOfAllowed, $numberAllowed ) |
| 194 | + ); |
| 195 | + } |
184 | 196 | |
185 | | - # Append the id to end of url. Strip off trailing / if present before appending one. |
186 | | - $url = preg_replace( "/\/+$/", "", $src ) . "/" . $args['id']; |
| 197 | + # Append the id to end of url. Strip off trailing / if present before appending one. |
| 198 | + $url = preg_replace( "/\/+$/", "", $src ) . "/" . $args['id']; |
187 | 199 | |
188 | | - # prevent multiple iframes and rendering of a same pad on a page |
189 | | - # show an error message if a pad is found more than once on a page. |
190 | | - # |
191 | | - # the empty id however may be used more than once as the empty id invokes an |
192 | | - # Etherpad Lite server showing its "create a pad" html page. |
| 200 | + # prevent multiple iframes and rendering of a same pad on a page |
| 201 | + # show an error message if a pad is found more than once on a page. |
| 202 | + # |
| 203 | + # the empty id however may be used more than once as the empty id invokes an |
| 204 | + # Etherpad Lite server showing its "create a pad" html page. |
193 | 205 | |
194 | | - if ( !in_array( $url, $wgEtherpadLitePadsOnThisPage ) ) { |
195 | | - $wgEtherpadLitePadsOnThisPage[] = $url; |
196 | | - } elseif ( $args['id'] !== "" ) { |
197 | | - return wfEtherpadLiteError( 'etherpadlite-pad-used-more-than-once', $url ); |
198 | | - } |
| 206 | + if ( !in_array( $url, $wgEtherpadLitePadsOnThisPage ) ) { |
| 207 | + $wgEtherpadLitePadsOnThisPage[] = $url; |
| 208 | + } elseif ( $args['id'] !== "" ) { |
| 209 | + return EtherpadLite::EtherpadLiteError( 'etherpadlite-pad-used-more-than-once', $url ); |
| 210 | + } |
199 | 211 | |
200 | 212 | |
201 | | - # preset the pad username from MediaWiki username or IP |
202 | | - # this not strict, as the pad username can be overwritten in the pad |
203 | | - # |
204 | | - # attention: |
205 | | - # 1. we must render the page for each visiting user to get their username |
206 | | - # 2. the pad username can currently be overwritten when editing the pad |
207 | | - # |
208 | | - # Future todo might be to make the adding of username optional |
209 | | - # since disabling of cache has a significant performance impact |
210 | | - # on larger sites. |
| 213 | + # preset the pad username from MediaWiki username or IP |
| 214 | + # this not strict, as the pad username can be overwritten in the pad |
| 215 | + # |
| 216 | + # attention: |
| 217 | + # 1. we must render the page for each visiting user to get their username |
| 218 | + # 2. the pad username can currently be overwritten when editing the pad |
| 219 | + # |
| 220 | + # Future todo might be to make the adding of username optional |
| 221 | + # since disabling of cache has a significant performance impact |
| 222 | + # on larger sites. |
211 | 223 | |
212 | | - $parser->disableCache(); |
| 224 | + $parser->disableCache(); |
213 | 225 | |
214 | | - # Etherpad Lite requires rawurlencoded userName, thus we must add it manually |
| 226 | + # Etherpad Lite requires rawurlencoded userName, thus we must add it manually |
215 | 227 | |
216 | | - $url = wfAppendQuery( $url, array( |
217 | | - "showControls" => $showControls, |
218 | | - "showChat" => $showChat, |
219 | | - "showLineNumbers" => $showLineNumbers, |
220 | | - "useMonospaceFont" => $useMonospaceFont, |
221 | | - "noColors" => $noColors, |
222 | | - ) |
223 | | - ) . "&userName=" . rawurlencode( $wgUser->getName() ); |
| 228 | + $url = wfAppendQuery( $url, array( |
| 229 | + "showControls" => $showControls, |
| 230 | + "showChat" => $showChat, |
| 231 | + "showLineNumbers" => $showLineNumbers, |
| 232 | + "useMonospaceFont" => $useMonospaceFont, |
| 233 | + "noColors" => $noColors, |
| 234 | + ) |
| 235 | + ) . "&userName=" . rawurlencode( $wgUser->getName() ); |
224 | 236 | |
225 | | - # @todo One could potentially stuff other css in the width argument |
226 | | - # since ; isn't checked for. Since overall css is checked for allowed |
227 | | - # rules, this isn't super big deal. |
228 | | - $iframeAttributes = array( |
229 | | - "style" => "width:" . $args['width'] . ";" . |
230 | | - "height:" . $args['height'], |
231 | | - "class" => "eplite-iframe-" . $args['id'] , |
232 | | - "src" => Sanitizer::cleanUrl( $url ), |
233 | | - ); |
| 237 | + # @todo One could potentially stuff other css in the width argument |
| 238 | + # since ; isn't checked for. Since overall css is checked for allowed |
| 239 | + # rules, this isn't super big deal. |
| 240 | + $iframeAttributes = array( |
| 241 | + "style" => "width:" . $args['width'] . ";" . |
| 242 | + "height:" . $args['height'], |
| 243 | + "class" => "eplite-iframe-" . $args['id'] , |
| 244 | + "src" => Sanitizer::cleanUrl( $url ), |
| 245 | + ); |
234 | 246 | |
235 | | - $sanitizedAttributes = Sanitizer::validateAttributes( $iframeAttributes, array ( "style", "class", "src" ) ); |
| 247 | + $sanitizedAttributes = Sanitizer::validateAttributes( $iframeAttributes, array ( "style", "class", "src" ) ); |
236 | 248 | |
237 | | - if ( !isset( $sanitizedAttributes['src'] ) ) { |
238 | | - // The Sanitizer decided that the src attribute was no good. |
239 | | - // (aka used a protocol that isn't in the whitelist) |
240 | | - return wfEtherpadLiteError( 'etherpadlite-invalid-pad-url', $src ); |
| 249 | + if ( !isset( $sanitizedAttributes['src'] ) ) { |
| 250 | + // The Sanitizer decided that the src attribute was no good. |
| 251 | + // (aka used a protocol that isn't in the whitelist) |
| 252 | + return EtherpadLite::EtherpadLiteError( 'etherpadlite-invalid-pad-url', $src ); |
| 253 | + } |
| 254 | + |
| 255 | + $output = Html::rawElement( |
| 256 | + 'iframe', |
| 257 | + $sanitizedAttributes |
| 258 | + ); |
| 259 | + |
| 260 | + wfDebug( "EtherpadLite::EtherpadLiteRender $output\n" ); |
| 261 | + |
| 262 | + return $output; |
| 263 | + |
241 | 264 | } |
242 | 265 | |
243 | | - $output = Html::rawElement( |
244 | | - 'iframe', |
245 | | - $sanitizedAttributes |
246 | | - ); |
| 266 | + /** |
| 267 | + * Output an error message, all wraped up nicely. |
| 268 | + * @param String $errorName The system message that this error is |
| 269 | + * @param String|Array $param Error parameter (or parameters) |
| 270 | + * @return String Html that is the error. |
| 271 | + */ |
| 272 | + private static function EtherpadLiteError( $errorName, $param ) { |
247 | 273 | |
248 | | - wfDebug( "EtherpadLite:wfEtherpadLiteRender $output\n" ); |
249 | | - return $output; |
250 | | -} |
251 | | -/** |
252 | | - * Output an error message, all wraped up nicely. |
253 | | - * @param String $errorName The system message that this error is |
254 | | - * @param String|Array $param Error parameter (or parameters) |
255 | | - * @return String Html that is the error. |
256 | | - */ |
257 | | -function wfEtherpadLiteError( $errorName, $param ) { |
258 | | - // Anything from a parser tag should use Content lang for message, |
259 | | - // since the cache doesn't vary by user language: do not use wfMsgForContent but wfMsgForContent |
260 | | - // The ->parse() part makes everything safe from an escaping standpoint. |
261 | | - return Html::rawElement( 'span', array( 'class' => 'error' ), |
262 | | - wfMessage( $errorName )->inContentLanguage()->params( $param )->parse() |
263 | | - ); |
264 | | -} |
| 274 | + // Anything from a parser tag should use Content lang for message, |
| 275 | + // since the cache doesn't vary by user language: do not use wfMsgForContent but wfMsgForContent |
| 276 | + // The ->parse() part makes everything safe from an escaping standpoint. |
| 277 | + |
| 278 | + return Html::rawElement( 'span', array( 'class' => 'error' ), |
| 279 | + wfMessage( $errorName )->inContentLanguage()->params( $param )->parse() |
| 280 | + ); |
| 281 | + |
| 282 | + } |
| 283 | + |
| 284 | +} /* class EtherpadLite */ |
Index: trunk/extensions/EtherpadLite/EtherpadLite.i18n.php |
— | — | @@ -16,7 +16,7 @@ |
17 | 17 | 'etherpadlite-invalid-pad-url' => '"$1" is not a valid Etherpad Lite URL or pad name.', |
18 | 18 | 'etherpadlite-url-is-not-whitelisted' => '"$1" is not in the whitelist of allowed Etherpad Lite servers. {{PLURAL:$3|$2 is the only allowed server|The allowed servers are as follows: $2}}.', |
19 | 19 | 'etherpadlite-empty-whitelist' => '"$1" is not in the whitelist of allowed Etherpad Lite servers. There are no allowed servers in the whitelist.', |
20 | | - 'etherpadlite-pad-used-more-than-once' => 'The pad "$1" has already been used before on this page; you can have many pads on a page, but only if they are different pads.', |
| 20 | + 'etherpadlite-pad-used-more-than-once' => 'The pad "$1" has already been used before on this page; you can have many pads on a page, but only if they are different pads.', |
21 | 21 | ); |
22 | 22 | |
23 | 23 | /** Message documentation (Message documentation) */ |