MediaWiki  REL1_24
Xml.php
Go to the documentation of this file.
00001 <?php
00026 class Xml {
00039     public static function element( $element, $attribs = null, $contents = '',
00040         $allowShortTag = true
00041     ) {
00042         $out = '<' . $element;
00043         if ( !is_null( $attribs ) ) {
00044             $out .= self::expandAttributes( $attribs );
00045         }
00046         if ( is_null( $contents ) ) {
00047             $out .= '>';
00048         } else {
00049             if ( $allowShortTag && $contents === '' ) {
00050                 $out .= ' />';
00051             } else {
00052                 $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
00053             }
00054         }
00055         return $out;
00056     }
00057 
00067     public static function expandAttributes( $attribs ) {
00068         $out = '';
00069         if ( is_null( $attribs ) ) {
00070             return null;
00071         } elseif ( is_array( $attribs ) ) {
00072             foreach ( $attribs as $name => $val ) {
00073                 $out .= " {$name}=\"" . Sanitizer::encodeAttribute( $val ) . '"';
00074             }
00075             return $out;
00076         } else {
00077             throw new MWException( 'Expected attribute array, got something else in ' . __METHOD__ );
00078         }
00079     }
00080 
00091     public static function elementClean( $element, $attribs = array(), $contents = '' ) {
00092         global $wgContLang;
00093         if ( $attribs ) {
00094             $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
00095         }
00096         if ( $contents ) {
00097             wfProfileIn( __METHOD__ . '-norm' );
00098             $contents = $wgContLang->normalize( $contents );
00099             wfProfileOut( __METHOD__ . '-norm' );
00100         }
00101         return self::element( $element, $attribs, $contents );
00102     }
00103 
00111     public static function openElement( $element, $attribs = null ) {
00112         return '<' . $element . self::expandAttributes( $attribs ) . '>';
00113     }
00114 
00120     public static function closeElement( $element ) {
00121         return "</$element>";
00122     }
00123 
00133     public static function tags( $element, $attribs = null, $contents ) {
00134         return self::openElement( $element, $attribs ) . $contents . "</$element>";
00135     }
00136 
00146     public static function monthSelector( $selected = '', $allmonths = null, $id = 'month' ) {
00147         global $wgLang;
00148         $options = array();
00149         if ( is_null( $selected ) ) {
00150             $selected = '';
00151         }
00152         if ( !is_null( $allmonths ) ) {
00153             $options[] = self::option(
00154                 wfMessage( 'monthsall' )->text(),
00155                 $allmonths,
00156                 $selected === $allmonths
00157             );
00158         }
00159         for ( $i = 1; $i < 13; $i++ ) {
00160             $options[] = self::option( $wgLang->getMonthName( $i ), $i, $selected === $i );
00161         }
00162         return self::openElement( 'select', array(
00163             'id' => $id,
00164             'name' => 'month',
00165             'class' => 'mw-month-selector'
00166         ) )
00167             . implode( "\n", $options )
00168             . self::closeElement( 'select' );
00169     }
00170 
00176     public static function dateMenu( $year, $month ) {
00177         # Offset overrides year/month selection
00178         if ( $month && $month !== -1 ) {
00179             $encMonth = intval( $month );
00180         } else {
00181             $encMonth = '';
00182         }
00183         if ( $year ) {
00184             $encYear = intval( $year );
00185         } elseif ( $encMonth ) {
00186             $timestamp = MWTimestamp::getInstance();
00187             $thisMonth = intval( $timestamp->format( 'n' ) );
00188             $thisYear = intval( $timestamp->format( 'Y' ) );
00189             if ( intval( $encMonth ) > $thisMonth ) {
00190                 $thisYear--;
00191             }
00192             $encYear = $thisYear;
00193         } else {
00194             $encYear = '';
00195         }
00196         $inputAttribs = array( 'id' => 'year', 'maxlength' => 4, 'size' => 7 );
00197         return self::label( wfMessage( 'year' )->text(), 'year' ) . ' ' .
00198             Html::input( 'year', $encYear, 'number', $inputAttribs ) . ' ' .
00199             self::label( wfMessage( 'month' )->text(), 'month' ) . ' ' .
00200             self::monthSelector( $encMonth, -1 );
00201     }
00202 
00213     public static function languageSelector( $selected, $customisedOnly = true,
00214         $inLanguage = null, $overrideAttrs = array(), Message $msg = null
00215     ) {
00216         global $wgLanguageCode;
00217 
00218         $include = $customisedOnly ? 'mwfile' : 'mw';
00219         $languages = Language::fetchLanguageNames( $inLanguage, $include );
00220 
00221         // Make sure the site language is in the list;
00222         // a custom language code might not have a defined name...
00223         if ( !array_key_exists( $wgLanguageCode, $languages ) ) {
00224             $languages[$wgLanguageCode] = $wgLanguageCode;
00225         }
00226 
00227         ksort( $languages );
00228 
00234         $selected = isset( $languages[$selected] ) ? $selected : $wgLanguageCode;
00235         $options = "\n";
00236         foreach ( $languages as $code => $name ) {
00237             $options .= Xml::option( "$code - $name", $code, $code == $selected ) . "\n";
00238         }
00239 
00240         $attrs = array( 'id' => 'wpUserLanguage', 'name' => 'wpUserLanguage' );
00241         $attrs = array_merge( $attrs, $overrideAttrs );
00242 
00243         if ( $msg === null ) {
00244             $msg = wfMessage( 'yourlanguage' );
00245         }
00246         return array(
00247             Xml::label( $msg->text(), $attrs['id'] ),
00248             Xml::tags( 'select', $attrs, $options )
00249         );
00250 
00251     }
00252 
00260     public static function span( $text, $class, $attribs = array() ) {
00261         return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
00262     }
00263 
00272     public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
00273         return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
00274     }
00275 
00284     public static function input( $name, $size = false, $value = false, $attribs = array() ) {
00285         $attributes = array( 'name' => $name );
00286 
00287         if ( $size ) {
00288             $attributes['size'] = $size;
00289         }
00290 
00291         if ( $value !== false ) { // maybe 0
00292             $attributes['value'] = $value;
00293         }
00294 
00295         return self::element( 'input',
00296             Html::getTextInputAttributes( $attributes + $attribs ) );
00297     }
00298 
00307     public static function password( $name, $size = false, $value = false,
00308         $attribs = array()
00309     ) {
00310         return self::input( $name, $size, $value,
00311             array_merge( $attribs, array( 'type' => 'password' ) ) );
00312     }
00313 
00322     public static function attrib( $name, $present = true ) {
00323         return $present ? array( $name => $name ) : array();
00324     }
00325 
00333     public static function check( $name, $checked = false, $attribs = array() ) {
00334         return self::element( 'input', array_merge(
00335             array(
00336                 'name' => $name,
00337                 'type' => 'checkbox',
00338                 'value' => 1 ),
00339             self::attrib( 'checked', $checked ),
00340             $attribs ) );
00341     }
00342 
00351     public static function radio( $name, $value, $checked = false, $attribs = array() ) {
00352         return self::element( 'input', array(
00353             'name' => $name,
00354             'type' => 'radio',
00355             'value' => $value ) + self::attrib( 'checked', $checked ) + $attribs );
00356     }
00357 
00368     public static function label( $label, $id, $attribs = array() ) {
00369         $a = array( 'for' => $id );
00370 
00371         # FIXME avoid copy pasting below:
00372         if ( isset( $attribs['class'] ) ) {
00373                 $a['class'] = $attribs['class'];
00374         }
00375         if ( isset( $attribs['title'] ) ) {
00376                 $a['title'] = $attribs['title'];
00377         }
00378 
00379         return self::element( 'label', $a, $label );
00380     }
00381 
00392     public static function inputLabel( $label, $name, $id, $size = false,
00393         $value = false, $attribs = array()
00394     ) {
00395         list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
00396         return $label . '&#160;' . $input;
00397     }
00398 
00411     public static function inputLabelSep( $label, $name, $id, $size = false,
00412         $value = false, $attribs = array()
00413     ) {
00414         return array(
00415             Xml::label( $label, $id, $attribs ),
00416             self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
00417         );
00418     }
00419 
00431     public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
00432         global $wgUseMediaWikiUIEverywhere;
00433         $chkLabel = self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
00434             '&#160;' .
00435             self::label( $label, $id, $attribs );
00436 
00437         if ( $wgUseMediaWikiUIEverywhere ) {
00438             $chkLabel = self::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
00439                 $chkLabel . self::closeElement( 'div' );
00440         }
00441         return $chkLabel;
00442     }
00443 
00456     public static function radioLabel( $label, $name, $value, $id,
00457         $checked = false, $attribs = array()
00458     ) {
00459         return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
00460             '&#160;' .
00461             self::label( $label, $id, $attribs );
00462     }
00463 
00471     public static function submitButton( $value, $attribs = array() ) {
00472         global $wgUseMediaWikiUIEverywhere;
00473         $baseAttrs = array(
00474             'type' => 'submit',
00475             'value' => $value,
00476         );
00477         // Done conditionally for time being as it is possible
00478         // some submit forms
00479         // might need to be mw-ui-destructive (e.g. delete a page)
00480         if ( $wgUseMediaWikiUIEverywhere ) {
00481             $baseAttrs['class'] = 'mw-ui-button mw-ui-constructive';
00482         }
00483         // Any custom attributes will take precendence of anything in baseAttrs e.g. override the class
00484         $attribs = $attribs + $baseAttrs;
00485         return Html::element( 'input', $attribs );
00486     }
00487 
00496     public static function option( $text, $value = null, $selected = false,
00497             $attribs = array() ) {
00498         if ( !is_null( $value ) ) {
00499             $attribs['value'] = $value;
00500         }
00501         if ( $selected ) {
00502             $attribs['selected'] = 'selected';
00503         }
00504         return Html::element( 'option', $attribs, $text );
00505     }
00506 
00519     public static function listDropDown( $name = '', $list = '', $other = '',
00520         $selected = '', $class = '', $tabindex = null
00521     ) {
00522         $optgroup = false;
00523 
00524         $options = self::option( $other, 'other', $selected === 'other' );
00525 
00526         foreach ( explode( "\n", $list ) as $option ) {
00527             $value = trim( $option );
00528             if ( $value == '' ) {
00529                 continue;
00530             } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
00531                 // A new group is starting ...
00532                 $value = trim( substr( $value, 1 ) );
00533                 if ( $optgroup ) {
00534                     $options .= self::closeElement( 'optgroup' );
00535                 }
00536                 $options .= self::openElement( 'optgroup', array( 'label' => $value ) );
00537                 $optgroup = true;
00538             } elseif ( substr( $value, 0, 2 ) == '**' ) {
00539                 // groupmember
00540                 $value = trim( substr( $value, 2 ) );
00541                 $options .= self::option( $value, $value, $selected === $value );
00542             } else {
00543                 // groupless reason list
00544                 if ( $optgroup ) {
00545                     $options .= self::closeElement( 'optgroup' );
00546                 }
00547                 $options .= self::option( $value, $value, $selected === $value );
00548                 $optgroup = false;
00549             }
00550         }
00551 
00552         if ( $optgroup ) {
00553             $options .= self::closeElement( 'optgroup' );
00554         }
00555 
00556         $attribs = array();
00557 
00558         if ( $name ) {
00559             $attribs['id'] = $name;
00560             $attribs['name'] = $name;
00561         }
00562 
00563         if ( $class ) {
00564             $attribs['class'] = $class;
00565         }
00566 
00567         if ( $tabindex ) {
00568             $attribs['tabindex'] = $tabindex;
00569         }
00570 
00571         return Xml::openElement( 'select', $attribs )
00572             . "\n"
00573             . $options
00574             . "\n"
00575             . Xml::closeElement( 'select' );
00576     }
00577 
00589     public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
00590         $s = Xml::openElement( 'fieldset', $attribs ) . "\n";
00591 
00592         if ( $legend ) {
00593             $s .= Xml::element( 'legend', null, $legend ) . "\n";
00594         }
00595 
00596         if ( $content !== false ) {
00597             $s .= $content . "\n";
00598             $s .= Xml::closeElement( 'fieldset' ) . "\n";
00599         }
00600 
00601         return $s;
00602     }
00603 
00615     public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
00616         return self::element( 'textarea',
00617                     Html::getTextInputAttributes(
00618                         array(
00619                             'name' => $name,
00620                             'id' => $name,
00621                             'cols' => $cols,
00622                             'rows' => $rows
00623                         ) + $attribs
00624                     ),
00625                     $content, false );
00626     }
00627 
00637     public static function escapeJsString( $string ) {
00638         // See ECMA 262 section 7.8.4 for string literal format
00639         $pairs = array(
00640             "\\" => "\\\\",
00641             "\"" => "\\\"",
00642             '\'' => '\\\'',
00643             "\n" => "\\n",
00644             "\r" => "\\r",
00645 
00646             # To avoid closing the element or CDATA section
00647             "<" => "\\x3c",
00648             ">" => "\\x3e",
00649 
00650             # To avoid any complaints about bad entity refs
00651             "&" => "\\x26",
00652 
00653             # Work around https://bugzilla.mozilla.org/show_bug.cgi?id=274152
00654             # Encode certain Unicode formatting chars so affected
00655             # versions of Gecko don't misinterpret our strings;
00656             # this is a common problem with Farsi text.
00657             "\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
00658             "\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
00659         );
00660 
00661         return strtr( $string, $pairs );
00662     }
00663 
00675     public static function encodeJsVar( $value, $pretty = false ) {
00676         if ( $value instanceof XmlJsCode ) {
00677             return $value->value;
00678         }
00679         return FormatJson::encode( $value, $pretty, FormatJson::UTF8_OK );
00680     }
00681 
00693     public static function encodeJsCall( $name, $args, $pretty = false ) {
00694         foreach ( $args as &$arg ) {
00695             $arg = Xml::encodeJsVar( $arg, $pretty );
00696             if ( $arg === false ) {
00697                 return false;
00698             }
00699         }
00700 
00701         return "$name(" . ( $pretty
00702             ? ( ' ' . implode( ', ', $args ) . ' ' )
00703             : implode( ',', $args )
00704         ) . ");";
00705     }
00706 
00716     public static function isWellFormed( $text ) {
00717         $parser = xml_parser_create( "UTF-8" );
00718 
00719         # case folding violates XML standard, turn it off
00720         xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
00721 
00722         if ( !xml_parse( $parser, $text, true ) ) {
00723             //$err = xml_error_string( xml_get_error_code( $parser ) );
00724             //$position = xml_get_current_byte_index( $parser );
00725             //$fragment = $this->extractFragment( $html, $position );
00726             //$this->mXmlError = "$err at byte $position:\n$fragment";
00727             xml_parser_free( $parser );
00728             return false;
00729         }
00730 
00731         xml_parser_free( $parser );
00732 
00733         return true;
00734     }
00735 
00744     public static function isWellFormedXmlFragment( $text ) {
00745         $html =
00746             Sanitizer::hackDocType() .
00747             '<html>' .
00748             $text .
00749             '</html>';
00750 
00751         return Xml::isWellFormed( $html );
00752     }
00753 
00761     public static function escapeTagsOnly( $in ) {
00762         return str_replace(
00763             array( '"', '>', '<' ),
00764             array( '&quot;', '&gt;', '&lt;' ),
00765             $in );
00766     }
00767 
00779     public static function buildForm( $fields, $submitLabel = null, $submitAttribs = array() ) {
00780         $form = '';
00781         $form .= "<table><tbody>";
00782 
00783         foreach ( $fields as $labelmsg => $input ) {
00784             $id = "mw-$labelmsg";
00785             $form .= Xml::openElement( 'tr', array( 'id' => $id ) );
00786 
00787             // TODO use a <label> here for accessibility purposes - will need
00788             // to either not use a table to build the form, or find the ID of
00789             // the input somehow.
00790 
00791             $form .= Xml::tags( 'td', array( 'class' => 'mw-label' ), wfMessage( $labelmsg )->parse() );
00792             $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) )
00793                 . $input . Xml::closeElement( 'td' );
00794             $form .= Xml::closeElement( 'tr' );
00795         }
00796 
00797         if ( $submitLabel ) {
00798             $form .= Xml::openElement( 'tr' );
00799             $form .= Xml::tags( 'td', array(), '' );
00800             $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) )
00801                 . Xml::submitButton( wfMessage( $submitLabel )->text(), $submitAttribs )
00802                 . Xml::closeElement( 'td' );
00803             $form .= Xml::closeElement( 'tr' );
00804         }
00805 
00806         $form .= "</tbody></table>";
00807 
00808         return $form;
00809     }
00810 
00818     public static function buildTable( $rows, $attribs = array(), $headers = null ) {
00819         $s = Xml::openElement( 'table', $attribs );
00820 
00821         if ( is_array( $headers ) ) {
00822             $s .= Xml::openElement( 'thead', $attribs );
00823 
00824             foreach ( $headers as $id => $header ) {
00825                 $attribs = array();
00826 
00827                 if ( is_string( $id ) ) {
00828                     $attribs['id'] = $id;
00829                 }
00830 
00831                 $s .= Xml::element( 'th', $attribs, $header );
00832             }
00833             $s .= Xml::closeElement( 'thead' );
00834         }
00835 
00836         foreach ( $rows as $id => $row ) {
00837             $attribs = array();
00838 
00839             if ( is_string( $id ) ) {
00840                 $attribs['id'] = $id;
00841             }
00842 
00843             $s .= Xml::buildTableRow( $attribs, $row );
00844         }
00845 
00846         $s .= Xml::closeElement( 'table' );
00847 
00848         return $s;
00849     }
00850 
00857     public static function buildTableRow( $attribs, $cells ) {
00858         $s = Xml::openElement( 'tr', $attribs );
00859 
00860         foreach ( $cells as $id => $cell ) {
00861             $attribs = array();
00862 
00863             if ( is_string( $id ) ) {
00864                 $attribs['id'] = $id;
00865             }
00866 
00867             $s .= Xml::element( 'td', $attribs, $cell );
00868         }
00869 
00870         $s .= Xml::closeElement( 'tr' );
00871 
00872         return $s;
00873     }
00874 }
00875 
00876 class XmlSelect {
00877     protected $options = array();
00878     protected $default = false;
00879     protected $attributes = array();
00880 
00881     public function __construct( $name = false, $id = false, $default = false ) {
00882         if ( $name ) {
00883             $this->setAttribute( 'name', $name );
00884         }
00885 
00886         if ( $id ) {
00887             $this->setAttribute( 'id', $id );
00888         }
00889 
00890         if ( $default !== false ) {
00891             $this->default = $default;
00892         }
00893     }
00894 
00898     public function setDefault( $default ) {
00899         $this->default = $default;
00900     }
00901 
00906     public function setAttribute( $name, $value ) {
00907         $this->attributes[$name] = $value;
00908     }
00909 
00914     public function getAttribute( $name ) {
00915         if ( isset( $this->attributes[$name] ) ) {
00916             return $this->attributes[$name];
00917         } else {
00918             return null;
00919         }
00920     }
00921 
00926     public function addOption( $name, $value = false ) {
00927         // Stab stab stab
00928         $value = $value !== false ? $value : $name;
00929 
00930         $this->options[] = array( $name => $value );
00931     }
00932 
00940     public function addOptions( $options ) {
00941         $this->options[] = $options;
00942     }
00943 
00953     static function formatOptions( $options, $default = false ) {
00954         $data = '';
00955 
00956         foreach ( $options as $label => $value ) {
00957             if ( is_array( $value ) ) {
00958                 $contents = self::formatOptions( $value, $default );
00959                 $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
00960             } else {
00961                 $data .= Xml::option( $label, $value, $value === $default ) . "\n";
00962             }
00963         }
00964 
00965         return $data;
00966     }
00967 
00971     public function getHTML() {
00972         $contents = '';
00973 
00974         foreach ( $this->options as $options ) {
00975             $contents .= self::formatOptions( $options, $this->default );
00976         }
00977 
00978         return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
00979     }
00980 }
00981 
00999 class XmlJsCode {
01000     public $value;
01001 
01002     function __construct( $value ) {
01003         $this->value = $value;
01004     }
01005 }