WordPress 4.1.4-scripts
[autoinstalls/wordpress.git] / wp-includes / kses.php
1 <?php
2 /**
3  * kses 0.2.2 - HTML/XHTML filter that only allows some elements and attributes
4  * Copyright (C) 2002, 2003, 2005  Ulf Harnhammar
5  *
6  * This program is free software and open source software; you can redistribute
7  * it and/or modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  * http://www.gnu.org/licenses/gpl.html
20  *
21  * [kses strips evil scripts!]
22  *
23  * Added wp_ prefix to avoid conflicts with existing kses users
24  *
25  * @version 0.2.2
26  * @copyright (C) 2002, 2003, 2005
27  * @author Ulf Harnhammar <http://advogato.org/person/metaur/>
28  *
29  * @package External
30  * @subpackage KSES
31  *
32  */
33
34 /**
35  * You can override this in a plugin.
36  *
37  * The wp_kses_allowed_html filter is more powerful and supplies context.
38  * CUSTOM_TAGS is not recommended and should be considered deprecated.
39  *
40  * @see wp_kses_allowed_html()
41  *
42  * @since 1.2.0
43  */
44 if ( ! defined( 'CUSTOM_TAGS' ) )
45         define( 'CUSTOM_TAGS', false );
46
47 // Ensure that these variables are added to the global namespace
48 // (e.g. if using namespaces / autoload in the current PHP environment).
49 global $allowedposttags, $allowedtags, $allowedentitynames;
50
51 if ( ! CUSTOM_TAGS ) {
52         /**
53          * Kses global for default allowable HTML tags.
54          *
55          * Can be override by using CUSTOM_TAGS constant.
56          *
57          * @global array $allowedposttags
58          * @since 2.0.0
59          */
60         $allowedposttags = array(
61                 'address' => array(),
62                 'a' => array(
63                         'href' => true,
64                         'rel' => true,
65                         'rev' => true,
66                         'name' => true,
67                         'target' => true,
68                 ),
69                 'abbr' => array(),
70                 'acronym' => array(),
71                 'area' => array(
72                         'alt' => true,
73                         'coords' => true,
74                         'href' => true,
75                         'nohref' => true,
76                         'shape' => true,
77                         'target' => true,
78                 ),
79                 'article' => array(
80                         'align' => true,
81                         'dir' => true,
82                         'lang' => true,
83                         'xml:lang' => true,
84                 ),
85                 'aside' => array(
86                         'align' => true,
87                         'dir' => true,
88                         'lang' => true,
89                         'xml:lang' => true,
90                 ),
91                 'audio' => array(
92                         'autoplay' => true,
93                         'controls' => true,
94                         'loop' => true,
95                         'muted' => true,
96                         'preload' => true,
97                         'src' => true,
98                 ),
99                 'b' => array(),
100                 'big' => array(),
101                 'blockquote' => array(
102                         'cite' => true,
103                         'lang' => true,
104                         'xml:lang' => true,
105                 ),
106                 'br' => array(),
107                 'button' => array(
108                         'disabled' => true,
109                         'name' => true,
110                         'type' => true,
111                         'value' => true,
112                 ),
113                 'caption' => array(
114                         'align' => true,
115                 ),
116                 'cite' => array(
117                         'dir' => true,
118                         'lang' => true,
119                 ),
120                 'code' => array(),
121                 'col' => array(
122                         'align' => true,
123                         'char' => true,
124                         'charoff' => true,
125                         'span' => true,
126                         'dir' => true,
127                         'valign' => true,
128                         'width' => true,
129                 ),
130                 'colgroup' => array(
131                         'align' => true,
132                         'char' => true,
133                         'charoff' => true,
134                         'span' => true,
135                         'valign' => true,
136                         'width' => true,
137                 ),
138                 'del' => array(
139                         'datetime' => true,
140                 ),
141                 'dd' => array(),
142                 'dfn' => array(),
143                 'details' => array(
144                         'align' => true,
145                         'dir' => true,
146                         'lang' => true,
147                         'open' => true,
148                         'xml:lang' => true,
149                 ),
150                 'div' => array(
151                         'align' => true,
152                         'dir' => true,
153                         'lang' => true,
154                         'xml:lang' => true,
155                 ),
156                 'dl' => array(),
157                 'dt' => array(),
158                 'em' => array(),
159                 'fieldset' => array(),
160                 'figure' => array(
161                         'align' => true,
162                         'dir' => true,
163                         'lang' => true,
164                         'xml:lang' => true,
165                 ),
166                 'figcaption' => array(
167                         'align' => true,
168                         'dir' => true,
169                         'lang' => true,
170                         'xml:lang' => true,
171                 ),
172                 'font' => array(
173                         'color' => true,
174                         'face' => true,
175                         'size' => true,
176                 ),
177                 'footer' => array(
178                         'align' => true,
179                         'dir' => true,
180                         'lang' => true,
181                         'xml:lang' => true,
182                 ),
183                 'form' => array(
184                         'action' => true,
185                         'accept' => true,
186                         'accept-charset' => true,
187                         'enctype' => true,
188                         'method' => true,
189                         'name' => true,
190                         'target' => true,
191                 ),
192                 'h1' => array(
193                         'align' => true,
194                 ),
195                 'h2' => array(
196                         'align' => true,
197                 ),
198                 'h3' => array(
199                         'align' => true,
200                 ),
201                 'h4' => array(
202                         'align' => true,
203                 ),
204                 'h5' => array(
205                         'align' => true,
206                 ),
207                 'h6' => array(
208                         'align' => true,
209                 ),
210                 'header' => array(
211                         'align' => true,
212                         'dir' => true,
213                         'lang' => true,
214                         'xml:lang' => true,
215                 ),
216                 'hgroup' => array(
217                         'align' => true,
218                         'dir' => true,
219                         'lang' => true,
220                         'xml:lang' => true,
221                 ),
222                 'hr' => array(
223                         'align' => true,
224                         'noshade' => true,
225                         'size' => true,
226                         'width' => true,
227                 ),
228                 'i' => array(),
229                 'img' => array(
230                         'alt' => true,
231                         'align' => true,
232                         'border' => true,
233                         'height' => true,
234                         'hspace' => true,
235                         'longdesc' => true,
236                         'vspace' => true,
237                         'src' => true,
238                         'usemap' => true,
239                         'width' => true,
240                 ),
241                 'ins' => array(
242                         'datetime' => true,
243                         'cite' => true,
244                 ),
245                 'kbd' => array(),
246                 'label' => array(
247                         'for' => true,
248                 ),
249                 'legend' => array(
250                         'align' => true,
251                 ),
252                 'li' => array(
253                         'align' => true,
254                         'value' => true,
255                 ),
256                 'map' => array(
257                         'name' => true,
258                 ),
259                 'mark' => array(),
260                 'menu' => array(
261                         'type' => true,
262                 ),
263                 'nav' => array(
264                         'align' => true,
265                         'dir' => true,
266                         'lang' => true,
267                         'xml:lang' => true,
268                 ),
269                 'p' => array(
270                         'align' => true,
271                         'dir' => true,
272                         'lang' => true,
273                         'xml:lang' => true,
274                 ),
275                 'pre' => array(
276                         'width' => true,
277                 ),
278                 'q' => array(
279                         'cite' => true,
280                 ),
281                 's' => array(),
282                 'samp' => array(),
283                 'span' => array(
284                         'dir' => true,
285                         'align' => true,
286                         'lang' => true,
287                         'xml:lang' => true,
288                 ),
289                 'section' => array(
290                         'align' => true,
291                         'dir' => true,
292                         'lang' => true,
293                         'xml:lang' => true,
294                 ),
295                 'small' => array(),
296                 'strike' => array(),
297                 'strong' => array(),
298                 'sub' => array(),
299                 'summary' => array(
300                         'align' => true,
301                         'dir' => true,
302                         'lang' => true,
303                         'xml:lang' => true,
304                 ),
305                 'sup' => array(),
306                 'table' => array(
307                         'align' => true,
308                         'bgcolor' => true,
309                         'border' => true,
310                         'cellpadding' => true,
311                         'cellspacing' => true,
312                         'dir' => true,
313                         'rules' => true,
314                         'summary' => true,
315                         'width' => true,
316                 ),
317                 'tbody' => array(
318                         'align' => true,
319                         'char' => true,
320                         'charoff' => true,
321                         'valign' => true,
322                 ),
323                 'td' => array(
324                         'abbr' => true,
325                         'align' => true,
326                         'axis' => true,
327                         'bgcolor' => true,
328                         'char' => true,
329                         'charoff' => true,
330                         'colspan' => true,
331                         'dir' => true,
332                         'headers' => true,
333                         'height' => true,
334                         'nowrap' => true,
335                         'rowspan' => true,
336                         'scope' => true,
337                         'valign' => true,
338                         'width' => true,
339                 ),
340                 'textarea' => array(
341                         'cols' => true,
342                         'rows' => true,
343                         'disabled' => true,
344                         'name' => true,
345                         'readonly' => true,
346                 ),
347                 'tfoot' => array(
348                         'align' => true,
349                         'char' => true,
350                         'charoff' => true,
351                         'valign' => true,
352                 ),
353                 'th' => array(
354                         'abbr' => true,
355                         'align' => true,
356                         'axis' => true,
357                         'bgcolor' => true,
358                         'char' => true,
359                         'charoff' => true,
360                         'colspan' => true,
361                         'headers' => true,
362                         'height' => true,
363                         'nowrap' => true,
364                         'rowspan' => true,
365                         'scope' => true,
366                         'valign' => true,
367                         'width' => true,
368                 ),
369                 'thead' => array(
370                         'align' => true,
371                         'char' => true,
372                         'charoff' => true,
373                         'valign' => true,
374                 ),
375                 'title' => array(),
376                 'tr' => array(
377                         'align' => true,
378                         'bgcolor' => true,
379                         'char' => true,
380                         'charoff' => true,
381                         'valign' => true,
382                 ),
383                 'track' => array(
384                         'default' => true,
385                         'kind' => true,
386                         'label' => true,
387                         'src' => true,
388                         'srclang' => true,
389                 ),
390                 'tt' => array(),
391                 'u' => array(),
392                 'ul' => array(
393                         'type' => true,
394                 ),
395                 'ol' => array(
396                         'start' => true,
397                         'type' => true,
398                 ),
399                 'var' => array(),
400                 'video' => array(
401                         'autoplay' => true,
402                         'controls' => true,
403                         'height' => true,
404                         'loop' => true,
405                         'muted' => true,
406                         'poster' => true,
407                         'preload' => true,
408                         'src' => true,
409                         'width' => true,
410                 ),
411         );
412
413         /**
414          * Kses allowed HTML elements.
415          *
416          * @global array $allowedtags
417          * @since 1.0.0
418          */
419         $allowedtags = array(
420                 'a' => array(
421                         'href' => true,
422                         'title' => true,
423                 ),
424                 'abbr' => array(
425                         'title' => true,
426                 ),
427                 'acronym' => array(
428                         'title' => true,
429                 ),
430                 'b' => array(),
431                 'blockquote' => array(
432                         'cite' => true,
433                 ),
434                 'cite' => array(),
435                 'code' => array(),
436                 'del' => array(
437                         'datetime' => true,
438                 ),
439                 'em' => array(),
440                 'i' => array(),
441                 'q' => array(
442                         'cite' => true,
443                 ),
444                 'strike' => array(),
445                 'strong' => array(),
446         );
447
448         $allowedentitynames = array(
449                 'nbsp',    'iexcl',  'cent',    'pound',  'curren', 'yen',
450                 'brvbar',  'sect',   'uml',     'copy',   'ordf',   'laquo',
451                 'not',     'shy',    'reg',     'macr',   'deg',    'plusmn',
452                 'acute',   'micro',  'para',    'middot', 'cedil',  'ordm',
453                 'raquo',   'iquest', 'Agrave',  'Aacute', 'Acirc',  'Atilde',
454                 'Auml',    'Aring',  'AElig',   'Ccedil', 'Egrave', 'Eacute',
455                 'Ecirc',   'Euml',   'Igrave',  'Iacute', 'Icirc',  'Iuml',
456                 'ETH',     'Ntilde', 'Ograve',  'Oacute', 'Ocirc',  'Otilde',
457                 'Ouml',    'times',  'Oslash',  'Ugrave', 'Uacute', 'Ucirc',
458                 'Uuml',    'Yacute', 'THORN',   'szlig',  'agrave', 'aacute',
459                 'acirc',   'atilde', 'auml',    'aring',  'aelig',  'ccedil',
460                 'egrave',  'eacute', 'ecirc',   'euml',   'igrave', 'iacute',
461                 'icirc',   'iuml',   'eth',     'ntilde', 'ograve', 'oacute',
462                 'ocirc',   'otilde', 'ouml',    'divide', 'oslash', 'ugrave',
463                 'uacute',  'ucirc',  'uuml',    'yacute', 'thorn',  'yuml',
464                 'quot',    'amp',    'lt',      'gt',     'apos',   'OElig',
465                 'oelig',   'Scaron', 'scaron',  'Yuml',   'circ',   'tilde',
466                 'ensp',    'emsp',   'thinsp',  'zwnj',   'zwj',    'lrm',
467                 'rlm',     'ndash',  'mdash',   'lsquo',  'rsquo',  'sbquo',
468                 'ldquo',   'rdquo',  'bdquo',   'dagger', 'Dagger', 'permil',
469                 'lsaquo',  'rsaquo', 'euro',    'fnof',   'Alpha',  'Beta',
470                 'Gamma',   'Delta',  'Epsilon', 'Zeta',   'Eta',    'Theta',
471                 'Iota',    'Kappa',  'Lambda',  'Mu',     'Nu',     'Xi',
472                 'Omicron', 'Pi',     'Rho',     'Sigma',  'Tau',    'Upsilon',
473                 'Phi',     'Chi',    'Psi',     'Omega',  'alpha',  'beta',
474                 'gamma',   'delta',  'epsilon', 'zeta',   'eta',    'theta',
475                 'iota',    'kappa',  'lambda',  'mu',     'nu',     'xi',
476                 'omicron', 'pi',     'rho',     'sigmaf', 'sigma',  'tau',
477                 'upsilon', 'phi',    'chi',     'psi',    'omega',  'thetasym',
478                 'upsih',   'piv',    'bull',    'hellip', 'prime',  'Prime',
479                 'oline',   'frasl',  'weierp',  'image',  'real',   'trade',
480                 'alefsym', 'larr',   'uarr',    'rarr',   'darr',   'harr',
481                 'crarr',   'lArr',   'uArr',    'rArr',   'dArr',   'hArr',
482                 'forall',  'part',   'exist',   'empty',  'nabla',  'isin',
483                 'notin',   'ni',     'prod',    'sum',    'minus',  'lowast',
484                 'radic',   'prop',   'infin',   'ang',    'and',    'or',
485                 'cap',     'cup',    'int',     'sim',    'cong',   'asymp',
486                 'ne',      'equiv',  'le',      'ge',     'sub',    'sup',
487                 'nsub',    'sube',   'supe',    'oplus',  'otimes', 'perp',
488                 'sdot',    'lceil',  'rceil',   'lfloor', 'rfloor', 'lang',
489                 'rang',    'loz',    'spades',  'clubs',  'hearts', 'diams',
490                 'sup1',    'sup2',   'sup3',    'frac14', 'frac12', 'frac34',
491                 'there4',
492         );
493
494         $allowedposttags = array_map( '_wp_add_global_attributes', $allowedposttags );
495 } else {
496         $allowedtags = wp_kses_array_lc( $allowedtags );
497         $allowedposttags = wp_kses_array_lc( $allowedposttags );
498 }
499
500 /**
501  * Filters content and keeps only allowable HTML elements.
502  *
503  * This function makes sure that only the allowed HTML element names, attribute
504  * names and attribute values plus only sane HTML entities will occur in
505  * $string. You have to remove any slashes from PHP's magic quotes before you
506  * call this function.
507  *
508  * The default allowed protocols are 'http', 'https', 'ftp', 'mailto', 'news',
509  * 'irc', 'gopher', 'nntp', 'feed', 'telnet, 'mms', 'rtsp' and 'svn'. This
510  * covers all common link protocols, except for 'javascript' which should not
511  * be allowed for untrusted users.
512  *
513  * @since 1.0.0
514  *
515  * @param string $string Content to filter through kses
516  * @param array $allowed_html List of allowed HTML elements
517  * @param array $allowed_protocols Optional. Allowed protocol in links.
518  * @return string Filtered content with only allowed HTML elements
519  */
520 function wp_kses( $string, $allowed_html, $allowed_protocols = array() ) {
521         if ( empty( $allowed_protocols ) )
522                 $allowed_protocols = wp_allowed_protocols();
523         $string = wp_kses_no_null($string);
524         $string = wp_kses_js_entities($string);
525         $string = wp_kses_normalize_entities($string);
526         $string = wp_kses_hook($string, $allowed_html, $allowed_protocols); // WP changed the order of these funcs and added args to wp_kses_hook
527         return wp_kses_split($string, $allowed_html, $allowed_protocols);
528 }
529
530 /**
531  * Return a list of allowed tags and attributes for a given context.
532  *
533  * @since 3.5.0
534  *
535  * @param string $context The context for which to retrieve tags. Allowed values are
536  *  post | strip | data | entities or the name of a field filter such as pre_user_description.
537  * @return array List of allowed tags and their allowed attributes.
538  */
539 function wp_kses_allowed_html( $context = '' ) {
540         global $allowedposttags, $allowedtags, $allowedentitynames;
541
542         if ( is_array( $context ) ) {
543                 /**
544                  * Filter HTML elements allowed for a given context.
545                  *
546                  * @since 3.5.0
547                  *
548                  * @param string $tags    Allowed tags, attributes, and/or entities.
549                  * @param string $context Context to judge allowed tags by. Allowed values are 'post',
550                  *                        'data', 'strip', 'entities', 'explicit', or the name of a filter.
551                  */
552                 return apply_filters( 'wp_kses_allowed_html', $context, 'explicit' );
553         }
554
555         switch ( $context ) {
556                 case 'post':
557                         /** This filter is documented in wp-includes/kses.php */
558                         return apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context );
559                         break;
560                 case 'user_description':
561                 case 'pre_user_description':
562                         $tags = $allowedtags;
563                         $tags['a']['rel'] = true;
564                         /** This filter is documented in wp-includes/kses.php */
565                         return apply_filters( 'wp_kses_allowed_html', $tags, $context );
566                         break;
567                 case 'strip':
568                         /** This filter is documented in wp-includes/kses.php */
569                         return apply_filters( 'wp_kses_allowed_html', array(), $context );
570                         break;
571                 case 'entities':
572                         /** This filter is documented in wp-includes/kses.php */
573                         return apply_filters( 'wp_kses_allowed_html', $allowedentitynames, $context);
574                         break;
575                 case 'data':
576                 default:
577                         /** This filter is documented in wp-includes/kses.php */
578                         return apply_filters( 'wp_kses_allowed_html', $allowedtags, $context );
579         }
580 }
581
582 /**
583  * You add any kses hooks here.
584  *
585  * There is currently only one kses WordPress hook and it is called here. All
586  * parameters are passed to the hooks and expected to receive a string.
587  *
588  * @since 1.0.0
589  *
590  * @param string $string Content to filter through kses
591  * @param array $allowed_html List of allowed HTML elements
592  * @param array $allowed_protocols Allowed protocol in links
593  * @return string Filtered content through 'pre_kses' hook
594  */
595 function wp_kses_hook( $string, $allowed_html, $allowed_protocols ) {
596         /**
597          * Filter content to be run through kses.
598          *
599          * @since 2.3.0
600          *
601          * @param string $string            Content to run through kses.
602          * @param array  $allowed_html      Allowed HTML elements.
603          * @param array  $allowed_protocols Allowed protocol in links.
604          */
605         $string = apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );
606         return $string;
607 }
608
609 /**
610  * This function returns kses' version number.
611  *
612  * @since 1.0.0
613  *
614  * @return string KSES Version Number
615  */
616 function wp_kses_version() {
617         return '0.2.2';
618 }
619
620 /**
621  * Searches for HTML tags, no matter how malformed.
622  *
623  * It also matches stray ">" characters.
624  *
625  * @since 1.0.0
626  *
627  * @param string $string Content to filter
628  * @param array $allowed_html Allowed HTML elements
629  * @param array $allowed_protocols Allowed protocols to keep
630  * @return string Content with fixed HTML tags
631  */
632 function wp_kses_split( $string, $allowed_html, $allowed_protocols ) {
633         global $pass_allowed_html, $pass_allowed_protocols;
634         $pass_allowed_html = $allowed_html;
635         $pass_allowed_protocols = $allowed_protocols;
636         return preg_replace_callback( '%(<!--.*?(-->|$))|(<[^>]*(>|$)|>)%', '_wp_kses_split_callback', $string );
637 }
638
639 /**
640  * Callback for wp_kses_split.
641  *
642  * @since 3.1.0
643  * @access private
644  */
645 function _wp_kses_split_callback( $match ) {
646         global $pass_allowed_html, $pass_allowed_protocols;
647         return wp_kses_split2( $match[0], $pass_allowed_html, $pass_allowed_protocols );
648 }
649
650 /**
651  * Callback for wp_kses_split for fixing malformed HTML tags.
652  *
653  * This function does a lot of work. It rejects some very malformed things like
654  * <:::>. It returns an empty string, if the element isn't allowed (look ma, no
655  * strip_tags()!). Otherwise it splits the tag into an element and an attribute
656  * list.
657  *
658  * After the tag is split into an element and an attribute list, it is run
659  * through another filter which will remove illegal attributes and once that is
660  * completed, will be returned.
661  *
662  * @access private
663  * @since 1.0.0
664  *
665  * @param string $string Content to filter
666  * @param array $allowed_html Allowed HTML elements
667  * @param array $allowed_protocols Allowed protocols to keep
668  * @return string Fixed HTML element
669  */
670 function wp_kses_split2($string, $allowed_html, $allowed_protocols) {
671         $string = wp_kses_stripslashes($string);
672
673         if (substr($string, 0, 1) != '<')
674                 return '&gt;';
675         # It matched a ">" character
676
677         if ( '<!--' == substr( $string, 0, 4 ) ) {
678                 $string = str_replace( array('<!--', '-->'), '', $string );
679                 while ( $string != ($newstring = wp_kses($string, $allowed_html, $allowed_protocols)) )
680                         $string = $newstring;
681                 if ( $string == '' )
682                         return '';
683                 // prevent multiple dashes in comments
684                 $string = preg_replace('/--+/', '-', $string);
685                 // prevent three dashes closing a comment
686                 $string = preg_replace('/-$/', '', $string);
687                 return "<!--{$string}-->";
688         }
689         # Allow HTML comments
690
691         if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches))
692                 return '';
693         # It's seriously malformed
694
695         $slash = trim($matches[1]);
696         $elem = $matches[2];
697         $attrlist = $matches[3];
698
699         if ( ! is_array( $allowed_html ) )
700                 $allowed_html = wp_kses_allowed_html( $allowed_html );
701
702         if ( ! isset($allowed_html[strtolower($elem)]) )
703                 return '';
704         # They are using a not allowed HTML element
705
706         if ($slash != '')
707                 return "</$elem>";
708         # No attributes are allowed for closing elements
709
710         return wp_kses_attr( $elem, $attrlist, $allowed_html, $allowed_protocols );
711 }
712
713 /**
714  * Removes all attributes, if none are allowed for this element.
715  *
716  * If some are allowed it calls wp_kses_hair() to split them further, and then
717  * it builds up new HTML code from the data that kses_hair() returns. It also
718  * removes "<" and ">" characters, if there are any left. One more thing it does
719  * is to check if the tag has a closing XHTML slash, and if it does, it puts one
720  * in the returned code as well.
721  *
722  * @since 1.0.0
723  *
724  * @param string $element HTML element/tag
725  * @param string $attr HTML attributes from HTML element to closing HTML element tag
726  * @param array $allowed_html Allowed HTML elements
727  * @param array $allowed_protocols Allowed protocols to keep
728  * @return string Sanitized HTML element
729  */
730 function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
731         # Is there a closing XHTML slash at the end of the attributes?
732
733         if ( ! is_array( $allowed_html ) )
734                 $allowed_html = wp_kses_allowed_html( $allowed_html );
735
736         $xhtml_slash = '';
737         if (preg_match('%\s*/\s*$%', $attr))
738                 $xhtml_slash = ' /';
739
740         # Are any attributes allowed at all for this element?
741         if ( ! isset($allowed_html[strtolower($element)]) || count($allowed_html[strtolower($element)]) == 0 )
742                 return "<$element$xhtml_slash>";
743
744         # Split it
745         $attrarr = wp_kses_hair($attr, $allowed_protocols);
746
747         # Go through $attrarr, and save the allowed attributes for this element
748         # in $attr2
749         $attr2 = '';
750
751         $allowed_attr = $allowed_html[strtolower($element)];
752         foreach ($attrarr as $arreach) {
753                 if ( ! isset( $allowed_attr[strtolower($arreach['name'])] ) )
754                         continue; # the attribute is not allowed
755
756                 $current = $allowed_attr[strtolower($arreach['name'])];
757                 if ( $current == '' )
758                         continue; # the attribute is not allowed
759
760                 if ( strtolower( $arreach['name'] ) == 'style' ) {
761                         $orig_value = $arreach['value'];
762                         $value = safecss_filter_attr( $orig_value );
763
764                         if ( empty( $value ) )
765                                 continue;
766
767                         $arreach['value'] = $value;
768                         $arreach['whole'] = str_replace( $orig_value, $value, $arreach['whole'] );
769                 }
770
771                 if ( ! is_array($current) ) {
772                         $attr2 .= ' '.$arreach['whole'];
773                 # there are no checks
774
775                 } else {
776                         # there are some checks
777                         $ok = true;
778                         foreach ($current as $currkey => $currval) {
779                                 if ( ! wp_kses_check_attr_val($arreach['value'], $arreach['vless'], $currkey, $currval) ) {
780                                         $ok = false;
781                                         break;
782                                 }
783                         }
784
785                         if ( $ok )
786                                 $attr2 .= ' '.$arreach['whole']; # it passed them
787                 } # if !is_array($current)
788         } # foreach
789
790         # Remove any "<" or ">" characters
791         $attr2 = preg_replace('/[<>]/', '', $attr2);
792
793         return "<$element$attr2$xhtml_slash>";
794 }
795
796 /**
797  * Builds an attribute list from string containing attributes.
798  *
799  * This function does a lot of work. It parses an attribute list into an array
800  * with attribute data, and tries to do the right thing even if it gets weird
801  * input. It will add quotes around attribute values that don't have any quotes
802  * or apostrophes around them, to make it easier to produce HTML code that will
803  * conform to W3C's HTML specification. It will also remove bad URL protocols
804  * from attribute values. It also reduces duplicate attributes by using the
805  * attribute defined first (foo='bar' foo='baz' will result in foo='bar').
806  *
807  * @since 1.0.0
808  *
809  * @param string $attr Attribute list from HTML element to closing HTML element tag
810  * @param array $allowed_protocols Allowed protocols to keep
811  * @return array List of attributes after parsing
812  */
813 function wp_kses_hair($attr, $allowed_protocols) {
814         $attrarr = array();
815         $mode = 0;
816         $attrname = '';
817         $uris = array('xmlns', 'profile', 'href', 'src', 'cite', 'classid', 'codebase', 'data', 'usemap', 'longdesc', 'action');
818
819         # Loop through the whole attribute list
820
821         while (strlen($attr) != 0) {
822                 $working = 0; # Was the last operation successful?
823
824                 switch ($mode) {
825                         case 0 : # attribute name, href for instance
826
827                                 if ( preg_match('/^([-a-zA-Z:]+)/', $attr, $match ) ) {
828                                         $attrname = $match[1];
829                                         $working = $mode = 1;
830                                         $attr = preg_replace( '/^[-a-zA-Z:]+/', '', $attr );
831                                 }
832
833                                 break;
834
835                         case 1 : # equals sign or valueless ("selected")
836
837                                 if (preg_match('/^\s*=\s*/', $attr)) # equals sign
838                                         {
839                                         $working = 1;
840                                         $mode = 2;
841                                         $attr = preg_replace('/^\s*=\s*/', '', $attr);
842                                         break;
843                                 }
844
845                                 if (preg_match('/^\s+/', $attr)) # valueless
846                                         {
847                                         $working = 1;
848                                         $mode = 0;
849                                         if(false === array_key_exists($attrname, $attrarr)) {
850                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
851                                         }
852                                         $attr = preg_replace('/^\s+/', '', $attr);
853                                 }
854
855                                 break;
856
857                         case 2 : # attribute value, a URL after href= for instance
858
859                                 if (preg_match('%^"([^"]*)"(\s+|/?$)%', $attr, $match))
860                                         # "value"
861                                         {
862                                         $thisval = $match[1];
863                                         if ( in_array(strtolower($attrname), $uris) )
864                                                 $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
865
866                                         if(false === array_key_exists($attrname, $attrarr)) {
867                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
868                                         }
869                                         $working = 1;
870                                         $mode = 0;
871                                         $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
872                                         break;
873                                 }
874
875                                 if (preg_match("%^'([^']*)'(\s+|/?$)%", $attr, $match))
876                                         # 'value'
877                                         {
878                                         $thisval = $match[1];
879                                         if ( in_array(strtolower($attrname), $uris) )
880                                                 $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
881
882                                         if(false === array_key_exists($attrname, $attrarr)) {
883                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname='$thisval'", 'vless' => 'n');
884                                         }
885                                         $working = 1;
886                                         $mode = 0;
887                                         $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
888                                         break;
889                                 }
890
891                                 if (preg_match("%^([^\s\"']+)(\s+|/?$)%", $attr, $match))
892                                         # value
893                                         {
894                                         $thisval = $match[1];
895                                         if ( in_array(strtolower($attrname), $uris) )
896                                                 $thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
897
898                                         if(false === array_key_exists($attrname, $attrarr)) {
899                                                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => $thisval, 'whole' => "$attrname=\"$thisval\"", 'vless' => 'n');
900                                         }
901                                         # We add quotes to conform to W3C's HTML spec.
902                                         $working = 1;
903                                         $mode = 0;
904                                         $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
905                                 }
906
907                                 break;
908                 } # switch
909
910                 if ($working == 0) # not well formed, remove and try again
911                 {
912                         $attr = wp_kses_html_error($attr);
913                         $mode = 0;
914                 }
915         } # while
916
917         if ($mode == 1 && false === array_key_exists($attrname, $attrarr))
918                 # special case, for when the attribute list ends with a valueless
919                 # attribute like "selected"
920                 $attrarr[$attrname] = array ('name' => $attrname, 'value' => '', 'whole' => $attrname, 'vless' => 'y');
921
922         return $attrarr;
923 }
924
925 /**
926  * Performs different checks for attribute values.
927  *
928  * The currently implemented checks are "maxlen", "minlen", "maxval", "minval"
929  * and "valueless".
930  *
931  * @since 1.0.0
932  *
933  * @param string $value Attribute value
934  * @param string $vless Whether the value is valueless. Use 'y' or 'n'
935  * @param string $checkname What $checkvalue is checking for.
936  * @param mixed $checkvalue What constraint the value should pass
937  * @return bool Whether check passes
938  */
939 function wp_kses_check_attr_val($value, $vless, $checkname, $checkvalue) {
940         $ok = true;
941
942         switch (strtolower($checkname)) {
943                 case 'maxlen' :
944                         # The maxlen check makes sure that the attribute value has a length not
945                         # greater than the given value. This can be used to avoid Buffer Overflows
946                         # in WWW clients and various Internet servers.
947
948                         if (strlen($value) > $checkvalue)
949                                 $ok = false;
950                         break;
951
952                 case 'minlen' :
953                         # The minlen check makes sure that the attribute value has a length not
954                         # smaller than the given value.
955
956                         if (strlen($value) < $checkvalue)
957                                 $ok = false;
958                         break;
959
960                 case 'maxval' :
961                         # The maxval check does two things: it checks that the attribute value is
962                         # an integer from 0 and up, without an excessive amount of zeroes or
963                         # whitespace (to avoid Buffer Overflows). It also checks that the attribute
964                         # value is not greater than the given value.
965                         # This check can be used to avoid Denial of Service attacks.
966
967                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
968                                 $ok = false;
969                         if ($value > $checkvalue)
970                                 $ok = false;
971                         break;
972
973                 case 'minval' :
974                         # The minval check makes sure that the attribute value is a positive integer,
975                         # and that it is not smaller than the given value.
976
977                         if (!preg_match('/^\s{0,6}[0-9]{1,6}\s{0,6}$/', $value))
978                                 $ok = false;
979                         if ($value < $checkvalue)
980                                 $ok = false;
981                         break;
982
983                 case 'valueless' :
984                         # The valueless check makes sure if the attribute has a value
985                         # (like <a href="blah">) or not (<option selected>). If the given value
986                         # is a "y" or a "Y", the attribute must not have a value.
987                         # If the given value is an "n" or an "N", the attribute must have one.
988
989                         if (strtolower($checkvalue) != $vless)
990                                 $ok = false;
991                         break;
992         } # switch
993
994         return $ok;
995 }
996
997 /**
998  * Sanitize string from bad protocols.
999  *
1000  * This function removes all non-allowed protocols from the beginning of
1001  * $string. It ignores whitespace and the case of the letters, and it does
1002  * understand HTML entities. It does its work in a while loop, so it won't be
1003  * fooled by a string like "javascript:javascript:alert(57)".
1004  *
1005  * @since 1.0.0
1006  *
1007  * @param string $string Content to filter bad protocols from
1008  * @param array $allowed_protocols Allowed protocols to keep
1009  * @return string Filtered content
1010  */
1011 function wp_kses_bad_protocol($string, $allowed_protocols) {
1012         $string = wp_kses_no_null($string);
1013         $iterations = 0;
1014
1015         do {
1016                 $original_string = $string;
1017                 $string = wp_kses_bad_protocol_once($string, $allowed_protocols);
1018         } while ( $original_string != $string && ++$iterations < 6 );
1019
1020         if ( $original_string != $string )
1021                 return '';
1022
1023         return $string;
1024 }
1025
1026 /**
1027  * Removes any invalid control characters in $string.
1028  *
1029  * Also removes any instance of the '\0' string.
1030  *
1031  * @since 1.0.0
1032  *
1033  * @param string $string
1034  * @return string
1035  */
1036 function wp_kses_no_null($string) {
1037         $string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $string);
1038         $string = preg_replace('/(\\\\0)+/', '', $string);
1039
1040         return $string;
1041 }
1042
1043 /**
1044  * Strips slashes from in front of quotes.
1045  *
1046  * This function changes the character sequence \" to just ". It leaves all
1047  * other slashes alone. It's really weird, but the quoting from
1048  * preg_replace(//e) seems to require this.
1049  *
1050  * @since 1.0.0
1051  *
1052  * @param string $string String to strip slashes
1053  * @return string Fixed string with quoted slashes
1054  */
1055 function wp_kses_stripslashes($string) {
1056         return preg_replace('%\\\\"%', '"', $string);
1057 }
1058
1059 /**
1060  * Goes through an array and changes the keys to all lower case.
1061  *
1062  * @since 1.0.0
1063  *
1064  * @param array $inarray Unfiltered array
1065  * @return array Fixed array with all lowercase keys
1066  */
1067 function wp_kses_array_lc($inarray) {
1068         $outarray = array ();
1069
1070         foreach ( (array) $inarray as $inkey => $inval) {
1071                 $outkey = strtolower($inkey);
1072                 $outarray[$outkey] = array ();
1073
1074                 foreach ( (array) $inval as $inkey2 => $inval2) {
1075                         $outkey2 = strtolower($inkey2);
1076                         $outarray[$outkey][$outkey2] = $inval2;
1077                 } # foreach $inval
1078         } # foreach $inarray
1079
1080         return $outarray;
1081 }
1082
1083 /**
1084  * Removes the HTML JavaScript entities found in early versions of Netscape 4.
1085  *
1086  * @since 1.0.0
1087  *
1088  * @param string $string
1089  * @return string
1090  */
1091 function wp_kses_js_entities($string) {
1092         return preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
1093 }
1094
1095 /**
1096  * Handles parsing errors in wp_kses_hair().
1097  *
1098  * The general plan is to remove everything to and including some whitespace,
1099  * but it deals with quotes and apostrophes as well.
1100  *
1101  * @since 1.0.0
1102  *
1103  * @param string $string
1104  * @return string
1105  */
1106 function wp_kses_html_error($string) {
1107         return preg_replace('/^("[^"]*("|$)|\'[^\']*(\'|$)|\S)*\s*/', '', $string);
1108 }
1109
1110 /**
1111  * Sanitizes content from bad protocols and other characters.
1112  *
1113  * This function searches for URL protocols at the beginning of $string, while
1114  * handling whitespace and HTML entities.
1115  *
1116  * @since 1.0.0
1117  *
1118  * @param string $string Content to check for bad protocols
1119  * @param string $allowed_protocols Allowed protocols
1120  * @return string Sanitized content
1121  */
1122 function wp_kses_bad_protocol_once($string, $allowed_protocols, $count = 1 ) {
1123         $string2 = preg_split( '/:|&#0*58;|&#x0*3a;/i', $string, 2 );
1124         if ( isset($string2[1]) && ! preg_match('%/\?%', $string2[0]) ) {
1125                 $string = trim( $string2[1] );
1126                 $protocol = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols );
1127                 if ( 'feed:' == $protocol ) {
1128                         if ( $count > 2 )
1129                                 return '';
1130                         $string = wp_kses_bad_protocol_once( $string, $allowed_protocols, ++$count );
1131                         if ( empty( $string ) )
1132                                 return $string;
1133                 }
1134                 $string = $protocol . $string;
1135         }
1136
1137         return $string;
1138 }
1139
1140 /**
1141  * Callback for wp_kses_bad_protocol_once() regular expression.
1142  *
1143  * This function processes URL protocols, checks to see if they're in the
1144  * whitelist or not, and returns different data depending on the answer.
1145  *
1146  * @access private
1147  * @since 1.0.0
1148  *
1149  * @param string $string URI scheme to check against the whitelist
1150  * @param string $allowed_protocols Allowed protocols
1151  * @return string Sanitized content
1152  */
1153 function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
1154         $string2 = wp_kses_decode_entities($string);
1155         $string2 = preg_replace('/\s/', '', $string2);
1156         $string2 = wp_kses_no_null($string2);
1157         $string2 = strtolower($string2);
1158
1159         $allowed = false;
1160         foreach ( (array) $allowed_protocols as $one_protocol )
1161                 if ( strtolower($one_protocol) == $string2 ) {
1162                         $allowed = true;
1163                         break;
1164                 }
1165
1166         if ($allowed)
1167                 return "$string2:";
1168         else
1169                 return '';
1170 }
1171
1172 /**
1173  * Converts and fixes HTML entities.
1174  *
1175  * This function normalizes HTML entities. It will convert `AT&T` to the correct
1176  * `AT&amp;T`, `&#00058;` to `&#58;`, `&#XYZZY;` to `&amp;#XYZZY;` and so on.
1177  *
1178  * @since 1.0.0
1179  *
1180  * @param string $string Content to normalize entities
1181  * @return string Content with normalized entities
1182  */
1183 function wp_kses_normalize_entities($string) {
1184         # Disarm all entities by converting & to &amp;
1185
1186         $string = str_replace('&', '&amp;', $string);
1187
1188         # Change back the allowed entities in our entity whitelist
1189
1190         $string = preg_replace_callback('/&amp;([A-Za-z]{2,8}[0-9]{0,2});/', 'wp_kses_named_entities', $string);
1191         $string = preg_replace_callback('/&amp;#(0*[0-9]{1,7});/', 'wp_kses_normalize_entities2', $string);
1192         $string = preg_replace_callback('/&amp;#[Xx](0*[0-9A-Fa-f]{1,6});/', 'wp_kses_normalize_entities3', $string);
1193
1194         return $string;
1195 }
1196
1197 /**
1198  * Callback for wp_kses_normalize_entities() regular expression.
1199  *
1200  * This function only accepts valid named entity references, which are finite,
1201  * case-sensitive, and highly scrutinized by HTML and XML validators.
1202  *
1203  * @since 3.0.0
1204  *
1205  * @param array $matches preg_replace_callback() matches array
1206  * @return string Correctly encoded entity
1207  */
1208 function wp_kses_named_entities($matches) {
1209         global $allowedentitynames;
1210
1211         if ( empty($matches[1]) )
1212                 return '';
1213
1214         $i = $matches[1];
1215         return ( ( ! in_array($i, $allowedentitynames) ) ? "&amp;$i;" : "&$i;" );
1216 }
1217
1218 /**
1219  * Callback for wp_kses_normalize_entities() regular expression.
1220  *
1221  * This function helps {@see wp_kses_normalize_entities()} to only accept 16-bit
1222  * values and nothing more for `&#number;` entities.
1223  *
1224  * @access private
1225  * @since 1.0.0
1226  *
1227  * @param array $matches preg_replace_callback() matches array
1228  * @return string Correctly encoded entity
1229  */
1230 function wp_kses_normalize_entities2($matches) {
1231         if ( empty($matches[1]) )
1232                 return '';
1233
1234         $i = $matches[1];
1235         if (valid_unicode($i)) {
1236                 $i = str_pad(ltrim($i,'0'), 3, '0', STR_PAD_LEFT);
1237                 $i = "&#$i;";
1238         } else {
1239                 $i = "&amp;#$i;";
1240         }
1241
1242         return $i;
1243 }
1244
1245 /**
1246  * Callback for wp_kses_normalize_entities() for regular expression.
1247  *
1248  * This function helps wp_kses_normalize_entities() to only accept valid Unicode
1249  * numeric entities in hex form.
1250  *
1251  * @access private
1252  *
1253  * @param array $matches preg_replace_callback() matches array
1254  * @return string Correctly encoded entity
1255  */
1256 function wp_kses_normalize_entities3($matches) {
1257         if ( empty($matches[1]) )
1258                 return '';
1259
1260         $hexchars = $matches[1];
1261         return ( ( ! valid_unicode(hexdec($hexchars)) ) ? "&amp;#x$hexchars;" : '&#x'.ltrim($hexchars,'0').';' );
1262 }
1263
1264 /**
1265  * Helper function to determine if a Unicode value is valid.
1266  *
1267  * @param int $i Unicode value
1268  * @return bool True if the value was a valid Unicode number
1269  */
1270 function valid_unicode($i) {
1271         return ( $i == 0x9 || $i == 0xa || $i == 0xd ||
1272                         ($i >= 0x20 && $i <= 0xd7ff) ||
1273                         ($i >= 0xe000 && $i <= 0xfffd) ||
1274                         ($i >= 0x10000 && $i <= 0x10ffff) );
1275 }
1276
1277 /**
1278  * Convert all entities to their character counterparts.
1279  *
1280  * This function decodes numeric HTML entities (`&#65;` and `&#x41;`).
1281  * It doesn't do anything with other entities like &auml;, but we don't
1282  * need them in the URL protocol whitelisting system anyway.
1283  *
1284  * @since 1.0.0
1285  *
1286  * @param string $string Content to change entities
1287  * @return string Content after decoded entities
1288  */
1289 function wp_kses_decode_entities($string) {
1290         $string = preg_replace_callback('/&#([0-9]+);/', '_wp_kses_decode_entities_chr', $string);
1291         $string = preg_replace_callback('/&#[Xx]([0-9A-Fa-f]+);/', '_wp_kses_decode_entities_chr_hexdec', $string);
1292
1293         return $string;
1294 }
1295
1296 /**
1297  * Regex callback for wp_kses_decode_entities()
1298  *
1299  * @param array $match preg match
1300  * @return string
1301  */
1302 function _wp_kses_decode_entities_chr( $match ) {
1303         return chr( $match[1] );
1304 }
1305
1306 /**
1307  * Regex callback for wp_kses_decode_entities()
1308  *
1309  * @param array $match preg match
1310  * @return string
1311  */
1312 function _wp_kses_decode_entities_chr_hexdec( $match ) {
1313         return chr( hexdec( $match[1] ) );
1314 }
1315
1316 /**
1317  * Sanitize content with allowed HTML Kses rules.
1318  *
1319  * @since 1.0.0
1320  *
1321  * @param string $data Content to filter, expected to be escaped with slashes
1322  * @return string Filtered content
1323  */
1324 function wp_filter_kses( $data ) {
1325         return addslashes( wp_kses( stripslashes( $data ), current_filter() ) );
1326 }
1327
1328 /**
1329  * Sanitize content with allowed HTML Kses rules.
1330  *
1331  * @since 2.9.0
1332  *
1333  * @param string $data Content to filter, expected to not be escaped
1334  * @return string Filtered content
1335  */
1336 function wp_kses_data( $data ) {
1337         return wp_kses( $data , current_filter() );
1338 }
1339
1340 /**
1341  * Sanitize content for allowed HTML tags for post content.
1342  *
1343  * Post content refers to the page contents of the 'post' type and not $_POST
1344  * data from forms.
1345  *
1346  * @since 2.0.0
1347  *
1348  * @param string $data Post content to filter, expected to be escaped with slashes
1349  * @return string Filtered post content with allowed HTML tags and attributes intact.
1350  */
1351 function wp_filter_post_kses($data) {
1352         return addslashes ( wp_kses( stripslashes( $data ), 'post' ) );
1353 }
1354
1355 /**
1356  * Sanitize content for allowed HTML tags for post content.
1357  *
1358  * Post content refers to the page contents of the 'post' type and not $_POST
1359  * data from forms.
1360  *
1361  * @since 2.9.0
1362  *
1363  * @param string $data Post content to filter
1364  * @return string Filtered post content with allowed HTML tags and attributes intact.
1365  */
1366 function wp_kses_post($data) {
1367         return wp_kses( $data , 'post' );
1368 }
1369
1370 /**
1371  * Strips all of the HTML in the content.
1372  *
1373  * @since 2.1.0
1374  *
1375  * @param string $data Content to strip all HTML from
1376  * @return string Filtered content without any HTML
1377  */
1378 function wp_filter_nohtml_kses( $data ) {
1379         return addslashes ( wp_kses( stripslashes( $data ), 'strip' ) );
1380 }
1381
1382 /**
1383  * Adds all Kses input form content filters.
1384  *
1385  * All hooks have default priority. The wp_filter_kses() function is added to
1386  * the 'pre_comment_content' and 'title_save_pre' hooks.
1387  *
1388  * The wp_filter_post_kses() function is added to the 'content_save_pre',
1389  * 'excerpt_save_pre', and 'content_filtered_save_pre' hooks.
1390  *
1391  * @since 2.0.0
1392  */
1393 function kses_init_filters() {
1394         // Normal filtering
1395         add_filter('title_save_pre', 'wp_filter_kses');
1396
1397         // Comment filtering
1398         if ( current_user_can( 'unfiltered_html' ) )
1399                 add_filter( 'pre_comment_content', 'wp_filter_post_kses' );
1400         else
1401                 add_filter( 'pre_comment_content', 'wp_filter_kses' );
1402
1403         // Post filtering
1404         add_filter('content_save_pre', 'wp_filter_post_kses');
1405         add_filter('excerpt_save_pre', 'wp_filter_post_kses');
1406         add_filter('content_filtered_save_pre', 'wp_filter_post_kses');
1407 }
1408
1409 /**
1410  * Removes all Kses input form content filters.
1411  *
1412  * A quick procedural method to removing all of the filters that kses uses for
1413  * content in WordPress Loop.
1414  *
1415  * Does not remove the kses_init() function from 'init' hook (priority is
1416  * default). Also does not remove kses_init() function from 'set_current_user'
1417  * hook (priority is also default).
1418  *
1419  * @since 2.0.6
1420  */
1421 function kses_remove_filters() {
1422         // Normal filtering
1423         remove_filter('title_save_pre', 'wp_filter_kses');
1424
1425         // Comment filtering
1426         remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
1427         remove_filter( 'pre_comment_content', 'wp_filter_kses' );
1428
1429         // Post filtering
1430         remove_filter('content_save_pre', 'wp_filter_post_kses');
1431         remove_filter('excerpt_save_pre', 'wp_filter_post_kses');
1432         remove_filter('content_filtered_save_pre', 'wp_filter_post_kses');
1433 }
1434
1435 /**
1436  * Sets up most of the Kses filters for input form content.
1437  *
1438  * If you remove the kses_init() function from 'init' hook and
1439  * 'set_current_user' (priority is default), then none of the Kses filter hooks
1440  * will be added.
1441  *
1442  * First removes all of the Kses filters in case the current user does not need
1443  * to have Kses filter the content. If the user does not have unfiltered_html
1444  * capability, then Kses filters are added.
1445  *
1446  * @since 2.0.0
1447  */
1448 function kses_init() {
1449         kses_remove_filters();
1450
1451         if (current_user_can('unfiltered_html') == false)
1452                 kses_init_filters();
1453 }
1454
1455 add_action('init', 'kses_init');
1456 add_action('set_current_user', 'kses_init');
1457
1458 /**
1459  * Inline CSS filter
1460  *
1461  * @since 2.8.1
1462  */
1463 function safecss_filter_attr( $css, $deprecated = '' ) {
1464         if ( !empty( $deprecated ) )
1465                 _deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented
1466
1467         $css = wp_kses_no_null($css);
1468         $css = str_replace(array("\n","\r","\t"), '', $css);
1469
1470         if ( preg_match( '%[\\\\(&=}]|/\*%', $css ) ) // remove any inline css containing \ ( & } = or comments
1471                 return '';
1472
1473         $css_array = explode( ';', trim( $css ) );
1474
1475         /**
1476          * Filter list of allowed CSS attributes.
1477          *
1478          * @since 2.8.1
1479          *
1480          * @param array $attr List of allowed CSS attributes.
1481          */
1482         $allowed_attr = apply_filters( 'safe_style_css', array( 'text-align', 'margin', 'color', 'float',
1483         'border', 'background', 'background-color', 'border-bottom', 'border-bottom-color',
1484         'border-bottom-style', 'border-bottom-width', 'border-collapse', 'border-color', 'border-left',
1485         'border-left-color', 'border-left-style', 'border-left-width', 'border-right', 'border-right-color',
1486         'border-right-style', 'border-right-width', 'border-spacing', 'border-style', 'border-top',
1487         'border-top-color', 'border-top-style', 'border-top-width', 'border-width', 'caption-side',
1488         'clear', 'cursor', 'direction', 'font', 'font-family', 'font-size', 'font-style',
1489         'font-variant', 'font-weight', 'height', 'letter-spacing', 'line-height', 'margin-bottom',
1490         'margin-left', 'margin-right', 'margin-top', 'overflow', 'padding', 'padding-bottom',
1491         'padding-left', 'padding-right', 'padding-top', 'text-decoration', 'text-indent', 'vertical-align',
1492         'width' ) );
1493
1494         if ( empty($allowed_attr) )
1495                 return $css;
1496
1497         $css = '';
1498         foreach ( $css_array as $css_item ) {
1499                 if ( $css_item == '' )
1500                         continue;
1501                 $css_item = trim( $css_item );
1502                 $found = false;
1503                 if ( strpos( $css_item, ':' ) === false ) {
1504                         $found = true;
1505                 } else {
1506                         $parts = explode( ':', $css_item );
1507                         if ( in_array( trim( $parts[0] ), $allowed_attr ) )
1508                                 $found = true;
1509                 }
1510                 if ( $found ) {
1511                         if( $css != '' )
1512                                 $css .= ';';
1513                         $css .= $css_item;
1514                 }
1515         }
1516
1517         return $css;
1518 }
1519
1520 /**
1521  * Helper function to add global attributes to a tag in the allowed html list.
1522  *
1523  * @since 3.5.0
1524  * @access private
1525  *
1526  * @param array $value An array of attributes.
1527  * @return array The array of attributes with global attributes added.
1528  */
1529 function _wp_add_global_attributes( $value ) {
1530         $global_attributes = array(
1531                 'class' => true,
1532                 'id' => true,
1533                 'style' => true,
1534                 'title' => true,
1535                 'role' => true,
1536         );
1537
1538         if ( true === $value )
1539                 $value = array();
1540
1541         if ( is_array( $value ) )
1542                 return array_merge( $value, $global_attributes );
1543
1544         return $value;
1545 }