diff options
Diffstat (limited to 'includes')
233 files changed, 10573 insertions, 6034 deletions
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index 39ec19f8..ca129029 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -1,7 +1,8 @@ <?php -if( !defined( 'MEDIAWIKI' ) ) - die( 1 ); +if( !defined( 'MEDIAWIKI' ) ) { + die( 1 ); +} if ( ! $wgUseAjax ) { die( 1 ); @@ -9,12 +10,16 @@ if ( ! $wgUseAjax ) { require_once( 'AjaxFunctions.php' ); +/** + * Object-Oriented Ajax functions. + * @addtogroup Ajax + */ class AjaxDispatcher { var $mode; var $func_name; var $args; - function AjaxDispatcher() { + function __construct() { wfProfileIn( __METHOD__ ); $this->mode = ""; @@ -28,14 +33,14 @@ class AjaxDispatcher { } if ($this->mode == "get") { - $this->func_name = $_GET["rs"]; + $this->func_name = isset( $_GET["rs"] ) ? $_GET["rs"] : ''; if (! empty($_GET["rsargs"])) { $this->args = $_GET["rsargs"]; } else { $this->args = array(); } } else { - $this->func_name = $_POST["rs"]; + $this->func_name = isset( $_POST["rs"] ) ? $_POST["rs"] : ''; if (! empty($_POST["rsargs"])) { $this->args = $_POST["rsargs"]; } else { @@ -47,7 +52,7 @@ class AjaxDispatcher { function performAction() { global $wgAjaxExportList, $wgOut; - + if ( empty( $this->mode ) ) { return; } @@ -59,7 +64,7 @@ class AjaxDispatcher { } else { try { $result = call_user_func_array($this->func_name, $this->args); - + if ( $result === false || $result === NULL ) { wfHttpError( 500, 'Internal Error', "{$this->func_name} returned no data" ); @@ -68,7 +73,7 @@ class AjaxDispatcher { if ( is_string( $result ) ) { $result= new AjaxResponse( $result ); } - + $result->sendHeaders(); $result->printText(); } @@ -82,7 +87,7 @@ class AjaxDispatcher { } } } - + wfProfileOut( __METHOD__ ); $wgOut = null; } diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php index eee2a1a4..86f853db 100644 --- a/includes/AjaxFunctions.php +++ b/includes/AjaxFunctions.php @@ -1,7 +1,13 @@ <?php -if( !defined( 'MEDIAWIKI' ) ) - die( 1 ); +/** + * @package MediaWiki + * @addtogroup Ajax + */ + +if( !defined( 'MEDIAWIKI' ) ) { + die( 1 ); +} /** * Function converts an Javascript escaped string back into a string with @@ -13,40 +19,39 @@ if( !defined( 'MEDIAWIKI' ) ) * @return string */ function js_unescape($source, $iconv_to = 'UTF-8') { - $decodedStr = ''; - $pos = 0; - $len = strlen ($source); - while ($pos < $len) { - $charAt = substr ($source, $pos, 1); - if ($charAt == '%') { - $pos++; - $charAt = substr ($source, $pos, 1); - if ($charAt == 'u') { - // we got a unicode character - $pos++; - $unicodeHexVal = substr ($source, $pos, 4); - $unicode = hexdec ($unicodeHexVal); - $decodedStr .= code2utf($unicode); - $pos += 4; - } - else { - // we have an escaped ascii character - $hexVal = substr ($source, $pos, 2); - $decodedStr .= chr (hexdec ($hexVal)); - $pos += 2; - } - } - else { - $decodedStr .= $charAt; - $pos++; - } - } - - if ($iconv_to != "UTF-8") { - $decodedStr = iconv("UTF-8", $iconv_to, $decodedStr); - } - - return $decodedStr; + $decodedStr = ''; + $pos = 0; + $len = strlen ($source); + + while ($pos < $len) { + $charAt = substr ($source, $pos, 1); + if ($charAt == '%') { + $pos++; + $charAt = substr ($source, $pos, 1); + if ($charAt == 'u') { + // we got a unicode character + $pos++; + $unicodeHexVal = substr ($source, $pos, 4); + $unicode = hexdec ($unicodeHexVal); + $decodedStr .= code2utf($unicode); + $pos += 4; + } else { + // we have an escaped ascii character + $hexVal = substr ($source, $pos, 2); + $decodedStr .= chr (hexdec ($hexVal)); + $pos += 2; + } + } else { + $decodedStr .= $charAt; + $pos++; + } + } + + if ($iconv_to != "UTF-8") { + $decodedStr = iconv("UTF-8", $iconv_to, $decodedStr); + } + + return $decodedStr; } /** @@ -71,7 +76,7 @@ function code2utf($num){ function wfSajaxSearch( $term ) { global $wgContLang, $wgOut; $limit = 16; - + $l = new Linker; $term = str_replace( ' ', '_', $wgContLang->ucfirst( @@ -81,7 +86,7 @@ function wfSajaxSearch( $term ) { if ( strlen( str_replace( '_', '', $term ) )<3 ) return; - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $res = $db->select( 'page', 'page_title', array( 'page_namespace' => 0, "page_title LIKE '". $db->strencode( $term) ."%'" ), @@ -108,8 +113,8 @@ function wfSajaxSearch( $term ) { $subtitlemsg = ( Title::newFromText($term) ? 'searchsubtitle' : 'searchsubtitleinvalid' ); $subtitle = $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ); #FIXME: parser is missing mTitle ! - $term = htmlspecialchars( $term ); - $html = '<div style="float:right; border:solid 1px black;background:gainsboro;padding:2px;"><a onclick="Searching_Hide_Results();">' + $term = urlencode( $term ); + $html = '<div style="float:right; border:solid 1px black;background:gainsboro;padding:2px;"><a onclick="Searching_Hide_Results();">' . wfMsg( 'hideresults' ) . '</a></div>' . '<h1 class="firstHeading">'.wfMsg('search') . '</h1><div id="contentSub">'. $subtitle . '</div><ul><li>' @@ -121,11 +126,11 @@ function wfSajaxSearch( $term ) { "search=$term&go=Go" ) . "</li></ul><h2>" . wfMsg( 'articletitles', $term ) . "</h2>" . '<ul>' .$r .'</ul>'.$more; - + $response = new AjaxResponse( $html ); - + $response->setCacheDuration( 30*60 ); - + return $response; } @@ -152,14 +157,14 @@ function wfAjaxWatch($pageID = "", $watch = "") { if($watch) { if(!$watching) { - $dbw =& wfGetDB(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); $dbw->begin(); $article->doWatch(); $dbw->commit(); } } else { if($watching) { - $dbw =& wfGetDB(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); $dbw->begin(); $article->doUnwatch(); $dbw->commit(); diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index a59c73bb..cb4af1b5 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -1,28 +1,32 @@ <?php +if( !defined( 'MEDIAWIKI' ) ) { + die( 1 ); +} -if( !defined( 'MEDIAWIKI' ) ) - die( 1 ); - +/** + * @todo document + * @addtogroup Ajax + */ class AjaxResponse { var $mCacheDuration; var $mVary; - + var $mDisabled; var $mText; var $mResponseCode; var $mLastModified; var $mContentType; - function AjaxResponse( $text = NULL ) { + function __construct( $text = NULL ) { $this->mCacheDuration = NULL; $this->mVary = NULL; - + $this->mDisabled = false; $this->mText = ''; $this->mResponseCode = '200 OK'; $this->mLastModified = false; $this->mContentType= 'text/html; charset=utf-8'; - + if ( $text ) { $this->addText( $text ); } @@ -39,15 +43,15 @@ class AjaxResponse { function setResponseCode( $code ) { $this->mResponseCode = $code; } - + function setContentType( $type ) { $this->mContentType = $type; } - + function disable() { $this->mDisabled = true; } - + function addText( $text ) { if ( ! $this->mDisabled && $text ) { $this->mText .= $text; @@ -59,62 +63,62 @@ class AjaxResponse { print $this->mText; } } - + function sendHeaders() { global $wgUseSquid, $wgUseESI; - + if ( $this->mResponseCode ) { $n = preg_replace( '/^ *(\d+)/', '\1', $this->mResponseCode ); header( "Status: " . $this->mResponseCode, true, (int)$n ); } - + header ("Content-Type: " . $this->mContentType ); - + if ( $this->mLastModified ) { header ("Last-Modified: " . $this->mLastModified ); } else { header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); } - + if ( $this->mCacheDuration ) { - + # If squid caches are configured, tell them to cache the response, # and tell the client to always check with the squid. Otherwise, # tell the client to use a cached copy, without a way to purge it. - + if( $wgUseSquid ) { - + # Expect explicite purge of the proxy cache, but require end user agents # to revalidate against the proxy on each visit. # Surrogate-Control controls our Squid, Cache-Control downstream caches - + if ( $wgUseESI ) { header( 'Surrogate-Control: max-age='.$this->mCacheDuration.', content="ESI/1.0"'); header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); } else { header( 'Cache-Control: s-maxage='.$this->mCacheDuration.', must-revalidate, max-age=0' ); } - + } else { - + # Let the client do the caching. Cache is not purged. header ("Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT"); header ("Cache-Control: s-max-age={$this->mCacheDuration},public,max-age={$this->mCacheDuration}"); } - + } else { # always expired, always modified header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past header ("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header ("Pragma: no-cache"); // HTTP/1.0 } - + if ( $this->mVary ) { header ( "Vary: " . $this->mVary ); } } - + /** * checkLastModified tells the client to use the client-cached response if * possible. If sucessful, the AjaxResponse is disabled so that @@ -154,9 +158,9 @@ class AjaxResponse { $this->setResponseCode( "304 Not Modified" ); $this->disable(); $this->mLastModified = $lastmod; - + wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false ); - + return true; } else { wfDebug( "$fname: READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false ); @@ -167,11 +171,11 @@ class AjaxResponse { $this->mLastModified = $lastmod; } } - + function loadFromMemcached( $mckey, $touched ) { global $wgMemc; if ( !$touched ) return false; - + $mcvalue = $wgMemc->get( $mckey ); if ( $mcvalue ) { # Check to see if the value has been invalidated @@ -183,20 +187,20 @@ class AjaxResponse { wfDebug( "$mckey has expired\n" ); } } - + return false; } - + function storeInMemcached( $mckey, $expiry = 86400 ) { global $wgMemc; - - $wgMemc->set( $mckey, + + $wgMemc->set( $mckey, array( 'timestamp' => wfTimestampNow(), 'value' => $this->mText ), $expiry ); - + return true; } } diff --git a/includes/Article.php b/includes/Article.php index 6b4f5270..0130ceba 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1,7 +1,6 @@ <?php /** * File for articles - * @package MediaWiki */ /** @@ -11,7 +10,6 @@ * Note: edit user interface and cache support functions have been * moved to separate EditPage and HTMLFileCache classes. * - * @package MediaWiki */ class Article { /**@{{ @@ -43,7 +41,7 @@ class Article { * @param $title Reference to a Title object. * @param $oldId Integer revision ID, null to fetch from request, zero for current */ - function Article( &$title, $oldId = null ) { + function __construct( &$title, $oldId = null ) { $this->mTitle =& $title; $this->mOldId = $oldId; $this->clear(); @@ -57,14 +55,14 @@ class Article { function setRedirectedFrom( $from ) { $this->mRedirectedFrom = $from; } - + /** * @return mixed false, Title of in-wiki target, or string with URL */ function followRedirect() { $text = $this->getContent(); $rt = Title::newFromRedirect( $text ); - + # process if title object is valid and not special:userlogout if( $rt ) { if( $rt->getInterwiki() != '' ) { @@ -73,7 +71,7 @@ class Article { // // This can be hard to reverse and may produce loops, // so they may be disabled in the site configuration. - + $source = $this->mTitle->getFullURL( 'redirect=no' ); return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) ); } @@ -84,7 +82,7 @@ class Article { // the rest of the page we're on. // // This can be hard to reverse, so they may be disabled. - + if( $rt->isSpecial( 'Userlogout' ) ) { // rolleyes } else { @@ -94,7 +92,7 @@ class Article { return $rt; } } - + // No or invalid redirect return false; } @@ -247,7 +245,7 @@ class Article { * @param array $conditions * @private */ - function pageData( &$dbr, $conditions ) { + function pageData( $dbr, $conditions ) { $fields = array( 'page_id', 'page_namespace', @@ -273,7 +271,7 @@ class Article { * @param Database $dbr * @param Title $title */ - function pageDataFromTitle( &$dbr, $title ) { + function pageDataFromTitle( $dbr, $title ) { return $this->pageData( $dbr, array( 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey() ) ); @@ -283,7 +281,7 @@ class Article { * @param Database $dbr * @param int $id */ - function pageDataFromId( &$dbr, $id ) { + function pageDataFromId( $dbr, $id ) { return $this->pageData( $dbr, array( 'page_id' => $id ) ); } @@ -296,17 +294,18 @@ class Article { */ function loadPageData( $data = 'fromdb' ) { if ( $data === 'fromdb' ) { - $dbr =& $this->getDB(); + $dbr = $this->getDB(); $data = $this->pageDataFromId( $dbr, $this->getId() ); } - + $lc =& LinkCache::singleton(); if ( $data ) { $lc->addGoodLinkObj( $data->page_id, $this->mTitle ); $this->mTitle->mArticleID = $data->page_id; + + # Old-fashioned restrictions. $this->mTitle->loadRestrictions( $data->page_restrictions ); - $this->mTitle->mRestrictionsLoaded = true; $this->mCounter = $data->page_counter; $this->mTouched = wfTimestamp( TS_MW, $data->page_touched ); @@ -333,7 +332,7 @@ class Article { return $this->mContent; } - $dbr =& $this->getDB(); + $dbr = $this->getDB(); # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. @@ -405,9 +404,8 @@ class Article { * * @return Database */ - function &getDB() { - $ret =& wfGetDB( DB_MASTER ); - return $ret; + function getDB() { + return wfGetDB( DB_MASTER ); } /** @@ -455,7 +453,7 @@ class Article { if ( $id == 0 ) { $this->mCounter = 0; } else { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $this->mCounter = $dbr->selectField( 'page', 'page_counter', array( 'page_id' => $id ), 'Article::getCount', $this->getSelectOptions() ); } @@ -471,12 +469,12 @@ class Article { * @return bool */ function isCountable( $text ) { - global $wgUseCommaCount, $wgContentNamespaces; + global $wgUseCommaCount; $token = $wgUseCommaCount ? ',' : '[['; return - array_search( $this->mTitle->getNamespace(), $wgContentNamespaces ) !== false - && ! $this->isRedirect( $text ) + $this->mTitle->isContentPage() + && !$this->isRedirect( $text ) && in_string( $token, $text ); } @@ -573,7 +571,7 @@ class Article { # XXX: this is expensive; cache this info somewhere. $contribs = array(); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $revTable = $dbr->tableName( 'revision' ); $userTable = $dbr->tableName( 'user' ); $user = $this->getUser(); @@ -613,7 +611,7 @@ class Article { $parserCache =& ParserCache::singleton(); $ns = $this->mTitle->getNamespace(); # shortcut - + # Get variables from query string $oldid = $this->getOldID(); @@ -627,16 +625,21 @@ class Article { $diff = $wgRequest->getVal( 'diff' ); $rcid = $wgRequest->getVal( 'rcid' ); $rdfrom = $wgRequest->getVal( 'rdfrom' ); + $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) ); $wgOut->setArticleFlag( true ); - if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) { + + # Discourage indexing of printable versions, but encourage following + if( $wgOut->isPrintable() ) { + $policy = 'noindex,follow'; + } elseif( isset( $wgNamespaceRobotPolicies[$ns] ) ) { + # Honour customised robot policies for this namespace $policy = $wgNamespaceRobotPolicies[$ns]; } else { - # The default policy. Dev note: make sure you change the documentation - # in DefaultSettings.php before changing it. + # Default to encourage indexing and following links $policy = 'index,follow'; } - $wgOut->setRobotpolicy( $policy ); + $wgOut->setRobotPolicy( $policy ); # If we got diff and oldid in the query, we want to see a # diff page instead of the article. @@ -647,8 +650,8 @@ class Article { $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid ); // DifferenceEngine directly fetched the revision: $this->mRevIdFetched = $de->mNewid; - $de->showDiffPage(); - + $de->showDiffPage( $diffOnly ); + // Needed to get the page's current revision $this->loadPageData(); if( $diff == 0 || $diff == $this->mLatest ) { @@ -658,7 +661,7 @@ class Article { wfProfileOut( __METHOD__ ); return; } - + if ( empty( $oldid ) && $this->checkTouched() ) { $wgOut->setETag($parserCache->getETag($this, $wgUser)); @@ -713,11 +716,11 @@ class Article { $wasRedirected = true; } } - + $outputDone = false; + wfRunHooks( 'ArticleViewHeader', array( &$this ) ); if ( $pcache ) { if ( $wgOut->tryParserCache( $this, $wgUser ) ) { - wfRunHooks( 'ArticleViewHeader', array( &$this ) ); $outputDone = true; } } @@ -764,17 +767,12 @@ class Article { } } if( !$outputDone ) { - /** - * @fixme: this hook doesn't work most of the time, as it doesn't - * trigger when the parser cache is used. - */ - wfRunHooks( 'ArticleViewHeader', array( &$this ) ) ; $wgOut->setRevisionId( $this->getRevIdFetched() ); # wrap user css and user js in pre and don't parse # XXX: use $this->mTitle->usCssJsSubpage() when php is fixed/ a workaround is found if ( $ns == NS_USER && - preg_match('/\\/[\\w]+\\.(css|js)$/', $this->mTitle->getDBkey()) + preg_match('/\\/[\\w]+\\.(?:css|js)$/', $this->mTitle->getDBkey()) ) { $wgOut->addWikiText( wfMsg('clearyourcache')); $wgOut->addHTML( '<pre>'.htmlspecialchars($this->mContent)."\n</pre>" ); @@ -795,7 +793,7 @@ class Article { $wgOut->addParserOutputNoText( $parseout ); } else if ( $pcache ) { # Display content and save to parser cache - $wgOut->addPrimaryWikiText( $text, $this ); + $this->outputWikiText( $text ); } else { # Display content, don't attempt to save to parser cache # Don't show section-edit links on old revisions... this way lies madness. @@ -803,11 +801,21 @@ class Article { $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false ); } # Display content and don't save to parser cache - $wgOut->addPrimaryWikiText( $text, $this, false ); + # With timing hack -- TS 2006-07-26 + $time = -wfTime(); + $this->outputWikiText( $text, false ); + $time += wfTime(); + + # Timing hack + if ( $time > 3 ) { + wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time, + $this->mTitle->getPrefixedDBkey())); + } if( !$this->isCurrent() ) { $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); } + } } /* title may have been set from the cache */ @@ -827,8 +835,9 @@ class Article { if ( $wgUseRCPatrol && !is_null( $rcid ) && $rcid != 0 && $wgUser->isAllowed( 'patrol' ) ) { $wgOut->addHTML( "<div class='patrollink'>" . - wfMsg ( 'markaspatrolledlink', - $sk->makeKnownLinkObj( $this->mTitle, wfMsg('markaspatrolledtext'), "action=markpatrolled&rcid=$rcid" ) + wfMsgHtml( 'markaspatrolledlink', + $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml('markaspatrolledtext'), + "action=markpatrolled&rcid=$rcid" ) ) . '</div>' ); @@ -845,7 +854,7 @@ class Article { function addTrackbacks() { global $wgOut, $wgUser; - $dbr =& wfGetDB(DB_SLAVE); + $dbr = wfGetDB(DB_SLAVE); $tbs = $dbr->select( /* FROM */ 'trackbacks', /* SELECT */ array('tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name'), @@ -891,7 +900,7 @@ class Article { return; } - $db =& wfGetDB(DB_MASTER); + $db = wfGetDB(DB_MASTER); $db->delete('trackbacks', array('tb_id' => $wgRequest->getInt('tbid'))); $wgTitle->invalidateCache(); $wgOut->addWikiText(wfMsg('trackbackdeleteok')); @@ -910,7 +919,7 @@ class Article { function purge() { global $wgUser, $wgRequest, $wgOut; - if ( $wgUser->isLoggedIn() || $wgRequest->wasPosted() ) { + if ( $wgUser->isAllowed( 'purge' ) || $wgRequest->wasPosted() ) { if( wfRunHooks( 'ArticlePurge', array( &$this ) ) ) { $this->doPurge(); } @@ -928,7 +937,7 @@ class Article { $wgOut->addHTML( $msg ); } } - + /** * Perform the actions of a page purging */ @@ -957,11 +966,10 @@ class Article { * Best if all done inside a transaction. * * @param Database $dbw - * @param string $restrictions * @return int The newly created page_id key * @private */ - function insertOn( &$dbw, $restrictions = '' ) { + function insertOn( $dbw ) { wfProfileIn( __METHOD__ ); $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' ); @@ -970,7 +978,7 @@ class Article { 'page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), 'page_counter' => 0, - 'page_restrictions' => $restrictions, + 'page_restrictions' => '', 'page_is_redirect' => 0, # Will set this shortly... 'page_is_new' => 1, 'page_random' => wfRandom(), @@ -996,7 +1004,7 @@ class Article { * when different from the currently set value. * Giving 0 indicates the new page flag should * be set on. - * @param bool $lastRevIsRedirect If given, will optimize adding and + * @param bool $lastRevIsRedirect If given, will optimize adding and * removing rows in redirect table. * @return bool true on success, false on failure * @private @@ -1006,7 +1014,7 @@ class Article { $text = $revision->getText(); $rt = Title::newFromRedirect( $text ); - + $conditions = array( 'page_id' => $this->getId() ); if( !is_null( $lastRevision ) ) { # An extra check against threads stepping on each other @@ -1028,20 +1036,20 @@ class Article { if ($result) { // FIXME: Should the result from updateRedirectOn() be returned instead? - $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); + $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); } - + wfProfileOut( __METHOD__ ); return $result; } /** - * Add row to the redirect table if this is a redirect, remove otherwise. + * Add row to the redirect table if this is a redirect, remove otherwise. * * @param Database $dbw * @param $redirectTitle a title object pointing to the redirect target, - * or NULL if this is not a redirect - * @param bool $lastRevIsRedirect If given, will optimize adding and + * or NULL if this is not a redirect + * @param bool $lastRevIsRedirect If given, will optimize adding and * removing rows in redirect table. * @return bool true on success, false on failure * @private @@ -1067,7 +1075,7 @@ class Article { $dbw->replace( 'redirect', array( 'rd_from' ), $set, __METHOD__ ); } else { - // This is not a redirect, remove row from redirect table + // This is not a redirect, remove row from redirect table $where = array( 'rd_from' => $this->getId() ); $dbw->delete( 'redirect', $where, __METHOD__); } @@ -1075,7 +1083,7 @@ class Article { wfProfileOut( __METHOD__ ); return ( $dbw->affectedRows() != 0 ); } - + return true; } @@ -1119,14 +1127,14 @@ class Article { */ function replaceSection($section, $text, $summary = '', $edittime = NULL) { wfProfileIn( __METHOD__ ); - + if( $section == '' ) { // Whole-page edit; let the text through unmolested. } else { if( is_null( $edittime ) ) { $rev = Revision::newFromTitle( $this->mTitle ); } else { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime ); } if( is_null( $rev ) ) { @@ -1166,10 +1174,10 @@ class Article { if ( $comment && $summary != "" ) { $text = "== {$summary} ==\n\n".$text; } - + $this->doEdit( $text, $summary, $flags ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); if ($watchthis) { if (!$this->mTitle->userIsWatching()) { $dbw->begin(); @@ -1196,7 +1204,7 @@ class Article { $good = $this->doEdit( $text, $summary, $flags ); if ( $good ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); if ($watchthis) { if (!$this->mTitle->userIsWatching()) { $dbw->begin(); @@ -1219,7 +1227,7 @@ class Article { /** * Article::doEdit() * - * Change an existing article or create a new article. Updates RC and all necessary caches, + * Change an existing article or create a new article. Updates RC and all necessary caches, * optionally via the deferred update array. * * $wgUser must be set before calling this function. @@ -1241,9 +1249,9 @@ class Article { * Defer some of the updates until the end of index.php * EDIT_AUTOSUMMARY * Fill in blank summaries with generated text where possible - * - * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. - * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If + * + * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. + * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception * to be thrown from the Database. These two conditions are also possible with auto-detection due * to MediaWiki's performance-optimised locking strategy. @@ -1267,7 +1275,7 @@ class Article { if( !wfRunHooks( 'ArticleSave', array( &$this, &$wgUser, &$text, &$summary, $flags & EDIT_MINOR, - null, null, &$flags ) ) ) + null, null, &$flags ) ) ) { wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" ); wfProfileOut( __METHOD__ ); @@ -1288,9 +1296,9 @@ class Article { $text = $this->preSaveTransform( $text ); $newsize = strlen( $text ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $now = wfTimestampNow(); - + if ( $flags & EDIT_UPDATE ) { # Update article, but only if changed. @@ -1316,7 +1324,7 @@ class Article { wfProfileOut( __METHOD__ ); return false; } - + $revision = new Revision( array( 'page' => $this->getId(), 'comment' => $summary, @@ -1340,10 +1348,11 @@ class Article { $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary, $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId ); - + # Mark as patrolled if the user can do so - if( $wgUser->isAllowed( 'autopatrol' ) ) { + if( $GLOBALS['wgUseRCPatrol'] && $wgUser->isAllowed( 'autopatrol' ) ) { RecentChange::markPatrolled( $rcid ); + PatrolLog::record( $rcid, true ); } } $wgUser->incEditCount(); @@ -1362,19 +1371,19 @@ class Article { } if ( $good ) { - # Invalidate cache of this article and all pages using this article + # Invalidate cache of this article and all pages using this article # as a template. Partly deferred. Article::onArticleEdit( $this->mTitle ); - + # Update links tables, site stats, etc. $changed = ( strcmp( $oldtext, $text ) != 0 ); $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed ); } } else { # Create new article - + # Set statistics members - # We work out if it's countable after PST to avoid counter drift + # We work out if it's countable after PST to avoid counter drift # when articles are created with {{subst:}} $this->mGoodAdjustment = (int)$this->isCountable( $text ); $this->mTotalAdjustment = 1; @@ -1403,8 +1412,9 @@ class Article { $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot, '', strlen( $text ), $revisionId ); # Mark as patrolled if the user can - if( $wgUser->isAllowed( 'autopatrol' ) ) { + if( $GLOBALS['wgUseRCPatrol'] && $wgUser->isAllowed( 'autopatrol' ) ) { RecentChange::markPatrolled( $rcid ); + PatrolLog::record( $rcid, true ); } } $wgUser->incEditCount(); @@ -1429,7 +1439,7 @@ class Article { array( &$this, &$wgUser, $text, $summary, $flags & EDIT_MINOR, null, null, &$flags ) ); - + wfProfileOut( __METHOD__ ); return $good; } @@ -1457,7 +1467,7 @@ class Article { } $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor ); } - + /** * Mark this particular edit as patrolled */ @@ -1470,25 +1480,25 @@ class Article { $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); return; } - + # Check permissions if( !$wgUser->isAllowed( 'patrol' ) ) { $wgOut->permissionRequired( 'patrol' ); return; } - + # If we haven't been given an rc_id value, we can't do anything $rcid = $wgRequest->getVal( 'rcid' ); if( !$rcid ) { $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); return; } - + # Handle the 'MarkPatrolled' hook if( !wfRunHooks( 'MarkPatrolled', array( $rcid, &$wgUser, false ) ) ) { return; } - + $return = SpecialPage::getTitleFor( 'Recentchanges' ); # If it's left up to us, check that the user is allowed to patrol this edit # If the user has the "autopatrol" right, then we'll assume there are no @@ -1507,11 +1517,12 @@ class Article { return; } } - + # Mark the edit as patrolled RecentChange::markPatrolled( $rcid ); + PatrolLog::record( $rcid ); wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); - + # Inform the user $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) ); $wgOut->addWikiText( wfMsgNoTrans( 'markedaspatrolledtext' ) ); @@ -1534,7 +1545,7 @@ class Article { $wgOut->readOnlyPage(); return; } - + if( $this->doWatch() ) { $wgOut->setPagetitle( wfMsg( 'addedwatch' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); @@ -1546,7 +1557,7 @@ class Article { $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() ); } - + /** * Add this page to $wgUser's watchlist * @return bool true on successful watch operation @@ -1556,13 +1567,13 @@ class Article { if( $wgUser->isAnon() ) { return false; } - + if (wfRunHooks('WatchArticle', array(&$wgUser, &$this))) { $wgUser->addWatch( $this->mTitle ); return wfRunHooks('WatchArticleComplete', array(&$wgUser, &$this)); } - + return false; } @@ -1581,7 +1592,7 @@ class Article { $wgOut->readOnlyPage(); return; } - + if( $this->doUnwatch() ) { $wgOut->setPagetitle( wfMsg( 'removedwatch' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); @@ -1593,7 +1604,7 @@ class Article { $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() ); } - + /** * Stop watching a page * @return bool true on successful unwatch @@ -1609,7 +1620,7 @@ class Article { return wfRunHooks('UnwatchArticleComplete', array(&$wgUser, &$this)); } - + return false; } @@ -1618,7 +1629,7 @@ class Article { */ function protect() { $form = new ProtectionForm( $this ); - $form->show(); + $form->execute(); } /** @@ -1635,14 +1646,21 @@ class Article { * @param string $reason * @return bool true on success */ - function updateRestrictions( $limit = array(), $reason = '' ) { + function updateRestrictions( $limit = array(), $reason = '', $cascade = 0, $expiry = null ) { global $wgUser, $wgRestrictionTypes, $wgContLang; - + $id = $this->mTitle->getArticleID(); if( !$wgUser->isAllowed( 'protect' ) || wfReadOnly() || $id == 0 ) { return false; } + if (!$cascade) { + $cascade = false; + } + + // Take this opportunity to purge out expired restrictions + Title::purgeExpiredRestrictions(); + # FIXME: Same limitations as described in ProtectionForm.php (line 37); # we expect a single selection, but the schema allows otherwise. $current = array(); @@ -1651,48 +1669,89 @@ class Article { $current = Article::flattenRestrictions( $current ); $updated = Article::flattenRestrictions( $limit ); - + $changed = ( $current != $updated ); + $changed = $changed || ($this->mTitle->areRestrictionsCascading() != $cascade); + $changed = $changed || ($this->mTitle->mRestrictionsExpiry != $expiry); $protect = ( $updated != '' ); - + # If nothing's changed, do nothing if( $changed ) { + global $wgGroupPermissions; if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) { - $dbw =& wfGetDB( DB_MASTER ); - + $dbw = wfGetDB( DB_MASTER ); + + $encodedExpiry = Block::encodeExpiry($expiry, $dbw ); + + $expiry_description = ''; + if ( $encodedExpiry != 'infinity' ) { + $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ) ).')'; + } + # Prepare a null revision to be added to the history $comment = $wgContLang->ucfirst( wfMsgForContent( $protect ? 'protectedarticle' : 'unprotectedarticle', $this->mTitle->getPrefixedText() ) ); + + foreach( $limit as $action => $restrictions ) { + # Check if the group level required to edit also can protect pages + # Otherwise, people who cannot normally protect can "protect" pages via transclusion + $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && $wgGroupPermissions[$restrictions]['protect'] ); + } + + $cascade_description = ''; + if ($cascade) { + $cascade_description = ' ['.wfMsg('protect-summary-cascade').']'; + } + if( $reason ) $comment .= ": $reason"; if( $protect ) $comment .= " [$updated]"; + if ( $expiry_description && $protect ) + $comment .= "$expiry_description"; + if ( $cascade ) + $comment .= "$cascade_description"; + $nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true ); $nullRevId = $nullRevision->insertOn( $dbw ); - + + # Update restrictions table + foreach( $limit as $action => $restrictions ) { + if ($restrictions != '' ) { + $dbw->replace( 'page_restrictions', array(array('pr_page', 'pr_type')), + array( 'pr_page' => $id, 'pr_type' => $action + , 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0 + , 'pr_expiry' => $encodedExpiry ), __METHOD__ ); + } else { + $dbw->delete( 'page_restrictions', array( 'pr_page' => $id, + 'pr_type' => $action ), __METHOD__ ); + } + } + # Update page record $dbw->update( 'page', array( /* SET */ 'page_touched' => $dbw->timestamp(), - 'page_restrictions' => $updated, + 'page_restrictions' => '', 'page_latest' => $nullRevId ), array( /* WHERE */ 'page_id' => $id ), 'Article::protect' ); wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) ); - + # Update the protection log $log = new LogPage( 'protect' ); + if( $protect ) { - $log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$updated]" ) ); + $log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) ); } else { $log->addEntry( 'unprotect', $this->mTitle, $reason ); } - + } # End hook } # End "changed" check - + return true; } @@ -1745,9 +1804,9 @@ class Article { } $wgOut->setPagetitle( wfMsg( 'confirmdelete' ) ); - + # Better double-check that it hasn't been deleted yet! - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $conds = $this->mTitle->pageCond(); $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ ); if ( $latest === false ) { @@ -1769,7 +1828,7 @@ class Article { # and insert a warning if it does $maxRevisions = 20; $authors = $this->getLastNAuthors( $maxRevisions, $latest ); - + if( count( $authors ) > 1 && !$confirm ) { $skin=$wgUser->getSkin(); $wgOut->addHTML( '<strong>' . wfMsg( 'historywarning' ) . ' ' . $skin->historyLink() . '</strong>' ); @@ -1813,7 +1872,7 @@ class Article { $reason = wfMsgForContent( 'exblank' ); } - if( $length < 500 && $reason === '' ) { + if( $reason === '' ) { # comment field=255, let's grep the first 150 to have some user # space left global $wgContLang; @@ -1849,7 +1908,7 @@ class Article { // First try the slave // If that doesn't have the latest revision, try the master $continue = 2; - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); do { $res = $db->select( array( 'page', 'revision' ), array( 'rev_id', 'rev_user_text' ), @@ -1868,7 +1927,7 @@ class Article { } $row = $db->fetchObject( $res ); if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); $continue--; } else { $continue = 0; @@ -1882,7 +1941,7 @@ class Article { wfProfileOut( __METHOD__ ); return $authors; } - + /** * Output deletion confirmation dialog */ @@ -1929,6 +1988,23 @@ class Article { </form>\n" ); $wgOut->returnToMain( false ); + + $this->showLogExtract( $wgOut ); + } + + + /** + * Fetch deletion log + */ + function showLogExtract( &$out ) { + # Show relevant lines from the deletion log: + $out->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" ); + $logViewer = new LogViewer( + new LogReader( + new FauxRequest( + array( 'page' => $this->mTitle->getPrefixedText(), + 'type' => 'delete' ) ) ) ); + $logViewer->showList( $out ); } @@ -1969,7 +2045,7 @@ class Article { wfDebug( __METHOD__."\n" ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $ns = $this->mTitle->getNamespace(); $t = $this->mTitle->getDBkey(); $id = $this->mTitle->getArticleID(); @@ -2004,12 +2080,16 @@ class Article { 'ar_text_id' => 'rev_text_id', 'ar_text' => '\'\'', // Be explicit to appease 'ar_flags' => '\'\'', // MySQL's "strict mode"... + 'ar_len' => 'rev_len' ), array( 'page_id' => $id, 'page_id = rev_page' ), __METHOD__ ); + # Delete restrictions for it + $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ ); + # Now that it's safely backed up, delete it $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__); @@ -2078,7 +2158,7 @@ class Article { $wgOut->addWikiText( wfMsg( 'sessionfailure' ) ); return; } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); # Enhanced rollback, marks edits rc_bot=1 $bot = $wgRequest->getBool( 'bot' ); @@ -2103,7 +2183,7 @@ class Article { if( $current->getComment() != '') { $wgOut->addHTML( wfMsg( 'editcomment', - htmlspecialchars( $current->getComment() ) ) ); + $wgUser->getSkin()->formatComment( $current->getComment() ) ) ); } return; } @@ -2189,7 +2269,7 @@ class Article { * Do standard deferred updates after page edit. * Update links tables, site stats, search index and message cache. * Every 1000th edit, prune the recent changes table. - * + * * @private * @param $text New text of the article * @param $summary Edit summary @@ -2222,7 +2302,7 @@ class Article { # Periodically flush old entries from the recentchanges table. global $wgRCMaxAge; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $cutoff = $dbw->timestamp( time() - $wgRCMaxAge ); $recentchanges = $dbw->tableName( 'recentchanges' ); $sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'"; @@ -2269,13 +2349,13 @@ class Article { wfProfileOut( __METHOD__ ); } - + /** * Perform article updates on a special page creation. * * @param Revision $rev * - * @fixme This is a shitty interface function. Kill it and replace the + * @todo This is a shitty interface function. Kill it and replace the * other shitty functions like editUpdates and such so it's not needed * anymore. */ @@ -2299,8 +2379,8 @@ class Article { global $wgLang, $wgOut, $wgUser; if ( !wfRunHooks( 'DisplayOldSubtitle', array(&$this, &$oldid) ) ) { - return; - } + return; + } $revision = Revision::newFromId( $oldid ); @@ -2326,10 +2406,10 @@ class Article { $nextdiff = $current ? wfMsg( 'diff' ) : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid ); - + $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() ) . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() ); - + $r = "\n\t\t\t\t<div id=\"mw-revision-info\">" . wfMsg( 'revision-info', $td, $userlinks ) . "</div>\n" . "\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; $wgOut->setSubtitle( $r ); @@ -2381,25 +2461,40 @@ class Article { * @return bool */ function isFileCacheable() { - global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest; + global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang; $action = $wgRequest->getVal( 'action' ); $oldid = $wgRequest->getVal( 'oldid' ); $diff = $wgRequest->getVal( 'diff' ); $redirect = $wgRequest->getVal( 'redirect' ); $printable = $wgRequest->getVal( 'printable' ); + $page = $wgRequest->getVal( 'page' ); + + //check for non-standard user language; this covers uselang, + //and extensions for auto-detecting user language. + $ulang = $wgLang->getCode(); + $clang = $wgContLang->getCode(); - return $wgUseFileCache - and (!$wgShowIPinHeader) - and ($this->getID() != 0) - and ($wgUser->isAnon()) - and (!$wgUser->getNewtalk()) - and ($this->mTitle->getNamespace() != NS_SPECIAL ) - and (empty( $action ) || $action == 'view') - and (!isset($oldid)) - and (!isset($diff)) - and (!isset($redirect)) - and (!isset($printable)) - and (!$this->mRedirectedFrom); + $cacheable = $wgUseFileCache + && (!$wgShowIPinHeader) + && ($this->getID() != 0) + && ($wgUser->isAnon()) + && (!$wgUser->getNewtalk()) + && ($this->mTitle->getNamespace() != NS_SPECIAL ) + && (empty( $action ) || $action == 'view') + && (!isset($oldid)) + && (!isset($diff)) + && (!isset($redirect)) + && (!isset($printable)) + && !isset($page) + && (!$this->mRedirectedFrom) + && ($ulang === $clang); + + if ( $cacheable ) { + //extension may have reason to disable file caching on some pages. + $cacheable = wfRunHooks( 'IsFileCacheable', array( $this ) ); + } + + return $cacheable; } /** @@ -2446,7 +2541,7 @@ class Article { function quickEdit( $text, $comment = '', $minor = 0 ) { wfProfileIn( __METHOD__ ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); $revision = new Revision( array( 'page' => $this->getId(), @@ -2471,7 +2566,7 @@ class Article { $id = intval( $id ); global $wgHitcounterUpdateFreq, $wgDBtype; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $pageTable = $dbw->tableName( 'page' ); $hitcounterTable = $dbw->tableName( 'hitcounter' ); $acchitsTable = $dbw->tableName( 'acchits' ); @@ -2555,7 +2650,7 @@ class Article { $title->touchLinks(); $title->purgeSquid(); - + # File cache if ( $wgUseFileCache ) { $cm = new HTMLFileCache( $title ); @@ -2617,7 +2712,7 @@ class Article { $wgOut->addHTML(wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' ) ); } } else { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $wl_clause = array( 'wl_title' => $page->getDBkey(), 'wl_namespace' => $page->getNamespace() ); @@ -2659,7 +2754,7 @@ class Article { return false; } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $rev_clause = array( 'rev_page' => $id ); @@ -2693,7 +2788,7 @@ class Article { return array(); } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( array( 'templatelinks' ), array( 'tl_namespace', 'tl_title' ), array( 'tl_from' => $id ), @@ -2708,7 +2803,7 @@ class Article { $dbr->freeResult( $res ); return $result; } - + /** * Return an auto-generated summary if the text provided is a redirect. * @@ -2785,6 +2880,84 @@ class Article { return $summary; } + + /** + * Add the primary page-view wikitext to the output buffer + * Saves the text into the parser cache if possible. + * Updates templatelinks if it is out of date. + * + * @param string $text + * @param bool $cache + */ + public function outputWikiText( $text, $cache = true ) { + global $wgParser, $wgUser, $wgOut; + + $popts = $wgOut->parserOptions(); + $popts->setTidy(true); + $parserOutput = $wgParser->parse( $text, $this->mTitle, + $popts, true, true, $this->getRevIdFetched() ); + $popts->setTidy(false); + if ( $cache && $this && $parserOutput->getCacheTime() != -1 ) { + $parserCache =& ParserCache::singleton(); + $parserCache->save( $parserOutput, $this, $wgUser ); + } + + if ( !wfReadOnly() && $this->mTitle->areRestrictionsCascading() ) { + // templatelinks table may have become out of sync, + // especially if using variable-based transclusions. + // For paranoia, check if things have changed and if + // so apply updates to the database. This will ensure + // that cascaded protections apply as soon as the changes + // are visible. + + # Get templates from templatelinks + $id = $this->mTitle->getArticleID(); + + $tlTemplates = array(); + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( array( 'templatelinks' ), + array( 'tl_namespace', 'tl_title' ), + array( 'tl_from' => $id ), + 'Article:getUsedTemplates' ); + + global $wgContLang; + + if ( false !== $res ) { + if ( $dbr->numRows( $res ) ) { + while ( $row = $dbr->fetchObject( $res ) ) { + $tlTemplates[] = $wgContLang->getNsText( $row->tl_namespace ) . ':' . $row->tl_title ; + } + } + } + + # Get templates from parser output. + $poTemplates_allns = $parserOutput->getTemplates(); + + $poTemplates = array (); + foreach ( $poTemplates_allns as $ns_templates ) { + $poTemplates = array_merge( $poTemplates, $ns_templates ); + } + + # Get the diff + $templates_diff = array_diff( $poTemplates, $tlTemplates ); + + if ( count( $templates_diff ) > 0 ) { + # Whee, link updates time. + $u = new LinksUpdate( $this->mTitle, $parserOutput ); + + $dbw = wfGetDb( DB_MASTER ); + $dbw->begin(); + + $u->doUpdate(); + + $dbw->commit(); + } + } + + $wgOut->addParserOutput( $parserOutput ); + } + } ?> diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php index e33ef1bf..9395032f 100644 --- a/includes/AuthPlugin.php +++ b/includes/AuthPlugin.php @@ -1,6 +1,5 @@ <?php /** - * @package MediaWiki */ # Copyright (C) 2004 Brion Vibber <brion@pobox.com> # http://www.mediawiki.org/ @@ -33,7 +32,6 @@ * This interface is new, and might change a bit before 1.4.0 final is * done... * - * @package MediaWiki */ class AuthPlugin { /** @@ -187,12 +185,14 @@ class AuthPlugin { * Add a user to the external authentication database. * Return true if successful. * - * @param User $user + * @param User $user - only the name should be assumed valid at this point * @param string $password + * @param string $email + * @param string $realname * @return bool * @public */ - function addUser( $user, $password ) { + function addUser( $user, $password, $email='', $realname='' ) { return true; } diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 8de5608f..72a71c71 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -8,9 +8,11 @@ function __autoload($className) { global $wgAutoloadClasses; static $localClasses = array( + # Includes 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', 'AjaxCachePolicy' => 'includes/AjaxFunctions.php', 'AjaxResponse' => 'includes/AjaxResponse.php', + 'AlphabeticPager' => 'includes/Pager.php', 'Article' => 'includes/Article.php', 'AuthPlugin' => 'includes/AuthPlugin.php', 'BagOStuff' => 'includes/BagOStuff.php', @@ -39,9 +41,8 @@ function __autoload($className) { 'Database' => 'includes/Database.php', 'DatabaseMysql' => 'includes/Database.php', 'ResultWrapper' => 'includes/Database.php', - 'OracleBlob' => 'includes/DatabaseOracle.php', - 'DatabaseOracle' => 'includes/DatabaseOracle.php', 'DatabasePostgres' => 'includes/DatabasePostgres.php', + 'DatabaseOracle' => 'includes/DatabaseOracle.php', 'DateFormatter' => 'includes/DateFormatter.php', 'DifferenceEngine' => 'includes/DifferenceEngine.php', '_DiffOp' => 'includes/DifferenceEngine.php', @@ -95,6 +96,7 @@ function __autoload($className) { 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php', 'Http' => 'includes/HttpFunctions.php', 'Image' => 'includes/Image.php', + 'ArchivedFile' => 'includes/Image.php', 'IP' => 'includes/IP.php', 'ThumbnailImage' => 'includes/Image.php', 'ImageGallery' => 'includes/ImageGallery.php', @@ -114,6 +116,10 @@ function __autoload($className) { 'MacBinary' => 'includes/MacBinary.php', 'MagicWord' => 'includes/MagicWord.php', 'MathRenderer' => 'includes/Math.php', + 'MediaTransformOutput' => 'includes/MediaTransformOutput.php', + 'ThumbnailImage' => 'includes/MediaTransformOutput.php', + 'MediaTransformError' => 'includes/MediaTransformOutput.php', + 'TransformParameterError' => 'includes/MediaTransformOutput.php', 'MessageCache' => 'includes/MessageCache.php', 'MimeMagic' => 'includes/MimeMagic.php', 'Namespace' => 'includes/Namespace.php', @@ -124,16 +130,18 @@ function __autoload($className) { 'ReverseChronologicalPager' => 'includes/Pager.php', 'TablePager' => 'includes/Pager.php', 'Parser' => 'includes/Parser.php', - 'ParserOutput' => 'includes/Parser.php', - 'ParserOptions' => 'includes/Parser.php', + 'ParserOutput' => 'includes/ParserOutput.php', + 'ParserOptions' => 'includes/ParserOptions.php', 'ParserCache' => 'includes/ParserCache.php', + 'PatrolLog' => 'includes/PatrolLog.php', 'ProfilerSimple' => 'includes/ProfilerSimple.php', 'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php', 'Profiler' => 'includes/Profiler.php', 'ProxyTools' => 'includes/ProxyTools.php', 'ProtectionForm' => 'includes/ProtectionForm.php', 'QueryPage' => 'includes/QueryPage.php', - 'PageQueryPage' => 'includes/QueryPage.php', + 'PageQueryPage' => 'includes/PageQueryPage.php', + 'ImageQueryPage' => 'includes/ImageQueryPage.php', 'RawPage' => 'includes/RawPage.php', 'RecentChange' => 'includes/RecentChange.php', 'Revision' => 'includes/Revision.php', @@ -148,6 +156,7 @@ function __autoload($className) { 'SearchPostgres' => 'includes/SearchPostgres.php', 'SearchUpdate' => 'includes/SearchUpdate.php', 'SearchUpdateMyISAM' => 'includes/SearchUpdate.php', + 'SearchOracle' => 'includes/SearchOracle.php', 'SiteConfiguration' => 'includes/SiteConfiguration.php', 'SiteStats' => 'includes/SiteStats.php', 'SiteStatsUpdate' => 'includes/SiteStats.php', @@ -160,7 +169,6 @@ function __autoload($className) { 'IPBlockForm' => 'includes/SpecialBlockip.php', 'SpecialBookSources' => 'includes/SpecialBooksources.php', 'BrokenRedirectsPage' => 'includes/SpecialBrokenRedirects.php', - 'CategoriesPage' => 'includes/SpecialCategories.php', 'EmailConfirmation' => 'includes/SpecialConfirmemail.php', 'ContributionsPage' => 'includes/SpecialContributions.php', 'DeadendPagesPage' => 'includes/SpecialDeadendpages.php', @@ -173,7 +181,6 @@ function __autoload($className) { 'ImportStreamSource' => 'includes/SpecialImport.php', 'IPUnblockForm' => 'includes/SpecialIpblocklist.php', 'ListredirectsPage' => 'includes/SpecialListredirects.php', - 'ListUsersPage' => 'includes/SpecialListusers.php', 'DBLockForm' => 'includes/SpecialLockdb.php', 'LogReader' => 'includes/SpecialLog.php', 'LogViewer' => 'includes/SpecialLog.php', @@ -185,6 +192,7 @@ function __autoload($className) { 'MostlinkedPage' => 'includes/SpecialMostlinked.php', 'MostlinkedCategoriesPage' => 'includes/SpecialMostlinkedcategories.php', 'MostrevisionsPage' => 'includes/SpecialMostrevisions.php', + 'FewestrevisionsPage' => 'includes/SpecialFewestrevisions.php', 'MovePageForm' => 'includes/SpecialMovepage.php', 'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php', 'NewPagesPage' => 'includes/SpecialNewpages.php', @@ -194,6 +202,7 @@ function __autoload($className) { 'PopularPagesPage' => 'includes/SpecialPopularpages.php', 'PreferencesForm' => 'includes/SpecialPreferences.php', 'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php', + 'PasswordResetForm' => 'includes/SpecialResetpass.php', 'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php', 'RevisionDeleter' => 'includes/SpecialRevisiondelete.php', 'SpecialSearch' => 'includes/SpecialSearch.php', @@ -215,6 +224,7 @@ function __autoload($className) { 'WantedCategoriesPage' => 'includes/SpecialWantedcategories.php', 'WantedPagesPage' => 'includes/SpecialWantedpages.php', 'WhatLinksHerePage' => 'includes/SpecialWhatlinkshere.php', + 'WithoutInterwikiPage' => 'includes/SpecialWithoutinterwiki.php', 'SquidUpdate' => 'includes/SquidUpdate.php', 'ReplacementArray' => 'includes/StringUtils.php', 'Replacer' => 'includes/StringUtils.php', @@ -237,13 +247,27 @@ function __autoload($className) { 'Xml' => 'includes/Xml.php', 'ZhClient' => 'includes/ZhClient.php', 'memcached' => 'includes/memcached-client.php', + + # Media + 'BitmapHandler' => 'includes/media/Bitmap.php', + 'BmpHandler' => 'includes/media/BMP.php', + 'DjVuHandler' => 'includes/media/DjVu.php', + 'MediaHandler' => 'includes/media/Generic.php', + 'ImageHandler' => 'includes/media/Generic.php', + 'SvgHandler' => 'includes/media/SVG.php', + + # Normal 'UtfNormal' => 'includes/normal/UtfNormal.php', + + # Templates 'UsercreateTemplate' => 'includes/templates/Userlogin.php', 'UserloginTemplate' => 'includes/templates/Userlogin.php', + + # Languages 'Language' => 'languages/Language.php', - 'PasswordResetForm' => 'includes/SpecialResetpass.php', + 'RandomPage' => 'includes/SpecialRandompage.php', - // API classes + # API 'ApiBase' => 'includes/api/ApiBase.php', 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php', @@ -274,6 +298,7 @@ function __autoload($className) { 'ApiResult' => 'includes/api/ApiResult.php', ); + wfProfileIn( __METHOD__ ); if ( isset( $localClasses[$className] ) ) { $filename = $localClasses[$className]; } elseif ( isset( $wgAutoloadClasses[$className] ) ) { @@ -290,6 +315,7 @@ function __autoload($className) { } if ( !$filename ) { # Give up + wfProfileOut( __METHOD__ ); return; } } @@ -300,6 +326,7 @@ function __autoload($className) { $filename = "$IP/$filename"; } require( $filename ); + wfProfileOut( __METHOD__ ); } function wfLoadAllExtensions() { @@ -311,10 +338,10 @@ function wfLoadAllExtensions() { # guaranteed by entering special pages via SpecialPage members such as # executePath(), but here we have to take a more explicit measure. - require_once( 'SpecialPage.php' ); + require_once( dirname(__FILE__) . '/SpecialPage.php' ); foreach( $wgAutoloadClasses as $class => $file ) { - if ( ! class_exists( $class ) ) { + if( !( class_exists( $class ) || interface_exists( $class ) ) ) { require( $file ); } } diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php index c720807d..2a04b9dd 100644 --- a/includes/BagOStuff.php +++ b/includes/BagOStuff.php @@ -19,7 +19,6 @@ # http://www.gnu.org/copyleft/gpl.html /** * - * @package MediaWiki */ /** @@ -29,15 +28,16 @@ * the PHP memcached client. * * backends for local hash array and SQL table included: - * $bag = new HashBagOStuff(); - * $bag = new MysqlBagOStuff($tablename); # connect to db first + * <code> + * $bag = new HashBagOStuff(); + * $bag = new MysqlBagOStuff($tablename); # connect to db first + * </code> * - * @package MediaWiki */ class BagOStuff { var $debugmode; - function BagOStuff() { + function __construct() { $this->set_debug( false ); } @@ -163,7 +163,6 @@ class BagOStuff { /** * Functional versions! * @todo document - * @package MediaWiki */ class HashBagOStuff extends BagOStuff { /* @@ -218,7 +217,6 @@ CREATE TABLE objectcache ( /** * @todo document * @abstract - * @package MediaWiki */ abstract class SqlBagOStuff extends BagOStuff { var $table; @@ -386,34 +384,32 @@ abstract class SqlBagOStuff extends BagOStuff { /** * @todo document - * @package MediaWiki */ class MediaWikiBagOStuff extends SqlBagOStuff { var $tableInitialised = false; function _doquery($sql) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->query($sql, 'MediaWikiBagOStuff::_doquery'); } function _doinsert($t, $v) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert', array( 'IGNORE' ) ); } function _fetchobject($result) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->fetchObject($result); } function _freeresult($result) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->freeResult($result); } function _dberror($result) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->lastError(); } function _maxdatetime() { - $dbw =& wfGetDB(DB_MASTER); if ( time() > 0x7fffffff ) { return $this->_fromunixtime( 1<<62 ); } else { @@ -421,24 +417,24 @@ class MediaWikiBagOStuff extends SqlBagOStuff { } } function _fromunixtime($ts) { - $dbw =& wfGetDB(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); return $dbw->timestamp($ts); } function _strencode($s) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->strencode($s); } function _blobencode($s) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->encodeBlob($s); } function _blobdecode($s) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->decodeBlob($s); } function getTableName() { if ( !$this->tableInitialised ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); /* This is actually a hack, we should be able to use Language classes here... or not */ if (!$dbw) @@ -463,7 +459,6 @@ class MediaWikiBagOStuff extends SqlBagOStuff { * that Turck's serializer is faster, so a possible future extension would be * to use it for arrays but not for objects. * - * @package MediaWiki */ class TurckBagOStuff extends BagOStuff { function get($key) { @@ -498,9 +493,7 @@ class TurckBagOStuff extends BagOStuff { /** * This is a wrapper for APC's shared memory functions * - * @package MediaWiki */ - class APCBagOStuff extends BagOStuff { function get($key) { $val = apc_fetch($key); @@ -528,7 +521,6 @@ class APCBagOStuff extends BagOStuff { * This is basically identical to the Turck MMCache version, * mostly because eAccelerator is based on Turck MMCache. * - * @package MediaWiki */ class eAccelBagOStuff extends BagOStuff { function get($key) { @@ -560,6 +552,9 @@ class eAccelBagOStuff extends BagOStuff { } } +/** + * @todo document + */ class DBABagOStuff extends BagOStuff { var $mHandler, $mFile, $mReader, $mWriter, $mDisabled; diff --git a/includes/Block.php b/includes/Block.php index ff813ba3..94bfa5b4 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -1,7 +1,6 @@ <?php /** * Blocks and bans object - * @package MediaWiki */ /** @@ -12,22 +11,24 @@ * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags * * @todo This could be used everywhere, but it isn't. - * @package MediaWiki */ class Block { /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry, - $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock; + $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock, $mHideName; /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName; const EB_KEEP_EXPIRED = 1; const EB_FOR_UPDATE = 2; const EB_RANGE_ONLY = 4; - function Block( $address = '', $user = 0, $by = 0, $reason = '', - $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0 ) + function __construct( $address = '', $user = 0, $by = 0, $reason = '', + $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0, + $hideName = 0 ) { $this->mId = 0; + # Expand valid IPv6 addresses + $address = IP::sanitizeIP( $address ); $this->mAddress = $address; $this->mUser = $user; $this->mBy = $by; @@ -38,6 +39,7 @@ class Block $this->mCreateAccount = $createAccount; $this->mExpiry = self::decodeExpiry( $expiry ); $this->mEnableAutoblock = $enableAutoblock; + $this->mHideName = $hideName; $this->mForUpdate = false; $this->mFromMaster = false; @@ -58,7 +60,7 @@ class Block static function newFromID( $id ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*', array( 'ipb_id' => $id ), __METHOD__ ) ); $block = new Block; @@ -74,7 +76,7 @@ class Block $this->mAddress = $this->mReason = $this->mTimestamp = ''; $this->mId = $this->mAnonOnly = $this->mCreateAccount = $this->mEnableAutoblock = $this->mAuto = $this->mUser = - $this->mBy = 0; + $this->mBy = $this->mHideName = 0; $this->mByName = false; } @@ -85,14 +87,14 @@ class Block { global $wgAntiLockFlags; if ( $this->mForUpdate || $this->mFromMaster ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) { $options = array(); } else { $options = array( 'FOR UPDATE' ); } } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $options = array(); } return $db; @@ -147,7 +149,7 @@ class Block } # Try range block - if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) { + if ( $this->loadRange( $address, $killExpired, $user ) ) { if ( $user && $this->mAnonOnly ) { $this->clear(); return false; @@ -176,7 +178,8 @@ class Block /** * Fill in member variables from a result wrapper */ - function loadFromResult( ResultWrapper $res, $killExpired = true ) { + function loadFromResult( ResultWrapper $res, $killExpired = true ) + { $ret = false; if ( 0 != $res->numRows() ) { # Get first block @@ -211,7 +214,7 @@ class Block * Search the database for any range blocks matching the given address, and * load the row if one is found. */ - function loadRange( $address, $killExpired = true ) + function loadRange( $address, $killExpired = true, $user = 0 ) { $iaddr = IP::toHex( $address ); if ( $iaddr === false ) { @@ -230,6 +233,10 @@ class Block "ipb_range_start <= '$iaddr'", "ipb_range_end >= '$iaddr'" ); + + if ( $user ) { + $conds['ipb_anon_only'] = 0; + } $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) ); $success = $this->loadFromResult( $res, $killExpired ); @@ -255,6 +262,7 @@ class Block $this->mAnonOnly = $row->ipb_anon_only; $this->mCreateAccount = $row->ipb_create_account; $this->mEnableAutoblock = $row->ipb_enable_autoblock; + $this->mHideName = $row->ipb_deleted; $this->mId = $row->ipb_id; $this->mExpiry = self::decodeExpiry( $row->ipb_expiry ); if ( isset( $row->user_name ) ) { @@ -286,7 +294,7 @@ class Block $block = new Block(); if ( $flags & Block::EB_FOR_UPDATE ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); if ( $wgAntiLockFlags & ALF_NO_BLOCK_LOCK ) { $options = ''; } else { @@ -294,7 +302,7 @@ class Block } $block->forUpdate( true ); } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $options = ''; } if ( $flags & Block::EB_RANGE_ONLY ) { @@ -341,7 +349,7 @@ class Block throw new MWException( "Block::delete() now requires that the mId member be filled\n" ); } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ ); return $dbw->affectedRows() > 0; } @@ -353,8 +361,7 @@ class Block function insert() { wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" ); - $dbw =& wfGetDB( DB_MASTER ); - $dbw->begin(); + $dbw = wfGetDB( DB_MASTER ); # Unset ipb_anon_only for user blocks, makes no sense if ( $this->mUser ) { @@ -385,6 +392,7 @@ class Block 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ), 'ipb_range_start' => $this->mRangeStart, 'ipb_range_end' => $this->mRangeEnd, + 'ipb_deleted' => $this->mHideName ), 'Block::insert', array( 'IGNORE' ) ); $affected = $dbw->affectedRows(); @@ -418,20 +426,20 @@ class Block } else { #Limit is 1, so no loop needed. $retroblockip = $row->rc_ip; - return $this->doAutoblock($retroblockip); + return $this->doAutoblock( $retroblockip, true ); } } } /** * Autoblocks the given IP, referring to this Block. - * @param $autoblockip The IP to autoblock. + * @param string $autoblockip The IP to autoblock. + * @param bool $justInserted The main block was just inserted * @return bool Whether or not an autoblock was inserted. */ - function doAutoblock( $autoblockip ) { + function doAutoblock( $autoblockip, $justInserted = false ) { # Check if this IP address is already blocked - $dbw =& wfGetDB( DB_MASTER ); - $dbw->begin(); + $dbw = wfGetDB( DB_MASTER ); # If autoblocks are disabled, go away. if ( !$this->mEnableAutoblock ) { @@ -480,7 +488,9 @@ class Block return; } # Just update the timestamp - $ipblock->updateTimestamp(); + if ( !$justInserted ) { + $ipblock->updateTimestamp(); + } return; } else { $ipblock = new Block; @@ -495,6 +505,8 @@ class Block $ipblock->mTimestamp = wfTimestampNow(); $ipblock->mAuto = 1; $ipblock->mCreateAccount = $this->mCreateAccount; + # Continue suppressing the name if needed + $ipblock->mHideName = $this->mHideName; # If the user is already blocked with an expiry date, we don't # want to pile on top of that! @@ -544,7 +556,7 @@ class Block $this->mTimestamp = wfTimestamp(); $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'ipblocks', array( /* SET */ 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp), @@ -628,16 +640,36 @@ class Block global $wgAutoblockExpiry; return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry ); } - - static function normaliseRange( $range ) - { + + /** + * Gets rid of uneeded numbers in quad-dotted/octet IP strings + * For example, 127.111.113.151/24 -> 127.111.113.0/24 + */ + static function normaliseRange( $range ) { $parts = explode( '/', $range ); if ( count( $parts ) == 2 ) { - $shift = 32 - $parts[1]; - $ipint = IP::toUnsigned( $parts[0] ); - $ipint = $ipint >> $shift << $shift; - $newip = long2ip( $ipint ); - $range = "$newip/{$parts[1]}"; + // IPv6 + if ( IP::isIPv6($range) && $parts[1] >= 64 && $parts[1] <= 128 ) { + $bits = $parts[1]; + $ipint = IP::toUnsigned6( $parts[0] ); + # Native 32 bit functions WONT work here!!! + # Convert to a padded binary number + $network = wfBaseConvert( $ipint, 10, 2, 128 ); + # Truncate the last (128-$bits) bits and replace them with zeros + $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT ); + # Convert back to an integer + $network = wfBaseConvert( $network, 2, 10 ); + # Reform octet address + $newip = IP::toOctet( $network ); + $range = "$newip/{$parts[1]}"; + } // IPv4 + else if ( IP::isIPv4($range) && $parts[1] >= 16 && $parts[1] <= 32 ) { + $shift = 32 - $parts[1]; + $ipint = IP::toUnsigned( $parts[0] ); + $ipint = $ipint >> $shift << $shift; + $newip = long2ip( $ipint ); + $range = "$newip/{$parts[1]}"; + } } return $range; } @@ -646,7 +678,7 @@ class Block * Purge expired blocks from the ipblocks table */ static function purgeExpired() { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ ); } @@ -658,7 +690,7 @@ class Block /* static $infinity; if ( !isset( $infinity ) ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $infinity = $dbr->bigTimestamp(); } return $infinity; diff --git a/includes/CacheDependency.php b/includes/CacheDependency.php index 4bb3d328..bb5c5437 100644 --- a/includes/CacheDependency.php +++ b/includes/CacheDependency.php @@ -4,6 +4,7 @@ * This class stores an arbitrary value along with its dependencies. * Users should typically only use DependencyWrapper::getFromCache(), rather * than instantiating one of these objects directly. + * @addtogroup Cache */ class DependencyWrapper { var $value; @@ -95,6 +96,9 @@ class DependencyWrapper { } } +/** + * @addtogroup Cache + */ abstract class CacheDependency { /** * Returns true if the dependency is expired, false otherwise @@ -107,6 +111,9 @@ abstract class CacheDependency { function loadDependencyValues() {} } +/** + * @addtogroup Cache + */ class FileDependency extends CacheDependency { var $filename, $timestamp; @@ -163,6 +170,9 @@ class FileDependency extends CacheDependency { } } +/** + * @addtogroup Cache + */ class TitleDependency extends CacheDependency { var $titleObj; var $ns, $dbk; @@ -219,6 +229,9 @@ class TitleDependency extends CacheDependency { } } +/** + * @addtogroup Cache + */ class TitleListDependency extends CacheDependency { var $linkBatch; var $timestamps; @@ -244,7 +257,7 @@ class TitleListDependency extends CacheDependency { # Do the query if ( count( $timestamps ) ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $where = $this->getLinkBatch()->constructSet( 'page', $dbr ); $res = $dbr->select( 'page', array( 'page_namespace', 'page_title', 'page_touched' ), @@ -299,6 +312,9 @@ class TitleListDependency extends CacheDependency { } } +/** + * @addtogroup Cache + */ class GlobalDependency extends CacheDependency { var $name, $value; @@ -312,6 +328,9 @@ class GlobalDependency extends CacheDependency { } } +/** + * @addtogroup Cache + */ class ConstantDependency extends CacheDependency { var $name, $value; diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php index 0086a2f9..356f9ea2 100644 --- a/includes/CategoryPage.php +++ b/includes/CategoryPage.php @@ -3,17 +3,23 @@ * Special handling for category description pages * Modelled after ImagePage.php * - * @package MediaWiki */ if( !defined( 'MEDIAWIKI' ) ) die( 1 ); /** - * @package MediaWiki */ class CategoryPage extends Article { function view() { + global $wgRequest, $wgUser; + + $diff = $wgRequest->getVal( 'diff' ); + $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) ); + + if ( isset( $diff ) && $diffOnly ) + return Article::view(); + if(!wfRunHooks('CategoryPageView', array(&$this))) return; if ( NS_CATEGORY == $this->mTitle->getNamespace() ) { @@ -175,7 +181,7 @@ class CategoryViewer { } function doCategoryQuery() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); if( $this->from != '' ) { $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from ); $this->flip = false; @@ -196,6 +202,7 @@ class CategoryViewer { #+ $pageCondition, __METHOD__, array( 'ORDER BY' => $this->flip ? 'cl_sortkey DESC' : 'cl_sortkey', + 'USE INDEX' => 'cl_sortkey', 'LIMIT' => $this->limit + 1 ) ); $count = 0; @@ -234,11 +241,12 @@ class CategoryViewer { function getSubcategorySection() { # Don't show subcategories section if there are none. $r = ''; - if( count( $this->children ) > 0 ) { + $c = count( $this->children ); + if( $c > 0 ) { # Showing subcategories $r .= "<div id=\"mw-subcategories\">\n"; $r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n"; - $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), count( $this->children) ); + $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), $c ); $r .= $this->formatList( $this->children, $this->children_start_char ); $r .= "\n</div>"; } @@ -247,11 +255,16 @@ class CategoryViewer { function getPagesSection() { $ti = htmlspecialchars( $this->title->getText() ); - $r = "<div id=\"mw-pages\">\n"; - $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n"; - $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), count( $this->articles) ); - $r .= $this->formatList( $this->articles, $this->articles_start_char ); - $r .= "\n</div>"; + # Don't show articles section if there are none. + $r = ''; + $c = count( $this->articles ); + if( $c > 0 ) { + $r = "<div id=\"mw-pages\">\n"; + $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n"; + $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), $c ); + $r .= $this->formatList( $this->articles, $this->articles_start_char ); + $r .= "\n</div>"; + } return $r; } @@ -391,7 +404,7 @@ class CategoryViewer { */ function pagingLinks( $title, $first, $last, $limit, $query = array() ) { global $wgUser, $wgLang; - $sk =& $this->getSkin(); + $sk = $this->getSkin(); $limitText = $wgLang->formatNum( $limit ); $prevLink = htmlspecialchars( wfMsg( 'prevn', $limitText ) ); diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php index a8cdf3ce..7faae935 100644 --- a/includes/Categoryfinder.php +++ b/includes/Categoryfinder.php @@ -1,26 +1,27 @@ <?php -/* -The "Categoryfinder" class takes a list of articles, creates an internal representation of all their parent -categories (as well as parents of parents etc.). From this representation, it determines which of these articles -are in one or all of a given subset of categories. - -Example use : - - # Determines wether the article with the page_id 12345 is in both - # "Category 1" and "Category 2" or their subcategories, respectively - - $cf = new Categoryfinder ; - $cf->seed ( - array ( 12345 ) , - array ( "Category 1","Category 2" ) , - "AND" - ) ; - $a = $cf->run() ; - print implode ( "," , $a ) ; - -*/ - +/** + * The "Categoryfinder" class takes a list of articles, creates an internal + * representation of all their parent categories (as well as parents of + * parents etc.). From this representation, it determines which of these + * articles are in one or all of a given subset of categories. + * + * Example use : + * <code> + * # Determines whether the article with the page_id 12345 is in both + * # "Category 1" and "Category 2" or their subcategories, respectively + * + * $cf = new Categoryfinder ; + * $cf->seed ( + * array ( 12345 ) , + * array ( "Category 1","Category 2" ) , + * "AND" + * ) ; + * $a = $cf->run() ; + * print implode ( "," , $a ) ; + * </code> + * + */ class Categoryfinder { var $articles = array () ; # The original article IDs passed to the seed function @@ -34,8 +35,8 @@ class Categoryfinder { /** * Constructor (currently empty). - */ - function Categoryfinder () { + */ + function __construct() { } /** @@ -61,10 +62,10 @@ class Categoryfinder { /** * Iterates through the parent tree starting with the seed values, * then checks the articles if they match the conditions - @return array of page_ids (those given to seed() that match the conditions) - */ + * @return array of page_ids (those given to seed() that match the conditions) + */ function run () { - $this->dbr =& wfGetDB( DB_SLAVE ); + $this->dbr = wfGetDB( DB_SLAVE ); while ( count ( $this->next ) > 0 ) { $this->scan_next_layer () ; } @@ -83,20 +84,20 @@ class Categoryfinder { /** * This functions recurses through the parent representation, trying to match the conditions - @param $id The article/category to check - @param $conds The array of categories to match - @return bool Does this match the conditions? - */ + * @param $id The article/category to check + * @param $conds The array of categories to match + * @return bool Does this match the conditions? + */ function check ( $id , &$conds ) { # Shortcut (runtime paranoia): No contitions=all matched if ( count ( $conds ) == 0 ) return true ; - + if ( !isset ( $this->parents[$id] ) ) return false ; # iterate through the parents foreach ( $this->parents[$id] AS $p ) { $pname = $p->cl_to ; - + # Is this a condition? if ( isset ( $conds[$pname] ) ) { # This key is in the category list! @@ -113,7 +114,7 @@ class Categoryfinder { } } } - + # Not done yet, try sub-parents if ( !isset ( $this->name2id[$pname] ) ) { # No sub-parent @@ -130,10 +131,10 @@ class Categoryfinder { /** * Scans a "parent layer" of the articles/categories in $this->next - */ + */ function scan_next_layer () { $fname = "Categoryfinder::scan_next_layer" ; - + # Find all parents of the article currently in $this->next $layer = array () ; $res = $this->dbr->select( @@ -161,7 +162,7 @@ class Categoryfinder { $this->dbr->freeResult( $res ) ; $this->next = array() ; - + # Find the IDs of all category pages in $layer, if they exist if ( count ( $layer ) > 0 ) { $res = $this->dbr->select( diff --git a/includes/ChangesList.php b/includes/ChangesList.php index a2c1a265..bc141579 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -1,15 +1,7 @@ <?php -/** - * @package MediaWiki - * Contain class to show various lists of change: - * - what's link here - * - related changes - * - recent changes - */ /** * @todo document - * @package MediaWiki */ class RCCacheEntry extends RecentChange { @@ -17,8 +9,7 @@ class RCCacheEntry extends RecentChange var $curlink , $difflink, $lastlink , $usertalklink , $versionlink ; var $userlink, $timestamp, $watched; - function newFromParent( $rc ) - { + function newFromParent( $rc ) { $rc2 = new RCCacheEntry; $rc2->mAttribs = $rc->mAttribs; $rc2->mExtra = $rc->mExtra; @@ -27,14 +18,17 @@ class RCCacheEntry extends RecentChange } ; /** - * @package MediaWiki + * Class to show various lists of changes: + * - what links here + * - related changes + * - recent changes */ class ChangesList { # Called by history lists and recent changes # /** @todo document */ - function ChangesList( &$skin ) { + function __construct( &$skin ) { $this->skin =& $skin; $this->preCacheMessages(); } @@ -47,7 +41,7 @@ class ChangesList { * @return ChangesList derivative */ public static function newFromUser( &$user ) { - $sk =& $user->getSkin(); + $sk = $user->getSkin(); $list = NULL; if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) { return $user->getOption( 'usenewrc' ) ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk ); @@ -64,7 +58,7 @@ class ChangesList { // Precache various messages if( !isset( $this->message ) ) { foreach( explode(' ', 'cur diff hist minoreditletter newpageletter last '. - 'blocklink changes history boteditletter' ) as $msg ) { + 'blocklink history boteditletter' ) as $msg ) { $this->message[$msg] = wfMsgExt( $msg, array( 'escape') ); } } @@ -212,6 +206,23 @@ class ChangesList { global $wgUseRCPatrol, $wgUser; return( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) ); } + + /** + * Returns the string which indicates the number of watching users + */ + function numberofWatchingusers( $count ) { + global $wgLang; + static $cache = array(); + if ( $count > 0 ) { + if ( !isset( $cache[$count] ) ) { + $cache[$count] = wfMsgExt('number_of_watching_users_RCview', + array('parsemag', 'escape'), $wgLang->formatNum($count)); + } + return $cache[$count]; + } else { + return ''; + } + } } @@ -229,6 +240,7 @@ class OldChangesList extends ChangesList { wfProfileIn( $fname ); # Extract DB fields into local scope + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( $rc->mAttribs ); # Should patrol-related stuff be shown? @@ -273,9 +285,7 @@ class OldChangesList extends ChangesList { $this->insertUserRelatedLinks($s,$rc); $this->insertComment($s, $rc); - if($rc->numberofWatchingusers > 0) { - $s .= ' ' . wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rc->numberofWatchingusers)); - } + $s .= rtrim(' ' . $this->numberofWatchingusers($rc->numberofWatchingusers)); $s .= "</li>\n"; @@ -301,6 +311,7 @@ class EnhancedChangesList extends ChangesList { $rc = RCCacheEntry::newFromParent( $baseRC ); # Extract fields from DB into the function scope (rc_xxxx variables) + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( $rc->mAttribs ); $curIdEq = 'curid=' . $rc_cur_id; @@ -405,7 +416,7 @@ class EnhancedChangesList extends ChangesList { * Enhanced RC group */ function recentChangesBlockGroup( $block ) { - global $wgContLang, $wgRCShowChangedSize; + global $wgLang, $wgContLang, $wgRCShowChangedSize; $r = ''; # Collate list of users @@ -467,22 +478,32 @@ class EnhancedChangesList extends ChangesList { $currentRevision = $block[0]->mAttribs['rc_this_oldid']; if( $block[0]->mAttribs['rc_type'] != RC_LOG ) { # Changes - $r .= ' ('.count($block).' '; + + $n = count($block); + static $nchanges = array(); + if ( !isset( $nchanges[$n] ) ) { + $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape'), + $wgLang->formatNum( $n ) ); + } + + $r .= ' ('; if( $isnew ) { - $r .= $this->message['changes']; + $r .= $nchanges[$n]; } else { $r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(), - $this->message['changes'], $curIdEq."&diff=$currentRevision&oldid=$oldid" ); + $nchanges[$n], $curIdEq."&diff=$currentRevision&oldid=$oldid" ); } + $r .= ') . . '; + # Character difference $chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'], $block[0]->mAttribs['rc_new_len'] ); if( $chardiff == '' ) { - $r .= '; '; + $r .= ' ('; } else { - $r .= '; ' . $chardiff . ' '; + $r .= ' ' . $chardiff. ' . . ('; } @@ -494,16 +515,14 @@ class EnhancedChangesList extends ChangesList { $r .= $users; - if($block[0]->numberofWatchingusers > 0) { - global $wgContLang; - $r .= wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($block[0]->numberofWatchingusers)); - } + $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers); $r .= "<br />\n"; # Sub-entries $r .= '<div id="'.$rci.'" style="display:none">'; foreach( $block as $rcObj ) { # Get rc_xxxx variables + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( $rcObj->mAttribs ); $r .= $this->spacerArrow(); @@ -607,6 +626,7 @@ class EnhancedChangesList extends ChangesList { global $wgContLang, $wgRCShowChangedSize; # Get rc_xxxx variables + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( $rcObj->mAttribs ); $curIdEq = 'curid='.$rc_cur_id; @@ -647,9 +667,7 @@ class EnhancedChangesList extends ChangesList { $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() ); } - if( $rcObj->numberofWatchingusers > 0 ) { - $r .= wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rcObj->numberofWatchingusers)); - } + $r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers); $r .= "<br />\n"; return $r; diff --git a/includes/CoreParserFunctions.php b/includes/CoreParserFunctions.php index 402a3ba9..72ceb45f 100644 --- a/includes/CoreParserFunctions.php +++ b/includes/CoreParserFunctions.php @@ -2,8 +2,8 @@ /** * Various core parser functions, registered in Parser::firstCallInit() + * @addtogroup Parser */ - class CoreParserFunctions { static function intFunction( $parser, $part1 = '' /*, ... */ ) { if ( strval( $part1 ) !== '' ) { @@ -87,7 +87,7 @@ class CoreParserFunctions { static function formatNum( $parser, $num = '' ) { return $parser->getFunctionLang()->formatNum( $num ); } - + static function grammar( $parser, $case = '', $word = '' ) { return $parser->getFunctionLang()->convertGrammar( $word, $case ); } @@ -135,6 +135,7 @@ class CoreParserFunctions { static function numberofarticles( $parser, $raw = null ) { return self::statisticsFunction( 'articles', $raw ); } static function numberoffiles( $parser, $raw = null ) { return self::statisticsFunction( 'images', $raw ); } static function numberofadmins( $parser, $raw = null ) { return self::statisticsFunction( 'admins', $raw ); } + static function numberofedits( $parser, $raw = null ) { return self::statisticsFunction( 'edits', $raw ); } static function pagesinnamespace( $parser, $namespace = 0, $raw = null ) { $count = SiteStats::pagesInNs( intval( $namespace ) ); @@ -151,7 +152,7 @@ class CoreParserFunctions { $lang = $wgContLang->getLanguageName( strtolower( $arg ) ); return $lang != '' ? $lang : $arg; } - + static function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) { $length = min( max( $length, 0 ), 500 ); $char = substr( $char, 0, 1 ); @@ -159,17 +160,21 @@ class CoreParserFunctions { ? str_pad( $string, $length, (string)$char, $direction ) : $string; } - + static function padleft( $parser, $string = '', $length = 0, $char = 0 ) { return self::pad( $string, $length, $char, STR_PAD_LEFT ); } - + static function padright( $parser, $string = '', $length = 0, $char = 0 ) { return self::pad( $string, $length, $char ); } - + static function anchorencode( $parser, $text ) { - return strtr( urlencode( $text ) , array( '%' => '.' , '+' => '_' ) ); + $a = urlencode( $text ); + $a = strtr( $a, array( '%' => '.', '+' => '_' ) ); + # leave colons alone, however + $a = str_replace( '.3A', ':', $a ); + return $a; } static function special( $parser, $text ) { @@ -180,14 +185,12 @@ class CoreParserFunctions { return wfMsgForContent( 'nosuchspecialpage' ); } } - + public static function defaultsort( $parser, $text ) { $text = trim( $text ); if( strlen( $text ) > 0 ) $parser->setDefaultSort( $text ); return ''; } - } - ?> diff --git a/includes/Credits.php b/includes/Credits.php index 62f0b256..87382a86 100644 --- a/includes/Credits.php +++ b/includes/Credits.php @@ -18,7 +18,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * @author <evan@wikitravel.org> - * @package MediaWiki */ /** diff --git a/includes/Database.php b/includes/Database.php index eb1ee135..3fd6ad16 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -2,7 +2,6 @@ /** * This file deals with MySQL interface functions * and query specifics/optimisations - * @package MediaWiki */ /** Number of times to re-try an operation in case of deadlock */ @@ -16,6 +15,10 @@ define( 'DEADLOCK_DELAY_MAX', 1500000 ); * Utility classes *****************************************************************************/ +/** + * Utility class. + * @addtogroup Database + */ class DBObject { public $mData; @@ -32,12 +35,66 @@ class DBObject { } }; +/** + * Utility class. + * @addtogroup Database + */ +class MySQLField { + private $name, $tablename, $default, $max_length, $nullable, + $is_pk, $is_unique, $is_key, $type; + function __construct ($info) { + $this->name = $info->name; + $this->tablename = $info->table; + $this->default = $info->def; + $this->max_length = $info->max_length; + $this->nullable = !$info->not_null; + $this->is_pk = $info->primary_key; + $this->is_unique = $info->unique_key; + $this->is_multiple = $info->multiple_key; + $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple); + $this->type = $info->type; + } + + function name() { + return $this->name; + } + + function tableName() { + return $this->tableName; + } + + function defaultValue() { + return $this->default; + } + + function maxLength() { + return $this->max_length; + } + + function nullable() { + return $this->nullable; + } + + function isKey() { + return $this->is_key; + } + + function isMultipleKey() { + return $this->is_multiple; + } + + function type() { + return $this->type; + } +} + /****************************************************************************** * Error classes *****************************************************************************/ /** * Database error base class + * @addtogroup Database */ class DBError extends MWException { public $db; @@ -53,6 +110,9 @@ class DBError extends MWException { } } +/** + * @addtogroup Database + */ class DBConnectionError extends DBError { public $error; @@ -154,6 +214,7 @@ border=\"0\" ALT=\"Google\"></A> $cache = new HTMLFileCache( $t ); if( $cache->isFileCached() ) { + // FIXME: $msg is not defined on the next line. $msg = '<p style="color: red"><b>'.$msg."<br />\n" . $cachederror . "</b></p>\n"; @@ -169,6 +230,9 @@ border=\"0\" ALT=\"Google\"></A> } } +/** + * @addtogroup Database + */ class DBQueryError extends DBError { public $error, $errno, $sql, $fname; @@ -222,13 +286,16 @@ class DBQueryError extends DBError { } } +/** + * @addtogroup Database + */ class DBUnexpectedError extends DBError {} /******************************************************************************/ /** * Database abstraction object - * @package MediaWiki + * @addtogroup Database */ class Database { @@ -247,9 +314,6 @@ class Database { protected $mTrxLevel = 0; protected $mErrorCount = 0; protected $mLBInfo = array(); - protected $mCascadingDeletes = false; - protected $mCleanupTriggers = false; - protected $mStrictIPs = false; #------------------------------------------------------------------------------ # Accessors @@ -344,14 +408,14 @@ class Database { * Returns true if this database supports (and uses) cascading deletes */ function cascadingDeletes() { - return $this->mCascadingDeletes; + return false; } /** * Returns true if this database supports (and uses) triggers (e.g. on the page table) */ function cleanupTriggers() { - return $this->mCleanupTriggers; + return false; } /** @@ -359,7 +423,7 @@ class Database { * Specifically, it uses a NULL value instead of an empty string. */ function strictIPs() { - return $this->mStrictIPs; + return false; } /** @@ -376,6 +440,14 @@ class Database { return true; } + /** + * Returns true if this database can do a native search on IP columns + * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32'; + */ + function searchableIPs() { + return false; + } + /**#@+ * Get function */ @@ -407,13 +479,11 @@ class Database { #------------------------------------------------------------------------------ /**@{{ + * Constructor. * @param string $server database server host * @param string $user database user name * @param string $password database user password * @param string $dbname database name - */ - - /** * @param failFunction * @param $flags * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php @@ -463,8 +533,7 @@ class Database { * @param failFunction * @param $flags */ - static function newFromParams( $server, $user, $password, $dbName, - $failFunction = false, $flags = 0 ) + static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) { return new Database( $server, $user, $password, $dbName, $failFunction, $flags ); } @@ -514,7 +583,7 @@ class Database { } if ($this->mConn === false) { $iplus = $i + 1; - wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); + #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n"); } } @@ -541,12 +610,19 @@ class Database { } if ( $success ) { - global $wgDBmysql5; - if( $wgDBmysql5 ) { + $version = $this->getServerVersion(); + if ( version_compare( $version, '4.1' ) >= 0 ) { // Tell the server we're communicating with it in UTF-8. // This may engage various charset conversions. - $this->query( 'SET NAMES utf8' ); + global $wgDBmysql5; + if( $wgDBmysql5 ) { + $this->query( 'SET NAMES utf8', __METHOD__ ); + } + // Turn off strict mode + $this->query( "SET sql_mode = ''", __METHOD__ ); } + + // Turn off strict mode if it is on } else { $this->reportConnectionError(); } @@ -599,10 +675,15 @@ class Database { } /** - * Usually aborts on failure - * If errors are explicitly ignored, returns success + * Usually aborts on failure. If errors are explicitly ignored, returns success. + * + * @param $sql String: SQL query + * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST comment (you can use __METHOD__ or add some extra info) + * @param $tempIgnore Bool: Whether to avoid throwing an exception on errors... maybe best to catch the exception instead? + * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure if $tempIgnore set + * @throws DBQueryError Thrown when the database returns an error of any kind */ - function query( $sql, $fname = '', $tempIgnore = false ) { + public function query( $sql, $fname = '', $tempIgnore = false ) { global $wgProfiling; if ( $wgProfiling ) { @@ -626,11 +707,21 @@ class Database { $this->mLastQuery = $sql; # Add a comment for easy SHOW PROCESSLIST interpretation - if ( $fname ) { - $commentedSql = preg_replace('/\s/', " /* $fname */ ", $sql, 1); - } else { - $commentedSql = $sql; - } + #if ( $fname ) { + global $wgUser; + if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) { + $userName = $wgUser->getName(); + if ( strlen( $userName ) > 15 ) { + $userName = substr( $userName, 0, 15 ) . '...'; + } + $userName = str_replace( '/', '', $userName ); + } else { + $userName = ''; + } + $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1); + #} else { + # $commentedSql = $sql; + #} # If DBO_TRX is set, start a transaction if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() && @@ -655,6 +746,11 @@ class Database { wfDebug( "Connection lost, reconnecting...\n" ); if ( $this->ping() ) { wfDebug( "Reconnected\n" ); + $sqlx = substr( $commentedSql, 0, 500 ); + $sqlx = strtr( $sqlx, "\t\n", ' ' ); + global $wgRequestTime; + $elapsed = round( microtime(true) - $wgRequestTime, 3 ); + wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" ); $ret = $this->doQuery( $commentedSql ); } else { wfDebug( "Failed\n" ); @@ -674,9 +770,11 @@ class Database { /** * The DBMS-dependent part of query() - * @param string $sql SQL query. + * @param $sql String: SQL query. + * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure + * @access private */ - function doQuery( $sql ) { + /*private*/ function doQuery( $sql ) { if( $this->bufferResults() ) { $ret = mysql_query( $sql, $this->mConn ); } else { @@ -817,7 +915,13 @@ class Database { } /** - * Fetch the next row from the given result object, in object form + * Fetch the next row from the given result object, in object form. + * Fields can be retrieved with $row->fieldname, with fields acting like + * member variables. + * + * @param $res SQL result object as returned from Database::query(), etc. + * @return MySQL row object + * @throws DBUnexpectedError Thrown if the database returns an error */ function fetchObject( $res ) { @/**/$row = mysql_fetch_object( $res ); @@ -828,8 +932,12 @@ class Database { } /** - * Fetch the next row from the given result object - * Returns an array + * Fetch the next row from the given result object, in associative array + * form. Fields are retrieved with $row['fieldname']. + * + * @param $res SQL result object as returned from Database::query(), etc. + * @return MySQL row object + * @throws DBUnexpectedError Thrown if the database returns an error */ function fetchRow( $res ) { @/**/$row = mysql_fetch_array( $res ); @@ -972,7 +1080,7 @@ class Database { * @return array */ function makeSelectOptions( $options ) { - $tailOpts = ''; + $preLimitTail = $postLimitTail = ''; $startOpts = ''; $noKeyOptions = array(); @@ -982,16 +1090,17 @@ class Database { } } - if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}"; - if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}"; + if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; + if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; - if (isset($options['LIMIT'])) { - $tailOpts .= $this->limitResult('', $options['LIMIT'], - isset($options['OFFSET']) ? $options['OFFSET'] : false); - } - - if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; - if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; + //if (isset($options['LIMIT'])) { + // $tailOpts .= $this->limitResult('', $options['LIMIT'], + // isset($options['OFFSET']) ? $options['OFFSET'] + // : false); + //} + + if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE'; + if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE'; if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; # Various MySQL extensions @@ -1010,7 +1119,7 @@ class Database { $useIndex = ''; } - return array( $startOpts, $useIndex, $tailOpts ); + return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); } /** @@ -1038,20 +1147,33 @@ class Database { else $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) ); } elseif ($table!='') { - $from = ' FROM ' . $this->tableName( $table ); + if ($table{0}==' ') { + $from = ' FROM ' . $table; + } else { + $from = ' FROM ' . $this->tableName( $table ); + } } else { $from = ''; } - list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $options ); + list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options ); if( !empty( $conds ) ) { if ( is_array( $conds ) ) { $conds = $this->makeList( $conds, LIST_AND ); } - $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $tailOpts"; + $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; } else { - $sql = "SELECT $startOpts $vars $from $useIndex $tailOpts"; + $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; + } + + if (isset($options['LIMIT'])) + $sql = $this->limitResult($sql, $options['LIMIT'], + isset($options['OFFSET']) ? $options['OFFSET'] : false); + $sql = "$sql $postLimitTail"; + + if (isset($options['EXPLAIN'])) { + $sql = 'EXPLAIN ' . $sql; } return $this->query( $sql, $fname ); @@ -1085,6 +1207,33 @@ class Database { return $obj; } + + /** + * Estimate rows in dataset + * Returns estimated count, based on EXPLAIN output + * Takes same arguments as Database::select() + */ + + function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { + $options['EXPLAIN']=true; + $res = $this->select ($table, $vars, $conds, $fname, $options ); + if ( $res === false ) + return false; + if (!$this->numRows($res)) { + $this->freeResult($res); + return 0; + } + + $rows=1; + + while( $plan = $this->fetchObject( $res ) ) { + $rows *= ($plan->rows > 0)?$plan->rows:1; // avoid resetting to zero + } + + $this->freeResult($res); + return $rows; + } + /** * Removes most variables from an SQL query and replaces them with X or N for numbers. @@ -1207,7 +1356,7 @@ class Database { for( $i = 0; $i < $n; $i++ ) { $meta = mysql_fetch_field( $res, $i ); if( $field == $meta->name ) { - return $meta; + return new MySQLField($meta); } } return false; @@ -1417,7 +1566,7 @@ class Database { } /** - * @desc: Fetch a number of table names into an zero-indexed numerical array + * Fetch a number of table names into an zero-indexed numerical array * This is handy when you need to construct SQL for joins * * Example: @@ -1952,20 +2101,50 @@ class Database { } /** + * Override database's default connection timeout. + * May be useful for very long batch queries such as + * full-wiki dumps, where a single query reads out + * over hours or days. + * @param int $timeout in seconds + */ + public function setTimeout( $timeout ) { + $this->query( "SET net_read_timeout=$timeout" ); + $this->query( "SET net_write_timeout=$timeout" ); + } + + /** * Read and execute SQL commands from a file. * Returns true on success, error string on failure + * @param string $filename File name to open + * @param callback $lineCallback Optional function called before reading each line + * @param callback $resultCallback Optional function called for each MySQL result */ - function sourceFile( $filename ) { + function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) { $fp = fopen( $filename, 'r' ); if ( false === $fp ) { return "Could not open \"{$filename}\".\n"; } + $error = $this->sourceStream( $fp, $lineCallback, $resultCallback ); + fclose( $fp ); + return $error; + } + /** + * Read and execute commands from an open file handle + * Returns true on success, error string on failure + * @param string $fp File handle + * @param callback $lineCallback Optional function called before reading each line + * @param callback $resultCallback Optional function called for each MySQL result + */ + function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) { $cmd = ""; $done = false; $dollarquote = false; while ( ! feof( $fp ) ) { + if ( $lineCallback ) { + call_user_func( $lineCallback ); + } $line = trim( fgets( $fp, 1024 ) ); $sl = strlen( $line ) - 1; @@ -1995,7 +2174,10 @@ class Database { if ( $done ) { $cmd = str_replace(';;', ";", $cmd); $cmd = $this->replaceVars( $cmd ); - $res = $this->query( $cmd, 'dbsource', true ); + $res = $this->query( $cmd, __METHOD__, true ); + if ( $resultCallback ) { + call_user_func( $resultCallback, $this->resultObject( $res ) ); + } if ( false === $res ) { $err = $this->lastError(); @@ -2006,10 +2188,10 @@ class Database { $done = false; } } - fclose( $fp ); return true; } + /** * Replace variables in sourced SQL */ @@ -2017,7 +2199,7 @@ class Database { $varnames = array( 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser', 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword', - 'wgDBadminuser', 'wgDBadminpassword', + 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions', ); // Ordinary variables @@ -2050,7 +2232,7 @@ class Database { * Database abstraction object for mySQL * Inherit all methods and properties of Database::Database() * - * @package MediaWiki + * @addtogroup Database * @see Database */ class DatabaseMysql extends Database { @@ -2060,8 +2242,7 @@ class DatabaseMysql extends Database { /** * Result wrapper for grabbing data queried by someone else - * - * @package MediaWiki + * @addtogroup Database */ class ResultWrapper { var $db, $result; @@ -2107,6 +2288,12 @@ class ResultWrapper { function seek( $row ) { $this->db->dataSeek( $this->result, $row ); } + + function rewind() { + if ($this->numRows()) { + $this->db->dataSeek($this->result, 0); + } + } } diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php index ca83b9e5..4b31b4f0 100644 --- a/includes/DatabaseFunctions.php +++ b/includes/DatabaseFunctions.php @@ -3,7 +3,6 @@ * Legacy database functions, for compatibility with pre-1.3 code * NOTE: this file is no longer loaded by default. * - * @package MediaWiki */ /** @@ -18,7 +17,7 @@ function wfQuery( $sql, $db, $fname = '' ) { # Someone has tried to call this the old way throw new FatalError( wfMsgNoDB( 'wrong_wfQuery_params', $db, $sql ) ); } - $c =& wfGetDB( $db ); + $c = wfGetDB( $db ); if ( $c !== false ) { return $c->query( $sql, $fname ); } else { @@ -34,7 +33,7 @@ function wfQuery( $sql, $db, $fname = '' ) { * @return Array: first row from the database */ function wfSingleQuery( $sql, $dbi, $fname = '' ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); $res = $db->query($sql, $fname ); $row = $db->fetchRow( $res ); $ret = $row[0]; @@ -54,7 +53,7 @@ function wfSingleQuery( $sql, $dbi, $fname = '' ) { * @return Returns the previous state. */ function wfIgnoreSQLErrors( $newstate, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->ignoreErrors( $newstate ); } else { @@ -73,7 +72,7 @@ function wfIgnoreSQLErrors( $newstate, $dbi = DB_LAST ) { */ function wfFreeResult( $res, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { $db->freeResult( $res ); return true; @@ -87,7 +86,7 @@ function wfFreeResult( $res, $dbi = DB_LAST ) * @return object|false object we requested */ function wfFetchObject( $res, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->fetchObject( $res, $dbi = DB_LAST ); } else { @@ -100,7 +99,7 @@ function wfFetchObject( $res, $dbi = DB_LAST ) { * @return object|false row we requested */ function wfFetchRow( $res, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->fetchRow ( $res, $dbi = DB_LAST ); } else { @@ -113,7 +112,7 @@ function wfFetchRow( $res, $dbi = DB_LAST ) { * @return integer|false number of rows */ function wfNumRows( $res, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->numRows( $res, $dbi = DB_LAST ); } else { @@ -126,7 +125,7 @@ function wfNumRows( $res, $dbi = DB_LAST ) { * @return integer|false number of fields */ function wfNumFields( $res, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->numFields( $res ); } else { @@ -143,7 +142,7 @@ function wfNumFields( $res, $dbi = DB_LAST ) { */ function wfFieldName( $res, $n, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->fieldName( $res, $n, $dbi = DB_LAST ); } else { @@ -156,7 +155,7 @@ function wfFieldName( $res, $n, $dbi = DB_LAST ) * @todo document function */ function wfInsertId( $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->insertId(); } else { @@ -168,7 +167,7 @@ function wfInsertId( $dbi = DB_LAST ) { * @todo document function */ function wfDataSeek( $res, $row, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->dataSeek( $res, $row ); } else { @@ -180,7 +179,7 @@ function wfDataSeek( $res, $row, $dbi = DB_LAST ) { * @todo document function */ function wfLastErrno( $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->lastErrno(); } else { @@ -192,7 +191,7 @@ function wfLastErrno( $dbi = DB_LAST ) { * @todo document function */ function wfLastError( $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->lastError(); } else { @@ -204,7 +203,7 @@ function wfLastError( $dbi = DB_LAST ) { * @todo document function */ function wfAffectedRows( $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->affectedRows(); } else { @@ -216,7 +215,7 @@ function wfAffectedRows( $dbi = DB_LAST ) { * @todo document function */ function wfLastDBquery( $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->lastQuery(); } else { @@ -235,7 +234,7 @@ function wfLastDBquery( $dbi = DB_LAST ) { */ function wfSetSQL( $table, $var, $value, $cond, $dbi = DB_MASTER ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->set( $table, $var, $value, $cond ); } else { @@ -254,7 +253,7 @@ function wfSetSQL( $table, $var, $value, $cond, $dbi = DB_MASTER ) */ function wfGetSQL( $table, $var, $cond='', $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->selectField( $table, $var, $cond ); } else { @@ -271,7 +270,7 @@ function wfGetSQL( $table, $var, $cond='', $dbi = DB_LAST ) * @return Result of Database::fieldExists() or false. */ function wfFieldExists( $table, $field, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->fieldExists( $table, $field ); } else { @@ -288,7 +287,7 @@ function wfFieldExists( $table, $field, $dbi = DB_LAST ) { * @return Result of Database::indexExists() or false. */ function wfIndexExists( $table, $index, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->indexExists( $table, $index ); } else { @@ -306,7 +305,7 @@ function wfIndexExists( $table, $index, $dbi = DB_LAST ) { * @return result of Database::insert() or false. */ function wfInsertArray( $table, $array, $fname = 'wfInsertArray', $dbi = DB_MASTER ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->insert( $table, $array, $fname ); } else { @@ -325,7 +324,7 @@ function wfInsertArray( $table, $array, $fname = 'wfInsertArray', $dbi = DB_MAST * @return result of Database::getArray() or false. */ function wfGetArray( $table, $vars, $conds, $fname = 'wfGetArray', $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->getArray( $table, $vars, $conds, $fname ); } else { @@ -344,7 +343,7 @@ function wfGetArray( $table, $vars, $conds, $fname = 'wfGetArray', $dbi = DB_LAS * @todo document function */ function wfUpdateArray( $table, $values, $conds, $fname = 'wfUpdateArray', $dbi = DB_MASTER ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { $db->update( $table, $values, $conds, $fname ); return true; @@ -357,7 +356,7 @@ function wfUpdateArray( $table, $values, $conds, $fname = 'wfUpdateArray', $dbi * @todo document function */ function wfTableName( $name, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->tableName( $name ); } else { @@ -369,7 +368,7 @@ function wfTableName( $name, $dbi = DB_LAST ) { * @todo document function */ function wfStrencode( $s, $dbi = DB_LAST ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->strencode( $s ); } else { @@ -381,7 +380,7 @@ function wfStrencode( $s, $dbi = DB_LAST ) { * @todo document function */ function wfNextSequenceValue( $seqName, $dbi = DB_MASTER ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->nextSequenceValue( $seqName ); } else { @@ -393,7 +392,7 @@ function wfNextSequenceValue( $seqName, $dbi = DB_MASTER ) { * @todo document function */ function wfUseIndexClause( $index, $dbi = DB_SLAVE ) { - $db =& wfGetDB( $dbi ); + $db = wfGetDB( $dbi ); if ( $db !== false ) { return $db->useIndexClause( $index ); } else { diff --git a/includes/DatabaseOracle.php b/includes/DatabaseOracle.php index 1a6f62f2..2b720df7 100644 --- a/includes/DatabaseOracle.php +++ b/includes/DatabaseOracle.php @@ -1,44 +1,141 @@ <?php /** - * Oracle. - * - * @package MediaWiki + * This is the Oracle database abstraction layer. + * @addtogroup Database */ +class ORABlob { + var $mData; -class OracleBlob extends DBObject { - function isLOB() { - return true; + function __construct($data) { + $this->mData = $data; } - function data() { + + function getData() { return $this->mData; } -}; +} + +/** + * The oci8 extension is fairly weak and doesn't support oci_num_rows, among + * other things. We use a wrapper class to handle that and other + * Oracle-specific bits, like converting column names back to lowercase. + * @addtogroup Database + */ +class ORAResult { + private $rows; + private $cursor; + private $stmt; + private $nrows; + private $db; + + function __construct(&$db, $stmt) { + $this->db =& $db; + if (($this->nrows = oci_fetch_all($stmt, $this->rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM)) === false) { + $e = oci_error($stmt); + $db->reportQueryError($e['message'], $e['code'], '', __FUNCTION__); + return; + } + + $this->cursor = 0; + $this->stmt = $stmt; + } + + function free() { + oci_free_statement($this->stmt); + } + + function seek($row) { + $this->cursor = min($row, $this->nrows); + } + + function numRows() { + return $this->nrows; + } + + function numFields() { + return oci_num_fields($this->stmt); + } + + function fetchObject() { + if ($this->cursor >= $this->nrows) + return false; + + $row = $this->rows[$this->cursor++]; + $ret = new stdClass(); + foreach ($row as $k => $v) { + $lc = strtolower(oci_field_name($this->stmt, $k + 1)); + $ret->$lc = $v; + } + + return $ret; + } + + function fetchAssoc() { + if ($this->cursor >= $this->nrows) + return false; + + $row = $this->rows[$this->cursor++]; + $ret = array(); + foreach ($row as $k => $v) { + $lc = strtolower(oci_field_name($this->stmt, $k + 1)); + $ret[$lc] = $v; + $ret[$k] = $v; + } + return $ret; + } +} /** - * - * @package MediaWiki + * @addtogroup Database */ class DatabaseOracle extends Database { var $mInsertId = NULL; var $mLastResult = NULL; - var $mFetchCache = array(); - var $mFetchID = array(); - var $mNcols = array(); - var $mFieldNames = array(), $mFieldTypes = array(); - var $mAffectedRows = array(); - var $mErr; + var $numeric_version = NULL; + var $lastResult = null; + var $cursor = 0; + var $mAffectedRows; function DatabaseOracle($server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) + $failFunction = false, $flags = 0 ) { - Database::Database( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix ); + + global $wgOut; + # Can't get a reference if it hasn't been set yet + if ( !isset( $wgOut ) ) { + $wgOut = NULL; + } + $this->mOut =& $wgOut; + $this->mFailFunction = $failFunction; + $this->mFlags = $flags; + $this->open( $server, $user, $password, $dbName); + + } + + function cascadingDeletes() { + return true; + } + function cleanupTriggers() { + return true; + } + function strictIPs() { + return true; + } + function realTimestamps() { + return true; + } + function implicitGroupby() { + return false; + } + function searchableIPs() { + return true; } - /* static */ function newFromParams( $server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) + static function newFromParams( $server = false, $user = false, $password = false, $dbName = false, + $failFunction = false, $flags = 0) { - return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix ); + return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags ); } /** @@ -47,23 +144,33 @@ class DatabaseOracle extends Database { */ function open( $server, $user, $password, $dbName ) { if ( !function_exists( 'oci_connect' ) ) { - throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n" ); + throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" ); } + + # Needed for proper UTF-8 functionality + putenv("NLS_LANG=AMERICAN_AMERICA.AL32UTF8"); + $this->close(); $this->mServer = $server; $this->mUser = $user; $this->mPassword = $password; $this->mDBname = $dbName; - $this->mConn = oci_new_connect($user, $password, $dbName, "AL32UTF8"); - if ( $this->mConn === false ) { - wfDebug( "DB connection error\n" ); - wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " - . substr( $password, 0, 3 ) . "...\n" ); - wfDebug( $this->lastError()."\n" ); - } else { - $this->mOpened = true; + if (!strlen($user)) { ## e.g. the class is being loaded + return; + } + + error_reporting( E_ALL ); + $this->mConn = oci_connect($user, $password, $dbName); + + if ($this->mConn == false) { + wfDebug("DB connection error\n"); + wfDebug("Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n"); + wfDebug($this->lastError()."\n"); + return false; } + + $this->mOpened = true; return $this->mConn; } @@ -73,116 +180,67 @@ class DatabaseOracle extends Database { */ function close() { $this->mOpened = false; - if ($this->mConn) { - return oci_close($this->mConn); + if ( $this->mConn ) { + return oci_close( $this->mConn ); } else { return true; } } - function parseStatement($sql) { - $this->mErr = $this->mLastResult = false; - if (($stmt = oci_parse($this->mConn, $sql)) === false) { - $this->lastError(); - return $this->mLastResult = false; - } - $this->mAffectedRows[$stmt] = 0; - return $this->mLastResult = $stmt; + function execFlags() { + return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; } function doQuery($sql) { - if (($stmt = $this->parseStatement($sql)) === false) - return false; - return $this->executeStatement($stmt); - } + wfDebug("SQL: [$sql]\n"); + if (!mb_check_encoding($sql)) { + throw new MWException("SQL encoding is invalid"); + } - function executeStatement($stmt) { - if (!oci_execute($stmt, OCI_DEFAULT)) { - $this->lastError(); - oci_free_statement($stmt); - return false; + if (($this->mLastResult = $stmt = oci_parse($this->mConn, $sql)) === false) { + $e = oci_error($this->mConn); + $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__); } - $this->mAffectedRows[$stmt] = oci_num_rows($stmt); - $this->mFetchCache[$stmt] = array(); - $this->mFetchID[$stmt] = 0; - $this->mNcols[$stmt] = oci_num_fields($stmt); - if ($this->mNcols[$stmt] == 0) - return $this->mLastResult; - for ($i = 1; $i <= $this->mNcols[$stmt]; $i++) { - $this->mFieldNames[$stmt][$i] = oci_field_name($stmt, $i); - $this->mFieldTypes[$stmt][$i] = oci_field_type($stmt, $i); + + if (oci_execute($stmt, $this->execFlags()) == false) { + $e = oci_error($stmt); + $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__); } - while (($o = oci_fetch_array($stmt)) !== false) { - foreach ($o as $key => $value) { - if (is_object($value)) { - $o[$key] = $value->load(); - } - } - $this->mFetchCache[$stmt][] = $o; + if (oci_statement_type($stmt) == "SELECT") + return new ORAResult($this, $stmt); + else { + $this->mAffectedRows = oci_num_rows($stmt); + return true; } - return $this->mLastResult; } - function queryIgnore( $sql, $fname = '' ) { - return $this->query( $sql, $fname, true ); + function queryIgnore($sql, $fname = '') { + return $this->query($sql, $fname, true); } - function freeResult( $res ) { - if (!oci_free_statement($res)) { - throw new DBUnexpectedError( $this, "Unable to free Oracle result\n" ); - } - unset($this->mFetchID[$res]); - unset($this->mFetchCache[$res]); - unset($this->mNcols[$res]); - unset($this->mFieldNames[$res]); - unset($this->mFieldTypes[$res]); + function freeResult($res) { + $res->free(); } - function fetchAssoc($res) { - if ($this->mFetchID[$res] >= count($this->mFetchCache[$res])) - return false; - - for ($i = 1; $i <= $this->mNcols[$res]; $i++) { - $name = $this->mFieldNames[$res][$i]; - if (isset($this->mFetchCache[$res][$this->mFetchID[$res]][$name])) - $value = $this->mFetchCache[$res][$this->mFetchID[$res]][$name]; - else $value = NULL; - $key = strtolower($name); - wfdebug("'$key' => '$value'\n"); - $ret[$key] = $value; - } - $this->mFetchID[$res]++; - return $ret; + function fetchObject($res) { + return $res->fetchObject(); } function fetchRow($res) { - $r = $this->fetchAssoc($res); - if (!$r) - return false; - $i = 0; - $ret = array(); - foreach ($r as $value) { - wfdebug("ret[$i]=[$value]\n"); - $ret[$i++] = $value; - } - return $ret; + return $res->fetchAssoc(); } - function fetchObject($res) { - $row = $this->fetchAssoc($res); - if (!$row) - return false; - $ret = new stdClass; - foreach ($row as $key => $value) - $ret->$key = $value; - return $ret; + function numRows($res) { + return $res->numRows(); } - function numRows($res) { - return count($this->mFetchCache[$res]); + function numFields($res) { + return $res->numFields(); + } + + function fieldName($stmt, $n) { + return pg_field_name($stmt, $n); } - function numFields( $res ) { return pg_num_fields( $res ); } - function fieldName( $res, $n ) { return pg_field_name( $res, $n ); } /** * This must be called after nextSequenceVal @@ -192,139 +250,153 @@ class DatabaseOracle extends Database { } function dataSeek($res, $row) { - $this->mFetchID[$res] = $row; + $res->seek($row); } function lastError() { - if ($this->mErr === false) { - if ($this->mLastResult !== false) { - $what = $this->mLastResult; - } else if ($this->mConn !== false) { - $what = $this->mConn; - } else { - $what = false; - } - $err = ($what !== false) ? oci_error($what) : oci_error(); - if ($err === false) { - $this->mErr = 'no error'; - } else { - $this->mErr = $err['message']; - } - } - return str_replace("\n", '<br />', $this->mErr); + if ($this->mConn === false) + $e = oci_error(); + else + $e = oci_error($this->mConn); + return $e['message']; } + function lastErrno() { - return 0; + if ($this->mConn === false) + $e = oci_error(); + else + $e = oci_error($this->mConn); + return $e['code']; } function affectedRows() { - return $this->mAffectedRows[$this->mLastResult]; + return $this->mAffectedRows; } /** * Returns information about an index * If errors are explicitly ignored, returns NULL on failure */ - function indexInfo ($table, $index, $fname = 'Database::indexInfo' ) { - $table = $this->tableName($table, true); - if ($index == 'PRIMARY') - $index = "${table}_pk"; - $sql = "SELECT uniqueness FROM all_indexes WHERE table_name='" . - $table . "' AND index_name='" . - $this->strencode(strtoupper($index)) . "'"; - $res = $this->query($sql, $fname); - if (!$res) - return NULL; - if (($row = $this->fetchObject($res)) == NULL) - return false; - $this->freeResult($res); - $row->Non_unique = !$row->uniqueness; - return $row; - - // BUG: !!!! This code needs to be synced up with database.php - + function indexInfo( $table, $index, $fname = 'Database::indexExists' ) { + return false; } - function indexUnique ($table, $index, $fname = 'indexUnique') { - if (!($i = $this->indexInfo($table, $index, $fname))) - return $i; - return $i->uniqueness == 'UNIQUE'; + function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { + return false; } - function fieldInfo( $table, $field ) { - $o = new stdClass; - $o->multiple_key = true; /* XXX */ - return $o; - } + function insert( $table, $a, $fname = 'Database::insert', $options = array() ) { + if (!is_array($options)) + $options = array($options); - function getColumnInformation($table, $field) { - $table = $this->tableName($table, true); - $field = strtoupper($field); + #if (in_array('IGNORE', $options)) + # $oldIgnore = $this->ignoreErrors(true); - $res = $this->doQuery("SELECT * FROM all_tab_columns " . - "WHERE table_name='".$table."' " . - "AND column_name='".$field."'"); - if (!$res) - return false; - $o = $this->fetchObject($res); - $this->freeResult($res); - return $o; - } + # IGNORE is performed using single-row inserts, ignoring errors in each + # FIXME: need some way to distiguish between key collision and other types of error + //$oldIgnore = $this->ignoreErrors(true); + if (!is_array(reset($a))) { + $a = array($a); + } + foreach ($a as $row) { + $this->insertOneRow($table, $row, $fname); + } + //$this->ignoreErrors($oldIgnore); + $retVal = true; - function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) { - $column = $this->getColumnInformation($table, $field); - if (!$column) - return false; - return true; + //if (in_array('IGNORE', $options)) + // $this->ignoreErrors($oldIgnore); + + return $retVal; } - function tableName($name, $forddl = false) { - # First run any transformations from the parent object - $name = parent::tableName( $name ); + function insertOneRow($table, $row, $fname) { + // "INSERT INTO tables (a, b, c)" + $sql = "INSERT INTO " . $this->tableName($table) . " (" . join(',', array_keys($row)) . ')'; + $sql .= " VALUES ("; + + // for each value, append ":key" + $first = true; + $returning = ''; + foreach ($row as $col => $val) { + if (is_object($val)) { + $what = "EMPTY_BLOB()"; + assert($returning === ''); + $returning = " RETURNING $col INTO :bval"; + $blobcol = $col; + } else + $what = ":$col"; + + if ($first) + $sql .= "$what"; + else + $sql.= ", $what"; + $first = false; + } + $sql .= ") $returning"; + + $stmt = oci_parse($this->mConn, $sql); + foreach ($row as $col => $val) { + if (!is_object($val)) { + if (oci_bind_by_name($stmt, ":$col", $row[$col]) === false) + $this->reportQueryError($this->lastErrno(), $this->lastError(), $sql, __METHOD__); + } + } + + if (($bval = oci_new_descriptor($this->mConn, OCI_D_LOB)) === false) { + $e = oci_error($stmt); + throw new DBUnexpectedError($this, "Cannot create LOB descriptor: " . $e['message']); + } + + if (strlen($returning)) + oci_bind_by_name($stmt, ":bval", $bval, -1, SQLT_BLOB); - # Replace backticks into empty - # Note: "foo" and foo are not the same in Oracle! - $name = str_replace('`', '', $name); + if (oci_execute($stmt, OCI_DEFAULT) === false) { + $e = oci_error($stmt); + $this->reportQueryError($e['message'], $e['code'], $sql, __METHOD__); + } + if (strlen($returning)) { + $bval->save($row[$blobcol]->getData()); + $bval->free(); + } + if (!$this->mTrxLevel) + oci_commit($this->mConn); - # Now quote Oracle reserved keywords + oci_free_statement($stmt); + } + + function tableName( $name ) { + # Replace reserved words with better ones switch( $name ) { case 'user': - case 'group': - case 'validate': - if ($forddl) - return $name; - else - return '"' . $name . '"'; - + return 'mwuser'; + case 'text': + return 'pagecontent'; default: - return strtoupper($name); + return $name; } } - function strencode( $s ) { - return str_replace("'", "''", $s); - } - /** * Return the next in a sequence, save the value for retrieval via insertId() */ - function nextSequenceValue( $seqName ) { - $r = $this->doQuery("SELECT $seqName.nextval AS val FROM dual"); - $o = $this->fetchObject($r); - $this->freeResult($r); - return $this->mInsertId = (int)$o->val; + function nextSequenceValue($seqName) { + $res = $this->query("SELECT $seqName.nextval FROM dual"); + $row = $this->fetchRow($res); + $this->mInsertId = $row[0]; + $this->freeResult($res); + return $this->mInsertId; } /** - * USE INDEX clause - * PostgreSQL doesn't have them and returns "" + * Oracle does not have a "USE INDEX" clause, so return an empty string */ - function useIndexClause( $index ) { + function useIndexClause($index) { return ''; } # REPLACE query wrapper - # PostgreSQL simulates this with a DELETE followed by INSERT + # Oracle simulates this with a DELETE followed by INSERT # $row is the row to insert, an associative array # $uniqueIndexes is an array of indexes. Each element may be either a # field name or an array of field names @@ -333,15 +405,15 @@ class DatabaseOracle extends Database { # However if you do this, you run the risk of encountering errors which wouldn't have # occurred in MySQL function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) { - $table = $this->tableName( $table ); + $table = $this->tableName($table); if (count($rows)==0) { return; } # Single row case - if ( !is_array( reset( $rows ) ) ) { - $rows = array( $rows ); + if (!is_array(reset($rows))) { + $rows = array($rows); } foreach( $rows as $row ) { @@ -377,14 +449,14 @@ class DatabaseOracle extends Database { # Now insert the row $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' . $this->makeList( $row, LIST_COMMA ) . ')'; - $this->query( $sql, $fname ); + $this->query($sql, $fname); } } # DELETE where the condition is a join function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { if ( !$conds ) { - throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' ); + throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); } $delTable = $this->tableName( $delTable ); @@ -421,17 +493,14 @@ class DatabaseOracle extends Database { } function limitResult($sql, $limit, $offset) { - $ret = "SELECT * FROM ($sql) WHERE ROWNUM < " . ((int)$limit + (int)($offset+1)); - if (is_numeric($offset)) - $ret .= " AND ROWNUM >= " . (int)$offset; - return $ret; - } - function limitResultForUpdate($sql, $limit) { - return $sql; + if ($offset === false) + $offset = 0; + return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < 1 + $limit + $offset"; } + /** * Returns an SQL expression for a simple conditional. - * Uses CASE on PostgreSQL. + * Uses CASE on Oracle * * @param string $cond SQL expression which will result in a boolean value * @param string $trueVal SQL expression to return if true @@ -442,15 +511,12 @@ class DatabaseOracle extends Database { return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; } - # FIXME: actually detecting deadlocks might be nice function wasDeadlock() { - return false; + return $this->lastErrno() == 'OCI-00060'; } - # Return DB-style timestamp used for MySQL schema function timestamp($ts = 0) { - return $this->strencode(wfTimestamp(TS_ORACLE, $ts)); -# return "TO_TIMESTAMP('" . $this->strencode(wfTimestamp(TS_DB, $ts)) . "', 'RRRR-MM-DD HH24:MI:SS')"; + return wfTimestamp(TS_ORACLE, $ts); } /** @@ -460,13 +526,25 @@ class DatabaseOracle extends Database { return $valuedata; } + function reportQueryError($error, $errno, $sql, $fname, $tempIgnore = false) { + # Ignore errors during error handling to avoid infinite + # recursion + $ignore = $this->ignoreErrors(true); + ++$this->mErrorCount; - function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { - $message = "A database error has occurred\n" . - "Query: $sql\n" . - "Function: $fname\n" . - "Error: $errno $error\n"; - throw new DBUnexpectedError($this, $message); + if ($ignore || $tempIgnore) { +echo "error ignored! query = [$sql]\n"; + wfDebug("SQL ERROR (ignored): $error\n"); + $this->ignoreErrors( $ignore ); + } + else { +echo "error!\n"; + $message = "A database error has occurred\n" . + "Query: $sql\n" . + "Function: $fname\n" . + "Error: $errno $error\n"; + throw new DBUnexpectedError($this, $message); + } } /** @@ -483,209 +561,125 @@ class DatabaseOracle extends Database { return oci_server_version($this->mConn); } - function setSchema($schema=false) { - $schemas=$this->mSchemas; - if ($schema) { array_unshift($schemas,$schema); } - $searchpath=$this->makeList($schemas,LIST_NAMES); - $this->query("SET search_path = $searchpath"); + /** + * Query whether a given table exists (in the given schema, or the default mw one if not given) + */ + function tableExists($table) { + $etable= $this->addQuotes($table); + $SQL = "SELECT 1 FROM user_tables WHERE table_name='$etable'"; + $res = $this->query($SQL); + $count = $res ? oci_num_rows($res) : 0; + if ($res) + $this->freeResult($res); + return $count; } - function begin() { + /** + * Query whether a given column exists in the mediawiki schema + */ + function fieldExists( $table, $field ) { + return true; // XXX } - function immediateCommit( $fname = 'Database::immediateCommit' ) { - oci_commit($this->mConn); - $this->mTrxLevel = 0; + function fieldInfo( $table, $field ) { + return false; // XXX } - function rollback( $fname = 'Database::rollback' ) { - oci_rollback($this->mConn); - $this->mTrxLevel = 0; + + function begin( $fname = '' ) { + $this->mTrxLevel = 1; } - function getLag() { - return false; + function immediateCommit( $fname = '' ) { + return true; } - function getStatus($which=null) { - $result = array('Threads_running' => 0, 'Threads_connected' => 0); - return $result; + function commit( $fname = '' ) { + oci_commit($this->mConn); + $this->mTrxLevel = 0; } - /** - * Returns an optional USE INDEX clause to go after the table, and a - * string to go at the end of the query - * - * @access private - * - * @param array $options an associative array of options to be turned into - * an SQL query, valid keys are listed in the function. - * @return array - */ - function makeSelectOptions($options) { - $tailOpts = ''; - - if (isset( $options['ORDER BY'])) { - $tailOpts .= " ORDER BY {$options['ORDER BY']}"; - } + /* Not even sure why this is used in the main codebase... */ + function limitResultForUpdate($sql, $num) { + return $sql; + } - return array('', $tailOpts); + function strencode($s) { + return str_replace("'", "''", $s); } - function maxListLen() { - return 1000; + function encodeBlob($b) { + return new ORABlob($b); + } + function decodeBlob($b) { + return $b; //return $b->load(); } - /** - * Query whether a given table exists - */ - function tableExists( $table ) { - $table = $this->tableName($table, true); - $res = $this->query( "SELECT COUNT(*) as NUM FROM user_tables WHERE table_name='" - . $table . "'" ); - if (!$res) - return false; - $row = $this->fetchObject($res); - $this->freeResult($res); - return $row->num >= 1; + function addQuotes( $s ) { + global $wgLang; + $s = $wgLang->checkTitleEncoding($s); + return "'" . $this->strencode($s) . "'"; } - /** - * UPDATE wrapper, takes a condition array and a SET array - */ - function update( $table, $values, $conds, $fname = 'Database::update' ) { - $table = $this->tableName( $table ); + function quote_ident( $s ) { + return $s; + } - $sql = "UPDATE $table SET "; - $first = true; - foreach ($values as $field => $v) { - if ($first) - $first = false; - else - $sql .= ", "; - $sql .= "$field = :n$field "; - } - if ( $conds != '*' ) { - $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); - } - $stmt = $this->parseStatement($sql); - if ($stmt === false) { - $this->reportQueryError( $this->lastError(), $this->lastErrno(), $stmt ); - return false; - } - if ($this->debug()) - wfDebug("SQL: $sql\n"); - $s = ''; - foreach ($values as $field => $v) { - oci_bind_by_name($stmt, ":n$field", $values[$field]); - if ($this->debug()) - $s .= " [$field] = [$v]\n"; - } - if ($this->debug()) - wfdebug(" PH: $s\n"); - $ret = $this->executeStatement($stmt); - return $ret; + /* For now, does nothing */ + function selectDB( $db ) { + return true; } /** - * INSERT wrapper, inserts an array into a table + * Returns an optional USE INDEX clause to go after the table, and a + * string to go at the end of the query * - * $a may be a single associative array, or an array of these with numeric keys, for - * multi-row insert. + * @private * - * Usually aborts on failure - * If errors are explicitly ignored, returns success + * @param array $options an associative array of options to be turned into + * an SQL query, valid keys are listed in the function. + * @return array */ - function insert( $table, $a, $fname = 'Database::insert', $options = array() ) { - # No rows to insert, easy just return now - if ( !count( $a ) ) { - return true; - } - - $table = $this->tableName( $table ); - if (!is_array($options)) - $options = array($options); - - $oldIgnore = false; - if (in_array('IGNORE', $options)) - $oldIgnore = $this->ignoreErrors( true ); - - if ( isset( $a[0] ) && is_array( $a[0] ) ) { - $multi = true; - $keys = array_keys( $a[0] ); - } else { - $multi = false; - $keys = array_keys( $a ); + function makeSelectOptions( $options ) { + $preLimitTail = $postLimitTail = ''; + $startOpts = ''; + + $noKeyOptions = array(); + foreach ( $options as $key => $option ) { + if ( is_numeric( $key ) ) { + $noKeyOptions[$option] = true; + } } - $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ('; - $return = ''; - $first = true; - foreach ($a as $key => $value) { - if ($first) - $first = false; - else - $sql .= ", "; - if (is_object($value) && $value->isLOB()) { - $sql .= "EMPTY_BLOB()"; - $return = "RETURNING $key INTO :bobj"; - } else - $sql .= ":$key"; + if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; + if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; + + if (isset($options['LIMIT'])) { + // $tailOpts .= $this->limitResult('', $options['LIMIT'], + // isset($options['OFFSET']) ? $options['OFFSET'] + // : false); } - $sql .= ") $return"; - if ($this->debug()) { - wfDebug("SQL: $sql\n"); - } + #if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; + #if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; + if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; - if (($stmt = $this->parseStatement($sql)) === false) { - $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname); - $this->ignoreErrors($oldIgnore); - return false; + if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { + $useIndex = $this->useIndexClause( $options['USE INDEX'] ); + } else { + $useIndex = ''; } + + return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); + } - /* - * If we're inserting multiple rows, parse the statement once and - * execute it for each set of values. Otherwise, convert it into an - * array and pretend. - */ - if (!$multi) - $a = array($a); - - foreach ($a as $key => $row) { - $blob = false; - $bdata = false; - $s = ''; - foreach ($row as $k => $value) { - if (is_object($value) && $value->isLOB()) { - $blob = oci_new_descriptor($this->mConn, OCI_D_LOB); - $bdata = $value->data(); - oci_bind_by_name($stmt, ":bobj", $blob, -1, OCI_B_BLOB); - } else - oci_bind_by_name($stmt, ":$k", $a[$key][$k], -1); - if ($this->debug()) - $s .= " [$k] = {$row[$k]}"; - } - if ($this->debug()) - wfDebug(" PH: $s\n"); - if (($s = $this->executeStatement($stmt)) === false) { - $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname); - $this->ignoreErrors($oldIgnore); - return false; - } - - if ($blob) { - $blob->save($bdata); - } - } - $this->ignoreErrors($oldIgnore); - return $this->mLastResult = $s; + public function setTimeout( $timeout ) { + // @todo fixme no-op } function ping() { + wfDebug( "Function ping() not written for DatabasePostgres.php yet"); return true; } - function encodeBlob($b) { - return new OracleBlob($b); - } -} + +} // end DatabaseOracle class ?> diff --git a/includes/DatabasePostgres.php b/includes/DatabasePostgres.php index 803c0e26..7158e2d1 100644 --- a/includes/DatabasePostgres.php +++ b/includes/DatabasePostgres.php @@ -7,12 +7,69 @@ * than MySQL ones, some of them should be moved to parent * Database class. * - * @package MediaWiki + * @addtogroup Database */ +class PostgresField { + private $name, $tablename, $type, $nullable, $max_length; + + static function fromText($db, $table, $field) { + global $wgDBmwschema; + + $q = <<<END +SELECT typname, attnotnull, attlen +FROM pg_class, pg_namespace, pg_attribute, pg_type +WHERE relnamespace=pg_namespace.oid +AND relkind='r' +AND attrelid=pg_class.oid +AND atttypid=pg_type.oid +AND nspname=%s +AND relname=%s +AND attname=%s; +END; + $res = $db->query(sprintf($q, + $db->addQuotes($wgDBmwschema), + $db->addQuotes($table), + $db->addQuotes($field))); + $row = $db->fetchObject($res); + if (!$row) + return null; + $n = new PostgresField; + $n->type = $row->typname; + $n->nullable = ($row->attnotnull == 'f'); + $n->name = $field; + $n->tablename = $table; + $n->max_length = $row->attlen; + return $n; + } + + function name() { + return $this->name; + } + + function tableName() { + return $this->tablename; + } + + function type() { + return $this->type; + } + + function nullable() { + return $this->nullable; + } + function maxLength() { + return $this->max_length; + } +} + +/** + * @addtogroup Database + */ class DatabasePostgres extends Database { var $mInsertId = NULL; var $mLastResult = NULL; + var $numeric_version = NULL; function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0 ) @@ -25,24 +82,31 @@ class DatabasePostgres extends Database { } $this->mOut =& $wgOut; $this->mFailFunction = $failFunction; - $this->mCascadingDeletes = true; - $this->mCleanupTriggers = true; - $this->mStrictIPs = true; $this->mFlags = $flags; $this->open( $server, $user, $password, $dbName); } + function cascadingDeletes() { + return true; + } + function cleanupTriggers() { + return true; + } + function strictIPs() { + return true; + } function realTimestamps() { return true; } - function implicitGroupby() { return false; } + function searchableIPs() { + return true; + } - static function newFromParams( $server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0) + static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0) { return new DatabasePostgres( $server, $user, $password, $dbName, $failFunction, $flags ); } @@ -57,9 +121,12 @@ class DatabasePostgres extends Database { throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" ); } - global $wgDBport; + if (!strlen($user)) { ## e.g. the class is being loaded + return; + } + $this->close(); $this->mServer = $server; $port = $wgDBport; @@ -75,9 +142,6 @@ class DatabasePostgres extends Database { $hstring .= "port=$port "; } - if (!strlen($user)) { ## e.g. the class is being loaded - return; - } error_reporting( E_ALL ); @$this->mConn = pg_connect("$hstring dbname=$dbName user=$user password=$password"); @@ -94,21 +158,15 @@ class DatabasePostgres extends Database { if (defined('MEDIAWIKI_INSTALL')) { global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, $wgDBts2schema; - print "OK</li>\n"; print "<li>Checking the version of Postgres..."; - $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0); - $thisver = array(); - if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) { - print "<b>FAILED</b> (could not determine the version)</li>\n"; - dieout("</ul>"); - } + $version = $this->getServerVersion(); $PGMINVER = "8.1"; - if ($thisver[1] < $PGMINVER) { - print "<b>FAILED</b>. Required version is $PGMINVER. You have $thisver[1]$thisver[2]</li>\n"; + if ($this->numeric_version < $PGMINVER) { + print "<b>FAILED</b>. Required version is $PGMINVER. You have $this->numeric_version ($version)</li>\n"; dieout("</ul>"); } - print "version $thisver[1]$thisver[2] is OK.</li>\n"; + print "version $this->numeric_version is OK.</li>\n"; $safeuser = $this->quote_ident($wgDBuser); ## Are we connecting as a superuser for the first time? @@ -232,7 +290,8 @@ class DatabasePostgres extends Database { $wgDBsuperuser = ''; return true; ## Reconnect as regular user - } + + } ## end superuser if (!defined('POSTGRES_SEARCHPATH')) { @@ -249,13 +308,24 @@ class DatabasePostgres extends Database { ## Does this user have the rights to the tsearch2 tables? $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0); print "<li>Checking tsearch2 permissions..."; + ## Let's check all four, just to be safe + error_reporting( 0 ); + $ts2tables = array('cfg','cfgmap','dict','parser'); + foreach ( $ts2tables AS $tname ) { + $SQL = "SELECT count(*) FROM $wgDBts2schema.pg_ts_$tname"; + $res = $this->doQuery($SQL); + if (!$res) { + print "<b>FAILED</b> to access pg_ts_$tname. Make sure that the user ". + "\"$wgDBuser\" has SELECT access to all four tsearch2 tables</li>\n"; + dieout("</ul>"); + } + } $SQL = "SELECT ts_name FROM $wgDBts2schema.pg_ts_cfg WHERE locale = '$ctype'"; $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END"; - error_reporting( 0 ); $res = $this->doQuery($SQL); error_reporting( E_ALL ); if (!$res) { - print "<b>FAILED</b>. Make sure that the user \"$wgDBuser\" has SELECT access to the tsearch2 tables</li>\n"; + print "<b>FAILED</b>. Could not determine the tsearch2 locale information</li>\n"; dieout("</ul>"); } print "OK</li>"; @@ -282,7 +352,7 @@ class DatabasePostgres extends Database { $res = $this->doQuery($SQL); if (!$res) { print "<b>FAILED</b>. "; - print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"ctype\"</li>\n"; + print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"$ctype\"</li>\n"; dieout("</ul>"); } print "OK</li>"; @@ -325,9 +395,13 @@ class DatabasePostgres extends Database { $result = $this->schemaExists($wgDBmwschema); if (!$result) { print "<li>Creating schema <b>$wgDBmwschema</b> ..."; + error_reporting( 0 ); $result = $this->doQuery("CREATE SCHEMA $wgDBmwschema"); + error_reporting( E_ALL ); if (!$result) { - print "<b>FAILED</b>.</li>\n"; + print "<b>FAILED</b>. The user \"$wgDBuser\" must be able to access the schema. ". + "You can try making them the owner of the database, or try creating the schema with a ". + "different user, and then grant access to the \"$wgDBuser\" user.</li>\n"; dieout("</ul>"); } print "OK</li>\n"; @@ -339,6 +413,39 @@ class DatabasePostgres extends Database { print "<li>Schema \"$wgDBmwschema\" exists and is owned by \"$user\". Excellent.</li>\n"; } + ## Always return GMT time to accomodate the existing integer-based timestamp assumption + print "<li>Setting the timezone to GMT for user \"$user\" ..."; + $SQL = "ALTER USER $safeuser SET timezone = 'GMT'"; + $result = pg_query($this->mConn, $SQL); + if (!$result) { + print "<b>FAILED</b>.</li>\n"; + dieout("</ul>"); + } + print "OK</li>\n"; + ## Set for the rest of this session + $SQL = "SET timezone = 'GMT'"; + $result = pg_query($this->mConn, $SQL); + if (!$result) { + print "<li>Failed to set timezone</li>\n"; + dieout("</ul>"); + } + + print "<li>Setting the datestyle to ISO, YMD for user \"$user\" ..."; + $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'"; + $result = pg_query($this->mConn, $SQL); + if (!$result) { + print "<b>FAILED</b>.</li>\n"; + dieout("</ul>"); + } + print "OK</li>\n"; + ## Set for the rest of this session + $SQL = "SET datestyle = 'ISO, YMD'"; + $result = pg_query($this->mConn, $SQL); + if (!$result) { + print "<li>Failed to set datestyle</li>\n"; + dieout("</ul>"); + } + ## Fix up the search paths if needed print "<li>Setting the search path for user \"$user\" ..."; $path = $this->quote_ident($wgDBmwschema); @@ -455,6 +562,30 @@ class DatabasePostgres extends Database { } /** + * Estimate rows in dataset + * Returns estimated count, based on EXPLAIN output + * This is not necessarily an accurate estimate, so use sparingly + * Returns -1 if count cannot be found + * Takes same arguments as Database::select() + */ + + function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { + $options['EXPLAIN'] = true; + $res = $this->select( $table, $vars, $conds, $fname, $options ); + $rows = -1; + if ( $res ) { + $row = $this->fetchRow( $res ); + $count = array(); + if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) { + $rows = $count[1]; + } + $this->freeResult($res); + } + return $rows; + } + + + /** * Returns information about an index * If errors are explicitly ignored, returns NULL on failure */ @@ -645,7 +776,7 @@ class DatabasePostgres extends Database { return ''; } - function limitResult($sql, $limit,$offset) { + function limitResult($sql, $limit,$offset=false) { return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":""); } @@ -707,26 +838,31 @@ class DatabasePostgres extends Database { * @return string Version information from the database */ function getServerVersion() { - $res = $this->query( "SELECT version()" ); - $row = $this->fetchRow( $res ); - $version = $row[0]; - $this->freeResult( $res ); + $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0); + $thisver = array(); + if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) { + die("Could not determine the numeric version from $version!"); + } + $this->numeric_version = $thisver[1]; return $version; } /** - * Query whether a given table exists (in the given schema, or the default mw one if not given) + * Query whether a given relation exists (in the given schema, or the + * default mw one if not given) */ - function tableExists( $table, $schema = false ) { + function relationExists( $table, $types, $schema = false ) { global $wgDBmwschema; + if (!is_array($types)) + $types = array($types); if (! $schema ) $schema = $wgDBmwschema; - $etable = preg_replace("/'/", "''", $table); - $eschema = preg_replace("/'/", "''", $schema); + $etable = $this->addQuotes($table); + $eschema = $this->addQuotes($schema); $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n " - . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' " - . "AND c.relkind IN ('r','v')"; + . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema " + . "AND c.relkind IN ('" . implode("','", $types) . "')"; $res = $this->query( $SQL ); $count = $res ? pg_num_rows($res) : 0; if ($res) @@ -734,6 +870,61 @@ class DatabasePostgres extends Database { return $count; } + /* + * For backward compatibility, this function checks both tables and + * views. + */ + function tableExists ($table, $schema = false) { + return $this->relationExists($table, array('r', 'v'), $schema); + } + + function sequenceExists ($sequence, $schema = false) { + return $this->relationExists($sequence, 'S', $schema); + } + + function triggerExists($table, $trigger) { + global $wgDBmwschema; + + $q = <<<END + SELECT 1 FROM pg_class, pg_namespace, pg_trigger + WHERE relnamespace=pg_namespace.oid AND relkind='r' + AND tgrelid=pg_class.oid + AND nspname=%s AND relname=%s AND tgname=%s +END; + $res = $this->query(sprintf($q, + $this->addQuotes($wgDBmwschema), + $this->addQuotes($table), + $this->addQuotes($trigger))); + if (!$res) + return NULL; + $rows = pg_num_rows($res); + $this->freeResult($res); + return $rows; + } + + function ruleExists($table, $rule) { + global $wgDBmwschema; + $exists = $this->selectField("pg_rules", "rulename", + array( "rulename" => $rule, + "tablename" => $table, + "schemaname" => $wgDBmwschema)); + return $exists === $rule; + } + + function constraintExists($table, $constraint) { + global $wgDBmwschema; + $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ". + "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", + $this->addQuotes($wgDBmwschema), + $this->addQuotes($table), + $this->addQuotes($constraint)); + $res = $this->query($SQL); + if (!$res) + return NULL; + $rows = pg_num_rows($res); + $this->freeResult($res); + return $rows; + } /** * Query whether a given schema exists. Returns the name of the owner @@ -752,7 +943,7 @@ class DatabasePostgres extends Database { /** * Query whether a given column exists in the mediawiki schema */ - function fieldExists( $table, $field ) { + function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) { global $wgDBmwschema; $etable = preg_replace("/'/", "''", $table); $eschema = preg_replace("/'/", "''", $wgDBmwschema); @@ -760,7 +951,7 @@ class DatabasePostgres extends Database { $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a " . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' " . "AND a.attrelid = c.oid AND a.attname = '$ecol'"; - $res = $this->query( $SQL ); + $res = $this->query( $SQL, $fname ); $count = $res ? pg_num_rows($res) : 0; if ($res) $this->freeResult( $res ); @@ -768,12 +959,10 @@ class DatabasePostgres extends Database { } function fieldInfo( $table, $field ) { - $res = $this->query( "SELECT $field FROM $table LIMIT 1" ); - $type = pg_field_type( $res, 0 ); - return $type; + return PostgresField::fromText($this, $table, $field); } - function begin( $fname = 'DatabasePostgrs::begin' ) { + function begin( $fname = 'DatabasePostgres::begin' ) { $this->query( 'BEGIN', $fname ); $this->mTrxLevel = 1; } @@ -791,10 +980,36 @@ class DatabasePostgres extends Database { } function setup_database() { - global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport; + global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser; + + ## Make sure that we can write to the correct schema + ## If not, Postgres will happily and silently go to the next search_path item + $ctest = "mw_test_table"; + if ($this->tableExists($ctest, $wgDBmwschema)) { + $this->doQuery("DROP TABLE $wgDBmwschema.$ctest"); + } + $SQL = "CREATE TABLE $wgDBmwschema.$ctest(a int)"; + error_reporting( 0 ); + $res = $this->doQuery($SQL); + error_reporting( E_ALL ); + if (!$res) { + print "<b>FAILED</b>. Make sure that the user \"$wgDBuser\" can write to the schema \"$wgDBmwschema\"</li>\n"; + dieout("</ul>"); + } + $this->doQuery("DROP TABLE $wgDBmwschema.mw_test_table"); dbsource( "../maintenance/postgres/tables.sql", $this); + ## Version-specific stuff + if ($this->numeric_version == 8.1) { + $this->doQuery("CREATE INDEX ts2_page_text ON pagecontent USING gist(textvector)"); + $this->doQuery("CREATE INDEX ts2_page_title ON page USING gist(titlevector)"); + } + else { + $this->doQuery("CREATE INDEX ts2_page_text ON pagecontent USING gin(textvector)"); + $this->doQuery("CREATE INDEX ts2_page_title ON page USING gin(titlevector)"); + } + ## Update version information $mwv = $this->addQuotes($wgVersion); $pgv = $this->addQuotes($this->getServerVersion()); @@ -827,6 +1042,8 @@ class DatabasePostgres extends Database { $this->query("$SQL $matches[1],$matches[2])"); } print " (table interwiki successfully populated)...\n"; + + $this->doQuery("COMMIT"); } function encodeBlob($b) { @@ -870,7 +1087,7 @@ class DatabasePostgres extends Database { * @return array */ function makeSelectOptions( $options ) { - $tailOpts = ''; + $preLimitTail = $postLimitTail = ''; $startOpts = ''; $noKeyOptions = array(); @@ -880,16 +1097,17 @@ class DatabasePostgres extends Database { } } - if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}"; - if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}"; + if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY " . $options['GROUP BY']; + if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY " . $options['ORDER BY']; - if (isset($options['LIMIT'])) { - $tailOpts .= $this->limitResult('', $options['LIMIT'], - isset($options['OFFSET']) ? $options['OFFSET'] : false); - } - - if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE'; - if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE'; + //if (isset($options['LIMIT'])) { + // $tailOpts .= $this->limitResult('', $options['LIMIT'], + // isset($options['OFFSET']) ? $options['OFFSET'] + // : false); + //} + + if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE'; + if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE'; if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) { @@ -898,7 +1116,11 @@ class DatabasePostgres extends Database { $useIndex = ''; } - return array( $startOpts, $useIndex, $tailOpts ); + return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); + } + + public function setTimeout( $timeout ) { + // @todo fixme no-op } function ping() { diff --git a/includes/DateFormatter.php b/includes/DateFormatter.php index c795618a..88a64453 100644 --- a/includes/DateFormatter.php +++ b/includes/DateFormatter.php @@ -1,15 +1,9 @@ <?php -/** - * Date formatter, recognises dates in plain text and formats them accoding to user preferences. - * - * @package MediaWiki - * @subpackage Parser - */ /** + * Date formatter, recognises dates in plain text and formats them accoding to user preferences. * @todo preferences, OutputPage - * @package MediaWiki - * @subpackage Parser + * @addtogroup Parser */ class DateFormatter { diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 0692401d..169d67c9 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -15,7 +15,6 @@ * Documentation is in the source and on: * http://www.mediawiki.org/wiki/Help:Configuration_settings * - * @package MediaWiki */ # This is not a valid entry point, perform no further processing unless MEDIAWIKI is defined @@ -32,7 +31,7 @@ require_once( 'includes/SiteConfiguration.php' ); $wgConf = new SiteConfiguration; /** MediaWiki version number */ -$wgVersion = '1.9.3'; +$wgVersion = '1.10.0'; /** Name of the site. It must be changed in LocalSettings.php */ $wgSitename = 'MediaWiki'; @@ -163,7 +162,6 @@ $wgTmpDirectory = false; /// defaults to "{$wgUploadDirectory}/tmp" $wgUploadBaseUrl = ""; /**#@-*/ - /** * By default deleted files are simply discarded; to save them and * make it possible to undelete images, create a directory which @@ -192,6 +190,7 @@ $wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split * * Problematic punctuation: * []{}|# Are needed for link syntax, never enable these + * <> Causes problems with HTML escaping, don't use * % Enabled by default, minor problems with path to query rewrite rules, see below * + Enabled by default, but doesn't work with path to query rewrite rules, corrupted by apache * ? Enabled by default, but doesn't work with path to PATH_INFO rewrites @@ -307,8 +306,8 @@ $wgVerifyMimeType= true; /** Sets the mime type definition file to use by MimeMagic.php. * @global string $wgMimeTypeFile */ -#$wgMimeTypeFile= "/etc/mime.types"; $wgMimeTypeFile= "includes/mime.types"; +#$wgMimeTypeFile= "/etc/mime.types"; #$wgMimeTypeFile= NULL; #use built-in defaults only. /** Sets the mime type info file to use by MimeMagic.php. @@ -372,7 +371,11 @@ $wgSharedUploadDBprefix = ''; $wgCacheSharedUploads = true; /** Allow for upload to be copied from an URL. Requires Special:Upload?source=web */ $wgAllowCopyUploads = false; -/** Max size for uploads, in bytes */ +/** + * Max size for uploads, in bytes. Currently only works for uploads from URL + * via CURL (see $wgAllowCopyUploads). The only way to impose limits on + * normal uploads is currently to edit php.ini. + */ $wgMaxUploadSize = 1024*1024*100; # 100MB /** @@ -502,8 +505,12 @@ $wgDBtype = "mysql"; $wgSearchType = null; /** Table name prefix */ $wgDBprefix = ''; +/** MySQL table options to use during installation or update */ +$wgDBTableOptions = 'TYPE=InnoDB'; + /**#@-*/ + /** Live high performance sites should disable this - some checks acquire giant mysql locks */ $wgCheckDBSchema = true; @@ -964,6 +971,7 @@ $wgGroupPermissions['user' ]['upload'] = true; $wgGroupPermissions['user' ]['reupload'] = true; $wgGroupPermissions['user' ]['reupload-shared'] = true; $wgGroupPermissions['user' ]['minoredit'] = true; +$wgGroupPermissions['user' ]['purge'] = true; // can use ?action=purge without clicking "ok" // Implicit group for accounts that pass $wgAutoConfirmAge $wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true; @@ -977,6 +985,7 @@ $wgGroupPermissions['emailconfirmed']['emailconfirmed'] = true; $wgGroupPermissions['bot' ]['bot'] = true; $wgGroupPermissions['bot' ]['autoconfirmed'] = true; $wgGroupPermissions['bot' ]['nominornewtalk'] = true; +$wgGroupPermissions['bot' ]['autopatrol'] = true; // Most extra permission abilities go to this group $wgGroupPermissions['sysop']['block'] = true; @@ -988,7 +997,7 @@ $wgGroupPermissions['sysop']['import'] = true; $wgGroupPermissions['sysop']['importupload'] = true; $wgGroupPermissions['sysop']['move'] = true; $wgGroupPermissions['sysop']['patrol'] = true; -$wgGroupPermissions['sysop']['autopatrol'] = true; +$wgGroupPermissions['sysop']['autopatrol'] = true; $wgGroupPermissions['sysop']['protect'] = true; $wgGroupPermissions['sysop']['proxyunbannable'] = true; $wgGroupPermissions['sysop']['rollback'] = true; @@ -1029,6 +1038,21 @@ $wgRestrictionTypes = array( 'edit', 'move' ); */ $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ); +/** + * Set the minimum permissions required to edit pages in each + * namespace. If you list more than one permission, a user must + * have all of them to edit pages in that namespace. + */ +$wgNamespaceProtection = array(); +$wgNamespaceProtection[ NS_MEDIAWIKI ] = array( 'editinterface' ); + +/** +* Pages in namespaces in this array can not be used as templates. +* Elements must be numeric namespace ids. +* Among other things, this may be useful to enforce read-restrictions +* which may otherwise be bypassed by using the template machanism. +*/ +$wgNonincludableNamespaces = array(); /** * Number of seconds an account is required to age before @@ -1045,6 +1069,11 @@ $wgAutoConfirmAge = 0; //$wgAutoConfirmAge = 600; // ten minutes //$wgAutoConfirmAge = 3600*24; // one day +# Number of edits an account requires before it is autoconfirmed +# Passing both this AND the time requirement is needed +$wgAutoConfirmCount = 0; +//$wgAutoConfirmCount = 50; + # Proxy scanner settings @@ -1096,7 +1125,7 @@ $wgCacheEpoch = '20030516000000'; * to ensure that client-side caches don't keep obsolete copies of global * styles. */ -$wgStyleVersion = '42b'; +$wgStyleVersion = '63'; # Server-side caching: @@ -1145,6 +1174,11 @@ $wgEnotifRevealEditorAddress = false; # UPO; reply-to address may be filled with $wgEnotifMinorEdits = true; # UPO; false: "minor edits" on pages do not trigger notification mails. # # Attention: _every_ change on a user_talk page trigger a notification mail (if the user is not yet notified) +/** + * Array of usernames who will be sent a notification email for every change which occurs on a wiki + */ +$wgUsersNotifedOnAllChanges = array(); + /** Show watching users in recent changes, watchlist and page history views */ $wgRCShowWatchingUsers = false; # UPO /** Show watching users in Page views */ @@ -1419,8 +1453,19 @@ $wgSiteNotice = ''; # Images settings # -/** dynamic server side image resizing ("Thumbnails") */ -$wgUseImageResize = false; +/** + * Plugins for media file type handling. + * Each entry in the array maps a MIME type to a class name + */ +$wgMediaHandlers = array( + 'image/jpeg' => 'BitmapHandler', + 'image/png' => 'BitmapHandler', + 'image/gif' => 'BitmapHandler', + 'image/x-ms-bmp' => 'BmpHandler', + 'image/svg+xml' => 'SvgHandler', + 'image/vnd.djvu' => 'DjVuHandler', +); + /** * Resizing can be done using PHP's internal image libraries or using @@ -1434,6 +1479,12 @@ $wgUseImageMagick = false; /** The convert command shipped with ImageMagick */ $wgImageMagickConvertCommand = '/usr/bin/convert'; +/** Sharpening parameter to ImageMagick */ +$wgSharpenParameter = '0x0.4'; + +/** Reduction in linear dimensions below which sharpening will be enabled */ +$wgSharpenReductionThreshold = 0.85; + /** * Use another resizing converter, e.g. GraphicMagick * %s will be replaced with the source path, %d with the destination @@ -1451,7 +1502,7 @@ $wgCustomConvertCommand = false; # # An external program is required to perform this conversion: $wgSVGConverters = array( - 'ImageMagick' => '$path/convert -background white -geometry $width $input $output', + 'ImageMagick' => '$path/convert -background white -geometry $width $input PNG:$output', 'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output', 'inkscape' => '$path/inkscape -z -w $width -f $input -e $output', 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', @@ -1499,11 +1550,17 @@ $wgIgnoreImageErrors = false; */ $wgGenerateThumbnailOnParse = true; +/** Obsolete, always true, kept for compatibility with extensions */ +$wgUseImageResize = true; + + /** Set $wgCommandLineMode if it's not set already, to avoid notices */ if( !isset( $wgCommandLineMode ) ) { $wgCommandLineMode = false; } +/** For colorized maintenance script output, is your terminal background dark ? */ +$wgCommandLineDarkBg = false; # # Recent changes settings @@ -1613,18 +1670,22 @@ $wgExportAllowListContributors = false ; /** Text matching this regular expression will be recognised as spam * See http://en.wikipedia.org/wiki/Regular_expression */ $wgSpamRegex = false; -/** Similarly if this function returns true */ +/** Similarly you can get a function to do the job. The function will be given + * the following args: + * - a Title object for the article the edit is made on + * - the text submitted in the textarea (wpTextbox1) + * - the section number. + * The return should be boolean indicating whether the edit matched some evilness: + * - true : block it + * - false : let it through + * + * For a complete example, have a look at the SpamBlacklist extension. + */ $wgFilterCallback = false; /** Go button goes straight to the edit screen if the article doesn't exist. */ $wgGoToEdit = false; -/** Allow limited user-specified HTML in wiki pages? - * It will be run through a whitelist for security. Set this to false if you - * want wiki pages to consist only of wiki markup. Note that replacements do not - * yet exist for all HTML constructs.*/ -$wgUserHtml = true; - /** Allow raw, unchecked HTML in <html>...</html> sections. * THIS IS VERY DANGEROUS on a publically editable site, so USE wgGroupPermissions * TO RESTRICT EDITING to only those that you trust @@ -1633,8 +1694,7 @@ $wgRawHtml = false; /** * $wgUseTidy: use tidy to make sure HTML output is sane. - * This should only be enabled if $wgUserHtml is true. - * tidy is a free tool that fixes broken HTML. + * Tidy is a free tool that fixes broken HTML. * See http://www.w3.org/People/Raggett/tidy/ * $wgTidyBin should be set to the path of the binary and * $wgTidyConf to the path of the configuration file. @@ -1649,7 +1709,7 @@ $wgRawHtml = false; $wgUseTidy = false; $wgAlwaysUseTidy = false; $wgTidyBin = 'tidy'; -$wgTidyConf = $IP.'/extensions/tidy/tidy.conf'; +$wgTidyConf = $IP.'/includes/tidy.conf'; $wgTidyOpts = ''; $wgTidyInternal = function_exists( 'tidy_load_config' ); @@ -1660,7 +1720,7 @@ $wgDefaultSkin = 'monobook'; * Settings added to this array will override the default globals for the user * preferences used by anonymous visitors and newly created accounts. * For instance, to disable section editing links: - * $wgDefaultUserOptions ['editsection'] = 0; + * $wgDefaultUserOptions ['editsection'] = 0; * */ $wgDefaultUserOptions = array( @@ -1831,9 +1891,28 @@ $wgFeedDiffCutoff = 32768; $wgExtraNamespaces = NULL; /** + * Namespace aliases + * These are alternate names for the primary localised namespace names, which + * are defined by $wgExtraNamespaces and the language file. If a page is + * requested with such a prefix, the request will be redirected to the primary + * name. + * + * Set this to a map from namespace names to IDs. + * Example: + * $wgNamespaceAliases = array( + * 'Wikipedian' => NS_USER, + * 'Help' => 100, + * ); + */ +$wgNamespaceAliases = array(); + +/** * Limit images on image description pages to a user-selectable limit. In order - * to reduce disk usage, limits can only be selected from a list. This is the - * list of settings the user can choose from: + * to reduce disk usage, limits can only be selected from a list. + * The user preference is saved as an array offset in the database, by default + * the offset is set with $wgDefaultUserOptions['imagesize']. Make sure you + * change it if you alter the array (see bug 8858). + * This is the list of settings the user can choose from: */ $wgImageLimits = array ( array(320,240), @@ -1883,9 +1962,9 @@ $wgBrowserBlackList = array( * * Reference: http://www.psychedelix.com/agents/index.shtml */ - '/^Mozilla\/2\.[^ ]+ .*?\((?!compatible).*; [UIN]/', - '/^Mozilla\/3\.[^ ]+ .*?\((?!compatible).*; [UIN]/', - '/^Mozilla\/4\.[^ ]+ .*?\((?!compatible).*; [UIN]/', + '/^Mozilla\/2\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/', + '/^Mozilla\/3\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/', + '/^Mozilla\/4\.[^ ]+ [^(]*?\((?!compatible).*; [UIN]/', /** * MSIE on Mac OS 9 is teh sux0r, converts þ to <thorn>, ð to <eth>, Þ to <THORN> and Ð to <ETH> @@ -1985,7 +2064,9 @@ $wgLogTypes = array( '', 'delete', 'upload', 'move', - 'import' ); + 'import', + 'patrol', +); /** * Lists the message key string for each log type. The localized messages @@ -2001,7 +2082,9 @@ $wgLogNames = array( 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', - 'import' => 'importlogpage' ); + 'import' => 'importlogpage', + 'patrol' => 'patrol-log-page', +); /** * Lists the message key string for descriptive text to be shown at the @@ -2017,7 +2100,9 @@ $wgLogHeaders = array( 'delete' => 'dellogpagetext', 'upload' => 'uploadlogpagetext', 'move' => 'movelogpagetext', - 'import' => 'importlogpagetext', ); + 'import' => 'importlogpagetext', + 'patrol' => 'patrol-log-header', +); /** * Lists the message key string for formatting individual events of each @@ -2039,7 +2124,8 @@ $wgLogActions = array( 'move/move' => '1movedto2', 'move/move_redir' => '1movedto2_redir', 'import/upload' => 'import-logentry-upload', - 'import/interwiki' => 'import-logentry-interwiki' ); + 'import/interwiki' => 'import-logentry-interwiki', +); /** * Experimental preview feature to fetch rendered text @@ -2166,6 +2252,9 @@ $wgRateLimits = array( 'mailpassword' => array( 'anon' => NULL, ), + 'emailuser' => array( + 'user' => null, + ), ); /** @@ -2235,7 +2324,7 @@ $wgTrustedMediaFormats= array( MEDIATYPE_BITMAP, //all bitmap formats MEDIATYPE_AUDIO, //all audio formats MEDIATYPE_VIDEO, //all plain video formats - "image/svg", //svg (only needed if inline rendering of svg is not supported) + "image/svg+xml", //svg (only needed if inline rendering of svg is not supported) "application/pdf", //PDF files #"application/x-shockwave-flash", //flash/shockwave movie ); @@ -2330,7 +2419,7 @@ $wgAllowDisplayTitle = false ; $wgReservedUsernames = array( 'MediaWiki default', // Default 'Main Page' and MediaWiki: message pages 'Conversion script', // Used for the old Wikipedia software upgrade - 'Maintenance script', // ... maintenance/edit.php uses this? + 'Maintenance script', // Maintenance scripts which perform editing, image import script 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade ); @@ -2338,7 +2427,7 @@ $wgReservedUsernames = array( * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't * perform basic stuff like MIME detection and which are vulnerable to further idiots uploading * crap files as images. When this directive is on, <title> will be allowed in files with - * an "image/svg" MIME type. You should leave this disabled if your web server is misconfigured + * an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured * and doesn't send appropriate MIME types for SVG images. */ $wgAllowTitlesInSVG = false; @@ -2364,25 +2453,42 @@ $wgMaxShellFileSize = 102400; /** * DJVU settings - * Path of the djvutoxml executable + * Path of the djvudump executable * Enable this and $wgDjvuRenderer to enable djvu rendering */ -# $wgDjvuToXML = 'djvutoxml'; -$wgDjvuToXML = null; +# $wgDjvuDump = 'djvudump'; +$wgDjvuDump = null; /** * Path of the ddjvu DJVU renderer - * Enable this and $wgDjvuToXML to enable djvu rendering + * Enable this and $wgDjvuDump to enable djvu rendering */ # $wgDjvuRenderer = 'ddjvu'; $wgDjvuRenderer = null; /** - * Path of the DJVU post processor - * May include command line options - * Default: ppmtojpeg, since ddjvu generates ppm output + * Path of the djvutoxml executable + * This works like djvudump except much, much slower as of version 3.5. + * + * For now I recommend you use djvudump instead. The djvuxml output is + * probably more stable, so we'll switch back to it as soon as they fix + * the efficiency problem. + * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583 + */ +# $wgDjvuToXML = 'djvutoxml'; +$wgDjvuToXML = null; + + +/** + * Shell command for the DJVU post processor + * Default: pnmtopng, since ddjvu generates ppm output + * Set this to false to output the ppm file directly. + */ +$wgDjvuPostProcessor = 'pnmtojpeg'; +/** + * File extension for the DJVU post processor output */ -$wgDjvuPostProcessor = 'ppmtojpeg'; +$wgDjvuOutputExtension = 'jpg'; /** * Enable direct access to the data API @@ -2416,4 +2522,14 @@ $wgBreakFrames = false; */ $wgDisableQueryPageUpdate = false; +/** + * Set this to false to disable cascading protection + */ +$wgEnableCascadingProtection = true; + +/** + * Disable output compression (enabled by default if zlib is available) + */ +$wgDisableOutputCompression = false; + ?> diff --git a/includes/Defines.php b/includes/Defines.php index 84bc4495..98e76277 100644 --- a/includes/Defines.php +++ b/includes/Defines.php @@ -1,7 +1,6 @@ <?php /** * A few constants that might be needed during LocalSettings.php - * @package MediaWiki */ /** diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php index a72f0153..af65ce3a 100644 --- a/includes/DifferenceEngine.php +++ b/includes/DifferenceEngine.php @@ -1,15 +1,14 @@ <?php /** * See diff.doc - * @package MediaWiki - * @subpackage DifferenceEngine + * @todo indicate where diff.doc can be found. + * @addtogroup DifferenceEngine */ /** * @todo document * @public - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class DifferenceEngine { /**#@+ @@ -63,8 +62,8 @@ class DifferenceEngine { $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer } - function showDiffPage() { - global $wgUser, $wgOut, $wgContLang, $wgUseExternalEditor, $wgUseRCPatrol; + function showDiffPage( $diffOnly = false ) { + global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol; $fname = 'DifferenceEngine::showDiffPage'; wfProfileIn( $fname ); @@ -118,6 +117,7 @@ CONTROL; # is the first version of that article. In that case, V' does not exist. if ( $this->mOldid === false ) { $this->showFirstRevision(); + $this->renderNewRevision(); // should we respect $diffOnly here or not? wfProfileOut( $fname ); return; } @@ -178,15 +178,34 @@ CONTROL; $oldHeader = "<strong>{$this->mOldtitle}</strong><br />" . $sk->revUserTools( $this->mOldRev ) . "<br />" . - $oldminor . $sk->revComment( $this->mOldRev, true ) . "<br />" . + $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) . "<br />" . $prevlink; $newHeader = "<strong>{$this->mNewtitle}</strong><br />" . $sk->revUserTools( $this->mNewRev ) . " $rollback<br />" . - $newminor . $sk->revComment( $this->mNewRev, true ) . "<br />" . + $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) . "<br />" . $nextlink . $patrol; $this->showDiff( $oldHeader, $newHeader ); + + if ( !$diffOnly ) + $this->renderNewRevision(); + + wfProfileOut( $fname ); + } + + /** + * Show the new revision of the page. + */ + function renderNewRevision() { + global $wgOut; + $fname = 'DifferenceEngine::renderNewRevision'; + wfProfileIn( $fname ); + $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" ); + #add deleted rev tag if needed + if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { + $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) ); + } if( !$this->mNewRev->isCurrent() ) { $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false ); @@ -196,7 +215,8 @@ CONTROL; if( is_object( $this->mNewRev ) ) { $wgOut->setRevisionId( $this->mNewRev->getId() ); } - $wgOut->addSecondaryWikiText( $this->mNewtext ); + + $wgOut->addWikiTextTidy( $this->mNewtext ); if( !$this->mNewRev->isCurrent() ) { $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); @@ -254,15 +274,6 @@ CONTROL; $wgOut->setSubtitle( wfMsg( 'difference' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); - - # Show current revision - # - $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" ); - if( is_object( $this->mNewRev ) ) { - $wgOut->setRevisionId( $this->mNewRev->getId() ); - } - $wgOut->addSecondaryWikiText( $this->mNewtext ); - wfProfileOut( $fname ); } @@ -322,9 +333,14 @@ CONTROL; } } + #loadtext is permission safe, this just clears out the diff if ( !$this->loadText() ) { wfProfileOut( $fname ); return false; + } else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) { + return ''; + } else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { + return ''; } $difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext ); @@ -463,6 +479,14 @@ CONTROL; * Add the header to a diff body */ function addHeader( $diff, $otitle, $ntitle, $multi = '' ) { + global $wgOut; + + if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) { + $otitle = '<span class="history-deleted">'.$otitle.'</span>'; + } + if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { + $ntitle = '<span class="history-deleted">'.$ntitle.'</span>'; + } $header = " <table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'> <tr> @@ -523,21 +547,17 @@ CONTROL; $newLink = $this->mNewPage->escapeLocalUrl(); $this->mPagetitle = htmlspecialchars( wfMsg( 'currentrev' ) ); $newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit' ); - $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid ); $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a> ($timestamp)" - . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)" - . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)"; + . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; } else { $newLink = $this->mNewPage->escapeLocalUrl( 'oldid=' . $this->mNewid ); $newEdit = $this->mNewPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mNewid ); - $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undo=' . $this->mNewid ); $this->mPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $timestamp ) ); $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>" - . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)" - . " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)"; + . " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; } // Load the old revision object @@ -568,6 +588,9 @@ CONTROL; $oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid ); $this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) . "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)"; + //now that we considered old rev, we can make undo link (bug 8133, multi-edit undo) + $newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undo=' . $this->mNewid); + $this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)"; } return true; @@ -589,13 +612,13 @@ CONTROL; } if ( $this->mOldRev ) { // FIXME: permission tests - $this->mOldtext = $this->mOldRev->getText(); + $this->mOldtext = $this->mOldRev->revText(); if ( $this->mOldtext === false ) { return false; } } if ( $this->mNewRev ) { - $this->mNewtext = $this->mNewRev->getText(); + $this->mNewtext = $this->mNewRev->revText(); if ( $this->mNewtext === false ) { return false; } @@ -633,8 +656,7 @@ define('USE_ASSERTS', function_exists('assert')); /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _DiffOp { var $type; @@ -657,8 +679,7 @@ class _DiffOp { /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _DiffOp_Copy extends _DiffOp { var $type = 'copy'; @@ -678,8 +699,7 @@ class _DiffOp_Copy extends _DiffOp { /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _DiffOp_Delete extends _DiffOp { var $type = 'delete'; @@ -697,8 +717,7 @@ class _DiffOp_Delete extends _DiffOp { /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _DiffOp_Add extends _DiffOp { var $type = 'add'; @@ -716,8 +735,7 @@ class _DiffOp_Add extends _DiffOp { /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _DiffOp_Change extends _DiffOp { var $type = 'change'; @@ -754,8 +772,7 @@ class _DiffOp_Change extends _DiffOp { * * @author Geoffrey T. Dairiki, Tim Starling * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _DiffEngine { @@ -1176,8 +1193,7 @@ class _DiffEngine * Class representing a 'diff' between two sequences of strings. * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class Diff { @@ -1315,11 +1331,9 @@ class Diff } /** - * FIXME: bad name. - * @todo document + * @todo document, bad name. * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class MappedDiff extends Diff { @@ -1382,8 +1396,7 @@ class MappedDiff extends Diff * to obtain fancier outputs. * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class DiffFormatter { @@ -1549,8 +1562,7 @@ define('NBSP', ' '); // iso-8859-x non-breaking space. /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class _HWLDF_WordAccumulator { function _HWLDF_WordAccumulator () { @@ -1562,9 +1574,12 @@ class _HWLDF_WordAccumulator { function _flushGroup ($new_tag) { if ($this->_group !== '') { - if ($this->_tag == 'mark') - $this->_line .= '<span class="diffchange">' . - htmlspecialchars ( $this->_group ) . '</span>'; + if ($this->_tag == 'ins') + $this->_line .= '<ins class="diffchange">' . + htmlspecialchars ( $this->_group ) . '</ins>'; + elseif ($this->_tag == 'del') + $this->_line .= '<del class="diffchange">' . + htmlspecialchars ( $this->_group ) . '</del>'; else $this->_line .= htmlspecialchars ( $this->_group ); } @@ -1608,8 +1623,7 @@ class _HWLDF_WordAccumulator { /** * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class WordLevelDiff extends MappedDiff { @@ -1669,7 +1683,7 @@ class WordLevelDiff extends MappedDiff if ($edit->type == 'copy') $orig->addWords($edit->orig); elseif ($edit->orig) - $orig->addWords($edit->orig, 'mark'); + $orig->addWords($edit->orig, 'del'); } $lines = $orig->getLines(); wfProfileOut( $fname ); @@ -1685,7 +1699,7 @@ class WordLevelDiff extends MappedDiff if ($edit->type == 'copy') $closing->addWords($edit->closing); elseif ($edit->closing) - $closing->addWords($edit->closing, 'mark'); + $closing->addWords($edit->closing, 'ins'); } $lines = $closing->getLines(); wfProfileOut( $fname ); @@ -1697,8 +1711,7 @@ class WordLevelDiff extends MappedDiff * Wikipedia Table style diff formatter. * @todo document * @private - * @package MediaWiki - * @subpackage DifferenceEngine + * @addtogroup DifferenceEngine */ class TableDiffFormatter extends DiffFormatter { diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php index 3b8a68ba..1e423565 100644 --- a/includes/DjVuImage.php +++ b/includes/DjVuImage.php @@ -1,11 +1,6 @@ <?php + /** - * Support for detecting/validating DjVu image files and getting - * some basic file metadata (resolution etc) - * - * File format docs are available in source package for DjVuLibre: - * http://djvulibre.djvuzone.org/ - * * * Copyright (C) 2006 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ @@ -25,9 +20,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * - * @package MediaWiki */ +/** + * Support for detecting/validating DjVu image files and getting + * some basic file metadata (resolution etc) + * + * File format docs are available in source package for DjVuLibre: + * http://djvulibre.djvuzone.org/ + * + * @addtogroup Media + */ class DjVuImage { function __construct( $filename ) { $this->mFilename = $filename; @@ -68,6 +71,7 @@ class DjVuImage { function dump() { $file = fopen( $this->mFilename, 'rb' ); $header = fread( $file, 12 ); + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) ); echo "$chunk $chunkLength\n"; $this->dumpForm( $file, $chunkLength, 1 ); @@ -83,6 +87,7 @@ class DjVuImage { if( $chunkHeader == '' ) { break; } + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) ); echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n"; @@ -111,6 +116,7 @@ class DjVuImage { if( strlen( $header ) < 16 ) { wfDebug( __METHOD__ . ": too short file header\n" ); } else { + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) ); if( $magic != 'AT&T' ) { @@ -134,6 +140,7 @@ class DjVuImage { if( strlen( $header ) < 8 ) { return array( false, 0 ); } else { + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( unpack( 'a4chunk/Nlength', $header ) ); return array( $chunk, $length ); } @@ -192,6 +199,7 @@ class DjVuImage { return false; } + // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( unpack( 'nwidth/' . 'nheight/' . @@ -214,17 +222,121 @@ class DjVuImage { * @return string */ function retrieveMetaData() { - global $wgDjvuToXML; - if ( isset( $wgDjvuToXML ) ) { - $cmd = $wgDjvuToXML . ' --without-anno --without-text ' . + global $wgDjvuToXML, $wgDjvuDump; + if ( isset( $wgDjvuDump ) ) { + # djvudump is faster as of version 3.5 + # http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583 + wfProfileIn( 'djvudump' ); + $cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename ); + $dump = wfShellExec( $cmd ); + $xml = $this->convertDumpToXML( $dump ); + wfProfileOut( 'djvudump' ); + } elseif ( isset( $wgDjvuToXML ) ) { + wfProfileIn( 'djvutoxml' ); + $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' . wfEscapeShellArg( $this->mFilename ); $xml = wfShellExec( $cmd ); + wfProfileOut( 'djvutoxml' ); } else { $xml = null; } return $xml; } - + + /** + * Hack to temporarily work around djvutoxml bug + */ + function convertDumpToXML( $dump ) { + if ( strval( $dump ) == '' ) { + return false; + } + + $xml = <<<EOT +<?xml version="1.0" ?> +<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd"> +<DjVuXML> +<HEAD></HEAD> +<BODY> +EOT; + + $dump = str_replace( "\r", '', $dump ); + $line = strtok( $dump, "\n" ); + $m = false; + $good = false; + if ( preg_match( '/^( *)FORM:DJVU/', $line, $m ) ) { + # Single-page + if ( $this->parseFormDjvu( $line, $xml ) ) { + $good = true; + } else { + return false; + } + } elseif ( preg_match( '/^( *)FORM:DJVM/', $line, $m ) ) { + # Multi-page + $parentLevel = strlen( $m[1] ); + # Find DIRM + $line = strtok( "\n" ); + while ( $line !== false ) { + $childLevel = strspn( $line, ' ' ); + if ( $childLevel <= $parentLevel ) { + # End of chunk + break; + } + + if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) { + wfDebug( "Indirect multi-page DjVu document, bad for server!\n" ); + return false; + } + if ( preg_match( '/^ *FORM:DJVU/', $line ) ) { + # Found page + if ( $this->parseFormDjvu( $line, $xml ) ) { + $good = true; + } else { + return false; + } + } + $line = strtok( "\n" ); + } + } + if ( !$good ) { + return false; + } + + $xml .= "</BODY>\n</DjVuXML>\n"; + return $xml; + } + + function parseFormDjvu( $line, &$xml ) { + $parentLevel = strspn( $line, ' ' ); + $line = strtok( "\n" ); + + # Find INFO + while ( $line !== false ) { + $childLevel = strspn( $line, ' ' ); + if ( $childLevel <= $parentLevel ) { + # End of chunk + break; + } + + if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) { + $xml .= Xml::tags( 'OBJECT', + array( + #'data' => '', + #'type' => 'image/x.djvu', + 'height' => $m[2], + 'width' => $m[1], + #'usemap' => '', + ), + "\n" . + Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" . + Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n" + ) . "\n"; + return true; + } + $line = strtok( "\n" ); + } + # Not found + return false; + } } diff --git a/includes/EditPage.php b/includes/EditPage.php index 7688a64a..bec6e300 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -1,18 +1,14 @@ <?php /** - * Contain the EditPage class - * @package MediaWiki + * Contains the EditPage class */ /** - * Splitting edit page/HTML interface from Article... + * The edit page/HTML interface (split from Article) * The actual database and text munging is still in Article, * but it should get easier to call those from alternate * interfaces. - * - * @package MediaWiki */ - class EditPage { var $mArticle; var $mTitle; @@ -69,22 +65,26 @@ class EditPage { /** * Fetch initial editing page content. */ - private function getContent() { + private function getContent( $def_text = '' ) { global $wgOut, $wgRequest, $wgParser; # Get variables from query string :P $section = $wgRequest->getVal( 'section' ); $preload = $wgRequest->getVal( 'preload' ); + $undoafter = $wgRequest->getVal( 'undoafter' ); $undo = $wgRequest->getVal( 'undo' ); wfProfileIn( __METHOD__ ); $text = ''; if( !$this->mTitle->exists() ) { - - # If requested, preload some text. - $text = $this->getPreloadedText( $preload ); - + if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { + # If this is a system message, get the default text. + $text = wfMsgWeirdKey ( $this->mTitle->getText() ) ; + } else { + # If requested, preload some text. + $text = $this->getPreloadedText( $preload ); + } # We used to put MediaWiki:Newarticletext here if # $text was empty at this point. # This is now shown above the edit box instead. @@ -94,51 +94,63 @@ class EditPage { // fetch the page record from the high-priority server, // which is needed to guarantee we don't pick up lagged // information. - + $text = $this->mArticle->getContent(); - if ( $undo > 0 ) { - #Undoing a specific edit overrides section editing; section-editing + if ( $undo > 0 && $undo > $undoafter ) { + # Undoing a specific edit overrides section editing; section-editing # doesn't work with undoing. - $undorev = Revision::newFromId($undo); + if ( $undoafter ) { + $undorev = Revision::newFromId($undo); + $oldrev = Revision::newFromId($undoafter); + } else { + $undorev = Revision::newFromId($undo); + $oldrev = $undorev ? $undorev->getPrevious() : null; + } #Sanity check, make sure it's the right page. # Otherwise, $text will be left as-is. - if (!is_null($undorev) && $undorev->getPage() == $this->mArticle->getID()) { - $oldrev = $undorev->getPrevious(); + if ( !is_null($undorev) && !is_null($oldrev) && $undorev->getPage()==$oldrev->getPage() && $undorev->getPage()==$this->mArticle->getID() ) { $undorev_text = $undorev->getText(); $oldrev_text = $oldrev->getText(); $currev_text = $text; #No use doing a merge if it's just a straight revert. - if ($currev_text != $undorev_text) { + if ( $currev_text != $undorev_text ) { $result = wfMerge($undorev_text, $oldrev_text, $currev_text, $text); } else { $text = $oldrev_text; $result = true; } - - if( $result ) { - # Inform the user of our success and set an automatic edit summary - $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) ); - $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() ); - $this->formtype = 'diff'; - } else { - # Warn the user that something went wrong - $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) ); + } else { + // Failed basic sanity checks. + // Older revisions may have been removed since the link + // was created, or we may simply have got bogus input. + $result = false; + } + + if( $result ) { + # Inform the user of our success and set an automatic edit summary + $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) ); + $firstrev = $oldrev->getNext(); + # If we just undid one rev, use an autosummary + if ( $firstrev->mId == $undo ) { + $this->summary = wfMsgForContent('undo-summary', $undo, $undorev->getUserText()); } - + $this->formtype = 'diff'; + } else { + # Warn the user that something went wrong + $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) ); } - } - else if( $section != '' ) { + } else if( $section != '' ) { if( $section == 'new' ) { $text = $this->getPreloadedText( $preload ); } else { - $text = $wgParser->getSection( $text, $section ); + $text = $wgParser->getSection( $text, $section, $def_text ); } } } - + wfProfileOut( __METHOD__ ); return $text; } @@ -282,7 +294,7 @@ class EditPage { global $wgOut, $wgUser, $wgRequest, $wgTitle; global $wgEmailConfirmToEdit; - if ( ! wfRunHooks( 'AlternateEdit', array( &$this ) ) ) + if ( ! wfRunHooks( 'AlternateEdit', array( &$this ) ) ) return; $fname = 'EditPage::edit'; @@ -301,7 +313,7 @@ class EditPage { return; } - if ( ! $this->mTitle->userCanEdit() ) { + if ( ! $this->mTitle->userCan( 'edit' ) ) { wfDebug( "$fname: user can't edit\n" ); $wgOut->readOnlyPage( $this->getContent(), true ); wfProfileOut( $fname ); @@ -335,7 +347,7 @@ class EditPage { wfProfileOut($fname); return; } - if ( !$this->mTitle->userCanCreate() && !$this->mTitle->exists() ) { + if ( !$this->mTitle->userCan( 'create' ) && !$this->mTitle->exists() ) { wfDebug( "$fname: no create permission\n" ); $this->noCreatePermission(); wfProfileOut( $fname ); @@ -421,7 +433,12 @@ class EditPage { # First time through: get contents, set time for conflict # checking, etc. if ( 'initial' == $this->formtype || $this->firsttime ) { - $this->initialiseForm(); + if ($this->initialiseForm() === false) { + $this->noSuchSectionPage(); + wfProfileOut( "$fname-business-end" ); + wfProfileOut( $fname ); + return; + } if( !$this->mTitle->getArticleId() ) wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) ); } @@ -482,7 +499,7 @@ class EditPage { // Remember whether a save was requested, so we can indicate // if we forced preview due to session failure. $this->mTriedSave = !$this->preview; - + if ( $this->tokenOk( $request ) ) { # Some browsers will not report any submit button # if the user hits enter in the comment box. @@ -519,8 +536,8 @@ class EditPage { } else { $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ); } - - $this->autoSumm = $request->getText( 'wpAutoSummary' ); + + $this->autoSumm = $request->getText( 'wpAutoSummary' ); } else { # Not a posted form? Start with nothing. wfDebug( "$fname: Not a posted form.\n" ); @@ -652,7 +669,7 @@ class EditPage { wfProfileOut( $fname ); return true; } - + if ( !$wgUser->isAllowed('edit') ) { if ( $wgUser->isAnon() ) { $this->userNotLoggedInPage(); @@ -696,7 +713,7 @@ class EditPage { if ( 0 == $aid ) { // Late check for create permission, just in case *PARANOIA* - if ( !$this->mTitle->userCanCreate() ) { + if ( !$this->mTitle->userCan( 'create' ) ) { wfDebug( "$fname: no create permission\n" ); $this->noCreatePermission(); wfProfileOut( $fname ); @@ -723,6 +740,8 @@ class EditPage { $this->mArticle->clear(); # Force reload of dates, etc. $this->mArticle->forUpdate( true ); # Lock the article + wfDebug("timestamp: {$this->mArticle->getTimestamp()}, edittime: {$this->edittime}\n"); + if( $this->mArticle->getTimestamp() != $this->edittime ) { $this->isConflict = true; if( $this->section == 'new' ) { @@ -794,7 +813,7 @@ class EditPage { } #And a similar thing for new sections - if( $this->section == 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) { + if( $this->section == 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) { if (trim($this->summary) == '') { $this->missingSummary = true; wfProfileOut( $fname ); @@ -860,10 +879,13 @@ class EditPage { function initialiseForm() { $this->edittime = $this->mArticle->getTimestamp(); $this->summary = ''; - $this->textbox1 = $this->getContent(); + $this->textbox1 = $this->getContent(false); + if ($this->textbox1 === false) return false; + if ( !$this->mArticle->exists() && $this->mArticle->mTitle->getNamespace() == NS_MEDIAWIKI ) - $this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() ) ; + $this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() ); wfProxyCheck(); + return true; } /** @@ -878,7 +900,7 @@ class EditPage { $fname = 'EditPage::showEditForm'; wfProfileIn( $fname ); - $sk =& $wgUser->getSkin(); + $sk = $wgUser->getSkin(); wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) ) ; @@ -920,15 +942,15 @@ class EditPage { if ( $this->missingComment ) { $wgOut->addWikiText( wfMsg( 'missingcommenttext' ) ); } - + if( $this->missingSummary && $this->section != 'new' ) { $wgOut->addWikiText( wfMsg( 'missingsummary' ) ); } - if( $this->missingSummary && $this->section == 'new' ) { - $wgOut->addWikiText( wfMsg( 'missingcommentheader' ) ); - } - + if( $this->missingSummary && $this->section == 'new' ) { + $wgOut->addWikiText( wfMsg( 'missingcommentheader' ) ); + } + if( !$this->hookError == '' ) { $wgOut->addWikiText( $this->hookError ); } @@ -936,11 +958,15 @@ class EditPage { if ( !$this->checkUnicodeCompliantBrowser() ) { $wgOut->addWikiText( wfMsg( 'nonunicodebrowser') ); } - if ( isset( $this->mArticle ) - && isset( $this->mArticle->mRevision ) - && !$this->mArticle->mRevision->isCurrent() ) { - $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() ); - $wgOut->addWikiText( wfMsg( 'editingold' ) ); + if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) { + // Let sysop know that this will make private content public if saved + if( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { + $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) ); + } + if( !$this->mArticle->mRevision->isCurrent() ) { + $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() ); + $wgOut->addWikiText( wfMsg( 'editingold' ) ); + } } } @@ -958,24 +984,33 @@ class EditPage { } } } - - if( $this->mTitle->isProtected( 'edit' ) ) { - # Is the protection due to the namespace, e.g. interface text? - if( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { - # Yes; remind the user - $notice = wfMsg( 'editinginterface' ); - } elseif( $this->mTitle->isSemiProtected() ) { - # No; semi protected + + if( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { + # Show a warning if editing an interface message + $wgOut->addWikiText( wfMsg( 'editinginterface' ) ); + } elseif( $this->mTitle->isProtected( 'edit' ) ) { + # Is the title semi-protected? + if( $this->mTitle->isSemiProtected() ) { $notice = wfMsg( 'semiprotectedpagewarning' ); - if( wfEmptyMsg( 'semiprotectedpagewarning', $notice ) || $notice == '-' ) { + if( wfEmptyMsg( 'semiprotectedpagewarning', $notice ) || $notice == '-' ) $notice = ''; - } } else { - # No; regular protection + # Then it must be protected based on static groups (regular) $notice = wfMsg( 'protectedpagewarning' ); } $wgOut->addWikiText( $notice ); } + if ( $this->mTitle->isCascadeProtected() ) { + # Is this page under cascading protection from some source pages? + list($cascadeSources, $restrictions) = $this->mTitle->getCascadeProtectionSources(); + if ( count($cascadeSources) > 0 ) { + # Explain, and list the titles responsible + $notice = wfMsgExt( 'cascadeprotectedwarning', array('parsemag'), count($cascadeSources) ) . "\n"; + foreach( $cascadeSources as $id => $page ) + $notice .= '* [[:' . $page->getPrefixedText() . "]]\n"; + } + $wgOut->addWikiText( $notice ); + } if ( $this->kblength === false ) { $this->kblength = (int)(strlen( $this->textbox1 ) / 1024); @@ -1005,8 +1040,6 @@ class EditPage { $summary = wfMsg('summary'); $subject = wfMsg('subject'); - $minor = wfMsgExt('minoredit', array('parseinline')); - $watchthis = wfMsgExt('watchthis', array('parseinline')); $cancel = $sk->makeKnownLink( $this->mTitle->getPrefixedText(), wfMsgExt('cancel', array('parseinline')) ); @@ -1041,31 +1074,10 @@ class EditPage { # Already watched $this->watchthis = true; } - - if( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true; - } - - $minoredithtml = ''; - - if ( $wgUser->isAllowed('minoredit') ) { - $minoredithtml = - "<input tabindex='3' type='checkbox' value='1' name='wpMinoredit'".($this->minoredit?" checked='checked'":""). - " accesskey='".wfMsg('accesskey-minoredit')."' id='wpMinoredit' />\n". - "<label for='wpMinoredit' title='".wfMsg('tooltip-minoredit')."'>{$minor}</label>\n"; - } - $watchhtml = ''; - - if ( $wgUser->isLoggedIn() ) { - $watchhtml = "<input tabindex='4' type='checkbox' name='wpWatchthis'". - ($this->watchthis?" checked='checked'":""). - " accesskey=\"".htmlspecialchars(wfMsg('accesskey-watch'))."\" id='wpWatchthis' />\n". - "<label for='wpWatchthis' title=\"" . - htmlspecialchars(wfMsg('tooltip-watch'))."\">{$watchthis}</label>\n"; + if( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true; } - $checkboxhtml = $minoredithtml . $watchhtml; - $wgOut->addHTML( $this->editFormPageTop ); if ( $wgUser->getOption( 'previewontop' ) ) { @@ -1132,68 +1144,18 @@ class EditPage { } } - $temp = array( - 'id' => 'wpSave', - 'name' => 'wpSave', - 'type' => 'submit', - 'tabindex' => '5', - 'value' => wfMsg('savearticle'), - 'accesskey' => wfMsg('accesskey-save'), - 'title' => wfMsg('tooltip-save'), - ); - $buttons['save'] = wfElement('input', $temp, ''); - $temp = array( - 'id' => 'wpDiff', - 'name' => 'wpDiff', - 'type' => 'submit', - 'tabindex' => '7', - 'value' => wfMsg('showdiff'), - 'accesskey' => wfMsg('accesskey-diff'), - 'title' => wfMsg('tooltip-diff'), - ); - $buttons['diff'] = wfElement('input', $temp, ''); + $tabindex = 2; - global $wgLivePreview; - if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) { - $temp = array( - 'id' => 'wpPreview', - 'name' => 'wpPreview', - 'type' => 'submit', - 'tabindex' => '6', - 'value' => wfMsg('showpreview'), - 'accesskey' => '', - 'title' => wfMsg('tooltip-preview'), - 'style' => 'display: none;', - ); - $buttons['preview'] = wfElement('input', $temp, ''); - $temp = array( - 'id' => 'wpLivePreview', - 'name' => 'wpLivePreview', - 'type' => 'submit', - 'tabindex' => '6', - 'value' => wfMsg('showlivepreview'), - 'accesskey' => wfMsg('accesskey-preview'), - 'title' => '', - 'onclick' => $this->doLivePreviewScript(), - ); - $buttons['live'] = wfElement('input', $temp, ''); - } else { - $temp = array( - 'id' => 'wpPreview', - 'name' => 'wpPreview', - 'type' => 'submit', - 'tabindex' => '6', - 'value' => wfMsg('showpreview'), - 'accesskey' => wfMsg('accesskey-preview'), - 'title' => wfMsg('tooltip-preview'), - ); - $buttons['preview'] = wfElement('input', $temp, ''); - $buttons['live'] = ''; - } + $checkboxes = self::getCheckboxes( $tabindex, $sk, + array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) ); + + $checkboxhtml = implode( $checkboxes, "\n" ); + + $buttons = $this->getEditButtons( $tabindex ); + $buttonshtml = implode( $buttons, "\n" ); $safemodehtml = $this->checkUnicodeCompliantBrowser() - ? "" - : "<input type='hidden' name=\"safemode\" value='1' />\n"; + ? '' : Xml::hidden( 'safemode', '1' ); $wgOut->addHTML( <<<END {$toolbar} @@ -1205,6 +1167,8 @@ END call_user_func_array( $formCallback, array( &$wgOut ) ); } + wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); + // Put these up at the top to ensure they aren't lost on early form submission $wgOut->addHTML( " <input type='hidden' value=\"" . htmlspecialchars( $this->section ) . "\" name=\"wpSection\" /> @@ -1236,10 +1200,7 @@ END $wgOut->addHTML( "<div class='editButtons'> - {$buttons['save']} - {$buttons['preview']} - {$buttons['live']} - {$buttons['diff']} +{$buttonshtml} <span class='editHelp'>{$cancel} | {$edithelp}</span> </div><!-- editButtons --> </div><!-- editOptions -->"); @@ -1282,7 +1243,7 @@ END if( $this->missingSummary ) { $wgOut->addHTML( "<input type=\"hidden\" name=\"wpIgnoreBlankSummary\" value=\"1\" />\n" ); } - + # For a bit more sophisticated detection of blank summaries, hash the # automatic one and pass that in a hidden field. $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary ); @@ -1308,7 +1269,7 @@ END } else { $wgOut->addHTML( '<div id="wikiPreview"></div>' ); } - + if ( $this->formtype == 'diff') { $wgOut->addHTML( $this->getDiff() ); } @@ -1361,7 +1322,7 @@ END } function getLastDelete() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $fname = 'EditPage::getLastDelete'; $res = $dbr->select( array( 'logging', 'user' ), @@ -1425,7 +1386,7 @@ END # don't parse user css/js, show message about preview # XXX: stupid php bug won't let us use $wgTitle->isCssJsSubpage() here - + if ( $this->isCssJsSubpage ) { if(preg_match("/\\.css$/", $wgTitle->getText() ) ) { $previewtext = wfMsg('usercsspreview'); @@ -1469,16 +1430,16 @@ END function blockedPage() { global $wgOut, $wgUser; $wgOut->blockedPage( false ); # Standard block notice on the top, don't 'return' - + # If the user made changes, preserve them when showing the markup - # (This happens when a user is blocked during edit, for instance) + # (This happens when a user is blocked during edit, for instance) $first = $this->firsttime || ( !$this->save && $this->textbox1 == '' ); if( $first ) { $source = $this->mTitle->exists() ? $this->getContent() : false; } else { $source = $this->textbox1; } - + # Spit out the source or the user's modified version if( $source !== false ) { $rows = $wgUser->getOption( 'rows' ); @@ -1496,14 +1457,14 @@ END function userNotLoggedInPage() { global $wgUser, $wgOut; $skin = $wgUser->getSkin(); - + $loginTitle = SpecialPage::getTitleFor( 'Userlogin' ); $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $this->mTitle->getPrefixedUrl() ); - + $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); - + $wgOut->addHtml( wfMsgWikiHtml( 'whitelistedittext', $loginLink ) ); $wgOut->returnToMain( false, $this->mTitle->getPrefixedUrl() ); } @@ -1519,12 +1480,27 @@ END $wgOut->setPageTitle( wfMsg( 'confirmedittitle' ) ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); - + $wgOut->addWikiText( wfMsg( 'confirmedittext' ) ); $wgOut->returnToMain( false ); } /** + * Creates a basic error page which informs the user that + * they have attempted to edit a nonexistant section. + */ + function noSuchSectionPage() { + global $wgOut; + + $wgOut->setPageTitle( wfMsg( 'nosuchsectiontitle' ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + $wgOut->setArticleRelated( false ); + + $wgOut->addWikiText( wfMsg( 'nosuchsectiontext', $this->section ) ); + $wgOut->returnToMain( false ); + } + + /** * Produce the stock "your edit contains spam" page * * @param $match Text which triggered one or more filters @@ -1539,7 +1515,7 @@ END $wgOut->addWikiText( wfMsg( 'spamprotectiontext' ) ); if ( $match ) $wgOut->addWikiText( wfMsg( 'spamprotectionmatch', "<nowiki>{$match}</nowiki>" ) ); - + $wgOut->returnToMain( false ); } @@ -1551,7 +1527,7 @@ END $fname = 'EditPage::mergeChangesInto'; wfProfileIn( $fname ); - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); // This is the revision the editor started from $baseRevision = Revision::loadFromTimestamp( @@ -1645,90 +1621,102 @@ END * can figure out a way to make them work in IE. However, we should make * sure these keys are not defined on the edit page. */ - $toolarray=array( - array( 'image'=>'button_bold.png', - 'open' => '\\\'\\\'\\\'', - 'close' => '\\\'\\\'\\\'', - 'sample'=> wfMsg('bold_sample'), - 'tip' => wfMsg('bold_tip'), - 'key' => 'B' - ), - array( 'image'=>'button_italic.png', - 'open' => '\\\'\\\'', - 'close' => '\\\'\\\'', - 'sample'=> wfMsg('italic_sample'), - 'tip' => wfMsg('italic_tip'), - 'key' => 'I' - ), - array( 'image'=>'button_link.png', - 'open' => '[[', - 'close' => ']]', - 'sample'=> wfMsg('link_sample'), - 'tip' => wfMsg('link_tip'), - 'key' => 'L' - ), - array( 'image'=>'button_extlink.png', - 'open' => '[', - 'close' => ']', - 'sample'=> wfMsg('extlink_sample'), - 'tip' => wfMsg('extlink_tip'), - 'key' => 'X' - ), - array( 'image'=>'button_headline.png', - 'open' => "\\n== ", - 'close' => " ==\\n", - 'sample'=> wfMsg('headline_sample'), - 'tip' => wfMsg('headline_tip'), - 'key' => 'H' - ), - array( 'image'=>'button_image.png', - 'open' => '[['.$wgContLang->getNsText(NS_IMAGE).":", - 'close' => ']]', - 'sample'=> wfMsg('image_sample'), - 'tip' => wfMsg('image_tip'), - 'key' => 'D' - ), - array( 'image' =>'button_media.png', - 'open' => '[['.$wgContLang->getNsText(NS_MEDIA).':', - 'close' => ']]', - 'sample'=> wfMsg('media_sample'), - 'tip' => wfMsg('media_tip'), - 'key' => 'M' - ), - array( 'image' =>'button_math.png', - 'open' => "<math>", - 'close' => "<\\/math>", - 'sample'=> wfMsg('math_sample'), - 'tip' => wfMsg('math_tip'), - 'key' => 'C' - ), - array( 'image' =>'button_nowiki.png', - 'open' => "<nowiki>", - 'close' => "<\\/nowiki>", - 'sample'=> wfMsg('nowiki_sample'), - 'tip' => wfMsg('nowiki_tip'), - 'key' => 'N' - ), - array( 'image' =>'button_sig.png', - 'open' => '--~~~~', - 'close' => '', - 'sample'=> '', - 'tip' => wfMsg('sig_tip'), - 'key' => 'Y' - ), - array( 'image' =>'button_hr.png', - 'open' => "\\n----\\n", - 'close' => '', - 'sample'=> '', - 'tip' => wfMsg('hr_tip'), - 'key' => 'R' - ) + $toolarray = array( + array( 'image' => 'button_bold.png', + 'id' => 'mw-editbutton-bold', + 'open' => '\\\'\\\'\\\'', + 'close' => '\\\'\\\'\\\'', + 'sample'=> wfMsg('bold_sample'), + 'tip' => wfMsg('bold_tip'), + 'key' => 'B' + ), + array( 'image' => 'button_italic.png', + 'id' => 'mw-editbutton-italic', + 'open' => '\\\'\\\'', + 'close' => '\\\'\\\'', + 'sample'=> wfMsg('italic_sample'), + 'tip' => wfMsg('italic_tip'), + 'key' => 'I' + ), + array( 'image' => 'button_link.png', + 'id' => 'mw-editbutton-link', + 'open' => '[[', + 'close' => ']]', + 'sample'=> wfMsg('link_sample'), + 'tip' => wfMsg('link_tip'), + 'key' => 'L' + ), + array( 'image' => 'button_extlink.png', + 'id' => 'mw-editbutton-extlink', + 'open' => '[', + 'close' => ']', + 'sample'=> wfMsg('extlink_sample'), + 'tip' => wfMsg('extlink_tip'), + 'key' => 'X' + ), + array( 'image' => 'button_headline.png', + 'id' => 'mw-editbutton-headline', + 'open' => "\\n== ", + 'close' => " ==\\n", + 'sample'=> wfMsg('headline_sample'), + 'tip' => wfMsg('headline_tip'), + 'key' => 'H' + ), + array( 'image' => 'button_image.png', + 'id' => 'mw-editbutton-image', + 'open' => '[['.$wgContLang->getNsText(NS_IMAGE).":", + 'close' => ']]', + 'sample'=> wfMsg('image_sample'), + 'tip' => wfMsg('image_tip'), + 'key' => 'D' + ), + array( 'image' => 'button_media.png', + 'id' => 'mw-editbutton-media', + 'open' => '[['.$wgContLang->getNsText(NS_MEDIA).':', + 'close' => ']]', + 'sample'=> wfMsg('media_sample'), + 'tip' => wfMsg('media_tip'), + 'key' => 'M' + ), + array( 'image' => 'button_math.png', + 'id' => 'mw-editbutton-math', + 'open' => "<math>", + 'close' => "<\\/math>", + 'sample'=> wfMsg('math_sample'), + 'tip' => wfMsg('math_tip'), + 'key' => 'C' + ), + array( 'image' => 'button_nowiki.png', + 'id' => 'mw-editbutton-nowiki', + 'open' => "<nowiki>", + 'close' => "<\\/nowiki>", + 'sample'=> wfMsg('nowiki_sample'), + 'tip' => wfMsg('nowiki_tip'), + 'key' => 'N' + ), + array( 'image' => 'button_sig.png', + 'id' => 'mw-editbutton-signature', + 'open' => '--~~~~', + 'close' => '', + 'sample'=> '', + 'tip' => wfMsg('sig_tip'), + 'key' => 'Y' + ), + array( 'image' => 'button_hr.png', + 'id' => 'mw-editbutton-hr', + 'open' => "\\n----\\n", + 'close' => '', + 'sample'=> '', + 'tip' => wfMsg('hr_tip'), + 'key' => 'R' + ) ); $toolbar = "<div id='toolbar'>\n"; $toolbar.="<script type='$wgJsMimeType'>\n/*<![CDATA[*/\n"; foreach($toolarray as $tool) { + $cssId = $tool['id']; $image=$wgStylePath.'/common/images/'.$tool['image']; $open=$tool['open']; $close=$tool['close']; @@ -1742,7 +1730,7 @@ END #$key = $tool["key"]; - $toolbar.="addButton('$image','$tip','$open','$close','$sample');\n"; + $toolbar.="addButton('$image','$tip','$open','$close','$sample','$cssId');\n"; } $toolbar.="/*]]>*/\n</script>"; @@ -1751,6 +1739,127 @@ END } /** + * Returns an array of html code of the following checkboxes: + * minor and watch + * + * @param $tabindex Current tabindex + * @param $skin Skin object + * @param $checked Array of checkbox => bool, where bool indicates the checked + * status of the checkbox + * + * @return array + */ + public static function getCheckboxes( &$tabindex, $skin, $checked ) { + global $wgUser; + + $checkboxes = array(); + + $checkboxes['minor'] = ''; + $minorLabel = wfMsgExt('minoredit', array('parseinline')); + if ( $wgUser->isAllowed('minoredit') ) { + $attribs = array( + 'tabindex' => ++$tabindex, + 'accesskey' => wfMsg( 'accesskey-minoredit' ), + 'id' => 'wpMinoredit', + ); + $checkboxes['minor'] = + Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) . + " <label for='wpMinoredit'".$skin->tooltipAndAccesskey('minoredit').">{$minorLabel}</label>"; + } + + $watchLabel = wfMsgExt('watchthis', array('parseinline')); + $checkboxes['watch'] = ''; + if ( $wgUser->isLoggedIn() ) { + $attribs = array( + 'tabindex' => ++$tabindex, + 'accesskey' => wfMsg( 'accesskey-watch' ), + 'id' => 'wpWatchthis', + ); + $checkboxes['watch'] = + Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) . + " <label for='wpWatchthis'".$skin->tooltipAndAccesskey('watch').">{$watchLabel}</label>"; + } + return $checkboxes; + } + + /** + * Returns an array of html code of the following buttons: + * save, diff, preview and live + * + * @param $tabindex Current tabindex + * + * @return array + */ + public function getEditButtons(&$tabindex) { + global $wgLivePreview, $wgUser; + + $buttons = array(); + + $temp = array( + 'id' => 'wpSave', + 'name' => 'wpSave', + 'type' => 'submit', + 'tabindex' => ++$tabindex, + 'value' => wfMsg('savearticle'), + 'accesskey' => wfMsg('accesskey-save'), + 'title' => wfMsg( 'tooltip-save' ).' ['.wfMsg( 'accesskey-save' ).']', + ); + $buttons['save'] = wfElement('input', $temp, ''); + + ++$tabindex; // use the same for preview and live preview + if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) { + $temp = array( + 'id' => 'wpPreview', + 'name' => 'wpPreview', + 'type' => 'submit', + 'tabindex' => $tabindex, + 'value' => wfMsg('showpreview'), + 'accesskey' => '', + 'title' => wfMsg( 'tooltip-preview' ).' ['.wfMsg( 'accesskey-preview' ).']', + 'style' => 'display: none;', + ); + $buttons['preview'] = wfElement('input', $temp, ''); + + $temp = array( + 'id' => 'wpLivePreview', + 'name' => 'wpLivePreview', + 'type' => 'submit', + 'tabindex' => $tabindex, + 'value' => wfMsg('showlivepreview'), + 'accesskey' => wfMsg('accesskey-preview'), + 'title' => '', + 'onclick' => $this->doLivePreviewScript(), + ); + $buttons['live'] = wfElement('input', $temp, ''); + } else { + $temp = array( + 'id' => 'wpPreview', + 'name' => 'wpPreview', + 'type' => 'submit', + 'tabindex' => $tabindex, + 'value' => wfMsg('showpreview'), + 'accesskey' => wfMsg('accesskey-preview'), + 'title' => wfMsg( 'tooltip-preview' ).' ['.wfMsg( 'accesskey-preview' ).']', + ); + $buttons['preview'] = wfElement('input', $temp, ''); + $buttons['live'] = ''; + } + + $temp = array( + 'id' => 'wpDiff', + 'name' => 'wpDiff', + 'type' => 'submit', + 'tabindex' => ++$tabindex, + 'value' => wfMsg('showdiff'), + 'accesskey' => wfMsg('accesskey-diff'), + 'title' => wfMsg( 'tooltip-diff' ).' ['.wfMsg( 'accesskey-diff' ).']', + ); + $buttons['diff'] = wfElement('input', $temp, ''); + + return $buttons; + } + + /** * Output preview text only. This can be sucked into the edit page * via JavaScript, and saves the server time rendering the skin as * well as theoretically being more robust on the client (doesn't @@ -1758,8 +1867,8 @@ END * failure, etc). * * @todo This doesn't include category or interlanguage links. - * Would need to enhance it a bit, maybe wrap them in XML - * or something... that might also require more skin + * Would need to enhance it a bit, <s>maybe wrap them in XML + * or something...</s> that might also require more skin * initialization, so check whether that's a problem. */ function livePreview() { @@ -1767,10 +1876,14 @@ END $wgOut->disable(); header( 'Content-type: text/xml; charset=utf-8' ); header( 'Cache-control: no-cache' ); - # FIXME - echo $this->getPreviewText( ); - /* To not shake screen up and down between preview and live-preview */ - echo "<br style=\"clear:both;\" />\n"; + + $s = + '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" . + Xml::openElement( 'livepreview' ) . + Xml::element( 'preview', null, $this->getPreviewText() ) . + Xml::element( 'br', array( 'style' => 'clear: both;' ) ) . + Xml::closeElement( 'livepreview' ); + echo $s; } diff --git a/includes/Exception.php b/includes/Exception.php index ad7ec14a..4cf0b7ba 100644 --- a/includes/Exception.php +++ b/includes/Exception.php @@ -1,5 +1,9 @@ <?php +/** + * MediaWiki exception + * @addtogroup Exception + */ class MWException extends Exception { function useOutputPage() { @@ -12,6 +16,7 @@ class MWException extends Exception return is_object( $wgLang ); } + /** Get a message from i18n */ function msg( $key, $fallback /*[, params...] */ ) { $args = array_slice( func_get_args(), 2 ); if ( $this->useMessageCache() ) { @@ -21,6 +26,7 @@ class MWException extends Exception } } + /* If wgShowExceptionDetails, return a HTML message with a backtrace to the error. */ function getHTML() { global $wgShowExceptionDetails; if( $wgShowExceptionDetails ) { @@ -33,6 +39,7 @@ class MWException extends Exception } } + /* If wgShowExceptionDetails, return a text message with a backtrace to the error */ function getText() { global $wgShowExceptionDetails; if( $wgShowExceptionDetails ) { @@ -43,7 +50,8 @@ class MWException extends Exception "in LocalSettings.php to show detailed debugging information.</p>"; } } - + + /* Return titles of this error page */ function getPageTitle() { if ( $this->useMessageCache() ) { return wfMsg( 'internalerror' ); @@ -52,7 +60,10 @@ class MWException extends Exception return "$wgSitename error"; } } - + + /** Return the requested URL and point to file and line number from which the + * exception occured + */ function getLogMessage() { global $wgRequest; $file = $this->getFile(); @@ -60,7 +71,8 @@ class MWException extends Exception $message = $this->getMessage(); return $wgRequest->getRequestURL() . " Exception from line $line of $file: $message"; } - + + /** Output the exception report using HTML */ function reportHTML() { global $wgOut; if ( $this->useOutputPage() ) { @@ -78,11 +90,15 @@ class MWException extends Exception echo $this->htmlFooter(); } } - + + /** Print the exception report using text */ function reportText() { echo $this->getText(); } + /* Output a report about the exception and takes care of formatting. + * It will be either HTML or plain text based on $wgCommandLineMode. + */ function report() { global $wgCommandLineMode; if ( $wgCommandLineMode ) { @@ -125,6 +141,7 @@ class MWException extends Exception /** * Exception class which takes an HTML error message, and does not * produce a backtrace. Replacement for OutputPage::fatalError(). + * @addtogroup Exception */ class FatalError extends MWException { function getHTML() { @@ -136,6 +153,9 @@ class FatalError extends MWException { } } +/** + * @addtogroup Exception + */ class ErrorPageError extends MWException { public $title, $msg; @@ -203,7 +223,7 @@ function wfReportException( Exception $e ) { function wfExceptionHandler( $e ) { global $wgFullyInitialised; wfReportException( $e ); - + // Final cleanup, similar to wfErrorExit() if ( $wgFullyInitialised ) { try { diff --git a/includes/Exif.php b/includes/Exif.php index 0860d5f7..3a06ca1b 100644 --- a/includes/Exif.php +++ b/includes/Exif.php @@ -1,7 +1,6 @@ <?php /** - * @package MediaWiki - * @subpackage Metadata + * @addtogroup Media * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason @@ -22,13 +21,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * - * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification - * @bug 1555, 1947 + * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification */ /** - * @package MediaWiki - * @subpackage Metadata + * @todo document (e.g. one-sentence class-overview description) + * @addtogroup Media */ class Exif { //@{ @@ -95,9 +93,9 @@ class Exif { var $basename; /** - * The private log to log to + * The private log to log to, e.g. 'exif' */ - var $log = 'exif'; + var $log = false; //@} @@ -106,7 +104,7 @@ class Exif { * * @param $file String: filename. */ - function Exif( $file ) { + function __construct( $file ) { /** * Page numbers here refer to pages in the EXIF 2.2 standard * @@ -563,7 +561,10 @@ class Exif { * @param $fname String: * @param $action Mixed: , default NULL. */ - function debug( $in, $fname, $action = NULL ) { + function debug( $in, $fname, $action = NULL ) { + if ( !$this->log ) { + return; + } $type = gettype( $in ); $class = ucfirst( __CLASS__ ); if ( $type === 'array' ) @@ -588,6 +589,9 @@ class Exif { * @param $io Boolean: Specify whether we're beginning or ending */ function debugFile( $fname, $io ) { + if ( !$this->log ) { + return; + } $class = ucfirst( __CLASS__ ); if ( $io ) { wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" ); @@ -599,8 +603,8 @@ class Exif { } /** - * @package MediaWiki - * @subpackage Metadata + * @todo document (e.g. one-sentence class-overview description) + * @addtogroup Media */ class FormatExif { /** @@ -733,7 +737,7 @@ class FormatExif { case 'DateTimeDigitized': if( $val == '0000:00:00 00:00:00' ) { $tags[$tag] = wfMsg('exif-unknowndate'); - } elseif( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/', $val ) ) { + } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) { $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) ); } break; diff --git a/includes/Export.php b/includes/Export.php index b7e0f9a1..9307795d 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -17,16 +17,15 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html + /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ - class WikiExporter { var $list_authors = false ; # Return distinct author list (when not returning full history) var $author_list = "" ; - + const FULL = 0; const CURRENT = 1; @@ -44,14 +43,14 @@ class WikiExporter { * main query is still running. * * @param Database $db - * @param mixed $history one of WikiExporter::FULL or WikiExporter::CURRENT, or an + * @param mixed $history one of WikiExporter::FULL or WikiExporter::CURRENT, or an * associative array: * offset: non-inclusive offset at which to start the query * limit: maximum number of rows to return * dir: "asc" or "desc" timestamp order * @param int $buffer one of WikiExporter::BUFFER or WikiExporter::STREAM */ - function WikiExporter( &$db, $history = WikiExporter::CURRENT, + function __construct( &$db, $history = WikiExporter::CURRENT, $buffer = WikiExporter::BUFFER, $text = WikiExporter::TEXT ) { $this->db =& $db; $this->history = $history; @@ -140,7 +139,10 @@ class WikiExporter { $fname = "do_list_authors" ; wfProfileIn( $fname ); $this->author_list = "<contributors>"; - $sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision} WHERE page_id=rev_page AND " . $cond ; + //rev_deleted + $nothidden = '(rev_deleted & '.Revision::DELETED_USER.') = 0'; + + $sql = "SELECT DISTINCT rev_user_text,rev_user FROM {$page},{$revision} WHERE page_id=rev_page AND $nothidden AND " . $cond ; $result = $this->db->query( $sql, $fname ); $resultset = $this->db->resultObject( $result ); while( $row = $resultset->fetchObject() ) { @@ -164,10 +166,10 @@ class WikiExporter { $page = $this->db->tableName( 'page' ); $revision = $this->db->tableName( 'revision' ); $text = $this->db->tableName( 'text' ); - + $order = 'ORDER BY page_id'; $limit = ''; - + if( $this->history == WikiExporter::FULL ) { $join = 'page_id=rev_page'; } elseif( $this->history == WikiExporter::CURRENT ) { @@ -185,7 +187,7 @@ class WikiExporter { $order .= ', rev_timestamp DESC'; } if ( !empty( $this->history['offset'] ) ) { - $join .= " AND rev_timestamp $op " . $this->db->addQuotes( + $join .= " AND rev_timestamp $op " . $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) ); } if ( !empty( $this->history['limit'] ) ) { @@ -229,7 +231,7 @@ class WikiExporter { $result = $this->db->query( $sql, $fname ); $wrapper = $this->db->resultObject( $result ); $this->outputStream( $wrapper ); - + if ( $this->list_authors ) { $this->outputStream( $wrapper ); } @@ -279,6 +281,9 @@ class WikiExporter { } } +/** + * @addtogroup Dump + */ class XmlDumpWriter { /** @@ -461,6 +466,7 @@ class XmlDumpWriter { /** * Base class for output stream; prints to stdout or buffer or whereever. + * @addtogroup Dump */ class DumpOutput { function writeOpenStream( $string ) { @@ -494,6 +500,7 @@ class DumpOutput { /** * Stream outputter to send data to a file. + * @addtogroup Dump */ class DumpFileOutput extends DumpOutput { var $handle; @@ -511,6 +518,7 @@ class DumpFileOutput extends DumpOutput { * Stream outputter to send data to a file via some filter program. * Even if compression is available in a library, using a separate * program can allow us to make use of a multi-processor system. + * @addtogroup Dump */ class DumpPipeOutput extends DumpFileOutput { function DumpPipeOutput( $command, $file = null ) { @@ -523,6 +531,7 @@ class DumpPipeOutput extends DumpFileOutput { /** * Sends dump output via the gzip compressor. + * @addtogroup Dump */ class DumpGZipOutput extends DumpPipeOutput { function DumpGZipOutput( $file ) { @@ -532,6 +541,7 @@ class DumpGZipOutput extends DumpPipeOutput { /** * Sends dump output via the bgzip2 compressor. + * @addtogroup Dump */ class DumpBZip2Output extends DumpPipeOutput { function DumpBZip2Output( $file ) { @@ -541,6 +551,7 @@ class DumpBZip2Output extends DumpPipeOutput { /** * Sends dump output via the p7zip compressor. + * @addtogroup Dump */ class Dump7ZipOutput extends DumpPipeOutput { function Dump7ZipOutput( $file ) { @@ -558,6 +569,7 @@ class Dump7ZipOutput extends DumpPipeOutput { * Dump output filter class. * This just does output filtering and streaming; XML formatting is done * higher up, so be careful in what you do. + * @addtogroup Dump */ class DumpFilter { function DumpFilter( &$sink ) { @@ -603,6 +615,7 @@ class DumpFilter { /** * Simple dump output filter to exclude all talk pages. + * @addtogroup Dump */ class DumpNotalkFilter extends DumpFilter { function pass( $page ) { @@ -612,6 +625,7 @@ class DumpNotalkFilter extends DumpFilter { /** * Dump output filter to include or exclude pages in a given set of namespaces. + * @addtogroup Dump */ class DumpNamespaceFilter extends DumpFilter { var $invert = false; @@ -666,6 +680,7 @@ class DumpNamespaceFilter extends DumpFilter { /** * Dump output filter to include only the last revision in each page sequence. + * @addtogroup Dump */ class DumpLatestFilter extends DumpFilter { var $page, $pageString, $rev, $revString; @@ -697,6 +712,7 @@ class DumpLatestFilter extends DumpFilter { /** * Base class for output stream; prints to stdout or buffer or whereever. + * @addtogroup Dump */ class DumpMultiWriter { function DumpMultiWriter( $sinks ) { diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php index 14b55fdb..c8ed8bde 100644 --- a/includes/ExternalEdit.php +++ b/includes/ExternalEdit.php @@ -3,12 +3,10 @@ * License: Public domain * * @author Erik Moeller <moeller@scireview.de> - * @package MediaWiki */ /** * - * @package MediaWiki * * Support for external editors to modify both text and files * in external applications. It works as follows: MediaWiki @@ -22,7 +20,7 @@ class ExternalEdit { - function ExternalEdit ( $article, $mode ) { + function __construct( $article, $mode ) { global $wgInputEncoding; $this->mArticle =& $article; $this->mTitle =& $article->mTitle; diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php index 79f1a528..fb66b652 100644 --- a/includes/ExternalStore.php +++ b/includes/ExternalStore.php @@ -1,7 +1,6 @@ <?php /** * - * @package MediaWiki * * Constructor class for data kept in external repositories * diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php index 861a9939..7b4ffc2f 100644 --- a/includes/ExternalStoreDB.php +++ b/includes/ExternalStoreDB.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki * * DB accessable external objects * */ -/** @package MediaWiki */ /** * External database storage will use one (or more) separate connection pools diff --git a/includes/ExternalStoreHttp.php b/includes/ExternalStoreHttp.php index daf62cc4..e6656986 100644 --- a/includes/ExternalStoreHttp.php +++ b/includes/ExternalStoreHttp.php @@ -1,7 +1,6 @@ <?php /** * - * @package MediaWiki * * Example class for HTTP accessable external objects * diff --git a/includes/FakeTitle.php b/includes/FakeTitle.php index ae05385a..293bdaf0 100644 --- a/includes/FakeTitle.php +++ b/includes/FakeTitle.php @@ -14,7 +14,6 @@ class FakeTitle { function getInterwikiCached() { $this->error(); } function isLocal() { $this->error(); } function isTrans() { $this->error(); } - function touchArray( $titles, $timestamp = '' ) { $this->error(); } function getText() { $this->error(); } function getPartialURL() { $this->error(); } function getDBkey() { $this->error(); } @@ -41,6 +40,7 @@ class FakeTitle { function isProtected() { $this->error(); } function userIsWatching() { $this->error(); } function userCan() { $this->error(); } + function userCanCreate() { $this->error(); } function userCanEdit() { $this->error(); } function userCanMove() { $this->error(); } function isMovable() { $this->error(); } @@ -71,7 +71,6 @@ class FakeTitle { function moveOverExistingRedirect() { $this->error(); } function moveToNewTitle() { $this->error(); } function isValidMoveTarget() { $this->error(); } - function createRedirect() { $this->error(); } function getParentCategories() { $this->error(); } function getParentCategoryTree() { $this->error(); } function pageCond() { $this->error(); } diff --git a/includes/Feed.php b/includes/Feed.php index 5c14865d..ed4343c3 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -1,6 +1,5 @@ <?php -# Basic support for outputting syndication feeds in RSS, other formats -# + # Copyright (C) 2004 Brion Vibber <brion@pobox.com> # http://www.mediawiki.org/ # @@ -20,15 +19,13 @@ # http://www.gnu.org/copyleft/gpl.html /** + * Basic support for outputting syndication feeds in RSS, other formats. * Contain a feed class as well as classes to build rss / atom ... feeds * Available feeds are defined in Defines.php - * @package MediaWiki */ - /** - * @todo document - * @package MediaWiki + * A base class for basic support for outputting syndication feeds in RSS and other formats. */ class FeedItem { /**#@+ @@ -45,7 +42,7 @@ class FeedItem { /**#@+ * @todo document */ - function FeedItem( $Title, $Description, $Url, $Date = '', $Author = '', $Comments = '' ) { + function __construct( $Title, $Description, $Url, $Date = '', $Author = '', $Comments = '' ) { $this->Title = $Title; $this->Description = $Description; $this->Url = $Url; @@ -77,8 +74,7 @@ class FeedItem { } /** - * @todo document - * @package MediaWiki + * @todo document (needs one-sentence top-level class description). */ class ChannelFeed extends FeedItem { /**#@+ @@ -160,8 +156,6 @@ class ChannelFeed extends FeedItem { /** * Generate a RSS feed - * @todo document - * @package MediaWiki */ class RSSFeed extends ChannelFeed { @@ -221,8 +215,6 @@ class RSSFeed extends ChannelFeed { /** * Generate an Atom feed - * @todo document - * @package MediaWiki */ class AtomFeed extends ChannelFeed { /** diff --git a/includes/FileStore.php b/includes/FileStore.php index 1fd35b01..dcec71c5 100644 --- a/includes/FileStore.php +++ b/includes/FileStore.php @@ -1,5 +1,8 @@ <?php +/** + * @todo document (needs one-sentence top-level class description). + */ class FileStore { const DELETE_ORIGINAL = 1; @@ -33,7 +36,7 @@ class FileStore { * suffer an uncaught error the lock will be released when the * connection is closed. * - * @fixme Probably only works on MySQL. Abstract to the Database class? + * @todo Probably only works on MySQL. Abstract to the Database class? */ static function lock() { global $wgDBtype; @@ -106,7 +109,7 @@ class FileStore { private function copyFile( $sourcePath, $destPath, $flags=0 ) { if( !file_exists( $sourcePath ) ) { // Abort! Abort! - throw new FSException( "missing source file '$sourcePath'\n" ); + throw new FSException( "missing source file '$sourcePath'" ); } $transaction = new FSTransaction(); @@ -125,7 +128,7 @@ class FileStore { if( !$ok ) { throw new FSException( - "failed to create directory for '$destPath'\n" ); + "failed to create directory for '$destPath'" ); } } @@ -138,7 +141,7 @@ class FileStore { $transaction->addRollback( FSTransaction::DELETE_FILE, $destPath ); } else { throw new FSException( - __METHOD__." failed to copy '$sourcePath' to '$destPath'\n" ); + __METHOD__." failed to copy '$sourcePath' to '$destPath'" ); } } @@ -175,7 +178,7 @@ class FileStore { * @throws FSException if file can't be deleted * @return FSTransaction * - * @fixme Might be worth preliminary permissions check + * @todo Might be worth preliminary permissions check */ static function deleteFile( $path ) { if( file_exists( $path ) ) { @@ -368,6 +371,9 @@ class FSTransaction { } } +/** + * @addtogroup Exception + */ class FSException extends MWException { } ?> diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index de07b321..1ffde741 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -1,8 +1,11 @@ <?php +if ( !defined( 'MEDIAWIKI' ) ) { + die( "This file is part of MediaWiki, it is not a valid entry point" ); +} + /** * Global functions used everywhere - * @package MediaWiki */ /** @@ -19,9 +22,9 @@ $wgTotalViews = -1; $wgTotalEdits = -1; -require_once( 'LogPage.php' ); -require_once( 'normal/UtfNormalUtil.php' ); -require_once( 'XmlFunctions.php' ); +require_once dirname(__FILE__) . '/LogPage.php'; +require_once dirname(__FILE__) . '/normal/UtfNormalUtil.php'; +require_once dirname(__FILE__) . '/XmlFunctions.php'; /** * Compatibility functions @@ -57,6 +60,30 @@ if ( !function_exists( 'mb_substr' ) ) { } } +if ( !function_exists( 'mb_strlen' ) ) { + /** + * Fallback implementation of mb_strlen, hardcoded to UTF-8. + * @param string $str + * @param string $enc optional encoding; ignored + * @return int + */ + function mb_strlen( $str, $enc="" ) { + $counts = count_chars( $str ); + $total = 0; + + // Count ASCII bytes + for( $i = 0; $i < 0x80; $i++ ) { + $total += $counts[$i]; + } + + // Count multibyte sequence heads + for( $i = 0xc0; $i < 0xff; $i++ ) { + $total += $counts[$i]; + } + return $total; + } +} + if ( !function_exists( 'array_diff_key' ) ) { /** * Exists in PHP 5.1.0+ @@ -168,7 +195,7 @@ function wfDebug( $text, $logonly = false ) { # Strip unprintables; they can switch terminal modes when binary data # gets dumped, which is pretty annoying. $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text ); - @error_log( $text, 3, $wgDebugLogFile ); + wfErrorLog( $text, $wgDebugLogFile ); } } @@ -187,7 +214,7 @@ function wfDebugLog( $logGroup, $text, $public = true ) { if( isset( $wgDebugLogGroups[$logGroup] ) ) { $time = wfTimestamp( TS_DB ); $wiki = wfWikiID(); - @error_log( "$time $wiki: $text", 3, $wgDebugLogGroups[$logGroup] ); + wfErrorLog( "$time $wiki: $text", $wgDebugLogGroups[$logGroup] ); } else if ( $public === true ) { wfDebug( $text, true ); } @@ -198,15 +225,28 @@ function wfDebugLog( $logGroup, $text, $public = true ) { * @param $text String: database error message. */ function wfLogDBError( $text ) { - global $wgDBerrorLog; + global $wgDBerrorLog, $wgDBname; if ( $wgDBerrorLog ) { $host = trim(`hostname`); - $text = date('D M j G:i:s T Y') . "\t$host\t".$text; - error_log( $text, 3, $wgDBerrorLog ); + $text = date('D M j G:i:s T Y') . "\t$host\t$wgDBname\t$text"; + wfErrorLog( $text, $wgDBerrorLog ); } } /** + * Log to a file without getting "file size exceeded" signals + */ +function wfErrorLog( $text, $file ) { + wfSuppressWarnings(); + $exists = file_exists( $file ); + $size = $exists ? filesize( $file ) : false; + if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) { + error_log( $text, 3, $file ); + } + wfRestoreWarnings(); +} + +/** * @todo document */ function wfLogProfilingData() { @@ -232,7 +272,7 @@ function wfLogProfilingData() { gmdate( 'YmdHis' ), $elapsed, urldecode( $wgRequest->getRequestURL() . $forward ) ); if ( '' != $wgDebugLogFile && ( $wgRequest->getVal('action') != 'raw' || $wgDebugRawPage ) ) { - error_log( $log . $prof, 3, $wgDebugLogFile ); + wfErrorLog( $log . $prof, $wgDebugLogFile ); } } } @@ -376,8 +416,11 @@ function wfMsgNoDBForContent( $key ) { * @return String: the requested message. */ function wfMsgReal( $key, $args, $useDB = true, $forContent=false, $transform = true ) { + $fname = 'wfMsgReal'; + wfProfileIn( $fname ); $message = wfMsgGetKey( $key, $useDB, $forContent, $transform ); $message = wfMsgReplaceArgs( $message, $args ); + wfProfileOut( $fname ); return $message; } @@ -515,11 +558,11 @@ function wfMsgWikiHtml( $key ) { * Returns message in the requested format * @param string $key Key of the message * @param array $options Processing rules: - * <i>parse<i>: parses wikitext to html - * <i>parseinline<i>: parses wikitext to html and removes the surrounding p's added by parser or tidy - * <i>escape<i>: filters message trough htmlspecialchars - * <i>replaceafter<i>: parameters are substituted after parsing or escaping - * <i>parsemag<i>: ?? + * <i>parse</i>: parses wikitext to html + * <i>parseinline</i>: parses wikitext to html and removes the surrounding p's added by parser or tidy + * <i>escape</i>: filters message trough htmlspecialchars + * <i>replaceafter</i>: parameters are substituted after parsing or escaping + * <i>parsemag</i>: transform the message using magic phrases */ function wfMsgExt( $key, $options ) { global $wgOut, $wgParser; @@ -569,7 +612,7 @@ function wfMsgExt( $key, $options ) { * Just like exit() but makes a note of it. * Commits open transactions except if the error parameter is set * - * @obsolete Please return control to the caller or throw an exception + * @deprecated Please return control to the caller or throw an exception */ function wfAbruptExit( $error = false ){ global $wgLoadBalancer; @@ -599,7 +642,7 @@ function wfAbruptExit( $error = false ){ } /** - * @obsolete Please return control the caller or throw an exception + * @deprecated Please return control the caller or throw an exception */ function wfErrorExit() { wfAbruptExit( true ); @@ -736,7 +779,7 @@ function wfBacktrace() { */ function wfShowingResults( $offset, $limit ) { global $wgLang; - return wfMsg( 'showingresults', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) ); + return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) ); } /** @@ -744,7 +787,7 @@ function wfShowingResults( $offset, $limit ) { */ function wfShowingResultsNum( $offset, $limit, $num ) { global $wgLang; - return wfMsg( 'showingresultsnum', $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) ); + return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) ); } /** @@ -941,6 +984,26 @@ function wfArrayToCGI( $array1, $array2 = NULL ) } /** + * Append a query string to an existing URL, which may or may not already + * have query string parameters already. If so, they will be combined. + * + * @param string $url + * @param string $query + * @return string + */ +function wfAppendQuery( $url, $query ) { + if( $query != '' ) { + if( false === strpos( $url, '?' ) ) { + $url .= '?'; + } else { + $url .= '&'; + } + $url .= $query; + } + return $url; +} + +/** * This is obsolete, use SquidUpdate::purge() * @deprecated */ @@ -1104,9 +1167,15 @@ function wfHttpError( $code, $label, $desc ) { * Note that some PHP configuration options may add output buffer * layers which cannot be removed; these are left in place. * - * @parameter bool $resetGzipEncoding + * @param bool $resetGzipEncoding */ function wfResetOutputBuffers( $resetGzipEncoding=true ) { + if( $resetGzipEncoding ) { + // Suppress Content-Encoding and Content-Length + // headers from 1.10+s wfOutputHandler + global $wgDisableOutputCompression; + $wgDisableOutputCompression = true; + } while( $status = ob_get_status() ) { if( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) { // Probably from zlib.output_compression or other @@ -1332,7 +1401,7 @@ define('TS_ISO_8601', 4); /** * An Exif timestamp (YYYY:MM:DD HH:MM:SS) * - * @url http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the + * @see http://exif.org/Exif2-2.PDF The Exif 2.2 spec, see page 28 for the * DateTime tag and page 36 for the DateTimeOriginal and * DateTimeDigitized tags. */ @@ -1624,6 +1693,7 @@ function wfMkdirParents( $fullDir, $mode = 0777 ) { foreach ( $createList as $dir ) { # use chmod to override the umask, as suggested by the PHP manual if ( !mkdir( $dir, $mode ) || !chmod( $dir, $mode ) ) { + wfDebugLog( 'mkdir', "Unable to create directory $dir\n" ); return false; } } @@ -1750,14 +1820,14 @@ function wfShellExec( $cmd, &$retval=null ) { } if ( php_uname( 's' ) == 'Linux' ) { - $time = ini_get( 'max_execution_time' ); + $time = intval( ini_get( 'max_execution_time' ) ); $mem = intval( $wgMaxShellMemory ); $filesize = intval( $wgMaxShellFileSize ); if ( $time > 0 && $mem > 0 ) { - $script = "$IP/bin/ulimit-tvf.sh"; + $script = "$IP/bin/ulimit4.sh"; if ( is_executable( $script ) ) { - $cmd = escapeshellarg( $script ) . " $time $mem $filesize $cmd"; + $cmd = escapeshellarg( $script ) . " $time $mem $filesize " . escapeshellarg( $cmd ); } } } elseif ( php_uname( 's' ) == 'Windows NT' ) { @@ -1844,23 +1914,83 @@ function wfBaseName( $path ) { } /** + * Generate a relative path name to the given file. + * May explode on non-matching case-insensitive paths, + * funky symlinks, etc. + * + * @param string $path Absolute destination path including target filename + * @param string $from Absolute source path, directory only + * @return string + */ +function wfRelativePath( $path, $from ) { + // Normalize mixed input on Windows... + $path = str_replace( '/', DIRECTORY_SEPARATOR, $path ); + $from = str_replace( '/', DIRECTORY_SEPARATOR, $from ); + + $pieces = explode( DIRECTORY_SEPARATOR, dirname( $path ) ); + $against = explode( DIRECTORY_SEPARATOR, $from ); + + // Trim off common prefix + while( count( $pieces ) && count( $against ) + && $pieces[0] == $against[0] ) { + array_shift( $pieces ); + array_shift( $against ); + } + + // relative dots to bump us to the parent + while( count( $against ) ) { + array_unshift( $pieces, '..' ); + array_shift( $against ); + } + + array_push( $pieces, wfBaseName( $path ) ); + + return implode( DIRECTORY_SEPARATOR, $pieces ); +} + +/** * Make a URL index, appropriate for the el_index field of externallinks. */ function wfMakeUrlIndex( $url ) { - wfSuppressWarnings(); + global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php $bits = parse_url( $url ); + wfSuppressWarnings(); wfRestoreWarnings(); - if ( !$bits || $bits['scheme'] !== 'http' ) { + if ( !$bits ) { return false; } + // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it + $delimiter = ''; + if ( in_array( $bits['scheme'] . '://' , $wgUrlProtocols ) ) { + $delimiter = '://'; + } elseif ( in_array( $bits['scheme'] .':' , $wgUrlProtocols ) ) { + $delimiter = ':'; + // parse_url detects for news: and mailto: the host part of an url as path + // We have to correct this wrong detection + if ( isset ( $bits['path'] ) ) { + $bits['host'] = $bits['path']; + $bits['path'] = ''; + } + } else { + return false; + } + // Reverse the labels in the hostname, convert to lower case - $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) ); + // For emails reverse domainpart only + if ( $bits['scheme'] == 'mailto' ) { + $mailparts = explode( '@', $bits['host'] ); + $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) ); + $reversedHost = $domainpart . '@' . $mailparts[0]; + } else { + $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) ); + } // Add an extra dot to the end if ( substr( $reversedHost, -1, 1 ) !== '.' ) { $reversedHost .= '.'; } // Reconstruct the pseudo-URL - $index = "http://$reversedHost"; + $prot = $bits['scheme']; + $index = "$prot$delimiter$reversedHost"; // Leave out user and password. Add the port, path, query and fragment if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port']; if ( isset( $bits['path'] ) ) { @@ -1908,9 +2038,11 @@ function wfExplodeMarkup( $separator, $text ) { * @param $sourceBase int 2-36 * @param $destBase int 2-36 * @param $pad int 1 or greater + * @param $lowercase bool * @return string or false on invalid input */ -function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1 ) { +function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1, $lowercase=true ) { + $input = strval( $input ); if( $sourceBase < 2 || $sourceBase > 36 || $destBase < 2 || @@ -1923,8 +2055,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad=1 ) { $input == '' ) { return false; } - - $digitChars = '0123456789abcdefghijklmnopqrstuvwxyz'; + $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $inDigits = array(); $outChars = ''; @@ -2024,7 +2155,7 @@ function wfIsLocalURL( $url ) { * Initialise php session */ function wfSetupSession() { - global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain; + global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain, $wgCookieSecure; if( $wgSessionsInMemcached ) { require_once( 'MemcachedSessions.php' ); } elseif( 'files' != ini_get( 'session.save_handler' ) ) { @@ -2032,7 +2163,7 @@ function wfSetupSession() { # application, it will end up failing. Try to recover. ini_set ( 'session.save_handler', 'files' ); } - session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain ); + session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure); session_cache_limiter( 'private, must-revalidate' ); @session_start(); } diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php index bda4720d..9a0b6a08 100644 --- a/includes/HTMLCacheUpdate.php +++ b/includes/HTMLCacheUpdate.php @@ -38,7 +38,7 @@ class HTMLCacheUpdate function doUpdate() { # Fetch the IDs $cond = $this->getToCondition(); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( $this->mTable, $this->getFromField(), $cond, __METHOD__ ); $resWrap = new ResultWrapper( $dbr, $res ); if ( $dbr->numRows( $res ) != 0 ) { @@ -136,7 +136,7 @@ class HTMLCacheUpdate return; } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $timestamp = $dbw->timestamp(); $done = false; @@ -184,6 +184,9 @@ class HTMLCacheUpdate } } +/** + * @todo document (e.g. one-sentence top-level class description). + */ class HTMLCacheUpdateJob extends Job { var $table, $start, $end; @@ -218,7 +221,7 @@ class HTMLCacheUpdateJob extends Job { $conds[] = "$fromField <= {$this->end}"; } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( $this->table, $fromField, $conds, __METHOD__ ); $update->invalidateIDs( new ResultWrapper( $dbr, $res ) ); $dbr->freeResult( $res ); diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php index d85a4411..1d3778b2 100644 --- a/includes/HTMLFileCache.php +++ b/includes/HTMLFileCache.php @@ -1,8 +1,7 @@ <?php /** * Contain the HTMLFileCache class - * @package MediaWiki - * @subpackage Cache + * @addtogroup Cache */ /** @@ -16,7 +15,6 @@ * $wgUseFileCache * $wgFileCacheDirectory * $wgUseGzip - * @package MediaWiki */ class HTMLFileCache { var $mTitle, $mFileCache; diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php index 189e5c79..715c8c88 100644 --- a/includes/HTMLForm.php +++ b/includes/HTMLForm.php @@ -2,13 +2,11 @@ /** * This file contain a class to easily build HTML forms as well as custom * functions used by SpecialUserrights.php - * @package MediaWiki */ /** * Class to build various forms * - * @package MediaWiki * @author jeluf, hashar */ class HTMLForm { @@ -125,6 +123,7 @@ class HTMLForm { function HTMLSelectGroups($selectname, $selectmsg, $selected=array(), $multiple=false, $size=6, $reverse=false) { $groups = User::getAllGroups(); $out = htmlspecialchars( wfMsg( $selectmsg ) ); + $out .= "<br />"; if( $multiple ) { $attribs = array( @@ -134,6 +133,7 @@ function HTMLSelectGroups($selectname, $selectmsg, $selected=array(), $multiple= } else { $attribs = array( 'name' => $selectname ); } + $attribs['style'] = 'width: 100%'; $out .= wfElement( 'select', $attribs, null ); foreach( $groups as $group ) { diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php index a06b620d..9dfd6d61 100644 --- a/includes/HistoryBlob.php +++ b/includes/HistoryBlob.php @@ -1,82 +1,86 @@ <?php /** * - * @package MediaWiki */ /** * Pure virtual parent - * @package MediaWiki + * @todo document (needs a one-sentence top-level class description, that answers the question: "what is a HistoryBlob?") */ -class HistoryBlob +interface HistoryBlob { /** * setMeta and getMeta currently aren't used for anything, I just thought * they might be useful in the future. * @param $meta String: a single string. */ - function setMeta( $meta ) {} + public function setMeta( $meta ); /** * setMeta and getMeta currently aren't used for anything, I just thought * they might be useful in the future. * Gets the meta-value */ - function getMeta() {} + public function getMeta(); /** * Adds an item of text, returns a stub object which points to the item. * You must call setLocation() on the stub object before storing it to the * database */ - function addItem() {} + public function addItem( $text ); /** * Get item by hash */ - function getItem( $hash ) {} + public function getItem( $hash ); # Set the "default text" # This concept is an odd property of the current DB schema, whereby each text item has a revision # associated with it. The default text is the text of the associated revision. There may, however, # be other revisions in the same object - function setText() {} + public function setText( $text ); /** * Get default text. This is called from Revision::getRevisionText() */ - function getText() {} + function getText(); } /** * The real object - * @package MediaWiki + * @todo document (needs one-sentence top-level class description + function descriptions). */ -class ConcatenatedGzipHistoryBlob extends HistoryBlob +class ConcatenatedGzipHistoryBlob implements HistoryBlob { - /* private */ var $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = ''; - /* private */ var $mFast = 0, $mSize = 0; + public $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = ''; + public $mFast = 0, $mSize = 0; - function ConcatenatedGzipHistoryBlob() { + /** Constructor */ + public function ConcatenatedGzipHistoryBlob() { if ( !function_exists( 'gzdeflate' ) ) { throw new MWException( "Need zlib support to read or write this kind of history object (ConcatenatedGzipHistoryBlob)\n" ); } } + # + # HistoryBlob implementation: + # + /** @todo document */ - function setMeta( $metaData ) { + public function setMeta( $metaData ) { $this->uncompress(); $this->mItems['meta'] = $metaData; } /** @todo document */ - function getMeta() { + public function getMeta() { $this->uncompress(); return $this->mItems['meta']; } /** @todo document */ - function addItem( $text ) { + public function addItem( $text ) { $this->uncompress(); $hash = md5( $text ); $this->mItems[$hash] = $text; @@ -87,7 +91,7 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob } /** @todo document */ - function getItem( $hash ) { + public function getItem( $hash ) { $this->uncompress(); if ( array_key_exists( $hash, $this->mItems ) ) { return $this->mItems[$hash]; @@ -97,13 +101,29 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob } /** @todo document */ - function removeItem( $hash ) { + public function setText( $text ) { + $this->uncompress(); + $stub = $this->addItem( $text ); + $this->mDefaultHash = $stub->mHash; + } + + /** @todo document */ + public function getText() { + $this->uncompress(); + return $this->getItem( $this->mDefaultHash ); + } + + # HistoryBlob implemented. + + + /** @todo document */ + public function removeItem( $hash ) { $this->mSize -= strlen( $this->mItems[$hash] ); unset( $this->mItems[$hash] ); } /** @todo document */ - function compress() { + public function compress() { if ( !$this->mCompressed ) { $this->mItems = gzdeflate( serialize( $this->mItems ) ); $this->mCompressed = true; @@ -111,25 +131,13 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob } /** @todo document */ - function uncompress() { + public function uncompress() { if ( $this->mCompressed ) { $this->mItems = unserialize( gzinflate( $this->mItems ) ); $this->mCompressed = false; } } - /** @todo document */ - function getText() { - $this->uncompress(); - return $this->getItem( $this->mDefaultHash ); - } - - /** @todo document */ - function setText( $text ) { - $this->uncompress(); - $stub = $this->addItem( $text ); - $this->mDefaultHash = $stub->mHash; - } /** @todo document */ function __sleep() { @@ -145,7 +153,7 @@ class ConcatenatedGzipHistoryBlob extends HistoryBlob /** * Determines if this object is happy */ - function isHappy( $maxFactor, $factorThreshold ) { + public function isHappy( $maxFactor, $factorThreshold ) { if ( count( $this->mItems ) == 0 ) { return true; } @@ -179,7 +187,7 @@ $wgBlobCache = array(); /** - * @package MediaWiki + * @todo document (needs one-sentence top-level class description + some function descriptions). */ class HistoryBlobStub { var $mOldId, $mHash, $mRef; @@ -197,28 +205,28 @@ class HistoryBlobStub { $this->mOldId = $id; } - /** - * Sets the location (old_id) of the referring object - */ + /** + * Sets the location (old_id) of the referring object + */ function setReferrer( $id ) { $this->mRef = $id; } - /** - * Gets the location of the referring object - */ + /** + * Gets the location of the referring object + */ function getReferrer() { return $this->mRef; } /** @todo document */ function getText() { - $fname = 'HistoryBlob::getText'; + $fname = 'HistoryBlobStub::getText'; global $wgBlobCache; if( isset( $wgBlobCache[$this->mOldId] ) ) { $obj = $wgBlobCache[$this->mOldId]; } else { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId ) ); if( !$row ) { return false; @@ -273,8 +281,6 @@ class HistoryBlobStub { * * Serialized HistoryBlobCurStub objects will be inserted into the text table * on conversion if $wgFastSchemaUpgrades is set to true. - * - * @package MediaWiki */ class HistoryBlobCurStub { var $mCurId; @@ -294,7 +300,7 @@ class HistoryBlobCurStub { /** @todo document */ function getText() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId ) ); if( !$row ) { return false; diff --git a/includes/Hooks.php b/includes/Hooks.php index 2eecfd72..b428b08d 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -18,7 +18,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * @author Evan Prodromou <evan@wikitravel.org> - * @package MediaWiki * @see hooks.txt */ @@ -103,7 +102,7 @@ function wfRunHooks($event, $args = null) { if ( isset( $object ) ) { $func = get_class( $object ) . '::' . $method; $callback = array( $object, $method ); - } elseif ( false !== ( $pos = strpos( '::', $func ) ) ) { + } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) { $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) ); } else { $callback = $func; diff --git a/includes/IP.php b/includes/IP.php index edf4af7a..8a2756c9 100644 --- a/includes/IP.php +++ b/includes/IP.php @@ -1,8 +1,5 @@ <?php /* - * Collection of public static functions to play with IP address - * and IP blocks. - * * @Author "Ashar Voultoiz" <hashar@altern.org> * @License GPL v2 or later */ @@ -12,22 +9,231 @@ // An IP is made of 4 bytes from x00 to xFF which is d0 to d255 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])'); define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE ); -// An IP block is an IP address and a prefix (d1 to d32) +// An IPv4 block is an IP address and a prefix (d1 to d32) define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)'); define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX); // For IPv6 canonicalization (NOT for strict validation; these are quite lax!) define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' ); define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' ); define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' ); +// An IPv6 block is an IP address and a prefix (d1 to d128) +define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)'); +// An IPv6 IP is made up of 8 octets. However abbreviations like "::" can be used. This is lax! +define( 'RE_IPV6_ADD', '(:(:' . RE_IPV6_WORD . '){1,7}|' . RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '|::$){1,7})' ); +define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX ); +// This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network +define( 'IP_ADDRESS_STRING', RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)|' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)'); +/** + * A collection of public static functions to play with IP address + * and IP blocks. + */ class IP { + /** + * Given a string, determine if it as valid IP + * Unlike isValid(), this looks for networks too + * @param $ip IP address. + * @return string + */ + public static function isIPAddress( $ip ) { + if ( !$ip ) return false; + if ( is_array( $ip ) ) { + throw new MWException( "invalid value passed to " . __METHOD__ ); + } + // IPv6 IPs with two "::" strings are ambiguous and thus invalid + return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip) && ( substr_count($ip, '::') < 2 ); + } + + public static function isIPv6( $ip ) { + if ( !$ip ) return false; + if( is_array( $ip ) ) { + throw new MWException( "invalid value passed to " . __METHOD__ ); + } + // IPv6 IPs with two "::" strings are ambiguous and thus invalid + return preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip) && ( substr_count($ip, '::') < 2); + } + + public static function isIPv4( $ip ) { + if ( !$ip ) return false; + return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip); + } + + /** + * Given an IP address in dotted-quad notation, returns an IPv6 octet. + * See http://www.answers.com/topic/ipv4-compatible-address + * IPs with the first 92 bits as zeros are reserved from IPv6 + * @param $ip quad-dotted IP address. + * @return string + */ + public static function IPv4toIPv6( $ip ) { + if ( !$ip ) return null; + // Convert only if needed + if ( self::isIPv6( $ip ) ) return $ip; + // IPv4 CIDRs + if ( strpos( $ip, '/' ) !== false ) { + $parts = explode( '/', $ip, 2 ); + if ( count( $parts ) != 2 ) { + return false; + } + $network = self::toUnsigned( $parts[0] ); + if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) { + $bits = $parts[1] + 96; + return self::toOctet( $network ) . "/$bits"; + } else { + return false; + } + } + return self::toOctet( self::toUnsigned( $ip ) ); + } /** + * Given an IPv6 address in octet notation, returns an unsigned integer. + * @param $ip octet ipv6 IP address. + * @return string + */ + public static function toUnsigned6( $ip ) { + if ( !$ip ) return null; + $ip = explode(':', self::sanitizeIP( $ip ) ); + $r_ip = ''; + foreach ($ip as $v) { + $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT ); + } + $r_ip = wfBaseConvert( $r_ip, 16, 10 ); + return $r_ip; + } + + /** + * Given an IPv6 address in octet notation, returns the expanded octet. + * IPv4 IPs will be trimmed, thats it... + * @param $ip octet ipv6 IP address. + * @return string + */ + public static function sanitizeIP( $ip ) { + if ( !$ip ) return null; + // Trim and return IPv4 addresses + if ( self::isIPv4($ip) ) return trim($ip); + // Only IPv6 addresses can be expanded + if ( !self::isIPv6($ip) ) return $ip; + // Remove any whitespaces, convert to upper case + $ip = strtoupper( trim($ip) ); + // Expand zero abbreviations + if ( strpos( $ip, '::' ) !== false ) { + $ip = str_replace('::', str_repeat(':0', 8 - substr_count($ip, ':')) . ':', $ip); + } + // For IPs that start with "::", correct the final IP so that it starts with '0' and not ':' + if ( $ip[0] == ':' ) $ip = "0$ip"; + // Remove leading zereos from each bloc as needed + $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip ); + return $ip; + } + + /** + * Given an unsigned integer, returns an IPv6 address in octet notation + * @param $ip integer IP address. + * @return string + */ + public static function toOctet( $ip_int ) { + // Convert to padded uppercase hex + $ip_hex = wfBaseConvert($ip_int, 10, 16, 32, false); + // Seperate into 8 octets + $ip_oct = substr( $ip_hex, 0, 4 ); + for ($n=1; $n < 8; $n++) { + $ip_oct .= ':' . substr($ip_hex, 4*$n, 4); + } + // NO leading zeroes + $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct ); + return $ip_oct; + } + + /** + * Convert a network specification in IPv6 CIDR notation to an integer network and a number of bits + * @return array(string, int) + */ + public static function parseCIDR6( $range ) { + # Expand any IPv6 IP + $parts = explode( '/', IP::sanitizeIP( $range ), 2 ); + if ( count( $parts ) != 2 ) { + return array( false, false ); + } + $network = self::toUnsigned6( $parts[0] ); + if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 128 ) { + $bits = $parts[1]; + if ( $bits == 0 ) { + $network = 0; + } else { + # Native 32 bit functions WONT work here!!! + # Convert to a padded binary number + $network = wfBaseConvert( $network, 10, 2, 128 ); + # Truncate the last (128-$bits) bits and replace them with zeros + $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT ); + # Convert back to an integer + $network = wfBaseConvert( $network, 2, 10 ); + } + } else { + $network = false; + $bits = false; + } + return array( $network, $bits ); + } + + /** + * Given a string range in a number of formats, return the start and end of + * the range in hexadecimal. For IPv6. + * + * Formats are: + * 2001:0db8:85a3::7344/96 CIDR + * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range + * 2001:0db8:85a3::7344/96 Single IP + * @return array(string, int) + */ + public static function parseRange6( $range ) { + # Expand any IPv6 IP + $range = IP::sanitizeIP( $range ); + if ( strpos( $range, '/' ) !== false ) { + # CIDR + list( $network, $bits ) = self::parseCIDR6( $range ); + if ( $network === false ) { + $start = $end = false; + } else { + $start = wfBaseConvert( $network, 10, 16, 32, false ); + # Turn network to binary (again) + $end = wfBaseConvert( $network, 10, 2, 128 ); + # Truncate the last (128-$bits) bits and replace them with ones + $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT ); + # Convert to hex + $end = wfBaseConvert( $end, 2, 16, 32, false ); + # see toHex() comment + $start = "v6-$start"; $end = "v6-$end"; + } + } elseif ( strpos( $range, '-' ) !== false ) { + # Explicit range + list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) ); + $start = self::toUnsigned6( $start ); $end = self::toUnsigned6( $end ); + if ( $start > $end ) { + $start = $end = false; + } else { + $start = wfBaseConvert( $start, 10, 16, 32, false ); + $end = wfBaseConvert( $end, 10, 16, 32, false ); + } + # see toHex() comment + $start = "v6-$start"; $end = "v6-$end"; + } else { + # Single IP + $start = $end = self::toHex( $range ); + } + if ( $start === false || $end === false ) { + return array( false, false ); + } else { + return array( $start, $end ); + } + } + + /** * Validate an IP address. * @return boolean True if it is valid. */ public static function isValid( $ip ) { - return preg_match( '/^' . RE_IP_ADD . '$/', $ip) ; + return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip) ); } /** @@ -44,7 +250,7 @@ class IP { * Comes from ProxyTools.php */ public static function isPublic( $ip ) { - $n = IP::toUnsigned( $ip ); + $n = self::toUnsigned( $ip ); if ( !$n ) { return false; } @@ -67,8 +273,8 @@ class IP { } foreach ( $privateRanges as $r ) { - $start = IP::toUnsigned( $r[0] ); - $end = IP::toUnsigned( $r[1] ); + $start = self::toUnsigned( $r[0] ); + $end = self::toUnsigned( $r[1] ); if ( $n >= $start && $n <= $end ) { return false; } @@ -80,15 +286,17 @@ class IP { * Split out an IP block as an array of 4 bytes and a mask, * return false if it can't be determined * - * @parameter $ip string A quad dotted IP address + * @param $ip string A quad dotted/octet IP address * @return array */ public static function toArray( $ipblock ) { $matches = array(); - if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) { - return false; - } else { + if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) { + return $matches; + } else if ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:'.RE_IPV6_PREFIX.'))?' . '$/', $ipblock, $matches ) ) { return $matches; + } else { + return false; } } @@ -100,23 +308,29 @@ class IP { * function for an IPv6 address will be prefixed with "v6-", a non- * hexadecimal string which sorts after the IPv4 addresses. * - * @param $ip Quad dotted IP address. + * @param $ip Quad dotted/octet IP address. + * @return hexidecimal */ public static function toHex( $ip ) { $n = self::toUnsigned( $ip ); if ( $n !== false ) { - $n = sprintf( '%08X', $n ); + $n = ( self::isIPv6($ip) ) ? "v6-" . wfBaseConvert( $n, 10, 16, 32, false ) : wfBaseConvert( $n, 10, 16, 8, false ); } return $n; } /** - * Given an IP address in dotted-quad notation, returns an unsigned integer. + * Given an IP address in dotted-quad/octet notation, returns an unsigned integer. * Like ip2long() except that it actually works and has a consistent error return value. * Comes from ProxyTools.php * @param $ip Quad dotted IP address. + * @return integer */ public static function toUnsigned( $ip ) { + // Use IPv6 functions if needed + if ( self::isIPv6( $ip ) ) { + return self::toUnsigned6( $ip ); + } if ( $ip == '255.255.255.255' ) { $n = -1; } else { @@ -149,13 +363,14 @@ class IP { /** * Convert a network specification in CIDR notation to an integer network and a number of bits + * @return array(string, int) */ public static function parseCIDR( $range ) { $parts = explode( '/', $range, 2 ); if ( count( $parts ) != 2 ) { return array( false, false ); } - $network = IP::toSigned( $parts[0] ); + $network = self::toSigned( $parts[0] ); if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) { $bits = $parts[1]; if ( $bits == 0 ) { @@ -182,11 +397,20 @@ class IP { * 1.2.3.4/24 CIDR * 1.2.3.4 - 1.2.3.5 Explicit range * 1.2.3.4 Single IP + * + * 2001:0db8:85a3::7344/96 CIDR + * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range + * 2001:0db8:85a3::7344 Single IP + * @return array(string, int) */ public static function parseRange( $range ) { + // Use IPv6 functions if needed + if ( self::isIPv6( $range ) ) { + return self::parseRange6( $range ); + } if ( strpos( $range, '/' ) !== false ) { # CIDR - list( $network, $bits ) = IP::parseCIDR( $range ); + list( $network, $bits ) = self::parseCIDR( $range ); if ( $network === false ) { $start = $end = false; } else { @@ -196,15 +420,16 @@ class IP { } elseif ( strpos( $range, '-' ) !== false ) { # Explicit range list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) ); + $start = self::toUnsigned( $start ); $end = self::toUnsigned( $end ); if ( $start > $end ) { $start = $end = false; } else { - $start = IP::toHex( $start ); - $end = IP::toHex( $end ); + $start = sprintf( '%08X', $start ); + $end = sprintf( '%08X', $end ); } } else { # Single IP - $start = $end = IP::toHex( $range ); + $start = $end = self::toHex( $range ); } if ( $start === false || $end === false ) { return array( false, false ); @@ -214,18 +439,15 @@ class IP { } /** - * Determine if a given integer IPv4 address is in a given CIDR network + * Determine if a given IPv4/IPv6 address is in a given CIDR network * @param $addr The address to check against the given range. * @param $range The range to check the given address against. * @return bool Whether or not the given address is in the given range. */ public static function isInRange( $addr, $range ) { - $unsignedIP = IP::toUnsigned($addr); - list( $start, $end ) = IP::parseRange($range); - - $start = hexdec($start); - $end = hexdec($end); - + // Convert to IPv6 if needed + $unsignedIP = self::toHex( $addr ); + list( $start, $end ) = self::parseRange( $range ); return (($unsignedIP >= $start) && ($unsignedIP <= $end)); } @@ -240,10 +462,11 @@ class IP { * @return valid dotted quad IPv4 address or null */ public static function canonicalize( $addr ) { - if ( IP::isValid( $addr ) ) + if ( self::isValid( $addr ) ) return $addr; // IPv6 loopback address + $m = array(); if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) return '127.0.0.1'; diff --git a/includes/Image.php b/includes/Image.php index 7a6442c3..09c2286e 100644 --- a/includes/Image.php +++ b/includes/Image.php @@ -1,6 +1,5 @@ <?php /** - * @package MediaWiki */ /** @@ -15,17 +14,24 @@ /** * Bump this number when serialized cache records may be incompatible. */ -define( 'MW_IMAGE_VERSION', 1 ); +define( 'MW_IMAGE_VERSION', 2 ); /** * Class to represent an image * * Provides methods to retrieve paths (physical, logical, URL), * to generate thumbnails or for uploading. - * @package MediaWiki + * + * @addtogroup Media */ class Image { + const DELETED_FILE = 1; + const DELETED_COMMENT = 2; + const DELETED_USER = 4; + const DELETED_RESTRICTED = 8; + const RENDER_NOW = 1; + /**#@+ * @private */ @@ -43,6 +49,7 @@ class Image $attr, # / $type, # MEDIATYPE_xxx (bitmap, drawing, audio...) $mime, # MIME type, determined by MimeMagic::guessMimeType + $extension, # The file extension (constructor) $size, # Size in bytes (loadFromXxx) $metadata, # Metadata $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx) @@ -81,18 +88,16 @@ class Image } $this->title =& $title; $this->name = $title->getDBkey(); - $this->metadata = serialize ( array() ) ; + $this->metadata = ''; $n = strrpos( $this->name, '.' ); $this->extension = Image::normalizeExtension( $n ? substr( $this->name, $n + 1 ) : '' ); $this->historyLine = 0; - $this->page = 1; $this->dataLoaded = false; } - /** * Normalize a file extension to the common form, and ensure it's clean. * Extensions with non-alphanumeric characters will be discarded. @@ -115,7 +120,7 @@ class Image return ''; } } - + /** * Get the memcached keys * Returns an array, first element is the local cache key, second is the shared cache key, if there is one @@ -264,32 +269,26 @@ class Image $this->mime = $magic->guessMimeType($this->imagePath,true); $this->type = $magic->getMediaType($this->imagePath,$this->mime); + $handler = MediaHandler::getHandler( $this->mime ); # Get size in bytes $this->size = filesize( $this->imagePath ); - $magic=& MimeMagic::singleton(); - - # Height and width - wfSuppressWarnings(); - if( $this->mime == 'image/svg' ) { - $gis = wfGetSVGsize( $this->imagePath ); - } elseif( $this->mime == 'image/vnd.djvu' ) { - $deja = new DjVuImage( $this->imagePath ); - $gis = $deja->getImageSize(); - } elseif ( !$magic->isPHPImageType( $this->mime ) ) { - # Don't try to get the width and height of sound and video files, that's bad for performance - $gis = false; + # Height, width and metadata + if ( $handler ) { + $gis = $handler->getImageSize( $this, $this->imagePath ); + $this->metadata = $handler->getMetadata( $this, $this->imagePath ); } else { - $gis = getimagesize( $this->imagePath ); + $gis = false; + $this->metadata = ''; } - wfRestoreWarnings(); wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n"); } else { $this->mime = NULL; $this->type = MEDIATYPE_UNKNOWN; + $this->metadata = ''; wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n"); } @@ -308,13 +307,6 @@ class Image # as ther's only one thread of execution, this should be safe anyway. $this->dataLoaded = true; - - if ( $this->mime == 'image/vnd.djvu' ) { - $this->metadata = $deja->retrieveMetaData(); - } else { - $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) ); - } - if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits']; else $this->bits = 0; @@ -328,7 +320,7 @@ class Image global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang; wfProfileIn( __METHOD__ ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $this->checkDBSchema($dbr); $row = $dbr->selectRow( 'image', @@ -341,15 +333,13 @@ class Image $this->loadFromRow( $row ); $this->imagePath = $this->getFullPath(); // Check for rows from a previous schema, quietly upgrade them - if ( is_null($this->type) ) { - $this->upgradeRow(); - } + $this->maybeUpgradeRow(); } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) { # In case we're on a wgCapitalLinks=false wiki, we # capitalize the first letter of the filename before # looking it up in the shared repository. $name = $wgContLang->ucfirst($this->name); - $dbc =& wfGetDB( DB_SLAVE, 'commons' ); + $dbc = Image::getCommonsDB(); $row = $dbc->selectRow( "`$wgSharedUploadDBname`.{$wgSharedUploadDBprefix}image", array( @@ -364,9 +354,7 @@ class Image $this->loadFromRow( $row ); // Check for rows from a previous schema, quietly upgrade them - if ( is_null($this->type) ) { - $this->upgradeRow(); - } + $this->maybeUpgradeRow(); } } @@ -378,7 +366,7 @@ class Image $this->type = 0; $this->fileExists = false; $this->fromSharedDirectory = false; - $this->metadata = serialize ( array() ) ; + $this->metadata = ''; $this->mime = false; } @@ -405,9 +393,7 @@ class Image if (!$minor) $minor= "unknown"; $this->mime = $major.'/'.$minor; } - $this->metadata = $row->img_metadata; - if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ; $this->dataLoaded = true; } @@ -434,8 +420,21 @@ class Image } /** - * Metadata was loaded from the database, but the row had a marker indicating it needs to be - * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row. + * Upgrade a row if it needs it + */ + function maybeUpgradeRow() { + if ( is_null($this->type) || $this->mime == 'image/svg' ) { + $this->upgradeRow(); + } else { + $handler = $this->getHandler(); + if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) { + $this->upgradeRow(); + } + } + } + + /** + * Fix assorted version-related problems with the image row by reloading it from the file */ function upgradeRow() { global $wgDBname, $wgSharedUploadDBname; @@ -451,17 +450,16 @@ class Image // Write to the other DB using selectDB, not database selectors // This avoids breaking replication in MySQL - $dbw =& wfGetDB( DB_MASTER, 'commons' ); - $dbw->selectDB( $wgSharedUploadDBname ); + $dbw = Image::getCommonsDB(); } else { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); } $this->checkDBSchema($dbw); list( $major, $minor ) = self::splitMime( $this->mime ); - wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n"); + wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n"); $dbw->update( 'image', array( @@ -479,7 +477,7 @@ class Image } wfProfileOut( __METHOD__ ); } - + /** * Split an internet media type into its two components; if not * a two-part name, set the minor type to 'unknown'. @@ -554,23 +552,49 @@ class Image /** * Return the width of the image * - * Returns -1 if the file specified is not a known image type + * Returns false on error * @public */ - function getWidth() { + function getWidth( $page = 1 ) { $this->load(); - return $this->width; + if ( $this->isMultipage() ) { + $dim = $this->getHandler()->getPageDimensions( $this, $page ); + if ( $dim ) { + return $dim['width']; + } else { + return false; + } + } else { + return $this->width; + } } /** * Return the height of the image * - * Returns -1 if the file specified is not a known image type + * Returns false on error * @public */ - function getHeight() { + function getHeight( $page = 1 ) { $this->load(); - return $this->height; + if ( $this->isMultipage() ) { + $dim = $this->getHandler()->getPageDimensions( $this, $page ); + if ( $dim ) { + return $dim['height']; + } else { + return false; + } + } else { + return $this->height; + } + } + + /** + * Get handler-specific metadata + */ + function getMetadata() { + $this->load(); + return $this->metadata; } /** @@ -610,58 +634,10 @@ class Image * @todo remember the result of this check. */ function canRender() { - global $wgUseImageMagick, $wgDjvuRenderer; - - if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false; - - $mime= $this->getMimeType(); - - if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false; - - #if it's SVG, check if there's a converter enabled - if ($mime === 'image/svg') { - global $wgSVGConverters, $wgSVGConverter; - - if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) { - wfDebug( "Image::canRender: SVG is ready!\n" ); - return true; - } else { - wfDebug( "Image::canRender: SVG renderer missing\n" ); - } - } - - #image formats available on ALL browsers - if ( $mime === 'image/gif' - || $mime === 'image/png' - || $mime === 'image/jpeg' ) return true; - - #image formats that can be converted to the above formats - if ($wgUseImageMagick) { - #convertable by ImageMagick (there are more...) - if ( $mime === 'image/vnd.wap.wbmp' - || $mime === 'image/x-xbitmap' - || $mime === 'image/x-xpixmap' - #|| $mime === 'image/x-icon' #file may be split into multiple parts - || $mime === 'image/x-portable-anymap' - || $mime === 'image/x-portable-bitmap' - || $mime === 'image/x-portable-graymap' - || $mime === 'image/x-portable-pixmap' - #|| $mime === 'image/x-photoshop' #this takes a lot of CPU and RAM! - || $mime === 'image/x-rgb' - || $mime === 'image/x-bmp' - || $mime === 'image/tiff' ) return true; - } - else { - #convertable by the PHP GD image lib - if ( $mime === 'image/vnd.wap.wbmp' - || $mime === 'image/x-xbitmap' ) return true; - } - if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true; - - return false; + $handler = $this->getHandler(); + return $handler && $handler->canRender(); } - /** * Return true if the file is of a type that can't be directly * rendered by typical browsers and needs to be re-rasterized. @@ -673,13 +649,8 @@ class Image * @return bool */ function mustRender() { - $mime= $this->getMimeType(); - - if ( $mime === "image/gif" - || $mime === "image/png" - || $mime === "image/jpeg" ) return false; - - return true; + $handler = $this->getHandler(); + return $handler && $handler->mustRender(); } /** @@ -745,15 +716,7 @@ class Image * @public */ function getEscapeLocalURL( $query=false) { - $this->getTitle(); - if ( $query === false ) { - if ( $this->page != 1 ) { - $query = 'page=' . $this->page; - } else { - $query = ''; - } - } - return $this->title->escapeLocalURL( $query ); + return $this->getTitle()->escapeLocalURL( $query ); } /** @@ -801,74 +764,83 @@ class Image * @todo document * @private */ - function thumbUrl( $width, $subdir='thumb') { + function thumbUrlFromName( $thumbName, $subdir = 'thumb' ) { global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath; - global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath; + if($this->fromSharedDirectory) { + $base = ''; + $path = $wgSharedUploadPath; + } else { + $base = $wgUploadBaseUrl; + $path = $wgUploadPath; + } + if ( Image::isHashed( $this->fromSharedDirectory ) ) { + $hashdir = wfGetHashPath($this->name, $this->fromSharedDirectory) . + wfUrlencode( $this->name ); + } else { + $hashdir = ''; + } + $url = "{$base}{$path}/{$subdir}{$hashdir}/" . wfUrlencode( $thumbName ); + return $url; + } - // Generate thumb.php URL if possible - $script = false; - $url = false; + /** + * @deprecated Use $image->transform()->getUrl() or thumbUrlFromName() + */ + function thumbUrl( $width, $subdir = 'thumb' ) { + $name = $this->thumbName( array( 'width' => $width ) ); + if ( strval( $name ) !== '' ) { + return array( false, $this->thumbUrlFromName( $name, $subdir ) ); + } else { + return array( false, false ); + } + } + function getTransformScript() { + global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath; if ( $this->fromSharedDirectory ) { - if ( $wgSharedThumbnailScriptPath ) { - $script = $wgSharedThumbnailScriptPath; - } + $script = $wgSharedThumbnailScriptPath; } else { - if ( $wgThumbnailScriptPath ) { - $script = $wgThumbnailScriptPath; - } + $script = $wgThumbnailScriptPath; } if ( $script ) { - $url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width ); - if( $this->mustRender() ) { - $url.= '&r=1'; - } + return "$script?f=" . urlencode( $this->name ); } else { - $name = $this->thumbName( $width ); - if($this->fromSharedDirectory) { - $base = ''; - $path = $wgSharedUploadPath; - } else { - $base = $wgUploadBaseUrl; - $path = $wgUploadPath; - } - if ( Image::isHashed( $this->fromSharedDirectory ) ) { - $url = "{$base}{$path}/{$subdir}" . - wfGetHashPath($this->name, $this->fromSharedDirectory) - . $this->name.'/'.$name; - $url = wfUrlencode( $url ); - } else { - $url = "{$base}{$path}/{$subdir}/{$name}"; - } + return false; } - return array( $script !== false, $url ); } /** - * Return the file name of a thumbnail of the specified width + * Get a ThumbnailImage which is the same size as the source + */ + function getUnscaledThumb( $page = false ) { + if ( $page ) { + $params = array( + 'page' => $page, + 'width' => $this->getWidth( $page ) + ); + } else { + $params = array( 'width' => $this->getWidth() ); + } + return $this->transform( $params ); + } + + /** + * Return the file name of a thumbnail with the specified parameters * - * @param integer $width Width of the thumbnail image - * @param boolean $shared Does the thumbnail come from the shared repository? + * @param array $params Handler-specific parameters * @private */ - function thumbName( $width ) { - $thumb = $width."px-".$this->name; - if ( $this->page != 1 ) { - $thumb = "page{$this->page}-$thumb"; + function thumbName( $params ) { + $handler = $this->getHandler(); + if ( !$handler ) { + return null; } - - if( $this->mustRender() ) { - if( $this->canRender() ) { - # Rasterize to PNG (for SVG vector images, etc) - $thumb .= '.png'; - } - else { - #should we use iconThumb here to get a symbolic thumbnail? - #or should we fail with an internal error? - return NULL; //can't make bitmap - } + list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime ); + $thumbName = $handler->makeParamString( $params ) . '-' . $this->name; + if ( $thumbExt != $this->extension ) { + $thumbName .= ".$thumbExt"; } - return $thumb; + return $thumbName; } /** @@ -887,9 +859,13 @@ class Image * @param integer $height maximum height of the image (optional) * @public */ - function createThumb( $width, $height=-1 ) { - $thumb = $this->getThumbnail( $width, $height ); - if( is_null( $thumb ) ) return ''; + function createThumb( $width, $height = -1 ) { + $params = array( 'width' => $width ); + if ( $height != -1 ) { + $params['height'] = $height; + } + $thumb = $this->transform( $params ); + if( is_null( $thumb ) || $thumb->isError() ) return ''; return $thumb->getUrl(); } @@ -907,149 +883,90 @@ class Image * * @return ThumbnailImage or null on failure * @public + * + * @deprecated use transform() */ function getThumbnail( $width, $height=-1, $render = true ) { - wfProfileIn( __METHOD__ ); - if ($this->canRender()) { - if ( $height > 0 ) { - $this->load(); - if ( $width > $this->width * $height / $this->height ) { - $width = wfFitBoxWidth( $this->width, $this->height, $height ); - } - } - if ( $render ) { - $thumb = $this->renderThumb( $width ); - } else { - // Don't render, just return the URL - if ( $this->validateThumbParams( $width, $height ) ) { - if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) { - $url = $this->getURL(); - } else { - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width ); - } - $thumb = new ThumbnailImage( $url, $width, $height ); - } else { - $thumb = null; - } - } - } else { - // not a bitmap or renderable image, don't try. - $thumb = $this->iconThumb(); + $params = array( 'width' => $width ); + if ( $height != -1 ) { + $params['height'] = $height; } - wfProfileOut( __METHOD__ ); - return $thumb; + $flags = $render ? self::RENDER_NOW : 0; + return $this->transform( $params, $flags ); } - + /** - * @return ThumbnailImage + * Transform a media file + * + * @param array $params An associative array of handler-specific parameters. Typical + * keys are width, height and page. + * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering + * @return MediaTransformOutput */ - function iconThumb() { - global $wgStylePath, $wgStyleDirectory; + function transform( $params, $flags = 0 ) { + global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors; - $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' ); - foreach( $try as $icon ) { - $path = '/common/images/icons/' . $icon; - $filepath = $wgStyleDirectory . $path; - if( file_exists( $filepath ) ) { - return new ThumbnailImage( $wgStylePath . $path, 120, 120 ); + wfProfileIn( __METHOD__ ); + do { + $handler = $this->getHandler(); + if ( !$handler || !$handler->canRender() ) { + // not a bitmap or renderable image, don't try. + $thumb = $this->iconThumb(); + break; } - } - return null; - } - /** - * Validate thumbnail parameters and fill in the correct height - * - * @param integer &$width Specified width (input/output) - * @param integer &$height Height (output only) - * @return false to indicate that an error should be returned to the user. - */ - function validateThumbParams( &$width, &$height ) { - global $wgSVGMaxSize, $wgMaxImageArea; - - $this->load(); + $script = $this->getTransformScript(); + if ( $script && !($flags & self::RENDER_NOW) ) { + // Use a script to transform on client request + $thumb = $handler->getScriptedTransform( $this, $script, $params ); + break; + } - if ( ! $this->exists() ) - { - # If there is no image, there will be no thumbnail - return false; - } - - $width = intval( $width ); - - # Sanity check $width - if( $width <= 0 || $this->width <= 0) { - # BZZZT - return false; - } + $normalisedParams = $params; + $handler->normaliseParams( $this, $normalisedParams ); + list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime ); + $thumbName = $this->thumbName( $normalisedParams ); + $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName"; + $thumbUrl = $this->thumbUrlFromName( $thumbName ); - # Don't thumbnail an image so big that it will fill hard drives and send servers into swap - # JPEG has the handy property of allowing thumbnailing without full decompression, so we make - # an exception for it. - if ( $this->getMediaType() == MEDIATYPE_BITMAP && - $this->getMimeType() !== 'image/jpeg' && - $this->width * $this->height > $wgMaxImageArea ) - { - return false; - } + $this->migrateThumbFile( $thumbName ); - # Don't make an image bigger than the source, or wgMaxSVGSize for SVGs - if ( $this->mustRender() ) { - $width = min( $width, $wgSVGMaxSize ); - } elseif ( $width > $this->width - 1 ) { - $width = $this->width; - $height = $this->height; - return true; - } + if ( file_exists( $thumbPath ) ) { + $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); + break; + } - $height = round( $this->height * $width / $this->width ); - return true; + if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) { + $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); + break; + } + $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params ); + + // Ignore errors if requested + if ( !$thumb ) { + $thumb = null; + } elseif ( $thumb->isError() ) { + $this->lastError = $thumb->toText(); + if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) { + $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); + } + } + + if ( $wgUseSquid ) { + wfPurgeSquidServers( array( $thumbUrl ) ); + } + } while (false); + + wfProfileOut( __METHOD__ ); + return $thumb; } - + /** - * Create a thumbnail of the image having the specified width. - * The thumbnail will not be created if the width is larger than the - * image's width. Let the browser do the scaling in this case. - * The thumbnail is stored on disk and is only computed if the thumbnail - * file does not exist OR if it is older than the image. - * Returns an object which can return the pathname, URL, and physical - * pixel size of the thumbnail -- or null on failure. - * - * @return ThumbnailImage or null on failure - * @private + * Fix thumbnail files from 1.4 or before, with extreme prejudice */ - function renderThumb( $width, $useScript = true ) { - global $wgUseSquid, $wgThumbnailEpoch; - - wfProfileIn( __METHOD__ ); - - $this->load(); - $height = -1; - if ( !$this->validateThumbParams( $width, $height ) ) { - # Validation error - wfProfileOut( __METHOD__ ); - return null; - } - - if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) { - # validateThumbParams (or the user) wants us to return the unscaled image - $thumb = new ThumbnailImage( $this->getURL(), $width, $height ); - wfProfileOut( __METHOD__ ); - return $thumb; - } - - list( $isScriptUrl, $url ) = $this->thumbUrl( $width ); - if ( $isScriptUrl && $useScript ) { - // Use thumb.php to render the image - $thumb = new ThumbnailImage( $url, $width, $height ); - wfProfileOut( __METHOD__ ); - return $thumb; - } - - $thumbName = $this->thumbName( $width, $this->fromSharedDirectory ); + function migrateThumbFile( $thumbName ) { $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory ); - $thumbPath = $thumbDir.'/'.$thumbName; - + $thumbPath = "$thumbDir/$thumbName"; if ( is_dir( $thumbPath ) ) { // Directory where file should be // This happened occasionally due to broken migration code in 1.5 @@ -1062,254 +979,50 @@ class Image break; } } - // Code below will ask if it exists, and the answer is now no + // Doesn't exist anymore clearstatcache(); } - - $done = true; - if ( !file_exists( $thumbPath ) || - filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) ) - { - // Create the directory if it doesn't exist - if ( is_file( $thumbDir ) ) { - // File where thumb directory should be, destroy if possible - @unlink( $thumbDir ); - } - wfMkdirParents( $thumbDir ); - - $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ). - '/'.$thumbName; - $done = false; - - // Migration from old directory structure - if ( is_file( $oldThumbPath ) ) { - if ( filemtime($oldThumbPath) >= filemtime($this->imagePath) ) { - if ( file_exists( $thumbPath ) ) { - if ( !is_dir( $thumbPath ) ) { - // Old image in the way of rename - unlink( $thumbPath ); - } else { - // This should have been dealt with already - throw new MWException( "Directory where image should be: $thumbPath" ); - } - } - // Rename the old image into the new location - rename( $oldThumbPath, $thumbPath ); - $done = true; - } else { - unlink( $oldThumbPath ); - } - } - if ( !$done ) { - $this->lastError = $this->reallyRenderThumb( $thumbPath, $width, $height ); - if ( $this->lastError === true ) { - $done = true; - } elseif( $GLOBALS['wgIgnoreImageErrors'] ) { - // Log the error but output anyway. - // With luck it's a transitory error... - $done = true; - } - - # Purge squid - # This has to be done after the image is updated and present for all machines on NFS, - # or else the old version might be stored into the squid again - if ( $wgUseSquid ) { - $urlArr = array( $url ); - wfPurgeSquidServers($urlArr); - } - } - } - - if ( $done ) { - $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath ); - } else { - $thumb = null; + if ( is_file( $thumbDir ) ) { + // File where directory should be + unlink( $thumbDir ); + // Doesn't exist anymore + clearstatcache(); } - wfProfileOut( __METHOD__ ); - return $thumb; - } // END OF function renderThumb + } /** - * Really render a thumbnail - * Call this only for images for which canRender() returns true. - * - * @param string $thumbPath Path to thumbnail - * @param int $width Desired width in pixels - * @param int $height Desired height in pixels - * @return bool True on error, false or error string on failure. - * @private + * Get a MediaHandler instance for this image */ - function reallyRenderThumb( $thumbPath, $width, $height ) { - global $wgSVGConverters, $wgSVGConverter; - global $wgUseImageMagick, $wgImageMagickConvertCommand; - global $wgCustomConvertCommand; - global $wgDjvuRenderer, $wgDjvuPostProcessor; - - $this->load(); - - $err = false; - $cmd = ""; - $retval = 0; - - if( $this->mime === "image/svg" ) { - #Right now we have only SVG - - global $wgSVGConverters, $wgSVGConverter; - if( isset( $wgSVGConverters[$wgSVGConverter] ) ) { - global $wgSVGConverterPath; - $cmd = str_replace( - array( '$path/', '$width', '$height', '$input', '$output' ), - array( $wgSVGConverterPath ? "$wgSVGConverterPath/" : "", - intval( $width ), - intval( $height ), - wfEscapeShellArg( $this->imagePath ), - wfEscapeShellArg( $thumbPath ) ), - $wgSVGConverters[$wgSVGConverter] ); - wfProfileIn( 'rsvg' ); - wfDebug( "reallyRenderThumb SVG: $cmd\n" ); - $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'rsvg' ); - } - } else { - if ( $this->mime === "image/vnd.djvu" && $wgDjvuRenderer ) { - // DJVU image - // The file contains several images. First, extract the - // page in hi-res, if it doesn't yet exist. Then, thumbnail - // it. - - $cmd = "{$wgDjvuRenderer} -page={$this->page} -size=${width}x${height} " . - wfEscapeShellArg( $this->imagePath ) . - " | {$wgDjvuPostProcessor} > " . wfEscapeShellArg($thumbPath); - wfProfileIn( 'ddjvu' ); - wfDebug( "reallyRenderThumb DJVU: $cmd\n" ); - $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'ddjvu' ); - - } elseif ( $wgUseImageMagick ) { - # use ImageMagick - - if ( $this->mime == 'image/jpeg' ) { - $quality = "-quality 80"; // 80% - } elseif ( $this->mime == 'image/png' ) { - $quality = "-quality 95"; // zlib 9, adaptive filtering - } else { - $quality = ''; // default - } - - # Specify white background color, will be used for transparent images - # in Internet Explorer/Windows instead of default black. - - # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}". - # It seems that ImageMagick has a bug wherein it produces thumbnails of - # the wrong size in the second case. - - $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) . - " {$quality} -background white -size {$width} ". - wfEscapeShellArg($this->imagePath) . - // Coalesce is needed to scale animated GIFs properly (bug 1017). - ' -coalesce ' . - // For the -resize option a "!" is needed to force exact size, - // or ImageMagick may decide your ratio is wrong and slice off - // a pixel. - " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) . - " -depth 8 " . - wfEscapeShellArg($thumbPath) . " 2>&1"; - wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n"); - wfProfileIn( 'convert' ); - $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'convert' ); - } elseif( $wgCustomConvertCommand ) { - # Use a custom convert command - # Variables: %s %d %w %h - $src = wfEscapeShellArg( $this->imagePath ); - $dst = wfEscapeShellArg( $thumbPath ); - $cmd = $wgCustomConvertCommand; - $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames - $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size - wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" ); - wfProfileIn( 'convert' ); - $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'convert' ); - } else { - # Use PHP's builtin GD library functions. - # - # First find out what kind of file this is, and select the correct - # input routine for this. - - $typemap = array( - 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), - 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ), - 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), - 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), - 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), - ); - if( !isset( $typemap[$this->mime] ) ) { - $err = 'Image type not supported'; - wfDebug( "$err\n" ); - return $err; - } - list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime]; - - if( !function_exists( $loader ) ) { - $err = "Incomplete GD library configuration: missing function $loader"; - wfDebug( "$err\n" ); - return $err; - } - if( $colorStyle == 'palette' ) { - $truecolor = false; - } elseif( $colorStyle == 'truecolor' ) { - $truecolor = true; - } elseif( $colorStyle == 'bits' ) { - $truecolor = ( $this->bits > 8 ); - } + function getHandler() { + return MediaHandler::getHandler( $this->getMimeType() ); + } - $src_image = call_user_func( $loader, $this->imagePath ); - if ( $truecolor ) { - $dst_image = imagecreatetruecolor( $width, $height ); - } else { - $dst_image = imagecreate( $width, $height ); - } - imagecopyresampled( $dst_image, $src_image, - 0,0,0,0, - $width, $height, $this->width, $this->height ); - call_user_func( $saveType, $dst_image, $thumbPath ); - imagedestroy( $dst_image ); - imagedestroy( $src_image ); - } - } + /** + * Get a ThumbnailImage representing a file type icon + * @return ThumbnailImage + */ + function iconThumb() { + global $wgStylePath, $wgStyleDirectory; - # - # Check for zero-sized thumbnails. Those can be generated when - # no disk space is available or some other error occurs - # - if( file_exists( $thumbPath ) ) { - $thumbstat = stat( $thumbPath ); - if( $thumbstat['size'] == 0 || $retval != 0 ) { - wfDebugLog( 'thumbnail', - sprintf( 'Removing bad %d-byte thumbnail "%s"', - $thumbstat['size'], $thumbPath ) ); - unlink( $thumbPath ); + $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' ); + foreach( $try as $icon ) { + $path = '/common/images/icons/' . $icon; + $filepath = $wgStyleDirectory . $path; + if( file_exists( $filepath ) ) { + return new ThumbnailImage( $wgStylePath . $path, 120, 120 ); } } - if ( $retval != 0 ) { - wfDebugLog( 'thumbnail', - sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', - wfHostname(), $retval, trim($err), $cmd ) ); - return wfMsg( 'thumbnail_error', $err ); - } else { - return true; - } + return null; } + /** + * Get last thumbnailing error. + * Largely obsolete. + */ function getLastError() { return $this->lastError; } - function imageJpegWrapper( $dst_image, $thumbPath ) { - imageinterlace( $dst_image ); - imagejpeg( $dst_image, $thumbPath, 95 ); - } - /** * Get all thumbnail names previously generated for this image */ @@ -1319,16 +1032,17 @@ class Image $files = array(); $dir = wfImageThumbDir( $this->name, $shared ); - // This generates an error on failure, hence the @ - $handle = @opendir( $dir ); + if ( is_dir( $dir ) ) { + $handle = opendir( $dir ); - if ( $handle ) { - while ( false !== ( $file = readdir($handle) ) ) { - if ( $file{0} != '.' ) { - $files[] = $file; + if ( $handle ) { + while ( false !== ( $file = readdir($handle) ) ) { + if ( $file{0} != '.' ) { + $files[] = $file; + } } + closedir( $handle ); } - closedir( $handle ); } } else { $files = array(); @@ -1361,8 +1075,10 @@ class Image $urls = array(); foreach ( $files as $file ) { $m = array(); - if ( preg_match( '/^(\d+)px/', $file, $m ) ) { - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] ); + # Check that the base image name is part of the thumb name + # This is a basic sanity check to avoid erasing unrelated directories + if ( strpos( $file, $this->name ) !== false ) { + $url = $this->thumbUrlFromName( $file ); $urls[] = $url; @unlink( "$dir/$file" ); } @@ -1377,7 +1093,7 @@ class Image wfPurgeSquidServers( $urls ); } } - + /** * Purge the image description page, but don't go after * pages using the image. Use when modifying file history @@ -1388,7 +1104,7 @@ class Image $page->invalidateCache(); $page->purgeSquid(); } - + /** * Purge metadata and all affected pages when the image is created, * deleted, or majorly updated. A set of additional URLs may be @@ -1399,12 +1115,15 @@ class Image // Delete thumbnails and refresh image metadata cache $this->purgeCache(); $this->purgeDescription(); - + // Purge cache of all pages using this image $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ); $update->doUpdate(); } + /** + * Check the image table schema on the given connection for subtle problems + */ function checkDBSchema(&$db) { static $checkDone = false; global $wgCheckDBSchema; @@ -1445,7 +1164,7 @@ class Image * @public */ function nextHistoryLine() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $this->checkDBSchema($dbr); @@ -1541,7 +1260,7 @@ class Image function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) { global $wgUser, $wgUseCopyrightUpload; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $this->checkDBSchema($dbw); @@ -1670,6 +1389,9 @@ class Image $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC ); } + # Hooks, hooks, the magic of hooks... + wfRunHooks( 'FileUpload', array( $this ) ); + # Add the log entry $log = new LogPage( 'upload' ); $log->addEntry( 'upload', $descTitle, $desc ); @@ -1697,9 +1419,9 @@ class Image wfProfileIn( __METHOD__ ); if ( $options ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); } $linkCache =& LinkCache::singleton(); @@ -1721,75 +1443,24 @@ class Image wfProfileOut( __METHOD__ ); return $retVal; } - - /** - * Retrive Exif data from the file and prune unrecognized tags - * and/or tags with invalid contents - * - * @param $filename - * @return array - */ - private function retrieveExifData( $filename ) { - global $wgShowEXIF; - - /* - if ( $this->getMimeType() !== "image/jpeg" ) - return array(); - */ - - if( $wgShowEXIF && file_exists( $filename ) ) { - $exif = new Exif( $filename ); - return $exif->getFilteredData(); - } - - return array(); - } function getExifData() { global $wgRequest; - if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' ) + $handler = $this->getHandler(); + if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) { return array(); - - $purge = $wgRequest->getVal( 'action' ) == 'purge'; - $ret = unserialize( $this->metadata ); - - $oldver = isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ? $ret['MEDIAWIKI_EXIF_VERSION'] : 0; - $newver = Exif::version(); - - if ( !count( $ret ) || $purge || $oldver != $newver ) { - $this->purgeMetadataCache(); - $this->updateExifData( $newver ); } - if ( isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ) - unset( $ret['MEDIAWIKI_EXIF_VERSION'] ); - $format = new FormatExif( $ret ); - - return $format->getFormattedData(); - } - - function updateExifData( $version ) { - if ( $this->getImagePath() === false ) # Not a local image - return; - - # Get EXIF data from image - $exif = $this->retrieveExifData( $this->imagePath ); - if ( count( $exif ) ) { - $exif['MEDIAWIKI_EXIF_VERSION'] = $version; - $this->metadata = serialize( $exif ); - } else { - $this->metadata = '0'; + if ( !$this->metadata ) { + return array(); } + $exif = unserialize( $this->metadata ); + if ( !$exif ) { + return array(); + } + unset( $exif['MEDIAWIKI_EXIF_VERSION'] ); + $format = new FormatExif( $exif ); - # Update EXIF data in database - $dbw =& wfGetDB( DB_MASTER ); - - $this->checkDBSchema($dbw); - - $dbw->update( 'image', - array( 'img_metadata' => $this->metadata ), - array( 'img_name' => $this->name ), - __METHOD__ - ); + return $format->getFormattedData(); } /** @@ -1801,7 +1472,7 @@ class Image function isLocal() { return !$this->fromSharedDirectory; } - + /** * Was this image ever deleted from the wiki? * @@ -1811,7 +1482,7 @@ class Image $title = Title::makeTitle( NS_IMAGE, $this->name ); return ( $title->isDeleted() > 0 ); } - + /** * Delete all versions of the image. * @@ -1823,37 +1494,37 @@ class Image * @param $reason * @return true on success, false on some kind of failure */ - function delete( $reason ) { + function delete( $reason, $suppress=false ) { $transaction = new FSTransaction(); $urlArr = array( $this->getURL() ); - + if( !FileStore::lock() ) { wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); return false; } - + try { $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); - + // Delete old versions $result = $dbw->select( 'oldimage', array( 'oi_archive_name' ), array( 'oi_name' => $this->name ) ); - + while( $row = $dbw->fetchObject( $result ) ) { $oldName = $row->oi_archive_name; - - $transaction->add( $this->prepareDeleteOld( $oldName, $reason ) ); - + + $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) ); + // We'll need to purge this URL from caches... $urlArr[] = wfImageArchiveUrl( $oldName ); } $dbw->freeResult( $result ); - + // And the current version... - $transaction->add( $this->prepareDeleteCurrent( $reason ) ); - + $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) ); + $dbw->immediateCommit(); } catch( MWException $e ) { wfDebug( __METHOD__.": db error, rolling back file transactions\n" ); @@ -1861,22 +1532,22 @@ class Image FileStore::unlock(); throw $e; } - + wfDebug( __METHOD__.": deleted db items, applying file transactions\n" ); $transaction->commit(); FileStore::unlock(); - + // Update site_stats $site_stats = $dbw->tableName( 'site_stats' ); $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ ); - + $this->purgeEverything( $urlArr ); - + return true; } - - + + /** * Delete an old version of the image. * @@ -1889,20 +1560,20 @@ class Image * @throws MWException or FSException on database or filestore failure * @return true on success, false on some kind of failure */ - function deleteOld( $archiveName, $reason ) { + function deleteOld( $archiveName, $reason, $suppress=false ) { $transaction = new FSTransaction(); $urlArr = array(); - + if( !FileStore::lock() ) { wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); return false; } - + $transaction = new FSTransaction(); try { $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); - $transaction->add( $this->prepareDeleteOld( $archiveName, $reason ) ); + $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) ); $dbw->immediateCommit(); } catch( MWException $e ) { wfDebug( __METHOD__.": db error, rolling back file transaction\n" ); @@ -1910,11 +1581,11 @@ class Image FileStore::unlock(); throw $e; } - + wfDebug( __METHOD__.": deleted db items, applying file transaction\n" ); $transaction->commit(); FileStore::unlock(); - + $this->purgeDescription(); // Squid purging @@ -1927,13 +1598,13 @@ class Image } return true; } - + /** * Delete the current version of a file. * May throw a database error. * @return true on success, false on failure */ - private function prepareDeleteCurrent( $reason ) { + private function prepareDeleteCurrent( $reason, $suppress=false ) { return $this->prepareDeleteVersion( $this->getFullPath(), $reason, @@ -1954,6 +1625,7 @@ class Image 'fa_user_text' => 'img_user_text', 'fa_timestamp' => 'img_timestamp' ), array( 'img_name' => $this->name ), + $suppress, __METHOD__ ); } @@ -1962,7 +1634,7 @@ class Image * May throw a database error. * @return true on success, false on failure */ - private function prepareDeleteOld( $archiveName, $reason ) { + private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) { $oldpath = wfImageArchiveDir( $this->name ) . DIRECTORY_SEPARATOR . $archiveName; return $this->prepareDeleteVersion( @@ -1987,6 +1659,7 @@ class Image array( 'oi_name' => $this->name, 'oi_archive_name' => $archiveName ), + $suppress, __METHOD__ ); } @@ -1999,14 +1672,14 @@ class Image * * @return FSTransaction */ - private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $fname ) { + private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) { global $wgUser, $wgSaveDeletedFiles; - + // Dupe the file into the file store if( file_exists( $path ) ) { if( $wgSaveDeletedFiles ) { $group = 'deleted'; - + $store = FileStore::get( $group ); $key = FileStore::calculateKey( $path, $this->extension ); $transaction = $store->insert( $key, $path, @@ -2022,7 +1695,7 @@ class Image $key = null; $transaction = new FSTransaction(); // empty } - + if( $transaction === false ) { // Fail to restore? wfDebug( __METHOD__.": import to file store failed, aborting\n" ); @@ -2030,16 +1703,28 @@ class Image return false; } + // Bitfields to further supress the image content + // Note that currently, live images are stored elsewhere + // and cannot be partially deleted + $bitfield = 0; + if ( $suppress ) { + $bitfield |= self::DELETED_FILE; + $bitfield |= self::DELETED_COMMENT; + $bitfield |= self::DELETED_USER; + $bitfield |= self::DELETED_RESTRICTED; + } + $dbw = wfGetDB( DB_MASTER ); $storageMap = array( 'fa_storage_group' => $dbw->addQuotes( $group ), 'fa_storage_key' => $dbw->addQuotes( $key ), - + 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ), 'fa_deleted_timestamp' => $dbw->timestamp(), - 'fa_deleted_reason' => $dbw->addQuotes( $reason ) ); + 'fa_deleted_reason' => $dbw->addQuotes( $reason ), + 'fa_deleted' => $bitfield); $allFields = array_merge( $storageMap, $fieldMap ); - + try { if( $wgSaveDeletedFiles ) { $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname ); @@ -2052,10 +1737,10 @@ class Image $transaction->rollback(); throw $e; } - + return $transaction; } - + /** * Restore all or specified deleted revisions to the given file. * Permissions and logging are left to the caller. @@ -2067,36 +1752,38 @@ class Image * @return the number of file revisions restored if successful, * or false on failure */ - function restore( $versions=array() ) { + function restore( $versions=array(), $Unsuppress=false ) { + global $wgUser; + if( !FileStore::lock() ) { wfDebug( __METHOD__." could not acquire filestore lock\n" ); return false; } - + $transaction = new FSTransaction(); try { $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); - + // Re-confirm whether this image presently exists; // if no we'll need to create an image record for the // first item we restore. $exists = $dbw->selectField( 'image', '1', array( 'img_name' => $this->name ), __METHOD__ ); - + // Fetch all or selected archived revisions for the file, // sorted from the most recent to the oldest. $conditions = array( 'fa_name' => $this->name ); if( $versions ) { $conditions['fa_id'] = $versions; } - + $result = $dbw->select( 'filearchive', '*', $conditions, __METHOD__, array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - + if( $dbw->numRows( $result ) < count( $versions ) ) { // There's some kind of conflict or confusion; // we can't restore everything we were asked to. @@ -2113,40 +1800,51 @@ class Image FileStore::unlock(); return true; } - + $revisions = 0; while( $row = $dbw->fetchObject( $result ) ) { + if ( $Unsuppress ) { + // Currently, fa_deleted flags fall off upon restore, lets be careful about this + } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) { + // Skip restoring file revisions that the user cannot restore + continue; + } $revisions++; $store = FileStore::get( $row->fa_storage_group ); if( !$store ) { wfDebug( __METHOD__.": skipping row with no file.\n" ); continue; } - + if( $revisions == 1 && !$exists ) { $destDir = wfImageDir( $row->fa_name ); if ( !is_dir( $destDir ) ) { wfMkdirParents( $destDir ); } $destPath = $destDir . DIRECTORY_SEPARATOR . $row->fa_name; - + // We may have to fill in data if this was originally // an archived file revision. if( is_null( $row->fa_metadata ) ) { $tempFile = $store->filePath( $row->fa_storage_key ); - $metadata = serialize( $this->retrieveExifData( $tempFile ) ); - + $magic = MimeMagic::singleton(); $mime = $magic->guessMimeType( $tempFile, true ); $media_type = $magic->getMediaType( $tempFile, $mime ); list( $major_mime, $minor_mime ) = self::splitMime( $mime ); + $handler = MediaHandler::getHandler( $mime ); + if ( $handler ) { + $metadata = $handler->getMetadata( false, $tempFile ); + } else { + $metadata = ''; + } } else { $metadata = $row->fa_metadata; $major_mime = $row->fa_major_mime; $minor_mime = $row->fa_minor_mime; $media_type = $row->fa_media_type; } - + $table = 'image'; $fields = array( 'img_name' => $row->fa_name, @@ -2177,7 +1875,7 @@ class Image wfMkdirParents( $destDir ); } $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName; - + $table = 'oldimage'; $fields = array( 'oi_name' => $row->fa_name, @@ -2191,13 +1889,13 @@ class Image 'oi_user_text' => $row->fa_user_text, 'oi_timestamp' => $row->fa_timestamp ); } - + $dbw->insert( $table, $fields, __METHOD__ ); - /// @fixme this delete is not totally safe, potentially + // @todo this delete is not totally safe, potentially $dbw->delete( 'filearchive', array( 'fa_id' => $row->fa_id ), __METHOD__ ); - + // Check if any other stored revisions use this file; // if so, we shouldn't remove the file from the deletion // archives so they will still work. @@ -2213,161 +1911,230 @@ class Image } else { $flags = 0; } - + $transaction->add( $store->export( $row->fa_storage_key, $destPath, $flags ) ); } - + $dbw->immediateCommit(); } catch( MWException $e ) { wfDebug( __METHOD__." caught error, aborting\n" ); $transaction->rollback(); throw $e; } - + $transaction->commit(); FileStore::unlock(); - + if( $revisions > 0 ) { if( !$exists ) { wfDebug( __METHOD__." restored $revisions items, creating a new current\n" ); - + // Update site_stats $site_stats = $dbw->tableName( 'site_stats' ); $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); - + $this->purgeEverything(); } else { wfDebug( __METHOD__." restored $revisions as archived versions\n" ); $this->purgeDescription(); } } - + return $revisions; } /** - * Select a page from a multipage document. Determines the page used for - * rendering thumbnails. + * Returns 'true' if this image is a multipage document, e.g. a DJVU + * document. * - * @param $page Integer: page number, starting with 1 + * @return Bool */ - function selectPage( $page ) { - wfDebug( __METHOD__." selecting page $page \n" ); - $this->page = $page; - if ( ! $this->dataLoaded ) { - $this->load(); - } - if ( ! isset( $this->multiPageXML ) ) { - $this->initializeMultiPageXML(); + function isMultipage() { + $handler = $this->getHandler(); + return $handler && $handler->isMultiPage(); + } + + /** + * Returns the number of pages of a multipage document, or NULL for + * documents which aren't multipage documents + */ + function pageCount() { + $handler = $this->getHandler(); + if ( $handler && $handler->isMultiPage() ) { + return $handler->pageCount( $this ); + } else { + return null; } - $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1]; - $this->height = intval( $o['height'] ); - $this->width = intval( $o['width'] ); } - function initializeMultiPageXML() { - # - # Check for files uploaded prior to DJVU support activation - # They have a '0' in their metadata field. - # - if ( $this->metadata == '0' || $this->metadata == '' ) { - $deja = new DjVuImage( $this->imagePath ); - $this->metadata = $deja->retrieveMetaData(); - $this->purgeMetadataCache(); + static function getCommonsDB() { + static $dbc; + global $wgLoadBalancer, $wgSharedUploadDBname; + if ( !isset( $dbc ) ) { + $i = $wgLoadBalancer->getGroupIndex( 'commons' ); + $dbinfo = $wgLoadBalancer->mServers[$i]; + $dbc = new Database( $dbinfo['host'], $dbinfo['user'], + $dbinfo['password'], $wgSharedUploadDBname ); + } + return $dbc; + } - # Update metadata in the database - $dbw =& wfGetDB( DB_MASTER ); - $dbw->update( 'image', - array( 'img_metadata' => $this->metadata ), - array( 'img_name' => $this->name ), - __METHOD__ - ); + /** + * Calculate the height of a thumbnail using the source and destination width + */ + static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) { + // Exact integer multiply followed by division + if ( $srcWidth == 0 ) { + return 0; + } else { + return round( $srcHeight * $dstWidth / $srcWidth ); } - wfSuppressWarnings(); - $this->multiPageXML = new SimpleXMLElement( $this->metadata ); - wfRestoreWarnings(); } /** - * Returns 'true' if this image is a multipage document, e.g. a DJVU - * document. + * Get an image size array like that returned by getimagesize(), or false if it + * can't be determined. * - * @return Bool + * @param string $fileName The filename + * @return array */ - function isMultipage() { - return ( $this->mime == 'image/vnd.djvu' ); + function getImageSize( $fileName ) { + $handler = $this->getHandler(); + return $handler->getImageSize( $this, $fileName ); } /** - * Returns the number of pages of a multipage document, or NULL for - * documents which aren't multipage documents + * Get the thumbnail extension and MIME type for a given source MIME type + * @return array thumbnail extension and MIME type */ - function pageCount() { - if ( ! $this->isMultipage() ) { - return null; - } - if ( ! isset( $this->multiPageXML ) ) { - $this->initializeMultiPageXML(); + static function getThumbType( $ext, $mime ) { + $handler = MediaHandler::getHandler( $mime ); + if ( $handler ) { + return $handler->getThumbType( $ext, $mime ); + } else { + return array( $ext, $mime ); } - return count( $this->multiPageXML->xpath( '//OBJECT' ) ); } - + } //class + /** - * Wrapper class for thumbnail images - * @package MediaWiki + * @addtogroup Media */ -class ThumbnailImage { +class ArchivedFile +{ /** - * @param string $path Filesystem path to the thumb - * @param string $url URL path to the thumb - * @private + * Returns a file object from the filearchive table + * In the future, all current and old image storage + * may use FileStore. There will be a "old" storage + * for current and previous file revisions as well as + * the "deleted" group for archived revisions + * @param $title, the corresponding image page title + * @param $id, the image id, a unique key + * @param $key, optional storage key + * @return ResultWrapper */ - function ThumbnailImage( $url, $width, $height, $path = false ) { - $this->url = $url; - $this->width = round( $width ); - $this->height = round( $height ); - # These should be integers when they get here. - # If not, there's a bug somewhere. But let's at - # least produce valid HTML code regardless. - $this->path = $path; + function ArchivedFile( $title, $id=0, $key='' ) { + if( !is_object( $title ) ) { + throw new MWException( 'Image constructor given bogus title.' ); + } + $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'"; + if( $title->getNamespace() == NS_IMAGE ) { + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'filearchive', + array( + 'fa_id', + 'fa_name', + 'fa_storage_key', + 'fa_storage_group', + 'fa_size', + 'fa_bits', + 'fa_width', + 'fa_height', + 'fa_metadata', + 'fa_media_type', + 'fa_major_mime', + 'fa_minor_mime', + 'fa_description', + 'fa_user', + 'fa_user_text', + 'fa_timestamp', + 'fa_deleted' ), + array( + 'fa_name' => $title->getDbKey(), + $conds ), + __METHOD__, + array( 'ORDER BY' => 'fa_timestamp DESC' ) ); + + if ( $dbr->numRows( $res ) == 0 ) { + // this revision does not exist? + return; + } + $ret = $dbr->resultObject( $res ); + $row = $ret->fetchObject(); + + // initialize fields for filestore image object + $this->mId = intval($row->fa_id); + $this->mName = $row->fa_name; + $this->mGroup = $row->fa_storage_group; + $this->mKey = $row->fa_storage_key; + $this->mSize = $row->fa_size; + $this->mBits = $row->fa_bits; + $this->mWidth = $row->fa_width; + $this->mHeight = $row->fa_height; + $this->mMetaData = $row->fa_metadata; + $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime"; + $this->mType = $row->fa_media_type; + $this->mDescription = $row->fa_description; + $this->mUser = $row->fa_user; + $this->mUserText = $row->fa_user_text; + $this->mTimestamp = $row->fa_timestamp; + $this->mDeleted = $row->fa_deleted; + } else { + throw new MWException( 'This title does not correspond to an image page.' ); + return; + } + return true; } /** - * @return string The thumbnail URL + * int $field one of DELETED_* bitfield constants + * for file or revision rows + * @return bool */ - function getUrl() { - return $this->url; + function isDeleted( $field ) { + return ($this->mDeleted & $field) == $field; } - + /** - * Return HTML <img ... /> tag for the thumbnail, will include - * width and height attributes and a blank alt text (as required). - * - * You can set or override additional attributes by passing an - * associative array of name => data pairs. The data will be escaped - * for HTML output, so should be in plaintext. - * - * @param array $attribs - * @return string - * @public + * Determine if the current user is allowed to view a particular + * field of this FileStore image file, if it's marked as deleted. + * @param int $field + * @return bool */ - function toHtml( $attribs = array() ) { - $attribs['src'] = $this->url; - $attribs['width'] = $this->width; - $attribs['height'] = $this->height; - if( !isset( $attribs['alt'] ) ) $attribs['alt'] = ''; - - $html = '<img '; - foreach( $attribs as $name => $data ) { - $html .= $name . '="' . htmlspecialchars( $data ) . '" '; + function userCan( $field ) { + if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) { + // images + global $wgUser; + $permission = ( $this->mDeleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED + ? 'hiderevision' + : 'deleterevision'; + wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" ); + return $wgUser->isAllowed( $permission ); + } else { + return true; } - $html .= '/>'; - return $html; } - } +/** + * Aliases for backwards compatibility with 1.6 + */ +define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE ); +define( 'MW_IMG_DELETED_COMMENT', Image::DELETED_COMMENT ); +define( 'MW_IMG_DELETED_USER', Image::DELETED_USER ); +define( 'MW_IMG_DELETED_RESTRICTED', Image::DELETED_RESTRICTED ); + ?> diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php index 931fdff1..d04110d4 100644 --- a/includes/ImageFunctions.php +++ b/includes/ImageFunctions.php @@ -21,7 +21,7 @@ function wfImageDir( $fname ) { } /** - * Returns the image directory of an image's thubnail + * Returns the image directory of an image's thumbnail * The result is an absolute path. * * This function is called from thumb.php before Setup.php is included diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php index 9d58b7f6..fba7714c 100644 --- a/includes/ImageGallery.php +++ b/includes/ImageGallery.php @@ -3,7 +3,6 @@ if ( ! defined( 'MEDIAWIKI' ) ) die( 1 ); /** - * @package MediaWiki */ /** @@ -11,23 +10,32 @@ if ( ! defined( 'MEDIAWIKI' ) ) * * Add images to the gallery using add(), then render that list to HTML using toHTML(). * - * @package MediaWiki + * @addtogroup Media */ class ImageGallery { var $mImages, $mShowBytes, $mShowFilename; var $mCaption = false; var $mSkin = false; - + /** * Is the gallery on a wiki page (i.e. not a special page) */ var $mParsing; /** + * Contextual title, used when images are being screened + * against the bad image list + */ + private $contextTitle = false; + + private $mPerRow = 4; // How many images wide should the gallery be? + private $mWidths = 120, $mHeights = 120; // How wide/tall each thumbnail should be + + /** * Create a new image gallery object. */ - function ImageGallery( ) { + function __construct( ) { $this->mImages = array(); $this->mShowBytes = true; $this->mShowFilename = true; @@ -40,7 +48,7 @@ class ImageGallery function setParsing( $val = true ) { $this->mParsing = $val; } - + /** * Set the caption (as plain text) * @@ -49,25 +57,58 @@ class ImageGallery function setCaption( $caption ) { $this->mCaption = htmlspecialchars( $caption ); } - + /** * Set the caption (as HTML) * * @param $caption Caption */ - function setCaptionHtml( $caption ) { + public function setCaptionHtml( $caption ) { $this->mCaption = $caption; } /** + * Set how many images will be displayed per row. + * + * @param int $num > 0; invalid numbers will be rejected + */ + public function setPerRow( $num ) { + if ($num > 0) { + $this->mPerRow = (int)$num; + } + } + + /** + * Set how wide each image will be, in pixels. + * + * @param int $num > 0; invalid numbers will be ignored + */ + public function setWidths( $num ) { + if ($num > 0) { + $this->mWidths = (int)$num; + } + } + + /** + * Set how high each image will be, in pixels. + * + * @param int $num > 0; invalid numbers will be ignored + */ + public function setHeights( $num ) { + if ($num > 0) { + $this->mHeights = (int)$num; + } + } + + /** * Instruct the class to use a specific skin for rendering * * @param $skin Skin object */ function useSkin( $skin ) { - $this->mSkin =& $skin; + $this->mSkin = $skin; } - + /** * Return the skin that should be used * @@ -76,9 +117,9 @@ class ImageGallery function getSkin() { if( !$this->mSkin ) { global $wgUser; - $skin =& $wgUser->getSkin(); + $skin = $wgUser->getSkin(); } else { - $skin =& $this->mSkin; + $skin = $this->mSkin; } return $skin; } @@ -143,14 +184,15 @@ class ImageGallery * */ function toHTML() { - global $wgLang, $wgGenerateThumbnailOnParse; + global $wgLang; $sk = $this->getSkin(); $s = '<table class="gallery" cellspacing="0" cellpadding="0">'; if( $this->mCaption ) - $s .= '<td class="galleryheader" colspan="4"><big>' . $this->mCaption . '</big></td>'; - + $s .= "\n\t<caption>{$this->mCaption}</caption>"; + + $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights ); $i = 0; foreach ( $this->mImages as $pair ) { $img =& $pair[0]; @@ -160,20 +202,19 @@ class ImageGallery if( $nt->getNamespace() != NS_IMAGE ) { # We're dealing with a non-image, spit out the name and be done with it. - $thumbhtml = '<div style="height: 152px;">' . htmlspecialchars( $nt->getText() ) . '</div>'; - } - else if( $this->mParsing && wfIsBadImage( $nt->getDBkey() ) ) { + $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">' + . htmlspecialchars( $nt->getText() ) . '</div>'; + } elseif( $this->mParsing && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) { # The image is blacklisted, just show it as a text link. - $thumbhtml = '<div style="height: 152px;">' + $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">' . $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>'; - } else if( !( $thumb = $img->getThumbnail( 120, 120, $wgGenerateThumbnailOnParse ) ) ) { + } elseif( !( $thumb = $img->transform( $params ) ) ) { # Error generating thumbnail. - $thumbhtml = '<div style="height: 152px;">' + $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">' . htmlspecialchars( $img->getLastError() ) . '</div>'; - } - else { - $vpad = floor( ( 150 - $thumb->height ) /2 ) - 2; - $thumbhtml = '<div class="thumb" style="padding: ' . $vpad . 'px 0;">' + } else { + $vpad = floor( ( 1.25*$this->mHeights - $thumb->height ) /2 ) - 2; + $thumbhtml = "\n\t\t\t".'<div class="thumb" style="padding: ' . $vpad . 'px 0; width: '.($this->mWidths+30).'px;">' . $sk->makeKnownLinkObj( $nt, $thumb->toHtml() ) . '</div>'; } @@ -200,27 +241,55 @@ class ImageGallery # in version 4.8.6 generated crackpot html in its absence, see: # http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar - $s .= ($i%4==0) ? '<tr>' : ''; - $s .= '<td><div class="gallerybox">' . $thumbhtml - . '<div class="gallerytext">' . "\n" . $textlink . $text . $nb - . "</div></div></td>\n"; - $s .= ($i%4==3) ? '</tr>' : ''; - $i++; + if ( $i % $this->mPerRow == 0 ) { + $s .= "\n\t<tr>"; + } + $s .= + "\n\t\t" . '<td><div class="gallerybox" style="width: '.($this->mWidths*1.25).'px;">' + . $thumbhtml + . "\n\t\t\t" . '<div class="gallerytext">' . "\n" + . $textlink . $text . $nb + . "\n\t\t\t</div>" + . "\n\t\t</div></td>"; + if ( $i % $this->mPerRow == $this->mPerRow - 1 ) { + $s .= "\n\t</tr>"; + } + ++$i; } - if( $i %4 != 0 ) { - $s .= "</tr>\n"; + if( $i % $this->mPerRow != 0 ) { + $s .= "\n\t</tr>"; } - $s .= '</table>'; + $s .= "\n</table>"; return $s; } - + /** * @return int Number of images in the gallery */ public function count() { return count( $this->mImages ); } + + /** + * Set the contextual title + * + * @param Title $title Contextual title + */ + public function setContextTitle( $title ) { + $this->contextTitle = $title; + } + + /** + * Get the contextual title, if applicable + * + * @return mixed Title or false + */ + public function getContextTitle() { + return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title + ? $this->contextTitle + : false; + } } //class ?> diff --git a/includes/ImagePage.php b/includes/ImagePage.php index 43b99130..13f8e46a 100644 --- a/includes/ImagePage.php +++ b/includes/ImagePage.php @@ -1,6 +1,5 @@ <?php /** - * @package MediaWiki */ /** @@ -11,7 +10,8 @@ if( !defined( 'MEDIAWIKI' ) ) /** * Special handling for image description pages - * @package MediaWiki + * + * @addtogroup Media */ class ImagePage extends Article { @@ -29,59 +29,62 @@ class ImagePage extends Article { } function view() { - global $wgOut, $wgShowEXIF; + global $wgOut, $wgShowEXIF, $wgRequest, $wgUser; $this->img = new Image( $this->mTitle ); - if( $this->mTitle->getNamespace() == NS_IMAGE ) { - if ($wgShowEXIF && $this->img->exists()) { - $exif = $this->img->getExifData(); - $showmeta = count($exif) ? true : false; - } else { - $exif = false; - $showmeta = false; - } + $diff = $wgRequest->getVal( 'diff' ); + $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) ); - if ($this->img->exists()) - $wgOut->addHTML($this->showTOC($showmeta)); + if ( $this->mTitle->getNamespace() != NS_IMAGE || ( isset( $diff ) && $diffOnly ) ) + return Article::view(); - $this->openShowImage(); + if ($wgShowEXIF && $this->img->exists()) { + $exif = $this->img->getExifData(); + $showmeta = count($exif) ? true : false; + } else { + $exif = false; + $showmeta = false; + } - # No need to display noarticletext, we use our own message, output in openShowImage() - if( $this->getID() ) { - Article::view(); - } else { - # Just need to set the right headers - $wgOut->setArticleFlag( true ); - $wgOut->setRobotpolicy( 'index,follow' ); - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); - $this->viewUpdates(); - } + if ($this->img->exists()) + $wgOut->addHTML($this->showTOC($showmeta)); - # Show shared description, if needed - if( $this->mExtraDescription ) { - $fol = wfMsg( 'shareddescriptionfollows' ); - if( $fol != '-' ) { - $wgOut->addWikiText( $fol ); - } - $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' ); - } + $this->openShowImage(); - $this->closeShowImage(); - $this->imageHistory(); - $this->imageLinks(); - if( $exif ) { - global $wgStylePath, $wgStyleVersion; - $expand = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-expand' ) ) ); - $collapse = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-collapse' ) ) ); - $wgOut->addHTML( "<h2 id=\"metadata\">" . wfMsgHtml( 'metadata' ) . "</h2>\n" ); - $wgOut->addWikiText( $this->makeMetadataTable( $exif ) ); - $wgOut->addHTML( - "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js?$wgStyleVersion\"></script>\n" . - "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" ); - } - } else { + # No need to display noarticletext, we use our own message, output in openShowImage() + if ( $this->getID() ) { Article::view(); + } else { + # Just need to set the right headers + $wgOut->setArticleFlag( true ); + $wgOut->setRobotpolicy( 'index,follow' ); + $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + $this->viewUpdates(); + } + + # Show shared description, if needed + if ( $this->mExtraDescription ) { + $fol = wfMsg( 'shareddescriptionfollows' ); + if( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) { + $wgOut->addWikiText( $fol ); + } + $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' ); + } + + $this->closeShowImage(); + $this->imageHistory(); + $this->imageLinks(); + + if ( $exif ) { + global $wgStylePath, $wgStyleVersion; + $expand = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-expand' ) ) ); + $collapse = htmlspecialchars( wfEscapeJsString( wfMsg( 'metadata-collapse' ) ) ); + $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ). "\n" ); + $wgOut->addWikiText( $this->makeMetadataTable( $exif ) ); + $wgOut->addHTML( + "<script type=\"text/javascript\" src=\"$wgStylePath/common/metadata.js?$wgStyleVersion\"></script>\n" . + "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" ); } } @@ -165,15 +168,19 @@ class ImagePage extends Article { function openShowImage() { global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang; - global $wgUseImageResize, $wgGenerateThumbnailOnParse; $full_url = $this->img->getURL(); - $anchoropen = ''; - $anchorclose = ''; + $linkAttribs = false; $sizeSel = intval( $wgUser->getOption( 'imagesize') ); - if( !isset( $wgImageLimits[$sizeSel] ) ) { $sizeSel = User::getDefaultOption( 'imagesize' ); + + // The user offset might still be incorrect, specially if + // $wgImageLimits got changed (see bug #8858). + if( !isset( $wgImageLimits[$sizeSel] ) ) { + // Default to the first offset in $wgImageLimits + $sizeSel = 0; + } } $max = $wgImageLimits[$sizeSel]; $maxWidth = $max[0]; @@ -183,21 +190,25 @@ class ImagePage extends Article { if ( $this->img->exists() ) { # image $page = $wgRequest->getIntOrNull( 'page' ); - if ( ! is_null( $page ) ) { - $this->img->selectPage( $page ); - } else { + if ( is_null( $page ) ) { + $params = array(); $page = 1; + } else { + $params = array( 'page' => $page ); } - $width = $this->img->getWidth(); - $height = $this->img->getHeight(); + $width_orig = $this->img->getWidth(); + $width = $width_orig; + $height_orig = $this->img->getHeight(); + $height = $height_orig; + $mime = $this->img->getMimeType(); $showLink = false; + $linkAttribs = array( 'href' => $full_url ); if ( $this->img->allowInlineDisplay() and $width and $height) { # image # "Download high res version" link below the image - $msg = wfMsgHtml('showbigimage', $width, $height, intval( $this->img->getSize()/1024 ) ); - + $msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->img->getSize() ), $mime ); # We'll show a thumbnail of this image if ( $width > $maxWidth || $height > $maxHeight ) { # Calculate the thumbnail size. @@ -213,38 +224,41 @@ class ImagePage extends Article { # Note that $height <= $maxHeight now, but might not be identical # because of rounding. } - - if( $wgUseImageResize ) { - $thumbnail = $this->img->getThumbnail( $width, -1, $wgGenerateThumbnailOnParse ); - if ( $thumbnail == null ) { - $url = $this->img->getViewURL(); - } else { - $url = $thumbnail->getURL(); - } - } else { - # No resize ability? Show the full image, but scale - # it down in the browser so it fits on the page. - $url = $this->img->getViewURL(); - } - $anchoropen = "<a href=\"{$full_url}\">"; - $anchorclose = "</a><br />"; - if( $this->img->mustRender() ) { - $showLink = true; - } else { - $anchorclose .= "\n$anchoropen{$msg}</a>"; - } + $msgbig = wfMsgHtml( 'show-big-image' ); + $msgsmall = wfMsgExt( 'show-big-image-thumb', + array( 'parseinline' ), $width, $height ); } else { - $url = $this->img->getViewURL(); + # Image is small enough to show full size on image page + $msgbig = htmlspecialchars( $this->img->getName() ); + $msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) ); + } + + $params['width'] = $width; + $thumbnail = $this->img->transform( $params ); + + $anchorclose = "<br />"; + if( $this->img->mustRender() ) { $showLink = true; + } else { + $anchorclose .= + $msgsmall . + '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize; } if ( $this->img->isMultipage() ) { $wgOut->addHTML( '<table class="multipageimage"><tr><td>' ); } - $wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen . - "<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" . - htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' ); + $imgAttribs = array( + 'border' => 0, + 'alt' => $this->img->getTitle()->getPrefixedText() + ); + + if ( $thumbnail ) { + $wgOut->addHTML( '<div class="fullImageLink" id="file">' . + $thumbnail->toHtml( $imgAttribs, $linkAttribs ) . + $anchorclose . '</div>' ); + } if ( $this->img->isMultipage() ) { $count = $this->img->pageCount(); @@ -252,22 +266,26 @@ class ImagePage extends Article { if ( $page > 1 ) { $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false ); $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page-1) ); - $this->img->selectPage( $page - 1 ); - $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' ); + $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none', + array( 'page' => $page - 1 ) ); } else { $thumb1 = ''; } if ( $page < $count ) { $label = wfMsg( 'imgmultipagenext' ); - $this->img->selectPage( $page + 1 ); $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page+1) ); - $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' ); + $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none', + array( 'page' => $page + 1 ) ); } else { $thumb2 = ''; } - $select = '<form name="pageselector" action="' . $this->img->getEscapeLocalUrl( '' ) . '" method="GET" onchange="document.pageselector.submit();">' ; + global $wgScript; + $select = '<form name="pageselector" action="' . + htmlspecialchars( $wgScript ) . + '" method="get" onchange="document.pageselector.submit();">' . + Xml::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ); $select .= $wgOut->parse( wfMsg( 'imgmultigotopre' ), false ) . ' <select id="pageselector" name="page">'; for ( $i=1; $i <= $count; $i++ ) { @@ -279,7 +297,7 @@ class ImagePage extends Article { htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>'; $wgOut->addHTML( '</td><td><div class="multipageimagenavbox">' . - "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" ); + "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" ); } } else { #if direct link is allowed but it's not a renderable image, show an icon. @@ -296,25 +314,26 @@ class ImagePage extends Article { if ($showLink) { - $filename = wfEscapeWikiText( $this->img->getName() ); - // Hacky workaround: for some reason we use the incorrect MIME type - // image/svg for SVG. This should be fixed internally, but at least - // make the displayed type right. - $mime = $this->img->getMimeType(); + // Workaround for incorrect MIME type on SVGs uploaded in previous versions if ($mime == 'image/svg') $mime = 'image/svg+xml'; - $info = wfMsg( 'fileinfo', - ceil($this->img->getSize()/1024.0), - $mime ); + $filename = wfEscapeWikiText( $this->img->getName() ); + $info = wfMsg( 'file-info', $sk->formatSize( $this->img->getSize() ), $mime ); + $infores = ''; + + // Check for MIME type. Other types may have more information in the future. + if (substr($mime,0,9) == 'image/svg' ) { + $infores = wfMsg('file-svg', $width_orig, $height_orig ) . '<br />'; + } global $wgContLang; $dirmark = $wgContLang->getDirMark(); if (!$this->img->isSafeFile()) { $warning = wfMsg( 'mediawarning' ); $wgOut->addWikiText( <<<END -<div class="fullMedia"> +<div class="fullMedia">$infores <span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark -<span class="fileInfo"> ($info)</span> +<span class="fileInfo"> $info</span> </div> <div class="mediaWarning">$warning</div> @@ -322,8 +341,8 @@ END ); } else { $wgOut->addWikiText( <<<END -<div class="fullMedia"> -[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> ($info)</span> +<div class="fullMedia">$infores +[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $info</span> </div> END ); @@ -360,7 +379,9 @@ END $wgOut->addHTML($sharedtext); if ($wgRepositoryBaseUrl && $wgFetchCommonsDescriptions) { - $text = Http::get($url . '?action=render'); + $renderUrl = wfAppendQuery( $url, 'action=render' ); + wfDebug( "Fetching shared description from $renderUrl\n" ); + $text = Http::get( $renderUrl ); if ($text) $this->mExtraDescription = $text; } @@ -389,11 +410,11 @@ END # "Upload a new version of this file" link if( $wgUser->isAllowed( 'reupload' ) ) { $ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) ); - $wgOut->addHtml( "<li><div>{$ulink}</div></li>" ); + $wgOut->addHtml( "<li><div class='plainlinks'>{$ulink}</div></li>" ); } # External editing link - $elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' ); + $elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' ); $wgOut->addHtml( '<li>' . $elink . '<div>' . wfMsgWikiHtml( 'edit-externally-help' ) . '</div></li>' ); $wgOut->addHtml( '</ul>' ); @@ -449,9 +470,9 @@ END { global $wgUser, $wgOut; - $wgOut->addHTML( '<h2 id="filelinks">' . wfMsg( 'imagelinks' ) . "</h2>\n" ); + $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'filelinks' ), wfMsg( 'imagelinks' ) ) . "\n" ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); $imagelinks = $dbr->tableName( 'imagelinks' ); @@ -619,7 +640,7 @@ END $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' ); return; } - if ( ! $this->mTitle->userCanEdit() ) { + if ( ! $this->mTitle->userCan( 'edit' ) ) { $wgOut->readOnlyPage( $this->getContent(), true ); return; } @@ -645,9 +666,6 @@ END } $oldver = wfTimestampNow() . "!{$name}"; - $dbr =& wfGetDB( DB_SLAVE ); - $size = $dbr->selectField( 'oldimage', 'oi_size', array( 'oi_archive_name' => $oldimage ) ); - if ( ! rename( $curfile, "${archive}/{$oldver}" ) ) { $wgOut->showFileRenameError( $curfile, "${archive}/{$oldver}" ); return; @@ -683,6 +701,7 @@ END wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" ); $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' ); $update->doUpdate(); + $this->img->upgradeRow(); $this->img->purgeCache(); } else { wfDebug( "ImagePage::doPurge no image\n" ); @@ -694,7 +713,7 @@ END /** * @todo document - * @package MediaWiki + * @addtogroup Media */ class ImageHistoryList { function ImageHistoryList( &$skin ) { @@ -702,8 +721,9 @@ class ImageHistoryList { } function beginImageHistoryList() { - $s = "\n<h2 id=\"filehistory\">" . wfMsg( 'imghistory' ) . "</h2>\n" . - "<p>" . wfMsg( 'imghistlegend' ) . "</p>\n".'<ul class="special">'; + $s = "\n" . + Xml::element( 'h2', array( 'id' => 'filehistory' ), wfMsg( 'imghistory' ) ) . + "\n<p>" . wfMsg( 'imghistlegend' ) . "</p>\n".'<ul class="special">'; return $s; } @@ -716,9 +736,9 @@ class ImageHistoryList { global $wgUser, $wgLang, $wgTitle, $wgContLang; $datetime = $wgLang->timeanddate( $timestamp, true ); - $del = wfMsg( 'deleteimg' ); - $delall = wfMsg( 'deleteimgcompletely' ); - $cur = wfMsg( 'cur' ); + $del = wfMsgHtml( 'deleteimg' ); + $delall = wfMsgHtml( 'deleteimgcompletely' ); + $cur = wfMsgHtml( 'cur' ); if ( $iscur ) { $url = Image::imageUrl( $img ); @@ -734,10 +754,10 @@ class ImageHistoryList { } } else { $url = htmlspecialchars( wfImageArchiveUrl( $img ) ); - if( $wgUser->getID() != 0 && $wgTitle->userCanEdit() ) { + if( $wgUser->getID() != 0 && $wgTitle->userCan( 'edit' ) ) { $token = urlencode( $wgUser->editToken( $img ) ); $rlink = $this->skin->makeKnownLinkObj( $wgTitle, - wfMsg( 'revertimg' ), 'action=revert&oldimage=' . + wfMsgHtml( 'revertimg' ), 'action=revert&oldimage=' . urlencode( $img ) . "&wpEditToken=$token" ); $dlink = $this->skin->makeKnownLinkObj( $wgTitle, $del, 'action=delete&oldimage=' . urlencode( $img ) . @@ -746,7 +766,7 @@ class ImageHistoryList { # Having live active links for non-logged in users # means that bots and spiders crawling our site can # inadvertently change content. Baaaad idea. - $rlink = wfMsg( 'revertimg' ); + $rlink = wfMsgHtml( 'revertimg' ); $dlink = $del; } } @@ -754,7 +774,7 @@ class ImageHistoryList { $userlink = $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext ); $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $size ) ); - $widthheight = wfMsg( 'widthheight', $width, $height ); + $widthheight = wfMsgHtml( 'widthheight', $width, $height ); $style = $this->skin->getInternalLinkAttributes( $url, $datetime ); $s = "<li> ({$dlink}) ({$rlink}) <a href=\"{$url}\"{$style}>{$datetime}</a> . . {$userlink} . . {$widthheight} ({$nbytes})"; diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php new file mode 100644 index 00000000..93f090a1 --- /dev/null +++ b/includes/ImageQueryPage.php @@ -0,0 +1,68 @@ +<?php + +/** + * Variant of QueryPage which uses a gallery to output results, thus + * suited for reports generating images + * + * @package MediaWiki + * @addtogroup SpecialPage + * @author Rob Church <robchur@gmail.com> + */ +class ImageQueryPage extends QueryPage { + + /** + * Format and output report results using the given information plus + * OutputPage + * + * @param OutputPage $out OutputPage to print to + * @param Skin $skin User skin to use + * @param Database $dbr Database (read) connection to use + * @param int $res Result pointer + * @param int $num Number of available result rows + * @param int $offset Paging offset + */ + protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { + if( $num > 0 ) { + $gallery = new ImageGallery(); + $gallery->useSkin( $skin ); + + # $res might contain the whole 1,000 rows, so we read up to + # $num [should update this to use a Pager] + for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { + $image = $this->prepareImage( $row ); + if( $image instanceof Image ) { + $gallery->add( $image, $this->getCellHtml( $row ) ); + } + } + + $out->addHtml( $gallery->toHtml() ); + } + } + + /** + * Prepare an image object given a result row + * + * @param object $row Result row + * @return Image + */ + private function prepareImage( $row ) { + $namespace = isset( $row->namespace ) ? $row->namespace : NS_IMAGE; + $title = Title::makeTitleSafe( $namespace, $row->title ); + return ( $title instanceof Title && $title->getNamespace() == NS_IMAGE ) + ? new Image( $title ) + : null; + } + + /** + * Get additional HTML to be shown in a results' cell + * + * @param object $row Result row + * @return string + */ + protected function getCellHtml( $row ) { + return ''; + } + +} + +?> diff --git a/includes/JobQueue.php b/includes/JobQueue.php index 746cf5de..140130fa 100644 --- a/includes/JobQueue.php +++ b/includes/JobQueue.php @@ -4,6 +4,9 @@ if ( !defined( 'MEDIAWIKI' ) ) { die( "This file is part of MediaWiki, it is not a valid entry point\n" ); } +/** + * Class to both describe a background job and handle jobs. + */ abstract class Job { var $command, $title, @@ -13,10 +16,20 @@ abstract class Job { $error; /*------------------------------------------------------------------------- + * Abstract functions + *------------------------------------------------------------------------*/ + + /** + * Run the job + * @return boolean success + */ + abstract function run(); + + /*------------------------------------------------------------------------- * Static functions *------------------------------------------------------------------------*/ - /** + /** * @deprecated use LinksUpdate::queueRecursiveJobs() */ /** @@ -26,25 +39,41 @@ abstract class Job { /** * Pop a job off the front of the queue * @static + * @param $offset Number of jobs to skip * @return Job or false if there's no jobs */ - static function pop() { + static function pop($offset=0) { wfProfileIn( __METHOD__ ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); - // Get a job from the slave - $row = $dbr->selectRow( 'job', '*', '', __METHOD__, - array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) - ); + /* Get a job from the slave, start with an offset, + scan full set afterwards, avoid hitting purged rows - if ( $row === false ) { - wfProfileOut( __METHOD__ ); - return false; + NB: If random fetch previously was used, offset + will always be ahead of few entries + */ + + $row = $dbr->selectRow( 'job', '*', "job_id >= ${offset}", __METHOD__, + array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 )); + + // Refetching without offset is needed as some of job IDs could have had delayed commits + // and have lower IDs than jobs already executed, blame concurrency :) + // + if ( $row === false) { + if ($offset!=0) + $row = $dbr->selectRow( 'job', '*', '', __METHOD__, + array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 )); + + if ($row === false ) { + wfProfileOut( __METHOD__ ); + return false; + } } + $offset = $row->job_id; // Try to delete it from the master - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ ); $affected = $dbw->affectedRows(); $dbw->immediateCommit(); @@ -53,7 +82,7 @@ abstract class Job { // Failed, someone else beat us to it // Try getting a random row $row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob', - 'MAX(job_id) as maxjob' ), '', __METHOD__ ); + 'MAX(job_id) as maxjob' ), "job_id >= $offset", __METHOD__ ); if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) { // No jobs to get wfProfileOut( __METHOD__ ); @@ -61,7 +90,7 @@ abstract class Job { } // Get the random row $row = $dbw->selectRow( 'job', '*', - array( 'job_id' => mt_rand( $row->minjob, $row->maxjob ) ), __METHOD__ ); + 'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ ); if ( $row === false ) { // Random job gone before we got the chance to select it // Give up @@ -72,7 +101,7 @@ abstract class Job { $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ ); $affected = $dbw->affectedRows(); $dbw->immediateCommit(); - + if ( !$affected ) { // Random job gone before we exclusively deleted it // Give up @@ -80,22 +109,22 @@ abstract class Job { return false; } } - + // If execution got to here, there's a row in $row that has been deleted from the database // by this thread. Hence the concurrent pop was successful. $namespace = $row->job_namespace; $dbkey = $row->job_title; $title = Title::makeTitleSafe( $namespace, $dbkey ); $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id ); - + // Remove any duplicates it may have later in the queue $dbw->delete( 'job', $job->insertFields(), __METHOD__ ); - + wfProfileOut( __METHOD__ ); return $job; } - /** + /** * Create an object of a subclass */ static function factory( $command, $title, $params = false, $id = 0 ) { @@ -126,6 +155,27 @@ abstract class Job { } } + /** + * Batch-insert a group of jobs into the queue. + * This will be wrapped in a transaction with a forced commit. + * + * This may add duplicate at insert time, but they will be + * removed later on, when the first one is popped. + * + * @param $jobs array of Job objects + */ + static function batchInsert( $jobs ) { + if( count( $jobs ) ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + foreach( $jobs as $job ) { + $rows[] = $job->insertFields(); + } + $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); + $dbw->commit(); + } + } + /*------------------------------------------------------------------------- * Non-static functions *------------------------------------------------------------------------*/ @@ -147,8 +197,8 @@ abstract class Job { function insert() { $fields = $this->insertFields(); - $dbw =& wfGetDB( DB_MASTER ); - + $dbw = wfGetDB( DB_MASTER ); + if ( $this->removeDuplicates ) { $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ ); if ( $dbw->numRows( $res ) ) { @@ -158,7 +208,7 @@ abstract class Job { $fields['job_id'] = $dbw->nextSequenceValue( 'job_job_id_seq' ); $dbw->insert( 'job', $fields, __METHOD__ ); } - + protected function insertFields() { return array( 'job_cmd' => $this->command, @@ -167,34 +217,7 @@ abstract class Job { 'job_params' => Job::makeBlob( $this->params ) ); } - - /** - * Batch-insert a group of jobs into the queue. - * This will be wrapped in a transaction with a forced commit. - * - * This may add duplicate at insert time, but they will be - * removed later on, when the first one is popped. - * - * @param $jobs array of Job objects - */ - static function batchInsert( $jobs ) { - if( count( $jobs ) ) { - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin(); - foreach( $jobs as $job ) { - $rows[] = $job->insertFields(); - } - $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); - $dbw->commit(); - } - } - /** - * Run the job - * @return boolean success - */ - abstract function run(); - function toString() { $paramString = ''; if ( $this->params ) { @@ -222,6 +245,10 @@ abstract class Job { } } + +/** + * Background job to update links for a given title. + */ class RefreshLinksJob extends Job { function __construct( $title, $params = '', $id = 0 ) { parent::__construct( 'refreshLinks', $title, $params, $id ); @@ -237,7 +264,7 @@ class RefreshLinksJob extends Job { $linkCache =& LinkCache::singleton(); $linkCache->clear(); - + if ( is_null( $this->title ) ) { $this->error = "refreshLinks: Invalid title"; wfProfileOut( __METHOD__ ); diff --git a/includes/Licenses.php b/includes/Licenses.php index dd1308b4..f4586ae5 100644 --- a/includes/Licenses.php +++ b/includes/Licenses.php @@ -1,9 +1,8 @@ <?php /** * A License class for use on Special:Upload - * - * @package MediaWiki - * @subpackage SpecialPage + * + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason @@ -31,12 +30,12 @@ class Licenses { /**#@-*/ /** - * Constrictor + * Constructor * * @param $str String: the string to build the licenses member from, will use * wfMsgForContent( 'licenses' ) if null (default: null) */ - function Licenses( $str = null ) { + function __construct( $str = null ) { // PHP sucks, this should be possible in the constructor $this->msg = is_null( $str ) ? wfMsgForContent( 'licenses' ) : $str; $this->html = ''; @@ -147,6 +146,9 @@ class Licenses { function getHtml() { return $this->html; } } +/** + * A License class for use on Special:Upload (represents a single type of license). + */ class License { /** * @var string diff --git a/includes/LinkBatch.php b/includes/LinkBatch.php index 61e1c040..065c540a 100644 --- a/includes/LinkBatch.php +++ b/includes/LinkBatch.php @@ -4,8 +4,7 @@ * Class representing a list of titles * The execute() method checks them all for existence and adds them to a LinkCache object + - * @package MediaWiki - * @subpackage Cache + * @addtogroup Cache */ class LinkBatch { /** @@ -13,7 +12,7 @@ class LinkBatch { */ var $data = array(); - function LinkBatch( $arr = array() ) { + function __construct( $arr = array() ) { foreach( $arr as $item ) { $this->addObj( $item ); } @@ -120,7 +119,7 @@ class LinkBatch { // Construct query // This is very similar to Parser::replaceLinkHolders - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); $set = $this->constructSet( 'page', $dbr ); if ( $set === false ) { diff --git a/includes/LinkCache.php b/includes/LinkCache.php index 8e56225b..53fb640a 100644 --- a/includes/LinkCache.php +++ b/includes/LinkCache.php @@ -1,13 +1,8 @@ <?php /** * Cache for article titles (prefixed DB keys) and ids linked from one source - * @package MediaWiki - * @subpackage Cache - */ - -/** - * @package MediaWiki - * @subpackage Cache + * + * @addtogroup Cache */ class LinkCache { // Increment $mClassVer whenever old serialized versions of this class @@ -29,7 +24,7 @@ class LinkCache { return $instance; } - function LinkCache() { + function __construct() { $this->mForUpdate = false; $this->mPageLinks = array(); $this->mGoodLinks = array(); @@ -135,14 +130,14 @@ class LinkCache { $id = $wgMemc->get( $key = $this->getKey( $title ) ); if( ! is_integer( $id ) ) { if ( $this->mForUpdate ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); if ( !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) { $options = array( 'FOR UPDATE' ); } else { $options = array(); } } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $options = array(); } diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php index e03b59dd..39341d5d 100644 --- a/includes/LinkFilter.php +++ b/includes/LinkFilter.php @@ -14,7 +14,7 @@ class LinkFilter { /** * @static */ - function matchEntry( $text, $filterEntry ) { + static function matchEntry( $text, $filterEntry ) { $regex = LinkFilter::makeRegex( $filterEntry ); return preg_match( $regex, $text ); } @@ -22,10 +22,10 @@ class LinkFilter { /** * @static */ - function makeRegex( $filterEntry ) { + private static function makeRegex( $filterEntry ) { $regex = '!http://'; if ( substr( $filterEntry, 0, 2 ) == '*.' ) { - $regex .= '([A-Za-z0-9.-]+\.|)'; + $regex .= '(?:[A-Za-z0-9.-]+\.|)'; $filterEntry = substr( $filterEntry, 2 ); } $regex .= preg_quote( $filterEntry, '!' ) . '!Si'; @@ -47,8 +47,10 @@ class LinkFilter { * Asterisks in any other location are considered invalid. * * @static + * @param $filterEntry String: domainparts + * @param $prot String: protocol */ - function makeLike( $filterEntry ) { + public static function makeLike( $filterEntry , $prot = 'http://' ) { if ( substr( $filterEntry, 0, 2 ) == '*.' ) { $subdomains = true; $filterEntry = substr( $filterEntry, 2 ); @@ -74,17 +76,31 @@ class LinkFilter { $path = '/'; $host = $filterEntry; } - $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) ); - if ( substr( $host, -1, 1 ) !== '.' ) { - $host .= '.'; - } - $like = "http://$host"; - - if ( $subdomains ) { - $like .= '%'; - } - if ( !$subdomains || $path !== '/' ) { - $like .= $path . '%'; + // Reverse the labels in the hostname, convert to lower case + // For emails reverse domainpart only + if ( $prot == 'mailto:' && strpos($host, '@') ) { + // complete email adress + $mailparts = explode( '@', $host ); + $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) ); + $host = $domainpart . '@' . $mailparts[0]; + $like = "$prot$host%"; + } elseif ( $prot == 'mailto:' ) { + // domainpart of email adress only. do not add '.' + $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) ); + $like = "$prot$host%"; + } else { + $host = strtolower( implode( '.', array_reverse( explode( '.', $host ) ) ) ); + if ( substr( $host, -1, 1 ) !== '.' ) { + $host .= '.'; + } + $like = "$prot$host"; + + if ( $subdomains ) { + $like .= '%'; + } + if ( !$subdomains || $path !== '/' ) { + $like .= $path . '%'; + } } return $like; } diff --git a/includes/Linker.php b/includes/Linker.php index 0eabab2f..b12e2ad0 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -4,19 +4,15 @@ * These functions are used for primarily page content: * links, embedded images, table of contents. Links are * also used in the skin. - * @package MediaWiki - */ - -/** * For the moment, Skin is a descendent class of Linker. * In the future, it should probably be further split * so that ever other bit of the wiki doesn't have to * go loading up Skin to get at it. * - * @package MediaWiki + * @addtogroup Skins */ class Linker { - function Linker() {} + function __construct() {} /** * @deprecated @@ -229,7 +225,7 @@ class Linker { } else { $threshold = $wgUser->getOption('stubthreshold') ; if ( $threshold > 0 ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $s = $dbr->selectRow( array( 'page' ), array( 'page_len', @@ -358,16 +354,8 @@ class Linker { * the end of the link. */ function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { - $u = $nt->escapeLocalURL( $query ); - - if ( '' == $text ) { - $text = htmlspecialchars( $nt->getPrefixedText() ); - } $style = $this->getInternalLinkAttributesObj( $nt, $text, 'stub' ); - - list( $inside, $trail ) = Linker::splitTrail( $trail ); - $s = "<a href=\"{$u}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}"; - return $s; + return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style ); } /** @@ -431,25 +419,19 @@ class Linker { } /** @todo document */ - function makeImageLinkObj( $nt, $label, $alt, $align = '', $width = false, $height = false, $framed = false, - $thumb = false, $manual_thumb = '', $page = null ) + function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false, + $thumb = false, $manual_thumb = '', $valign = '' ) { - global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse; + global $wgContLang, $wgUser, $wgThumbLimits; $img = new Image( $nt ); - if ( ! is_null( $page ) ) { - $img->selectPage( $page ); - } - if ( !$img->allowInlineDisplay() && $img->exists() ) { return $this->makeKnownLinkObj( $nt ); } - $url = $img->getViewURL(); $error = $prefix = $postfix = ''; - - wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" ); + $page = isset( $params['page'] ) ? $params['page'] : false; if ( 'center' == $align ) { @@ -458,6 +440,19 @@ class Linker { $align = 'none'; } + if ( !isset( $params['width'] ) ) { + $params['width'] = $img->getWidth( $page ); + if( $thumb || $framed ) { + $wopt = $wgUser->getOption( 'thumbsize' ); + + if( !isset( $wgThumbLimits[$wopt] ) ) { + $wopt = User::getDefaultOption( 'thumbsize' ); + } + + $params['width'] = min( $params['width'], $wgThumbLimits[$wopt] ); + } + } + if ( $thumb || $framed ) { # Create a thumbnail. Alignment depends on language @@ -470,70 +465,39 @@ class Linker { if ( $align == '' ) { $align = $wgContLang->isRTL() ? 'left' : 'right'; } - - - if ( $width === false ) { - $wopt = $wgUser->getOption( 'thumbsize' ); - - if( !isset( $wgThumbLimits[$wopt] ) ) { - $wopt = User::getDefaultOption( 'thumbsize' ); - } - - $width = min( $img->getWidth(), $wgThumbLimits[$wopt] ); - } - - return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $width, $height, $framed, $manual_thumb ).$postfix; + return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix; } - if ( $width && $img->exists() ) { - - # Create a resized image, without the additional thumbnail - # features - - if ( $height == false ) - $height = -1; - if ( $manual_thumb == '') { - $thumb = $img->getThumbnail( $width, $height, $wgGenerateThumbnailOnParse ); - if ( $thumb ) { - // In most cases, $width = $thumb->width or $height = $thumb->height. - // If not, we're scaling the image larger than it can be scaled, - // so we send to the browser a smaller thumbnail, and let the client do the scaling. - - if ($height != -1 && $width > $thumb->width * $height / $thumb->height) { - // $height is the limiting factor, not $width - // set $width to the largest it can be, such that the resulting - // scaled height is at most $height - $width = floor($thumb->width * $height / $thumb->height); - } - $height = round($thumb->height * $width / $thumb->width); + if ( $params['width'] && $img->exists() ) { + # Create a resized image, without the additional thumbnail features + $thumb = $img->transform( $params ); + } else { + $thumb = false; + } - wfDebug( "makeImageLinkObj: client-size set to '$width x $height'\n" ); - $url = $thumb->getUrl(); - } else { - $error = htmlspecialchars( $img->getLastError() ); - // Do client-side scaling... - $height = intval( $img->getHeight() * $width / $img->getWidth() ); - } - } + if ( $page ) { + $query = 'page=' . urlencode( $page ); } else { - $width = $img->width; - $height = $img->height; + $query = ''; + } + $u = $nt->getLocalURL( $query ); + $imgAttribs = array( + 'alt' => $alt, + 'longdesc' => $u + ); + if ( $valign ) { + $imgAttribs['style'] = "vertical-align: $valign"; } + $linkAttribs = array( + 'href' => $u, + 'class' => 'image', + 'title' => $alt + ); - wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" ); - $u = $nt->escapeLocalURL(); - if ( $error ) { - $s = $error; - } elseif ( $url == '' ) { + if ( !$thumb ) { $s = $this->makeBrokenImageLinkObj( $img->getTitle() ); - //$s .= "<br />{$alt}<br />{$url}<br />\n"; } else { - $s = '<a href="'.$u.'" class="image" title="'.$alt.'">' . - '<img src="'.$url.'" alt="'.$alt.'" ' . - ( $width - ? ( 'width="'.$width.'" height="'.$height.'" ' ) - : '' ) . - 'longdesc="'.$u.'" /></a>'; + $s = $thumb->toHtml( $imgAttribs, $linkAttribs ); } if ( '' != $align ) { $s = "<div class=\"float{$align}\"><span>{$s}</span></div>"; @@ -545,86 +509,64 @@ class Linker { * Make HTML for a thumbnail including image, border and caption * $img is an Image object */ - function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) { - global $wgStylePath, $wgContLang, $wgGenerateThumbnailOnParse; + function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) { + global $wgStylePath, $wgContLang; $thumbUrl = ''; $error = ''; - $width = $height = 0; - if ( $img->exists() ) { - $width = $img->getWidth(); - $height = $img->getHeight(); - } - if ( 0 == $width || 0 == $height ) { - $width = $height = 180; - } - if ( $boxwidth == 0 ) { - $boxwidth = 180; + $page = isset( $params['page'] ) ? $params['page'] : false; + + if ( empty( $params['width'] ) ) { + $params['width'] = 180; } - if ( $framed ) { + $thumb = false; + if ( $manual_thumb != '' ) { + # Use manually specified thumbnail + $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); + if( $manual_title ) { + $manual_img = new Image( $manual_title ); + $thumb = $manual_img->getUnscaledThumb(); + } + } elseif ( $framed ) { // Use image dimensions, don't scale - $boxwidth = $width; - $boxheight = $height; - $thumbUrl = $img->getViewURL(); + $thumb = $img->getUnscaledThumb( $page ); } else { - if ( $boxheight === false ) - $boxheight = -1; - if ( '' == $manual_thumb ) { - $thumb = $img->getThumbnail( $boxwidth, $boxheight, $wgGenerateThumbnailOnParse ); - if ( $thumb ) { - $thumbUrl = $thumb->getUrl(); - $boxwidth = $thumb->width; - $boxheight = $thumb->height; - } else { - $error = $img->getLastError(); - } - } + $thumb = $img->transform( $params ); } - $oboxwidth = $boxwidth + 2; - if ( $manual_thumb != '' ) # Use manually specified thumbnail - { - $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $manual_thumb ) ; - if( $manual_title ) { - $manual_img = new Image( $manual_title ); - $thumbUrl = $manual_img->getViewURL(); - if ( $manual_img->exists() ) - { - $width = $manual_img->getWidth(); - $height = $manual_img->getHeight(); - $boxwidth = $width ; - $boxheight = $height ; - $oboxwidth = $boxwidth + 2 ; - } - } + if ( $thumb ) { + $outerWidth = $thumb->getWidth() + 2; + } else { + $outerWidth = $params['width'] + 2; } - $u = $img->getEscapeLocalURL(); + $query = $page ? 'page=' . urlencode( $page ) : ''; + $u = $img->getTitle()->getLocalURL( $query ); $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) ); $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right'; $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : ''; - $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">"; - if( $thumbUrl == '' ) { - // Couldn't generate thumbnail? Scale the image client-side. - $thumbUrl = $img->getViewURL(); - if( $boxheight == -1 ) { - // Approximate... - $boxheight = intval( $height * $boxwidth / $width ); - } - } - if ( $error ) { - $s .= htmlspecialchars( $error ); + $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">"; + if ( !$thumb ) { + $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) ); $zoomicon = ''; } elseif( !$img->exists() ) { $s .= $this->makeBrokenImageLinkObj( $img->getTitle() ); $zoomicon = ''; } else { - $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'. - '<img src="'.$thumbUrl.'" alt="'.$alt.'" ' . - 'width="'.$boxwidth.'" height="'.$boxheight.'" ' . - 'longdesc="'.$u.'" class="thumbimage" /></a>'; + $imgAttribs = array( + 'alt' => $alt, + 'longdesc' => $u, + 'class' => 'thumbimage' + ); + $linkAttribs = array( + 'href' => $u, + 'class' => 'internal', + 'title' => $alt + ); + + $s .= $thumb->toHtml( $imgAttribs, $linkAttribs ); if ( $framed ) { $zoomicon=""; } else { @@ -680,8 +622,6 @@ class Linker { * * @param $title Title object. * @param $text String: pre-sanitized HTML - * @param $nourl Boolean: Mask absolute URLs, so the parser doesn't - * linkify them (it is currently not context-aware) * @return string HTML * * @public @@ -756,10 +696,10 @@ class Linker { /** * @param $userId Integer: user id in database. * @param $userText String: user name in database. + * @param $redContribsWhenNoEdits Bool: return a red contribs link when the user had no edits and this is true. * @return string HTML fragment with talk and/or block links - * @private */ - function userToolLinks( $userId, $userText ) { + public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false ) { global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans; $talkable = !( $wgDisableAnonTalk && 0 == $userId ); $blockable = ( $wgSysopUserBans || 0 == $userId ); @@ -769,9 +709,15 @@ class Linker { $items[] = $this->userTalkLink( $userId, $userText ); } if( $userId ) { + // check if the user has an edit + if( $redContribsWhenNoEdits && User::edits( $userId ) == 0 ) { + $style = "class='new'"; + } else { + $style = ''; + } $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); - $items[] = $this->makeKnownLinkObj( $contribsPage , - wfMsgHtml( 'contribslink' ) ); + + $items[] = $this->makeKnownLinkObj( $contribsPage, wfMsgHtml( 'contribslink' ), '', '', '', '', $style ); } if( $blockable && $wgUser->isAllowed( 'block' ) ) { $items[] = $this->blockLink( $userId, $userText ); @@ -785,17 +731,22 @@ class Linker { } /** + * Alias for userToolLinks( $userId, $userText, true ); + */ + public function userToolLinksRedContribs( $userId, $userText ) { + return $this->userToolLinks( $userId, $userText, true ); + } + + + /** * @param $userId Integer: user id in database. * @param $userText String: user name in database. * @return string HTML fragment with user talk link * @private */ function userTalkLink( $userId, $userText ) { - global $wgLang; - $talkname = $wgLang->getNsText( NS_TALK ); # use the shorter name - $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText ); - $userTalkLink = $this->makeLinkObj( $userTalkPage, $talkname ); + $userTalkLink = $this->makeLinkObj( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) ); return $userTalkLink; } @@ -860,7 +811,7 @@ class Linker { * Since you can't set a default parameter for a reference, I've turned it * temporarily to a value pass. Should be adjusted further. --brion * - * $param string $comment + * @param string $comment * @param mixed $title Title object (to generate link to the section in autocomment) or null * @param bool $local Whether section links should refer to local page */ @@ -1013,7 +964,7 @@ class Linker { /** @todo document */ function tocList($toc) { global $wgJsMimeType; - $title = wfMsgForContent('toc') ; + $title = wfMsgHtml('toc') ; return '<table id="toc" class="toc" summary="' . $title .'"><tr><td>' . '<div id="toctitle"><h2>' . $title . "</h2></div>\n" @@ -1023,8 +974,8 @@ class Linker { . "</ul>\n</td></tr></table>" . '<script type="' . $wgJsMimeType . '">' . ' if (window.showTocToggle) {' - . ' var tocShowText = "' . wfEscapeJsString( wfMsgForContent('showtoc') ) . '";' - . ' var tocHideText = "' . wfEscapeJsString( wfMsgForContent('hidetoc') ) . '";' + . ' var tocShowText = "' . wfEscapeJsString( wfMsg('showtoc') ) . '";' + . ' var tocHideText = "' . wfEscapeJsString( wfMsg('hidetoc') ) . '";' . ' showTocToggle();' . ' } ' . "</script>\n"; @@ -1134,7 +1085,7 @@ class Linker { global $wgUser; wfProfileIn( __METHOD__ ); - $sk =& $wgUser->getSkin(); + $sk = $wgUser->getSkin(); $outText = ''; if ( count( $templates ) > 0 ) { @@ -1182,10 +1133,14 @@ class Linker { */ public function formatSize( $size ) { global $wgLang; + // For small sizes no decimal places necessary + $round = 0; if( $size > 1024 ) { $size = $size / 1024; if( $size > 1024 ) { $size = $size / 1024; + // For MB and bigger two decimal places are smarter + $round = 2; if( $size > 1024 ) { $size = $size / 1024; $msg = 'size-gigabytes'; @@ -1198,10 +1153,59 @@ class Linker { } else { $msg = 'size-bytes'; } - $size = round( $size, 0 ); + $size = round( $size, $round ); return wfMsgHtml( $msg, $wgLang->formatNum( $size ) ); } - + + /** + * Given the id of an interface element, constructs the appropriate title + * and accesskey attributes from the system messages. (Note, this is usu- + * ally the id but isn't always, because sometimes the accesskey needs to + * go on a different element than the id, for reverse-compatibility, etc.) + * + * @param string $name Id of the element, minus prefixes. + * @return string title and accesskey attributes, ready to drop in an + * element (e.g., ' title="This does something [x]" accesskey="x"'). + */ + public function tooltipAndAccesskey($name) { + $out = ''; + + $tooltip = wfMsg('tooltip-'.$name); + if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') { + // Compatibility: formerly some tooltips had [alt-.] hardcoded + $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip ); + $out .= ' title="'.htmlspecialchars($tooltip); + } + $accesskey = wfMsg('accesskey-'.$name); + if ($accesskey && $accesskey != '-' && !wfEmptyMsg('accesskey-'.$name, $accesskey)) { + if ($out) $out .= " [$accesskey]\" accesskey=\"$accesskey\""; + else $out .= " title=\"[$accesskey]\" accesskey=\"$accesskey\""; + } elseif ($out) { + $out .= '"'; + } + return $out; + } + + /** + * Given the id of an interface element, constructs the appropriate title + * attribute from the system messages. (Note, this is usually the id but + * isn't always, because sometimes the accesskey needs to go on a different + * element than the id, for reverse-compatibility, etc.) + * + * @param string $name Id of the element, minus prefixes. + * @return string title attribute, ready to drop in an element + * (e.g., ' title="This does something"'). + */ + public function tooltip($name) { + $out = ''; + + $tooltip = wfMsg('tooltip-'.$name); + if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') { + $out = ' title="'.htmlspecialchars($tooltip).'"'; + } + + return $out; + } } ?> diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php index 9e25bf07..856c665d 100644 --- a/includes/LinksUpdate.php +++ b/includes/LinksUpdate.php @@ -1,19 +1,15 @@ <?php /** - * See deferred.txt - * @package MediaWiki - */ - -/** - * @todo document - * @package MediaWiki + * See docs/deferred.txt + * + * @todo document (e.g. one-sentence top-level class description). */ class LinksUpdate { /**@{{ * @private */ - var $mId, //!< Page ID of the article linked from + var $mId, //!< Page ID of the article linked from $mTitle, //!< Title object of the article linked from $mLinks, //!< Map of title strings to IDs for the links in the document $mImages, //!< DB keys of the images used, in the array key only @@ -41,7 +37,7 @@ class LinksUpdate { } else { $this->mOptions = array( 'FOR UPDATE' ); } - $this->mDb =& wfGetDB( DB_MASTER ); + $this->mDb = wfGetDB( DB_MASTER ); if ( !is_object( $title ) ) { throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " . @@ -172,7 +168,7 @@ class LinksUpdate { wfProfileIn( __METHOD__ ); $batchSize = 100; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( array( 'templatelinks', 'page' ), array( 'page_namespace', 'page_title' ), array( diff --git a/includes/LoadBalancer.php b/includes/LoadBalancer.php index 396ef865..4ebe26c7 100644 --- a/includes/LoadBalancer.php +++ b/includes/LoadBalancer.php @@ -1,7 +1,6 @@ <?php /** * - * @package MediaWiki */ @@ -9,7 +8,6 @@ * Database load balancing object * * @todo document - * @package MediaWiki */ class LoadBalancer { /* private */ var $mServers, $mConnections, $mLoads, $mGroupLoads; @@ -24,7 +22,7 @@ class LoadBalancer { */ const AVG_STATUS_POLL = 2000; - function LoadBalancer( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false ) + function __construct( $servers, $failFunction = false, $waitTimeout = 10, $waitForMasterNow = false ) { $this->mServers = $servers; $this->mFailFunction = $failFunction; @@ -32,7 +30,7 @@ class LoadBalancer { $this->mWriteIndex = -1; $this->mForce = -1; $this->mConnections = array(); - $this->mLastIndex = 1; + $this->mLastIndex = -1; $this->mLoads = array(); $this->mWaitForFile = false; $this->mWaitForPos = false; @@ -97,7 +95,9 @@ class LoadBalancer { # Unset excessively lagged servers $lags = $this->getLagTimes(); foreach ( $lags as $i => $lag ) { - if ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) { + if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) && + ( $lag === false || $lag > $this->mServers[$i]['max lag'] ) ) + { unset( $loads[$i] ); } } @@ -504,8 +504,7 @@ class LoadBalancer { * Save master pos to the session and to memcached, if the session exists */ function saveMasterPos() { - global $wgSessionStarted; - if ( $wgSessionStarted && count( $this->mServers ) > 1 ) { + if ( session_id() != '' && count( $this->mServers ) > 1 ) { # If this entire request was served from a slave without opening a connection to the # master (however unlikely that may be), then we can fetch the position from the slave. if ( empty( $this->mConnections[0] ) ) { diff --git a/includes/LogPage.php b/includes/LogPage.php index dd395126..af03bbba 100644 --- a/includes/LogPage.php +++ b/includes/LogPage.php @@ -21,7 +21,6 @@ /** * Contain log classes * - * @package MediaWiki */ /** @@ -29,7 +28,6 @@ * The logs are now kept in a table which is easier to manage and trim * than ever-growing wiki pages. * - * @package MediaWiki */ class LogPage { /* @access private */ @@ -44,7 +42,7 @@ class LogPage { * 'upload', 'move' * @param bool $rc Whether to update recent changes as well as the logging table */ - function LogPage( $type, $rc = true ) { + function __construct( $type, $rc = true ) { $this->type = $type; $this->updateRecentChanges = $rc; } @@ -55,7 +53,7 @@ class LogPage { global $wgUser; $fname = 'LogPage::saveContent'; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $uid = $wgUser->getID(); $this->timestamp = $now = wfTimestampNow(); @@ -119,7 +117,7 @@ class LogPage { } /** - * @fixme: handle missing log types + * @todo handle missing log types * @static */ function logHeader( $type ) { @@ -134,6 +132,10 @@ class LogPage { global $wgLang, $wgContLang, $wgLogActions; $key = "$type/$action"; + + if( $key == 'patrol/patrol' ) + return PatrolLog::makeActionText( $title, $params, $skin ); + if( isset( $wgLogActions[$key] ) ) { if( is_null( $title ) ) { $rv=wfMsg( $wgLogActions[$key] ); @@ -183,8 +185,13 @@ class LogPage { } } else { array_unshift( $params, $titleLink ); - if ( $translate && $key == 'block/block' ) { - $params[1] = $wgLang->translateBlockExpiry($params[1]); + if ( $key == 'block/block' ) { + if ( $translate ) { + $params[1] = $wgLang->translateBlockExpiry( $params[1] ); + } + $params[2] = isset( $params[2] ) + ? self::formatBlockFlags( $params[2] ) + : ''; } $rv = wfMsgReal( $wgLogActions[$key], $params, true, !$skin ); } @@ -241,6 +248,41 @@ class LogPage { return explode( "\n", $blob ); } } + + /** + * Convert a comma-delimited list of block log flags + * into a more readable (and translated) form + * + * @param $flags Flags to format + * @return string + */ + public static function formatBlockFlags( $flags ) { + $flags = explode( ',', trim( $flags ) ); + if( count( $flags ) > 0 ) { + for( $i = 0; $i < count( $flags ); $i++ ) + $flags[$i] = self::formatBlockFlag( $flags[$i] ); + return '(' . implode( ', ', $flags ) . ')'; + } else { + return ''; + } + } + + /** + * Translate a block log flag if possible + * + * @param $flag Flag to translate + * @return string + */ + public static function formatBlockFlag( $flag ) { + static $messages = array(); + if( !isset( $messages[$flag] ) ) { + $k = 'block-log-flags-' . $flag; + $msg = wfMsg( $k ); + $messages[$flag] = htmlspecialchars( wfEmptyMsg( $k, $msg ) ? $flag : $msg ); + } + return $messages[$flag]; + } + } ?> diff --git a/includes/MacBinary.php b/includes/MacBinary.php index 05c3ce5c..2f6ad4f4 100644 --- a/includes/MacBinary.php +++ b/includes/MacBinary.php @@ -22,12 +22,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class MacBinary { - function MacBinary( $filename ) { + function __construct( $filename ) { $this->open( $filename ); $this->loadHeader(); } @@ -269,4 +268,4 @@ class MacBinary { } } -?>
\ No newline at end of file +?> diff --git a/includes/MagicWord.php b/includes/MagicWord.php index 60bfd0f4..bf72a0c8 100644 --- a/includes/MagicWord.php +++ b/includes/MagicWord.php @@ -1,8 +1,7 @@ <?php /** * File for magic words - * @package MediaWiki - * @subpackage Parser + * @addtogroup Parser */ /** @@ -21,7 +20,6 @@ * magic words which are also Parser variables, add a MagicWordwgVariableIDs * hook. Use string keys. * - * @package MediaWiki */ class MagicWord { /**#@+ @@ -55,6 +53,7 @@ class MagicWord { 'localhour', 'numberofarticles', 'numberoffiles', + 'numberofedits', 'sitename', 'server', 'servername', @@ -108,7 +107,7 @@ class MagicWord { /**#@-*/ - function MagicWord($id = 0, $syn = '', $cs = false) { + function __construct($id = 0, $syn = '', $cs = false) { $this->mId = $id; $this->mSynonyms = (array)$syn; $this->mCaseSensitive = $cs; diff --git a/includes/Math.php b/includes/Math.php index 9fa631f7..88934e5f 100644 --- a/includes/Math.php +++ b/includes/Math.php @@ -1,7 +1,6 @@ <?php /** * Contain everything related to <math> </math> parsing - * @package MediaWiki */ /** @@ -11,7 +10,6 @@ * * by Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004) * - * @package MediaWiki */ class MathRenderer { var $mode = MW_MATH_MODERN; @@ -22,7 +20,7 @@ class MathRenderer { var $mathml = ''; var $conservativeness = 0; - function MathRenderer( $tex ) { + function __construct( $tex ) { $this->tex = $tex; } @@ -156,7 +154,7 @@ class MathRenderer { $md5_sql = pack('H32', $this->md5); # Binary packed, not hex - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->replace( 'math', array( 'math_inputhash' ), array( 'math_inputhash' => $md5_sql, @@ -185,7 +183,7 @@ class MathRenderer { $fname = 'MathRenderer::_recall'; $this->md5 = md5( $this->tex ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $rpage = $dbr->selectRow( 'math', array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ), array( 'math_inputhash' => pack("H32", $this->md5)), # Binary packed, not hex diff --git a/includes/MediaTransformOutput.php b/includes/MediaTransformOutput.php new file mode 100644 index 00000000..60057e3a --- /dev/null +++ b/includes/MediaTransformOutput.php @@ -0,0 +1,166 @@ +<?php + +/** + * Base class for the output of MediaHandler::doTransform() and Image::transform(). + * + * @addtogroup Media + */ +abstract class MediaTransformOutput { + /** + * Get the width of the output box + */ + function getWidth() { + return $this->width; + } + + /** + * Get the height of the output box + */ + function getHeight() { + return $this->height; + } + + /** + * @return string The thumbnail URL + */ + function getUrl() { + return $this->url; + } + + /** + * @return string Destination file path (local filesystem) + */ + function getPath() { + return $this->path; + } + + /** + * Fetch HTML for this transform output + * @param array $attribs Advisory associative array of HTML attributes supplied + * by the linker. These can be incorporated into the output in any way. + * @param array $linkAttribs Attributes of a suggested enclosing <a> tag. + * May be ignored. + */ + abstract function toHtml( $attribs = array() , $linkAttribs = false ); + + /** + * This will be overridden to return true in error classes + */ + function isError() { + return false; + } + + /** + * Wrap some XHTML text in an anchor tag with the given attributes + */ + protected function linkWrap( $linkAttribs, $contents ) { + if ( $linkAttribs ) { + return Xml::tags( 'a', $linkAttribs, $contents ); + } else { + return $contents; + } + } +} + + +/** + * Media transform output for images + * + * @addtogroup Media + */ +class ThumbnailImage extends MediaTransformOutput { + /** + * @param string $path Filesystem path to the thumb + * @param string $url URL path to the thumb + * @private + */ + function ThumbnailImage( $url, $width, $height, $path = false ) { + $this->url = $url; + # These should be integers when they get here. + # If not, there's a bug somewhere. But let's at + # least produce valid HTML code regardless. + $this->width = round( $width ); + $this->height = round( $height ); + $this->path = $path; + } + + /** + * Return HTML <img ... /> tag for the thumbnail, will include + * width and height attributes and a blank alt text (as required). + * + * You can set or override additional attributes by passing an + * associative array of name => data pairs. The data will be escaped + * for HTML output, so should be in plaintext. + * + * If $linkAttribs is given, the image will be enclosed in an <a> tag. + * + * @param array $attribs + * @param array $linkAttribs + * @return string + * @public + */ + function toHtml( $attribs = array(), $linkAttribs = false ) { + $attribs['src'] = $this->url; + $attribs['width'] = $this->width; + $attribs['height'] = $this->height; + if( !isset( $attribs['alt'] ) ) $attribs['alt'] = ''; + return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) ); + } + +} + +/** + * Basic media transform error class + * + * @addtogroup Media + */ +class MediaTransformError extends MediaTransformOutput { + var $htmlMsg, $textMsg, $width, $height, $url, $path; + + function __construct( $msg, $width, $height /*, ... */ ) { + $args = array_slice( func_get_args(), 3 ); + $htmlArgs = array_map( 'htmlspecialchars', $args ); + $htmlArgs = array_map( 'nl2br', $htmlArgs ); + + $this->htmlMsg = wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $msg, true ) ), $htmlArgs ); + $this->textMsg = wfMsgReal( $msg, $args ); + $this->width = intval( $width ); + $this->height = intval( $height ); + $this->url = false; + $this->path = false; + } + + function toHtml( $attribs = array(), $linkAttribs = false ) { + return "<table class=\"MediaTransformError\" style=\"" . + "width: {$this->width}px; height: {$this->height}px;\"><tr><td>" . + $this->htmlMsg . + "</td></tr></table>"; + } + + function toText() { + return $this->textMsg; + } + + function getHtmlMsg() { + return $this->htmlMsg; + } + + function isError() { + return true; + } +} + +/** + * Shortcut class for parameter validation errors + * + * @addtogroup Media + */ +class TransformParameterError extends MediaTransformError { + function __construct( $params ) { + parent::__construct( 'thumbnail_error', + max( @$params['width'], 180 ), max( @$params['height'], 180 ), + wfMsg( 'thumbnail_invalid_params' ) ); + } +} + +?> diff --git a/includes/MemcachedSessions.php b/includes/MemcachedSessions.php index e2dc52ca..3bcf5535 100644 --- a/includes/MemcachedSessions.php +++ b/includes/MemcachedSessions.php @@ -6,7 +6,6 @@ * be necessary to change the cookie settings to work across hostnames. * See: http://www.php.net/manual/en/function.session-set-save-handler.php * - * @package MediaWiki */ /** diff --git a/includes/MessageCache.php b/includes/MessageCache.php index a269c620..e2cbf5f6 100644 --- a/includes/MessageCache.php +++ b/includes/MessageCache.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage Cache + * @addtogroup Cache */ /** @@ -17,7 +16,6 @@ define( 'MSG_CACHE_VERSION', 1 ); * Message cache * Performs various MediaWiki namespace-related functions * - * @package MediaWiki */ class MessageCache { var $mCache, $mUseCache, $mDisable, $mExpiry; @@ -298,10 +296,10 @@ class MessageCache { * Loads all or main part of cacheable messages from the database */ function loadFromDB() { - global $wgLang, $wgMaxMsgCacheEntrySize; + global $wgMaxMsgCacheEntrySize; wfProfileIn( __METHOD__ ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $this->mCache = array(); # Load titles for all oversized pages in the MediaWiki namespace @@ -547,7 +545,7 @@ class MessageCache { if ( $type == ' ' ) { $message = substr( $entry, 1 ); - $this->mCache[$title] = $message; + $this->mCache[$title] = $entry; return $message; } elseif ( $entry == '!NONEXISTENT' ) { return false; diff --git a/includes/Metadata.php b/includes/Metadata.php index 4e0d91b7..b995b223 100644 --- a/includes/Metadata.php +++ b/includes/Metadata.php @@ -18,7 +18,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * @author Evan Prodromou <evan@wikitravel.org> - * @package MediaWiki */ /** @@ -74,7 +73,9 @@ function wfCreativeCommonsRdf($article) { function rdfSetup() { global $wgOut, $_SERVER; - $rdftype = wfNegotiateType(wfAcceptToPrefs($_SERVER['HTTP_ACCEPT']), wfAcceptToPrefs(RDF_TYPE_PREFS)); + $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : null; + + $rdftype = wfNegotiateType(wfAcceptToPrefs($httpaccept), wfAcceptToPrefs(RDF_TYPE_PREFS)); if (!$rdftype) { wfHttpError(406, "Not Acceptable", wfMsg("notacceptable")); diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index ca05dbb3..db35535d 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -1,7 +1,6 @@ <?php /** Module defining helper functions for detecting and dealing with mime types. * - * @package MediaWiki */ /** Defines a set of well known mime types @@ -23,9 +22,10 @@ image/x-bmp bmp image/gif gif image/jpeg jpeg jpg jpe image/png png -image/svg+xml svg +image/svg+xml image/svg svg image/tiff tiff tif image/vnd.djvu djvu +image/x-portable-pixmap ppm text/plain txt text/html html htm video/ogg ogm ogg @@ -51,9 +51,10 @@ image/x-bmp image/bmp [BITMAP] image/gif [BITMAP] image/jpeg [BITMAP] image/png [BITMAP] -image/svg image/svg+xml [DRAWING] +image/svg+xml [DRAWING] image/tiff [BITMAP] image/vnd.djvu [BITMAP] +image/x-portable-pixmap [BITMAP] text/plain [TEXT] text/html [TEXT] video/ogg [VIDEO] @@ -70,13 +71,13 @@ if ($wgLoadFileinfoExtension) { if(!extension_loaded('fileinfo')) dl('fileinfo.' . PHP_SHLIB_SUFFIX); } -/** Implements functions related to mime types such as detection and mapping to -* file extension, -* -* Instances of this class are stateles, there only needs to be one global instance -* of MimeMagic. Please use MimeMagic::singleton() to get that instance. -* @package MediaWiki -*/ +/** + * Implements functions related to mime types such as detection and mapping to + * file extension. + * + * Instances of this class are stateles, there only needs to be one global instance + * of MimeMagic. Please use MimeMagic::singleton() to get that instance. + */ class MimeMagic { /** @@ -105,7 +106,7 @@ class MimeMagic { * * This constructor parses the mime.types and mime.info files and build internal mappings. */ - function MimeMagic() { + function __construct() { /* * --- load mime.types --- */ @@ -149,7 +150,7 @@ class MimeMagic { if (empty($ext)) continue; - if (@$this->mMimeToExt[$mime]) $this->mMimeToExt[$mime] .= ' '.$ext; + if ( !empty($this->mMimeToExt[$mime])) $this->mMimeToExt[$mime] .= ' '.$ext; else $this->mMimeToExt[$mime]= $ext; $extensions= explode(' ',$ext); @@ -158,7 +159,7 @@ class MimeMagic { $e= trim($e); if (empty($e)) continue; - if (@$this->mExtToMime[$e]) $this->mExtToMime[$e] .= ' '.$mime; + if ( !empty($this->mExtToMime[$e])) $this->mExtToMime[$e] .= ' '.$mime; else $this->mExtToMime[$e]= $mime; } } @@ -262,7 +263,7 @@ class MimeMagic { function getTypesForExtension($ext) { $ext= strtolower($ext); - $r= @$this->mExtToMime[$ext]; + $r= isset( $this->mExtToMime[$ext] ) ? $this->mExtToMime[$ext] : null; return $r; } @@ -341,7 +342,7 @@ class MimeMagic { } - /** mime type detection. This uses detectMimeType to detect the mim type of the file, + /** mime type detection. This uses detectMimeType to detect the mime type of the file, * but applies additional checks to determine some well known file formats that may be missed * or misinterpreter by the default mime detection (namely xml based formats like XHTML or SVG). * @@ -399,8 +400,8 @@ class MimeMagic { #print "<br>ANALYSING $file ($mime): doctype= $doctype; tag= $tag<br>"; - if (strpos($doctype,"-//W3C//DTD SVG")===0) $mime= "image/svg"; - elseif ($tag==="svg") $mime= "image/svg"; + if (strpos($doctype,"-//W3C//DTD SVG")===0) $mime= "image/svg+xml"; + elseif ($tag==="svg") $mime= "image/svg+xml"; elseif (strpos($doctype,"-//W3C//DTD XHTML")===0) $mime= "text/html"; elseif ($tag==="html") $mime= "text/html"; } @@ -424,7 +425,9 @@ class MimeMagic { $match= array(); $prog= ""; - if (preg_match('%/?([^\s]+/)(w+)%sim',$head,$match)) $script= $match[2]; + if (preg_match('%/?([^\s]+/)(w+)%sim',$head,$match)) { + $script= $match[2]; // FIXME: $script variable not used; should this be "$prog = $match[2];" instead? + } $mime= "application/x-$prog"; } diff --git a/includes/Namespace.php b/includes/Namespace.php index 78493902..dd67b55a 100644 --- a/includes/Namespace.php +++ b/includes/Namespace.php @@ -1,7 +1,6 @@ <?php /** * Provide things related to namespaces - * @package MediaWiki */ /** @@ -41,7 +40,6 @@ if( is_array( $wgExtraNamespaces ) ) { * These are synonyms for the names given in the language file * Users and translators should not change them * - * @package MediaWiki */ class Namespace { @@ -125,5 +123,19 @@ class Namespace { static function canTalk( $index ) { return( $index >= NS_MAIN ); } + + /** + * Does this namespace contain content, for the purposes + * of calculating statistics, etc? + * + * @param $index Index to check + * @return bool + */ + public static function isContent( $index ) { + global $wgContentNamespaces; + return $index == NS_MAIN || in_array( $index, $wgContentNamespaces ); + } + } + ?> diff --git a/includes/ObjectCache.php b/includes/ObjectCache.php index 2b26cf4e..a493a75c 100644 --- a/includes/ObjectCache.php +++ b/includes/ObjectCache.php @@ -1,7 +1,6 @@ <?php /** - * @package MediaWiki - * @subpackage Cache + * @addtogroup Cache */ /** @@ -9,8 +8,7 @@ * It acts as a memcached server with no RAM, that is, all objects are * cleared the moment they are set. All set operations succeed and all * get operations return null. - * @package MediaWiki - * @subpackage Cache + * @addtogroup Cache */ class FakeMemCachedClient { function add ($key, $val, $exp = 0) { return true; } diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php new file mode 100644 index 00000000..d7e7c90f --- /dev/null +++ b/includes/OutputHandler.php @@ -0,0 +1,64 @@ +<?php + +/** + * Standard output handler for use with ob_start + */ +function wfOutputHandler( $s ) { + global $wgDisableOutputCompression; + $s = wfMangleFlashPolicy( $s ); + if ( !$wgDisableOutputCompression && !ini_get( 'zlib.output_compression' ) ) { + if ( !defined( 'MW_NO_OUTPUT_COMPRESSION' ) ) { + $s = wfGzipHandler( $s ); + } + if ( !ini_get( 'output_handler' ) ) { + wfDoContentLength( strlen( $s ) ); + } + } + return $s; +} + +/** + * Handler that compresses data with gzip if allowed by the Accept header. + * Unlike ob_gzhandler, it works for HEAD requests too. + */ +function wfGzipHandler( $s ) { + if ( function_exists( 'gzencode' ) && !headers_sent() ) { + $tokens = preg_split( '/[,; ]/', $_SERVER['HTTP_ACCEPT_ENCODING'] ); + if ( in_array( 'gzip', $tokens ) ) { + header( 'Content-Encoding: gzip' ); + $s = gzencode( $s, 3 ); + + # Set vary header if it hasn't been set already + $headers = headers_list(); + $foundVary = false; + foreach ( $headers as $header ) { + if ( substr( $header, 0, 5 ) == 'Vary:' ) { + $foundVary = true; + break; + } + } + if ( !$foundVary ) { + header( 'Vary: Accept-Encoding' ); + } + } + } + return $s; +} + +/** + * Mangle flash policy tags which open up the site to XSS attacks. + */ +function wfMangleFlashPolicy( $s ) { + return preg_replace( '/\<\s*cross-domain-policy\s*\>/i', '<NOT-cross-domain-policy>', $s ); +} + +/** + * Add a Content-Length header if possible. This makes it cooperate with squid better. + */ +function wfDoContentLength( $length ) { + if ( !headers_sent() && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' ) { + header( "Content-Length: $length" ); + } +} + +?> diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 6d3cc0ac..03e832a4 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2,12 +2,10 @@ if ( ! defined( 'MEDIAWIKI' ) ) die( 1 ); /** - * @package MediaWiki */ /** * @todo document - * @package MediaWiki */ class OutputPage { var $mMetatags, $mKeywords; @@ -34,7 +32,7 @@ class OutputPage { * Constructor * Initialise private variables */ - function OutputPage() { + function __construct() { $this->mMetatags = $this->mKeywords = $this->mLinktags = array(); $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext = $this->mRedirect = $this->mLastModified = @@ -49,6 +47,7 @@ class OutputPage { $this->mParserOptions = null; $this->mSquidMaxage = 0; $this->mScripts = ''; + $this->mHeadItems = array(); $this->mETag = false; $this->mRevisionId = null; $this->mNewSectionLink = false; @@ -71,7 +70,7 @@ class OutputPage { # To add an http-equiv meta tag, precede the name with "http:" function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); } function addKeyword( $text ) { array_push( $this->mKeywords, $text ); } - function addScript( $script ) { $this->mScripts .= $script; } + function addScript( $script ) { $this->mScripts .= "\t\t".$script; } /** * Add a self-contained script tag with the given contents @@ -79,10 +78,24 @@ class OutputPage { */ function addInlineScript( $script ) { global $wgJsMimeType; - $this->mScripts .= "<script type=\"$wgJsMimeType\"><!--\n$script\n--></script>"; + $this->mScripts .= "<script type=\"$wgJsMimeType\">/*<![CDATA[*/\n$script\n/*]]>*/</script>"; } - function getScript() { return $this->mScripts; } + function getScript() { + return $this->mScripts . $this->getHeadItems(); + } + + function getHeadItems() { + $s = ''; + foreach ( $this->mHeadItems as $item ) { + $s .= $item; + } + return $s; + } + + function addHeadItem( $name, $value ) { + $this->mHeadItems[$name] = $value; + } function setETag($tag) { $this->mETag = $tag; } function setArticleBodyOnly($only) { $this->mArticleBodyOnly = $only; } @@ -254,7 +267,7 @@ class OutputPage { $lb->setArray( $arr ); $lb->execute(); - $sk =& $wgUser->getSkin(); + $sk = $wgUser->getSkin(); foreach ( $categories as $category => $unused ) { $title = Title::makeTitleSafe( NS_CATEGORY, $category ); $text = $wgContLang->convertHtml( $title->getText() ); @@ -315,14 +328,26 @@ class OutputPage { $this->addWikiTextTitle($text, $title, $linestart); } - private function addWikiTextTitle($text, &$title, $linestart) { + function addWikiTextTitleTidy($text, &$title, $linestart = true) { + $this->addWikiTextTitle( $text, $title, $linestart, true ); + } + + public function addWikiTextTitle($text, &$title, $linestart, $tidy = false) { global $wgParser; + $fname = 'OutputPage:addWikiTextTitle'; wfProfileIn($fname); + wfIncrStats('pcache_not_possible'); - $parserOutput = $wgParser->parse( $text, $title, $this->parserOptions(), + + $popts = $this->parserOptions(); + $popts->setTidy($tidy); + + $parserOutput = $wgParser->parse( $text, $title, $popts, $linestart, true, $this->mRevisionId ); + $this->addParserOutput( $parserOutput ); + wfProfileOut($fname); } @@ -345,6 +370,7 @@ class OutputPage { $this->mSubtitle .= $parserOutput->mSubtitle ; } $this->mNoGallery = $parserOutput->getNoGallery(); + $this->mHeadItems = array_merge( $this->mHeadItems, (array)$parserOutput->mHeadItems ); wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); } @@ -366,6 +392,7 @@ class OutputPage { * @param string $text * @param Article $article * @param bool $cache + * @deprecated Use Article::outputWikitext */ public function addPrimaryWikiText( $text, $article, $cache = true ) { global $wgParser, $wgUser; @@ -384,17 +411,19 @@ class OutputPage { } /** - * For anything that isn't primary text or interface message - * - * @param string $text - * @param bool $linestart Is this the start of a line? + * @deprecated use addWikiTextTidy() */ public function addSecondaryWikiText( $text, $linestart = true ) { global $wgTitle; - $popts = $this->parserOptions(); - $popts->setTidy(true); - $this->addWikiTextTitle($text, $wgTitle, $linestart); - $popts->setTidy(false); + $this->addWikiTextTitleTidy($text, $wgTitle, $linestart); + } + + /** + * Add wikitext with tidy enabled + */ + public function addWikiTextTidy( $text, $linestart = true ) { + global $wgTitle; + $this->addWikiTextTitleTidy($text, $wgTitle, $linestart); } @@ -476,7 +505,7 @@ class OutputPage { # maintain different caches for logged-in users and non-logged in ones $wgRequest->response()->header( 'Vary: Accept-Encoding, Cookie' ); if( !$this->uncacheableBecauseRequestvars() && $this->mEnableClientCache ) { - if( $wgUseSquid && ! isset( $_COOKIE[ini_get( 'session.name') ] ) && + if( $wgUseSquid && session_id() == '' && ! $this->isPrintable() && $this->mSquidMaxage != 0 ) { if ( $wgUseESI ) { @@ -536,13 +565,16 @@ class OutputPage { if ( $wgUseAjax ) { $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajax.js?$wgStyleVersion\"></script>\n" ); + + wfRunHooks( 'AjaxAddScript', array( &$this ) ); + if( $wgAjaxSearch ) { - $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js\"></script>\n" ); + $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxsearch.js?$wgStyleVersion\"></script>\n" ); $this->addScript( "<script type=\"{$wgJsMimeType}\">hookEvent(\"load\", sajax_onload);</script>\n" ); } if( $wgAjaxWatch && $wgUser->isLoggedIn() ) { - $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js\"></script>\n" ); + $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/ajaxwatch.js?$wgStyleVersion\"></script>\n" ); } } @@ -750,7 +782,7 @@ class OutputPage { $this->returnToMain( false ); } - /** @obsolete */ + /** @deprecated */ public function errorpage( $title, $msg ) { throw new ErrorPageError( $title, $msg ); } @@ -792,10 +824,10 @@ class OutputPage { $groupName = User::getGroupName( $key ); $groupPage = User::getGroupPage( $key ); if( $groupPage ) { - $skin =& $wgUser->getSkin(); - $groups[] = '"'.$skin->makeLinkObj( $groupPage, $groupName ).'"'; + $skin = $wgUser->getSkin(); + $groups[] = $skin->makeLinkObj( $groupPage, $groupName ); } else { - $groups[] = '"'.$groupName.'"'; + $groups[] = $groupName; } } } @@ -860,7 +892,7 @@ class OutputPage { $this->returnToMain( true, $mainPage ); } - /** @obsolete */ + /** @deprecated */ public function databaseError( $fname, $sql, $error, $errno ) { throw new MWException( "OutputPage::databaseError is obsolete\n" ); } @@ -881,10 +913,22 @@ class OutputPage { $this->setPageTitle( wfMsg( 'viewsource' ) ); $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) ); + list( $cascadeSources, $restrictions ) = $wgTitle->getCascadeProtectionSources(); + # Determine if protection is due to the page being a system message # and show an appropriate explanation - if( $wgTitle->getNamespace() == NS_MEDIAWIKI && !$wgUser->isAllowed( 'editinterface' ) ) { + if( $wgTitle->getNamespace() == NS_MEDIAWIKI ) { $this->addWikiText( wfMsg( 'protectedinterface' ) ); + } if ( $cascadeSources && count($cascadeSources) > 0 ) { + $titles = ''; + + foreach ( $cascadeSources as $title ) { + $titles .= '* [[:' . $title->getPrefixedText() . "]]\n"; + } + + $notice = wfMsgExt( 'cascadeprotected', array('parsemag'), count($cascadeSources) ) . "\n$titles"; + + $this->addWikiText( $notice ); } else { $this->addWikiText( wfMsg( 'protectedpagetext' ) ); } @@ -900,17 +944,8 @@ class OutputPage { if( is_string( $source ) ) { $this->addWikiText( wfMsg( 'viewsourcetext' ) ); - if( $source === '' ) { - global $wgTitle; - if ( $wgTitle->getNamespace() == NS_MEDIAWIKI ) { - $source = wfMsgWeirdKey ( $wgTitle->getText() ); - } else { - $source = ''; - } - } $rows = $wgUser->getIntOption( 'rows' ); $cols = $wgUser->getIntOption( 'cols' ); - $text = "\n<textarea name='wpTextbox1' id='wpTextbox1' cols='$cols' rows='$rows' readonly='readonly'>" . htmlspecialchars( $source ) . "\n</textarea>"; $this->addHTML( $text ); @@ -921,32 +956,32 @@ class OutputPage { $this->returnToMain( false ); } - /** @obsolete */ + /** @deprecated */ public function fatalError( $message ) { throw new FatalError( $message ); } - /** @obsolete */ + /** @deprecated */ public function unexpectedValueError( $name, $val ) { throw new FatalError( wfMsg( 'unexpected', $name, $val ) ); } - /** @obsolete */ + /** @deprecated */ public function fileCopyError( $old, $new ) { throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) ); } - /** @obsolete */ + /** @deprecated */ public function fileRenameError( $old, $new ) { throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) ); } - /** @obsolete */ + /** @deprecated */ public function fileDeleteError( $name ) { throw new FatalError( wfMsg( 'filedeleteerror', $name ) ); } - /** @obsolete */ + /** @deprecated */ public function fileNotFoundError( $name ) { throw new FatalError( wfMsg( 'filenotfound', $name ) ); } @@ -1082,6 +1117,7 @@ class OutputPage { $ret .= $sk->getHeadScripts(); $ret .= $this->mScripts; $ret .= $sk->getUserStyles(); + $ret .= $this->getHeadItems(); if ($wgUseTrackbacks && $this->isArticleRelated()) $ret .= $wgTitle->trackbackRDF(); @@ -1118,11 +1154,11 @@ class OutputPage { "/<.*?>/" => '', "/_/" => ' ' ); - $ret .= "<meta name=\"keywords\" content=\"" . + $ret .= "\t\t<meta name=\"keywords\" content=\"" . htmlspecialchars(preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ))) . "\" />\n"; } foreach ( $this->mLinktags as $tag ) { - $ret .= '<link'; + $ret .= "\t\t<link"; foreach( $tag as $attr => $val ) { $ret .= " $attr=\"" . htmlspecialchars( $val ) . "\""; } diff --git a/includes/PageHistory.php b/includes/PageHistory.php index aea0f0ed..b1cf41f0 100644 --- a/includes/PageHistory.php +++ b/includes/PageHistory.php @@ -3,7 +3,6 @@ * Page history * * Split off from Article.php and Skin.php, 2003-12-22 - * @package MediaWiki */ /** @@ -14,9 +13,7 @@ * Construct it by passing in an Article, and call $h->history() to print the * history. * - * @package MediaWiki */ - class PageHistory { const DIR_PREV = 0; const DIR_NEXT = 1; @@ -33,7 +30,7 @@ class PageHistory { * @param Article $article * @returns nothing */ - function PageHistory($article) { + function __construct($article) { global $wgUser; $this->mArticle =& $article; @@ -101,6 +98,8 @@ class PageHistory { $wgOut->redirect( $wgTitle->getLocalURL( "action=history&limit={$limit}&dir=prev" ) ); return; } + + wfRunHooks( 'PageHistoryBeforeList', array( &$this->mArticle ) ); /** * Do the list @@ -159,7 +158,7 @@ class PageHistory { 'class' => 'historysubmit', 'type' => 'submit', 'accesskey' => wfMsg( 'accesskey-compareselectedversions' ), - 'title' => wfMsg( 'tooltip-compareselectedversions' ), + 'title' => wfMsg( 'tooltip-compareselectedversions' ).' ['.wfMsg( 'accesskey-compareselectedversions' ).']', 'value' => wfMsg( 'compareselectedversions' ), ) ) ) : ''; @@ -179,7 +178,7 @@ class PageHistory { * @return string HTML output for the row */ function historyLine( $row, $next, $counter = '', $notificationtimestamp = false, $latest = false, $firstInList = false ) { - global $wgUser; + global $wgUser, $wgLang; $rev = new Revision( $row ); $rev->setTitle( $this->mTitle ); @@ -199,31 +198,58 @@ class PageHistory { if( $firstInList ) { // We don't currently handle well changing the top revision's settings $del = wfMsgHtml( 'rev-delundel' ); + } else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { + // If revision was hidden from sysops + $del = wfMsgHtml( 'rev-delundel' ); } else { $del = $this->mSkin->makeKnownLinkObj( $revdel, wfMsg( 'rev-delundel' ), 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) . '&oldid=' . urlencode( $rev->getId() ) ); } - $s .= "(<small>$del</small>) "; + $s .= " (<small>$del</small>) "; } - $s .= " $link <span class='history-user'>$user</span>"; + $s .= " $link"; + #getUser is safe, but this avoids making the invalid untargeted contribs links + if( $row->rev_deleted & Revision::DELETED_USER ) { + $user = '<span class="history-deleted">' . wfMsg('rev-deleted-user') . '</span>'; + } + $s .= " <span class='history-user'>$user</span>"; if( $row->rev_minor_edit ) { $s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') ); } - $s .= $this->mSkin->revComment( $rev ); + if (!is_null($size = $rev->getSize())) { + if ($size == 0) + $stxt = wfMsgHtml('historyempty'); + else + $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); + $s .= " <span class=\"history-size\">$stxt</span>"; + } + + #getComment is safe, but this is better formatted + if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) { + $s .= " <span class=\"history-deleted\"><span class=\"comment\">" . + wfMsgHtml( 'rev-deleted-comment' ) . "</span></span>"; + } else { + $s .= $this->mSkin->revComment( $rev ); + } + if ($notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp)) { $s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>'; } + #add blurb about text having been deleted if( $row->rev_deleted & Revision::DELETED_TEXT ) { $s .= ' ' . wfMsgHtml( 'deletedrev' ); } if( $wgUser->isAllowed( 'rollback' ) && $latest ) { $s .= ' '.$this->mSkin->generateRollback( $rev ); } + + wfRunHooks( 'PageHistoryLineEnding', array( &$row , &$s ) ); + $s .= "</li>\n"; return $s; @@ -332,7 +358,7 @@ class PageHistory { function getLatestId() { if( is_null( $this->mLatestId ) ) { $id = $this->mTitle->getArticleID(); - $db =& wfGetDB(DB_SLAVE); + $db = wfGetDB(DB_SLAVE); $this->mLatestId = $db->selectField( 'page', "page_latest", array( 'page_id' => $id ), @@ -349,7 +375,7 @@ class PageHistory { function fetchRevisions($limit, $offset, $direction) { $fname = 'PageHistory::fetchRevisions'; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); if ($direction == PageHistory::DIR_PREV) list($dirs, $oper) = array("ASC", ">="); @@ -365,8 +391,7 @@ class PageHistory { $res = $dbr->select( 'revision', - array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text', - 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'), + Revision::selectFields(), array_merge(array("rev_page=$page_id"), $offsets), $fname, array('ORDER BY' => "rev_timestamp $dirs", @@ -391,7 +416,7 @@ class PageHistory { if ($wgUser->isAnon() || !$wgShowUpdatedMarker) return $this->mNotificationTimestamp = false; - $dbr =& wfGetDB(DB_SLAVE); + $dbr = wfGetDB(DB_SLAVE); $this->mNotificationTimestamp = $dbr->selectField( 'watchlist', @@ -497,6 +522,9 @@ class PageHistory { } +/** + * @addtogroup Pager + */ class PageHistoryPager extends ReverseChronologicalPager { public $mLastRow = false, $mPageHistory; @@ -508,8 +536,7 @@ class PageHistoryPager extends ReverseChronologicalPager { function getQueryInfo() { return array( 'tables' => 'revision', - 'fields' => array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text', - 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'), + 'fields' => Revision::selectFields(), 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ), 'options' => array( 'USE INDEX' => 'page_timestamp' ) ); diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php new file mode 100644 index 00000000..5b82ebf6 --- /dev/null +++ b/includes/PageQueryPage.php @@ -0,0 +1,26 @@ +<?php + +/** + * Variant of QueryPage which formats the result as a simple link to the page + * + * @package MediaWiki + * @addtogroup SpecialPage + */ +class PageQueryPage extends QueryPage { + + /** + * Format the result as a simple link to the page + * + * @param Skin $skin + * @param object $row Result row + * @return string + */ + public function formatResult( $skin, $row ) { + global $wgContLang; + $title = Title::makeTitleSafe( $row->namespace, $row->title ); + return $skin->makeKnownLinkObj( $title, + htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) ); + } +} + +?> diff --git a/includes/Pager.php b/includes/Pager.php index 0987cc06..a475dc16 100644 --- a/includes/Pager.php +++ b/includes/Pager.php @@ -2,6 +2,7 @@ /** * Basic pager interface. + * @addtogroup Pager */ interface Pager { function getNavigationBar(); @@ -46,6 +47,8 @@ interface Pager { * please see the examples in PageHistory.php and SpecialIpblocklist.php. You just need * to override formatRow(), getQueryInfo() and getIndexField(). Don't forget to call the * parent constructor if you override it. + * + * @addtogroup Pager */ abstract class IndexPager implements Pager { public $mRequest; @@ -69,17 +72,18 @@ abstract class IndexPager implements Pager { public $mResult; function __construct() { - global $wgRequest; + global $wgRequest, $wgUser; $this->mRequest = $wgRequest; - + # NB: the offset is quoted, not validated. It is treated as an arbitrary string # to support the widest variety of index types. Be careful outputting it into # HTML! $this->mOffset = $this->mRequest->getText( 'offset' ); - $this->mLimit = $this->mRequest->getInt( 'limit', $this->mDefaultLimit ); - if ( $this->mLimit <= 0 || $this->mLimit > 50000 ) { - $this->mLimit = $this->mDefaultLimit; - } + + # Use consistent behavior for the limit options + $this->mDefaultLimit = intval( $wgUser->getOption( 'rclimit' ) ); + list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset(); + $this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' ); $this->mIndexField = $this->getIndexField(); $this->mDb = wfGetDB( DB_SLAVE ); @@ -386,8 +390,45 @@ abstract class IndexPager implements Pager { abstract function getIndexField(); } + +/** + * IndexPager with an alphabetic list and a formatted navigation bar + * @addtogroup Pager + */ +abstract class AlphabeticPager extends IndexPager { + public $mDefaultDirection = false; + + function __construct() { + parent::__construct(); + } + + /** + * Shamelessly stolen bits from ReverseChronologicalPager, d + * didn't want to do class magic as may be still revamped + */ + function getNavigationBar() { + global $wgLang; + + $linkTexts = array( + 'prev' => wfMsgHtml( "prevn", $this->mLimit ), + 'next' => wfMsgHtml( 'nextn', $this->mLimit ), + 'first' => wfMsgHtml('page_first'), /* Introduced the message */ + 'last' => wfMsgHtml( 'page_last' ) /* Introduced the message */ + ); + + $pagingLinks = $this->getPagingLinks( $linkTexts ); + $limitLinks = $this->getLimitLinks(); + $limits = implode( ' | ', $limitLinks ); + + $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits); + return $this->mNavigationBar; + + } +} + /** * IndexPager with a formatted navigation bar + * @addtogroup Pager */ abstract class ReverseChronologicalPager extends IndexPager { public $mDefaultDirection = true; @@ -413,13 +454,15 @@ abstract class ReverseChronologicalPager extends IndexPager { $limitLinks = $this->getLimitLinks(); $limits = implode( ' | ', $limitLinks ); - $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits); + $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . + wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits); return $this->mNavigationBar; } } /** * Table-based display with a user-selectable sort order + * @addtogroup Pager */ abstract class TablePager extends IndexPager { var $mSort; diff --git a/includes/Parser.php b/includes/Parser.php index 8d67279d..8e36e170 100644 --- a/includes/Parser.php +++ b/includes/Parser.php @@ -2,8 +2,7 @@ /** * File for Parser and related classes * - * @package MediaWiki - * @subpackage Parser + * @addtogroup Parser */ /** @@ -57,9 +56,10 @@ define( 'MW_COLON_STATE_COMMENTDASH', 6 ); define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 ); /** - * PHP Parser - * - * Processes wiki markup + * PHP Parser - Processes wiki markup (which uses a more user-friendly + * syntax, such as "[[link]]" for making links), and provides a one-way + * transformation of that wiki markup it into XHTML output / markup + * (which in turn the browser understands, and can display). * * <pre> * There are four main entry points into the Parser class: @@ -86,10 +86,11 @@ define( 'MW_COLON_STATE_COMMENTDASHDASH', 7 ); * * only within ParserOptions * </pre> * - * @package MediaWiki + * @addtogroup Parser */ class Parser { + const VERSION = MW_PARSER_VERSION; /**#@+ * @private */ @@ -114,7 +115,7 @@ class Parser $ot, // Shortcut alias, see setOutputType() $mRevisionId, // ID to display in {{REVISIONID}} tags $mRevisionTimestamp, // The timestamp of the specified revision ID - $mRevIdForTs; // The revision ID which was used to fetch the timestamp + $mRevIdForTs; // The revision ID which was used to fetch the timestamp /**#@-*/ @@ -162,6 +163,7 @@ class Parser $this->setFunctionHook( 'numberofarticles', array( 'CoreParserFunctions', 'numberofarticles' ), SFH_NO_HASH ); $this->setFunctionHook( 'numberoffiles', array( 'CoreParserFunctions', 'numberoffiles' ), SFH_NO_HASH ); $this->setFunctionHook( 'numberofadmins', array( 'CoreParserFunctions', 'numberofadmins' ), SFH_NO_HASH ); + $this->setFunctionHook( 'numberofedits', array( 'CoreParserFunctions', 'numberofedits' ), SFH_NO_HASH ); $this->setFunctionHook( 'language', array( 'CoreParserFunctions', 'language' ), SFH_NO_HASH ); $this->setFunctionHook( 'padleft', array( 'CoreParserFunctions', 'padleft' ), SFH_NO_HASH ); $this->setFunctionHook( 'padright', array( 'CoreParserFunctions', 'padright' ), SFH_NO_HASH ); @@ -211,7 +213,7 @@ class Parser 'titles' => array() ); $this->mRevisionTimestamp = $this->mRevisionId = null; - + /** * Prefix for temporary replacement strings for the multipass parser. * \x07 should never appear in input as it's disallowed in XML. @@ -262,7 +264,6 @@ class Parser * Convert wikitext to HTML * Do not call this function recursively. * - * @private * @param string $text Text we want to parse * @param Title &$title A title object * @param array $options @@ -271,7 +272,7 @@ class Parser * @param int $revid number to pass in {{REVISIONID}} * @return ParserOutput a ParserOutput */ - function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) { + public function parse( $text, &$title, $options, $linestart = true, $clearState = true, $revid = null ) { /** * First pass--just handle <nowiki> sections, pass the rest off * to internalParse() which does all the real work. @@ -724,7 +725,7 @@ class Parser $descriptorspec = array( 0 => array('pipe', 'r'), 1 => array('pipe', 'w'), - 2 => array('file', '/dev/null', 'a') + 2 => array('file', '/dev/null', 'a') // FIXME: this line in UNIX-specific, it generates a warning on Windows, because /dev/null is not a valid Windows file. ); $pipes = array(); $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes); @@ -872,7 +873,7 @@ class Parser array_push ( $td_history , false ); array_push ( $last_tag_history , '' ); } - else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) { + else if ( $first_character == '|' || $first_character == '!' || substr ( $line , 0 , 2 ) == '|+' ) { // This might be cell elements, td, th or captions if ( substr ( $line , 0 , 2 ) == '|+' ) { $first_character = '+'; @@ -1002,6 +1003,7 @@ class Parser $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ) ); $text = $this->replaceVariables( $text, $args ); + wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) ); // Tables need to come after variable replacement for things to work // properly; putting them before other transformations should keep @@ -1086,7 +1088,7 @@ class Parser } $url = wfMsg( $urlmsg, $id); - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); $la = $sk->getExternalLinkAttributes( $url, $keyword.$id ); $text = "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>"; } @@ -1287,7 +1289,8 @@ class Parser $output .= '</i>'; if ($state == 'bi') $output .= '</b>'; - if ($state == 'both') + # There might be lonely ''''', so make sure we have a buffer + if ($state == 'both' && $buffer) $output .= '<b><i>'.$buffer.'</i></b>'; return $output; } @@ -1306,7 +1309,7 @@ class Parser $fname = 'Parser::replaceExternalLinks'; wfProfileIn( $fname ); - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); $bits = preg_split( EXT_LINK_BRACKETED, $text, -1, PREG_SPLIT_DELIM_CAPTURE ); @@ -1395,7 +1398,7 @@ class Parser $s = array_shift( $bits ); $i = 0; - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); while ( $i < count( $bits ) ){ $protocol = $bits[$i++]; @@ -1468,7 +1471,7 @@ class Parser * @param string * @return string * @static - * @fixme This can merge genuinely required bits in the path or query string, + * @todo This can merge genuinely required bits in the path or query string, * breaking legit URLs. A proper fix would treat the various parts of * the URL differently; as a workaround, just use the output for * statistical records, not for actual linking/output. @@ -1503,7 +1506,7 @@ class Parser * @private */ function maybeMakeExternalImage( $url ) { - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); $imagesfrom = $this->mOptions->getAllowExternalImagesFrom(); $imagesexception = !empty($imagesfrom); $text = false; @@ -1533,7 +1536,7 @@ class Parser # the % is needed to support urlencoded titles as well if ( !$tc ) { $tc = Title::legalChars() . '#%'; } - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); #split the entire text string on occurences of [[ $a = explode( '[[', ' ' . $s ); @@ -1552,7 +1555,6 @@ class Parser $e2 = wfMsgForContent( 'linkprefix' ); $useLinkPrefixExtension = $wgContLang->linkPrefixExtension(); - if( is_null( $this->mTitle ) ) { throw new MWException( __METHOD__.": \$this->mTitle is null\n" ); } @@ -1569,10 +1571,11 @@ class Parser $prefix = ''; } - if($wgContLang->hasVariants()) + if($wgContLang->hasVariants()) { $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText()); - else + } else { $selflink = array($this->mTitle->getPrefixedText()); + } $useSubpages = $this->areSubpagesAllowed(); wfProfileOut( $fname.'-setup' ); @@ -1626,7 +1629,7 @@ class Parser $might_be_img = true; $text = $m[2]; if ( strpos( $m[1], '%' ) !== false ) { - $m[1] = urldecode($m[1]); + $m[1] = urldecode($m[1]); } $trail = ""; } else { # Invalid form; output directly @@ -1640,7 +1643,7 @@ class Parser # Don't allow internal links to pages containing # PROTO: where PROTO is a valid URL protocol; these # should be external links. - if (preg_match('/^(\b(?:' . wfUrlProtocols() . '))/', $m[1])) { + if (preg_match('/^\b(?:' . wfUrlProtocols() . ')/', $m[1])) { $s .= $prefix . '[[' . $line ; continue; } @@ -1723,8 +1726,8 @@ class Parser wfProfileIn( "$fname-interwiki" ); if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) { $this->mOutput->addLanguageLink( $nt->getFullText() ); - $s = rtrim($s . "\n"); - $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail; + $s = rtrim($s . $prefix); + $s .= trim($trail, "\n") == '' ? '': $prefix . $trail; wfProfileOut( "$fname-interwiki" ); continue; } @@ -1778,11 +1781,12 @@ class Parser } } - if( ( in_array( $nt->getPrefixedText(), $selflink ) ) && - ( $nt->getFragment() === '' ) ) { - # Self-links are handled specially; generally de-link and change to bold. - $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail ); - continue; + # Self-link checking + if( $nt->getFragment() === '' ) { + if( in_array( $nt->getPrefixedText(), $selflink, true ) ) { + $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail ); + continue; + } } # Special and Media are pseudo-namespaces; no pages actually exist in them @@ -1862,7 +1866,7 @@ class Parser */ function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { list( $inside, $trail ) = Linker::splitTrail( $trail ); - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); $link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix ); return $this->armorLinks( $link ) . $trail; } @@ -1923,9 +1927,9 @@ class Parser # Look at the first character if( $target != '' && $target{0} == '/' ) { # / at end means we don't want the slash to be shown - if( substr( $target, -1, 1 ) == '/' ) { - $target = substr( $target, 1, -1 ); - $noslash = $target; + $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m ); + if( $trailingSlashes ) { + $noslash = $target = substr( $target, 1, -strlen($m[0][0]) ); } else { $noslash = substr( $target, 1 ); } @@ -2134,9 +2138,9 @@ class Parser wfProfileIn( "$fname-paragraph" ); # No prefix (not in list)--go to paragraph mode // XXX: use a stack for nestable elements like span, table and div - $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t ); + $openmatch = preg_match('/(?:<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/tr|<\\/td|<\\/th)/iS', $t ); $closematch = preg_match( - '/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'. + '/(?:<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'. '<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t ); if ( $openmatch or $closematch ) { $paragraphStack = false; @@ -2538,6 +2542,8 @@ class Parser return $varCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); case 'numberofadmins': return $varCache[$index] = $wgContLang->formatNum( SiteStats::admins() ); + case 'numberofedits': + return $varCache[$index] = $wgContLang->formatNum( SiteStats::edits() ); case 'currenttimestamp': return $varCache[$index] = wfTimestampNow(); case 'localtimestamp': @@ -2852,7 +2858,7 @@ class Parser return $text; } - + /// Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. static function createAssocArgs( $args ) { $assocArgs = array(); @@ -2872,10 +2878,10 @@ class Parser } } } - + return $assocArgs; } - + /** * Return the text of a template, after recursively * replacing any variables or templates within the template. @@ -2888,7 +2894,7 @@ class Parser * @private */ function braceSubstitution( $piece ) { - global $wgContLang, $wgLang, $wgAllowDisplayTitle; + global $wgContLang, $wgLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces; $fname = __METHOD__ /*. '-L' . count( $this->mArgStack )*/; wfProfileIn( $fname ); wfProfileIn( __METHOD__.'-setup' ); @@ -3031,6 +3037,19 @@ class Parser } else { # set $text to cached message. $text = $linestart . $this->mTemplates[$piece['title']]; + #treat title for cached page the same as others + $ns = NS_TEMPLATE; + $subpage = ''; + $part1 = $this->maybeDoSubpageLink( $part1, $subpage ); + if ($subpage !== '') { + $ns = $this->mTitle->getNamespace(); + } + $title = Title::newFromText( $part1, $ns ); + //used by include size checking + $titleText = $title->getPrefixedText(); + //used by edit section links + $replaceHeadings = true; + } } @@ -3066,6 +3085,9 @@ class Parser $isHTML = true; $this->disableCache(); } + } else if ( $wgNonincludableNamespaces && in_array( $title->getNamespace(), $wgNonincludableNamespaces ) ) { + $found = false; //access denied + wfDebug( "$fname: template inclusion denied for " . $title->getPrefixedDBkey() ); } else { $articleContent = $this->fetchTemplate( $title ); if ( $articleContent !== false ) { @@ -3155,7 +3177,7 @@ class Parser # If the template begins with a table or block-level # element, it should be treated as beginning a new line. - if (!$piece['lineStart'] && preg_match('/^({\\||:|;|#|\*)/', $text)) /*}*/{ + if (!$piece['lineStart'] && preg_match('/^(?:{\\||:|;|#|\*)/', $text)) /*}*/{ $text = "\n" . $text; } } elseif ( !$noargs ) { @@ -3271,7 +3293,7 @@ class Parser return wfMsg('scarytranscludedisabled'); $url = $title->getFullUrl( "action=$action" ); - + if (strlen($url) > 255) return wfMsg('scarytranscludetoolong'); return $this->fetchScaryTemplateMaybeFromCache($url); @@ -3279,7 +3301,7 @@ class Parser function fetchScaryTemplateMaybeFromCache($url) { global $wgTranscludeCacheExpiry; - $dbr =& wfGetDB(DB_SLAVE); + $dbr = wfGetDB(DB_SLAVE); $obj = $dbr->selectRow('transcache', array('tc_time', 'tc_contents'), array('tc_url' => $url)); if ($obj) { @@ -3294,7 +3316,7 @@ class Parser if (!$text) return wfMsg('scarytranscludefailed', $url); - $dbw =& wfGetDB(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); $dbw->replace('transcache', array('tc_url'), array( 'tc_url' => $url, 'tc_time' => time(), @@ -3395,7 +3417,7 @@ class Parser global $wgMaxTocLevel, $wgContLang; $doNumberHeadings = $this->mOptions->getNumberHeadings(); - if( !$this->mTitle->userCanEdit() ) { + if( !$this->mTitle->quickUserCan( 'edit' ) ) { $showEditLink = 0; } else { $showEditLink = $this->mOptions->getEditSection(); @@ -3437,7 +3459,7 @@ class Parser } # We need this to perform operations on the HTML - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); # headline counter $headlineCount = 0; @@ -3723,11 +3745,7 @@ class Parser } # Trim trailing whitespace - # __END__ tag allows for trailing - # whitespace to be deliberately included $text = rtrim( $text ); - $mw =& MagicWord::get( 'end' ); - $mw->matchAndRemove( $text ); return $text; } @@ -3847,7 +3865,7 @@ class Parser wfProfileIn($fname); - if ( $wgTitle ) { + if ( $wgTitle && !( $wgTitle instanceof FakeTitle ) ) { $this->mTitle = $wgTitle; } else { $this->mTitle = Title::newFromText('msg'); @@ -3966,12 +3984,12 @@ class Parser $pdbks = array(); $colours = array(); - $sk =& $this->mOptions->getSkin(); + $sk = $this->mOptions->getSkin(); $linkCache =& LinkCache::singleton(); if ( !empty( $this->mLinkHolders['namespaces'] ) ) { wfProfileIn( $fname.'-check' ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); $threshold = $wgUser->getOption('stubthreshold'); @@ -4161,8 +4179,8 @@ class Parser if(isset($categoryMap[$vardbk])){ $oldkey = $categoryMap[$vardbk]; if($oldkey != $vardbk) - $varCategories[$oldkey]=$vardbk; - } + $varCategories[$oldkey]=$vardbk; + } } // rebuild the categories in original order (if there are replacements) @@ -4303,6 +4321,7 @@ class Parser */ function renderImageGallery( $text, $params ) { $ig = new ImageGallery(); + $ig->setContextTitle( $this->mTitle ); $ig->setShowBytes( false ); $ig->setShowFilename( false ); $ig->setParsing(); @@ -4314,6 +4333,15 @@ class Parser $caption = $this->replaceInternalLinks( $caption ); $ig->setCaptionHtml( $caption ); } + if( isset( $params['perrow'] ) ) { + $ig->setPerRow( $params['perrow'] ); + } + if( isset( $params['widths'] ) ) { + $ig->setWidths( $params['widths'] ); + } + if( isset( $params['heights'] ) ) { + $ig->setHeights( $params['heights'] ); + } $lines = explode( "\n", $text ); foreach ( $lines as $line ) { @@ -4359,10 +4387,8 @@ class Parser * Parse image options text and use it to make an image */ function makeImage( $nt, $options ) { - global $wgUseImageResize, $wgDjvuRenderer; - - $align = ''; - + # @TODO: let the MediaHandler specify its transform parameters + # # Check if the options text is of the form "options|alt text" # Options are: # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang @@ -4372,61 +4398,74 @@ class Parser # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox # * center center the image # * framed Keep original image size, no magnify-button. - - $part = explode( '|', $options); - + # vertical-align values (no % or length right now): + # * baseline + # * sub + # * super + # * top + # * text-top + # * middle + # * bottom + # * text-bottom + + + $part = array_map( 'trim', explode( '|', $options) ); + + $mwAlign = array(); + $alignments = array( 'left', 'right', 'center', 'none', 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom' ); + foreach ( $alignments as $alignment ) { + $mwAlign[$alignment] =& MagicWord::get( 'img_'.$alignment ); + } $mwThumb =& MagicWord::get( 'img_thumbnail' ); $mwManualThumb =& MagicWord::get( 'img_manualthumb' ); - $mwLeft =& MagicWord::get( 'img_left' ); - $mwRight =& MagicWord::get( 'img_right' ); - $mwNone =& MagicWord::get( 'img_none' ); $mwWidth =& MagicWord::get( 'img_width' ); - $mwCenter =& MagicWord::get( 'img_center' ); $mwFramed =& MagicWord::get( 'img_framed' ); $mwPage =& MagicWord::get( 'img_page' ); $caption = ''; - $width = $height = $framed = $thumb = false; - $page = null; + $params = array(); + $framed = $thumb = false; $manual_thumb = '' ; + $align = $valign = ''; + $sk = $this->mOptions->getSkin(); foreach( $part as $val ) { - if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) { + if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) { $thumb=true; } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) { # use manually specified thumbnail $thumb=true; $manual_thumb = $match; - } elseif ( ! is_null( $mwRight->matchVariableStartToEnd($val) ) ) { - # remember to set an alignment, don't render immediately - $align = 'right'; - } elseif ( ! is_null( $mwLeft->matchVariableStartToEnd($val) ) ) { - # remember to set an alignment, don't render immediately - $align = 'left'; - } elseif ( ! is_null( $mwCenter->matchVariableStartToEnd($val) ) ) { - # remember to set an alignment, don't render immediately - $align = 'center'; - } elseif ( ! is_null( $mwNone->matchVariableStartToEnd($val) ) ) { - # remember to set an alignment, don't render immediately - $align = 'none'; - } elseif ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer - && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) { - # Select a page in a multipage document - $page = $match; - } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) { - wfDebug( "img_width match: $match\n" ); - # $match is the image width in pixels - $m = array(); - if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) { - $width = intval( $m[1] ); - $height = intval( $m[2] ); + } else { + foreach( $alignments as $alignment ) { + if ( ! is_null( $mwAlign[$alignment]->matchVariableStartToEnd($val) ) ) { + switch ( $alignment ) { + case 'left': case 'right': case 'center': case 'none': + $align = $alignment; break; + default: + $valign = $alignment; + } + continue 2; + } + } + if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) { + # Select a page in a multipage document + $params['page'] = $match; + } elseif ( !isset( $params['width'] ) && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) { + wfDebug( "img_width match: $match\n" ); + # $match is the image width in pixels + $m = array(); + if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) { + $params['width'] = intval( $m[1] ); + $params['height'] = intval( $m[2] ); + } else { + $params['width'] = intval($match); + } + } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) { + $framed=true; } else { - $width = intval($match); + $caption = $val; } - } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) { - $framed=true; - } else { - $caption = $val; } } # Strip bad stuff out of the alt text @@ -4439,8 +4478,7 @@ class Parser $alt = Sanitizer::stripAllTags( $alt ); # Linker does the rest - $sk =& $this->mOptions->getSkin(); - return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page ); + return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign ); } /** @@ -4518,24 +4556,6 @@ class Parser $uniq = preg_quote( $this->uniqPrefix(), '/' ); $comment = "(?:$uniq-!--.*?QINU)"; $secs = preg_split( - /* - "/ - ^( - (?:$comment|<\/?noinclude>)* # Initial comments will be stripped - (?: - (=+) # Should this be limited to 6? - .+? # Section title... - \\2 # Ending = count must match start - | - ^ - <h([1-6])\b.*?> - .*? - <\/h\\3\s*> - ) - (?:$comment|<\/?noinclude>|\s+)* # Trailing whitespace ok - )$ - /mix", - */ "/ ( ^ @@ -4559,7 +4579,8 @@ class Parser // "Section 0" returns the content before any other section. $rv = $secs[0]; } else { - $rv = ""; + //track missing section, will replace if found. + $rv = $newtext; } } elseif( $mode == "replace" ) { if( $section == 0 ) { @@ -4614,8 +4635,10 @@ class Parser } } } - # reinsert stripped tags - $rv = trim( $stripState->unstripBoth( $rv ) ); + if (is_string($rv)) + # reinsert stripped tags + $rv = trim( $stripState->unstripBoth( $rv ) ); + return $rv; } @@ -4628,34 +4651,35 @@ class Parser * * @param $text String: text to look in * @param $section Integer: section number + * @param $deftext: default to return if section is not found * @return string text of the requested section */ - function getSection( $text, $section ) { - return $this->extractSections( $text, $section, "get" ); + public function getSection( $text, $section, $deftext='' ) { + return $this->extractSections( $text, $section, "get", $deftext ); } - function replaceSection( $oldtext, $section, $text ) { + public function replaceSection( $oldtext, $section, $text ) { return $this->extractSections( $oldtext, $section, "replace", $text ); } /** - * Get the timestamp associated with the current revision, adjusted for + * Get the timestamp associated with the current revision, adjusted for * the default server-local timestamp */ function getRevisionTimestamp() { if ( is_null( $this->mRevisionTimestamp ) ) { wfProfileIn( __METHOD__ ); global $wgContLang; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', array( 'rev_id' => $this->mRevisionId ), __METHOD__ ); - + // Normalize timestamp to internal MW format for timezone processing. // This has the added side-effect of replacing a null value with // the current time, which gives us more sensible behavior for // previews. $timestamp = wfTimestamp( TS_MW, $timestamp ); - + // The cryptic '' timezone parameter tells to use the site-default // timezone offset instead of the user settings. // @@ -4663,12 +4687,12 @@ class Parser // to other users, and potentially even used inside links and such, // it needs to be consistent for all visitors. $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' ); - + wfProfileOut( __METHOD__ ); } return $this->mRevisionTimestamp; } - + /** * Mutator for $mDefaultSort * @@ -4677,7 +4701,7 @@ class Parser public function setDefaultSort( $sort ) { $this->mDefaultSort = $sort; } - + /** * Accessor for $mDefaultSort * Will use the title/prefixed title if none is set @@ -4693,241 +4717,13 @@ class Parser : $this->mTitle->getPrefixedText(); } } - -} - -/** - * @todo document - * @package MediaWiki - */ -class ParserOutput -{ - var $mText, # The output text - $mLanguageLinks, # List of the full text of language links, in the order they appear - $mCategories, # Map of category names to sort keys - $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}} - $mCacheTime, # Time when this object was generated, or -1 for uncacheable. Used in ParserCache. - $mVersion, # Compatibility check - $mTitleText, # title text of the chosen language variant - $mLinks, # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken. - $mTemplates, # 2-D map of NS/DBK to ID for the template references. ID=zero for broken. - $mImages, # DB keys of the images used, in the array key only - $mExternalLinks, # External link URLs, in the key only - $mHTMLtitle, # Display HTML title - $mSubtitle, # Additional subtitle - $mNewSection, # Show a new section link? - $mNoGallery; # No gallery on category page? (__NOGALLERY__) - - function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(), - $containsOldMagic = false, $titletext = '' ) - { - $this->mText = $text; - $this->mLanguageLinks = $languageLinks; - $this->mCategories = $categoryLinks; - $this->mContainsOldMagic = $containsOldMagic; - $this->mCacheTime = ''; - $this->mVersion = MW_PARSER_VERSION; - $this->mTitleText = $titletext; - $this->mLinks = array(); - $this->mTemplates = array(); - $this->mImages = array(); - $this->mExternalLinks = array(); - $this->mHTMLtitle = "" ; - $this->mSubtitle = "" ; - $this->mNewSection = false; - $this->mNoGallery = false; - } - function getText() { return $this->mText; } - function &getLanguageLinks() { return $this->mLanguageLinks; } - function getCategoryLinks() { return array_keys( $this->mCategories ); } - function &getCategories() { return $this->mCategories; } - function getCacheTime() { return $this->mCacheTime; } - function getTitleText() { return $this->mTitleText; } - function &getLinks() { return $this->mLinks; } - function &getTemplates() { return $this->mTemplates; } - function &getImages() { return $this->mImages; } - function &getExternalLinks() { return $this->mExternalLinks; } - function getNoGallery() { return $this->mNoGallery; } - function getSubtitle() { return $this->mSubtitle; } - - function containsOldMagic() { return $this->mContainsOldMagic; } - function setText( $text ) { return wfSetVar( $this->mText, $text ); } - function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); } - function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); } - function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } - function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } - function setTitleText( $t ) { return wfSetVar($this->mTitleText, $t); } - function setSubtitle( $st ) { return wfSetVar( $this->mSubtitle, $st ); } - - function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; } - function addImage( $name ) { $this->mImages[$name] = 1; } - function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; } - function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; } - - function setNewSection( $value ) { - $this->mNewSection = (bool)$value; - } - function getNewSection() { - return (bool)$this->mNewSection; - } - - function addLink( $title, $id = null ) { - $ns = $title->getNamespace(); - $dbk = $title->getDBkey(); - if ( !isset( $this->mLinks[$ns] ) ) { - $this->mLinks[$ns] = array(); - } - if ( is_null( $id ) ) { - $id = $title->getArticleID(); - } - $this->mLinks[$ns][$dbk] = $id; - } - - function addTemplate( $title, $id ) { - $ns = $title->getNamespace(); - $dbk = $title->getDBkey(); - if ( !isset( $this->mTemplates[$ns] ) ) { - $this->mTemplates[$ns] = array(); - } - $this->mTemplates[$ns][$dbk] = $id; - } - - /** - * Return true if this cached output object predates the global or - * per-article cache invalidation timestamps, or if it comes from - * an incompatible older version. - * - * @param string $touched the affected article's last touched timestamp - * @return bool - * @public - */ - function expired( $touched ) { - global $wgCacheEpoch; - return $this->getCacheTime() == -1 || // parser says it's uncacheable - $this->getCacheTime() < $touched || - $this->getCacheTime() <= $wgCacheEpoch || - !isset( $this->mVersion ) || - version_compare( $this->mVersion, MW_PARSER_VERSION, "lt" ); - } } /** - * Set options of the Parser - * @todo document - * @package MediaWiki + * @todo document, briefly. + * @addtogroup Parser */ -class ParserOptions -{ - # All variables are supposed to be private in theory, although in practise this is not the case. - var $mUseTeX; # Use texvc to expand <math> tags - var $mUseDynamicDates; # Use DateFormatter to format dates - var $mInterwikiMagic; # Interlanguage links are removed and returned in an array - var $mAllowExternalImages; # Allow external images inline - var $mAllowExternalImagesFrom; # If not, any exception? - var $mSkin; # Reference to the preferred skin - var $mDateFormat; # Date format index - var $mEditSection; # Create "edit section" links - var $mNumberHeadings; # Automatically number headings - var $mAllowSpecialInclusion; # Allow inclusion of special pages - var $mTidy; # Ask for tidy cleanup - var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR - var $mMaxIncludeSize; # Maximum size of template expansions, in bytes - var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS - - var $mUser; # Stored user object, just used to initialise the skin - - function getUseTeX() { return $this->mUseTeX; } - function getUseDynamicDates() { return $this->mUseDynamicDates; } - function getInterwikiMagic() { return $this->mInterwikiMagic; } - function getAllowExternalImages() { return $this->mAllowExternalImages; } - function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; } - function getEditSection() { return $this->mEditSection; } - function getNumberHeadings() { return $this->mNumberHeadings; } - function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; } - function getTidy() { return $this->mTidy; } - function getInterfaceMessage() { return $this->mInterfaceMessage; } - function getMaxIncludeSize() { return $this->mMaxIncludeSize; } - function getRemoveComments() { return $this->mRemoveComments; } - - function &getSkin() { - if ( !isset( $this->mSkin ) ) { - $this->mSkin = $this->mUser->getSkin(); - } - return $this->mSkin; - } - - function getDateFormat() { - if ( !isset( $this->mDateFormat ) ) { - $this->mDateFormat = $this->mUser->getDatePreference(); - } - return $this->mDateFormat; - } - - function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); } - function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); } - function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); } - function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); } - function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); } - function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); } - function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); } - function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); } - function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); } - function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); } - function setSkin( $x ) { $this->mSkin = $x; } - function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); } - function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); } - function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } - - function ParserOptions( $user = null ) { - $this->initialiseFromUser( $user ); - } - - /** - * Get parser options - * @static - */ - static function newFromUser( $user ) { - return new ParserOptions( $user ); - } - - /** Get user options */ - function initialiseFromUser( $userInput ) { - global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages; - global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize; - $fname = 'ParserOptions::initialiseFromUser'; - wfProfileIn( $fname ); - if ( !$userInput ) { - global $wgUser; - if ( isset( $wgUser ) ) { - $user = $wgUser; - } else { - $user = new User; - } - } else { - $user =& $userInput; - } - - $this->mUser = $user; - - $this->mUseTeX = $wgUseTeX; - $this->mUseDynamicDates = $wgUseDynamicDates; - $this->mInterwikiMagic = $wgInterwikiMagic; - $this->mAllowExternalImages = $wgAllowExternalImages; - $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom; - $this->mSkin = null; # Deferred - $this->mDateFormat = null; # Deferred - $this->mEditSection = true; - $this->mNumberHeadings = $user->getOption( 'numberheadings' ); - $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion; - $this->mTidy = false; - $this->mInterfaceMessage = false; - $this->mMaxIncludeSize = $wgMaxArticleSize * 1024; - $this->mRemoveComments = true; - wfProfileOut( $fname ); - } -} - class OnlyIncludeReplacer { var $output = ''; @@ -4940,6 +4736,10 @@ class OnlyIncludeReplacer { } } +/** + * @todo document, briefly. + * @addtogroup Parser + */ class StripState { var $general, $nowiki; diff --git a/includes/ParserCache.php b/includes/ParserCache.php index 37a42b7f..1489fcf9 100644 --- a/includes/ParserCache.php +++ b/includes/ParserCache.php @@ -1,13 +1,8 @@ <?php /** * - * @package MediaWiki - * @subpackage Cache - */ - -/** - * - * @package MediaWiki + * @addtogroup Cache + * @todo document */ class ParserCache { /** @@ -28,14 +23,14 @@ class ParserCache { * * @param object $memCached */ - function ParserCache( &$memCached ) { + function __construct( &$memCached ) { $this->mMemc =& $memCached; } function getKey( &$article, &$user ) { global $action; $hash = $user->getPageRenderingHash(); - if( !$article->mTitle->userCanEdit() ) { + if( !$article->mTitle->quickUserCan( 'edit' ) ) { // section edit links are suppressed even if the user has them on $edit = '!edit=0'; } else { @@ -95,31 +90,30 @@ class ParserCache { function save( $parserOutput, &$article, &$user ){ global $wgParserCacheExpireTime; $key = $this->getKey( $article, $user ); - + if( $parserOutput->getCacheTime() != -1 ) { - + $now = wfTimestampNow(); $parserOutput->setCacheTime( $now ); - + // Save the timestamp so that we don't have to load the revision row on view $parserOutput->mTimestamp = $article->getTimestamp(); - + $parserOutput->mText .= "\n<!-- Saved in parser cache with key $key and timestamp $now -->\n"; wfDebug( "Saved in parser cache with key $key and timestamp $now\n" ); - + if( $parserOutput->containsOldMagic() ){ $expire = 3600; # 1 hour } else { $expire = $wgParserCacheExpireTime; } $this->mMemc->set( $key, $parserOutput, $expire ); - + } else { wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" ); } - } - + } ?> diff --git a/includes/ParserOptions.php b/includes/ParserOptions.php new file mode 100644 index 00000000..e335720f --- /dev/null +++ b/includes/ParserOptions.php @@ -0,0 +1,119 @@ +<?php + +/** + * Set options of the Parser + * @todo document + * @addtogroup Parser + */ +class ParserOptions +{ + # All variables are supposed to be private in theory, although in practise this is not the case. + var $mUseTeX; # Use texvc to expand <math> tags + var $mUseDynamicDates; # Use DateFormatter to format dates + var $mInterwikiMagic; # Interlanguage links are removed and returned in an array + var $mAllowExternalImages; # Allow external images inline + var $mAllowExternalImagesFrom; # If not, any exception? + var $mSkin; # Reference to the preferred skin + var $mDateFormat; # Date format index + var $mEditSection; # Create "edit section" links + var $mNumberHeadings; # Automatically number headings + var $mAllowSpecialInclusion; # Allow inclusion of special pages + var $mTidy; # Ask for tidy cleanup + var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR + var $mMaxIncludeSize; # Maximum size of template expansions, in bytes + var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS + + var $mUser; # Stored user object, just used to initialise the skin + + function getUseTeX() { return $this->mUseTeX; } + function getUseDynamicDates() { return $this->mUseDynamicDates; } + function getInterwikiMagic() { return $this->mInterwikiMagic; } + function getAllowExternalImages() { return $this->mAllowExternalImages; } + function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; } + function getEditSection() { return $this->mEditSection; } + function getNumberHeadings() { return $this->mNumberHeadings; } + function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; } + function getTidy() { return $this->mTidy; } + function getInterfaceMessage() { return $this->mInterfaceMessage; } + function getMaxIncludeSize() { return $this->mMaxIncludeSize; } + function getRemoveComments() { return $this->mRemoveComments; } + + function getSkin() { + if ( !isset( $this->mSkin ) ) { + $this->mSkin = $this->mUser->getSkin(); + } + return $this->mSkin; + } + + function getDateFormat() { + if ( !isset( $this->mDateFormat ) ) { + $this->mDateFormat = $this->mUser->getDatePreference(); + } + return $this->mDateFormat; + } + + function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); } + function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); } + function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); } + function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); } + function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); } + function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); } + function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); } + function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); } + function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); } + function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); } + function setSkin( $x ) { $this->mSkin = $x; } + function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); } + function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); } + function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); } + + function __construct( $user = null ) { + $this->initialiseFromUser( $user ); + } + + /** + * Get parser options + * @static + */ + static function newFromUser( $user ) { + return new ParserOptions( $user ); + } + + /** Get user options */ + function initialiseFromUser( $userInput ) { + global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages; + global $wgAllowExternalImagesFrom, $wgAllowSpecialInclusion, $wgMaxArticleSize; + $fname = 'ParserOptions::initialiseFromUser'; + wfProfileIn( $fname ); + if ( !$userInput ) { + global $wgUser; + if ( isset( $wgUser ) ) { + $user = $wgUser; + } else { + $user = new User; + } + } else { + $user =& $userInput; + } + + $this->mUser = $user; + + $this->mUseTeX = $wgUseTeX; + $this->mUseDynamicDates = $wgUseDynamicDates; + $this->mInterwikiMagic = $wgInterwikiMagic; + $this->mAllowExternalImages = $wgAllowExternalImages; + $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom; + $this->mSkin = null; # Deferred + $this->mDateFormat = null; # Deferred + $this->mEditSection = true; + $this->mNumberHeadings = $user->getOption( 'numberheadings' ); + $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion; + $this->mTidy = false; + $this->mInterfaceMessage = false; + $this->mMaxIncludeSize = $wgMaxArticleSize * 1024; + $this->mRemoveComments = true; + wfProfileOut( $fname ); + } +} + +?> diff --git a/includes/ParserOutput.php b/includes/ParserOutput.php new file mode 100644 index 00000000..03f1819c --- /dev/null +++ b/includes/ParserOutput.php @@ -0,0 +1,133 @@ +<?php +/** + * @todo document + * @addtogroup Parser + */ +class ParserOutput +{ + var $mText, # The output text + $mLanguageLinks, # List of the full text of language links, in the order they appear + $mCategories, # Map of category names to sort keys + $mContainsOldMagic, # Boolean variable indicating if the input contained variables like {{CURRENTDAY}} + $mCacheTime, # Time when this object was generated, or -1 for uncacheable. Used in ParserCache. + $mVersion, # Compatibility check + $mTitleText, # title text of the chosen language variant + $mLinks, # 2-D map of NS/DBK to ID for the links in the document. ID=zero for broken. + $mTemplates, # 2-D map of NS/DBK to ID for the template references. ID=zero for broken. + $mImages, # DB keys of the images used, in the array key only + $mExternalLinks, # External link URLs, in the key only + $mHTMLtitle, # Display HTML title + $mSubtitle, # Additional subtitle + $mNewSection, # Show a new section link? + $mNoGallery, # No gallery on category page? (__NOGALLERY__) + $mHeadItems; # Items to put in the <head> section + + function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(), + $containsOldMagic = false, $titletext = '' ) + { + $this->mText = $text; + $this->mLanguageLinks = $languageLinks; + $this->mCategories = $categoryLinks; + $this->mContainsOldMagic = $containsOldMagic; + $this->mCacheTime = ''; + $this->mVersion = Parser::VERSION; + $this->mTitleText = $titletext; + $this->mLinks = array(); + $this->mTemplates = array(); + $this->mImages = array(); + $this->mExternalLinks = array(); + $this->mHTMLtitle = "" ; + $this->mSubtitle = "" ; + $this->mNewSection = false; + $this->mNoGallery = false; + $this->mHeadItems = array(); + } + + function getText() { return $this->mText; } + function &getLanguageLinks() { return $this->mLanguageLinks; } + function getCategoryLinks() { return array_keys( $this->mCategories ); } + function &getCategories() { return $this->mCategories; } + function getCacheTime() { return $this->mCacheTime; } + function getTitleText() { return $this->mTitleText; } + function &getLinks() { return $this->mLinks; } + function &getTemplates() { return $this->mTemplates; } + function &getImages() { return $this->mImages; } + function &getExternalLinks() { return $this->mExternalLinks; } + function getNoGallery() { return $this->mNoGallery; } + function getSubtitle() { return $this->mSubtitle; } + + function containsOldMagic() { return $this->mContainsOldMagic; } + function setText( $text ) { return wfSetVar( $this->mText, $text ); } + function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); } + function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategories, $cl ); } + function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); } + function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); } + function setTitleText( $t ) { return wfSetVar($this->mTitleText, $t); } + function setSubtitle( $st ) { return wfSetVar( $this->mSubtitle, $st ); } + + function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; } + function addImage( $name ) { $this->mImages[$name] = 1; } + function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; } + function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; } + + function setNewSection( $value ) { + $this->mNewSection = (bool)$value; + } + function getNewSection() { + return (bool)$this->mNewSection; + } + + function addLink( $title, $id = null ) { + $ns = $title->getNamespace(); + $dbk = $title->getDBkey(); + if ( !isset( $this->mLinks[$ns] ) ) { + $this->mLinks[$ns] = array(); + } + if ( is_null( $id ) ) { + $id = $title->getArticleID(); + } + $this->mLinks[$ns][$dbk] = $id; + } + + function addTemplate( $title, $id ) { + $ns = $title->getNamespace(); + $dbk = $title->getDBkey(); + if ( !isset( $this->mTemplates[$ns] ) ) { + $this->mTemplates[$ns] = array(); + } + $this->mTemplates[$ns][$dbk] = $id; + } + + /** + * Return true if this cached output object predates the global or + * per-article cache invalidation timestamps, or if it comes from + * an incompatible older version. + * + * @param string $touched the affected article's last touched timestamp + * @return bool + * @public + */ + function expired( $touched ) { + global $wgCacheEpoch; + return $this->getCacheTime() == -1 || // parser says it's uncacheable + $this->getCacheTime() < $touched || + $this->getCacheTime() <= $wgCacheEpoch || + !isset( $this->mVersion ) || + version_compare( $this->mVersion, Parser::VERSION, "lt" ); + } + + /** + * Add some text to the <head>. + * If $tag is set, the section with that tag will only be included once + * in a given page. + */ + function addHeadItem( $section, $tag = false ) { + if ( $tag !== false ) { + $this->mHeadItems[$tag] = $section; + } else { + $this->mHeadItems[] = $section; + } + } +} + +?> diff --git a/includes/PatrolLog.php b/includes/PatrolLog.php new file mode 100644 index 00000000..a22839ff --- /dev/null +++ b/includes/PatrolLog.php @@ -0,0 +1,83 @@ +<?php + +/** + * Class containing static functions for working with + * logs of patrol events + * + * @author Rob Church <robchur@gmail.com> + */ +class PatrolLog { + + /** + * Record a log event for a change being patrolled + * + * @param mixed $change Change identifier or RecentChange object + * @param bool $auto Was this patrol event automatic? + */ + public static function record( $change, $auto = false ) { + if( !( is_object( $change ) && $change instanceof RecentChange ) ) { + $change = RecentChange::newFromId( $change ); + if( !is_object( $change ) ) + return false; + } + $title = Title::makeTitleSafe( $change->getAttribute( 'rc_namespace' ), + $change->getAttribute( 'rc_title' ) ); + if( is_object( $title ) ) { + $params = self::buildParams( $change, $auto ); + $log = new LogPage( 'patrol', false ); # False suppresses RC entries + $log->addEntry( 'patrol', $title, '', $params ); + return true; + } else { + return false; + } + } + + /** + * Generate the log action text corresponding to a patrol log item + * + * @param Title $title Title of the page that was patrolled + * @param array $params Log parameters (from logging.log_params) + * @param Skin $skin Skin to use for building links, etc. + * @return string + */ + public static function makeActionText( $title, $params, $skin ) { + # This is a bit of a hack, but...if $skin is not a Skin, then *do nothing* + # -- this is fine, because the action text we would be queried for under + # these conditions would have gone into recentchanges, which we aren't + # supposed to be updating + if( is_object( $skin ) ) { + list( $cur, $prev, $auto ) = $params; + # Standard link to the page in question + $link = $skin->makeLinkObj( $title ); + # Generate a diff link + $bits[] = 'oldid=' . urlencode( $cur ); + $bits[] = 'diff=prev'; + $bits = implode( '&', $bits ); + $diff = $skin->makeLinkObj( $title, htmlspecialchars( wfMsg( 'patrol-log-diff', $cur ) ), $bits ); + # Indicate whether or not the patrolling was automatic + $auto = $auto ? wfMsgHtml( 'patrol-log-auto' ) : ''; + # Put it all together + return wfMsgHtml( 'patrol-log-line', $diff, $link, $auto ); + } else { + return ''; + } + } + + /** + * Prepare log parameters for a patrolled change + * + * @param RecentChange $change RecentChange to represent + * @param bool $auto Whether the patrol event was automatic + * @return array + */ + private static function buildParams( $change, $auto ) { + return array( + $change->getAttribute( 'rc_this_oldid' ), + $change->getAttribute( 'rc_last_oldid' ), + (int)$auto + ); + } + +} + +?>
\ No newline at end of file diff --git a/includes/Profiler.php b/includes/Profiler.php index 30cda63f..da3a82ed 100644 --- a/includes/Profiler.php +++ b/includes/Profiler.php @@ -1,7 +1,6 @@ <?php /** * This file is only included if profiling is enabled - * @package MediaWiki */ $wgProfiling = true; @@ -41,14 +40,13 @@ if (!function_exists('memory_get_usage')) { /** * @todo document - * @package MediaWiki + * @addtogroup Profiler */ class Profiler { var $mStack = array (), $mWorkStack = array (), $mCollated = array (); var $mCalls = array (), $mTotals = array (); - function Profiler() - { + function __construct() { // Push an entry for the pre-profile setup time onto the stack global $wgRequestTime; if ( !empty( $wgRequestTime ) ) { @@ -57,7 +55,6 @@ class Profiler { } else { $this->profileIn( '-total' ); } - } function profileIn($functionname) { @@ -291,7 +288,7 @@ class Profiler { * @return Integer * @private */ - function calltreeCount(& $stack, $start) { + function calltreeCount($stack, $start) { $level = $stack[$start][1]; $count = 0; for ($i = $start -1; $i >= 0 && $stack[$i][1] > $level; $i --) { @@ -308,7 +305,7 @@ class Profiler { global $wguname, $wgProfilePerHost; $fname = 'Profiler::logToDB'; - $dbw = & wfGetDB(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); if (!is_object($dbw)) return false; $errorState = $dbw->ignoreErrors( true ); diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php index e69bfc47..f43c7dfc 100644 --- a/includes/ProfilerSimple.php +++ b/includes/ProfilerSimple.php @@ -1,20 +1,17 @@ <?php -/** - * Simple profiler base class - * @package MediaWiki - */ -/** - * @todo document - * @package MediaWiki - */ require_once(dirname(__FILE__).'/Profiler.php'); +/** + * Simple profiler base class. + * @todo document methods (?) + * @addtogroup Profiler + */ class ProfilerSimple extends Profiler { var $mMinimumTime = 0; var $mProfileID = false; - function ProfilerSimple() { + function __construct() { global $wgRequestTime,$wgRUstart; if (!empty($wgRequestTime) && !empty($wgRUstart)) { $this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart)); @@ -26,7 +23,6 @@ class ProfilerSimple extends Profiler { if (!is_array($entry)) { $entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0); $this->mCollated["-setup"] =& $entry; - } $entry['cpu'] += $elapsedcpu; $entry['cpu_sq'] += $elapsedcpu*$elapsedcpu; @@ -57,7 +53,7 @@ class ProfilerSimple extends Profiler { if ($wgDebugFunctionEntry) { $this->debug(str_repeat(' ', count($this->mWorkStack)).'Entering '.$functionname."\n"); } - $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime()); + $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime()); } function profileOut($functionname) { @@ -87,7 +83,6 @@ class ProfilerSimple extends Profiler { if (!is_array($entry)) { $entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0); $this->mCollated[$functionname] =& $entry; - } $entry['cpu'] += $elapsedcpu; $entry['cpu_sq'] += $elapsedcpu*$elapsedcpu; diff --git a/includes/ProfilerSimpleUDP.php b/includes/ProfilerSimpleUDP.php index a8527c38..500f1cbd 100644 --- a/includes/ProfilerSimpleUDP.php +++ b/includes/ProfilerSimpleUDP.php @@ -1,11 +1,13 @@ <?php -/* ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon - (the one from wikipedia/udpprofile CVS ) -*/ require_once(dirname(__FILE__).'/Profiler.php'); require_once(dirname(__FILE__).'/ProfilerSimple.php'); +/** + * ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon + * (the one from mediawiki/trunk/udpprofile SVN ) + * @addtogroup Profiler + */ class ProfilerSimpleUDP extends ProfilerSimple { function getFunctionReport() { global $wgUDPProfilerHost; @@ -15,8 +17,7 @@ class ProfilerSimpleUDP extends ProfilerSimple { # Less than minimum, ignore return; } - - + $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); $plength=0; $packet=""; diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php index f96262fe..3cafbd55 100644 --- a/includes/ProtectionForm.php +++ b/includes/ProtectionForm.php @@ -17,27 +17,42 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html - * - * @package MediaWiki - * @subpackage SpecialPage */ +/** + * @todo document, briefly. + * @addtogroup SpecialPage + */ class ProtectionForm { var $mRestrictions = array(); var $mReason = ''; + var $mCascade = false; + var $mExpiry = null; - function ProtectionForm( &$article ) { + function __construct( &$article ) { global $wgRequest, $wgUser; global $wgRestrictionTypes, $wgRestrictionLevels; $this->mArticle =& $article; $this->mTitle =& $article->mTitle; if( $this->mTitle ) { + $this->mTitle->loadRestrictions(); + foreach( $wgRestrictionTypes as $action ) { // Fixme: this form currently requires individual selections, // but the db allows multiples separated by commas. $this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) ); } + + $this->mCascade = $this->mTitle->areRestrictionsCascading(); + + if ( $this->mTitle->mRestrictionsExpiry == 'infinity' ) { + $this->mExpiry = 'infinite'; + } else if ( strlen($this->mTitle->mRestrictionsExpiry) == 0 ) { + $this->mExpiry = ''; + } else { + $this->mExpiry = wfTimestamp( TS_RFC2822, $this->mTitle->mRestrictionsExpiry ); + } } // The form will be available in read-only to show levels. @@ -48,6 +63,9 @@ class ProtectionForm { if( $wgRequest->wasPosted() ) { $this->mReason = $wgRequest->getText( 'mwProtect-reason' ); + $this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' ); + $this->mExpiry = $wgRequest->getText( 'mwProtect-expiry' ); + foreach( $wgRestrictionTypes as $action ) { $val = $wgRequest->getVal( "mwProtect-level-$action" ); if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) { @@ -56,9 +74,21 @@ class ProtectionForm { } } } + + function execute() { + global $wgRequest; + if( $wgRequest->wasPosted() ) { + if( $this->save() ) { + global $wgOut; + $wgOut->redirect( $this->mTitle->getFullUrl() ); + } + } else { + $this->show(); + } + } - function show() { - global $wgOut; + function show( $err = null ) { + global $wgOut, $wgUser; $wgOut->setRobotpolicy( 'noindex,nofollow' ); @@ -69,17 +99,47 @@ class ProtectionForm { return; } - if( $this->save() ) { - $wgOut->redirect( $this->mTitle->getFullUrl() ); - return; + list( $cascadeSources, $restrictions ) = $this->mTitle->getCascadeProtectionSources(); + + if ( "" != $err ) { + $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); + $wgOut->addHTML( "<p class='error'>{$err}</p>\n" ); + } + + if ( $cascadeSources && count($cascadeSources) > 0 ) { + $titles = ''; + + foreach ( $cascadeSources as $title ) { + $titles .= '* [[:' . $title->getPrefixedText() . "]]\n"; + } + + $notice = wfMsgExt( 'protect-cascadeon', array('parsemag'), count($cascadeSources) ) . "\r\n$titles"; + + $wgOut->addWikiText( $notice ); } $wgOut->setPageTitle( wfMsg( 'confirmprotect' ) ); $wgOut->setSubtitle( wfMsg( 'protectsub', $this->mTitle->getPrefixedText() ) ); - $wgOut->addWikiText( - wfMsg( $this->disabled ? "protect-viewtext" : "protect-text", - wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ) ); + # Show an appropriate message if the user isn't allowed or able to change + # the protection settings at this time + if( $this->disabled ) { + if( $wgUser->isAllowed( 'protect' ) ) { + if( $wgUser->isBlocked() ) { + # Blocked + $message = 'protect-locked-blocked'; + } else { + # Database lock + $message = 'protect-locked-dblock'; + } + } else { + # Permission error + $message = 'protect-locked-access'; + } + } else { + $message = 'protect-text'; + } + $wgOut->addWikiText( wfMsg( $message, wfEscapeWikiText( $this->mTitle->getPrefixedText() ) ) ); $wgOut->addHTML( $this->buildForm() ); @@ -88,20 +148,43 @@ class ProtectionForm { function save() { global $wgRequest, $wgUser, $wgOut; - if( !$wgRequest->wasPosted() ) { - return false; - } - + if( $this->disabled ) { + $this->show(); return false; } $token = $wgRequest->getVal( 'wpEditToken' ); if( !$wgUser->matchEditToken( $token ) ) { - throw new FatalError( wfMsg( 'sessionfailure' ) ); + $this->show( wfMsg( 'sessionfailure' ) ); + return false; } - $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason ); + if ( strlen( $this->mExpiry ) == 0 ) { + $this->mExpiry = 'infinite'; + } + + if ( $this->mExpiry == 'infinite' || $this->mExpiry == 'indefinite' ) { + $expiry = Block::infinity(); + } else { + # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1 + $expiry = strtotime( $this->mExpiry ); + + if ( $expiry < 0 || $expiry === false ) { + $this->show( wfMsg( 'protect_expiry_invalid' ) ); + return false; + } + + $expiry = wfTimestamp( TS_MW, $expiry ); + + if ( $expiry < wfTimestampNow() ) { + $this->show( wfMsg( 'protect_expiry_old' ) ); + return false; + } + + } + + $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason, $this->mCascade, $expiry ); if( !$ok ) { throw new FatalError( "Unknown error at restriction save time." ); } @@ -117,6 +200,7 @@ class ProtectionForm { // The submission needs to reenable the move permission selector // if it's in locked mode, or some browsers won't submit the data. $out .= wfOpenElement( 'form', array( + 'id' => 'mw-Protect-Form', 'action' => $this->mTitle->getLocalUrl( 'action=protect' ), 'method' => 'post', 'onsubmit' => 'protectEnable(true)' ) ); @@ -148,13 +232,25 @@ class ProtectionForm { $out .= "</tbody>\n"; $out .= "</table>\n"; + global $wgEnableCascadingProtection; + + if ($wgEnableCascadingProtection) + $out .= $this->buildCascadeInput(); + + $out .= "<table>\n"; + $out .= "<tbody>\n"; + + $out .= $this->buildExpiryInput(); + if( !$this->disabled ) { - $out .= "<table>\n"; - $out .= "<tbody>\n"; $out .= "<tr><td>" . $this->buildReasonInput() . "</td></tr>\n"; $out .= "<tr><td></td><td>" . $this->buildSubmit() . "</td></tr>\n"; - $out .= "</tbody>\n"; - $out .= "</table>\n"; + } + + $out .= "</tbody>\n"; + $out .= "</table>\n"; + + if ( !$this->disabled ) { $out .= "</form>\n"; $out .= $this->buildCleanupScript(); } @@ -202,11 +298,38 @@ class ProtectionForm { wfElement( 'input', array( 'size' => 60, 'name' => $id, - 'id' => $id ) ); + 'id' => $id, + 'value' => $this->mReason ) ); + } + + function buildCascadeInput() { + $id = 'mwProtect-cascade'; + $ci = wfCheckLabel( wfMsg( 'protect-cascade' ), $id, $id, $this->mCascade, $this->disabledAttrib); + return $ci; + } + + function buildExpiryInput() { + $id = 'mwProtect-expiry'; + + $ci = "<tr> <td align=\"right\">"; + $ci .= wfElement( 'label', array ( + 'id' => "$id-label", + 'for' => $id ), + wfMsg( 'protectexpiry' ) ); + $ci .= "</td> <td align=\"left\">"; + $ci .= wfElement( 'input', array( + 'size' => 60, + 'name' => $id, + 'id' => $id, + 'value' => $this->mExpiry ) + $this->disabledAttrib ); + $ci .= "</td></tr>"; + + return $ci; } function buildSubmit() { return wfElement( 'input', array( + 'id' => 'mw-Protect-submit', 'type' => 'submit', 'value' => wfMsg( 'confirm' ) ) ); } @@ -219,8 +342,17 @@ class ProtectionForm { } function buildCleanupScript() { - return '<script type="text/javascript">protectInitialize("mwProtectSet","' . - wfEscapeJsString( wfMsg( 'protect-unchain' ) ) . '")</script>'; + global $wgRestrictionLevels, $wgGroupPermissions; + $script = 'var wgCascadeableLevels='; + $CascadeableLevels = array(); + foreach( $wgRestrictionLevels as $key ) { + if ( isset($wgGroupPermissions[$key]['protect']) && $wgGroupPermissions[$key]['protect'] ) { + $CascadeableLevels[]="'" . wfEscapeJsString($key) . "'"; + } + } + $script .= "[" . implode(',',$CascadeableLevels) . "];\n"; + $script .= 'protectInitialize("mwProtectSet","' . wfEscapeJsString( wfMsg( 'protect-unchain' ) ) . '")'; + return '<script type="text/javascript">' . $script . '</script>'; } /** @@ -239,5 +371,4 @@ class ProtectionForm { } } - ?> diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php index 22ea4947..f72b640f 100644 --- a/includes/ProxyTools.php +++ b/includes/ProxyTools.php @@ -1,27 +1,63 @@ <?php /** * Functions for dealing with proxies - * @package MediaWiki */ +/** + * Extracts the XFF string from the request header + * Checks first for "X-Forwarded-For", then "Client-ip" + * Note: headers are spoofable + * @return string + */ function wfGetForwardedFor() { if( function_exists( 'apache_request_headers' ) ) { // More reliable than $_SERVER due to case and -/_ folding $set = apache_request_headers(); $index = 'X-Forwarded-For'; + $index2 = 'Client-ip'; } else { // Subject to spoofing with headers like X_Forwarded_For $set = $_SERVER; $index = 'HTTP_X_FORWARDED_FOR'; + $index2 = 'CLIENT-IP'; } + #Try a couple of headers if( isset( $set[$index] ) ) { return $set[$index]; + } else if( isset( $set[$index2] ) ) { + return $set[$index2]; } else { return null; } } -/** Work out the IP address based on various globals */ +/** + * Returns the browser/OS data from the request header + * Note: headers are spoofable + * @return string + */ +function wfGetAgent() { + if( function_exists( 'apache_request_headers' ) ) { + // More reliable than $_SERVER due to case and -/_ folding + $set = apache_request_headers(); + $index = 'User-Agent'; + } else { + // Subject to spoofing with headers like X_Forwarded_For + $set = $_SERVER; + $index = 'HTTP_USER_AGENT'; + } + if( isset( $set[$index] ) ) { + return $set[$index]; + } else { + return ''; + } +} + +/** + * Work out the IP address based on various globals + * For trusted proxies, use the XFF client IP (first of the chain) + * @return string + */ function wfGetIP() { global $wgIP; @@ -66,6 +102,13 @@ function wfGetIP() { return $ip; } +/** + * Checks if an IP is a trusted proxy providor + * Useful to tell if X-Fowarded-For data is possibly bogus + * Squid cache servers for the site and AOL are whitelisted + * @param string $ip + * @return bool + */ function wfIsTrustedProxy( $ip ) { global $wgSquidServers, $wgSquidServersNoPurge; @@ -130,6 +173,7 @@ function wfProxyCheck() { /** * Convert a network specification in CIDR notation to an integer network and a number of bits + * @return array(string, int) */ function wfParseCIDR( $range ) { return IP::parseCIDR( $range ); @@ -137,6 +181,7 @@ function wfParseCIDR( $range ) { /** * Check if an IP address is in the local proxy list + * @return bool */ function wfIsLocallyBlockedProxy( $ip ) { global $wgProxyList; @@ -169,6 +214,7 @@ function wfIsLocallyBlockedProxy( $ip ) { /** * TODO: move this list to the database in a global IP info table incorporating * trusted ISP proxies, blocked IP addresses and open proxies. + * @return bool */ function wfIsAOLProxy( $ip ) { $ranges = array( diff --git a/includes/QueryPage.php b/includes/QueryPage.php index ff6355e7..143c8be6 100644 --- a/includes/QueryPage.php +++ b/includes/QueryPage.php @@ -1,42 +1,45 @@ <?php /** * Contain a class for special pages - * @package MediaWiki */ /** - * List of query page classes and their associated special pages, for periodic update purposes + * List of query page classes and their associated special pages, + * for periodic updates. + * + * DO NOT CHANGE THIS LIST without testing that + * maintenance/updateSpecialPages.php still works. */ global $wgQueryPages; // not redundant $wgQueryPages = array( // QueryPage subclass Special page name Limit (false for none, none for the default) //---------------------------------------------------------------------------- - array( 'AncientPagesPage', 'Ancientpages' ), - array( 'BrokenRedirectsPage', 'BrokenRedirects' ), - array( 'CategoriesPage', 'Categories' ), - array( 'DeadendPagesPage', 'Deadendpages' ), - array( 'DisambiguationsPage', 'Disambiguations' ), - array( 'DoubleRedirectsPage', 'DoubleRedirects' ), - array( 'ListUsersPage', 'Listusers' ), - array( 'ListredirectsPage', 'Listredirects' ), - array( 'LonelyPagesPage', 'Lonelypages' ), - array( 'LongPagesPage', 'Longpages' ), - array( 'MostcategoriesPage', 'Mostcategories' ), - array( 'MostimagesPage', 'Mostimages' ), - array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ), - array( 'MostlinkedPage', 'Mostlinked' ), - array( 'MostrevisionsPage', 'Mostrevisions' ), - array( 'NewPagesPage', 'Newpages' ), - array( 'ShortPagesPage', 'Shortpages' ), - array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ), - array( 'UncategorizedPagesPage', 'Uncategorizedpages' ), - array( 'UncategorizedImagesPage', 'Uncategorizedimages' ), - array( 'UnusedCategoriesPage', 'Unusedcategories' ), - array( 'UnusedimagesPage', 'Unusedimages' ), - array( 'WantedCategoriesPage', 'Wantedcategories' ), - array( 'WantedPagesPage', 'Wantedpages' ), - array( 'UnwatchedPagesPage', 'Unwatchedpages' ), - array( 'UnusedtemplatesPage', 'Unusedtemplates' ), + array( 'AncientPagesPage', 'Ancientpages' ), + array( 'BrokenRedirectsPage', 'BrokenRedirects' ), + array( 'DeadendPagesPage', 'Deadendpages' ), + array( 'DisambiguationsPage', 'Disambiguations' ), + array( 'DoubleRedirectsPage', 'DoubleRedirects' ), + array( 'ListredirectsPage', 'Listredirects' ), + array( 'LonelyPagesPage', 'Lonelypages' ), + array( 'LongPagesPage', 'Longpages' ), + array( 'MostcategoriesPage', 'Mostcategories' ), + array( 'MostimagesPage', 'Mostimages' ), + array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ), + array( 'MostlinkedPage', 'Mostlinked' ), + array( 'MostrevisionsPage', 'Mostrevisions' ), + array( 'FewestrevisionsPage', 'Fewestrevisions' ), + array( 'NewPagesPage', 'Newpages' ), + array( 'ShortPagesPage', 'Shortpages' ), + array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ), + array( 'UncategorizedPagesPage', 'Uncategorizedpages' ), + array( 'UncategorizedImagesPage', 'Uncategorizedimages' ), + array( 'UnusedCategoriesPage', 'Unusedcategories' ), + array( 'UnusedimagesPage', 'Unusedimages' ), + array( 'WantedCategoriesPage', 'Wantedcategories' ), + array( 'WantedPagesPage', 'Wantedpages' ), + array( 'UnwatchedPagesPage', 'Unwatchedpages' ), + array( 'UnusedtemplatesPage', 'Unusedtemplates' ), + array( 'WithoutInterwikiPage', 'Withoutinterwiki' ), ); wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) ); @@ -49,8 +52,7 @@ if ( !$wgDisableCounters ) * This is a class for doing query pages; since they're almost all the same, * we factor out some of the functionality into a superclass, and let * subclasses derive from it. - * - * @package MediaWiki + * @addtogroup SpecialPage */ class QueryPage { /** @@ -59,7 +61,7 @@ class QueryPage { * @var bool */ var $listoutput = false; - + /** * The offset and limit in use, as passed to the query() function * @@ -197,8 +199,8 @@ class QueryPage { */ function recache( $limit, $ignoreErrors = true ) { $fname = get_class($this) . '::recache'; - $dbw =& wfGetDB( DB_MASTER ); - $dbr =& wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) ); + $dbw = wfGetDB( DB_MASTER ); + $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) ); if ( !$dbw || !$dbr ) { return false; } @@ -282,7 +284,7 @@ class QueryPage { $sname = $this->getName(); $fname = get_class($this) . '::doQuery'; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $wgOut->setSyndicated( $this->isSyndicated() ); @@ -306,7 +308,7 @@ class QueryPage { $updated = $wgLang->timeAndDate( $tRow->qci_timestamp, true, true ); $cacheNotice = wfMsg( 'perfcachedts', $updated ); $wgOut->addMeta( 'Data-Cache-Time', $tRow->qci_timestamp ); - $wgOut->addScript( '<script language="JavaScript">var dataCacheTime = \'' . $tRow->qci_timestamp . '\';</script>' ); + $wgOut->addInlineScript( "var dataCacheTime = '{$tRow->qci_timestamp}';" ); } else { $cacheNotice = wfMsg( 'perfcached' ); } @@ -330,58 +332,99 @@ class QueryPage { $num = $dbr->numRows($res); $this->preprocessResults( $dbr, $res ); - - $sk = $wgUser->getSkin( ); - - if($shownavigation) { - $wgOut->addHTML( $this->getPageHeader() ); - $top = wfShowingResults( $offset, $num); - $wgOut->addHTML( "<p>{$top}\n" ); - - # often disable 'next' link when we reach the end - $atend = $num < $limit; - - $sl = wfViewPrevNext( $offset, $limit , - $wgContLang->specialPage( $sname ), - wfArrayToCGI( $this->linkParameters() ), $atend ); - $wgOut->addHTML( "<br />{$sl}</p>\n" ); + $sk = $wgUser->getSkin(); + + # Top header and navigation + if( $shownavigation ) { + $wgOut->addHtml( $this->getPageHeader() ); + if( $num > 0 ) { + $wgOut->addHtml( '<p>' . wfShowingResults( $offset, $num ) . '</p>' ); + # Disable the "next" link when we reach the end + $paging = wfViewPrevNext( $offset, $limit, $wgContLang->specialPage( $sname ), + wfArrayToCGI( $this->linkParameters() ), ( $num < $limit ) ); + $wgOut->addHtml( '<p>' . $paging . '</p>' ); + } else { + # No results to show, so don't bother with "showing X of Y" etc. + # -- just let the user know and give up now + $wgOut->addHtml( '<p>' . wfMsgHtml( 'specialpage-empty' ) . '</p>' ); + return; + } + } + + # The actual results; specialist subclasses will want to handle this + # with more than a straight list, so we hand them the info, plus + # an OutputPage, and let them get on with it + $this->outputResults( $wgOut, + $wgUser->getSkin(), + $dbr, # Should use a ResultWrapper for this + $res, + $dbr->numRows( $res ), + $offset ); + + # Repeat the paging links at the bottom + if( $shownavigation ) { + $wgOut->addHtml( '<p>' . $paging . '</p>' ); } - if ( $num > 0 ) { - $s = array(); - if ( ! $this->listoutput ) - $s[] = $this->openList( $offset ); - - # Only read at most $num rows, because $res may contain the whole 1000 - for ( $i = 0; $i < $num && $obj = $dbr->fetchObject( $res ); $i++ ) { - $format = $this->formatResult( $sk, $obj ); - if ( $format ) { - $attr = ( isset ( $obj->usepatrol ) && $obj->usepatrol && - $obj->patrolled == 0 ) ? ' class="not-patrolled"' : ''; - $s[] = $this->listoutput ? $format : "<li{$attr}>{$format}</li>\n"; + + return $num; + } + + /** + * Format and output report results using the given information plus + * OutputPage + * + * @param OutputPage $out OutputPage to print to + * @param Skin $skin User skin to use + * @param Database $dbr Database (read) connection to use + * @param int $res Result pointer + * @param int $num Number of available result rows + * @param int $offset Paging offset + */ + protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { + global $wgContLang; + + if( $num > 0 ) { + $html = array(); + if( !$this->listoutput ) + $html[] = $this->openList( $offset ); + + # $res might contain the whole 1,000 rows, so we read up to + # $num [should update this to use a Pager] + for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { + $line = $this->formatResult( $skin, $row ); + if( $line ) { + $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) + ? ' class="not-patrolled"' + : ''; + $html[] = $this->listoutput + ? $format + : "<li{$attr}>{$line}</li>\n"; } } - - if($this->tryLastResult()) { - // flush the very last result - $obj = null; - $format = $this->formatResult( $sk, $obj ); - if( $format ) { - $attr = ( isset ( $obj->usepatrol ) && $obj->usepatrol && - $obj->patrolled == 0 ) ? ' class="not-patrolled"' : ''; - $s[] = "<li{$attr}>{$format}</li>\n"; + + # Flush the final result + if( $this->tryLastResult() ) { + $row = null; + $line = $this->formatResult( $skin, $row ); + if( $line ) { + $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) + ? ' class="not-patrolled"' + : ''; + $html[] = $this->listoutput + ? $format + : "<li{$attr}>{$line}</li>\n"; } } - - $dbr->freeResult( $res ); - if ( ! $this->listoutput ) - $s[] = $this->closeList(); - $str = $this->listoutput ? $wgContLang->listToText( $s ) : implode( '', $s ); - $wgOut->addHTML( $str ); - } - if($shownavigation) { - $wgOut->addHTML( "<p>{$sl}</p>\n" ); + + if( !$this->listoutput ) + $html[] = $this->closeList(); + + $html = $this->listoutput + ? $wgContLang->listToText( $html ) + : implode( '', $html ); + + $out->addHtml( $html ); } - return $num; } function openList( $offset ) { @@ -411,7 +454,7 @@ class QueryPage { $this->feedUrl() ); $feed->outHeader(); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $sql = $this->getSQL() . $this->getOrder(); $sql = $dbr->limitResult( $sql, $limit, 0 ); $res = $dbr->query( $sql, 'QueryPage::doFeed' ); @@ -482,20 +525,4 @@ class QueryPage { } } -/** - * This is a subclass for very simple queries that are just looking for page - * titles that match some criteria. It formats each result item as a link to - * that page. - * - * @package MediaWiki - */ -class PageQueryPage extends QueryPage { - - function formatResult( $skin, $result ) { - global $wgContLang; - $nt = Title::makeTitle( $result->namespace, $result->title ); - return $skin->makeKnownLinkObj( $nt, htmlspecialchars( $wgContLang->convert( $nt->getPrefixedText() ) ) ); - } -} - ?> diff --git a/includes/RawPage.php b/includes/RawPage.php index a0b76886..93484829 100644 --- a/includes/RawPage.php +++ b/includes/RawPage.php @@ -7,12 +7,11 @@ * License: GPL (http://www.gnu.org/copyleft/gpl.html) * * @author Gabriel Wicke <wicke@wikidev.net> - * @package MediaWiki */ /** - * @todo document - * @package MediaWiki + * A simple method to retrieve the plain source of an article, + * using "action=raw" in the GET request string. */ class RawPage { var $mArticle, $mTitle, $mRequest; @@ -20,9 +19,8 @@ class RawPage { var $mSmaxage, $mMaxage; var $mContentType, $mExpandTemplates; - function RawPage( &$article, $request = false ) { + function __construct( &$article, $request = false ) { global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType; - global $wgUser; $allowedCTypes = array('text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit'); $this->mArticle =& $article; @@ -39,7 +37,7 @@ class RawPage { $maxage = $this->mRequest->getInt( 'maxage', $wgSquidMaxage ); $this->mExpandTemplates = $this->mRequest->getVal( 'templates' ) === 'expand'; $this->mUseMessageCache = $this->mRequest->getBool( 'usemsgcache' ); - + $oldid = $this->mRequest->getInt( 'oldid' ); switch ( $wgRequest->getText( 'direction' ) ) { case 'next': @@ -85,8 +83,7 @@ class RawPage { // Output may contain user-specific data; vary for open sessions $this->mPrivateCache = ( $this->mSmaxage == 0 ) || - ( isset( $_COOKIE[ini_get( 'session.name' )] ) || - $wgUser->isLoggedIn() ); + ( session_id() != '' ); if ( $ctype == '' or ! in_array( $ctype, $allowedCTypes ) ) { $this->mContentType = 'text/x-wiki'; @@ -137,7 +134,13 @@ class RawPage { # allow the client to cache this for 24 hours $mode = $this->mPrivateCache ? 'private' : 'public'; header( 'Cache-Control: '.$mode.', s-maxage='.$this->mSmaxage.', max-age='.$this->mMaxage ); - echo $this->getRawText(); + $text = $this->getRawText(); + + if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) { + wfDebug( __METHOD__ . ': RawPageViewBeforeOutput hook broke raw page output.' ); + } + + echo $text; $wgOut->disable(); } @@ -189,6 +192,20 @@ class RawPage { header( "HTTP/1.0 404 Not Found" ); } + // Special-case for empty CSS/JS + // + // Internet Explorer for Mac handles empty files badly; + // particularly so when keep-alive is active. It can lead + // to long timeouts as it seems to sit there waiting for + // more data that never comes. + // + // Give it a comment... + if( strlen( $text ) == 0 && + ($this->mContentType == 'text/css' || + $this->mContentType == 'text/javascript' ) ) { + return "/* Empty */"; + } + return $this->parseArticleText( $text ); } diff --git a/includes/RecentChange.php b/includes/RecentChange.php index 1c7791c2..fced4343 100644 --- a/includes/RecentChange.php +++ b/includes/RecentChange.php @@ -1,7 +1,6 @@ <?php /** * - * @package MediaWiki */ /** @@ -39,7 +38,6 @@ * numberofWatchingusers * * @todo document functions and variables - * @package MediaWiki */ class RecentChange { @@ -49,7 +47,7 @@ class RecentChange # Factory methods - /* static */ function newFromRow( $row ) + public static function newFromRow( $row ) { $rc = new RecentChange; $rc->loadFromRow( $row ); @@ -72,7 +70,7 @@ class RecentChange * @return RecentChange */ public static function newFromId( $rcid ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'recentchanges', '*', array( 'rc_id' => $rcid ), __METHOD__ ); if( $res && $dbr->numRows( $res ) > 0 ) { $row = $dbr->fetchObject( $res ); @@ -118,7 +116,7 @@ class RecentChange global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPPort, $wgRC2UDPPrefix; $fname = 'RecentChange::save'; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); if ( !is_array($this->mExtra) ) { $this->mExtra = array(); } @@ -216,7 +214,7 @@ class RecentChange { $fname = 'RecentChange::markPatrolled'; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'recentchanges', array( /* SET */ @@ -504,6 +502,8 @@ class RecentChange function getIRCLine() { global $wgUseRCPatrol; + // FIXME: Would be good to replace these 2 extract() calls with something more explicit + // e.g. list ($rc_type, $rc_id) = array_values ($this->mAttribs); [or something like that] extract($this->mAttribs); extract($this->mExtra); diff --git a/includes/Revision.php b/includes/Revision.php index c5235e22..71f214e3 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -1,19 +1,17 @@ <?php /** - * @package MediaWiki * @todo document */ /** - * @package MediaWiki * @todo document */ class Revision { - const DELETED_TEXT = 1; - const DELETED_COMMENT = 2; - const DELETED_USER = 4; + const DELETED_TEXT = 1; + const DELETED_COMMENT = 2; + const DELETED_USER = 4; const DELETED_RESTRICTED = 8; - + /** * Load a page revision from a given revision ID number. * Returns null if no such revision can be found. @@ -79,7 +77,7 @@ class Revision { * @access public * @static */ - public static function loadFromPageId( &$db, $pageid, $id = 0 ) { + public static function loadFromPageId( $db, $pageid, $id = 0 ) { $conds=array('page_id=rev_page','rev_page'=>intval( $pageid ), 'page_id'=>intval( $pageid )); if( $id ) { $conds['rev_id']=intval($id); @@ -145,10 +143,10 @@ class Revision { * @static */ private static function newFromConds( $conditions ) { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $row = Revision::loadFromConds( $db, $conditions ); if( is_null( $row ) ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $row = Revision::loadFromConds( $dbw, $conditions ); } return $row; @@ -164,7 +162,7 @@ class Revision { * @access private * @static */ - private static function loadFromConds( &$db, $conditions ) { + private static function loadFromConds( $db, $conditions ) { $res = Revision::fetchFromConds( $db, $conditions ); if( $res ) { $row = $res->fetchObject(); @@ -226,7 +224,7 @@ class Revision { * @access private * @static */ - private static function fetchFromConds( &$db, $conditions ) { + private static function fetchFromConds( $db, $conditions ) { $res = $db->select( array( 'page', 'revision' ), array( 'page_namespace', @@ -240,7 +238,8 @@ class Revision { 'rev_user', 'rev_minor_edit', 'rev_timestamp', - 'rev_deleted' ), + 'rev_deleted', + 'rev_len' ), $conditions, 'Revision::fetchRow', array( 'LIMIT' => 1 ) ); @@ -249,6 +248,25 @@ class Revision { } /** + * Return the list of revision fields that should be selected to create + * a new revision. + */ + static function selectFields() { + return array( + 'rev_id', + 'rev_page', + 'rev_text_id', + 'rev_timestamp', + 'rev_comment', + 'rev_minor_edit', + 'rev_user', + 'rev_user_text,'. + 'rev_deleted', + 'rev_len' + ); + } + + /** * @param object $row * @access private */ @@ -263,6 +281,11 @@ class Revision { $this->mMinorEdit = intval( $row->rev_minor_edit ); $this->mTimestamp = $row->rev_timestamp; $this->mDeleted = intval( $row->rev_deleted ); + + if( !isset( $row->rev_len ) || is_null( $row->rev_len ) ) + $this->mSize = null; + else + $this->mSize = intval( $row->rev_len ); if( isset( $row->page_latest ) ) { $this->mCurrent = ( $row->rev_id == $row->page_latest ); @@ -293,7 +316,8 @@ class Revision { $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0; $this->mTimestamp = isset( $row['timestamp'] ) ? strval( $row['timestamp'] ) : wfTimestamp( TS_MW ); $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0; - + $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null; + // Enforce spacing trimming on supplied text $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null; $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null; @@ -301,6 +325,9 @@ class Revision { $this->mTitle = null; # Load on demand if needed $this->mCurrent = false; + # If we still have no len_size, see it we have the text to figure it out + if ( !$this->mSize ) + $this->mSize = is_null($this->mText) ? null : strlen($this->mText); } else { throw new MWException( 'Revision constructor passed invalid row format.' ); } @@ -325,6 +352,13 @@ class Revision { } /** + * Returns the length of the text in this revision, or null if unknown. + */ + function getSize() { + return $this->mSize; + } + + /** * Returns the title of the page associated with this entry. * @return Title */ @@ -332,7 +366,7 @@ class Revision { if( isset( $this->mTitle ) ) { return $this->mTitle; } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( array( 'page', 'revision' ), array( 'page_namespace', 'page_title' ), @@ -459,6 +493,18 @@ class Revision { } return $this->mText; } + + /** + * Fetch revision text if it's available to THIS user + * @return string + */ + function revText() { + if( !$this->userCan( self::DELETED_TEXT ) ) { + return ""; + } else { + return $this->getRawText(); + } + } /** * @return string @@ -565,7 +611,7 @@ class Revision { # Old revisions kept around in a legacy encoding? # Upconvert on demand. global $wgInputEncoding, $wgContLang; - $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding . '//IGNORE', $text ); + $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text ); } } wfProfileOut( $fname ); @@ -666,6 +712,7 @@ class Revision { 'rev_user_text' => $this->mUserText, 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ), 'rev_deleted' => $this->mDeleted, + 'rev_len' => $this->mSize, ), $fname ); @@ -706,7 +753,7 @@ class Revision { if( !$row ) { // Text data is immutable; check slaves first. - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'text', array( 'old_text', 'old_flags' ), array( 'old_id' => $this->getTextId() ), @@ -715,7 +762,7 @@ class Revision { if( !$row ) { // Possible slave lag! - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $row = $dbw->selectRow( 'text', array( 'old_text', 'old_flags' ), array( 'old_id' => $this->getTextId() ), @@ -802,12 +849,12 @@ class Revision { * @param integer $id */ static function getTimestampFromID( $id ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', array( 'rev_id' => $id ), __METHOD__ ); if ( $timestamp === false ) { # Not in slave, try master - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $timestamp = $dbw->selectField( 'revision', 'rev_timestamp', array( 'rev_id' => $id ), __METHOD__ ); } diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php index 0c0f7244..fa5416dc 100644 --- a/includes/Sanitizer.php +++ b/includes/Sanitizer.php @@ -20,8 +20,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * - * @package MediaWiki - * @subpackage Parser + * @addtogroup Parser */ /** @@ -29,7 +28,7 @@ * Sanitizer::normalizeCharReferences and Sanitizer::decodeCharReferences */ define( 'MW_CHAR_REFS_REGEX', - '/&([A-Za-z0-9]+); + '/&([A-Za-z0-9\x80-\xff]+); |&\#([0-9]+); |&\#x([0-9A-Za-z]+); |&\#X([0-9A-Za-z]+); @@ -316,7 +315,20 @@ $wgHtmlEntities = array( 'zwj' => 8205, 'zwnj' => 8204 ); -/** @package MediaWiki */ +/** + * Character entity aliases accepted by MediaWiki + */ +global $wgHtmlEntityAliases; +$wgHtmlEntityAliases = array( + 'רלמ' => 'rlm', + 'رلم' => 'rlm', +); + + +/** + * XHTML sanitizer for MediaWiki + * @addtogroup Parser + */ class Sanitizer { /** * Cleans up HTML, removes dangerous tags and attributes, and @@ -328,48 +340,41 @@ class Sanitizer { * @return string */ static function removeHTMLtags( $text, $processCallback = null, $args = array() ) { - global $wgUseTidy, $wgUserHtml; + global $wgUseTidy; - static $htmlpairs, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags, + static $htmlpairs, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags, $htmllist, $listtags, $htmlsingleallowed, $htmlelements, $staticInitialised; - + wfProfileIn( __METHOD__ ); - + if ( !$staticInitialised ) { - if( $wgUserHtml ) { - $htmlpairs = array( # Tags that must be closed - 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1', - 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's', - 'strike', 'strong', 'tt', 'var', 'div', 'center', - 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre', - 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'u' - ); - $htmlsingle = array( - 'br', 'hr', 'li', 'dt', 'dd' - ); - $htmlsingleonly = array( # Elements that cannot have close tags - 'br', 'hr' - ); - $htmlnest = array( # Tags that can be nested--?? - 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul', - 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span' - ); - $tabletags = array( # Can only appear inside table - 'td', 'th', 'tr', - ); - $htmllist = array( # Tags used by list - 'ul','ol', - ); - $listtags = array( # Tags that can appear in a list - 'li', - ); - - } else { - $htmlpairs = array(); - $htmlsingle = array(); - $htmlnest = array(); - $tabletags = array(); - } + + $htmlpairs = array( # Tags that must be closed + 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1', + 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's', + 'strike', 'strong', 'tt', 'var', 'div', 'center', + 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre', + 'ruby', 'rt' , 'rb' , 'rp', 'p', 'span', 'u' + ); + $htmlsingle = array( + 'br', 'hr', 'li', 'dt', 'dd' + ); + $htmlsingleonly = array( # Elements that cannot have close tags + 'br', 'hr' + ); + $htmlnest = array( # Tags that can be nested--?? + 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul', + 'dl', 'font', 'big', 'small', 'sub', 'sup', 'span' + ); + $tabletags = array( # Can only appear inside table, we will close them + 'td', 'th', 'tr', + ); + $htmllist = array( # Tags used by list + 'ul','ol', + ); + $listtags = array( # Tags that can appear in a list + 'li', + ); $htmlsingleallowed = array_merge( $htmlsingle, $tabletags ); $htmlelements = array_merge( $htmlsingle, $htmlpairs, $htmlnest ); @@ -386,7 +391,7 @@ class Sanitizer { # Remove HTML comments $text = Sanitizer::removeHTMLcomments( $text ); $bits = explode( '<', $text ); - $text = array_shift( $bits ); + $text = str_replace( '>', '>', array_shift( $bits ) ); if(!$wgUseTidy) { $tagstack = $tablestack = array(); foreach ( $bits as $x ) { @@ -396,7 +401,7 @@ class Sanitizer { } else { $slash = $t = $params = $brace = $rest = null; } - + $badtag = 0 ; if ( isset( $htmlelements[$t = strtolower( $t )] ) ) { # Check our stack @@ -453,6 +458,10 @@ class Sanitizer { } else if( isset( $htmlsingle[$t] ) ) { # Hack to not close $htmlsingle tags $brace = NULL; + } else if( isset( $tabletags[$t] ) + && in_array($t ,$tagstack) ) { + // New table tag but forgot to close the previous one + $text .= "</$t>"; } else { if ( $t == 'table' ) { array_push( $tablestack, $tagstack ); @@ -472,7 +481,7 @@ class Sanitizer { } if ( ! $badtag ) { $rest = str_replace( '>', '>', $rest ); - $close = ( $brace == '/>' ) ? ' /' : ''; + $close = ( $brace == '/>' && !$slash ) ? ' /' : ''; $text .= "<$slash$t$newparams$close>$rest"; continue; } @@ -613,7 +622,7 @@ class Sanitizer { $stripped = preg_replace( '!\\\\([0-9A-Fa-f]{1,6})[ \\n\\r\\t\\f]?!e', 'codepointToUtf8(hexdec("$1"))', $stripped ); $stripped = str_replace( '\\', '', $stripped ); - if( preg_match( '/(expression|tps*:\/\/|url\\s*\().*/is', + if( preg_match( '/(?:expression|tps*:\/\/|url\\s*\().*/is', $stripped ) ) { # haxx0r return false; @@ -645,15 +654,15 @@ class Sanitizer { if( trim( $text ) == '' ) { return ''; } - + $stripped = Sanitizer::validateTagAttributes( Sanitizer::decodeTagAttributes( $text ), $element ); - + $attribs = array(); foreach( $stripped as $attribute => $value ) { $encAttribute = htmlspecialchars( $attribute ); $encValue = Sanitizer::safeEncodeAttribute( $value ); - + $attribs[] = "$encAttribute=\"$encValue\""; } return count( $attribs ) ? ' ' . implode( ' ', $attribs ) : ''; @@ -666,7 +675,7 @@ class Sanitizer { */ static function encodeAttribute( $text ) { $encValue = htmlspecialchars( $text ); - + // Whitespace is normalized during attribute decoding, // so if we've been passed non-spaces we must encode them // ahead of time or they won't be preserved. @@ -675,10 +684,10 @@ class Sanitizer { "\r" => ' ', "\t" => '	', ) ); - + return $encValue; } - + /** * Encode an attribute value for HTML tags, with extra armoring * against further wiki processing. @@ -687,7 +696,7 @@ class Sanitizer { */ static function safeEncodeAttribute( $text ) { $encValue = Sanitizer::encodeAttribute( $text ); - + # Templates and links may be expanded in later parsing, # creating invalid or dangerous output. Suppress this. $encValue = strtr( $encValue, array( @@ -716,12 +725,10 @@ class Sanitizer { * Given a value escape it so that it can be used in an id attribute and * return it, this does not validate the value however (see first link) * - * @link http://www.w3.org/TR/html401/types.html#type-name Valid characters + * @see http://www.w3.org/TR/html401/types.html#type-name Valid characters * in the id and * name attributes - * @link http://www.w3.org/TR/html401/struct/links.html#h-12.2.3 Anchors with the id attribute - * - * @bug 4461 + * @see http://www.w3.org/TR/html401/struct/links.html#h-12.2.3 Anchors with the id attribute * * @static * @@ -743,9 +750,9 @@ class Sanitizer { * Given a value, escape it so that it can be used as a CSS class and * return it. * - * TODO: For extra validity, input should be validated UTF-8. + * @todo For extra validity, input should be validated UTF-8. * - * @link http://www.w3.org/TR/CSS21/syndata.html Valid characters/format + * @see http://www.w3.org/TR/CSS21/syndata.html Valid characters/format * * @param string $class * @return string @@ -795,11 +802,11 @@ class Sanitizer { foreach( $pairs as $set ) { $attribute = strtolower( $set[1] ); $value = Sanitizer::getTagAttributeCallback( $set ); - + // Normalize whitespace $value = preg_replace( '/[\t\r\n ]+/', ' ', $value ); $value = trim( $value ); - + // Decode character references $attribs[$attribute] = Sanitizer::decodeCharReferences( $value ); } @@ -850,11 +857,16 @@ class Sanitizer { */ private static function normalizeAttributeValue( $text ) { return str_replace( '"', '"', - preg_replace( - '/\r\n|[\x20\x0d\x0a\x09]/', - ' ', + self::normalizeWhitespace( Sanitizer::normalizeCharReferences( $text ) ) ); } + + private static function normalizeWhitespace( $text ) { + return preg_replace( + '/\r\n|[\x20\x0d\x0a\x09]/', + ' ', + $text ); + } /** * Ensure that any entities and character references are legal @@ -900,16 +912,19 @@ class Sanitizer { /** * If the named entity is defined in the HTML 4.0/XHTML 1.0 DTD, - * return the named entity reference as is. Otherwise, returns - * HTML-escaped text of pseudo-entity source (eg &foo;) + * return the named entity reference as is. If the entity is a + * MediaWiki-specific alias, returns the HTML equivalent. Otherwise, + * returns HTML-escaped text of pseudo-entity source (eg &foo;) * * @param string $name * @return string * @static */ static function normalizeEntity( $name ) { - global $wgHtmlEntities; - if( isset( $wgHtmlEntities[$name] ) ) { + global $wgHtmlEntities, $wgHtmlEntityAliases; + if ( isset( $wgHtmlEntityAliases[$name] ) ) { + return "&{$wgHtmlEntityAliases[$name]};"; + } elseif( isset( $wgHtmlEntities[$name] ) ) { return "&$name;"; } else { return "&$name;"; @@ -1006,7 +1021,10 @@ class Sanitizer { * @return string */ static function decodeEntity( $name ) { - global $wgHtmlEntities; + global $wgHtmlEntities, $wgHtmlEntityAliases; + if ( isset( $wgHtmlEntityAliases[$name] ) ) { + $name = $wgHtmlEntityAliases[$name]; + } if( isset( $wgHtmlEntities[$name] ) ) { return codepointToUtf8( $wgHtmlEntities[$name] ); } else { @@ -1173,8 +1191,10 @@ class Sanitizer { /** * Take a fragment of (potentially invalid) HTML and return - * a version with any tags removed, encoded suitably for literal - * inclusion in an attribute value. + * a version with any tags removed, encoded as plain text. + * + * Warning: this return value must be further escaped for literal + * inclusion in HTML output as of 1.10! * * @param string $text HTML fragment * @return string @@ -1184,14 +1204,8 @@ class Sanitizer { $text = StringUtils::delimiterReplace( '<', '>', '', $text ); # Normalize &entities and whitespace - $text = Sanitizer::normalizeAttributeValue( $text ); - - # Will be placed into "double-quoted" attributes, - # make sure remaining bits are safe. - $text = str_replace( - array('<', '>', '"'), - array('<', '>', '"'), - $text ); + $text = self::decodeCharReferences( $text ); + $text = self::normalizeWhitespace( $text ); return $text; } @@ -1215,7 +1229,7 @@ class Sanitizer { $out .= "]>\n"; return $out; } - + static function cleanUrl( $url, $hostname=true ) { # Normalize any HTML entities in input. They will be # re-escaped by makeExternalLink(). @@ -1223,12 +1237,12 @@ class Sanitizer { # Escape any control characters introduced by the above step $url = preg_replace( '/[\][<>"\\x00-\\x20\\x7F]/e', "urlencode('\\0')", $url ); - + # Validate hostname portion $matches = array(); if( preg_match( '!^([^:]+:)(//[^/]+)?(.*)$!iD', $url, $matches ) ) { list( /* $whole */, $protocol, $host, $rest ) = $matches; - + // Characters that will be ignored in IDNs. // http://tools.ietf.org/html/3454#section-3.1 // Strip them before further processing so blacklists and such work. @@ -1247,11 +1261,11 @@ class Sanitizer { \xe2\x80\x8d| # 200d ZERO WIDTH JOINER [\xef\xb8\x80-\xef\xb8\x8f] # fe00-fe00f VARIATION SELECTOR-1-16 /xuD"; - + $host = preg_replace( $strip, '', $host ); - + // @fixme: validate hostnames here - + return $protocol . $host . $rest; } else { return $url; diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php index cec40c91..24795ba9 100644 --- a/includes/SearchEngine.php +++ b/includes/SearchEngine.php @@ -1,12 +1,7 @@ <?php /** * Contain a class for special pages - * @package MediaWiki - * @subpackage Search - */ - -/** - * @package MediaWiki + * @addtogroup Search */ class SearchEngine { var $limit = 10; @@ -124,17 +119,33 @@ class SearchEngine { if ( $title->getNamespace() == NS_USER ) { return $title; } + + # Go to images that exist even if there's no local page. + # There may have been a funny upload, or it may be on a shared + # file repository such as Wikimedia Commons. + if( $title->getNamespace() == NS_IMAGE ) { + $image = new Image( $title ); + if( $image->exists() ) { + return $title; + } + } + + # MediaWiki namespace? Page may be "implied" if not customized. + # Just return it, with caps forced as the message system likes it. + if( $title->getNamespace() == NS_MEDIAWIKI ) { + return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( $title->getText() ) ); + } # Quoted term? Try without the quotes... $matches = array(); if( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) { return SearchEngine::getNearMatch( $matches[1] ); } - + return NULL; } - function legalSearchChars() { + public static function legalSearchChars() { return "A-Za-z_'0-9\\x80-\\xFF\\-"; } @@ -193,9 +204,8 @@ class SearchEngine { * active database backend, and return a configured instance. * * @return SearchEngine - * @private */ - function create() { + public static function create() { global $wgDBtype, $wgSearchType; if( $wgSearchType ) { $class = $wgSearchType; @@ -203,6 +213,8 @@ class SearchEngine { $class = 'SearchMySQL4'; } else if ( $wgDBtype == 'postgres' ) { $class = 'SearchPostgres'; + } else if ( $wgDBtype == 'oracle' ) { + $class = 'SearchOracle'; } else { $class = 'SearchEngineDummy'; } @@ -232,12 +244,15 @@ class SearchEngine { * @param string $title * @abstract */ - function updateTitle( $id, $title ) { + function updateTitle( $id, $title ) { // no-op - } + } } -/** @package MediaWiki */ + +/** + * @addtogroup Search + */ class SearchResultSet { /** * Fetch an array of regular expression fragments for matching @@ -312,7 +327,10 @@ class SearchResultSet { } } -/** @package MediaWiki */ + +/** + * @addtogroup Search + */ class SearchResult { function SearchResult( $row ) { $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title ); @@ -335,7 +353,7 @@ class SearchResult { } /** - * @package MediaWiki + * @addtogroup Search */ class SearchEngineDummy { function search( $term ) { diff --git a/includes/SearchMySQL.php b/includes/SearchMySQL.php index 15515952..0e02a684 100644 --- a/includes/SearchMySQL.php +++ b/includes/SearchMySQL.php @@ -20,11 +20,8 @@ /** * Search engine hook base class for MySQL. * Specific bits for MySQL 3 and 4 variants are in child classes. - * @package MediaWiki - * @subpackage Search + * @addtogroup Search */ - -/** @package MediaWiki */ class SearchMySQL extends SearchEngine { /** * Perform a full text search query and return a result set. @@ -150,7 +147,7 @@ class SearchMySQL extends SearchEngine { * @param string $text */ function update( $id, $title, $text ) { - $dbw=& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->replace( 'searchindex', array( 'si_page' ), array( @@ -168,7 +165,7 @@ class SearchMySQL extends SearchEngine { * @param string $title */ function updateTitle( $id, $title ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'searchindex', array( 'si_title' => $title ), @@ -178,7 +175,9 @@ class SearchMySQL extends SearchEngine { } } -/** @package MediaWiki */ +/** + * @addtogroup Search + */ class MySQLSearchResultSet extends SearchResultSet { function MySQLSearchResultSet( $resultSet, $terms ) { $this->mResultSet = $resultSet; diff --git a/includes/SearchMySQL4.php b/includes/SearchMySQL4.php index c20e3f8e..97ce3850 100644 --- a/includes/SearchMySQL4.php +++ b/includes/SearchMySQL4.php @@ -19,20 +19,14 @@ /** * Search engine hook for MySQL 4+ - * @package MediaWiki - * @subpackage Search - */ - -/** - * @package MediaWiki - * @subpackage Search + * @addtogroup Search */ class SearchMySQL4 extends SearchMySQL { var $strictMatching = true; /** @todo document */ - function SearchMySQL4( &$db ) { - $this->db =& $db; + function SearchMySQL4( $db ) { + $this->db = $db; } /** @todo document */ diff --git a/includes/SearchOracle.php b/includes/SearchOracle.php new file mode 100644 index 00000000..c9a675e6 --- /dev/null +++ b/includes/SearchOracle.php @@ -0,0 +1,235 @@ +<?php +# Copyright (C) 2004 Brion Vibber <brion@pobox.com> +# http://www.mediawiki.org/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# http://www.gnu.org/copyleft/gpl.html + +/** + * Search engine hook base class for Oracle (ConText). + * @addtogroup Search + */ +class SearchOracle extends SearchEngine { + function __construct($db) { + $this->db = $db; + } + + /** + * Perform a full text search query and return a result set. + * + * @param string $term - Raw search term + * @return OracleSearchResultSet + * @access public + */ + function searchText( $term ) { + $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), true))); + return new OracleSearchResultSet($resultSet, $this->searchTerms); + } + + /** + * Perform a title-only search query and return a result set. + * + * @param string $term - Raw search term + * @return ORacleSearchResultSet + * @access public + */ + function searchTitle($term) { + $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), false))); + return new MySQLSearchResultSet($resultSet, $this->searchTerms); + } + + + /** + * Return a partial WHERE clause to exclude redirects, if so set + * @return string + * @private + */ + function queryRedirect() { + if ($this->showRedirects) { + return ''; + } else { + return 'AND page_is_redirect=0'; + } + } + + /** + * Return a partial WHERE clause to limit the search to the given namespaces + * @return string + * @private + */ + function queryNamespaces() { + $namespaces = implode(',', $this->namespaces); + if ($namespaces == '') { + $namespaces = '0'; + } + return 'AND page_namespace IN (' . $namespaces . ')'; + } + + /** + * Return a LIMIT clause to limit results on the query. + * @return string + * @private + */ + function queryLimit($sql) { + return $this->db->limitResult($sql, $this->limit, $this->offset); + } + + /** + * Does not do anything for generic search engine + * subclasses may define this though + * @return string + * @private + */ + function queryRanking($filteredTerm, $fulltext) { + return ' ORDER BY score(1)'; + } + + /** + * Construct the full SQL query to do the search. + * The guts shoulds be constructed in queryMain() + * @param string $filteredTerm + * @param bool $fulltext + * @private + */ + function getQuery( $filteredTerm, $fulltext ) { + return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' . + $this->queryRedirect() . ' ' . + $this->queryNamespaces() . ' ' . + $this->queryRanking( $filteredTerm, $fulltext ) . ' '); + } + + + /** + * Picks which field to index on, depending on what type of query. + * @param bool $fulltext + * @return string + */ + function getIndexField($fulltext) { + return $fulltext ? 'si_text' : 'si_title'; + } + + /** + * Get the base part of the search query. + * + * @param string $filteredTerm + * @param bool $fulltext + * @return string + * @private + */ + function queryMain( $filteredTerm, $fulltext ) { + $match = $this->parseQuery($filteredTerm, $fulltext); + $page = $this->db->tableName('page'); + $searchindex = $this->db->tableName('searchindex'); + return 'SELECT page_id, page_namespace, page_title ' . + "FROM $page,$searchindex " . + 'WHERE page_id=si_page AND ' . $match; + } + + /** @todo document */ + function parseQuery($filteredText, $fulltext) { + global $wgContLang; + $lc = SearchEngine::legalSearchChars(); + $this->searchTerms = array(); + + # FIXME: This doesn't handle parenthetical expressions. + $m = array(); + $q = array(); + + if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', + $filteredText, $m, PREG_SET_ORDER)) { + foreach($m as $terms) { + $q[] = $terms[1] . $wgContLang->stripForSearch($terms[2]); + + if (!empty($terms[3])) { + $regexp = preg_quote( $terms[3], '/' ); + if ($terms[4]) + $regexp .= "[0-9A-Za-z_]+"; + } else { + $regexp = preg_quote(str_replace('"', '', $terms[2]), '/'); + } + $this->searchTerms[] = $regexp; + } + } + + $searchon = $this->db->strencode(join(',', $q)); + $field = $this->getIndexField($fulltext); + return " CONTAINS($field, '$searchon', 1) > 0 "; + } + + /** + * Create or update the search index record for the given page. + * Title and text should be pre-processed. + * + * @param int $id + * @param string $title + * @param string $text + */ + function update($id, $title, $text) { + $dbw = wfGetDB(DB_MASTER); + $dbw->replace('searchindex', + array('si_page'), + array( + 'si_page' => $id, + 'si_title' => $title, + 'si_text' => $text + ), 'SearchOracle::update' ); + $dbw->query("CALL ctx_ddl.sync_index('si_text_idx')"); + $dbw->query("CALL ctx_ddl.sync_index('si_title_idx')"); + } + + /** + * Update a search index record's title only. + * Title should be pre-processed. + * + * @param int $id + * @param string $title + */ + function updateTitle($id, $title) { + $dbw = wfGetDB(DB_MASTER); + + $dbw->update('searchindex', + array('si_title' => $title), + array('si_page' => $id), + 'SearchOracle::updateTitle', + array()); + } +} + +/** + * @addtogroup Search + */ +class OracleSearchResultSet extends SearchResultSet { + function __construct($resultSet, $terms) { + $this->mResultSet = $resultSet; + $this->mTerms = $terms; + } + + function termMatches() { + return $this->mTerms; + } + + function numRows() { + return $this->mResultSet->numRows(); + } + + function next() { + $row = $this->mResultSet->fetchObject(); + if ($row === false) + return false; + return new SearchResult($row); + } +} + +?> diff --git a/includes/SearchPostgres.php b/includes/SearchPostgres.php index 457636b4..3a624ced 100644 --- a/includes/SearchPostgres.php +++ b/includes/SearchPostgres.php @@ -1,5 +1,5 @@ <?php -# Copyright (C) 2006 Greg Sabino Mullane <greg@turnstep.com> +# Copyright (C) 2006-2007 Greg Sabino Mullane <greg@turnstep.com> # http://www.mediawiki.org/ # # This program is free software; you can redistribute it and/or modify @@ -17,35 +17,31 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html -## XXX Better catching of SELECT to_tsquery('the') - /** * Search engine hook base class for Postgres - * @package MediaWiki - * @subpackage Search + * @addtogroup Search */ - -/** @package MediaWiki */ class SearchPostgres extends SearchEngine { - function SearchPostgres( &$db ) { - $this->db =& $db; + function SearchPostgres( $db ) { + $this->db = $db; } /** * Perform a full text search query via tsearch2 and return a result set. - * Currently searches a page's current title (p.page_title) and text (t.old_text) + * Currently searches a page's current title (page.page_title) and + * latest revision article text (pagecontent.old_text) * * @param string $term - Raw search term * @return PostgresSearchResultSet * @access public */ - function searchText( $term ) { - $resultSet = $this->db->resultObject( $this->db->query( $this->searchQuery( $term, 'textvector' ) ) ); + function searchTitle( $term ) { + $resultSet = $this->db->resultObject( $this->db->query( $this->searchQuery( $term , 'titlevector', 'page_title' ))); return new PostgresSearchResultSet( $resultSet, $this->searchTerms ); } - function searchTitle( $term ) { - $resultSet = $this->db->resultObject( $this->db->query( $this->searchQuery( $term , 'titlevector' ) ) ); + function searchText( $term ) { + $resultSet = $this->db->resultObject( $this->db->query( $this->searchQuery( $term, 'textvector', 'old_text' ))); return new PostgresSearchResultSet( $resultSet, $this->searchTerms ); } @@ -53,39 +49,63 @@ class SearchPostgres extends SearchEngine { /* * Transform the user's search string into a better form for tsearch2 */ - function parseQuery( $filteredText, $fulltext ) { - global $wgContLang; - $lc = SearchEngine::legalSearchChars(); - $searchon = ''; - $this->searchTerms = array(); - - # FIXME: This doesn't handle parenthetical expressions. - $m = array(); - if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', - $filteredText, $m, PREG_SET_ORDER ) ) { + function parseQuery( $term ) { + + wfDebug( "parseQuery received: $term" ); + + ## No backslashes allowed + $term = preg_replace('/\\\/', '', $term); + + ## Collapse parens into nearby words: + $term = preg_replace('/\s*\(\s*/', ' (', $term); + $term = preg_replace('/\s*\)\s*/', ') ', $term); + + ## Treat colons as word separators: + $term = preg_replace('/:/', ' ', $term); + + $searchstring = ''; + if( preg_match_all('/([-!]?)(\S+)\s*/', $term, $m, PREG_SET_ORDER ) ) { foreach( $m as $terms ) { - if( $searchon !== '' ) $searchon .= ' '; - if($terms[1] == '') { - $terms[1] = '+'; + if (strlen($terms[1])) { + $searchstring .= ' & !'; } - $searchon .= $terms[1] . $wgContLang->stripForSearch( $terms[2] ); - if( !empty( $terms[3] ) ) { - $regexp = preg_quote( $terms[3], '/' ); - if( $terms[4] ) $regexp .= "[0-9A-Za-z_]+"; - } else { - $regexp = preg_quote( str_replace( '"', '', $terms[2] ), '/' ); + if (strtolower($terms[2]) === 'and') { + $searchstring .= ' & '; + } + else if (strtolower($terms[2]) === 'or' or $terms[2] === '|') { + $searchstring .= ' | '; + } + else if (strtolower($terms[2]) === 'not') { + $searchstring .= ' & !'; + } + else { + $searchstring .= " & $terms[2]"; } - $this->searchTerms[] = $regexp; } - wfDebug( "Would search with '$searchon'\n" ); - wfDebug( 'Match with /\b' . implode( '\b|\b', $this->searchTerms ) . "\b/\n" ); - } else { - wfDebug( "Can't understand search query '{$this->filteredText}'\n" ); } - $searchon = preg_replace('/(\s+)/','&',$searchon); - $searchon = $this->db->strencode( $searchon ); - return $searchon; + ## Strip out leading junk + $searchstring = preg_replace('/^[\s\&\|]+/', '', $searchstring); + + ## Remove any doubled-up operators + $searchstring = preg_replace('/([\!\&\|]) +(?:[\&\|] +)+/', "$1 ", $searchstring); + + ## Remove any non-spaced operators (e.g. "Zounds!") + $searchstring = preg_replace('/([^ ])[\!\&\|]/', "$1", $searchstring); + + ## Remove any trailing whitespace or operators + $searchstring = preg_replace('/[\s\!\&\|]+$/', '', $searchstring); + + ## Remove unnecessary quotes around everything + $searchstring = preg_replace('/^[\'"](.*)[\'"]$/', "$1", $searchstring); + + ## Quote the whole thing + $searchstring = $this->db->addQuotes($searchstring); + + wfDebug( "parseQuery returned: $searchstring" ); + + return $searchstring; + } /** @@ -94,14 +114,37 @@ class SearchPostgres extends SearchEngine { * @param string $fulltext * @private */ - function searchQuery( $filteredTerm, $fulltext ) { + function searchQuery( $term, $fulltext, $colname ) { + + $searchstring = $this->parseQuery( $term ); - $match = $this->parseQuery( $filteredTerm, $fulltext ); + ## We need a separate query here so gin does not complain about empty searches + $SQL = "SELECT to_tsquery('default',$searchstring)"; + $res = $this->db->doQuery($SQL); + if (!$res) { + ## TODO: Better output (example to catch: one 'two) + die ("Sorry, that was not a valid search string. Please go back and try again"); + } + $top = pg_fetch_result($res,0,0); - $query = "SELECT page_id, page_namespace, page_title, old_text AS page_text, ". - "rank(titlevector, to_tsquery('default','$match')) AS rnk ". + if ($top === "") { ## e.g. if only stopwords are used XXX return something better + $query = "SELECT page_id, page_namespace, page_title, 0 AS score ". + "FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " . + "AND r.rev_text_id = c.old_id AND 1=0"; + } + else { + $m = array(); + if( preg_match_all("/'([^']+)'/", $top, $m, PREG_SET_ORDER ) ) { + foreach( $m as $terms ) { + $this->searchTerms[$terms[1]] = $terms[1]; + } + } + + $query = "SELECT page_id, page_namespace, page_title, ". + "rank($fulltext, to_tsquery('default',$searchstring),5) AS score ". "FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " . - "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery('default','$match')"; + "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery('default',$searchstring)"; + } ## Redirects if (! $this->showRedirects) @@ -112,25 +155,51 @@ class SearchPostgres extends SearchEngine { $query .= ' AND page_namespace = 0'; else { $namespaces = implode( ',', $this->namespaces ); - $query .= " AND page_namespace IN ($namespaces)"; + $query .= " AND page_namespace IN ($namespaces)"; } - $query .= " ORDER BY rnk DESC, page_id DESC"; + $query .= " ORDER BY score DESC, page_id DESC"; $query .= $this->db->limitResult( '', $this->limit, $this->offset ); + wfDebug( "searchQuery returned: $query" ); + return $query; } - ## These two functions are done automatically via triggers + ## Most of the work of these two functions are done automatically via triggers - function update( $id, $title, $text ) { return true; } - function updateTitle( $id, $title ) { return true; } + function update( $pageid, $title, $text ) { + ## We don't want to index older revisions + $SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id = ". + "(SELECT rev_text_id FROM revision WHERE rev_page = $pageid ". + "ORDER BY rev_text_id DESC LIMIT 1 OFFSET 1)"; + $this->db->doQuery($SQL); + return true; + } + + function updateTitle( $id, $title ) { + return true; + } } ## end of the SearchPostgres class +/** + * @addtogroup Search + */ +class PostgresSearchResult extends SearchResult { + function PostgresSearchResult( $row ) { + $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title ); + $this->score = $row->score; + } + function getScore() { + return $this->score; + } +} -/** @package MediaWiki */ +/** + * @addtogroup Search + */ class PostgresSearchResultSet extends SearchResultSet { function PostgresSearchResultSet( $resultSet, $terms ) { $this->mResultSet = $resultSet; @@ -150,9 +219,10 @@ class PostgresSearchResultSet extends SearchResultSet { if( $row === false ) { return false; } else { - return new SearchResult( $row ); + return new PostgresSearchResult( $row ); } } } + ?> diff --git a/includes/SearchTsearch2.php b/includes/SearchTsearch2.php index 1fca9899..b504f034 100644 --- a/includes/SearchTsearch2.php +++ b/includes/SearchTsearch2.php @@ -19,14 +19,12 @@ /** * Search engine hook for PostgreSQL / Tsearch2 - * @package MediaWiki - * @subpackage Search + * @addtogroup Search */ /** * @todo document - * @package MediaWiki - * @subpackage Search + * @addtogroup Search */ class SearchTsearch2 extends SearchEngine { var $strictMatching = false; @@ -97,7 +95,7 @@ class SearchTsearch2 extends SearchEngine { } function update( $id, $title, $text ) { - $dbw=& wfGetDB(DB_MASTER); + $dbw = wfGetDB(DB_MASTER); $searchindex = $dbw->tableName( 'searchindex' ); $sql = "DELETE FROM $searchindex WHERE si_page={$id}"; $dbw->query($sql,"SearchTsearch2:update"); @@ -110,13 +108,13 @@ class SearchTsearch2 extends SearchEngine { } function updateTitle($id,$title) { - $dbw=& wfGetDB(DB_MASTER); - $searchindex = $dbw->tableName( 'searchindex' ); - $sql = "UPDATE $searchindex SET si_title=to_tsvector('" . - $dbw->strencode( $title ) . - "') WHERE si_page={$id}"; + $dbw = wfGetDB(DB_MASTER); + $searchindex = $dbw->tableName( 'searchindex' ); + $sql = "UPDATE $searchindex SET si_title=to_tsvector('" . + $dbw->strencode( $title ) . + "') WHERE si_page={$id}"; - $dbw->query( $sql, "SearchMySQL4::updateTitle" ); + $dbw->query( $sql, "SearchMySQL4::updateTitle" ); } } diff --git a/includes/SearchUpdate.php b/includes/SearchUpdate.php index 37981a67..724197c1 100644 --- a/includes/SearchUpdate.php +++ b/includes/SearchUpdate.php @@ -1,12 +1,7 @@ <?php /** * See deferred.txt - * @package MediaWiki - */ - -/** - * - * @package MediaWiki + * @addtogroup Search */ class SearchUpdate { @@ -38,7 +33,7 @@ class SearchUpdate { wfProfileIn( $fname ); $search = SearchEngine::create(); - $lc = $search->legalSearchChars() . '&#;'; + $lc = SearchEngine::legalSearchChars() . '&#;'; if( $this->mText === false ) { $search->updateTitle($this->mId, @@ -98,15 +93,20 @@ class SearchUpdate { # Strip wiki '' and ''' $text = preg_replace( "/''[']*/", " ", $text ); wfProfileOut( "$fname-regexps" ); + + wfRunHooks( 'SearchUpdate', array( $this->mId, $this->mNamespace, $this->mTitle, &$text ) ); + + # Perform the actual update $search->update($this->mId, Title::indexTitle( $this->mNamespace, $this->mTitle ), $text); + wfProfileOut( $fname ); } } /** * Placeholder class - * @package MediaWiki + * @addtogroup Search */ class SearchUpdateMyISAM extends SearchUpdate { # Inherits everything diff --git a/includes/Setup.php b/includes/Setup.php index 80a5b48a..47ba494f 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -1,7 +1,6 @@ <?php /** * Include most things that's need to customize the site - * @package MediaWiki */ /** @@ -134,13 +133,14 @@ if ( $wgDBprefix ) { } else { $wgCookiePrefix = $wgDBname; } +$wgCookiePrefix = strtr($wgCookiePrefix, "=,; +.\"'\\[", "__________"); # If session.auto_start is there, we can't touch session name # if( !ini_get( 'session.auto_start' ) ) session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' ); -if( !$wgCommandLineMode && ( isset( $_COOKIE[session_name()] ) || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) { +if( !$wgCommandLineMode && ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) { wfIncrStats( 'request_with_session' ); wfSetupSession(); $wgSessionStarted = true; diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php index 8fd5d6b6..0968460c 100644 --- a/includes/SiteConfiguration.php +++ b/includes/SiteConfiguration.php @@ -1,9 +1,4 @@ <?php -/** - * This is a class used to hold configuration settings, particularly for multi-wiki sites. - * - * @package MediaWiki - */ /** * The include paths change after this file is included from commandLine.inc, @@ -13,7 +8,10 @@ if (!defined('SITE_CONFIGURATION')) { define('SITE_CONFIGURATION', 1); -/** @package MediaWiki */ +/** + * This is a class used to hold configuration settings, particularly for multi-wiki sites. + * + */ class SiteConfiguration { var $suffixes = array(); var $wikis = array(); diff --git a/includes/SiteStats.php b/includes/SiteStats.php index e2774a14..e320a196 100644 --- a/includes/SiteStats.php +++ b/includes/SiteStats.php @@ -2,7 +2,6 @@ /** * Static accessor class for site_stats and related things - * @package MediaWiki */ class SiteStats { static $row, $loaded = false; @@ -18,17 +17,54 @@ class SiteStats { return; } - $dbr =& wfGetDB( DB_SLAVE ); - self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ ); + self::$row = self::loadAndLazyInit(); # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) { # Update schema $u = new SiteStatsUpdate( 0, 0, 0 ); $u->doUpdate(); + $dbr = wfGetDB( DB_SLAVE ); self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ ); } } + + static function loadAndLazyInit() { + wfDebug( __METHOD__ . ": reading site_stats from slave\n" ); + $row = self::doLoad( wfGetDB( DB_SLAVE ) ); + + if( $row === false ) { + // Might have just been initialzed during this request? + wfDebug( __METHOD__ . ": site_stats missing on slave\n" ); + $row = self::doLoad( wfGetDB( DB_MASTER ) ); + } + + if( $row === false ) { + // Normally the site_stats table is initialized at install time. + // Some manual construction scenarios may leave the table empty, + // however, for instance when importing from a dump into a clean + // schema with mwdumper. + wfDebug( __METHOD__ . ": initializing empty site_stats\n" ); + + global $IP; + require_once "$IP/maintenance/initStats.inc"; + + ob_start(); + wfInitStats(); + ob_end_clean(); + + $row = self::doLoad( wfGetDB( DB_MASTER ) ); + } + + if( $row === false ) { + wfDebug( __METHOD__ . ": init of site_stats failed o_O\n" ); + } + return $row; + } + + static function doLoad( $db ) { + return $db->selectRow( 'site_stats', '*', false, __METHOD__ ); + } static function views() { self::load(); @@ -62,7 +98,7 @@ class SiteStats { static function admins() { if ( !isset( self::$admins ) ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); self::$admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), __METHOD__ ); } return self::$admins; @@ -71,7 +107,7 @@ class SiteStats { static function pagesInNs( $ns ) { wfProfileIn( __METHOD__ ); if( !isset( self::$pageCount[$ns] ) ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $pageCount[$ns] = (int)$dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), __METHOD__ ); } wfProfileOut( __METHOD__ ); @@ -83,13 +119,12 @@ class SiteStats { /** * - * @package MediaWiki */ class SiteStatsUpdate { var $mViews, $mEdits, $mGood, $mPages, $mUsers; - function SiteStatsUpdate( $views, $edits, $good, $pages = 0, $users = 0 ) { + function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) { $this->mViews = $views; $this->mEdits = $edits; $this->mGood = $good; @@ -112,7 +147,7 @@ class SiteStatsUpdate { function doUpdate() { $fname = 'SiteStatsUpdate::doUpdate'; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); # First retrieve the row just to find out which schema we're in $row = $dbw->selectRow( 'site_stats', '*', false, $fname ); @@ -126,7 +161,7 @@ class SiteStatsUpdate { if ( isset( $row->ss_total_pages ) ) { # Update schema if required if ( $row->ss_total_pages == -1 && !$this->mViews ) { - $dbr =& wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') ); + $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') ); list( $page, $user ) = $dbr->tableNamesN( 'page', 'user' ); $sql = "SELECT COUNT(page_namespace) AS total FROM $page"; diff --git a/includes/Skin.php b/includes/Skin.php index f8e733ef..0ca95f7e 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -2,18 +2,15 @@ if ( ! defined( 'MEDIAWIKI' ) ) die( 1 ); -/** - * - * @package MediaWiki - * @subpackage Skins - */ - # See skin.txt /** * The main skin class that provide methods and properties for all other skins. * This base class is also the "Standard" skin. - * @package MediaWiki + * + * See docs/skin.txt for more information. + * + * @addtogroup Skins */ class Skin extends Linker { /**#@+ @@ -25,16 +22,17 @@ class Skin extends Linker { var $rcMoveIndex; var $mWatchLinkNum = 0; // Appended to end of watch link id's /**#@-*/ + protected $skinname = 'standard' ; /** Constructor, call parent constructor */ - function Skin() { parent::Linker(); } + function Skin() { parent::__construct(); } /** * Fetch the set of available skins. * @return array of strings * @static */ - static function &getSkinNames() { + static function getSkinNames() { global $wgValidSkinNames; static $skinsInitialised = false; if ( !$skinsInitialised ) { @@ -144,8 +142,8 @@ class Skin extends Linker { } /** @return string skin name */ - function getSkinName() { - return 'standard'; + public function getSkinName() { + return $this->skinname; } function qbSetting() { @@ -189,16 +187,19 @@ class Skin extends Linker { function preloadExistence() { global $wgUser, $wgTitle; - if ( $wgTitle->isTalkPage() ) { - $otherTab = $wgTitle->getSubjectPage(); + // User/talk link + $titles = array( $wgUser->getUserPage(), $wgUser->getTalkPage() ); + + // Other tab link + if ( $wgTitle->getNamespace() == NS_SPECIAL ) { + // nothing + } elseif ( $wgTitle->isTalkPage() ) { + $titles[] = $wgTitle->getSubjectPage(); } else { - $otherTab = $wgTitle->getTalkPage(); + $titles[] = $wgTitle->getTalkPage(); } - $lb = new LinkBatch( array( - $wgUser->getUserPage(), - $wgUser->getTalkPage(), - $otherTab - )); + + $lb = new LinkBatch( $titles ); $lb->execute(); } @@ -241,7 +242,7 @@ class Skin extends Linker { function outputPage( &$out ) { global $wgDebugComments; - wfProfileIn( 'Skin::outputPage' ); + wfProfileIn( __METHOD__ ); $this->initPage( $out ); $out->out( $out->headElement() ); @@ -268,6 +269,7 @@ class Skin extends Linker { $out->out( $out->reportTime() ); $out->out( "\n</body></html>" ); + wfProfileOut( __METHOD__ ); } static function makeVariablesScript( $data ) { @@ -293,7 +295,7 @@ class Skin extends Linker { global $wgStylePath, $wgUser; global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang; global $wgTitle, $wgCanonicalNamespaceNames, $wgOut, $wgArticle; - global $wgBreakFrames; + global $wgBreakFrames, $wgRequest; $ns = $wgTitle->getNamespace(); $nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText(); @@ -309,15 +311,25 @@ class Skin extends Linker { 'wgNamespaceNumber' => $wgTitle->getNamespace(), 'wgPageName' => $wgTitle->getPrefixedDBKey(), 'wgTitle' => $wgTitle->getText(), + 'wgAction' => $wgRequest->getText( 'action', 'view' ), 'wgArticleId' => $wgTitle->getArticleId(), 'wgIsArticle' => $wgOut->isArticle(), 'wgUserName' => $wgUser->isAnon() ? NULL : $wgUser->getName(), + 'wgUserGroups' => $wgUser->isAnon() ? NULL : $wgUser->getEffectiveGroups(), 'wgUserLanguage' => $wgLang->getCode(), 'wgContentLanguage' => $wgContLang->getCode(), 'wgBreakFrames' => $wgBreakFrames, 'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0, ); + global $wgLivePreview; + if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) { + $vars['wgLivepreviewMessageLoading'] = wfMsg( 'livepreview-loading' ); + $vars['wgLivepreviewMessageReady'] = wfMsg( 'livepreview-ready' ); + $vars['wgLivepreviewMessageFailed'] = wfMsg( 'livepreview-failed' ); + $vars['wgLivepreviewMessageError'] = wfMsg( 'livepreview-error' ); + } + return self::makeVariablesScript( $vars ); } @@ -392,7 +404,6 @@ class Skin extends Linker { * @return string */ function getUserJs() { - $fname = 'Skin::getUserJs'; wfProfileIn( __METHOD__ ); global $wgStylePath; @@ -419,7 +430,7 @@ var wgAjaxWatch = { wfProfileOut( __METHOD__ ); return $s; - } + } /** * Return html code that include User stylesheets @@ -505,7 +516,7 @@ END; } else $a = array( 'bgcolor' => '#FFFFFF' ); if($wgOut->isArticle() && $wgUser->getOption('editondblclick') && - $wgTitle->userCanEdit() ) { + $wgTitle->userCan( 'edit' ) ) { $s = $wgTitle->getFullURL( $this->editUrlOptions() ); $s = 'document.location = "' .wfEscapeJSString( $s ) .'";'; $a += array ('ondblclick' => $s); @@ -519,7 +530,7 @@ END; $a['onload'] .= 'setupRightClickEdit()'; } $a['class'] = 'ns-'.$wgTitle->getNamespace().' '.($wgContLang->isRTL() ? "rtl" : "ltr"). - ' '.Sanitizer::escapeId( 'page-'.$wgTitle->getPrefixedText() ); + ' '.Sanitizer::escapeClass( 'page-'.$wgTitle->getPrefixedText() ); return $a; } @@ -844,14 +855,22 @@ END; return $subpages; } + /** + * Returns true if the IP should be shown in the header + */ + function showIPinHeader() { + global $wgShowIPinHeader; + return $wgShowIPinHeader && session_id() != ''; + } + function nameAndLogin() { - global $wgUser, $wgTitle, $wgLang, $wgContLang, $wgShowIPinHeader; + global $wgUser, $wgTitle, $wgLang, $wgContLang; $lo = $wgContLang->specialPage( 'Userlogout' ); $s = ''; if ( $wgUser->isAnon() ) { - if( $wgShowIPinHeader && isset( $_COOKIE[ini_get('session.name')] ) ) { + if( $this->showIPinHeader() ) { $n = wfGetIP(); $tl = $this->makeKnownLinkObj( $wgUser->getTalkPage(), @@ -1041,7 +1060,7 @@ END; } if ($wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $watchlist = $dbr->tableName( 'watchlist' ); $sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" . $dbr->strencode($wgTitle->getDBKey()) . @@ -1185,29 +1204,29 @@ END; return $s; } - function privacyLink() { - $privacy = wfMsg( 'privacy' ); - if ($privacy == '-') { + private function footerLink ( $desc, $page ) { + // if the link description has been set to "-" in the default language, + if ( wfMsgForContent( $desc ) == '-') { + // then it is disabled, for all languages. return ''; } else { - return $this->makeKnownLink( wfMsgForContent( 'privacypage' ), $privacy); + // Otherwise, we display the link for the user, described in their + // language (which may or may not be the same as the default language), + // but we make the link target be the one site-wide page. + return $this->makeKnownLink( wfMsgForContent( $page ), wfMsg( $desc ) ); } } + function privacyLink() { + return $this->footerLink( 'privacy', 'privacypage' ); + } + function aboutLink() { - $s = $this->makeKnownLink( wfMsgForContent( 'aboutpage' ), - wfMsg( 'aboutsite' ) ); - return $s; + return $this->footerLink( 'aboutsite', 'aboutpage' ); } function disclaimerLink() { - $disclaimers = wfMsg( 'disclaimers' ); - if ($disclaimers == '-') { - return ''; - } else { - return $this->makeKnownLink( wfMsgForContent( 'disclaimerpage' ), - $disclaimers ); - } + return $this->footerLink( 'disclaimers', 'disclaimerpage' ); } function editThisPage() { @@ -1216,7 +1235,7 @@ END; if ( ! $wgOut->isArticleRelated() ) { $s = wfMsg( 'protectedpage' ); } else { - if ( $wgTitle->userCanEdit() ) { + if ( $wgTitle->userCan( 'edit' ) ) { $t = wfMsg( 'editthispage' ); } else { $t = wfMsg( 'viewsource' ); @@ -1301,7 +1320,7 @@ END; function moveThisPage() { global $wgTitle; - if ( $wgTitle->userCanMove() ) { + if ( $wgTitle->userCan( 'move' ) ) { return $this->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ), wfMsg( 'movethispage' ), 'target=' . $wgTitle->getPrefixedURL() ); } else { @@ -1503,7 +1522,7 @@ END; /* these are used extensively in SkinTemplate, but also some other places */ static function makeMainPageUrl( $urlaction = '' ) { $title = Title::newMainPage(); - self::checkTitle( $title, $name ); + self::checkTitle( $title, '' ); return $title->getLocalURL( $urlaction ); } @@ -1569,7 +1588,7 @@ END; } # make sure we have some title to operate on - static function checkTitle( &$title, &$name ) { + static function checkTitle( &$title, $name ) { if( !is_object( $title ) ) { $title = Title::newFromText( $name ); if( !is_object( $title ) ) { diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php index ff095477..cddd2195 100644 --- a/includes/SkinTemplate.php +++ b/includes/SkinTemplate.php @@ -18,25 +18,11 @@ if ( ! defined( 'MEDIAWIKI' ) ) # http://www.gnu.org/copyleft/gpl.html /** - * Template-filler skin base class - * Formerly generic PHPTal (http://phptal.sourceforge.net/) skin - * Based on Brion's smarty skin - * Copyright (C) Gabriel Wicke -- http://www.aulinx.de/ - * - * Todo: Needs some serious refactoring into functions that correspond - * to the computations individual esi snippets need. Most importantly no body - * parsing for most of those of course. - * - * @package MediaWiki - * @subpackage Skins - */ - -/** * Wrapper object for MediaWiki's localization functions, * to be passed to the template engine. * * @private - * @package MediaWiki + * @addtogroup Skins */ class MediaWiki_I18N { var $_context = array(); @@ -68,8 +54,16 @@ class MediaWiki_I18N { } /** + * Template-filler skin base class + * Formerly generic PHPTal (http://phptal.sourceforge.net/) skin + * Based on Brion's smarty skin + * @copyright Copyright © Gabriel Wicke -- http://www.aulinx.de/ + * + * @todo Needs some serious refactoring into functions that correspond + * to the computations individual esi snippets need. Most importantly no body + * parsing for most of those of course. * - * @package MediaWiki + * @addtogroup Skins */ class SkinTemplate extends Skin { /**#@+ @@ -193,9 +187,9 @@ class SkinTemplate extends Skin { $tpl->set( 'title', $wgOut->getPageTitle() ); $tpl->set( 'pagetitle', $wgOut->getHTMLTitle() ); $tpl->set( 'displaytitle', $wgOut->mPageLinkTitle ); - $tpl->set( 'pageclass', Sanitizer::escapeClass( 'page-'.$wgTitle->getPrefixedText() ) ); + $tpl->set( 'pageclass', Sanitizer::escapeClass( 'page-'.$this->mTitle->getPrefixedText() ) ); - $nsname = isset( $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] ) ? + $nsname = isset( $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] ) ? $wgCanonicalNamespaceNames[ $this->mTitle->getNamespace() ] : $this->mTitle->getNsText(); @@ -339,8 +333,8 @@ class SkinTemplate extends Skin { $tpl->setRef( 'newtalk', $ntl ); $tpl->setRef( 'skin', $this); $tpl->set( 'logo', $this->logoText() ); - if ( $wgOut->isArticle() and (!isset( $oldid ) or isset( $diff )) and - $wgArticle and 0 != $wgArticle->getID() ) + if ( $wgOut->isArticle() and (!isset( $oldid ) or isset( $diff )) and + $wgArticle and 0 != $wgArticle->getID() ) { if ( !$wgDisableCounters ) { $viewcount = $wgLang->formatNum( $wgArticle->getCount() ); @@ -354,7 +348,7 @@ class SkinTemplate extends Skin { } if ($wgPageShowWatchingUsers) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $watchlist = $dbr->tableName( 'watchlist' ); $sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_title='" . $dbr->strencode($this->mTitle->getDBKey()) . @@ -458,6 +452,11 @@ class SkinTemplate extends Skin { $tpl->set( 'sidebar', $this->buildSidebar() ); $tpl->set( 'nav_urls', $this->buildNavUrls() ); + // original version by hansm + if( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) { + wfDebug( __METHOD__ . ': Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!' ); + } + // execute template wfProfileIn( "$fname-execute" ); $res = $tpl->execute(); @@ -476,7 +475,7 @@ class SkinTemplate extends Skin { * @param mixed $str * @private */ - function printOrError( &$str ) { + function printOrError( $str ) { echo $str; } @@ -486,7 +485,7 @@ class SkinTemplate extends Skin { * @private */ function buildPersonalUrls() { - global $wgTitle, $wgShowIPinHeader; + global $wgTitle, $wgRequest; $fname = 'SkinTemplate::buildPersonalUrls'; $pageurl = $wgTitle->getLocalURL(); @@ -511,22 +510,37 @@ class SkinTemplate extends Skin { $href = self::makeSpecialUrl( 'Preferences' ); $personal_urls['preferences'] = array( 'text' => wfMsg( 'mypreferences' ), - 'href' => self::makeSpecialUrl( 'Preferences' ), + 'href' => $href, 'active' => ( $href == $pageurl ) ); $href = self::makeSpecialUrl( 'Watchlist' ); $personal_urls['watchlist'] = array( - 'text' => wfMsg( 'watchlist' ), + 'text' => wfMsg( 'mywatchlist' ), 'href' => $href, 'active' => ( $href == $pageurl ) ); + + # We need to do an explicit check for Special:Contributions, as we + # have to match both the title, and the target (which could come + # from request values or be specified in "sub page" form. The plot + # thickens, because $wgTitle is altered for special pages, so doesn't + # contain the original alias-with-subpage. + $title = Title::newFromText( $wgRequest->getText( 'title' ) ); + if( $title instanceof Title && $title->getNamespace() == NS_SPECIAL ) { + list( $spName, $spPar ) = + SpecialPage::resolveAliasWithSubpage( $title->getText() ); + $active = $spName == 'Contributions' + && ( ( $spPar && $spPar == $this->username ) + || $wgRequest->getText( 'target' ) == $this->username ); + } else { + $active = false; + } + $href = self::makeSpecialUrlSubpage( 'Contributions', $this->username ); $personal_urls['mycontris'] = array( 'text' => wfMsg( 'mycontris' ), 'href' => $href, - // FIXME # 'active' was disabed in r11346 with message: "disable bold link to my contributions; link was bold on all - // Special:Contributions, not just current user's (fix me please!)". Until resolved, explicitly setting active to false. - 'active' => false # ( ( $href == $pageurl . '/' . $this->username ) + 'active' => $active ); $personal_urls['logout'] = array( 'text' => wfMsg( 'userlogout' ), @@ -536,7 +550,7 @@ class SkinTemplate extends Skin { 'active' => false ); } else { - if( $wgShowIPinHeader && isset( $_COOKIE[ini_get("session.name")] ) ) { + if( $this->showIPinHeader() ) { $href = &$this->userpageUrlDetails['href']; $personal_urls['anonuserpage'] = array( 'text' => $this->username, @@ -555,7 +569,7 @@ class SkinTemplate extends Skin { $personal_urls['anonlogin'] = array( 'text' => wfMsg('userlogin'), 'href' => self::makeSpecialUrl( 'Userlogin', 'returnto=' . $this->thisurl ), - 'active' => $wgTitle->isSpecial( 'Userlogin' ) + 'active' => $wgTitle->isSpecial( 'Userlogin' ) ); } else { @@ -567,19 +581,11 @@ class SkinTemplate extends Skin { } } - wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$wgTitle ) ); + wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$wgTitle ) ); wfProfileOut( $fname ); return $personal_urls; } - /** - * Returns true if the IP should be shown in the header - */ - function showIPinHeader() { - global $wgShowIPinHeader; - return $wgShowIPinHeader && isset( $_COOKIE[ini_get("session.name")] ); - } - function tabAction( $title, $message, $selected, $query='', $checkEdit=false ) { $classes = array(); if( $selected ) { @@ -604,6 +610,9 @@ class SkinTemplate extends Skin { function makeTalkUrlDetails( $name, $urlaction = '' ) { $title = Title::newFromText( $name ); + if( !is_object($title) ) { + throw new MWException( __METHOD__." given invalid pagename $name" ); + } $title = $title->getTalkPage(); self::checkTitle( $title, $name ); return array( @@ -659,7 +668,7 @@ class SkinTemplate extends Skin { true); wfProfileIn( "$fname-edit" ); - if ( $this->mTitle->userCanEdit() && ( $this->mTitle->exists() || $this->mTitle->userCanCreate() ) ) { + if ( $this->mTitle->quickUserCan( 'edit' ) && ( $this->mTitle->exists() || $this->mTitle->quickUserCan( 'create' ) ) ) { $istalk = $this->mTitle->isTalkPage(); $istalkclass = $istalk?' istalk':''; $content_actions['edit'] = array( @@ -716,7 +725,7 @@ class SkinTemplate extends Skin { 'href' => $this->mTitle->getLocalUrl( 'action=delete' ) ); } - if ( $this->mTitle->userCanMove()) { + if ( $this->mTitle->quickUserCan( 'move' ) ) { $moveTitle = SpecialPage::getTitleFor( 'Movepage', $this->thispage ); $content_actions['move'] = array( 'class' => $this->mTitle->isSpecial( 'Movepage' ) ? 'selected' : false, @@ -755,6 +764,7 @@ class SkinTemplate extends Skin { ); } } + wfRunHooks( 'SkinTemplateTabs', array( &$this , &$content_actions ) ) ; } else { @@ -762,7 +772,7 @@ class SkinTemplate extends Skin { $content_actions[$this->mTitle->getNamespaceKey()] = array( 'class' => 'selected', - 'text' => wfMsg('specialpage'), + 'text' => wfMsg('nstab-special'), 'href' => $wgRequest->getRequestURL(), // @bug 2457, 2510 ); @@ -832,16 +842,14 @@ class SkinTemplate extends Skin { // default permalink to being off, will override it as required below. $nav_urls['permalink'] = false; - + // A print stylesheet is attached to all pages, but nobody ever // figures that out. :) Add a link... if( $this->iscontent && ($action == '' || $action == 'view' || $action == 'purge' ) ) { - $revid = $wgArticle ? $wgArticle->getLatest() : 0; - if ( !( $revid == 0 ) ) - $nav_urls['print'] = array( - 'text' => wfMsg( 'printableversion' ), - 'href' => $wgRequest->appendQuery( 'printable=yes' ) - ); + $nav_urls['print'] = array( + 'text' => wfMsg( 'printableversion' ), + 'href' => $wgRequest->appendQuery( 'printable=yes' ) + ); // Also add a "permalink" while we're at it if ( (int)$oldid ) { @@ -850,6 +858,7 @@ class SkinTemplate extends Skin { 'href' => '' ); } else { + $revid = $wgArticle ? $wgArticle->getLatest() : 0; if ( !( $revid == 0 ) ) $nav_urls['permalink'] = array( 'text' => wfMsg( 'permalink' ), @@ -894,7 +903,7 @@ class SkinTemplate extends Skin { if ( $wgUser->isAllowed( 'block' ) ) { $nav_urls['blockip'] = array( 'href' => self::makeSpecialUrlSubpage( 'Blockip', $this->mTitle->getText() ) - ); + ); } else { $nav_urls['blockip'] = false; } @@ -1010,7 +1019,7 @@ class SkinTemplate extends Skin { wfProfileIn( $fname ); $out = false; wfRunHooks( 'SkinTemplateSetupPageCss', array( &$out ) ); - + wfProfileOut( $fname ); return $out; } @@ -1065,8 +1074,7 @@ class SkinTemplate extends Skin { /** * Generic wrapper for template functions, with interface * compatible with what we use of PHPTAL 0.7. - * @package MediaWiki - * @subpackage Skins + * @addtogroup Skins */ class QuickTemplate { /** diff --git a/includes/SpecialAllmessages.php b/includes/SpecialAllmessages.php index a28ab3c2..0862cd17 100644 --- a/includes/SpecialAllmessages.php +++ b/includes/SpecialAllmessages.php @@ -1,8 +1,7 @@ <?php /** * Use this special page to get a list of the MediaWiki system messages. - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -89,8 +88,8 @@ function makeHTMLText( $messages ) { global $wgLang, $wgContLang, $wgUser; wfProfileIn( __METHOD__ ); - $sk =& $wgUser->getSkin(); - $talk = $wgLang->getNsText( NS_TALK ); + $sk = $wgUser->getSkin(); + $talk = wfMsg( 'talkpagelinktext' ); $input = wfElement( 'input', array( 'type' => 'text', @@ -124,7 +123,7 @@ function makeHTMLText( $messages ) { NS_MEDIAWIKI => array(), NS_MEDIAWIKI_TALK => array() ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); $sql = "SELECT page_namespace,page_title FROM $page WHERE page_namespace IN (" . NS_MEDIAWIKI . ", " . NS_MEDIAWIKI_TALK . ")"; $res = $dbr->query( $sql ); diff --git a/includes/SpecialAllpages.php b/includes/SpecialAllpages.php index 737e6834..03e164bd 100644 --- a/includes/SpecialAllpages.php +++ b/includes/SpecialAllpages.php @@ -1,13 +1,12 @@ <?php /** - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * Entry point : initialise variables and call subfunctions. * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) - * @param $specialPage @see SpecialPage object. + * @param $specialPage See the SpecialPage object. */ function wfSpecialAllpages( $par=NULL, $specialPage ) { global $wgRequest, $wgOut, $wgContLang; @@ -37,6 +36,10 @@ function wfSpecialAllpages( $par=NULL, $specialPage ) { } } +/** + * Implements Special:Allpages + * @addtogroup SpecialPage + */ class SpecialAllpages { var $maxPerPage=960; var $topLevelMax=50; @@ -89,7 +92,7 @@ function showToplevel ( $namespace = NS_MAIN, $including = false ) { # TODO: Either make this *much* faster or cache the title index points # in the querycache table. - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $out = ""; $where = array( 'page_namespace' => $namespace ); @@ -217,7 +220,7 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) { } else { list( $namespace, $fromKey, $from ) = $fromList; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'page', array( 'page_namespace', 'page_title', 'page_is_redirect' ), array( @@ -261,31 +264,35 @@ function showChunk( $namespace = NS_MAIN, $from, $including = false ) { if ( $including ) { $out2 = ''; } else { - - # Get the last title from previous chunk - $dbr =& wfGetDB( DB_SLAVE ); - $res_prev = $dbr->select( - 'page', - 'page_title', - array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), - $fname, - array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) - ); - - # Get first title of previous complete chunk - if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { - $pt = $dbr->fetchObject( $res_prev ); - $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); + if( $from == '' ) { + // First chunk; no previous link. + $prevTitle = null; } else { - # The previous chunk is not complete, need to link to the very first title - # available in the database - $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', array( 'page_namespace' => $namespace ), $fname, array( 'LIMIT' => 1) ); + # Get the last title from previous chunk + $dbr = wfGetDB( DB_SLAVE ); + $res_prev = $dbr->select( + 'page', + 'page_title', + array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ), + $fname, + array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) ) + ); - # Show the previous link if it s not the current requested chunk - if( $from != $reallyFirstPage_title ) { - $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); + # Get first title of previous complete chunk + if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { + $pt = $dbr->fetchObject( $res_prev ); + $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); } else { - $prevTitle = null; + # The previous chunk is not complete, need to link to the very first title + # available in the database + $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', array( 'page_namespace' => $namespace ), $fname, array( 'LIMIT' => 1) ); + + # Show the previous link if it s not the current requested chunk + if( $from != $reallyFirstPage_title ) { + $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); + } else { + $prevTitle = null; + } } } diff --git a/includes/SpecialAncientpages.php b/includes/SpecialAncientpages.php index 39a3c8ea..c0bbb7ba 100644 --- a/includes/SpecialAncientpages.php +++ b/includes/SpecialAncientpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * Implements Special:Ancientpages + * @addtogroup SpecialPage */ class AncientPagesPage extends QueryPage { @@ -24,7 +22,7 @@ class AncientPagesPage extends QueryPage { function getSQL() { global $wgDBtype; - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $page = $db->tableName( 'page' ); $revision = $db->tableName( 'revision' ); #$use_index = $db->useIndexClause( 'cur_timestamp' ); # FIXME! this is gone diff --git a/includes/SpecialBlockip.php b/includes/SpecialBlockip.php index 626922bb..5f47fa13 100644 --- a/includes/SpecialBlockip.php +++ b/includes/SpecialBlockip.php @@ -2,8 +2,7 @@ /** * Constructor for Special:Blockip page * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -12,6 +11,13 @@ function wfSpecialBlockip( $par ) { global $wgUser, $wgOut, $wgRequest; + # Can't block when the database is locked + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + + # Permission check if( !$wgUser->isAllowed( 'block' ) ) { $wgOut->permissionRequired( 'block' ); return; @@ -31,28 +37,31 @@ function wfSpecialBlockip( $par ) { } /** - * Form object + * Form object for the Special:Blockip page. * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class IPBlockForm { var $BlockAddress, $BlockExpiry, $BlockReason; function IPBlockForm( $par ) { - global $wgRequest; + global $wgRequest, $wgUser; $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); + $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' ); $this->BlockReason = $wgRequest->getText( 'wpBlockReason' ); + $this->BlockReasonList = $wgRequest->getText( 'wpBlockReasonList' ); $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') ); $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' ); - # Unchecked checkboxes are not included in the form data at all, so having one + # Unchecked checkboxes are not included in the form data at all, so having one # that is true by default is a bit tricky $byDefault = !$wgRequest->wasPosted(); $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault ); $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault ); $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault ); + # Re-check user's rights to hide names, very serious, defaults to 0 + $this->BlockHideName = $wgRequest->getBool( 'wpHideName', 0 ) && $wgUser->isAllowed( 'hideuser' ); } function showForm( $err ) { @@ -62,15 +71,17 @@ class IPBlockForm { $wgOut->addWikiText( wfMsg( 'blockiptext' ) ); if($wgSysopUserBans) { - $mIpaddress = wfMsgHtml( 'ipadressorusername' ); + $mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' ); } else { - $mIpaddress = wfMsgHtml( 'ipaddress' ); + $mIpaddress = Xml::label( wfMsg( 'ipadress' ), 'mw-bi-target' ); } - $mIpbexpiry = wfMsgHtml( 'ipbexpiry' ); - $mIpbother = wfMsgHtml( 'ipbother' ); + $mIpbexpiry = Xml::label( wfMsg( 'ipbexpiry' ), 'wpBlockExpiry' ); + $mIpbother = Xml::label( wfMsg( 'ipbother' ), 'mw-bi-other' ); $mIpbothertime = wfMsgHtml( 'ipbotheroption' ); - $mIpbreason = wfMsgHtml( 'ipbreason' ); - $mIpbsubmit = wfMsgHtml( 'ipbsubmit' ); + $mIpbreasonother = Xml::label( wfMsg( 'ipbreason' ), 'wpBlockReasonList' ); + $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' ); + $mIpbreasonotherlist = wfMsgHtml( 'ipbreasonotherlist' ); + $titleObj = SpecialPage::getTitleFor( 'Blockip' ); $action = $titleObj->escapeLocalURL( "action=submit" ); @@ -79,10 +90,7 @@ class IPBlockForm { $wgOut->addHTML( "<p class='error'>{$err}</p>\n" ); } - $scBlockAddress = htmlspecialchars( $this->BlockAddress ); - $scBlockReason = htmlspecialchars( $this->BlockReason ); - $scBlockOtherTime = htmlspecialchars( $this->BlockOther ); - $scBlockExpiryOptions = htmlspecialchars( wfMsgForContent( 'ipboptions' ) ); + $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); $showblockoptions = $scBlockExpiryOptions != '-'; if (!$showblockoptions) @@ -100,15 +108,55 @@ class IPBlockForm { $blockExpiryFormOptions .= "<option value=\"$value\"$selected>$show</option>"; } + $scBlockReasonList = wfMsgForContent( 'ipbreason-dropdown' ); + $blockReasonList = ''; + if ( $scBlockReasonList != '' && $scBlockReasonList != '-' ) { + $blockReasonList = "<option value=\"other\">$mIpbreasonotherlist</option>"; + $optgroup = ""; + foreach ( explode( "\n", $scBlockReasonList ) as $option) { + $value = trim( htmlspecialchars($option) ); + if ( $value == '' ) { + continue; + } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) { + // A new group is starting ... + $value = trim( substr( $value, 1 ) ); + $blockReasonList .= "$optgroup<optgroup label=\"$value\">"; + $optgroup = "</optgroup>"; + } elseif ( substr( $value, 0, 2) == '**' ) { + // groupmember + $selected = ""; + $value = trim( substr( $value, 2 ) ); + if ( $this->BlockReasonList === $value) + $selected = ' selected="selected"'; + $blockReasonList .= "<option value=\"$value\"$selected>$value</option>"; + } else { + // groupless block reason + $selected = ""; + if ( $this->BlockReasonList === $value) + $selected = ' selected="selected"'; + $blockReasonList .= "$optgroup<option value=\"$value\"$selected>$value</option>"; + $optgroup = ""; + } + } + $blockReasonList .= $optgroup; + } + $token = htmlspecialchars( $wgUser->editToken() ); + global $wgStylePath, $wgStyleVersion; $wgOut->addHTML( " +<script type=\"text/javascript\" src=\"$wgStylePath/common/block.js?$wgStyleVersion\"> +</script> <form id=\"blockip\" method=\"post\" action=\"{$action}\"> <table border='0'> <tr> <td align=\"right\">{$mIpaddress}:</td> <td align=\"left\"> - <input tabindex='1' type='text' size='40' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" /> + " . Xml::input( 'wpBlockAddress', 45, $this->BlockAddress, + array( + 'tabindex' => '1', + 'id' => 'mw-bi-target', + 'onchange' => 'updateBlockOptions()' ) ) . " </td> </tr> <tr>"); @@ -127,71 +175,124 @@ class IPBlockForm { <tr id='wpBlockOther'> <td align=\"right\">{$mIpbother}:</td> <td align=\"left\"> - <input tabindex='3' type='text' size='40' name=\"wpBlockOther\" value=\"{$scBlockOtherTime}\" /> + " . Xml::input( 'wpBlockOther', 45, $this->BlockOther, + array( 'tabindex' => '3', 'id' => 'mw-bi-other' ) ) . " </td> - </tr> - <tr> + </tr>"); + if ( $blockReasonList != '' ) { + $wgOut->addHTML(" + <tr> + <td align=\"right\">{$mIpbreasonother}:</td> + <td align=\"left\"> + <select tabindex='4' id=\"wpBlockReasonList\" name=\"wpBlockReasonList\"> + $blockReasonList + </select> + </td> + </tr>"); + } + $wgOut->addHTML(" + <tr id=\"wpBlockReason\"> <td align=\"right\">{$mIpbreason}:</td> <td align=\"left\"> - <input tabindex='3' type='text' size='40' name=\"wpBlockReason\" value=\"{$scBlockReason}\" /> + " . Xml::input( 'wpBlockReason', 45, $this->BlockReason, + array( 'tabindex' => '5', 'id' => 'mw-bi-reason' ) ) . " </td> </tr> - <tr> + <tr id='wpAnonOnlyRow'> <td> </td> <td align=\"left\"> - " . wfCheckLabel( wfMsg( 'ipbanononly' ), + " . wfCheckLabel( wfMsgHtml( 'ipbanononly' ), 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly, - array( 'tabindex' => 4 ) ) . " + array( 'tabindex' => '6' ) ) . " </td> </tr> - <tr> + <tr id='wpCreateAccountRow'> <td> </td> <td align=\"left\"> - " . wfCheckLabel( wfMsg( 'ipbcreateaccount' ), + " . wfCheckLabel( wfMsgHtml( 'ipbcreateaccount' ), 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount, - array( 'tabindex' => 5 ) ) . " + array( 'tabindex' => '7' ) ) . " </td> </tr> - <tr> - <td> </td> - <td align=\"left\"> - " . wfCheckLabel( wfMsg( 'ipbenableautoblock' ), - 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, - array( 'tabindex' => 6 ) ) . " - </td> - </tr> + <tr id='wpEnableAutoblockRow'> + <td> </td> + <td align=\"left\"> + " . wfCheckLabel( wfMsgHtml( 'ipbenableautoblock' ), + 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, + array( 'tabindex' => '8' ) ) . " + </td> + </tr> + "); + // Allow some users to hide name from block log, blocklist and listusers + if ( $wgUser->isAllowed( 'hideuser' ) ) { + $wgOut->addHTML(" + <tr> + <td> </td> + <td align=\"left\"> + " . wfCheckLabel( wfMsgHtml( 'ipbhidename' ), + 'wpHideName', 'wpHideName', $this->BlockHideName, + array( 'tabindex' => '9' ) ) . " + </td> + </tr> + "); + } + $wgOut->addHTML(" <tr> <td style='padding-top: 1em'> </td> <td style='padding-top: 1em' align=\"left\"> - <input tabindex='7' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" /> + " . Xml::submitButton( wfMsgHtml( 'ipbsubmit' ), + array( 'name' => 'wpBlock', 'tabindex' => '10' ) ) . " </td> </tr> - </table> - <input type='hidden' name='wpEditToken' value=\"{$token}\" /> -</form>\n" ); + </table>" . + Xml::hidden( 'wpEditToken', $token ) . +"</form> +<script type=\"text/javascript\">updateBlockOptions()</script> +\n" ); + + $wgOut->addHtml( $this->getConvenienceLinks() ); $user = User::newFromName( $this->BlockAddress ); if( is_object( $user ) ) { $this->showLogFragment( $wgOut, $user->getUserPage() ); } elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) { $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); + } elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) { + $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) ); } - } function doSubmit() { global $wgOut, $wgUser, $wgSysopUserBans, $wgSysopRangeBans; $userId = 0; - $this->BlockAddress = trim( $this->BlockAddress ); - $rxIP = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; - + # Expand valid IPv6 addresses, usernames are left as is + $this->BlockAddress = IP::sanitizeIP( $this->BlockAddress ); + # isIPv4() and IPv6() are used for final validation + $rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'; + $rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}'; + $rxIP = "($rxIP4|$rxIP6)"; + # Check for invalid specifications - if ( ! preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { + if ( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) { $matches = array(); - if ( preg_match( "/^($rxIP)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { + if ( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { + # IPv4 if ( $wgSysopRangeBans ) { - if ( $matches[2] > 31 || $matches[2] < 16 ) { + if ( !IP::isIPv4( $this->BlockAddress ) || $matches[2] < 16 || $matches[2] > 32 ) { + $this->showForm( wfMsg( 'ip_range_invalid' ) ); + return; + } + $this->BlockAddress = Block::normaliseRange( $this->BlockAddress ); + } else { + # Range block illegal + $this->showForm( wfMsg( 'range_block_disabled' ) ); + return; + } + } else if ( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) { + # IPv6 + if ( $wgSysopRangeBans ) { + if ( !IP::isIPv6( $this->BlockAddress ) || $matches[2] < 64 || $matches[2] > 128 ) { $this->showForm( wfMsg( 'ip_range_invalid' ) ); return; } @@ -220,6 +321,14 @@ class IPBlockForm { } } + $reasonstr = $this->BlockReasonList; + if ( $reasonstr != 'other' && $this->BlockReason != '') { + // Entry from drop down menu + additional comment + $reasonstr .= ': ' . $this->BlockReason; + } elseif ( $reasonstr == 'other' ) { + $reasonstr = $this->BlockReason; + } + $expirestr = $this->BlockExpiry; if( $expirestr == 'other' ) $expirestr = $this->BlockOther; @@ -247,23 +356,29 @@ class IPBlockForm { # Note: for a user block, ipb_address is only for display purposes $block = new Block( $this->BlockAddress, $userId, $wgUser->getID(), - $this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, - $this->BlockCreateAccount, $this->BlockEnableAutoblock ); + $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, + $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName); if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) { if ( !$block->insert() ) { - $this->showForm( wfMsg( 'ipb_already_blocked', + $this->showForm( wfMsg( 'ipb_already_blocked', htmlspecialchars( $this->BlockAddress ) ) ); return; } wfRunHooks('BlockIpComplete', array($block, $wgUser)); - # Make log entry - $log = new LogPage( 'block' ); + # Prepare log parameters + $logParams = array(); + $logParams[] = $expirestr; + $logParams[] = $this->blockLogFlags(); + + # Make log entry, if the name is hidden, put it in the oversight log + $log_type = ($this->BlockHideName) ? 'oversight' : 'block'; + $log = new LogPage( $log_type ); $log->addEntry( 'block', Title::makeTitle( NS_USER, $this->BlockAddress ), - $this->BlockReason, $expirestr ); + $reasonstr, $logParams ); # Report to the user $titleObj = SpecialPage::getTitleFor( 'Blockip' ); @@ -280,14 +395,80 @@ class IPBlockForm { $text = wfMsg( 'blockipsuccesstext', $this->BlockAddress ); $wgOut->addWikiText( $text ); } - + function showLogFragment( $out, $title ) { $out->addHtml( wfElement( 'h2', NULL, LogPage::logName( 'block' ) ) ); $request = new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'block' ) ); $viewer = new LogViewer( new LogReader( $request ) ); $viewer->showList( $out ); } - -} + /** + * Return a comma-delimited list of "flags" to be passed to the log + * reader for this block, to provide more information in the logs + * + * @return array + */ + private function blockLogFlags() { + $flags = array(); + if( $this->BlockAnonOnly && IP::isIPAddress( $this->BlockAddress ) ) + // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log + $flags[] = 'anononly'; + if( $this->BlockCreateAccount ) + $flags[] = 'nocreate'; + if( !$this->BlockEnableAutoblock ) + $flags[] = 'noautoblock'; + return implode( ',', $flags ); + } + + /** + * Builds unblock and block list links + * + * @return string + */ + private function getConvenienceLinks() { + global $wgUser; + $skin = $wgUser->getSkin(); + $links[] = $skin->makeLink ( 'MediaWiki:ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); + $links[] = $this->getUnblockLink( $skin ); + $links[] = $this->getBlockListLink( $skin ); + return '<p class="mw-ipb-conveniencelinks">' . implode( ' | ', $links ) . '</p>'; + } + + /** + * Build a convenient link to unblock the given username or IP + * address, if available; otherwise link to a blank unblock + * form + * + * @param $skin Skin to use + * @return string + */ + private function getUnblockLink( $skin ) { + $list = SpecialPage::getTitleFor( 'Ipblocklist' ); + if( $this->BlockAddress ) { + $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock-addr', $addr ), + 'action=unblock&ip=' . urlencode( $this->BlockAddress ) ); + } else { + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock' ), 'action=unblock' ); + } + } + + /** + * Build a convenience link to the block list + * + * @param $skin Skin to use + * @return string + */ + private function getBlockListLink( $skin ) { + $list = SpecialPage::getTitleFor( 'Ipblocklist' ); + if( $this->BlockAddress ) { + $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) ); + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist-addr', $addr ), + 'ip=' . urlencode( $this->BlockAddress ) ); + } else { + return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist' ) ); + } + } +} ?> diff --git a/includes/SpecialBlockme.php b/includes/SpecialBlockme.php index 5bfce4ee..c2cb1a58 100644 --- a/includes/SpecialBlockme.php +++ b/includes/SpecialBlockme.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** diff --git a/includes/SpecialBooksources.php b/includes/SpecialBooksources.php index 5c047fbe..d3136ea4 100644 --- a/includes/SpecialBooksources.php +++ b/includes/SpecialBooksources.php @@ -4,8 +4,7 @@ * Special page outputs information on sourcing a book with a particular ISBN * The parser creates links to this page when dealing with ISBNs in wikitext * - * @package MediaWiki - * @subpackage Special pages + * @addtogroup SpecialPage * @author Rob Church <robchur@gmail.com> * @todo Validate ISBNs using the standard check-digit method */ @@ -34,7 +33,7 @@ class SpecialBookSources extends SpecialPage { $this->isbn = $this->cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) ); $wgOut->addWikiText( wfMsgNoTrans( 'booksources-summary' ) ); $wgOut->addHtml( $this->makeForm() ); - if( strlen( $this->isbn) > 0 ) + if( strlen( $this->isbn ) > 0 ) $this->showList(); } @@ -75,6 +74,10 @@ class SpecialBookSources extends SpecialPage { private function showList() { global $wgOut, $wgContLang; + # Hook to allow extensions to insert additional HTML, + # e.g. for API-interacting plugins and so on + wfRunHooks( 'BookInformation', array( $this->isbn, &$wgOut ) ); + # Check for a local page such as Project:Book_sources and use that if available $title = Title::makeTitleSafe( NS_PROJECT, wfMsg( 'booksources' ) ); # Should this be wfMsgForContent()? -- RC if( is_object( $title ) && $title->exists() ) { diff --git a/includes/SpecialBrokenRedirects.php b/includes/SpecialBrokenRedirects.php index 50935654..208a7e1f 100644 --- a/includes/SpecialBrokenRedirects.php +++ b/includes/SpecialBrokenRedirects.php @@ -1,14 +1,13 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * A special page listing redirects to non existent page. Those should be + * fixed to point to an existing page. + * @addtogroup SpecialPage */ class BrokenRedirectsPage extends PageQueryPage { var $targets = array(); @@ -26,17 +25,17 @@ class BrokenRedirectsPage extends PageQueryPage { } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); + $dbr = wfGetDB( DB_SLAVE ); + list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' ); $sql = "SELECT 'BrokenRedirects' AS type, p1.page_namespace AS namespace, p1.page_title AS title, - pl_namespace, - pl_title - FROM $pagelinks AS pl - JOIN $page p1 ON (p1.page_is_redirect=1 AND pl.pl_from=p1.page_id) - LEFT JOIN $page AS p2 ON (pl_namespace=p2.page_namespace AND pl_title=p2.page_title ) + rd_namespace, + rd_title + FROM $redirect AS rd + JOIN $page p1 ON (rd.rd_from=p1.page_id) + LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title ) WHERE p2.page_namespace IS NULL"; return $sql; } @@ -46,11 +45,11 @@ class BrokenRedirectsPage extends PageQueryPage { } function formatResult( $skin, $result ) { - global $wgContLang; + global $wgUser, $wgContLang; $fromObj = Title::makeTitle( $result->namespace, $result->title ); - if ( isset( $result->pl_title ) ) { - $toObj = Title::makeTitle( $result->pl_namespace, $result->pl_title ); + if ( isset( $result->rd_title ) ) { + $toObj = Title::makeTitle( $result->rd_namespace, $result->rd_title ); } else { $blinks = $fromObj->getBrokenLinksFrom(); if ( $blinks ) { @@ -66,11 +65,19 @@ class BrokenRedirectsPage extends PageQueryPage { } $from = $skin->makeKnownLinkObj( $fromObj ,'', 'redirect=no' ); - $edit = $skin->makeBrokenLinkObj( $fromObj , "(".wfMsg("qbedit").")" , 'redirect=no'); + $edit = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-edit' ), 'action=edit' ); $to = $skin->makeBrokenLinkObj( $toObj ); $arr = $wgContLang->getArrow(); - - return "$from $edit $arr $to"; + + $out = "{$from} {$edit}"; + + if( $wgUser->isAllowed( 'delete' ) ) { + $delete = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-delete' ), 'action=delete' ); + $out .= " {$delete}"; + } + + $out .= " {$arr} {$to}"; + return $out; } } diff --git a/includes/SpecialCategories.php b/includes/SpecialCategories.php index 346eac63..45e1ae6c 100644 --- a/includes/SpecialCategories.php +++ b/includes/SpecialCategories.php @@ -1,69 +1,66 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ -/** - * - * @package MediaWiki - * @subpackage SpecialPage - */ -class CategoriesPage extends QueryPage { - - function getName() { - return "Categories"; - } - - function isExpensive() { - return false; - } +function wfSpecialCategories() { + global $wgOut; - function isSyndicated() { return false; } + $cap = new CategoryPager(); + $wgOut->addHTML( + wfMsgWikiHtml( 'categoriespagetext' ) . + $cap->getNavigationBar() + . '<ul>' . $cap->getBody() . '</ul>' . + $cap->getNavigationBar() + ); +} - function getPageHeader() { - return wfMsgWikiHtml( 'categoriespagetext' ); +/** + * @addtogroup SpecialPage + * @addtogroup Pager + */ +class CategoryPager extends AlphabeticPager { + function getQueryInfo() { + return array( + 'tables' => array('categorylinks'), + 'fields' => array('cl_to','count(*) AS count'), + 'options' => array('GROUP BY' => 'cl_to') + ); } - function getSQL() { - $NScat = NS_CATEGORY; - $dbr =& wfGetDB( DB_SLAVE ); - $categorylinks = $dbr->tableName( 'categorylinks' ); - $implicit_groupby = $dbr->implicitGroupby() ? '1' : 'cl_to'; - $s= "SELECT 'Categories' as type, - {$NScat} as namespace, - cl_to as title, - $implicit_groupby as value, - COUNT(*) as count - FROM $categorylinks - GROUP BY 1,2,3,4"; - return $s; + function getIndexField() { + return "cl_to"; } - - function sortDescending() { - return false; + + /* Override getBody to apply LinksBatch on resultset before actually outputting anything. */ + function getBody() { + if (!$this->mQueryDone) { + $this->doQuery(); + } + $batch = new LinkBatch; + + $this->mResult->rewind(); + + while ( $row = $this->mResult->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_CATEGORY, $row->cl_to ) ); + } + $batch->execute(); + $this->mResult->rewind(); + return parent::getBody(); } - - function formatResult( $skin, $result ) { + + function formatRow($result) { global $wgLang; - $title = Title::makeTitle( NS_CATEGORY, $result->title ); - $plink = $skin->makeLinkObj( $title, $title->getText() ); - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->count ) ); - return wfSpecialList($plink, $nlinks); + $title = Title::makeTitle( NS_CATEGORY, $result->cl_to ); + return ( + '<li>' . + $this->getSkin()->makeLinkObj( $title, $title->getText() ) + . ' ' . + wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->count ) ) + . "</li>\n" ); } } -/** - * - */ -function wfSpecialCategories() { - list( $limit, $offset ) = wfCheckLimits(); - - $cap = new CategoriesPage(); - - return $cap->doQuery( $offset, $limit ); -} - ?> diff --git a/includes/SpecialConfirmemail.php b/includes/SpecialConfirmemail.php index e64232aa..58e55899 100644 --- a/includes/SpecialConfirmemail.php +++ b/includes/SpecialConfirmemail.php @@ -1,15 +1,6 @@ <?php /** - * Special page allows users to request email confirmation message, and handles - * processing of the confirmation code when the link in the email is followed - * - * @package MediaWiki - * @subpackage Special pages - * @author Rob Church <robchur@gmail.com> - */ - -/** * Main execution point * * @param $par Parameters passed to the page @@ -19,6 +10,13 @@ function wfSpecialConfirmemail( $par ) { $form->execute( $par ); } +/** + * Special page allows users to request email confirmation message, and handles + * processing of the confirmation code when the link in the email is followed + * + * @addtogroup SpecialPage + * @author Rob Church <robchur@gmail.com> + */ class EmailConfirmation extends SpecialPage { /** diff --git a/includes/SpecialContributions.php b/includes/SpecialContributions.php index 0a1ef6ee..82c8d608 100644 --- a/includes/SpecialContributions.php +++ b/includes/SpecialContributions.php @@ -1,188 +1,165 @@ <?php /** - * @package MediaWiki - * @subpackage SpecialPage + * Special:Contributions, show user contributions in a paged list + * @addtogroup SpecialPage */ -/** @package MediaWiki */ -class ContribsFinder { - var $username, $offset, $limit, $namespace; - var $dbr; +class ContribsPager extends IndexPager { + public $mDefaultDirection = true; + var $messages, $target; + var $namespace = '', $mDb; - /** - * Constructor - * @param $username Username as a string - */ - function ContribsFinder( $username ) { - $this->username = $username; - $this->namespace = false; - $this->dbr =& wfGetDB( DB_SLAVE ); - } - - function setNamespace( $ns ) { - $this->namespace = $ns; - } + function __construct( $target, $namespace = false ) { + global $wgUser; - function setLimit( $limit ) { - $this->limit = $limit; + parent::__construct(); + foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist minoreditletter' ) as $msg ) { + $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') ); + } + $this->target = $target; + $this->namespace = $namespace; + $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); } - function setOffset( $offset ) { - $this->offset = $offset; + function getDefaultQuery() { + $query = parent::getDefaultQuery(); + $query['target'] = $this->target; + return $query; } - /** - * Get timestamp of either first or last contribution made by the user. - * @todo Maybe it should be private ? - * @param $dir string 'ASC' or 'DESC'. - * @return Revision timestamp (rev_timestamp). - */ - function getEditLimit( $dir ) { - list( $index, $usercond ) = $this->getUserCond(); - $nscond = $this->getNamespaceCond(); - $use_index = $this->dbr->useIndexClause( $index ); - list( $revision, $page) = $this->dbr->tableNamesN( 'revision', 'page' ); - $sql = "SELECT rev_timestamp " . - " FROM $page,$revision $use_index " . - " WHERE rev_page=page_id AND $usercond $nscond" . - " ORDER BY rev_timestamp $dir LIMIT 1"; - - $res = $this->dbr->query( $sql, __METHOD__ ); - $row = $this->dbr->fetchObject( $res ); - if ( $row ) { - return $row->rev_timestamp; - } else { - return false; - } - } + function getQueryInfo() { + list( $index, $userCond ) = $this->getUserCond(); + $conds = array_merge( array( 'page_id=rev_page' ), $userCond, $this->getNamespaceCond() ); - /** - * Get timestamps of first and last contributions made by the user. - * @return Array containing first rev_timestamp and last rev_timestamp. - */ - function getEditLimits() { return array( - $this->getEditLimit( "ASC" ), - $this->getEditLimit( "DESC" ) + 'tables' => array( 'page', 'revision' ), + 'fields' => array( + 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'rev_id', 'rev_page', + 'rev_text_id', 'rev_timestamp', 'rev_comment', 'rev_minor_edit', 'rev_user', + 'rev_user_text', 'rev_deleted' + ), + 'conds' => $conds, + 'options' => array( 'FORCE INDEX' => $index ) ); } function getUserCond() { - $condition = ''; + $condition = array(); - if ( $this->username == 'newbies' ) { - $max = $this->dbr->selectField( 'user', 'max(user_id)', false, 'make_sql' ); - $condition = '>' . (int)($max - $max / 100); - } - - if ( $condition == '' ) { - $condition = ' rev_user_text=' . $this->dbr->addQuotes( $this->username ); - $index = 'usertext_timestamp'; - } else { - $condition = ' rev_user '.$condition ; + if ( $this->target == 'newbies' ) { + $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); + $condition[] = 'rev_user >' . (int)($max - $max / 100); $index = 'user_timestamp'; + } else { + $condition['rev_user_text'] = $this->target; + $index = 'usertext_timestamp'; } return array( $index, $condition ); } function getNamespaceCond() { - if ( $this->namespace !== false ) - return ' AND page_namespace = ' . (int)$this->namespace; - return ''; + if ( $this->namespace !== '' ) { + return array( 'page_namespace' => (int)$this->namespace ); + } else { + return array(); + } } - /** - * @return Timestamp of first entry in previous page. - */ - function getPreviousOffsetForPaging() { - list( $index, $usercond ) = $this->getUserCond(); - $nscond = $this->getNamespaceCond(); - - $use_index = $this->dbr->useIndexClause( $index ); - list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' ); - - $sql = "SELECT rev_timestamp FROM $page, $revision $use_index " . - "WHERE page_id = rev_page AND rev_timestamp > '" . $this->offset . "' AND " . - $usercond . $nscond; - $sql .= " ORDER BY rev_timestamp ASC"; - $sql = $this->dbr->limitResult( $sql, $this->limit, 0 ); - $res = $this->dbr->query( $sql ); - - $numRows = $this->dbr->numRows( $res ); - if ( $numRows ) { - $this->dbr->dataSeek( $res, $numRows - 1 ); - $row = $this->dbr->fetchObject( $res ); - $offset = $row->rev_timestamp; - } else { - $offset = false; + function getIndexField() { + return 'rev_timestamp'; + } + + function getStartBody() { + return "<ul>\n"; + } + + function getEndBody() { + return "</ul>\n"; + } + + function getNavigationBar() { + if ( isset( $this->mNavigationBar ) ) { + return $this->mNavigationBar; } - $this->dbr->freeResult( $res ); - return $offset; + $linkTexts = array( + 'prev' => wfMsgHtml( "sp-contributions-newer", $this->mLimit ), + 'next' => wfMsgHtml( 'sp-contributions-older', $this->mLimit ), + 'first' => wfMsgHtml('sp-contributions-newest'), + 'last' => wfMsgHtml( 'sp-contributions-oldest' ) + ); + + $pagingLinks = $this->getPagingLinks( $linkTexts ); + $limitLinks = $this->getLimitLinks(); + $limits = implode( ' | ', $limitLinks ); + + $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . + wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits); + return $this->mNavigationBar; } /** - * @return Timestamp of first entry in next page. - */ - function getFirstOffsetForPaging() { - list( $index, $usercond ) = $this->getUserCond(); - $use_index = $this->dbr->useIndexClause( $index ); - list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' ); - $nscond = $this->getNamespaceCond(); - $sql = "SELECT rev_timestamp FROM $page, $revision $use_index " . - "WHERE page_id = rev_page AND " . - $usercond . $nscond; - $sql .= " ORDER BY rev_timestamp ASC"; - $sql = $this->dbr->limitResult( $sql, $this->limit, 0 ); - $res = $this->dbr->query( $sql ); - - $numRows = $this->dbr->numRows( $res ); - if ( $numRows ) { - $this->dbr->dataSeek( $res, $numRows - 1 ); - $row = $this->dbr->fetchObject( $res ); - $offset = $row->rev_timestamp; + * Generates each row in the contributions list. + * + * Contributions which are marked "top" are currently on top of the history. + * For these contributions, a [rollback] link is shown for users with sysop + * privileges. The rollback link restores the most recent version that was not + * written by the target user. + * + * @todo This would probably look a lot nicer in a table. + */ + function formatRow( $row ) { + wfProfileIn( __METHOD__ ); + + global $wgLang, $wgUser; + + $sk = $this->getSkin(); + $rev = new Revision( $row ); + + $page = Title::makeTitle( $row->page_namespace, $row->page_title ); + $link = $sk->makeKnownLinkObj( $page ); + $difftext = $topmarktext = ''; + if( $row->rev_id == $row->page_latest ) { + $topmarktext .= '<strong>' . $this->messages['uctop'] . '</strong>'; + if( !$row->page_is_new ) { + $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')'; + } else { + $difftext .= $this->messages['newarticle']; + } + + if( $wgUser->isAllowed( 'rollback' ) ) { + $topmarktext .= ' '.$sk->generateRollback( $rev ); + } + + } + if( $rev->userCan( Revision::DELETED_TEXT ) ) { + $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; } else { - $offset = false; + $difftext = '(' . $this->messages['diff'] . ')'; } - $this->dbr->freeResult( $res ); - return $offset; - } + $histlink='('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')'; - /* private */ function makeSql() { - $offsetQuery = ''; + $comment = $sk->revComment( $rev ); + $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); - list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' ); - list( $index, $userCond ) = $this->getUserCond(); + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $d = '<span class="history-deleted">' . $d . '</span>'; + } - if ( $this->offset ) - $offsetQuery = "AND rev_timestamp < '{$this->offset}'"; - - $nscond = $this->getNamespaceCond(); - $use_index = $this->dbr->useIndexClause( $index ); - $sql = "SELECT - page_namespace,page_title,page_is_new,page_latest, - rev_id,rev_page,rev_text_id,rev_timestamp,rev_comment,rev_minor_edit,rev_user,rev_user_text, - rev_deleted - FROM $page,$revision $use_index - WHERE page_id=rev_page AND $userCond $nscond $offsetQuery - ORDER BY rev_timestamp DESC"; - $sql = $this->dbr->limitResult( $sql, $this->limit, 0 ); - return $sql; - } + if( $row->rev_minor_edit ) { + $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> '; + } else { + $mflag = ''; + } - /** - * This do the search for the user given when creating the object. - * It should probably be the only public function in this class. - * @return Array of contributions. - */ - function find() { - $contribs = array(); - $res = $this->dbr->query( $this->makeSql(), __METHOD__ ); - while ( $c = $this->dbr->fetchObject( $res ) ) - $contribs[] = $c; - $this->dbr->freeResult( $res ); - return $contribs; + $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link} {$comment} {$topmarktext}"; + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $ret .= ' ' . wfMsgHtml( 'deletedrev' ); + } + $ret = "<li>$ret</li>\n"; + wfProfileOut( __METHOD__ ); + return $ret; } -}; +} /** * Special page "user contributions". @@ -194,154 +171,105 @@ class ContribsFinder { function wfSpecialContributions( $par = null ) { global $wgUser, $wgOut, $wgLang, $wgRequest; - $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' ); - if ( !strlen( $target ) ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; + $options = array(); + + if ( isset( $par ) && $par == 'newbies' ) { + $target = 'newbies'; + $options['contribs'] = 'newbie'; + } elseif ( isset( $par ) ) { + $target = $par; + } else { + $target = $wgRequest->getVal( 'target' ); } - $nt = Title::newFromURL( $target ); - if ( !$nt ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; + // check for radiobox + if ( $wgRequest->getVal( 'contribs' ) == 'newbie' ) { + $target = 'newbies'; + $options['contribs'] = 'newbie'; } - $options = array(); - - list( $options['limit'], $options['offset']) = wfCheckLimits(); - $options['offset'] = $wgRequest->getVal( 'offset' ); - /* Offset must be an integral. */ - if ( !strlen( $options['offset'] ) || !preg_match( '/^[0-9]+$/', $options['offset'] ) ) - $options['offset'] = ''; + if ( !strlen( $target ) ) { + $wgOut->addHTML( contributionsForm( '' ) ); + return; + } - $title = SpecialPage::getTitleFor( 'Contributions' ); + $options['limit'] = $wgRequest->getInt( 'limit', 50 ); $options['target'] = $target; - $nt =& Title::makeTitle( NS_USER, $nt->getDBkey() ); - $finder = new ContribsFinder( ( $target == 'newbies' ) ? 'newbies' : $nt->getText() ); - $finder->setLimit( $options['limit'] ); - $finder->setOffset( $options['offset'] ); + $nt = Title::makeTitleSafe( NS_USER, $target ); + if ( !$nt ) { + $wgOut->addHTML( contributionsForm( '' ) ); + return; + } + $id = User::idFromName( $nt->getText() ); + if ( $target != 'newbies' ) { + $target = $nt->getText(); + $wgOut->setSubtitle( contributionsSub( $nt, $id ) ); + } else { + $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); + } + if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { $options['namespace'] = intval( $ns ); - $finder->setNamespace( $options['namespace'] ); } else { $options['namespace'] = ''; } - if ( $wgUser->isAllowed( 'rollback' ) && $wgRequest->getBool( 'bot' ) ) { $options['bot'] = '1'; } - if ( $wgRequest->getText( 'go' ) == 'prev' ) { - $offset = $finder->getPreviousOffsetForPaging(); - if ( $offset !== false ) { - $options['offset'] = $offset; - $prevurl = $title->getLocalURL( wfArrayToCGI( $options ) ); - $wgOut->redirect( $prevurl ); - return; - } - } - - if ( $wgRequest->getText( 'go' ) == 'first' && $target != 'newbies') { - $offset = $finder->getFirstOffsetForPaging(); - if ( $offset !== false ) { - $options['offset'] = $offset; - $prevurl = $title->getLocalURL( wfArrayToCGI( $options ) ); - $wgOut->redirect( $prevurl ); - return; - } - } - - if ( $target == 'newbies' ) { - $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); - } else { - $wgOut->setSubtitle( wfMsgHtml( 'contribsub', contributionsSub( $nt ) ) ); - } - - $id = User::idFromName( $nt->getText() ); wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); - $wgOut->addHTML( contributionsForm( $options) ); + $wgOut->addHTML( contributionsForm( $options ) ); - $contribs = $finder->find(); - - if ( count( $contribs ) == 0) { + $pager = new ContribsPager( $target, $options['namespace'] ); + if ( !$pager->getNumRows() ) { $wgOut->addWikiText( wfMsg( 'nocontribs' ) ); return; } - - list( $early, $late ) = $finder->getEditLimits(); - $lastts = count( $contribs ) ? $contribs[count( $contribs ) - 1]->rev_timestamp : 0; - $atstart = ( !count( $contribs ) || $late == $contribs[0]->rev_timestamp ); - $atend = ( !count( $contribs ) || $early == $lastts ); - - // These four are defaults - $newestlink = wfMsgHtml( 'sp-contributions-newest' ); - $oldestlink = wfMsgHtml( 'sp-contributions-oldest' ); - $newerlink = wfMsgHtml( 'sp-contributions-newer', $options['limit'] ); - $olderlink = wfMsgHtml( 'sp-contributions-older', $options['limit'] ); - - if ( !$atstart ) { - $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'offset' => '' ), $options ) ); - $newestlink = "<a href=\"$stuff\">$newestlink</a>"; - $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'go' => 'prev' ), $options ) ); - $newerlink = "<a href=\"$stuff\">$newerlink</a>"; - } - - if ( !$atend ) { - $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'go' => 'first' ), $options ) ); - $oldestlink = "<a href=\"$stuff\">$oldestlink</a>"; - $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'offset' => $lastts ), $options ) ); - $olderlink = "<a href=\"$stuff\">$olderlink</a>"; - } - - if ( $target == 'newbies' ) { - $firstlast ="($newestlink)"; - } else { - $firstlast = "($newestlink | $oldestlink)"; - } - - $urls = array(); - foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { - $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'limit' => $num ), $options ) ); - $urls[] = "<a href=\"$stuff\">".$wgLang->formatNum( $num )."</a>"; + $wgOut->addHTML( + '<p>' . $pager->getNavigationBar() . '</p>' . + $pager->getBody() . + '<p>' . $pager->getNavigationBar() . '</p>' ); + + # If there were contributions, and it was a valid user or IP, show + # the appropriate "footer" message - WHOIS tools, etc. + if( $target != 'newbies' ) { + $message = IP::isIPAddress( $target ) + ? 'sp-contributions-footer-anon' + : 'sp-contributions-footer'; + + + $text = wfMsg( $message, $target ); + if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { + $wgOut->addHtml( '<div class="mw-contributions-footer">' ); + $wgOut->addWikiText( $text ); + $wgOut->addHtml( '</div>' ); + } } - $bits = implode( $urls, ' | ' ); - - $prevnextbits = $firstlast .' '. wfMsgHtml( 'viewprevnext', $newerlink, $olderlink, $bits ); - - $wgOut->addHTML( "<p>{$prevnextbits}</p>\n" ); - - $wgOut->addHTML( "<ul>\n" ); - - $sk = $wgUser->getSkin(); - foreach ( $contribs as $contrib ) - $wgOut->addHTML( ucListEdit( $sk, $contrib ) ); - - $wgOut->addHTML( "</ul>\n" ); - $wgOut->addHTML( "<p>{$prevnextbits}</p>\n" ); } /** * Generates the subheading with links - * @param $nt @see Title object for the target + * @param Title $nt Title object for the target + * @param integer $id User ID for the target + * @return String: appropriately-escaped HTML to be output literally */ -function contributionsSub( $nt ) { +function contributionsSub( $nt, $id ) { global $wgSysopUserBans, $wgLang, $wgUser; $sk = $wgUser->getSkin(); - $id = User::idFromName( $nt->getText() ); if ( 0 == $id ) { - $ul = $nt->getText(); + $user = $nt->getText(); } else { - $ul = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); + $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); } $talk = $nt->getTalkPage(); if( $talk ) { # Talk page link - $tools[] = $sk->makeLinkObj( $talk, $wgLang->getNsText( NS_TALK ) ); + $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) ); if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) { # Block link if( $wgUser->isAllowed( 'block' ) ) @@ -351,9 +279,18 @@ function contributionsSub( $nt ) { } # Other logs link $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() ); - $ul .= ' (' . implode( ' | ', $tools ) . ')'; + $links = implode( ' | ', $tools ); + } + + // Old message 'contribsub' had one parameter, but that doesn't work for + // languages that want to put the "for" bit right after $user but before + // $links. If 'contribsub' is around, use it for reverse compatibility, + // otherwise use 'contribsub2'. + if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { + return wfMsgHtml( 'contribsub2', $user, $links ); + } else { + return wfMsgHtml( 'contribsub', "$user ($links)" ); } - return $ul; } /** @@ -361,97 +298,48 @@ function contributionsSub( $nt ) { * @param $options Array: the options to be included. */ function contributionsForm( $options ) { - global $wgScript, $wgTitle; + global $wgScript, $wgTitle, $wgRequest; $options['title'] = $wgTitle->getPrefixedText(); - - $f = "<form method='get' action=\"$wgScript\">\n"; - foreach ( $options as $name => $value ) { - if( $name === 'namespace') continue; - $f .= "\t" . wfElement( 'input', array( - 'name' => $name, - 'type' => 'hidden', - 'value' => $value ) ) . "\n"; + if ( !isset( $options['target'] ) ) { + $options['target'] = ''; + } else { + $options['target'] = str_replace( '_' , ' ' , $options['target'] ); } - $f .= '<p>' . wfMsgHtml( 'namespace' ) . ' ' . - HTMLnamespaceselector( $options['namespace'], '' ) . - wfElement( 'input', array( - 'type' => 'submit', - 'value' => wfMsg( 'allpagessubmit' ) ) - ) . - "</p></form>\n"; - - return $f; -} - -/** - * Generates each row in the contributions list. - * - * Contributions which are marked "top" are currently on top of the history. - * For these contributions, a [rollback] link is shown for users with sysop - * privileges. The rollback link restores the most recent version that was not - * written by the target user. - * - * @todo This would probably look a lot nicer in a table. - */ -function ucListEdit( $sk, $row ) { - $fname = 'ucListEdit'; - wfProfileIn( $fname ); - - global $wgLang, $wgUser, $wgRequest; - static $messages; - if( !isset( $messages ) ) { - foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist minoreditletter' ) as $msg ) { - $messages[$msg] = wfMsgExt( $msg, array( 'escape') ); - } + if ( !isset( $options['namespace'] ) ) { + $options['namespace'] = ''; } - $rev = new Revision( $row ); - - $page = Title::makeTitle( $row->page_namespace, $row->page_title ); - $link = $sk->makeKnownLinkObj( $page ); - $difftext = $topmarktext = ''; - if( $row->rev_id == $row->page_latest ) { - $topmarktext .= '<strong>' . $messages['uctop'] . '</strong>'; - if( !$row->page_is_new ) { - $difftext .= '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=0' ) . ')'; - } else { - $difftext .= $messages['newarticle']; - } - - if( $wgUser->isAllowed( 'rollback' ) ) { - $topmarktext .= ' '.$sk->generateRollback( $rev ); - } - - } - if( $rev->userCan( Revision::DELETED_TEXT ) ) { - $difftext = '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; - } else { - $difftext = '(' . $messages['diff'] . ')'; + if ( !isset( $options['contribs'] ) ) { + $options['contribs'] = 'user'; } - $histlink='('.$sk->makeKnownLinkObj( $page, $messages['hist'], 'action=history' ) . ')'; - - $comment = $sk->revComment( $rev ); - $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $d = '<span class="history-deleted">' . $d . '</span>'; + if ( $options['contribs'] == 'newbie' ) { + $options['target'] = ''; } - if( $row->rev_minor_edit ) { - $mflag = '<span class="minor">' . $messages['minoreditletter'] . '</span> '; - } else { - $mflag = ''; - } + $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link} {$comment} {$topmarktext}"; - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $ret .= ' ' . wfMsgHtml( 'deletedrev' ); - } - $ret = "<li>$ret</li>\n"; - wfProfileOut( $fname ); - return $ret; + foreach ( $options as $name => $value ) { + if ( in_array( $name, array( 'namespace', 'target', 'contribs' ) ) ) { + continue; + } + $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; + } + + $f .= '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . + Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '<br />' . + Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), 'contribs' , 'user', 'user', $options['contribs'] == 'user' ? true : false ) . ' ' . + Xml::input( 'target', 20, $options['target']) . ' '. + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . + Xml::namespaceSelector( $options['namespace'], '' ) . + Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . + '</fieldset>' . + Xml::closeElement( 'form' ); + return $f; } + ?> diff --git a/includes/SpecialDeadendpages.php b/includes/SpecialDeadendpages.php index 4ffe5e03..48d27add 100644 --- a/includes/SpecialDeadendpages.php +++ b/includes/SpecialDeadendpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class DeadendPagesPage extends PageQueryPage { @@ -38,18 +36,18 @@ class DeadendPagesPage extends PageQueryPage { return false; } - /** + /** * @return string an sqlquery */ function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " . "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " . "WHERE pl_from IS NULL " . "AND page_namespace = 0 " . "AND page_is_redirect = 0"; - } + } } /** @@ -57,11 +55,11 @@ class DeadendPagesPage extends PageQueryPage { */ function wfSpecialDeadendpages() { - list( $limit, $offset ) = wfCheckLimits(); + list( $limit, $offset ) = wfCheckLimits(); - $depp = new DeadendPagesPage(); + $depp = new DeadendPagesPage(); - return $depp->doQuery( $offset, $limit ); + return $depp->doQuery( $offset, $limit ); } ?> diff --git a/includes/SpecialDisambiguations.php b/includes/SpecialDisambiguations.php index 626b967c..da0562ab 100644 --- a/includes/SpecialDisambiguations.php +++ b/includes/SpecialDisambiguations.php @@ -1,15 +1,9 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ -/** - * - * @package MediaWiki - * @subpackage SpecialPage - */ class DisambiguationsPage extends PageQueryPage { function getName() { @@ -19,69 +13,67 @@ class DisambiguationsPage extends PageQueryPage { function isExpensive( ) { return true; } function isSyndicated() { return false; } - function getDisambiguationPageObj() { - return Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage'); - } - - function getPageHeader( ) { - global $wgUser; - $sk = $wgUser->getSkin(); - return '<p>'.wfMsg('disambiguationstext', $sk->makeKnownLinkObj($this->getDisambiguationPageObj()))."</p><br />\n"; + function getPageHeader( ) { + global $wgOut; + return $wgOut->parse( wfMsg( 'disambiguations-text' ) ); } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); - list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); + $dbr = wfGetDB( DB_SLAVE ); + + $dMsgText = wfMsgForContent('disambiguationspage'); - $dMsgText = wfMsgForContent('disambiguationspage'); - $linkBatch = new LinkBatch; - - # If the text can be treated as a title, use it verbatim. - # Otherwise, pull the titles from the links table - $dp = Title::newFromText($dMsgText); - if( $dp ) { - if($dp->getNamespace() != NS_TEMPLATE) { - # FIXME we assume the disambiguation message is a template but - # the page can potentially be from another namespace :/ - wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); - } - $linkBatch->addObj( $dp ); - } else { - # Get all the templates linked from the Mediawiki:Disambiguationspage - $disPageObj = $this->getDisambiguationPageObj(); - $res = $dbr->select( - array('pagelinks', 'page'), - 'pl_title', - array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, - 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), - 'DisambiguationsPage::getSQL' ); - - while ( $row = $dbr->fetchObject( $res ) ) { - $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); - } - $dbr->freeResult( $res ); - } - - $set = $linkBatch->constructSet( 'lb.tl', $dbr ); - if( $set === false ) { - $set = 'FALSE'; # We must always return a valid sql query, but this way DB will always quicly return an empty result - wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); - } - - $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," - ." pb.page_title AS title, la.pl_from AS value" - ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" - ." WHERE $set" # disambiguation template(s) - .' AND pa.page_id = la.pl_from' - .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace - .' AND pb.page_id = lb.tl_from' - .' AND pb.page_namespace = la.pl_namespace' - .' AND pb.page_title = la.pl_title' - .' ORDER BY lb.tl_namespace, lb.tl_title'; - - return $sql; + + # If the text can be treated as a title, use it verbatim. + # Otherwise, pull the titles from the links table + $dp = Title::newFromText($dMsgText); + if( $dp ) { + if($dp->getNamespace() != NS_TEMPLATE) { + # FIXME we assume the disambiguation message is a template but + # the page can potentially be from another namespace :/ + wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); + } + $linkBatch->addObj( $dp ); + } else { + # Get all the templates linked from the Mediawiki:Disambiguationspage + $disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' ); + $res = $dbr->select( + array('pagelinks', 'page'), + 'pl_title', + array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, + 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), + __METHOD__ ); + + while ( $row = $dbr->fetchObject( $res ) ) { + $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); + } + + $dbr->freeResult( $res ); + } + + $set = $linkBatch->constructSet( 'lb.tl', $dbr ); + if( $set === false ) { + # We must always return a valid sql query, but this way DB will always quicly return an empty result + $set = 'FALSE'; + wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); + } + + list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); + + $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," + ." pb.page_title AS title, la.pl_from AS value" + ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" + ." WHERE $set" # disambiguation template(s) + .' AND pa.page_id = la.pl_from' + .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace + .' AND pb.page_id = lb.tl_from' + .' AND pb.page_namespace = la.pl_namespace' + .' AND pb.page_title = la.pl_title' + .' ORDER BY lb.tl_namespace, lb.tl_title'; + + return $sql; } function getOrder() { @@ -93,10 +85,10 @@ class DisambiguationsPage extends PageQueryPage { $title = Title::newFromId( $result->value ); $dp = Title::makeTitle( $result->namespace, $result->title ); - $from = $skin->makeKnownLinkObj( $title,''); - $edit = $skin->makeBrokenLinkObj( $title, "(".wfMsg("qbedit").")" , 'redirect=no'); + $from = $skin->makeKnownLinkObj( $title, '' ); + $edit = $skin->makeKnownLinkObj( $title, "(".wfMsgHtml("qbedit").")" , 'redirect=no&action=edit' ); $arr = $wgContLang->getArrow(); - $to = $skin->makeKnownLinkObj( $dp,''); + $to = $skin->makeKnownLinkObj( $dp, '' ); return "$from $edit $arr $to"; } @@ -112,4 +104,5 @@ function wfSpecialDisambiguations() { return $sd->doQuery( $offset, $limit ); } -?> + +?>
\ No newline at end of file diff --git a/includes/SpecialDoubleRedirects.php b/includes/SpecialDoubleRedirects.php index cf1153ea..e7b355c5 100644 --- a/includes/SpecialDoubleRedirects.php +++ b/includes/SpecialDoubleRedirects.php @@ -1,14 +1,13 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * A special page listing redirects to redirecting page. + * The software will automatically not follow double redirects, to prevent loops. + * @addtogroup SpecialPage */ class DoubleRedirectsPage extends PageQueryPage { @@ -26,7 +25,7 @@ class DoubleRedirectsPage extends PageQueryPage { function getSQLText( &$dbr, $namespace = null, $title = null ) { - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); + list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' ); $limitToTitle = !( $namespace === null && $title === null ); $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ; @@ -34,14 +33,13 @@ class DoubleRedirectsPage extends PageQueryPage { " pa.page_namespace as namespace, pa.page_title as title," . " pb.page_namespace as nsb, pb.page_title as tb," . " pc.page_namespace as nsc, pc.page_title as tc" . - " FROM $pagelinks AS la, $pagelinks AS lb, $page AS pa, $page AS pb, $page AS pc" . - " WHERE pa.page_is_redirect=1 AND pb.page_is_redirect=1" . - " AND la.pl_from=pa.page_id" . - " AND la.pl_namespace=pb.page_namespace" . - " AND la.pl_title=pb.page_title" . - " AND lb.pl_from=pb.page_id" . - " AND lb.pl_namespace=pc.page_namespace" . - " AND lb.pl_title=pc.page_title"; + " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" . + " WHERE ra.rd_from=pa.page_id" . + " AND ra.rd_namespace=pb.page_namespace" . + " AND ra.rd_title=pb.page_title" . + " AND rb.rd_from=pb.page_id" . + " AND rb.rd_namespace=pc.page_namespace" . + " AND rb.rd_title=pc.page_title"; if( $limitToTitle ) { $encTitle = $dbr->addQuotes( $title ); @@ -53,7 +51,7 @@ class DoubleRedirectsPage extends PageQueryPage { } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); return $this->getSQLText( $dbr ); } @@ -68,7 +66,7 @@ class DoubleRedirectsPage extends PageQueryPage { $titleA = Title::makeTitle( $result->namespace, $result->title ); if ( $result && !isset( $result->nsb ) ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $sql = $this->getSQLText( $dbr, $result->namespace, $result->title ); $res = $dbr->query( $sql, $fname ); if ( $res ) { diff --git a/includes/SpecialEmailuser.php b/includes/SpecialEmailuser.php index 38745a37..900a2c32 100644 --- a/includes/SpecialEmailuser.php +++ b/includes/SpecialEmailuser.php @@ -1,15 +1,14 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ -/** - * - */ require_once('UserMailer.php'); +/** + * @todo document + */ function wfSpecialEmailuser( $par ) { global $wgUser, $wgOut, $wgRequest, $wgEnableEmail, $wgEnableUserEmail; @@ -51,7 +50,14 @@ function wfSpecialEmailuser( $par ) { if ( "success" == $action ) { $f->showSuccess( $nu ); } else if ( "submit" == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) + { + # Check against the rate limiter + if( $wgUser->pingLimiter( 'emailuser' ) ) { + $wgOut->rateLimited(); + return; + } + $f->doSubmit(); } else { $f->showForm(); @@ -59,9 +65,8 @@ function wfSpecialEmailuser( $par ) { } /** - * @todo document - * @package MediaWiki - * @subpackage SpecialPage + * Implements the Special:Emailuser web interface, and invokes userMailer for sending the email message. + * @addtogroup SpecialPage */ class EmailUserForm { diff --git a/includes/SpecialExport.php b/includes/SpecialExport.php index 5e6d6d8d..a597fdd0 100644 --- a/includes/SpecialExport.php +++ b/includes/SpecialExport.php @@ -18,10 +18,37 @@ # http://www.gnu.org/copyleft/gpl.html /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ +function wfExportGetPagesFromCategory( $title ) { + global $wgContLang; + + $name = $title->getDBKey(); + + $dbr = wfGetDB( DB_SLAVE ); + + list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); + $sql = "SELECT page_namespace, page_title FROM $page " . + "JOIN $categorylinks ON cl_from = page_id " . + "WHERE cl_to = " . $dbr->addQuotes( $name ); + + $pages = array(); + $res = $dbr->query( $sql, 'wfExportGetPagesFromCategory' ); + while ( $row = $dbr->fetchObject( $res ) ) { + $n = $row->page_title; + if ($row->page_namespace) { + $ns = $wgContLang->getNsText( $row->page_namespace ); + $n = $ns . ':' . $n; + } + + $pages[] = $n; + } + $dbr->freeResult($res); + + return $pages; +} + /** * */ @@ -30,7 +57,21 @@ function wfSpecialExport( $page = '' ) { global $wgExportAllowHistory, $wgExportMaxHistory; $curonly = true; - if( $wgRequest->wasPosted() ) { + $doexport = false; + + if ( $wgRequest->getCheck( 'addcat' ) ) { + $page = $wgRequest->getText( 'pages' ); + $catname = $wgRequest->getText( 'catname' ); + + if ( $catname !== '' && $catname !== NULL && $catname !== false ) { + $t = Title::makeTitleSafe( NS_CATEGORY, $catname ); + if ( $t ) { + $catpages = wfExportGetPagesFromCategory( $t ); + if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); + } + } + } + else if( $wgRequest->wasPosted() ) { $page = $wgRequest->getText( 'pages' ); $curonly = $wgRequest->getCheck( 'curonly' ); $rawOffset = $wgRequest->getVal( 'offset' ); @@ -60,6 +101,8 @@ function wfSpecialExport( $page = '' ) { $history['dir'] = 'desc'; } } + + if( $page != '' ) $doexport = true; } else { // Default to current-only for GET requests $page = $wgRequest->getText( 'pages', $page ); @@ -69,7 +112,10 @@ function wfSpecialExport( $page = '' ) { } else { $history = WikiExporter::CURRENT; } + + if( $page != '' ) $doexport = true; } + if( !$wgExportAllowHistory ) { // Override $history = WikiExporter::CURRENT; @@ -78,7 +124,7 @@ function wfSpecialExport( $page = '' ) { $list_authors = $wgRequest->getCheck( 'listauthors' ); if ( !$curonly || !$wgExportAllowListContributors ) $list_authors = false ; - if( $page != '' ) { + if ( $doexport ) { $wgOut->disable(); // Cancel output buffering and gzipping if set @@ -87,7 +133,7 @@ function wfSpecialExport( $page = '' ) { header( "Content-type: application/xml; charset=utf-8" ); $pages = explode( "\n", $page ); - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); $exporter = new WikiExporter( $db, $history ); $exporter->list_authors = $list_authors ; $exporter->openStream(); @@ -105,7 +151,13 @@ function wfSpecialExport( $page = '' ) { } } }*/ - $exporter->pageByName( $page ); + + #Bug 8824: Only export pages the user can read + $title = Title::newFromText( $page ); + if( is_null( $title ) ) continue; #TODO: perhaps output an <error> tag or something. + if( !$title->userCan( 'read' ) ) continue; #TODO: perhaps output an <error> tag or something. + + $exporter->pageByTitle( $title ); } $exporter->closeStream(); @@ -116,7 +168,12 @@ function wfSpecialExport( $page = '' ) { $titleObj = SpecialPage::getTitleFor( "Export" ); $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalUrl() ) ); - $form .= wfOpenElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ) . '</textarea><br />'; + + $form .= wfInputLabel( wfMsg( 'export-addcattext' ), 'catname', 'catname', 40 ) . ' '; + $form .= wfSubmitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />'; + + $form .= wfOpenElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ) . htmlspecialchars($page). '</textarea><br />'; + if( $wgExportAllowHistory ) { $form .= wfCheck( 'curonly', true, array( 'value' => 'true', 'id' => 'curonly' ) ); $form .= wfLabel( wfMsg( 'exportcuronly' ), 'curonly' ) . '<br />'; diff --git a/includes/SpecialFewestrevisions.php b/includes/SpecialFewestrevisions.php new file mode 100644 index 00000000..4c0cd686 --- /dev/null +++ b/includes/SpecialFewestrevisions.php @@ -0,0 +1,65 @@ +<?php + +/** + * Special page for listing the articles with the fewest revisions. + * + * @package MediaWiki + * @addtogroup SpecialPage + * @author Martin Drashkov + */ +class FewestrevisionsPage extends QueryPage { + + function getName() { + return 'Fewestrevisions'; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSql() { + $dbr = wfGetDB( DB_SLAVE ); + list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); + + return "SELECT 'Fewestrevisions' as type, + page_namespace as namespace, + page_title as title, + COUNT(*) as value + FROM $revision + JOIN $page ON page_id = rev_page + WHERE page_namespace = " . NS_MAIN . " + GROUP BY 1,2,3 + HAVING COUNT(*) > 1"; + } + + function sortDescending() { + return false; + } + + function formatResult( $skin, $result ) { + global $wgLang, $wgContLang; + + $nt = Title::makeTitleSafe( $result->namespace, $result->title ); + $text = $wgContLang->convert( $nt->getPrefixedText() ); + + $plink = $skin->makeKnownLinkObj( $nt, $text ); + + $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'), + $wgLang->formatNum( $result->value ) ); + $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ); + + return wfSpecialList( $plink, $nlink ); + } +} + +function wfSpecialFewestrevisions() { + list( $limit, $offset ) = wfCheckLimits(); + $frp = new FewestrevisionsPage(); + $frp->doQuery( $offset, $limit ); +} + +?> diff --git a/includes/SpecialImagelist.php b/includes/SpecialImagelist.php index 5ecbe8a6..92b9ae11 100644 --- a/includes/SpecialImagelist.php +++ b/includes/SpecialImagelist.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -24,6 +23,10 @@ function wfSpecialImagelist() { . $nav ); } +/** + * @addtogroup SpecialPage + * @addtogroup Pager + */ class ImageListPager extends TablePager { var $mFieldNames = null; var $mMessages = array(); @@ -40,11 +43,11 @@ class ImageListPager extends TablePager { if ( $search != '' && !$wgMiserMode ) { $nt = Title::newFromUrl( $search ); if( $nt ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); $m = str_replace( "%", "\\%", $m ); $m = str_replace( "_", "\\_", $m ); - $this->mQueryConds = array( "LCASE(img_name) LIKE '%{$m}%'" ); + $this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" ); } } @@ -138,17 +141,14 @@ class ImageListPager extends TablePager { function getForm() { global $wgRequest, $wgMiserMode; $url = $this->getTitle()->escapeLocalURL(); - $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' ); - $msgSearch = wfMsgHtml( 'imagelist_search_for' ); $search = $wgRequest->getText( 'ilsearch' ); - $encSearch = htmlspecialchars( $search ); - $s = "<form method=\"get\" action=\"$url\">\n" . + $s = "<form method=\"get\" action=\"$url\">\n" . wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ); if ( !$wgMiserMode ) { - $s .= "<br/>\n" . $msgSearch . - " <input type=\"text\" size=\"20\" name=\"ilsearch\" value=\"$encSearch\"/><br/>\n"; + $s .= "<br/>\n" . + Xml::inputLabel( wfMsg( 'imagelist_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); } - $s .= " <input type=\"submit\" value=\"$msgSubmit\"/>\n" . + $s .= " " . Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ." \n" . $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) . "</form>\n"; return $s; diff --git a/includes/SpecialImport.php b/includes/SpecialImport.php index 1c8ee2e0..c7b861d0 100644 --- a/includes/SpecialImport.php +++ b/includes/SpecialImport.php @@ -19,8 +19,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -34,11 +33,11 @@ function wfSpecialImport( $page = '' ) { $namespace = $wgImportTargetNamespace; $frompage = ''; $history = true; - + if( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit') { $isUpload = false; $namespace = $wgRequest->getIntOrNull( 'namespace' ); - + switch( $wgRequest->getVal( "source" ) ) { case "upload": $isUpload = true; @@ -65,17 +64,17 @@ function wfSpecialImport( $page = '' ) { $wgOut->addWikiText( wfEscapeWikiText( $source->getMessage() ) ); } else { $wgOut->addWikiText( wfMsg( "importstart" ) ); - + $importer = new WikiImporter( $source ); if( !is_null( $namespace ) ) { $importer->setTargetNamespace( $namespace ); } $reporter = new ImportReporter( $importer, $isUpload, $interwiki ); - + $reporter->open(); $result = $importer->doImport(); $reporter->close(); - + if( WikiError::isError( $result ) ) { $wgOut->addWikiText( wfMsg( "importfailed", wfEscapeWikiText( $result->getMessage() ) ) ); @@ -161,6 +160,7 @@ function wfSpecialImport( $page = '' ) { /** * Reporting callback + * @addtogroup SpecialPage */ class ImportReporter { function __construct( $importer, $upload, $interwiki ) { @@ -169,27 +169,27 @@ class ImportReporter { $this->mIsUpload = $upload; $this->mInterwiki = $interwiki; } - + function open() { global $wgOut; $wgOut->addHtml( "<ul>\n" ); } - + function reportPage( $title, $origTitle, $revisionCount, $successCount ) { global $wgOut, $wgUser, $wgLang, $wgContLang; - + $skin = $wgUser->getSkin(); - + $this->mPageCount++; - + $localCount = $wgLang->formatNum( $successCount ); $contentCount = $wgContLang->formatNum( $successCount ); - + $wgOut->addHtml( "<li>" . $skin->makeKnownLinkObj( $title ) . " " . wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) . "</li>\n" ); - + if( $successCount > 0 ) { $log = new LogPage( 'import' ); if( $this->mIsUpload ) { @@ -203,7 +203,7 @@ class ImportReporter { $contentCount, $interwiki ); $log->addEntry( 'interwiki', $title, $detail ); } - + $comment = $detail; // quick $dbw = wfGetDB( DB_MASTER ); $nullRevision = Revision::newNullRevision( @@ -211,7 +211,7 @@ class ImportReporter { $nullRevision->insertOn( $dbw ); } } - + function close() { global $wgOut; if( $this->mPageCount == 0 ) { @@ -223,8 +223,7 @@ class ImportReporter { /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class WikiRevision { var $title = null; @@ -279,7 +278,7 @@ class WikiRevision { return $this->title; } - function getID() { + function getID() { return $this->id; } @@ -304,7 +303,7 @@ class WikiRevision { } function importOldRevision() { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); # Sneak a single revision into place $user = User::newFromName( $this->getUser() ); @@ -338,7 +337,7 @@ class WikiRevision { return false; } } - + # FIXME: Use original rev_id optionally # FIXME: blah blah blah @@ -362,14 +361,14 @@ class WikiRevision { if( $created ) { wfDebug( __METHOD__ . ": running onArticleCreate\n" ); Article::onArticleCreate( $this->title ); - + wfDebug( __METHOD__ . ": running create updates\n" ); $article->createUpdates( $revision ); - + } elseif( $changed ) { wfDebug( __METHOD__ . ": running onArticleEdit\n" ); Article::onArticleEdit( $this->title ); - + wfDebug( __METHOD__ . ": running edit updates\n" ); $article->editUpdates( $this->getText(), @@ -378,16 +377,15 @@ class WikiRevision { $this->timestamp, $revId ); } - + return true; } } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Import + * @addtogroup SpecialPage */ class WikiImporter { var $mSource = null; @@ -446,7 +444,7 @@ class WikiImporter { print "$data\n"; } else { global $wgOut; - $wgOut->addHTML( "<li>$data</li>\n" ); + $wgOut->addHTML( "<li>" . htmlspecialchars( $data ) . "</li>\n" ); } } @@ -486,7 +484,7 @@ class WikiImporter { $this->mRevisionCallback = $callback; return $previous; } - + /** * Set a target namespace to override the defaults */ @@ -508,7 +506,7 @@ class WikiImporter { * @private */ function importRevision( &$revision ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); return $dbw->deadlockLoop( array( &$revision, 'importOldRevision' ) ); } @@ -627,9 +625,14 @@ class WikiImporter { xml_set_character_data_handler( $parser, "char_append" ); break; case "revision": - $this->workRevision = new WikiRevision; - $this->workRevision->setTitle( $this->pageTitle ); - $this->workRevisionCount++; + if( is_object( $this->pageTitle ) ) { + $this->workRevision = new WikiRevision; + $this->workRevision->setTitle( $this->pageTitle ); + $this->workRevisionCount++; + } else { + // Skipping items due to invalid page title + $this->workRevision = null; + } xml_set_element_handler( $parser, "in_revision", "out_revision" ); break; default: @@ -646,7 +649,7 @@ class WikiImporter { $this->pageOutCallback( $this->pageTitle, $this->origTitle, $this->workRevisionCount, $this->workSuccessCount ); - + $this->workTitle = null; $this->workRevision = null; $this->workRevisionCount = 0; @@ -681,30 +684,42 @@ class WikiImporter { } else { $this->pageTitle = Title::newFromText( $this->workTitle ); } - $this->pageCallback( $this->workTitle ); + if( is_null( $this->pageTitle ) ) { + // Invalid page title? Ignore the page + $this->notice( "Skipping invalid page title '$this->workTitle'" ); + } else { + $this->pageCallback( $this->workTitle ); + } break; case "id": if ( $this->parenttag == 'revision' ) { - $this->workRevision->setID( $this->appenddata ); + if( $this->workRevision ) + $this->workRevision->setID( $this->appenddata ); } break; case "text": - $this->workRevision->setText( $this->appenddata ); + if( $this->workRevision ) + $this->workRevision->setText( $this->appenddata ); break; case "username": - $this->workRevision->setUsername( $this->appenddata ); + if( $this->workRevision ) + $this->workRevision->setUsername( $this->appenddata ); break; case "ip": - $this->workRevision->setUserIP( $this->appenddata ); + if( $this->workRevision ) + $this->workRevision->setUserIP( $this->appenddata ); break; case "timestamp": - $this->workRevision->setTimestamp( $this->appenddata ); + if( $this->workRevision ) + $this->workRevision->setTimestamp( $this->appenddata ); break; case "comment": - $this->workRevision->setComment( $this->appenddata ); + if( $this->workRevision ) + $this->workRevision->setComment( $this->appenddata ); break; case "minor": - $this->workRevision->setMinor( true ); + if( $this->workRevision ) + $this->workRevision->setMinor( true ); break; default: $this->debug( "Bad append: {$this->appendfield}" ); @@ -741,10 +756,12 @@ class WikiImporter { } xml_set_element_handler( $parser, "in_page", "out_page" ); - $ok = call_user_func_array( $this->mRevisionCallback, - array( &$this->workRevision, &$this ) ); - if( $ok ) { - $this->workSuccessCount++; + if( $this->workRevision ) { + $ok = call_user_func_array( $this->mRevisionCallback, + array( &$this->workRevision, &$this ) ); + if( $ok ) { + $this->workSuccessCount++; + } } } @@ -774,7 +791,10 @@ class WikiImporter { } -/** @package MediaWiki */ +/** + * @todo document (e.g. one-sentence class description). + * @addtogroup SpecialPage + */ class ImportStringSource { function ImportStringSource( $string ) { $this->mString = $string; @@ -795,7 +815,10 @@ class ImportStringSource { } } -/** @package MediaWiki */ +/** + * @todo document (e.g. one-sentence class description). + * @addtogroup SpecialPage + */ class ImportStreamSource { function ImportStreamSource( $handle ) { $this->mHandle = $handle; @@ -809,7 +832,7 @@ class ImportStreamSource { return fread( $this->mHandle, 32768 ); } - function newFromFile( $filename ) { + static function newFromFile( $filename ) { $file = @fopen( $filename, 'rt' ); if( !$file ) { return new WikiErrorMsg( "importcantopen" ); diff --git a/includes/SpecialIpblocklist.php b/includes/SpecialIpblocklist.php index 293059f2..8cb5729e 100644 --- a/includes/SpecialIpblocklist.php +++ b/includes/SpecialIpblocklist.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -10,7 +9,7 @@ */ function wfSpecialIpblocklist() { global $wgUser, $wgOut, $wgRequest; - + $ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ); $id = $wgRequest->getVal( 'id' ); $reason = $wgRequest->getText( 'wpUnblockReason' ); @@ -27,8 +26,18 @@ function wfSpecialIpblocklist() { $wgOut->permissionRequired( 'block' ); return; } + # Can't unblock when the database is locked + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } $ipu->doSubmit(); } else if ( "unblock" == $action ) { + # Can't unblock when the database is locked + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } $ipu->showForm( "" ); } else { $ipu->showList( "" ); @@ -36,15 +45,14 @@ function wfSpecialIpblocklist() { } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:ipblocklist GUI + * @addtogroup SpecialPage */ class IPUnblockForm { var $ip, $reason, $id; function IPUnblockForm( $ip, $id, $reason ) { - $this->ip = $ip; + $this->ip = strtr( $ip, '_', ' ' ); $this->id = $id; $this->reason = $reason; } @@ -154,7 +162,7 @@ class IPUnblockForm { } function showList( $msg ) { - global $wgOut; + global $wgOut, $wgUser; $wgOut->setPagetitle( wfMsg( "ipblocklist" ) ); if ( "" != $msg ) { @@ -168,6 +176,9 @@ class IPUnblockForm { $conds = array(); $matches = array(); + // Is user allowed to see all the blocks? + if ( !$wgUser->isAllowed( 'oversight' ) ) + $conds['ipb_deleted'] = 0; if ( $this->ip == '' ) { // No extra conditions } elseif ( substr( $this->ip, 0, 1 ) == '#' ) { @@ -189,17 +200,21 @@ class IPUnblockForm { } } + # TODO: difference message between + # a) an real empty list and + # b) requested ip/username not on list $pager = new IPBlocklistPager( $this, $conds ); - $s = $pager->getNavigationBar() . - $this->searchForm(); if ( $pager->getNumRows() ) { + $s = $this->searchForm() . + $pager->getNavigationBar(); $s .= "<ul>" . $pager->getBody() . "</ul>"; + $s .= $pager->getNavigationBar(); } else { - $s .= '<p>' . wfMsgHTML( 'ipblocklistempty' ) . '</p>'; + $s = $this->searchForm() . + '<p>' . wfMsgHTML( 'ipblocklistempty' ) . '</p>'; } - $s .= $pager->getNavigationBar(); $wgOut->addHTML( $s ); } @@ -223,7 +238,7 @@ class IPUnblockForm { 'value' => $this->ip ) ) . wfElement( 'input', array( 'type' => 'submit', - 'value' => wfMsg( 'searchbutton' ) ) ) . + 'value' => wfMsg( 'ipblocklist-submit' ) ) ) . '</form>'; } @@ -287,19 +302,27 @@ class IPUnblockForm { $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) ); - $s = "<li>{$line}"; - + $unblocklink = ''; if ( $wgUser->isAllowed('block') ) { $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $s .= ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')'; + $unblocklink = ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')'; } - $s .= $sk->commentBlock( $block->mReason ); - $s .= "</li>\n"; + + $comment = $sk->commentBlock( $block->mReason ); + + $s = "{$line} $comment"; + if ( $block->mHideName ) + $s = '<span class="history-deleted">' . $s . '</span>'; + wfProfileOut( __METHOD__ ); - return $s; + return "<li>$s $unblocklink</li>\n"; } } +/** + * @todo document + * @addtogroup Pager + */ class IPBlocklistPager extends ReverseChronologicalPager { public $mForm, $mConds; diff --git a/includes/SpecialListredirects.php b/includes/SpecialListredirects.php index f717ef72..09dc2b39 100644 --- a/includes/SpecialListredirects.php +++ b/includes/SpecialListredirects.php @@ -1,7 +1,6 @@ <?php /** - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Rob Church <robchur@gmail.com> * @copyright © 2006 Rob Church @@ -9,10 +8,9 @@ */ /** - * @package MediaWiki - * @subpackage SpecialPage + * Special:Listredirects - Lists all the redirects on the wiki. + * @addtogroup SpecialPage */ - class ListredirectsPage extends QueryPage { function getName() { return( 'Listredirects' ); } @@ -21,7 +19,7 @@ class ListredirectsPage extends QueryPage { function sortDescending() { return( false ); } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace, 0 AS value FROM $page WHERE page_is_redirect = 1"; return( $sql ); diff --git a/includes/SpecialListusers.php b/includes/SpecialListusers.php index b0794344..42498430 100644 --- a/includes/SpecialListusers.php +++ b/includes/SpecialListusers.php @@ -23,8 +23,7 @@ # http://www.gnu.org/copyleft/gpl.html /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -32,176 +31,169 @@ * rights (sysop, bureaucrat, developer) will have them displayed * next to their names. * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ -class ListUsersPage extends QueryPage { - var $requestedGroup = ''; - var $requestedUser = ''; - function getName() { - return 'Listusers'; +class UsersPager extends AlphabeticPager { + + function __construct($group=null) { + global $wgRequest; + $this->requestedGroup = $group != "" ? $group : $wgRequest->getVal( 'group' ); + $un = $wgRequest->getText( 'username' ); + $this->requestedUser = ''; + if ( $un != '' ) { + $username = Title::makeTitleSafe( NS_USER, $un ); + if( ! is_null( $username ) ) { + $this->requestedUser = $username->getText(); + } + } + parent::__construct(); } - function isSyndicated() { return false; } - /** - * Not expensive, this class won't work properly with the caching system anyway - */ - function isExpensive() { - return false; + + function getIndexField() { + return 'user_name'; } - /** - * Fetch user page links and cache their existence - */ - function preprocessResults( &$db, &$res ) { - $batch = new LinkBatch; - while ( $row = $db->fetchObject( $res ) ) { - $batch->addObj( Title::makeTitleSafe( $row->namespace, $row->title ) ); + function getQueryInfo() { + $conds=array(); + // don't show hidden names + $conds[]='ipb_deleted IS NULL OR ipb_deleted = 0'; + if ($this->requestedGroup != "") { + $conds['ug_group'] = $this->requestedGroup; } - $batch->execute(); + if ($this->requestedUser != "") { + $conds[] = 'user_name >= ' . wfGetDB()->addQuotes( $this->requestedUser ); + } + + list ($user,$user_groups,$ipblocks) = wfGetDB()->tableNamesN('user','user_groups','ipblocks'); - // Back to start for display - if( $db->numRows( $res ) > 0 ) { - // If there are no rows we get an error seeking. - $db->dataSeek( $res, 0 ); + return array( + 'tables' => " $user LEFT JOIN $user_groups ON user_id=ug_user LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ", + 'fields' => array('user_name', + 'MAX(user_id) AS user_id', + 'COUNT(ug_group) AS numgroups', + 'MAX(ug_group) AS singlegroup'), + 'options' => array('GROUP BY' => 'user_name'), + 'conds' => $conds + ); + + } + + function formatRow( $row ) { + $userPage = Title::makeTitle( NS_USER, $row->user_name ); + $name = $this->getSkin()->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ); + + if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) { + $list = array(); + foreach( self::getGroups( $row->user_id ) as $group ) + $list[] = self::buildGroupLink( $group ); + $groups = implode( ', ', $list ); + } elseif( $row->numgroups == 1 ) { + $groups = self::buildGroupLink( $row->singlegroup ); + } else { + $groups = ''; } + + return '<li>' . wfSpecialList( $name, $groups ) . '</li>'; + } + + function getBody() { + if (!$this->mQueryDone) { + $this->doQuery(); + } + $batch = new LinkBatch; + $db = $this->mDb; + + $this->mResult->rewind(); + + while ( $row = $this->mResult->fetchObject() ) { + $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); + } + $batch->execute(); + $this->mResult->rewind(); + return parent::getBody(); } - /** - * Show a drop down list to select a group as well as a user name - * search box. - * @todo localize - */ function getPageHeader( ) { + global $wgRequest; $self = $this->getTitle(); # Form tag - $out = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); - + $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $self->getLocalUrl() ) ) . + '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'listusers' ) ); + + # Username field + $out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' . + Xml::input( 'username', 20, $this->requestedUser, array( 'id' => 'offset' ) ) . ' '; + + if( $this->mLimit ) + $out .= Xml::hidden( 'limit', $this->mLimit ); + # Group drop-down list - $out .= wfElement( 'label', array( 'for' => 'group' ), wfMsg( 'group' ) ) . ' '; - $out .= wfOpenElement( 'select', array( 'name' => 'group' ) ); - $out .= wfElement( 'option', array( 'value' => '' ), wfMsg( 'group-all' ) ); # Item for "all groups" + $out .= Xml::label( wfMsg( 'group' ), 'group' ) . ' ' . + Xml::openElement('select', array( 'name' => 'group', 'id' => 'group' ) ) . + Xml::option( wfMsg( 'group-all' ), '' ); # Item for "all groups" + $groups = User::getAllGroups(); foreach( $groups as $group ) { $attribs = array( 'value' => $group ); - if( $group == $this->requestedGroup ) - $attribs['selected'] = 'selected'; - $out .= wfElement( 'option', $attribs, User::getGroupName( $group ) ); + $attribs['selected'] = ( $group == $this->requestedGroup ) ? 'selected' : ''; + $out .= Xml::option( User::getGroupName( $group ), $attribs['value'], $attribs['selected'] ); } - $out .= wfCloseElement( 'select' ) . ' ';;# . wfElement( 'br' ); - - # Username field - $out .= wfElement( 'label', array( 'for' => 'username' ), wfMsg( 'listusersfrom' ) ) . ' '; - $out .= wfElement( 'input', array( 'type' => 'text', 'id' => 'username', 'name' => 'username', - 'value' => $this->requestedUser ) ) . ' '; - - # Preserve offset and limit - if( $this->offset ) - $out .= wfElement( 'input', array( 'type' => 'hidden', 'name' => 'offset', 'value' => $this->offset ) ); - if( $this->limit ) - $out .= wfElement( 'input', array( 'type' => 'hidden', 'name' => 'limit', 'value' => $this->limit ) ); + $out .= Xml::closeElement( 'select' ) . ' '; # Submit button and form bottom - $out .= wfElement( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'allpagessubmit' ) ) ); - $out .= wfCloseElement( 'form' ); + $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . + '</fieldset>' . + Xml::closeElement( 'form' ); return $out; } - function getSQL() { - global $wgDBtype; - $dbr =& wfGetDB( DB_SLAVE ); - $user = $dbr->tableName( 'user' ); - $user_groups = $dbr->tableName( 'user_groups' ); - - // We need to get an 'atomic' list of users, so that we - // don't break the list half-way through a user's group set - // and so that lists by group will show all group memberships. - // - // On MySQL 4.1 we could use GROUP_CONCAT to grab group - // assignments together with users pretty easily. On other - // versions, it's not so easy to do it consistently. - // For now we'll just grab the number of memberships, so - // we can then do targetted checks on those who are in - // non-default groups as we go down the list. - - $userspace = NS_USER; - $sql = "SELECT 'Listusers' as type, $userspace AS namespace, user_name AS title, " . - "user_name as value, user_id, COUNT(ug_group) as numgroups " . - "FROM $user ". - "LEFT JOIN $user_groups ON user_id=ug_user " . - $this->userQueryWhere( $dbr ) . - " GROUP BY user_name"; - if ( $wgDBtype != 'mysql' ) { - $sql .= ",user_id"; - } - return $sql; - } - - function userQueryWhere( &$dbr ) { - $conds = $this->userQueryConditions( $dbr ); - return empty( $conds ) - ? "" - : "WHERE " . $dbr->makeList( $conds, LIST_AND ); - } - - function userQueryConditions( $dbr ) { - $conds = array(); - if( $this->requestedGroup != '' ) { - $conds['ug_group'] = $this->requestedGroup; - } - if( $this->requestedUser != '' ) { - $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); - } - return $conds; + /** + * Preserve group and username offset parameters when paging + * @return array + */ + function getDefaultQuery() { + $query = parent::getDefaultQuery(); + if( $this->requestedGroup != '' ) + $query['group'] = $this->requestedGroup; + if( $this->requestedUser != '' ) + $query['username'] = $this->requestedUser; + return $query; } - function linkParameters() { - $conds = array(); - if( $this->requestedGroup != '' ) { - $conds['group'] = $this->requestedGroup; - } - if( $this->requestedUser != '' ) { - $conds['username'] = $this->requestedUser; + /** + * Get a list of groups the specified user belongs to + * + * @param int $uid + * @return array + */ + private static function getGroups( $uid ) { + $dbr = wfGetDB( DB_SLAVE ); + $groups = array(); + $res = $dbr->select( 'user_groups', 'ug_group', array( 'ug_user' => $uid ), __METHOD__ ); + if( $res && $dbr->numRows( $res ) > 0 ) { + while( $row = $dbr->fetchObject( $res ) ) + $groups[] = $row->ug_group; + $dbr->freeResult( $res ); } - return $conds; - } - - function sortDescending() { - return false; + return $groups; } - function formatResult( $skin, $result ) { - $userPage = Title::makeTitle( $result->namespace, $result->title ); - $name = $skin->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ); - $groups = null; - - if( !isset( $result->numgroups ) || $result->numgroups > 0 ) { - $dbr =& wfGetDB( DB_SLAVE ); - $result = $dbr->select( 'user_groups', - array( 'ug_group' ), - array( 'ug_user' => $result->user_id ), - 'ListUsersPage::formatResult' ); - $groups = array(); - while( $row = $dbr->fetchObject( $result ) ) { - $groups[$row->ug_group] = User::getGroupMember( $row->ug_group ); - } - $dbr->freeResult( $result ); - - if( count( $groups ) > 0 ) { - foreach( $groups as $group => $desc ) { - $list[] = User::makeGroupLinkHTML( $group, $desc ); - } - $groups = implode( ', ', $list ); - } else { - $groups = ''; - } - - } - - return wfSpecialList( $name, $groups ); + /** + * Format a link to a group description page + * + * @param string $group + * @return string + */ + private static function buildGroupLink( $group ) { + static $cache = array(); + if( !isset( $cache[$group] ) ) + $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) ); + return $cache[$group]; } } @@ -210,25 +202,26 @@ class ListUsersPage extends QueryPage { * $par string (optional) A group to list users from */ function wfSpecialListusers( $par = null ) { - global $wgRequest; + global $wgRequest, $wgOut; list( $limit, $offset ) = wfCheckLimits(); - - $slu = new ListUsersPage(); - - /** - * Get some parameters - */ $groupTarget = isset($par) ? $par : $wgRequest->getVal( 'group' ); - $slu->requestedGroup = $groupTarget; - # 'Validate' the username first - $username = $wgRequest->getText( 'username', '' ); - $user = User::newFromName( $username ); - $slu->requestedUser = is_object( $user ) ? $user->getName() : ''; + $up = new UsersPager($par); + + # getBody() first to check, if empty + $usersbody = $up->getBody(); + $s = $up->getPageHeader(); + if( $usersbody ) { + $s .= $up->getNavigationBar(); + $s .= '<ul>' . $usersbody . '</ul>'; + $s .= $up->getNavigationBar() ; + } else { + $s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>'; + }; - return $slu->doQuery( $offset, $limit ); + $wgOut->addHTML( $s ); } ?> diff --git a/includes/SpecialLockdb.php b/includes/SpecialLockdb.php index f0142e5c..db4006f5 100644 --- a/includes/SpecialLockdb.php +++ b/includes/SpecialLockdb.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -37,9 +36,8 @@ function wfSpecialLockdb() { } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * A form to make the database readonly (eg for maintenance purposes). + * @addtogroup SpecialPage */ class DBLockForm { var $reason = ''; @@ -126,7 +124,7 @@ END $wgOut->addWikiText( wfMsg( 'lockdbsuccesstext' ) ); } - function notWritable() { + public static function notWritable() { global $wgOut; $wgOut->errorPage( 'lockdb', 'lockfilenotwritable' ); } diff --git a/includes/SpecialLog.php b/includes/SpecialLog.php index 7076d819..3c9d0960 100644 --- a/includes/SpecialLog.php +++ b/includes/SpecialLog.php @@ -19,8 +19,7 @@ /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -38,18 +37,17 @@ function wfSpecialLog( $par = '' ) { /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class LogReader { var $db, $joinClauses, $whereClauses; - var $type = '', $user = '', $title = null; + var $type = '', $user = '', $title = null, $pattern = false; /** * @param WebRequest $request For internal use use a FauxRequest object to pass arbitrary parameters. */ function LogReader( $request ) { - $this->db =& wfGetDB( DB_SLAVE ); + $this->db = wfGetDB( DB_SLAVE ); $this->setupQuery( $request ); } @@ -68,11 +66,15 @@ class LogReader { $this->limitType( $request->getVal( 'type' ) ); $this->limitUser( $request->getText( 'user' ) ); - $this->limitTitle( $request->getText( 'page' ) ); + $this->limitTitle( $request->getText( 'page' ) , $request->getBool( 'pattern' ) ); $this->limitTime( $request->getVal( 'from' ), '>=' ); $this->limitTime( $request->getVal( 'until' ), '<=' ); list( $this->limit, $this->offset ) = $request->getLimitOffset(); + + // XXX This all needs to use Pager, ugly hack for now. + global $wgMiserMode; + if ($wgMiserMode && ($this->offset >10000)) $this->offset=10000; } /** @@ -118,15 +120,22 @@ class LogReader { * @param string $page Title name as text * @private */ - function limitTitle( $page ) { + function limitTitle( $page , $pattern ) { + global $wgMiserMode; $title = Title::newFromText( $page ); if( empty( $page ) || is_null( $title ) ) { return false; } $this->title =& $title; - $safetitle = $this->db->strencode( $title->getDBkey() ); + $this->pattern = $pattern; $ns = $title->getNamespace(); - $this->whereClauses[] = "log_namespace=$ns AND log_title='$safetitle'"; + if ( $pattern && !$wgMiserMode ) { + $safetitle = $this->db->escapeLike( $title->getDBkey() ); // use escapeLike to avoid expensive search patterns like 't%st%' + $this->whereClauses[] = "log_namespace=$ns AND log_title LIKE '$safetitle%'"; + } else { + $safetitle = $this->db->strencode( $title->getDBkey() ); + $this->whereClauses[] = "log_namespace=$ns AND log_title = '$safetitle'"; + } } /** @@ -190,6 +199,13 @@ class LogReader { } /** + * @return boolean The checkbox, if titles should be searched by a pattern too + */ + function queryPattern() { + return $this->pattern; + } + + /** * @return string The text of the title that this LogReader has been limited to. */ function queryTitle() { @@ -203,8 +219,7 @@ class LogReader { /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class LogViewer { /** @@ -218,7 +233,7 @@ class LogViewer { */ function LogViewer( &$reader ) { global $wgUser; - $this->skin =& $wgUser->getSkin(); + $this->skin = $wgUser->getSkin(); $this->reader =& $reader; } @@ -230,9 +245,13 @@ class LogViewer { $this->showHeader( $wgOut ); $this->showOptions( $wgOut ); $result = $this->getLogRows(); - $this->showPrevNext( $wgOut ); - $this->doShowList( $wgOut, $result ); - $this->showPrevNext( $wgOut ); + if ( $this->numResults > 0 ) { + $this->showPrevNext( $wgOut ); + $this->doShowList( $wgOut, $result ); + $this->showPrevNext( $wgOut ); + } else { + $this->showError( $wgOut ); + } } /** @@ -252,8 +271,8 @@ class LogViewer { $batch = new LinkBatch; while ( $s = $result->fetchObject() ) { // User link - $title = Title::makeTitleSafe( NS_USER, $s->user_name ); - $batch->addObj( $title ); + $batch->addObj( Title::makeTitleSafe( NS_USER, $s->user_name ) ); + $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $s->user_name ) ); // Move destination link if ( $s->log_type == 'move' ) { @@ -276,32 +295,38 @@ class LogViewer { * @param OutputPage $out where to send output */ function showList( &$out ) { - $this->doShowList( $out, $this->getLogRows() ); + $result = $this->getLogRows(); + if ( $this->numResults > 0 ) { + $this->doShowList( $out, $result ); + } else { + $this->showError( $out ); + } } function doShowList( &$out, $result ) { // Rewind result pointer and go through it again, making the HTML - if ($this->numResults > 0) { - $html = "\n<ul>\n"; - $result->seek( 0 ); - while( $s = $result->fetchObject() ) { - $html .= $this->logLine( $s ); - } - $html .= "\n</ul>\n"; - $out->addHTML( $html ); - } else { - $out->addWikiText( wfMsg( 'logempty' ) ); + $html = "\n<ul>\n"; + $result->seek( 0 ); + while( $s = $result->fetchObject() ) { + $html .= $this->logLine( $s ); } + $html .= "\n</ul>\n"; + $out->addHTML( $html ); $result->free(); } + function showError( &$out ) { + $out->addWikiText( wfMsg( 'logempty' ) ); + } + /** * @param Object $s a single row from the result set * @return string Formatted HTML list item * @private */ function logLine( $s ) { - global $wgLang; + global $wgLang, $wgUser;; + $skin = $wgUser->getSkin(); $title = Title::makeTitle( $s->log_namespace, $s->log_title ); $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $s->log_timestamp), true ); @@ -314,20 +339,43 @@ class LogViewer { $linkCache->addBadLinkObj( $title ); } - $userLink = $this->skin->userLink( $s->log_user, $s->user_name ) . $this->skin->userToolLinks( $s->log_user, $s->user_name ); + $userLink = $this->skin->userLink( $s->log_user, $s->user_name ) . $this->skin->userToolLinksRedContribs( $s->log_user, $s->user_name ); $comment = $this->skin->commentBlock( $s->log_comment ); $paramArray = LogPage::extractParams( $s->log_params ); $revert = ''; + // show revertmove link if ( $s->log_type == 'move' && isset( $paramArray[0] ) ) { - $specialTitle = SpecialPage::getTitleFor( 'Movepage' ); $destTitle = Title::newFromText( $paramArray[0] ); if ( $destTitle ) { - $revert = '(' . $this->skin->makeKnownLinkObj( $specialTitle, wfMsg( 'revertmove' ), + $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ), + wfMsg( 'revertmove' ), 'wpOldTitle=' . urlencode( $destTitle->getPrefixedDBkey() ) . '&wpNewTitle=' . urlencode( $title->getPrefixedDBkey() ) . '&wpReason=' . urlencode( wfMsgForContent( 'revertmove' ) ) . '&wpMovetalk=0' ) . ')'; } + // show undelete link + } elseif ( $s->log_action == 'delete' && $wgUser->isAllowed( 'delete' ) ) { + $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ), + wfMsg( 'undeletebtn' ) , + 'target='. urlencode( $title->getPrefixedDBkey() ) ) . ')'; + + // show unblock link + } elseif ( $s->log_action == 'block' && $wgUser->isAllowed( 'block' ) ) { + $revert = '(' . $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Ipblocklist' ), + wfMsg( 'unblocklink' ), + 'action=unblock&ip=' . urlencode( $s->log_title ) ) . ')'; + // show change protection link + } elseif ( $s->log_action == 'protect' && $wgUser->isAllowed( 'protect' ) ) { + $revert = '(' . $skin->makeKnownLink( $title->getPrefixedDBkey() , + wfMsg( 'protect_change' ), + 'action=unprotect' ) . ')'; + // show user tool links for self created users + } elseif ( $s->log_action == 'create2' ) { + $revert = $this->skin->userToolLinksRedContribs( $s->log_user, $s->log_title ); + // do not show $comment for self created accounts. It includes wrong user tool links: + // 'blockip' for users w/o block allowance and broken links for very long usernames (bug 4756) + $comment = ''; } $action = LogPage::actionText( $s->log_type, $s->log_action, $title, $this->skin, $paramArray, true, true ); @@ -352,17 +400,20 @@ class LogViewer { * @private */ function showOptions( &$out ) { - global $wgScript; + global $wgScript, $wgMiserMode; $action = htmlspecialchars( $wgScript ); $title = SpecialPage::getTitleFor( 'Log' ); $special = htmlspecialchars( $title->getPrefixedDBkey() ); $out->addHTML( "<form action=\"$action\" method=\"get\">\n" . - "<input type='hidden' name='title' value=\"$special\" />\n" . - $this->getTypeMenu() . - $this->getUserInput() . - $this->getTitleInput() . - "<input type='submit' value=\"" . wfMsg( 'allpagessubmit' ) . "\" />" . - "</form>" ); + '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'log' ) ) . + Xml::hidden( 'title', $special ) . "\n" . + $this->getTypeMenu() . "\n" . + $this->getUserInput() . "\n" . + $this->getTitleInput() . "\n" . + (!$wgMiserMode?($this->getTitlePattern()."\n"):"") . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + "</fieldset></form>" ); } /** @@ -371,12 +422,26 @@ class LogViewer { */ function getTypeMenu() { $out = "<select name='type'>\n"; - foreach( LogPage::validTypes() as $type ) { - $text = htmlspecialchars( LogPage::logName( $type ) ); - $selected = ($type == $this->reader->queryType()) ? ' selected="selected"' : ''; - $out .= "<option value=\"$type\"$selected>$text</option>\n"; + + $validTypes = LogPage::validTypes(); + $m = array(); // Temporary array + + // First pass to load the log names + foreach( $validTypes as $type ) { + $text = LogPage::logName( $type ); + $m[$text] = $type; + } + + // Second pass to sort by name + ksort($m); + + // Third pass generates sorted XHTML content + foreach( $m as $text => $type ) { + $selected = ($type == $this->reader->queryType()); + $out .= Xml::option( $text, $type, $selected ) . "\n"; } - $out .= "</select>\n"; + + $out .= '</select>'; return $out; } @@ -385,8 +450,8 @@ class LogViewer { * @private */ function getUserInput() { - $user = htmlspecialchars( $this->reader->queryUser() ); - return wfMsg('specialloguserlabel') . "<input type='text' name='user' size='12' value=\"$user\" />\n"; + $user = $this->reader->queryUser(); + return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'user', 12, $user ); } /** @@ -394,8 +459,17 @@ class LogViewer { * @private */ function getTitleInput() { - $title = htmlspecialchars( $this->reader->queryTitle() ); - return wfMsg('speciallogtitlelabel') . "<input type='text' name='page' size='20' value=\"$title\" />\n"; + $title = $this->reader->queryTitle(); + return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'page', 20, $title ); + } + + /** + * @return boolean Checkbox + * @private + */ + function getTitlePattern() { + $pattern = $this->reader->queryPattern(); + return Xml::checkLabel( wfMsg( 'log-title-wildcard' ), 'pattern', 'pattern', $pattern ); } /** @@ -408,6 +482,7 @@ class LogViewer { $pieces[] = 'type=' . urlencode( $this->reader->queryType() ); $pieces[] = 'user=' . urlencode( $this->reader->queryUser() ); $pieces[] = 'page=' . urlencode( $this->reader->queryTitle() ); + $pieces[] = 'pattern=' . urlencode( $this->reader->queryPattern() ); $bits = implode( '&', $pieces ); list( $limit, $offset ) = $wgRequest->getLimitOffset(); diff --git a/includes/SpecialLonelypages.php b/includes/SpecialLonelypages.php index 8770a9e7..430af7a7 100644 --- a/includes/SpecialLonelypages.php +++ b/includes/SpecialLonelypages.php @@ -1,14 +1,13 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * A special page looking for articles with no article linking to them, + * thus being lonely. + * @addtogroup SpecialPage */ class LonelyPagesPage extends PageQueryPage { @@ -29,7 +28,7 @@ class LonelyPagesPage extends PageQueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' ); return diff --git a/includes/SpecialLongpages.php b/includes/SpecialLongpages.php index 3736d6fc..40659889 100644 --- a/includes/SpecialLongpages.php +++ b/includes/SpecialLongpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class LongPagesPage extends ShortPagesPage { @@ -24,13 +22,12 @@ class LongPagesPage extends ShortPagesPage { /** * constructor */ -function wfSpecialLongpages() -{ - list( $limit, $offset ) = wfCheckLimits(); +function wfSpecialLongpages() { + list( $limit, $offset ) = wfCheckLimits(); - $lpp = new LongPagesPage(); + $lpp = new LongPagesPage(); - $lpp->doQuery( $offset, $limit ); + $lpp->doQuery( $offset, $limit ); } ?> diff --git a/includes/SpecialMIMEsearch.php b/includes/SpecialMIMEsearch.php index 8678118f..d50efc02 100644 --- a/includes/SpecialMIMEsearch.php +++ b/includes/SpecialMIMEsearch.php @@ -3,16 +3,16 @@ * A special page to search for files by MIME type as defined in the * img_major_mime and img_minor_mime fields in the image table * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** - * @package MediaWiki - * @subpackage SpecialPage + * Searches the database for files of the requested MIME type, comparing this with the + * 'img_major_mime' and 'img_minor_mime' fields in the image table. + * @addtogroup SpecialPage */ class MIMEsearchPage extends QueryPage { var $major, $minor; @@ -38,7 +38,7 @@ class MIMEsearchPage extends QueryPage { } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $image = $dbr->tableName( 'image' ); $major = $dbr->addQuotes( $this->major ); $minor = $dbr->addQuotes( $this->minor ); @@ -69,7 +69,7 @@ class MIMEsearchPage extends QueryPage { $download = $skin->makeMediaLink( $nt->getText(), 'fuck me!', wfMsgHtml( 'download' ) ); $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'), $wgLang->formatNum( $result->img_size ) ); - $dimensions = wfMsg( 'widthheight', $wgLang->formatNum( $result->img_width ), + $dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ), $wgLang->formatNum( $result->img_height ) ); $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text ); $time = $wgLang->timeanddate( $result->img_timestamp ); @@ -79,7 +79,7 @@ class MIMEsearchPage extends QueryPage { } /** - * constructor + * Output the HTML search form, and constructs the MIMEsearchPage object. */ function wfSpecialMIMEsearch( $par = null ) { global $wgRequest, $wgTitle, $wgOut; diff --git a/includes/SpecialMostcategories.php b/includes/SpecialMostcategories.php index 41bfb0cd..df2b9adf 100644 --- a/includes/SpecialMostcategories.php +++ b/includes/SpecialMostcategories.php @@ -1,7 +1,6 @@ <?php /** - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason @@ -9,8 +8,8 @@ */ /** - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Mostcategories + * @addtogroup SpecialPage */ class MostcategoriesPage extends QueryPage { @@ -19,7 +18,7 @@ class MostcategoriesPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' ); return " diff --git a/includes/SpecialMostimages.php b/includes/SpecialMostimages.php index 17c07c70..9d16f389 100644 --- a/includes/SpecialMostimages.php +++ b/includes/SpecialMostimages.php @@ -1,7 +1,6 @@ <?php /** - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason @@ -9,17 +8,17 @@ */ /** - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Mostimages + * @addtogroup SpecialPage */ -class MostimagesPage extends QueryPage { +class MostimagesPage extends ImageQueryPage { function getName() { return 'Mostimages'; } function isExpensive() { return true; } function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $imagelinks = $dbr->tableName( 'imagelinks' ); return " @@ -34,20 +33,12 @@ class MostimagesPage extends QueryPage { "; } - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - - $nt = Title::makeTitle( $result->namespace, $result->title ); - $text = $wgContLang->convert( $nt->getPrefixedText() ); - - $plink = $skin->makeKnownLink( $nt->getPrefixedText(), $text ); - - $nl = wfMsgExt( 'nlinks', array( 'parsemag', 'escape'), - $wgLang->formatNum ( $result->value ) ); - $nlink = $skin->makeKnownLink( $nt->getPrefixedText() . '#filelinks', $nl ); - - return wfSpecialList($plink, $nlink); + function getCellHtml( $row ) { + global $wgLang; + return wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $row->value ) ) . '<br />'; } + } /** diff --git a/includes/SpecialMostlinked.php b/includes/SpecialMostlinked.php index 2794ecbb..ab089cf8 100644 --- a/includes/SpecialMostlinked.php +++ b/includes/SpecialMostlinked.php @@ -1,10 +1,10 @@ <?php /** - * A special page to show pages ordered by the number of pages linking to them + * A special page to show pages ordered by the number of pages linking to them. + * Implements Special:Mostlinked * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @author Rob Church <robchur@gmail.com> @@ -12,11 +12,6 @@ * @copyright © 2006 Rob Church * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ - -/** - * @package MediaWiki - * @subpackage SpecialPage - */ class MostlinkedPage extends QueryPage { function getName() { return 'Mostlinked'; } @@ -27,7 +22,7 @@ class MostlinkedPage extends QueryPage { * Note: Getting page_namespace only works if $this->isCached() is false */ function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' ); return "SELECT 'Mostlinked' AS type, diff --git a/includes/SpecialMostlinkedcategories.php b/includes/SpecialMostlinkedcategories.php index e1f84847..725e5b39 100644 --- a/includes/SpecialMostlinkedcategories.php +++ b/includes/SpecialMostlinkedcategories.php @@ -2,18 +2,12 @@ /** * A querypage to show categories ordered in descending order by the pages in them * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ - -/** - * @package MediaWiki - * @subpackage SpecialPage - */ class MostlinkedCategoriesPage extends QueryPage { function getName() { return 'Mostlinkedcategories'; } @@ -21,7 +15,7 @@ class MostlinkedCategoriesPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $categorylinks = $dbr->tableName( 'categorylinks' ); $name = $dbr->addQuotes( $this->getName() ); return diff --git a/includes/SpecialMostrevisions.php b/includes/SpecialMostrevisions.php index 1e3334e9..59157056 100644 --- a/includes/SpecialMostrevisions.php +++ b/includes/SpecialMostrevisions.php @@ -2,8 +2,7 @@ /** * A special page to show pages in the * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason @@ -11,8 +10,7 @@ */ /** - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class MostrevisionsPage extends QueryPage { @@ -21,7 +19,7 @@ class MostrevisionsPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' ); return " diff --git a/includes/SpecialMovepage.php b/includes/SpecialMovepage.php index e3112c4c..d8f01874 100644 --- a/includes/SpecialMovepage.php +++ b/includes/SpecialMovepage.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -42,9 +41,8 @@ function wfSpecialMovepage( $par = null ) { } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * HTML form for Special:Movepage + * @addtogroup SpecialPage */ class MovePageForm { var $oldTitle, $newTitle, $reason; # Text input @@ -68,7 +66,10 @@ class MovePageForm { } function showForm( $err ) { - global $wgOut, $wgUser; + global $wgOut, $wgUser, $wgContLang; + + $start = $wgContLang->isRTL() ? 'right' : 'left'; + $end = $wgContLang->isRTL() ? 'left' : 'right'; $wgOut->setPagetitle( wfMsg( 'movepage' ) ); @@ -108,10 +109,10 @@ class MovePageForm { $submitVar = 'wpDeleteAndMove'; $confirm = " <tr> - <td align='right'> + <td align='$end'> <input type='checkbox' name='wpConfirm' id='wpConfirm' value=\"true\" /> </td> - <td align='left'><label for='wpConfirm'>{$confirmText}</label></td> + <td align='$start'><label for='wpConfirm'>{$confirmText}</label></td> </tr>"; $err = ''; } else { @@ -148,19 +149,19 @@ class MovePageForm { <form id=\"movepage\" method=\"post\" action=\"{$action}\"> <table border='0'> <tr> - <td align='right'>{$movearticle}:</td> - <td align='left'><strong>{$oldTitle}</strong></td> + <td align='$end'>{$movearticle}:</td> + <td align='$start'><strong>{$oldTitle}</strong></td> </tr> <tr> - <td align='right'><label for='wpNewTitle'>{$newtitle}:</label></td> - <td align='left'> + <td align='$end'><label for='wpNewTitle'>{$newtitle}:</label></td> + <td align='$start'> <input type='text' size='40' name='wpNewTitle' id='wpNewTitle' value=\"{$encNewTitle}\" /> <input type='hidden' name=\"wpOldTitle\" value=\"{$encOldTitle}\" /> </td> </tr> <tr> - <td align='right' valign='top'><br /><label for='wpReason'>{$movereason}:</label></td> - <td align='left' valign='top'><br /> + <td align='$end' valign='top'><br /><label for='wpReason'>{$movereason}:</label></td> + <td align='$start' valign='top'><br /> <textarea cols='60' rows='2' name='wpReason' id='wpReason'>{$encReason}</textarea> </td> </tr>" ); @@ -168,7 +169,7 @@ class MovePageForm { if ( $considerTalk ) { $wgOut->addHTML( " <tr> - <td align='right'> + <td align='$end'> <input type='checkbox' id=\"wpMovetalk\" name=\"wpMovetalk\"{$moveTalkChecked} value=\"1\" /> </td> <td><label for=\"wpMovetalk\">{$movetalk}</label></td> @@ -177,7 +178,7 @@ class MovePageForm { $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching(); $watch = '<tr>'; - $watch .= '<td align="right">' . Xml::check( 'wpWatch', $watchChecked, array( 'id' => 'watch' ) ) . '</td>'; + $watch .= "<td align=\"$end\">" . Xml::check( 'wpWatch', $watchChecked, array( 'id' => 'watch' ) ) . '</td>'; $watch .= '<td>' . Xml::label( wfMsg( 'move-watch' ), 'watch' ) . '</td>'; $watch .= '</tr>'; $wgOut->addHtml( $watch ); @@ -186,7 +187,7 @@ class MovePageForm { {$confirm} <tr> <td> </td> - <td align='left'> + <td align='$start'> <input type='submit' name=\"{$submitVar}\" value=\"{$movepagebtn}\" /> </td> </tr> diff --git a/includes/SpecialNewimages.php b/includes/SpecialNewimages.php index 062e7e12..72b169b1 100644 --- a/includes/SpecialNewimages.php +++ b/includes/SpecialNewimages.php @@ -1,18 +1,17 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * */ function wfSpecialNewimages( $par, $specialPage ) { - global $wgUser, $wgOut, $wgLang, $wgRequest, $wgGroupPermissions; + global $wgUser, $wgOut, $wgLang, $wgRequest, $wgGroupPermissions, $wgMiserMode; $wpIlMatch = $wgRequest->getText( 'wpIlMatch' ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $sk = $wgUser->getSkin(); $shownav = !$specialPage->including(); $hidebots = $wgRequest->getBool('hidebots',1); @@ -75,23 +74,23 @@ function wfSpecialNewimages( $par, $specialPage ) { $where = array(); $searchpar = ''; - if ( $wpIlMatch != '' ) { + if ( $wpIlMatch != '' && !$wgMiserMode) { $nt = Title::newFromUrl( $wpIlMatch ); if($nt ) { $m = $dbr->strencode( strtolower( $nt->getDBkey() ) ); $m = str_replace( '%', "\\%", $m ); $m = str_replace( '_', "\\_", $m ); - $where[] = "LCASE(img_name) LIKE '%{$m}%'"; + $where[] = "LOWER(img_name) LIKE '%{$m}%'"; $searchpar = '&wpIlMatch=' . urlencode( $wpIlMatch ); } } $invertSort = false; if( $until = $wgRequest->getVal( 'until' ) ) { - $where[] = 'img_timestamp < ' . $dbr->timestamp( $until ); + $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'"; } if( $from = $wgRequest->getVal( 'from' ) ) { - $where[] = 'img_timestamp >= ' . $dbr->timestamp( $from ); + $where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'"; $invertSort = true; } $sql='SELECT img_size, img_name, img_user, img_user_text,'. @@ -158,12 +157,12 @@ function wfSpecialNewimages( $par, $specialPage ) { $sub = wfMsg( 'ilsubmit' ); $titleObj = SpecialPage::getTitleFor( 'Newimages' ); $action = $titleObj->escapeLocalURL( $hidebots ? '' : 'hidebots=0' ); - if ($shownav) { + if ($shownav && !$wgMiserMode) { $wgOut->addHTML( "<form id=\"imagesearch\" method=\"post\" action=\"" . "{$action}\">" . - "<input type='text' size='20' name=\"wpIlMatch\" value=\"" . - htmlspecialchars( $wpIlMatch ) . "\" /> " . - "<input type='submit' name=\"wpIlSubmit\" value=\"{$sub}\" /></form>" ); + Xml::input( 'wpIlMatch', 20, $wpIlMatch ) . ' ' . + Xml::submitButton( $sub, array( 'name' => 'wpIlSubmit' ) ) . + "</form>" ); } /** @@ -178,21 +177,21 @@ function wfSpecialNewimages( $par, $specialPage ) { } $now = wfTimestampNow(); $date = $wgLang->timeanddate( $now, true ); - $dateLink = $sk->makeKnownLinkObj( $titleObj, wfMsg( 'sp-newimages-showfrom', $date ), 'from='.$now.$botpar.$searchpar ); + $dateLink = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml( 'sp-newimages-showfrom', $date ), 'from='.$now.$botpar.$searchpar ); - $botLink = $sk->makeKnownLinkObj($titleObj, wfMsg( 'showhidebots', ($hidebots ? wfMsg('show') : wfMsg('hide'))),'hidebots='.($hidebots ? '0' : '1').$searchpar); + $botLink = $sk->makeKnownLinkObj($titleObj, wfMsgHtml( 'showhidebots', ($hidebots ? wfMsgHtml('show') : wfMsgHtml('hide'))),'hidebots='.($hidebots ? '0' : '1').$searchpar); - $prevLink = wfMsg( 'prevn', $wgLang->formatNum( $limit ) ); + $prevLink = wfMsgHtml( 'prevn', $wgLang->formatNum( $limit ) ); if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) { $prevLink = $sk->makeKnownLinkObj( $titleObj, $prevLink, 'from=' . $firstTimestamp . $botpar . $searchpar ); } - $nextLink = wfMsg( 'nextn', $wgLang->formatNum( $limit ) ); + $nextLink = wfMsgHtml( 'nextn', $wgLang->formatNum( $limit ) ); if( $shownImages > $limit && $lastTimestamp ) { $nextLink = $sk->makeKnownLinkObj( $titleObj, $nextLink, 'until=' . $lastTimestamp.$botpar.$searchpar ); } - $prevnext = '<p>' . $botLink . ' '. wfMsg( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'</p>'; + $prevnext = '<p>' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'</p>'; if ($shownav) $wgOut->addHTML( $prevnext ); diff --git a/includes/SpecialNewpages.php b/includes/SpecialNewpages.php index 62007383..48037a73 100644 --- a/includes/SpecialNewpages.php +++ b/includes/SpecialNewpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Newpages + * @addtogroup SpecialPage */ class NewPagesPage extends QueryPage { @@ -41,7 +39,7 @@ class NewPagesPage extends QueryPage { function getSQL() { global $wgUser, $wgUseRCPatrol; $usepatrol = ( $wgUseRCPatrol && $wgUser->isAllowed( 'patrol' ) ) ? 1 : 0; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $recentchanges, $page ) = $dbr->tableNamesN( 'recentchanges', 'page' ); $uwhere = $this->makeUserWhere( $dbr ); @@ -52,9 +50,9 @@ class NewPagesPage extends QueryPage { rc_namespace AS namespace, rc_title AS title, rc_cur_id AS cur_id, - rc_user AS user, + rc_user AS \"user\", rc_user_text AS user_text, - rc_comment as comment, + rc_comment as \"comment\", rc_timestamp AS timestamp, rc_timestamp AS value, '{$usepatrol}' as usepatrol, @@ -133,13 +131,16 @@ class NewPagesPage extends QueryPage { */ function getPageHeader() { $self = SpecialPage::getTitleFor( $this->getName() ); - $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); - $form .= '<table><tr><td align="right">' . wfMsgHtml( 'namespace' ) . '</td>'; - $form .= '<td>' . HtmlNamespaceSelector( $this->namespace ) . '</td><tr>'; - $form .= '<tr><td align="right">' . wfMsgHtml( 'newpages-username' ) . '</td>'; - $form .= '<td>' . wfInput( 'username', 30, $this->username ) . '</td></tr>'; - $form .= '<tr><td></td><td>' . wfSubmitButton( wfMsg( 'allpagessubmit' ) ) . '</td></tr></table>'; - $form .= wfHidden( 'offset', $this->offset ) . wfHidden( 'limit', $this->limit ) . '</form>'; + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); + # Namespace selector + $form .= '<table><tr><td align="right">' . Xml::label( wfMsg( 'namespace' ), 'namespace' ) . '</td>'; + $form .= '<td>' . Xml::namespaceSelector( $this->namespace ) . '</td></tr>'; + # Username filter + $form .= '<tr><td align="right">' . Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) . '</td>'; + $form .= '<td>' . Xml::input( 'username', 30, $this->username, array( 'id' => 'mw-np-username' ) ) . '</td></tr>'; + + $form .= '<tr><td></td><td>' . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</td></tr></table>'; + $form .= Xml::hidden( 'offset', $this->offset ) . Xml::hidden( 'limit', $this->limit ) . '</form>'; return $form; } diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php index 86438756..cf882509 100644 --- a/includes/SpecialPage.php +++ b/includes/SpecialPage.php @@ -17,8 +17,7 @@ * SpecialPage::$mList. To remove a core static special page at runtime, use * a SpecialPage_initList hook. * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -27,8 +26,8 @@ /** * Parent special page class, also static functions for handling the special - * page list - * @package MediaWiki + * page list. + * @addtogroup SpecialPage */ class SpecialPage { @@ -104,16 +103,18 @@ class SpecialPage 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ), 'Mostimages' => array( 'SpecialPage', 'Mostimages' ), 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ), + 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ), 'Shortpages' => array( 'SpecialPage', 'Shortpages' ), 'Longpages' => array( 'SpecialPage', 'Longpages' ), 'Newpages' => array( 'IncludableSpecialPage', 'Newpages' ), 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ), 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ), + 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ), 'Allpages' => array( 'IncludableSpecialPage', 'Allpages' ), 'Prefixindex' => array( 'IncludableSpecialPage', 'Prefixindex' ) , 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ), 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ), - 'Contributions' => array( 'UnlistedSpecialPage', 'Contributions' ), + 'Contributions' => array( 'SpecialPage', 'Contributions' ), 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ), 'Whatlinkshere' => array( 'UnlistedSpecialPage', 'Whatlinkshere' ), 'Recentchangeslinked' => array( 'UnlistedSpecialPage', 'Recentchangeslinked' ), @@ -138,6 +139,7 @@ class SpecialPage 'Revisiondelete' => array( 'SpecialPage', 'Revisiondelete', 'deleterevision' ), 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ), 'Randomredirect' => array( 'SpecialPage', 'Randomredirect' ), + 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ), 'Mypage' => array( 'SpecialMypage' ), 'Mytalk' => array( 'SpecialMytalk' ), @@ -535,7 +537,7 @@ class SpecialPage $this->mFunction = $function; } if ( $file === 'default' ) { - $this->mFile = "Special{$name}.php"; + $this->mFile = dirname(__FILE__) . "/Special{$name}.php"; } else { $this->mFile = $file; } @@ -691,7 +693,7 @@ class SpecialPage /** * Shortcut to construct a special page which is unlisted by default - * @package MediaWiki + * @addtogroup SpecialPage */ class UnlistedSpecialPage extends SpecialPage { @@ -702,7 +704,7 @@ class UnlistedSpecialPage extends SpecialPage /** * Shortcut to construct an includable special page - * @package MediaWiki + * @addtogroup SpecialPage */ class IncludableSpecialPage extends SpecialPage { @@ -711,6 +713,10 @@ class IncludableSpecialPage extends SpecialPage } } +/** + * Shortcut to construct a special page alias. + * @addtogroup SpecialPage + */ class SpecialRedirectToSpecial extends UnlistedSpecialPage { var $redirName, $redirSubpage; @@ -730,6 +736,17 @@ class SpecialRedirectToSpecial extends UnlistedSpecialPage { } } +/** SpecialMypage, SpecialMytalk and SpecialMycontributions special pages + * are used to get user independant links pointing to the user page, talk + * page and list of contributions. + * This can let us cache a single copy of any generated content for all + * users. + */ + +/** + * Shortcut to construct a special page pointing to current user user's page. + * @addtogroup SpecialPage + */ class SpecialMypage extends UnlistedSpecialPage { function __construct() { parent::__construct( 'Mypage' ); @@ -746,6 +763,10 @@ class SpecialMypage extends UnlistedSpecialPage { } } +/** + * Shortcut to construct a special page pointing to current user talk page. + * @addtogroup SpecialPage + */ class SpecialMytalk extends UnlistedSpecialPage { function __construct() { parent::__construct( 'Mytalk' ); @@ -762,6 +783,10 @@ class SpecialMytalk extends UnlistedSpecialPage { } } +/** + * Shortcut to construct a special page pointing to current user contributions. + * @addtogroup SpecialPage + */ class SpecialMycontributions extends UnlistedSpecialPage { function __construct() { parent::__construct( 'Mycontributions' ); diff --git a/includes/SpecialPopularpages.php b/includes/SpecialPopularpages.php index 77d41437..cd2f60e7 100644 --- a/includes/SpecialPopularpages.php +++ b/includes/SpecialPopularpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Popularpages + * @addtogroup SpecialPage */ class PopularPagesPage extends QueryPage { @@ -23,16 +21,28 @@ class PopularPagesPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); - return + $query = "SELECT 'Popularpages' as type, page_namespace as namespace, page_title as title, page_counter as value - FROM $page - WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0"; + FROM $page "; + $where = + "WHERE page_is_redirect=0 AND page_namespace"; + + global $wgContentNamespaces; + if( empty( $wgContentNamespaces ) ) { + $where .= '='.NS_MAIN; + } else if( count( $wgContentNamespaces ) > 1 ) { + $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')'; + } else { + $where .= '='.$wgContentNamespaces[0]; + } + + return $query . $where; } function formatResult( $skin, $result ) { @@ -49,11 +59,11 @@ class PopularPagesPage extends QueryPage { * Constructor */ function wfSpecialPopularpages() { - list( $limit, $offset ) = wfCheckLimits(); + list( $limit, $offset ) = wfCheckLimits(); - $ppp = new PopularPagesPage(); + $ppp = new PopularPagesPage(); - return $ppp->doQuery( $offset, $limit ); + return $ppp->doQuery( $offset, $limit ); } ?> diff --git a/includes/SpecialPreferences.php b/includes/SpecialPreferences.php index 643932c4..5ca818cd 100644 --- a/includes/SpecialPreferences.php +++ b/includes/SpecialPreferences.php @@ -1,8 +1,7 @@ <?php /** * Hold things related to displaying and saving user preferences. - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -18,14 +17,13 @@ function wfSpecialPreferences() { /** * Preferences form handling * This object will show the preferences form and can save it as well. - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class PreferencesForm { var $mQuickbar, $mOldpass, $mNewpass, $mRetypePass, $mStubs; var $mRows, $mCols, $mSkin, $mMath, $mDate, $mUserEmail, $mEmailFlag, $mNick; var $mUserLanguage, $mUserVariant; - var $mSearch, $mRecent, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; + var $mSearch, $mRecent, $mRecentDays, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; var $mReset, $mPosted, $mToggles, $mSearchNs, $mRealName, $mImageSize; var $mUnderline, $mWatchlistEdits; @@ -54,6 +52,7 @@ class PreferencesForm { $this->mUserVariant = $request->getVal( 'wpUserVariant' ); $this->mSearch = $request->getVal( 'wpSearch' ); $this->mRecent = $request->getVal( 'wpRecent' ); + $this->mRecentDays = $request->getVal( 'wpRecentDays' ); $this->mHourDiff = $request->getVal( 'wpHourDiff' ); $this->mSearchLines = $request->getVal( 'wpSearchLines' ); $this->mSearchChars = $request->getVal( 'wpSearchChars' ); @@ -170,7 +169,7 @@ class PreferencesForm { /** * Used to validate the user inputed timezone before saving it as - * 'timeciorrection', will return '00:00' if fed bogus data. + * 'timecorrection', will return '00:00' if fed bogus data. * Note: It's not a 100% correct implementation timezone-wise, it will * accept stuff like '14:30', * @access private @@ -263,6 +262,7 @@ class PreferencesForm { $wgUser->setOption( 'contextlines', $this->validateIntOrNull( $this->mSearchLines ) ); $wgUser->setOption( 'contextchars', $this->validateIntOrNull( $this->mSearchChars ) ); $wgUser->setOption( 'rclimit', $this->validateIntOrNull( $this->mRecent ) ); + $wgUser->setOption( 'rcdays', $this->validateInt( $this->mRecentDays, 1, 7 ) ); $wgUser->setOption( 'wllimit', $this->validateIntOrNull( $this->mWatchlistEdits, 0, 1000 ) ); $wgUser->setOption( 'rows', $this->validateInt( $this->mRows, 4, 1000 ) ); $wgUser->setOption( 'cols', $this->validateInt( $this->mCols, 4, 1000 ) ); @@ -365,6 +365,7 @@ class PreferencesForm { $this->mImageSize = $wgUser->getOption( 'imagesize' ); $this->mThumbSize = $wgUser->getOption( 'thumbsize' ); $this->mRecent = $wgUser->getOption( 'rclimit' ); + $this->mRecentDays = $wgUser->getOption( 'rcdays' ); $this->mWatchlistEdits = $wgUser->getOption( 'wllimit' ); $this->mUnderline = $wgUser->getOption( 'underline' ); $this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' ); @@ -838,7 +839,7 @@ class PreferencesForm { # Editing # - global $wgLivePreview, $wgUseRCPatrol; + global $wgLivePreview; $wgOut->addHTML( '<fieldset><legend>' . wfMsg( 'textboxsize' ) . '</legend> <div>' . wfInputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . @@ -861,15 +862,27 @@ class PreferencesForm { ) ) . '</fieldset>' ); - $wgOut->addHTML( '<fieldset><legend>' . htmlspecialchars(wfMsg('prefs-rc')) . '</legend>' . - wfInputLabel( wfMsg( 'recentchangescount' ), - 'wpRecent', 'wpRecent', 3, $this->mRecent ) . - $this->getToggles( array( - 'hideminor', - $wgRCShowWatchingUsers ? 'shownumberswatching' : false, - 'usenewrc' ) - ) . '</fieldset>' - ); + # Recent changes + $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-rc' ) . '</legend>' ); + + $rc = '<table><tr>'; + $rc .= '<td>' . Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) . '</td>'; + $rc .= '<td>' . Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . '</td>'; + $rc .= '</tr><tr>'; + $rc .= '<td>' . Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) . '</td>'; + $rc .= '<td>' . Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) . '</td>'; + $rc .= '</tr></table>'; + $wgOut->addHtml( $rc ); + + $wgOut->addHtml( '<br />' ); + + $toggles[] = 'hideminor'; + if( $wgRCShowWatchingUsers ) + $toggles[] = 'shownumberswatching'; + $toggles[] = 'usenewrc'; + $wgOut->addHtml( $this->getToggles( $toggles ) ); + + $wgOut->addHtml( '</fieldset>' ); # Watchlist $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'prefs-watchlist' ) . '</legend>' ); @@ -941,11 +954,11 @@ class PreferencesForm { $wgOut->addHTML( '</fieldset>' ); $token = $wgUser->editToken(); + $skin = $wgUser->getSkin(); $wgOut->addHTML( " <div id='prefsubmit'> <div> - <input type='submit' name='wpSaveprefs' class='btnSavePrefs' value=\"" . wfMsgHtml( 'saveprefs' ) . "\" accesskey=\"". - wfMsgHtml('accesskey-save')."\" title=\"".wfMsgHtml('tooltip-save')."\" /> + <input type='submit' name='wpSaveprefs' class='btnSavePrefs' value=\"" . wfMsgHtml( 'saveprefs' ) . '"'.$skin->tooltipAndAccesskey('save')." /> <input type='submit' name='wpReset' value=\"" . wfMsgHtml( 'resetprefs' ) . "\" /> </div> diff --git a/includes/SpecialPrefixindex.php b/includes/SpecialPrefixindex.php index ce296b4b..b7c51d49 100644 --- a/includes/SpecialPrefixindex.php +++ b/includes/SpecialPrefixindex.php @@ -1,11 +1,8 @@ <?php /** - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ -require_once 'SpecialAllpages.php'; - /** * Entry point : initialise variables and call subfunctions. * @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default NULL) @@ -44,6 +41,10 @@ function wfSpecialPrefixIndex( $par=NULL, $specialPage ) { } } +/** + * implements Special:Prefixindex + * @addtogroup SpecialPage + */ class SpecialPrefixindex extends SpecialAllpages { var $maxPerPage=960; var $topLevelMax=50; @@ -71,11 +72,11 @@ function showChunk( $namespace = NS_MAIN, $prefix, $including = false, $from = n $out = wfMsgWikiHtml( 'allpagesbadtitle' ); } else { list( $namespace, $prefixKey, $prefix ) = $prefixList; - list( $fromNs, $fromKey, $from ) = $fromList; + list( /* $fromNs */, $fromKey, $from ) = $fromList; ### FIXME: should complain if $fromNs != $namespace - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'page', array( 'page_namespace', 'page_title', 'page_is_redirect' ), diff --git a/includes/SpecialProtectedpages.php b/includes/SpecialProtectedpages.php new file mode 100644 index 00000000..91b138ff --- /dev/null +++ b/includes/SpecialProtectedpages.php @@ -0,0 +1,260 @@ +<?php +/** + * + * @addtogroup SpecialPage + */ + +/** + * @todo document + * @addtogroup SpecialPage + */ +class ProtectedPagesForm { + function showList( $msg = '' ) { + global $wgOut, $wgRequest; + + $wgOut->setPagetitle( wfMsg( "protectedpages" ) ); + if ( "" != $msg ) { + $wgOut->setSubtitle( $msg ); + } + + // Purge expired entries on one in every 10 queries + if ( !mt_rand( 0, 10 ) ) { + Title::purgeExpiredRestrictions(); + } + + $type = $wgRequest->getVal( 'type' ); + $level = $wgRequest->getVal( 'level' ); + $minsize = $wgRequest->getIntOrNull( 'minsize' ); + $NS = $wgRequest->getIntOrNull( 'namespace' ); + + $pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $minsize ); + + $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $minsize ) ); + + if ( $pager->getNumRows() ) { + $s = $pager->getNavigationBar(); + $s .= "<ul>" . + $pager->getBody() . + "</ul>"; + $s .= $pager->getNavigationBar(); + } else { + $s = '<p>' . wfMsgHTML( 'protectedpagesempty' ) . '</p>'; + } + $wgOut->addHTML( $s ); + } + + /** + * Callback function to output a restriction + */ + function formatRow( $row ) { + global $wgUser, $wgLang; + + wfProfileIn( __METHOD__ ); + + static $skin=null; + + if( is_null( $skin ) ) + $skin = $wgUser->getSkin(); + + $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); + $link = $skin->makeLinkObj( $title ); + + $description_items = array (); + + $protType = wfMsgHtml( 'restriction-level-' . $row->pr_level ); + + $description_items[] = $protType; + + $expiry_description = ''; $stxt = ''; + + if ( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { + $expiry = Block::decodeExpiry( $row->pr_expiry ); + + $expiry_description = wfMsgForContent( 'protect-expiring', $wgLang->timeanddate( $expiry ) ); + + $description_items[] = $expiry_description; + } + + if (!is_null($size = $row->page_len)) { + if ($size == 0) + $stxt = ' <small>' . wfMsgHtml('historyempty') . '</small>'; + else + $stxt = ' <small>' . wfMsgHtml('historysize', $wgLang->formatNum( $size ) ) . '</small>'; + } + wfProfileOut( __METHOD__ ); + + return '<li>' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "</li>\n"; + } + + /** + * @param $namespace int + * @param $type string + * @param $level string + * @param $minsize int + * @private + */ + function showOptions( $namespace, $type='edit', $level, $minsize ) { + global $wgScript; + $action = htmlspecialchars( $wgScript ); + $title = SpecialPage::getTitleFor( 'ProtectedPages' ); + $special = htmlspecialchars( $title->getPrefixedDBkey() ); + return "<form action=\"$action\" method=\"get\">\n" . + '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) . + Xml::hidden( 'title', $special ) . "\n" . + $this->getNamespaceMenu( $namespace ) . "\n" . + $this->getTypeMenu( $type ) . "\n" . + $this->getLevelMenu( $level ) . "<br/>\n" . + $this->getSizeLimit( $minsize ) . "\n" . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + "</fieldset></form>"; + } + + function getNamespaceMenu( $namespace=NULL ) { + return "<label for='namespace'>" . wfMsgHtml('namespace') . "</label>" . HTMLnamespaceselector($namespace, ''); + } + + /** + * @return string Formatted HTML + * @private + */ + function getSizeLimit( $minsize=0 ) { + $out = Xml::input('minsize', 9, $minsize, array( 'id' => 'minsize' ) ); + return "<label for='minsize'>" . wfMsgHtml('minimum-size') . "</label>: " . $out; + } + + /** + * @return string Formatted HTML + * @private + */ + function getTypeMenu( $pr_type ) { + global $wgRestrictionTypes, $wgUser; + + $out = "<select name='type'>\n"; + $m = array(); // Temporary array + + // First pass to load the log names + foreach( $wgRestrictionTypes as $type ) { + $text = wfMsgHtml("restriction-$type"); + $m[$text] = $type; + } + + // Second pass to sort by name + ksort($m); + + // Third pass generates sorted XHTML content + foreach( $m as $text => $type ) { + $selected = ($type == $pr_type ); + $out .= Xml::option( $text, $type, $selected ) . "\n"; + } + + $out .= '</select>'; + return "<label for='type'>" . wfMsgHtml('restriction-type') . "</label>: " . $out; + } + + /** + * @return string Formatted HTML + * @private + */ + function getLevelMenu( $pr_level ) { + global $wgRestrictionLevels, $wgUser; + + $out = "<select name='level'>\n"; + $m = array( wfMsgHtml('restriction-level-all') => 0 ); // Temporary array + + // First pass to load the log names + foreach( $wgRestrictionLevels as $type ) { + if ( $type !='' && $type !='*') { + $text = wfMsgHtml("restriction-level-$type"); + $m[$text] = $type; + } + } + + // Second pass to sort by name + ksort($m); + + // Third pass generates sorted XHTML content + foreach( $m as $text => $type ) { + $selected = ($type == $pr_level ); + $out .= Xml::option( $text, $type, $selected ) . "\n"; + } + + $out .= '</select>'; + return "<label for='level'>" . wfMsgHtml('restriction-level') . "</label>: " . $out; + } +} + +/** + * @todo document + * @addtogroup Pager + */ +class ProtectedPagesPager extends ReverseChronologicalPager { + public $mForm, $mConds; + + function __construct( $form, $conds = array(), $type, $level, $namespace, $minsize ) { + $this->mForm = $form; + $this->mConds = $conds; + $this->type = ( $type ) ? $type : 'edit'; + $this->level = $level; + $this->namespace = $namespace; + $this->minsize = intval($minsize); + parent::__construct(); + } + + function getStartBody() { + wfProfileIn( __METHOD__ ); + # Do a link batch query + $this->mResult->seek( 0 ); + $lb = new LinkBatch; + + while ( $row = $this->mResult->fetchObject() ) { + $name = str_replace( ' ', '_', $row->page_title ); + $lb->add( $row->page_namespace, $name ); + } + + $lb->execute(); + wfProfileOut( __METHOD__ ); + return ''; + } + + function formatRow( $row ) { + $block = new Block; + return $this->mForm->formatRow( $row ); + } + + function getQueryInfo() { + $conds = $this->mConds; + $conds[] = 'pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); + $conds[] = 'page_id=pr_page'; + $conds[] = 'page_len>=' . $this->minsize; + $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); + if ( $this->level ) + $conds[] = 'pr_level=' . $this->mDb->addQuotes( $this->level ); + if ( !is_null($this->namespace) ) + $conds[] = 'page_namespace=' . $this->mDb->addQuotes( $this->namespace ); + return array( + 'tables' => array( 'page_restrictions', 'page' ), + 'fields' => 'max(pr_id) AS pr_id,page_namespace,page_title,page_len,pr_type,pr_level,pr_expiry', + 'conds' => $conds, + 'options' => array( 'GROUP BY' => 'page_namespace,page_title,pr_level,pr_expiry,page_len,pr_type' ), + ); + } + + function getIndexField() { + return 'pr_id'; + } +} + +/** + * Constructor + */ +function wfSpecialProtectedpages() { + + list( $limit, $offset ) = wfCheckLimits(); + + $ppForm = new ProtectedPagesForm(); + + $ppForm->showList(); +} + +?> diff --git a/includes/SpecialRandompage.php b/includes/SpecialRandompage.php index 2cd31eb5..e6c4abe8 100644 --- a/includes/SpecialRandompage.php +++ b/includes/SpecialRandompage.php @@ -1,58 +1,108 @@ <?php + /** - * @package MediaWiki - * @subpackage SpecialPage + * Special page to direct the user to a random page + * + * @addtogroup SpecialPage + * @author Rob Church <robchur@gmail.com>, Ilmari Karonen + * @license GNU General Public Licence 2.0 or later */ /** - * Constructor - * - * @param $par The namespace to get a random page from (default NS_MAIN), - * used as e.g. Special:Randompage/Category + * Main execution point + * @param $par Namespace to select the page from */ -function wfSpecialRandompage( $par = NS_MAIN ) { - global $wgOut, $wgExtraRandompageSQL; - $fname = 'wfSpecialRandompage'; - - # Determine namespace - $t = Title::newFromText ( $par . ":Dummy" ) ; - $namespace = $t->getNamespace () ; - - # NOTE! We use a literal constant in the SQL instead of the RAND() - # function because RAND() will return a different value for every row - # in the table. That's both very slow and returns results heavily - # biased towards low values, as rows later in the table will likely - # never be reached for comparison. - # - # Using a literal constant means the whole thing gets optimized on - # the index, and the comparison is both fast and fair. - - # interpolation and sprintf() can muck up with locale-specific decimal separator - $randstr = wfRandom(); - - $db =& wfGetDB( DB_SLAVE ); - $use_index = $db->useIndexClause( 'page_random' ); - $page = $db->tableName( 'page' ); - - $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ''; - $sql = "SELECT page_id,page_title - FROM $page $use_index - WHERE page_namespace=$namespace AND page_is_redirect=0 $extra - AND page_random>$randstr - ORDER BY page_random"; - $sql = $db->limitResult($sql, 1, 0); - $res = $db->query( $sql, $fname ); - - $title = null; - if( $s = $db->fetchObject( $res ) ) { - $title =& Title::makeTitle( $namespace, $s->page_title ); - } +function wfSpecialRandompage( $par = null ) { + global $wgOut, $wgContLang; + + $rnd = new RandomPage(); + $rnd->setNamespace( $wgContLang->getNsIndex( $par ) ); + $rnd->setRedirect( false ); + + $title = $rnd->getRandomTitle(); + if( is_null( $title ) ) { - # That's not supposed to happen :) - $title = Title::newMainPage(); + $wgOut->addWikiText( wfMsg( 'randompage-nopages' ) ); + return; } - $wgOut->reportTime(); # for logfile + + $wgOut->reportTime(); $wgOut->redirect( $title->getFullUrl() ); } + +/** + * Special page to direct the user to a random page + * + * @addtogroup SpecialPage + */ +class RandomPage { + private $namespace = NS_MAIN; // namespace to select pages from + private $redirect = false; // select redirects instead of normal pages? + + public function getNamespace ( ) { + return $this->namespace; + } + public function setNamespace ( $ns ) { + if( $ns < NS_MAIN ) $ns = NS_MAIN; + $this->namespace = $ns; + } + public function getRedirect ( ) { + return $this->redirect; + } + public function setRedirect ( $redirect ) { + $this->redirect = $redirect; + } + + /** + * Choose a random title. + * @return Title object (or null if nothing to choose from) + */ + public function getRandomTitle ( ) { + $randstr = wfRandom(); + $row = $this->selectRandomPageFromDB( $randstr ); + + /* If we picked a value that was higher than any in + * the DB, wrap around and select the page with the + * lowest value instead! One might think this would + * skew the distribution, but in fact it won't cause + * any more bias than what the page_random scheme + * causes anyway. Trust me, I'm a mathematician. :) + */ + if( !$row ) + $row = $this->selectRandomPageFromDB( "0" ); + + if( $row ) + return Title::makeTitleSafe( $this->namespace, $row->page_title ); + else + return null; + } + + private function selectRandomPageFromDB ( $randstr ) { + global $wgExtraRandompageSQL; + $fname = 'RandomPage::selectRandomPageFromDB'; + + $dbr = wfGetDB( DB_SLAVE ); + + $use_index = $dbr->useIndexClause( 'page_random' ); + $page = $dbr->tableName( 'page' ); + + $ns = (int) $this->namespace; + $redirect = $this->redirect ? 1 : 0; + + $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ""; + $sql = "SELECT page_title + FROM $page $use_index + WHERE page_namespace = $ns + AND page_is_redirect = $redirect + AND page_random >= $randstr + $extra + ORDER BY page_random"; + + $sql = $dbr->limitResult( $sql, 1, 0 ); + $res = $dbr->query( $sql, $fname ); + return $dbr->fetchObject( $res ); + } +} + ?> diff --git a/includes/SpecialRandomredirect.php b/includes/SpecialRandomredirect.php index 2cb2498b..75a6b81d 100644 --- a/includes/SpecialRandomredirect.php +++ b/includes/SpecialRandomredirect.php @@ -3,50 +3,29 @@ /** * Special page to direct the user to a random redirect page (minus the second redirect) * - * @package MediaWiki - * @subpackage Special pages - * @author Rob Church <robchur@gmail.com> - * @licence GNU General Public Licence 2.0 or later + * @addtogroup SpecialPage + * @author Rob Church <robchur@gmail.com>, Ilmari Karonen + * @license GNU General Public Licence 2.0 or later */ /** * Main execution point * @param $par Namespace to select the redirect from */ -function wfSpecialRandomredirect( $par = NULL ) { - global $wgOut, $wgExtraRandompageSQL, $wgContLang; - $fname = 'wfSpecialRandomredirect'; +function wfSpecialRandomredirect( $par = null ) { + global $wgOut, $wgContLang; - # Validate the namespace - $namespace = $wgContLang->getNsIndex( $par ); - if( $namespace === false || $namespace < NS_MAIN ) - $namespace = NS_MAIN; + $rnd = new RandomPage(); + $rnd->setNamespace( $wgContLang->getNsIndex( $par ) ); + $rnd->setRedirect( true ); - # Same logic as RandomPage - $randstr = wfRandom(); + $title = $rnd->getRandomTitle(); - $dbr =& wfGetDB( DB_SLAVE ); - $use_index = $dbr->useIndexClause( 'page_random' ); - $page = $dbr->tableName( 'page' ); + if( is_null( $title ) ) { + $wgOut->addWikiText( wfMsg( 'randomredirect-nopages' ) ); + return; + } - $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : ''; - $sql = "SELECT page_id,page_title - FROM $page $use_index - WHERE page_namespace = $namespace AND page_is_redirect = 1 $extra - AND page_random > $randstr - ORDER BY page_random"; - - $sql = $dbr->limitResult( $sql, 1, 0 ); - $res = $dbr->query( $sql, $fname ); - - $title = NULL; - if( $row = $dbr->fetchObject( $res ) ) - $title = Title::makeTitleSafe( $namespace, $row->page_title ); - - # Catch dud titles and return to the main page - if( is_null( $title ) ) - $title = Title::newMainPage(); - $wgOut->reportTime(); $wgOut->redirect( $title->getFullUrl( 'redirect=no' ) ); } diff --git a/includes/SpecialRecentchanges.php b/includes/SpecialRecentchanges.php index 3b8d69f2..84444e62 100644 --- a/includes/SpecialRecentchanges.php +++ b/includes/SpecialRecentchanges.php @@ -1,14 +1,13 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * */ -require_once( 'ChangesList.php' ); +require_once( dirname(__FILE__) . '/ChangesList.php' ); /** * Constructor @@ -22,7 +21,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) { # Get query parameters $feedFormat = $wgRequest->getVal( 'feed' ); - /* Checkbox values can't be true be default, because + /* Checkbox values can't be true by default, because * we cannot differentiate between unset and not set at all */ $defaults = array( @@ -58,7 +57,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) { if( $feedFormat ) { global $wgFeedLimit; if( $limit > $wgFeedLimit ) { - $options['limit'] = $wgFeedLimit; + $limit = $wgFeedLimit; } } else { @@ -105,7 +104,7 @@ function wfSpecialRecentchanges( $par, $specialPage ) { # Database connection and caching - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $recentchanges, $watchlist ) = $dbr->tableNamesN( 'recentchanges', 'watchlist' ); @@ -470,7 +469,7 @@ function rcDayLimitLinks( $days, $limit, $page='Recentchanges', $more='', $doall /** * Makes change an option link which carries all the other options - * @param $title @see Title + * @param $title see Title * @param $override * @param $options */ @@ -625,7 +624,7 @@ function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment ) { $skin = $wgUser->getSkin(); $completeText = '<p>' . $skin->formatComment( $comment ) . "</p>\n"; - if( $title->getNamespace() >= 0 ) { + if( $title->getNamespace() >= 0 && $title->userCan( 'read' ) ) { if( $oldid ) { wfProfileIn( "$fname-dodiff" ); @@ -692,7 +691,7 @@ function rcApplyDiffStyle( $text ) { 'diff-addedline' => 'background: #cfc; font-size: smaller;', 'diff-deletedline' => 'background: #ffa; font-size: smaller;', 'diff-context' => 'background: #eee; font-size: smaller;', - 'diffchange' => 'color: red; font-weight: bold;', + 'diffchange' => 'color: red; font-weight: bold; text-decoration: none;', ); foreach( $styles as $class => $style ) { diff --git a/includes/SpecialRecentchangeslinked.php b/includes/SpecialRecentchangeslinked.php index 15292898..14508d3a 100644 --- a/includes/SpecialRecentchangeslinked.php +++ b/includes/SpecialRecentchangeslinked.php @@ -1,8 +1,7 @@ <?php /** * This is to display changes made to all articles linked in an article. - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -25,20 +24,15 @@ function wfSpecialRecentchangeslinked( $par = NULL ) { $wgOut->setPagetitle( wfMsg( 'recentchangeslinked' ) ); $sk = $wgUser->getSkin(); - # Validate the title - $nt = Title::newFromURL( $target ); - if( !is_object( $nt ) ) { - $wgOut->errorPage( 'notargettitle', 'notargettext' ); + if (is_null($target)) { + $wgOut->errorpage( 'notargettitle', 'notargettext' ); return; } - - # Check for existence - # Do a quiet redirect back to the page itself if it doesn't - if( !$nt->exists() ) { - $wgOut->redirect( $nt->getLocalUrl() ); + $nt = Title::newFromURL( $target ); + if( !$nt ) { + $wgOut->errorpage( 'notargettitle', 'notargettext' ); return; } - $id = $nt->getArticleId(); $wgOut->setSubtitle( htmlspecialchars( wfMsg( 'rclsub', $nt->getPrefixedText() ) ) ); @@ -48,7 +42,7 @@ function wfSpecialRecentchangeslinked( $par = NULL ) { } list( $limit, /* offset */ ) = wfCheckLimits( 100, 'rclimit' ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE,'recentchangeslinked' ); $cutoff = $dbr->timestamp( time() - ( $days * 86400 ) ); $hideminor = ($hideminor ? 1 : 0); @@ -72,7 +66,7 @@ function wfSpecialRecentchangeslinked( $par = NULL ) { $GROUPBY = " GROUP BY rc_cur_id,rc_namespace,rc_title, - rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor, + rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,rc_deleted, rc_new, rc_id, rc_this_oldid, rc_last_oldid, rc_bot, rc_patrolled, rc_type, rc_old_len, rc_new_len " . ($uid ? ",wl_user" : "") . " ORDER BY rc_timestamp DESC @@ -98,7 +92,8 @@ function wfSpecialRecentchangeslinked( $par = NULL ) { rc_patrolled, rc_type, rc_old_len, - rc_new_len + rc_new_len, + rc_deleted " . ($uid ? ",wl_user" : "") . " FROM $categorylinks, $recentchanges " . ($uid ? "LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . " @@ -127,7 +122,8 @@ $GROUPBY rc_patrolled, rc_type, rc_old_len, - rc_new_len + rc_new_len, + rc_deleted " . ($uid ? ",wl_user" : "") . " FROM $pagelinks, $recentchanges " . ($uid ? " LEFT OUTER JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . " @@ -141,8 +137,8 @@ $GROUPBY } $res = $dbr->query( $sql, $fname ); - $wgOut->addHTML("< ".$sk->makeKnownLinkObj($nt, "", "redirect=no" )."<br />\n"); - $note = wfMsg( "rcnote", $limit, $days, $wgLang->timeAndDate( wfTimestampNow(), true ) ); + $wgOut->addHTML("< ".$sk->makeLinkObj($nt, "", "redirect=no" )."<br />\n"); + $note = wfMsgExt( "rcnote", array ( 'parseinline' ), $limit, $days, $wgLang->timeAndDate( wfTimestampNow(), true ) ); $wgOut->addHTML( "<hr />\n{$note}\n<br />" ); $note = rcDayLimitlinks( $days, $limit, "Recentchangeslinked", @@ -155,15 +151,19 @@ $GROUPBY $s = $list->beginRecentChangesList(); $count = $dbr->numRows( $res ); - $counter = 1; - while ( $limit ) { - if ( 0 == $count ) { break; } - $obj = $dbr->fetchObject( $res ); - --$count; - $rc = RecentChange::newFromRow( $obj ); - $rc->counter = $counter++; - $s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) ); - --$limit; + if ( $count ) { + $counter = 1; + while ( $limit ) { + if ( 0 == $count ) { break; } + $obj = $dbr->fetchObject( $res ); + --$count; + $rc = RecentChange::newFromRow( $obj ); + $rc->counter = $counter++; + $s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) ); + --$limit; + } + } else { + $wgOut->addWikiText( wfMsg('recentchangeslinked-noresult') ); } $s .= $list->endRecentChangesList(); diff --git a/includes/SpecialResetpass.php b/includes/SpecialResetpass.php index cde582b1..dc1e53c4 100644 --- a/includes/SpecialResetpass.php +++ b/includes/SpecialResetpass.php @@ -1,10 +1,15 @@ <?php +/** Constructor */ function wfSpecialResetpass( $par ) { $form = new PasswordResetForm(); $form->execute( $par ); } +/** + * Let users recover their password. + * @addtogroup SpecialPage + */ class PasswordResetForm extends SpecialPage { function __construct( $name=null, $reset=null ) { if( $name !== null ) { @@ -68,7 +73,7 @@ class PasswordResetForm extends SpecialPage { } function showForm() { - global $wgOut, $wgUser, $wgLang, $wgRequest; + global $wgOut, $wgUser, $wgRequest; $self = SpecialPage::getTitleFor( 'Resetpass' ); $form = @@ -134,7 +139,7 @@ class PasswordResetForm extends SpecialPage { } /** - * @throws PasswordError + * @throws PasswordError when cannot set the new password because requirements not met. */ function attemptReset( $newpass, $retype ) { $user = User::newFromName( $this->mName ); diff --git a/includes/SpecialRevisiondelete.php b/includes/SpecialRevisiondelete.php index fb5e9ec8..5c70d5ae 100644 --- a/includes/SpecialRevisiondelete.php +++ b/includes/SpecialRevisiondelete.php @@ -35,6 +35,10 @@ function wfSpecialRevisiondelete( $par = null ) { } } +/** + * Implements the GUI for Revision Deletion. + * @addtogroup SpecialPage + */ class RevisionDeleteForm { /** * @param Title $page @@ -170,7 +174,10 @@ class RevisionDeleteForm { } } - +/** + * Implements the actions for Revision Deletion. + * @addtogroup SpecialPage + */ class RevisionDeleter { function __construct( $db ) { $this->db = $db; diff --git a/includes/SpecialSearch.php b/includes/SpecialSearch.php index 9ecd39ef..fdaa8541 100644 --- a/includes/SpecialSearch.php +++ b/includes/SpecialSearch.php @@ -19,8 +19,7 @@ /** * Run text & title search and display the output - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -43,9 +42,8 @@ function wfSpecialSearch( $par = '' ) { } /** - * @todo document - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Search - Run text & title search and display the output + * @addtogroup SpecialPage */ class SpecialSearch { @@ -161,12 +159,14 @@ class SpecialSearch { $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) + ( $textMatches ? $textMatches->numRows() : 0); - if ( $num >= $this->limit ) { - $top = wfShowingResults( $this->offset, $this->limit ); - } else { - $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); + if ( $num > 0 ) { + if ( $num >= $this->limit ) { + $top = wfShowingResults( $this->offset, $this->limit ); + } else { + $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); + } + $wgOut->addHTML( "<p>{$top}</p>\n" ); } - $wgOut->addHTML( "<p>{$top}</p>\n" ); if( $num || $this->offset ) { $prevnext = wfViewPrevNext( $this->offset, $this->limit, @@ -314,7 +314,7 @@ class SpecialSearch { wfProfileOut( $fname ); return "<!-- Broken link in search result -->\n"; } - $sk =& $wgUser->getSkin(); + $sk = $wgUser->getSkin(); $contextlines = $wgUser->getOption( 'contextlines', 5 ); $contextchars = $wgUser->getOption( 'contextchars', 50 ); diff --git a/includes/SpecialShortpages.php b/includes/SpecialShortpages.php index 03164deb..72b093e0 100644 --- a/includes/SpecialShortpages.php +++ b/includes/SpecialShortpages.php @@ -1,15 +1,13 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * SpecialShortpages extends QueryPage. It is used to return the shortest * pages in the database. - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class ShortPagesPage extends QueryPage { @@ -29,7 +27,7 @@ class ShortPagesPage extends QueryPage { } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $page = $dbr->tableName( 'page' ); $name = $dbr->addQuotes( $this->getName() ); diff --git a/includes/SpecialSpecialpages.php b/includes/SpecialSpecialpages.php index 78f9dee5..bb202358 100644 --- a/includes/SpecialSpecialpages.php +++ b/includes/SpecialSpecialpages.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -51,7 +50,7 @@ function wfSpecialSpecialpages_gen($pages,$heading,$sk) { /** Now output the HTML */ $wgOut->addHTML( '<h2>' . wfMsgHtml( $heading ) . "</h2>\n<ul>" ); foreach ( $sortedPages as $desc => $title ) { - $link = $sk->makeKnownLinkObj( $title, $desc ); + $link = $sk->makeKnownLinkObj( $title , htmlspecialchars( $desc ) ); $wgOut->addHTML( "<li>{$link}</li>\n" ); } $wgOut->addHTML( "</ul>\n" ); diff --git a/includes/SpecialStatistics.php b/includes/SpecialStatistics.php index a5a0fc3a..1c9e0ab6 100644 --- a/includes/SpecialStatistics.php +++ b/includes/SpecialStatistics.php @@ -1,8 +1,7 @@ <?php /** * -* @package MediaWiki -* @subpackage SpecialPage +* @addtogroup SpecialPage */ /** @@ -14,7 +13,7 @@ function wfSpecialStatistics() { $action = $wgRequest->getVal( 'action' ); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $views = SiteStats::views(); $edits = SiteStats::edits(); @@ -24,7 +23,7 @@ function wfSpecialStatistics() { $users = SiteStats::users(); $admins = $dbr->selectField( 'user_groups', 'COUNT(*)', array( 'ug_group' => 'sysop' ), $fname ); - $numJobs = $dbr->selectField( 'job', 'COUNT(*)', '', $fname ); + $numJobs = $dbr->estimateRowCount('job'); if ($action == 'raw') { $wgOut->disable(); @@ -33,7 +32,7 @@ function wfSpecialStatistics() { return; } else { $text = '==' . wfMsg( 'sitestats' ) . "==\n" ; - $text .= wfMsg( 'sitestatstext', + $text .= wfMsgExt( 'sitestatstext', array ( 'parsemag' ), $wgLang->formatNum( $total ), $wgLang->formatNum( $good ), $wgLang->formatNum( $views ), @@ -46,7 +45,7 @@ function wfSpecialStatistics() { $text .= "\n==" . wfMsg( 'userstats' ) . "==\n"; - $text .= wfMsg( 'userstatstext', + $text .= wfMsgExt( 'userstatstext', array ( 'parsemag' ), $wgLang->formatNum( $users ), $wgLang->formatNum( $admins ), '[[' . wfMsgForContent( 'grouppage-sysop' ) . ']]', # TODO somehow remove, kept for backwards compatibility @@ -64,7 +63,7 @@ function wfSpecialStatistics() { $res = $dbr->query( $sql, $fname ); if( $res ) { $wgOut->addHtml( '<h2>' . wfMsgHtml( 'statistics-mostpopular' ) . '</h2>' ); - $skin =& $wgUser->getSkin(); + $skin = $wgUser->getSkin(); $wgOut->addHtml( '<ol>' ); while( $row = $dbr->fetchObject( $res ) ) { $link = $skin->makeKnownLinkObj( Title::makeTitleSafe( $row->page_namespace, $row->page_title ) ); @@ -76,6 +75,10 @@ function wfSpecialStatistics() { } } + $footer = wfMsg( 'statistics-footer' ); + if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) + $wgOut->addWikiText( $footer ); + } } ?> diff --git a/includes/SpecialUncategorizedcategories.php b/includes/SpecialUncategorizedcategories.php index ba399f0c..e02c9bbd 100644 --- a/includes/SpecialUncategorizedcategories.php +++ b/includes/SpecialUncategorizedcategories.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -11,9 +10,8 @@ require_once( "SpecialUncategorizedpages.php" ); /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Uncategorizedcategories + * @addtogroup SpecialPage */ class UncategorizedCategoriesPage extends UncategorizedPagesPage { function UncategorizedCategoriesPage() { diff --git a/includes/SpecialUncategorizedimages.php b/includes/SpecialUncategorizedimages.php index 1daba8ed..22e34669 100644 --- a/includes/SpecialUncategorizedimages.php +++ b/includes/SpecialUncategorizedimages.php @@ -3,31 +3,30 @@ /** * Special page lists images which haven't been categorised * - * @package MediaWiki - * @subpackage Special pages + * @addtogroup SpecialPage * @author Rob Church <robchur@gmail.com> */ - -class UncategorizedImagesPage extends QueryPage { + +class UncategorizedImagesPage extends ImageQueryPage { function getName() { return 'Uncategorizedimages'; } - + function sortDescending() { return false; } - + function isExpensive() { return true; } - + function isSyndicated() { return false; } - + function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); $ns = NS_IMAGE; @@ -36,14 +35,7 @@ class UncategorizedImagesPage extends QueryPage { FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0"; } - - function formatResult( &$skin, $row ) { - global $wgContLang; - $title = Title::makeTitleSafe( NS_IMAGE, $row->title ); - $label = htmlspecialchars( $wgContLang->convert( $title->getText() ) ); - return $skin->makeKnownLinkObj( $title, $label ); - } - + } function wfSpecialUncategorizedimages() { diff --git a/includes/SpecialUncategorizedpages.php b/includes/SpecialUncategorizedpages.php index dbf23a60..408ac726 100644 --- a/includes/SpecialUncategorizedpages.php +++ b/includes/SpecialUncategorizedpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * A special page looking for page without any category. + * @addtogroup SpecialPage */ class UncategorizedPagesPage extends PageQueryPage { var $requestedNamespace = NS_MAIN; @@ -27,7 +25,7 @@ class UncategorizedPagesPage extends PageQueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); $name = $dbr->addQuotes( $this->getName() ); diff --git a/includes/SpecialUndelete.php b/includes/SpecialUndelete.php index 7c9b1191..8e740f6d 100644 --- a/includes/SpecialUndelete.php +++ b/includes/SpecialUndelete.php @@ -4,52 +4,92 @@ * Special page allowing users with the appropriate permissions to view * and restore deleted content * - * @package MediaWiki - * @subpackage Special pages + * @addtogroup SpecialPage */ /** - * + * Constructor */ function wfSpecialUndelete( $par ) { - global $wgRequest; + global $wgRequest; $form = new UndeleteForm( $wgRequest, $par ); $form->execute(); } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * Used to show archived pages and eventually restore them. + * @addtogroup SpecialPage */ class PageArchive { - var $title; + protected $title; - function PageArchive( &$title ) { + function __construct( $title ) { if( is_null( $title ) ) { throw new MWException( 'Archiver() given a null title.'); } - $this->title =& $title; + $this->title = $title; } /** * List all deleted pages recorded in the archive table. Returns result * wrapper with (ar_namespace, ar_title, count) fields, ordered by page - * namespace/title. Can be called staticaly. + * namespace/title. * * @return ResultWrapper */ - /* static */ function listAllPages() { - $dbr =& wfGetDB( DB_SLAVE ); - $archive = $dbr->tableName( 'archive' ); - - $sql = "SELECT ar_namespace,ar_title, COUNT(*) AS count FROM $archive " . - "GROUP BY ar_namespace,ar_title ORDER BY ar_namespace,ar_title"; - - return $dbr->resultObject( $dbr->query( $sql, 'PageArchive::listAllPages' ) ); + public static function listAllPages() { + $dbr = wfGetDB( DB_SLAVE ); + return self::listPages( $dbr, '' ); + } + + /** + * List deleted pages recorded in the archive table matching the + * given title prefix. + * Returns result wrapper with (ar_namespace, ar_title, count) fields. + * + * @return ResultWrapper + */ + public static function listPagesByPrefix( $prefix ) { + $dbr = wfGetDB( DB_SLAVE ); + + $title = Title::newFromText( $prefix ); + if( $title ) { + $ns = $title->getNamespace(); + $encPrefix = $dbr->escapeLike( $title->getDbKey() ); + } else { + // Prolly won't work too good + // @todo handle bare namespace names cleanly? + $ns = 0; + $encPrefix = $dbr->escapeLike( $prefix ); + } + $conds = array( + 'ar_namespace' => $ns, + "ar_title LIKE '$encPrefix%'", + ); + return self::listPages( $dbr, $conds ); } + protected static function listPages( $dbr, $condition ) { + return $dbr->resultObject( + $dbr->select( + array( 'archive' ), + array( + 'ar_namespace', + 'ar_title', + 'COUNT(*) AS count', + ), + $condition, + __METHOD__, + array( + 'GROUP BY' => 'ar_namespace,ar_title', + 'ORDER BY' => 'ar_namespace,ar_title', + 'LIMIT' => 100, + ) + ) + ); + } + /** * List the revisions of the given page. Returns result wrapper with * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields. @@ -57,9 +97,9 @@ class PageArchive { * @return ResultWrapper */ function listRevisions() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'archive', - array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment' ), + array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len' ), array( 'ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDBkey() ), 'PageArchive::listRevisions', @@ -74,11 +114,11 @@ class PageArchive { * if not a file page. * * @return ResultWrapper - * @fixme Does this belong in Image for fuller encapsulation? + * @todo Does this belong in Image for fuller encapsulation? */ function listFiles() { if( $this->title->getNamespace() == NS_IMAGE ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'filearchive', array( 'fa_id', @@ -119,7 +159,7 @@ class PageArchive { * @return Revision */ function getRevision( $timestamp ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'archive', array( 'ar_rev_id', @@ -130,7 +170,8 @@ class PageArchive { 'ar_timestamp', 'ar_minor_edit', 'ar_flags', - 'ar_text_id' ), + 'ar_text_id', + 'ar_len' ), array( 'ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDbkey(), 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), @@ -163,7 +204,7 @@ class PageArchive { return Revision::getRevisionText( $row, "ar_" ); } else { // New-style: keyed to the text storage backend. - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $text = $dbr->selectRow( 'text', array( 'old_text', 'old_flags' ), array( 'old_id' => $row->ar_text_id ), @@ -182,7 +223,7 @@ class PageArchive { * @return string */ function getLastRevisionText() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'archive', array( 'ar_text', 'ar_flags', 'ar_text_id' ), array( 'ar_namespace' => $this->title->getNamespace(), @@ -201,7 +242,7 @@ class PageArchive { * @return bool */ function isDeleted() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', array( 'ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDBkey() ) ); @@ -282,7 +323,7 @@ class PageArchive { $restoreAll = empty( $timestamps ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $page = $dbw->tableName( 'archive' ); # Does this page already exist? We'll have to update it... @@ -333,7 +374,8 @@ class PageArchive { 'ar_timestamp', 'ar_minor_edit', 'ar_flags', - 'ar_text_id' ), + 'ar_text_id', + 'ar_len' ), /* WHERE */ array( 'ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDBkey(), @@ -373,6 +415,7 @@ class PageArchive { 'timestamp' => $row->ar_timestamp, 'minor_edit' => $row->ar_minor_edit, 'text_id' => $row->ar_text_id, + 'len' => $row->ar_len ) ); $revision->insertOn( $dbw ); $restored++; @@ -389,8 +432,10 @@ class PageArchive { } if( $newid ) { + wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) ); Article::onArticleCreate( $this->title ); } else { + wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) ); Article::onArticleEdit( $this->title ); } } else { @@ -411,18 +456,19 @@ class PageArchive { } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * The HTML form for Special:Undelete, which allows users with the appropriate + * permissions to view and restore deleted content. + * @addtogroup SpecialPage */ class UndeleteForm { var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj; var $mTargetTimestamp, $mAllowed, $mComment; - function UndeleteForm( &$request, $par = "" ) { + function UndeleteForm( $request, $par = "" ) { global $wgUser; $this->mAction = $request->getVal( 'action' ); $this->mTarget = $request->getVal( 'target' ); + $this->mSearchPrefix = $request->getText( 'prefix' ); $time = $request->getVal( 'timestamp' ); $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; $this->mFile = $request->getVal( 'file' ); @@ -467,9 +513,23 @@ class UndeleteForm { } function execute() { - + global $wgOut; + if ( $this->mAllowed ) { + $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); + } else { + $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); + } + if( is_null( $this->mTargetObj ) ) { - return $this->showList(); + $this->showSearchForm(); + + # List undeletable articles + if( $this->mSearchPrefix ) { + $result = PageArchive::listPagesByPrefix( + $this->mSearchPrefix ); + $this->showList( $result ); + } + return; } if( $this->mTimestamp !== '' ) { return $this->showRevision( $this->mTimestamp ); @@ -483,17 +543,35 @@ class UndeleteForm { return $this->showHistory(); } - /* private */ function showList() { - global $wgLang, $wgContLang, $wgUser, $wgOut; - - # List undeletable articles - $result = PageArchive::listAllPages(); + function showSearchForm() { + global $wgOut, $wgScript; + $wgOut->addWikiText( wfMsg( 'undelete-header' ) ); + + $wgOut->addHtml( + Xml::openElement( 'form', array( + 'method' => 'get', + 'action' => $wgScript ) ) . + '<fieldset>' . + Xml::element( 'legend', array(), + wfMsg( 'undelete-search-box' ) ) . + Xml::hidden( 'title', + SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . + Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), + 'prefix', 'prefix', 20, + $this->mSearchPrefix ) . + Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) . + '</fieldset>' . + '</form>' ); + } - if ( $this->mAllowed ) { - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); - } else { - $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); + /* private */ function showList( $result ) { + global $wgLang, $wgContLang, $wgUser, $wgOut; + + if( $result->numRows() == 0 ) { + $wgOut->addWikiText( wfMsg( 'undelete-no-results' ) ); + return; } + $wgOut->addWikiText( wfMsg( "undeletepagetext" ) ); $sk = $wgUser->getSkin(); @@ -502,7 +580,10 @@ class UndeleteForm { while( $row = $result->fetchObject() ) { $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); $link = $sk->makeKnownLinkObj( $undelete, htmlspecialchars( $title->getPrefixedText() ), 'target=' . $title->getPrefixedUrl() ); - $revs = wfMsgHtml( 'undeleterevisions', $wgLang->formatNum( $row->count ) ); + #$revs = wfMsgHtml( 'undeleterevisions', $wgLang->formatNum( $row->count ) ); + $revs = wfMsgExt( 'undeleterevisions', + array( 'parseinline' ), + $wgLang->formatNum( $row->count ) ); $wgOut->addHtml( "<li>{$link} ({$revs})</li>\n" ); } $result->free(); @@ -513,15 +594,19 @@ class UndeleteForm { /* private */ function showRevision( $timestamp ) { global $wgLang, $wgUser, $wgOut; + $self = SpecialPage::getTitleFor( 'Undelete' ); + $skin = $wgUser->getSkin(); if(!preg_match("/[0-9]{14}/",$timestamp)) return 0; $archive = new PageArchive( $this->mTargetObj ); $rev = $archive->getRevision( $timestamp ); - $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); - $wgOut->addWikiText( "(" . wfMsg( "undeleterevision", - $wgLang->timeAndDate( $timestamp ) ) . ")\n" ); + $wgOut->setPageTitle( wfMsg( 'undeletepage' ) ); + $link = $skin->makeKnownLinkObj( $self, htmlspecialchars( $this->mTargetObj->getPrefixedText() ), + 'target=' . $this->mTargetObj->getPrefixedUrl() ); + $wgOut->addHtml( '<p>' . wfMsgHtml( 'undelete-revision', $link, + htmlspecialchars( $wgLang->timeAndDate( $timestamp ) ) ) . '</p>' ); if( !$rev ) { $wgOut->addWikiText( wfMsg( 'undeleterevision-missing' ) ); @@ -532,12 +617,9 @@ class UndeleteForm { if( $this->mPreview ) { $wgOut->addHtml( "<hr />\n" ); - $article = new Article ( $archive->title ); # OutputPage wants an Article obj - $wgOut->addPrimaryWikiText( $rev->getText(), $article, false ); + $wgOut->addWikiTextTitleTidy( $rev->getText(), $this->mTargetObj, false ); } - - $self = SpecialPage::getTitleFor( "Undelete" ); - + $wgOut->addHtml( wfElement( 'textarea', array( 'readonly' => true, @@ -673,7 +755,7 @@ class UndeleteForm { } $wgOut->addHTML( "<h2>" . htmlspecialchars( wfMsg( "history" ) ) . "</h2>\n" ); - + if( $haveRevisions ) { # The page's stored (deleted) history: $wgOut->addHTML("<ul>"); @@ -690,8 +772,16 @@ class UndeleteForm { $pageLink = $wgLang->timeanddate( $ts, true ); } $userLink = $sk->userLink( $row->ar_user, $row->ar_user_text ) . $sk->userToolLinks( $row->ar_user, $row->ar_user_text ); + $stxt = ''; + if (!is_null($size = $row->ar_len)) { + if ($size == 0) { + $stxt = wfMsgHtml('historyempty'); + } else { + $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) ); + } + } $comment = $sk->commentBlock( $row->ar_comment ); - $wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $comment</li>\n" ); + $wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $stxt $comment</li>\n" ); } $revisions->free(); @@ -753,7 +843,7 @@ class UndeleteForm { $this->mFileVersions ); if( $ok ) { - $skin =& $wgUser->getSkin(); + $skin = $wgUser->getSkin(); $link = $skin->makeKnownLinkObj( $this->mTargetObj ); $wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) ); return true; diff --git a/includes/SpecialUnlockdb.php b/includes/SpecialUnlockdb.php index 1f24d131..e864a182 100644 --- a/includes/SpecialUnlockdb.php +++ b/includes/SpecialUnlockdb.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -31,8 +30,7 @@ function wfSpecialUnlockdb() { /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class DBUnlockForm { function showForm( $err ) diff --git a/includes/SpecialUnusedcategories.php b/includes/SpecialUnusedcategories.php index 80f46a87..5cd3406b 100644 --- a/includes/SpecialUnusedcategories.php +++ b/includes/SpecialUnusedcategories.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class UnusedCategoriesPage extends QueryPage { @@ -22,7 +20,7 @@ class UnusedCategoriesPage extends QueryPage { function getSQL() { $NScat = NS_CATEGORY; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); return "SELECT 'Unusedcategories' as type, {$NScat} as namespace, page_title as title, page_title as value diff --git a/includes/SpecialUnusedimages.php b/includes/SpecialUnusedimages.php index 75d702c8..6b99192a 100644 --- a/includes/SpecialUnusedimages.php +++ b/includes/SpecialUnusedimages.php @@ -1,15 +1,14 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Unusedimages + * @addtogroup SpecialPage */ -class UnusedimagesPage extends QueryPage { +class UnusedimagesPage extends ImageQueryPage { function getName() { return 'Unusedimages'; @@ -22,7 +21,7 @@ class UnusedimagesPage extends QueryPage { function getSQL() { global $wgCountCategorizedImagesAsUsed; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); if ( $wgCountCategorizedImagesAsUsed ) { list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' ); @@ -40,34 +39,6 @@ class UnusedimagesPage extends QueryPage { } } - function formatResult( $skin, $result ) { - global $wgLang, $wgContLang; - $title = Title::makeTitle( NS_IMAGE, $result->title ); - - $imageUrl = htmlspecialchars( Image::imageUrl( $result->title ) ); - $dirmark = $wgContLang->getDirMark(); // To keep text in correct order - - $return = - # The 'desc' linking to the image page - '('.$skin->makeKnownLinkObj( $title, wfMsg('imgdesc') ).') ' . $dirmark . - - # Link to the image itself - '<a href="' . $imageUrl . '">' . htmlspecialchars( $title->getText() ) . - '</a> . . ' . $dirmark . - - # Last modified date - $wgLang->timeanddate($result->value) . ' . . ' . $dirmark . - - # Link to username - $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), - $result->img_user_text) . $dirmark . - - # If there is a description, show it - $skin->commentBlock( $wgContLang->convert( $result->img_description ) ); - - return $return; - } - function getPageHeader() { return wfMsg( "unusedimagestext" ); } diff --git a/includes/SpecialUnusedtemplates.php b/includes/SpecialUnusedtemplates.php index 2af9abc6..8b72e8a7 100644 --- a/includes/SpecialUnusedtemplates.php +++ b/includes/SpecialUnusedtemplates.php @@ -1,19 +1,12 @@ <?php /** - * @package MediaWiki - * @subpackage Special pages - * + * implements Special:Unusedtemplates * @author Rob Church <robchur@gmail.com> * @copyright © 2006 Rob Church * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + * @addtogroup SpecialPage */ - -/** - * @package MediaWiki - * @subpackage SpecialPage - */ - class UnusedtemplatesPage extends QueryPage { function getName() { return( 'Unusedtemplates' ); } @@ -22,7 +15,7 @@ class UnusedtemplatesPage extends QueryPage { function sortDescending() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' ); $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title, page_namespace AS namespace, 0 AS value diff --git a/includes/SpecialUnwatchedpages.php b/includes/SpecialUnwatchedpages.php index f9dff724..fed0b590 100644 --- a/includes/SpecialUnwatchedpages.php +++ b/includes/SpecialUnwatchedpages.php @@ -1,19 +1,13 @@ <?php /** - * A special page that displays a list of pages that are not on anyones watchlist - * - * @package MediaWiki - * @subpackage SpecialPage + * A special page that displays a list of pages that are not on anyones watchlist. + * Implements Special:Unwatchedpages * + * @addtogroup SpecialPage * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ - -/** - * @package MediaWiki - * @subpackage SpecialPage - */ class UnwatchedpagesPage extends QueryPage { function getName() { return 'Unwatchedpages'; } @@ -21,7 +15,7 @@ class UnwatchedpagesPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' ); $mwns = NS_MEDIAWIKI; return diff --git a/includes/SpecialUpload.php b/includes/SpecialUpload.php index d2fd839c..e07c414c 100644 --- a/includes/SpecialUpload.php +++ b/includes/SpecialUpload.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ @@ -16,9 +15,8 @@ function wfSpecialUpload() { } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Upload + * @addtogroup SpecialPage */ class UploadForm { /**#@+ @@ -126,11 +124,11 @@ class UploadForm { $this->mOname = array_pop( explode( '/', $url ) ); $this->mSessionKey = false; $this->mStashed = false; - + // PHP won't auto-cleanup the file $this->mRemoveTempFile = file_exists( $local_file ); } - + /** * Safe copy from URL * Returns true if there was an error, false otherwise @@ -158,19 +156,19 @@ class UploadForm { $wgOut->errorPage( 'upload-file-error', 'upload-file-error-text'); return true; } - + $ch = curl_init(); curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed - curl_setopt( $ch, CURLOPT_URL, $url); + curl_setopt( $ch, CURLOPT_URL, $url); curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); curl_exec( $ch ); $error = curl_errno( $ch ) ? true : false; $errornum = curl_errno( $ch ); // if ( $error ) print curl_error ( $ch ) ; # Debugging output curl_close( $ch ); - + fclose( $this->mUploadTempFile ); unset( $this->mUploadTempFile ); if( $error ) { @@ -180,10 +178,10 @@ class UploadForm { else $wgOut->errorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" ); } - + return $error; } - + /** * Callback function for CURL-based web transfer * Write data to file unless we've passed the length limit; @@ -200,7 +198,7 @@ class UploadForm { fwrite( $this->mUploadTempFile, $data ); return $length; } - + /** * Start doing stuff * @access public @@ -298,13 +296,12 @@ class UploadForm { * only the final one for the whitelist. */ list( $partname, $ext ) = $this->splitExtensions( $basename ); - + if( count( $ext ) ) { $finalExt = $ext[count( $ext ) - 1]; } else { $finalExt = ''; } - $fullExt = implode( '.', $ext ); # If there was more than one "extension", reassemble the base # filename to prevent bogus complaints about length @@ -335,7 +332,7 @@ class UploadForm { * If the image is protected, non-sysop users won't be able * to modify it by uploading a new revision. */ - if( !$nt->userCanEdit() ) { + if( !$nt->userCan( 'edit' ) ) { return $this->uploadError( wfMsgWikiHtml( 'protectedpage' ) ); } @@ -350,10 +347,12 @@ class UploadForm { /* Don't allow users to override the blacklist (check file extension) */ global $wgStrictFileExtensions; global $wgFileExtensions, $wgFileBlacklist; - if( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || - ($wgStrictFileExtensions && - !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) { - return $this->uploadError( wfMsgHtml( 'badfiletype', htmlspecialchars( $finalExt ) ) ); + if ($finalExt == '') { + return $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) ); + } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || + ($wgStrictFileExtensions && + !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) { + return $this->uploadError( wfMsgExt( 'filetype-badtype', array ( 'parseinline' ), htmlspecialchars( $finalExt ), implode ( ', ', $wgFileExtensions ) ) ); } /** @@ -396,13 +395,13 @@ class UploadForm { global $wgCheckFileExtensions; if ( $wgCheckFileExtensions ) { if ( ! $this->checkFileExtension( $finalExt, $wgFileExtensions ) ) { - $warning .= '<li>'.wfMsgHtml( 'badfiletype', htmlspecialchars( $finalExt ) ).'</li>'; + $warning .= '<li>'.wfMsgExt( 'filetype-badtype', array ( 'parseinline' ), htmlspecialchars( $finalExt ), implode ( ', ', $wgFileExtensions ) ).'</li>'; } } global $wgUploadSizeWarning; if ( $wgUploadSizeWarning && ( $this->mUploadSize > $wgUploadSizeWarning ) ) { - $skin =& $wgUser->getSkin(); + $skin = $wgUser->getSkin(); $wsize = $skin->formatSize( $wgUploadSizeWarning ); $asize = $skin->formatSize( $this->mUploadSize ); $warning .= '<li>' . wfMsgHtml( 'large-file', $wsize, $asize ) . '</li>'; @@ -411,21 +410,73 @@ class UploadForm { $warning .= '<li>'.wfMsgHtml( 'emptyfile' ).'</li>'; } - if( $nt->getArticleID() ) { - global $wgUser; - $sk = $wgUser->getSkin(); + global $wgUser; + $sk = $wgUser->getSkin(); + $image = new Image( $nt ); + + // Check for uppercase extension. We allow these filenames but check if an image + // with lowercase extension exists already + if ( $finalExt != strtolower( $finalExt ) ) { + $nt_lc = Title::newFromText( $partname . '.' . strtolower( $finalExt ) ); + $image_lc = new Image( $nt_lc ); + } + + if( $image->exists() ) { $dlink = $sk->makeKnownLinkObj( $nt ); - $warning .= '<li>'.wfMsgHtml( 'fileexists', $dlink ).'</li>'; - } else { + if ( $image->allowInlineDisplay() ) { + $dlink2 = $sk->makeImageLinkObj( $nt, wfMsgExt( 'fileexists-thumb', 'parseinline', $dlink ), $nt->getText(), 'right', array(), false, true ); + } elseif ( !$image->allowInlineDisplay() && $image->isSafeFile() ) { + $icon = $image->iconThumb(); + $dlink2 = '<div style="float:right" id="mw-media-icon"><a href="' . $image->getURL() . '">' . $icon->toHtml() . '</a><br />' . $dlink . '</div>'; + } else { + $dlink2 = ''; + } + + $warning .= '<li>' . wfMsgExt( 'fileexists', 'parseline', $dlink ) . '</li>' . $dlink2; + + } elseif ( isset( $image_lc) && $image_lc->exists() ) { + # Check if image with lowercase extension exists. + # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension + $dlink = $sk->makeKnownLinkObj( $nt_lc ); + if ( $image_lc->allowInlineDisplay() ) { + $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline', $dlink ), $nt_lc->getText(), 'right', array(), false, true ); + } elseif ( !$image_lc->allowInlineDisplay() && $image_lc->isSafeFile() ) { + $icon = $image_lc->iconThumb(); + $dlink2 = '<div style="float:right" id="mw-media-icon"><a href="' . $image_lc->getURL() . '">' . $icon->toHtml() . '</a><br />' . $dlink . '</div>'; + } else { + $dlink2 = ''; + } + + $warning .= '<li>' . wfMsgExt( 'fileexists-extension', 'parsemag' , $partname . '.' . $finalExt , $dlink ) . '</li>' . $dlink2; + + } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) ) { + # Check for filenames like 50px- or 180px-, these are mostly thumbnails + $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $finalExt ); + $image_thb = new Image( $nt_thb ); + if ($image_thb->exists() ) { + # Check if an image without leading '180px-' (or similiar) exists + $dlink = $sk->makeKnownLinkObj( $nt_thb); + if ( $image_thb->allowInlineDisplay() ) { + $dlink2 = $sk->makeImageLinkObj( $nt_thb, wfMsgExt( 'fileexists-thumb', 'parseinline', $dlink ), $nt_thb->getText(), 'right', array(), false, true ); + } elseif ( !$image_thb->allowInlineDisplay() && $image_thb->isSafeFile() ) { + $icon = $image_thb->iconThumb(); + $dlink2 = '<div style="float:right" id="mw-media-icon"><a href="' . $image_thb->getURL() . '">' . $icon->toHtml() . '</a><br />' . $dlink . '</div>'; + } else { + $dlink2 = ''; + } + + $warning .= '<li>' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) . '</li>' . $dlink2; + } else { + # Image w/o '180px-' does not exists, but we do not like these filenames + $warning .= '<li>' . wfMsgExt( 'file-thumbnail-no', 'parseinline' , substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '</li>'; + } + } + if ( $image->wasDeleted() ) { # If the file existed before and was deleted, warn the user of this # Don't bother doing so if the image exists now, however - $image = new Image( $nt ); - if( $image->wasDeleted() ) { - $skin = $wgUser->getSkin(); - $ltitle = SpecialPage::getTitleFor( 'Log' ); - $llink = $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ), 'type=delete&page=' . $nt->getPrefixedUrl() ); - $warning .= wfOpenElement( 'li' ) . wfMsgWikiHtml( 'filewasdeleted', $llink ) . wfCloseElement( 'li' ); - } + $ltitle = SpecialPage::getTitleFor( 'Log' ); + $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ), 'type=delete&page=' . $nt->getPrefixedUrl() ); + $warning .= wfOpenElement( 'li' ) . wfMsgWikiHtml( 'filewasdeleted', $llink ) . wfCloseElement( 'li' ); } if( $warning != '' ) { @@ -482,7 +533,7 @@ class UploadForm { */ function saveUploadedFile( $saveName, $tempName, $useRename = false ) { global $wgOut, $wgAllowCopyUploads; - + if ( !$useRename AND $wgAllowCopyUploads AND $this->mSourceType == 'web' ) $useRename = true; $fname= "SpecialUpload::saveUploadedFile"; @@ -491,7 +542,7 @@ class UploadForm { $archive = wfImageArchiveDir( $saveName ); if ( !is_dir( $dest ) ) wfMkdirParents( $dest ); if ( !is_dir( $archive ) ) wfMkdirParents( $archive ); - + $this->mSavedFile = "{$dest}/{$saveName}"; if( is_file( $this->mSavedFile ) ) { @@ -725,7 +776,7 @@ class UploadForm { "<span class='error'>{$msg}</span>\n" ); } $wgOut->addHTML( '<div id="uploadtext">' ); - $wgOut->addWikiText( wfMsg( 'uploadtext' ) ); + $wgOut->addWikiText( wfMsgNoTrans( 'uploadtext', $this->mDestFile ) ); $wgOut->addHTML( '</div>' ); $sourcefilename = wfMsgHtml( 'sourcefilename' ); @@ -753,19 +804,19 @@ class UploadForm { // Prepare form for upload or upload/copy if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) { - $filename_form = - "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked />" . - "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' onfocus='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")'" . + $filename_form = + "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked />" . + "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' onfocus='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")'" . ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . "size='40' />" . wfMsgHTML( 'upload_source_file' ) . "<br/>" . "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='web' onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" . - "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' onfocus='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")'" . + "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' onfocus='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")'" . ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFileURL\")' ") . "size='40' DISABLED />" . wfMsgHtml( 'upload_source_url' ) ; } else { - $filename_form = - "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " . - ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . + $filename_form = + "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " . + ($this->mDestFile?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") . "size='40' />" . "<input type='hidden' name='wpSourceType' value='file' />" ; } @@ -817,7 +868,7 @@ class UploadForm { $copystatus = htmlspecialchars( $this->mUploadCopyStatus ); $filesource = wfMsgHtml ( 'filesource' ); $uploadsource = htmlspecialchars( $this->mUploadSource ); - + $wgOut->addHTML( " <td align='right' nowrap='nowrap'><label for='wpUploadCopyStatus'>$filestatus:</label></td> <td><input tabindex='5' type='text' name='wpUploadCopyStatus' id='wpUploadCopyStatus' value=\"$copystatus\" size='40' /></td> @@ -930,7 +981,7 @@ class UploadForm { global $wgMimeTypeBlacklist; if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { - return new WikiErrorMsg( 'badfiletype', htmlspecialchars( $mime ) ); + return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) ); } } @@ -1074,13 +1125,13 @@ class UploadForm { $chunk = Sanitizer::decodeCharReferences( $chunk ); #look for script-types - if (preg_match('!type\s*=\s*[\'"]?\s*(\w*/)?(ecma|java)!sim',$chunk)) return true; + if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true; #look for html-style script-urls - if (preg_match('!(href|src|data)\s*=\s*[\'"]?\s*(ecma|java)script:!sim',$chunk)) return true; + if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; #look for css-style script-urls - if (preg_match('!url\s*\(\s*[\'"]?\s*(ecma|java)script:!sim',$chunk)) return true; + if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; wfDebug("SpecialUpload::detectScript: no scripts found\n"); return false; @@ -1255,6 +1306,4 @@ class UploadForm { } } - - ?> diff --git a/includes/SpecialUploadMogile.php b/includes/SpecialUploadMogile.php index 05bfca08..27af62e7 100644 --- a/includes/SpecialUploadMogile.php +++ b/includes/SpecialUploadMogile.php @@ -1,12 +1,11 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * + * You will need the extension MogileClient to use this special page. */ require_once( 'MogileFS.php' ); @@ -19,7 +18,10 @@ function wfSpecialUploadMogile() { $form->execute(); } -/** @package MediaWiki */ +/** + * Extends Special:Upload with MogileFS. + * @addtogroup SpecialPage + */ class UploadFormMogile extends UploadForm { /** * Move the uploaded file from its temporary location to the final diff --git a/includes/SpecialUserlogin.php b/includes/SpecialUserlogin.php index e60e3d54..e8f33b8d 100644 --- a/includes/SpecialUserlogin.php +++ b/includes/SpecialUserlogin.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -11,7 +10,7 @@ function wfSpecialUserlogin() { global $wgCommandLineMode; global $wgRequest; - if( !$wgCommandLineMode && !isset( $_COOKIE[session_name()] ) ) { + if( session_id() == '' ) { wfSetupSession(); } @@ -20,11 +19,9 @@ function wfSpecialUserlogin() { } /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Login + * @addtogroup SpecialPage */ - class LoginForm { const SUCCESS = 0; @@ -42,7 +39,7 @@ class LoginForm { /** * Constructor - * @param webrequest $request A webrequest object passed by reference + * @param WebRequest $request A WebRequest object passed by reference */ function LoginForm( &$request ) { global $wgLang, $wgAllowRealName, $wgEnableEmail; @@ -149,12 +146,12 @@ class LoginForm { */ function addNewAccount() { global $wgUser, $wgEmailAuthentication; - + # Create the account and abort if there's a problem doing so $u = $this->addNewAccountInternal(); if( $u == NULL ) return; - + # If we showed up language selection links, and one was in use, be # smart (and sensible) and save that language as the user's preference global $wgLoginLanguageSelector; @@ -231,6 +228,7 @@ class LoginForm { return false; } + # Check anonymous user ($wgUser) limitations : if (!$wgUser->isAllowedToCreateAccount()) { $this->userNotPrivilegedMessage(); return false; @@ -244,6 +242,7 @@ class LoginForm { return; } + # Now create a dummy user ($u) and check if it is valid $name = trim( $this->mName ); $u = User::newFromName( $name, 'creatable' ); if ( is_null( $u ) ) { @@ -261,7 +260,7 @@ class LoginForm { return false; } - if ( !$wgUser->isValidPassword( $this->mPassword ) ) { + if ( !$u->isValidPassword( $this->mPassword ) ) { $this->mainLoginForm( wfMsg( 'passwordtooshort', $wgMinimalPasswordLength ) ); return false; } @@ -274,7 +273,7 @@ class LoginForm { return false; } - if ( $wgAccountCreationThrottle ) { + if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { $key = wfMemcKey( 'acctcreate', 'ip', $ip ); $value = $wgMemc->incr( $key ); if ( !$value ) { @@ -286,15 +285,11 @@ class LoginForm { } } - if( !$wgAuth->addUser( $u, $this->mPassword ) ) { + if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { $this->mainLoginForm( wfMsg( 'externaldberror' ) ); return false; } - # Update user count - $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); - $ssUpdate->doUpdate(); - return $this->initUser( $u ); } @@ -307,18 +302,27 @@ class LoginForm { * @private */ function initUser( $u ) { + global $wgAuth; + $u->addToDatabase(); - $u->setPassword( $this->mPassword ); + + if ( $wgAuth->allowPasswordChange() ) { + $u->setPassword( $this->mPassword ); + } + $u->setEmail( $this->mEmail ); $u->setRealName( $this->mRealName ); $u->setToken(); - global $wgAuth; $wgAuth->initUser( $u ); $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); $u->saveSettings(); + # Update user count + $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); + $ssUpdate->doUpdate(); + return $u; } @@ -361,6 +365,7 @@ class LoginForm { } if (!$u->checkPassword( $this->mPassword )) { + if( $u->checkTemporaryPassword( $this->mPassword ) ) { // The e-mailed temporary password should not be used // for actual logins; that's a very sloppy habit, @@ -383,7 +388,7 @@ class LoginForm { if( !$u->isEmailConfirmed() ) { $u->confirmEmail(); } - + // At this point we just return an appropriate code // indicating that the UI should show a password // reset form; bot interfaces etc will probably just @@ -393,14 +398,14 @@ class LoginForm { } else { return '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS; } - } else { + } else { $wgAuth->updateUser( $u ); $wgUser = $u; return self::SUCCESS; } } - + function processLogin() { global $wgUser, $wgAuth; @@ -446,7 +451,7 @@ class LoginForm { wfDebugDieBacktrace( "Unhandled case value" ); } } - + function resetLoginForm( $error ) { global $wgOut; $wgOut->addWikiText( "<div class=\"errorbox\">$error</div>" ); @@ -459,19 +464,19 @@ class LoginForm { */ function mailPassword() { global $wgUser, $wgOut, $wgAuth; - + if( !$wgAuth->allowPasswordChange() ) { $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); return; } - + # Check against blocked IPs # fixme -- should we not? if( $wgUser->isBlocked() ) { $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); return; } - + # Check against the rate limiter if( $wgUser->pingLimiter( 'mailpassword' ) ) { $wgOut->rateLimited(); @@ -496,7 +501,7 @@ class LoginForm { if ( $u->isPasswordReminderThrottled() ) { global $wgPasswordReminderResendTime; # Round the time in hours to 3 d.p., in case someone is specifying minutes or seconds. - $this->mainLoginForm( wfMsg( 'throttled-mailpassword', + $this->mainLoginForm( wfMsg( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) ); return; } @@ -587,7 +592,7 @@ class LoginForm { # haven't bothered to log out before trying to create an account to # evade it, but we'll leave that to their guilty conscience to figure # out. - + $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); @@ -642,7 +647,7 @@ class LoginForm { $q .= $returnto; $linkq .= $returnto; } - + # Pass any language selection on to the mode switch link if( $wgLoginLanguageSelector && $this->mLanguage ) $linkq .= '&uselang=' . $this->mLanguage; @@ -656,7 +661,7 @@ class LoginForm { $template->set( 'link', wfMsgHtml( $linkmsg, $link ) ); else $template->set( 'link', '' ); - + $template->set( 'header', '' ); $template->set( 'name', $this->mName ); $template->set( 'password', $this->mPassword ); @@ -673,14 +678,14 @@ class LoginForm { $template->set( 'useemail', $wgEnableEmail ); $template->set( 'canreset', $wgAuth->allowPasswordChange() ); $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); - + # Prepare language selection links as needed if( $wgLoginLanguageSelector ) { $template->set( 'languages', $this->makeLanguageSelector() ); if( $this->mLanguage ) $template->set( 'uselang', $this->mLanguage ); } - + // Give authentication and captcha plugins a chance to modify the form $wgAuth->modifyUITemplate( $template ); if ( $this->mType == 'signup' ) { @@ -694,7 +699,7 @@ class LoginForm { $wgOut->setArticleRelated( false ); $wgOut->addTemplate( $template ); } - + /** * @private */ @@ -709,11 +714,17 @@ class LoginForm { } /** + * Check if a session cookie is present. + * + * This will not pick up a cookie set during _this_ request, but is + * meant to ensure that the client is returning the cookie which was + * set on a previous pass through the system. + * * @private */ function hasSessionCookie() { - global $wgDisableCookieCheck; - return ( $wgDisableCookieCheck ) ? true : ( isset( $_COOKIE[session_name()] ) ); + global $wgDisableCookieCheck, $wgRequest; + return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); } /** @@ -756,7 +767,7 @@ class LoginForm { $wgOut->addWikiText( wfMsg( 'acct_creation_throttle_hit', $limit ) ); } - + /** * Produce a bar of links which allow the user to select another language * during login/registration but retain "returnto" @@ -778,7 +789,7 @@ class LoginForm { return ''; } } - + /** * Create a language selector link for a particular language * Links back to this page preserving type and returnto @@ -794,9 +805,8 @@ class LoginForm { $attr[] = 'type=signup'; if( $this->mReturnTo ) $attr[] = 'returnto=' . $this->mReturnTo; - $skin =& $wgUser->getSkin(); + $skin = $wgUser->getSkin(); return $skin->makeKnownLinkObj( $self, htmlspecialchars( $text ), implode( '&', $attr ) ); } - } ?> diff --git a/includes/SpecialUserlogout.php b/includes/SpecialUserlogout.php index f3fcbc4f..9f1bdb3a 100644 --- a/includes/SpecialUserlogout.php +++ b/includes/SpecialUserlogout.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** diff --git a/includes/SpecialUserrights.php b/includes/SpecialUserrights.php index 99abd7a7..d12a9cc4 100644 --- a/includes/SpecialUserrights.php +++ b/includes/SpecialUserrights.php @@ -3,13 +3,12 @@ /** * Special page to allow managing user group membership * - * @package MediaWiki - * @subpackage Special pages + * @addtogroup SpecialPage * @todo This code is disgusting and needs a total rewrite */ /** */ -require_once('HTMLForm.php'); +require_once( dirname(__FILE__) . '/HTMLForm.php'); /** Entry point */ function wfSpecialUserrights() { @@ -20,8 +19,7 @@ function wfSpecialUserrights() { /** * A class to manage user levels rights. - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ class UserrightsForm extends HTMLForm { var $mPosted, $mRequest, $mSaveprefs; @@ -55,10 +53,12 @@ class UserrightsForm extends HTMLForm { if( $this->mRequest->getCheck( 'saveusergroups' ) ) { global $wgUser; $username = $this->mRequest->getVal( 'user-editname' ); + $reason = $this->mRequest->getVal( 'user-reason' ); if( $wgUser->matchEditToken( $this->mRequest->getVal( 'wpEditToken' ), $username ) ) { $this->saveUserGroups( $username, $this->mRequest->getArray( 'member' ), - $this->mRequest->getArray( 'available' ) ); + $this->mRequest->getArray( 'available' ), + $reason ); } } } @@ -71,9 +71,10 @@ class UserrightsForm extends HTMLForm { * @param string $username Username to apply changes to. * @param array $removegroup id of groups to be removed. * @param array $addgroup id of groups to be added. + * @param string $reason Reason for group change * */ - function saveUserGroups( $username, $removegroup, $addgroup) { + function saveUserGroups( $username, $removegroup, $addgroup, $reason ) { global $wgOut; $u = User::newFromName($username); @@ -109,7 +110,7 @@ class UserrightsForm extends HTMLForm { wfRunHooks( 'UserRights', array( &$u, $addgroup, $removegroup ) ); $log = new LogPage( 'rights' ); - $log->addEntry( 'rights', Title::makeTitle( NS_USER, $u->getName() ), '', array( $this->makeGroupNameList( $oldGroups ), + $log->addEntry( 'rights', Title::makeTitle( NS_USER, $u->getName() ), $reason, array( $this->makeGroupNameList( $oldGroups ), $this->makeGroupNameList( $newGroups ) ) ); } @@ -137,7 +138,7 @@ class UserrightsForm extends HTMLForm { * @param string $username Name of the user. */ function editUserGroupsForm($username) { - global $wgOut, $wgUser; + global $wgOut; $user = User::newFromName($username); if( is_null( $user ) ) { @@ -149,30 +150,52 @@ class UserrightsForm extends HTMLForm { } $groups = $user->getGroups(); - - $wgOut->addHTML( "<form name=\"editGroup\" action=\"$this->action\" method=\"post\">\n". - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'user-editname', - 'value' => $username ) ) . - wfElement( 'input', array( - 'type' => 'hidden', - 'name' => 'wpEditToken', - 'value' => $wgUser->editToken( $username ) ) ) . - $this->fieldset( 'editusergroup', - $wgOut->parse( wfMsg('editinguser', $username ) ) . - '<table border="0" align="center"><tr><td>'. - HTMLSelectGroups('member', $this->mName.'-groupsmember', $groups,true,6). - '</td><td>'. - HTMLSelectGroups('available', $this->mName.'-groupsavailable', $groups,true,6,true). - '</td></tr></table>'."\n". - $wgOut->parse( wfMsg('userrights-groupshelp') ) . - wfElement( 'input', array( - 'type' => 'submit', - 'name' => 'saveusergroups', - 'value' => wfMsg( 'saveusergroups' ) ) ) - )); - $wgOut->addHTML( "</form>\n" ); + $this->showEditUserGroupsForm( $username, $groups ); + } + + function showEditUserGroupsForm( $username, $groups ) { + global $wgOut, $wgUser; + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->action, 'name' => 'editGroup' ) ) . + Xml::hidden( 'user-editname', $username ) . + Xml::hidden( 'wpEditToken', $wgUser->editToken( $username ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) . + $wgOut->parse( wfMsg( 'editinguser', $username ) ) . + "<table border='0'> + <tr> + <td></td> + <td> + <table width='400'> + <tr> + <td width='50%'>" . HTMLSelectGroups( 'member', $this->mName.'-groupsmember', $groups, true, 6 ) . "</td> + <td width='50%'>" . HTMLSelectGroups( 'available', $this->mName.'-groupsavailable', $groups, true, 6, true) . "</td> + </tr> + </table> + </tr> + <tr> + <td colspan='2'>" . + $wgOut->parse( wfMsg('userrights-groupshelp') ) . + "</td> + </tr> + <tr> + <td>" . + Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) . + "</td> + <td>" . + Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason' ) ) . + "</td> + </tr> + <tr> + <td></td> + <td>" . + Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups' ) ) . + "</td> + </tr> + </table>\n" . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ) . "\n" + ); } } // end class UserrightsForm ?> diff --git a/includes/SpecialVersion.php b/includes/SpecialVersion.php index dba694c0..6de2da11 100644 --- a/includes/SpecialVersion.php +++ b/includes/SpecialVersion.php @@ -2,10 +2,7 @@ /**#@+ * Give information about the version of MediaWiki, PHP, the DB and extensions * - * @package MediaWiki - * @subpackage SpecialPage - * - * @bug 2019, 4531 + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason @@ -50,10 +47,7 @@ class SpecialVersion { */ function MediaWikiCredits() { $version = self::getVersion(); - $dbr =& wfGetDB( DB_SLAVE ); - - global $wgLanguageNames, $wgLanguageCode; - $mwlang = $wgLanguageNames[$wgLanguageCode]; + $dbr = wfGetDB( DB_SLAVE ); $ret = "__NOTOC__ @@ -110,21 +104,19 @@ class SpecialVersion { $out .= wfOpenElement('table', array('id' => 'sv-ext') ); foreach ( $extensionTypes as $type => $text ) { - if ( count( @$wgExtensionCredits[$type] ) ) { + if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) { $out .= $this->openExtType( $text ); usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); foreach ( $wgExtensionCredits[$type] as $extension ) { - wfSuppressWarnings(); $out .= $this->formatCredits( - $extension['name'], - $extension['version'], - $extension['author'], - $extension['url'], - $extension['description'] + isset ( $extension['name'] ) ? $extension['name'] : '', + isset ( $extension['version'] ) ? $extension['version'] : null, + isset ( $extension['author'] ) ? $extension['author'] : '', + isset ( $extension['url'] ) ? $extension['url'] : null, + isset ( $extension['description'] ) ? $extension['description'] : '' ); - wfRestoreWarnings(); } } } @@ -195,7 +187,7 @@ class SpecialVersion { foreach ($myWgHooks as $hook => $hooks) $ret .= "<tr><td>$hook</td><td>" . $this->listToText( $hooks ) . "</td></tr>\n"; - + $ret .= '</table>'; return $ret; } else @@ -269,8 +261,6 @@ class SpecialVersion { /** * Retrieve the revision number of a Subversion working directory. * - * @bug 7335 - * * @param string $dir * @return mixed revision number as int, or false if not a SVN checkout */ diff --git a/includes/SpecialWantedcategories.php b/includes/SpecialWantedcategories.php index 05ee7ec0..27a9f176 100644 --- a/includes/SpecialWantedcategories.php +++ b/includes/SpecialWantedcategories.php @@ -1,19 +1,13 @@ <?php /** - * A querypage to list the most wanted categories + * A querypage to list the most wanted categories - implements Special:Wantedcategories * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ - -/** - * @package MediaWiki - * @subpackage SpecialPage - */ class WantedCategoriesPage extends QueryPage { function getName() { return 'Wantedcategories'; } @@ -21,7 +15,7 @@ class WantedCategoriesPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' ); $name = $dbr->addQuotes( $this->getName() ); return diff --git a/includes/SpecialWantedpages.php b/includes/SpecialWantedpages.php index 8e5cee3e..8b700209 100644 --- a/includes/SpecialWantedpages.php +++ b/includes/SpecialWantedpages.php @@ -1,14 +1,12 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** - * - * @package MediaWiki - * @subpackage SpecialPage + * implements Special:Wantedpages + * @addtogroup SpecialPage */ class WantedPagesPage extends QueryPage { var $nlinks; @@ -30,7 +28,7 @@ class WantedPagesPage extends QueryPage { function getSQL() { global $wgWantedPagesThreshold; $count = $wgWantedPagesThreshold - 1; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $pagelinks = $dbr->tableName( 'pagelinks' ); $page = $dbr->tableName( 'page' ); return diff --git a/includes/SpecialWatchlist.php b/includes/SpecialWatchlist.php index 33e19a2b..2e660bd5 100644 --- a/includes/SpecialWatchlist.php +++ b/includes/SpecialWatchlist.php @@ -1,14 +1,13 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** * */ -require_once( 'SpecialRecentchanges.php' ); +require_once( dirname(__FILE__) . '/SpecialRecentchanges.php' ); /** * Constructor @@ -16,12 +15,12 @@ require_once( 'SpecialRecentchanges.php' ); * @param $par Parameter passed to the page */ function wfSpecialWatchlist( $par ) { - global $wgUser, $wgOut, $wgLang, $wgMemc, $wgRequest, $wgContLang; + global $wgUser, $wgOut, $wgLang, $wgRequest, $wgContLang; global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; global $wgEnotifWatchlist; $fname = 'wfSpecialWatchlist'; - $skin =& $wgUser->getSkin(); + $skin = $wgUser->getSkin(); $specialTitle = SpecialPage::getTitleFor( 'Watchlist' ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); @@ -86,25 +85,25 @@ function wfSpecialWatchlist( $par ) { # Deleting items from watchlist if(($action == 'submit') && isset($remove) && is_array($id)) { $wgOut->addWikiText( wfMsg( 'removingchecked' ) ); - $wgOut->addHTML( '<p>' ); + $wgOut->addHTML( "<ul id=\"mw-unwatch-list\">\n" ); foreach($id as $one) { $t = Title::newFromURL( $one ); if( !is_null( $t ) ) { $wl = WatchedItem::fromUserTitle( $wgUser, $t ); if( $wl->removeWatch() === false ) { - $wgOut->addHTML( wfMsg( 'couldntremove', htmlspecialchars($one) ) . "<br />\n" ); + $wgOut->addHTML( '<li class="mw-unwatch-failure">' . wfMsg( 'couldntremove', htmlspecialchars($one) ) . "</li>\n" ); } else { wfRunHooks('UnwatchArticle', array(&$wgUser, new Article($t))); - $wgOut->addHTML( '(' . htmlspecialchars($one) . ')<br />' ); + $wgOut->addHTML( '<li class="mw-unwatch-success">[[' . htmlspecialchars($one) . "]]</li>\n" ); } } else { - $wgOut->addHTML( wfMsg( 'iteminvalidname', htmlspecialchars($one) ) . "<br />\n" ); + $wgOut->addHTML( '<li class="mw-unwatch-invalid">' . wfMsg( 'iteminvalidname', htmlspecialchars($one) ) . "</li>\n" ); } } - $wgOut->addHTML( "</p>\n<p>" . wfMsg( 'wldone' ) . "</p>\n" ); + $wgOut->addHTML( "</ul>\n<p>" . wfMsg( 'wldone' ) . "</p>\n" ); } - $dbr =& wfGetDB( DB_SLAVE, 'watchlist' ); + $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); list( $page, $watchlist, $recentchanges ) = $dbr->tableNamesN( 'page', 'watchlist', 'recentchanges' ); $sql = "SELECT COUNT(*) AS n FROM $watchlist WHERE wl_user=$uid"; @@ -158,7 +157,7 @@ function wfSpecialWatchlist( $par ) { /* Edit watchlist form */ if($wgRequest->getBool('edit') || $par == 'edit' ) { - $wgOut->addWikiText( wfMsg( 'watchlistcontains', $wgLang->formatNum( $nitems ) ) . + $wgOut->addWikiText( wfMsgExt( 'watchlistcontains', array( 'parseinline' ), $wgLang->formatNum( $nitems ) ) . "\n\n" . wfMsg( 'watcheditlist' ) ); $wgOut->addHTML( '<form action=\'' . @@ -258,7 +257,8 @@ function wfSpecialWatchlist( $par ) { $andLatest=''; $limitWatchlist = 'LIMIT ' . intval( $wgUser->getOption( 'wllimit' ) ); } else { - $andLatest= 'AND rc_this_oldid=page_latest'; + # Top log Ids for a page are not stored + $andLatest= 'AND (rc_this_oldid=page_latest OR rc_type=' . RC_LOG . ') '; $limitWatchlist = ''; } @@ -299,10 +299,10 @@ function wfSpecialWatchlist( $par ) { $wgOut->addHTML( "<hr />\n" ); if($days >= 1) { - $wgOut->addWikiText( wfMsg( 'rcnote', $wgLang->formatNum( $numRows ), + $wgOut->addWikiText( wfMsgExt( 'rcnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), $wgLang->formatNum( $days ), $wgLang->timeAndDate( wfTimestampNow(), true ) ) . '<br />' , false ); } elseif($days > 0) { - $wgOut->addWikiText( wfMsg( 'wlnote', $wgLang->formatNum( $numRows ), + $wgOut->addWikiText( wfMsgExt( 'wlnote', array( 'parseinline' ), $wgLang->formatNum( $numRows ), $wgLang->formatNum( round($days*24) ) ) . '<br />' , false ); } @@ -353,6 +353,18 @@ function wfSpecialWatchlist( $par ) { /* End bottom header */ + /* Do link batch query */ + $linkBatch = new LinkBatch; + while ( $row = $dbr->fetchObject( $res ) ) { + $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); + if ( $row->rc_user != 0 ) { + $linkBatch->add( NS_USER, $userNameUnderscored ); + } + $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); + } + $linkBatch->execute(); + $dbr->dataSeek( $res, 0 ); + $list = ChangesList::newFromUser( $wgUser ); $s = $list->beginRecentChangesList(); @@ -435,7 +447,7 @@ function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { * @return integer */ function wlCountItems( &$user, $talk = true ) { - $dbr =& wfGetDB( DB_SLAVE, 'watchlist' ); + $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); # Fetch the raw count $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->mId ), 'wlCountItems' ); @@ -471,7 +483,7 @@ function wlHandleClear( &$out, &$request, $par ) { # See if we're clearing or confirming if( $request->wasPosted() && $wgUser->matchEditToken( $request->getText( 'token' ), 'clearwatchlist' ) ) { # Clearing, so do it and report the result - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'watchlist', array( 'wl_user' => $wgUser->mId ), 'wlHandleClear' ); $out->addWikiText( wfMsgExt( 'watchlistcleardone', array( 'parsemag', 'escape'), $wgLang->formatNum( $count ) ) ); $out->returnToMain(); diff --git a/includes/SpecialWhatlinkshere.php b/includes/SpecialWhatlinkshere.php index bed783f8..277e279f 100644 --- a/includes/SpecialWhatlinkshere.php +++ b/includes/SpecialWhatlinkshere.php @@ -1,8 +1,7 @@ <?php /** * - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ /** @@ -15,15 +14,21 @@ function wfSpecialWhatlinkshere($par = NULL) { $page->execute(); } +/** + * implements Special:Whatlinkshere + * @addtogroup SpecialPage + */ class WhatLinksHerePage { var $request, $par; - var $limit, $from, $dir, $target; + var $limit, $from, $back, $target; var $selfTitle, $skin; + private $namespace; + function WhatLinksHerePage( &$request, $par = null ) { global $wgUser; $this->request =& $request; - $this->skin =& $wgUser->getSkin(); + $this->skin = $wgUser->getSkin(); $this->par = $par; } @@ -35,10 +40,7 @@ class WhatLinksHerePage { $this->limit = 50; } $this->from = $this->request->getInt( 'from' ); - $this->dir = $this->request->getText( 'dir', 'next' ); - if ( $this->dir != 'prev' ) { - $this->dir = 'next'; - } + $this->back = $this->request->getInt( 'back' ); $targetString = isset($this->par) ? $this->par : $this->request->getVal( 'target' ); @@ -59,7 +61,7 @@ class WhatLinksHerePage { $wgOut->addHTML( wfMsg( 'whatlinkshere-barrow' ) . ' ' .$this->skin->makeLinkObj($this->target, '', 'redirect=no' )."<br />\n"); - $this->showIndirectLinks( 0, $this->target, $this->limit, $this->from, $this->dir ); + $this->showIndirectLinks( 0, $this->target, $this->limit, $this->from, $this->back ); } /** @@ -67,20 +69,21 @@ class WhatLinksHerePage { * @param Title $target Target title * @param int $limit Number of entries to display * @param Title $from Display from this article ID - * @param string $dir 'next' or 'prev', whether $fromTitle is the start or end of the list + * @param Title $back Display from this article ID at backwards scrolling * @private */ - function showIndirectLinks( $level, $target, $limit, $from = 0, $dir = 'next' ) { + function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) { global $wgOut; $fname = 'WhatLinksHerePage::showIndirectLinks'; + $dbr = wfGetDB( DB_READ ); + $options = array(); - $dbr =& wfGetDB( DB_READ ); - - // Some extra validation - $from = intval( $from ); - if ( !$from && $dir == 'prev' ) { - // Before start? No make sense - $dir = 'next'; + $ns = $this->request->getIntOrNull( 'namespace' ); + if ( isset( $ns ) ) { + $options['namespace'] = $ns; + $this->setNamespace( $options['namespace'] ); + } else { + $options['namespace'] = ''; } // Make the query @@ -96,18 +99,18 @@ class WhatLinksHerePage { 'tl_title' => $target->getDBkey(), ); + if ( $this->namespace !== null ){ + $plConds['page_namespace'] = (int)$this->namespace; + $tlConds['page_namespace'] = (int)$this->namespace; + } + if ( $from ) { - if ( 'prev' == $dir ) { - $offsetCond = "page_id < $from"; - $options = array( 'ORDER BY page_id DESC' ); - } else { - $offsetCond = "page_id >= $from"; - $options = array( 'ORDER BY page_id' ); - } + $offsetCond = "page_id >= $from"; } else { $offsetCond = false; - $options = array( 'ORDER BY page_id,is_template DESC' ); } + $options['ORDER BY'] = 'page_id'; + // Read an extra row as an at-end check $queryLimit = $limit + 1; $options['LIMIT'] = $queryLimit; @@ -121,14 +124,37 @@ class WhatLinksHerePage { $plConds, $fname, $options ); $tlRes = $dbr->select( array( 'templatelinks', 'page' ), $fields, $tlConds, $fname, $options ); - if ( !$dbr->numRows( $plRes ) && !$dbr->numRows( $tlRes ) ) { - if ( 0 == $level ) { + if ( 0 == $level && !isset( $this->namespace ) ) { + // really no links to here $wgOut->addWikiText( wfMsg( 'nolinkshere', $this->target->getPrefixedText() ) ); + } elseif ( 0 == $level && isset( $this->namespace ) ) { + // no links from requested namespace to here + $options = array(); // reinitialize for a further namespace search + $options['namespace'] = $this->namespace; + $options['target'] = $this->target->getPrefixedText(); + list( $options['limit'], $options['offset']) = wfCheckLimits(); + $wgOut->addHTML( $this->whatlinkshereForm( $options ) ); + $wgOut->addWikiText( wfMsg( 'nolinkshere-ns', $this->target->getPrefixedText() ) ); } return; } + $options = array(); + list( $options['limit'], $options['offset']) = wfCheckLimits(); + if ( ( $ns = $this->request->getVal( 'namespace', null ) ) !== null && $ns !== '' && ctype_digit($ns) ) { + $options['namespace'] = intval( $ns ); + $this->setNamespace( $options['namespace'] ); + } else { + $options['namespace'] = ''; + $this->setNamespace( null ); + } + $options['offset'] = $this->request->getVal( 'offset' ); + /* Offset must be an integral. */ + if ( !strlen( $options['offset'] ) || !preg_match( '/^[0-9]+$/', $options['offset'] ) ) + $options['offset'] = ''; + $options['target'] = $this->target->getPrefixedDBkey(); + // Read the rows into an array and remove duplicates // templatelinks comes second so that the templatelinks row overwrites the // pagelinks row, so we get (inclusion) rather than nothing @@ -150,46 +176,27 @@ class WhatLinksHerePage { $numRows = count( $rows ); // Work out the start and end IDs, for prev/next links - if ( $dir == 'prev' ) { - // Descending order - if ( $numRows > $limit ) { - // More rows available before these ones - // Get the ID from the next row past the end of the displayed set - $prevId = $rows[$limit]->page_id; - // Remove undisplayed rows - $rows = array_slice( $rows, 0, $limit ); - } else { - // No more rows available before - $prevId = 0; - } - // Assume that the ID specified in $from exists, so there must be another page - $nextId = $from; - - // Reverse order ready for display - $rows = array_reverse( $rows ); + if ( $numRows > $limit ) { + // More rows available after these ones + // Get the ID from the last row in the result set + $nextId = $rows[$limit]->page_id; + // Remove undisplayed rows + $rows = array_slice( $rows, 0, $limit ); } else { - // Ascending - if ( $numRows > $limit ) { - // More rows available after these ones - // Get the ID from the last row in the result set - $nextId = $rows[$limit]->page_id; - // Remove undisplayed rows - $rows = array_slice( $rows, 0, $limit ); - } else { - // No more rows after - $nextId = false; - } - $prevId = $from; + // No more rows after + $nextId = false; } + $prevId = $from; - if ( 0 == $level ) { + if ( $level == 0 ) { + $wgOut->addHTML( $this->whatlinkshereForm( $options ) ); $wgOut->addWikiText( wfMsg( 'linkshere', $this->target->getPrefixedText() ) ); } $isredir = wfMsg( 'isredirect' ); $istemplate = wfMsg( 'istemplate' ); if( $level == 0 ) { - $prevnext = $this->getPrevNext( $limit, $prevId, $nextId ); + $prevnext = $this->getPrevNext( $limit, $prevId, $nextId, $options['namespace'] ); $wgOut->addHTML( $prevnext ); } @@ -240,16 +247,21 @@ class WhatLinksHerePage { function getPrevNext( $limit, $prevId, $nextId ) { global $wgLang; $fmtLimit = $wgLang->formatNum( $limit ); - $prev = wfMsg( 'prevn', $fmtLimit ); - $next = wfMsg( 'nextn', $fmtLimit ); + $prev = wfMsgExt( 'whatlinkshere-prev', array( 'parsemag', 'escape' ), $fmtLimit ); + $next = wfMsgExt( 'whatlinkshere-next', array( 'parsemag', 'escape' ), $fmtLimit ); + + $nsText = ''; + if( is_int($this->namespace) ) { + $nsText = "&namespace={$this->namespace}"; + } if ( 0 != $prevId ) { - $prevLink = $this->makeSelfLink( $prev, "limit={$limit}&from={$prevId}&dir=prev" ); + $prevLink = $this->makeSelfLink( $prev, "limit={$limit}&from={$this->back}{$nsText}" ); } else { $prevLink = $prev; } if ( 0 != $nextId ) { - $nextLink = $this->makeSelfLink( $next, "limit={$limit}&from={$nextId}" ); + $nextLink = $this->makeSelfLink( $next, "limit={$limit}&from={$nextId}&back={$prevId}{$nsText}" ); } else { $nextLink = $next; } @@ -262,12 +274,42 @@ class WhatLinksHerePage { return wfMsg( 'viewprevnext', $prevLink, $nextLink, $nums ); } - function numLink( $limit, $from ) { + function numLink( $limit, $from, $ns = null ) { global $wgLang; $query = "limit={$limit}&from={$from}"; + if( is_int($this->namespace) ) { $query .= "&namespace={$this->namespace}";} $fmtLimit = $wgLang->formatNum( $limit ); return $this->makeSelfLink( $fmtLimit, $query ); } + + function whatlinkshereForm( $options ) { + global $wgScript, $wgTitle; + + $options['title'] = $wgTitle->getPrefixedText(); + + $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => "$wgScript" ) ) . + '<fieldset>' . + Xml::element( 'legend', array(), wfMsg( 'whatlinkshere' ) ); + + foreach ( $options as $name => $value ) { + if( $name === 'namespace') continue; + $f .= "\t" . Xml::hidden( $name, $value ). "\n"; + } + + $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $options['namespace'], '' ) . + Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . + '</fieldset>' . + Xml::closeElement( 'form' ) . "\n"; + + return $f; + } + + /** Set the namespace we are filtering on */ + private function setNamespace( $ns ) { + $this->namespace = $ns; + } + } ?> diff --git a/includes/SpecialWithoutinterwiki.php b/includes/SpecialWithoutinterwiki.php new file mode 100644 index 00000000..e5341d5d --- /dev/null +++ b/includes/SpecialWithoutinterwiki.php @@ -0,0 +1,56 @@ +<?php + +/** + * Special page lists pages without language links + * + * @package MediaWiki + * @addtogroup SpecialPage + * @author Rob Church <robchur@gmail.com> + */ +class WithoutInterwikiPage extends PageQueryPage { + + function getName() { + return 'Withoutinterwiki'; + } + + function getPageHeader() { + return '<p>' . wfMsgHtml( 'withoutinterwiki-header' ) . '</p>'; + } + + function sortDescending() { + return false; + } + + function isExpensive() { + return true; + } + + function isSyndicated() { + return false; + } + + function getSQL() { + $dbr = wfGetDB( DB_SLAVE ); + list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' ); + return + "SELECT 'Withoutinterwiki' AS type, + page_namespace AS namespace, + page_title AS title, + page_title AS value + FROM $page + LEFT JOIN $langlinks + ON ll_from = page_id + WHERE ll_title IS NULL + AND page_namespace=" . NS_MAIN . " + AND page_is_redirect = 0"; + } + +} + +function wfSpecialWithoutinterwiki() { + list( $limit, $offset ) = wfCheckLimits(); + $wip = new WithoutInterwikiPage(); + $wip->doQuery( $offset, $limit ); +} + +?> diff --git a/includes/SquidUpdate.php b/includes/SquidUpdate.php index 2e2a4a5d..700fc8ef 100644 --- a/includes/SquidUpdate.php +++ b/includes/SquidUpdate.php @@ -1,17 +1,15 @@ <?php /** * See deferred.txt - * @package MediaWiki */ /** * - * @package MediaWiki */ class SquidUpdate { var $urlArr, $mMaxTitles; - function SquidUpdate( $urlArr = Array(), $maxTitles = false ) { + function __construct( $urlArr = Array(), $maxTitles = false ) { global $wgMaxSquidPurgeTitles; if ( $maxTitles === false ) { $this->mMaxTitles = $wgMaxSquidPurgeTitles; @@ -29,7 +27,7 @@ class SquidUpdate { wfProfileIn( $fname ); # Get a list of URLs linking to this page - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( array( 'links', 'page' ), array( 'page_namespace', 'page_title' ), array( diff --git a/includes/StringUtils.php b/includes/StringUtils.php index 0090604d..9a451aa8 100644 --- a/includes/StringUtils.php +++ b/includes/StringUtils.php @@ -1,5 +1,7 @@ <?php - +/** + * A collection of static methods to play with strings. + */ class StringUtils { /** * Perform an operation equivalent to diff --git a/includes/StubObject.php b/includes/StubObject.php index 1501d963..894550cd 100644 --- a/includes/StubObject.php +++ b/includes/StubObject.php @@ -24,7 +24,7 @@ class StubObject { } static function isRealObject( $obj ) { - return is_object( $obj ) && !is_a( $obj, 'StubObject' ); + return is_object( $obj ) && !($obj instanceof StubObject); } function _call( $name, $args ) { @@ -35,7 +35,7 @@ class StubObject { function _newObject() { return wfCreateObject( $this->mClass, $this->mParams ); } - + function __call( $name, $args ) { return $this->_call( $name, $args ); } @@ -100,7 +100,8 @@ class StubUserLang extends StubObject { } # Validate $code - if( empty( $code ) || !preg_match( '/^[a-z]+(-[a-z]+)?$/', $code ) ) { + if( empty( $code ) || !preg_match( '/^[a-z-]+$/', $code ) ) { + wfDebug( "Invalid user language code\n" ); $code = $wgContLanguageCode; } diff --git a/includes/Title.php b/includes/Title.php index 56414c8a..0ff2e807 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -2,11 +2,12 @@ /** * See title.txt * - * @package MediaWiki */ /** */ -require_once( 'normal/UtfNormal.php' ); +if ( !class_exists( 'UtfNormal' ) ) { + require_once( dirname(__FILE__) . '/normal/UtfNormal.php' ); +} define ( 'GAID_FOR_UPDATE', 1 ); @@ -17,12 +18,14 @@ define ( 'GAID_FOR_UPDATE', 1 ); # reset the cache. define( 'MW_TITLECACHE_MAX', 1000 ); +# Constants for pr_cascade bitfield +define( 'CASCADE', 1 ); + /** * Title class * - Represents a title, which may contain an interwiki designation or namespace * - Can fetch various kinds of data from the database, albeit inefficiently. * - * @package MediaWiki */ class Title { /** @@ -41,21 +44,24 @@ class Title { * @private */ - var $mTextform; # Text form (spaces not underscores) of the main part - var $mUrlform; # URL-encoded form of the main part - var $mDbkeyform; # Main part with underscores - var $mNamespace; # Namespace index, i.e. one of the NS_xxxx constants - var $mInterwiki; # Interwiki prefix (or null string) - var $mFragment; # Title fragment (i.e. the bit after the #) - var $mArticleID; # Article ID, fetched from the link cache on demand - var $mLatestID; # ID of most recent revision - var $mRestrictions; # Array of groups allowed to edit this article - # Only null or "sysop" are supported - var $mRestrictionsLoaded; # Boolean for initialisation on demand - var $mPrefixedText; # Text form including namespace/interwiki, initialised on demand - var $mDefaultNamespace; # Namespace index when there is no namespace - # Zero except in {{transclusion}} tags - var $mWatched; # Is $wgUser watching this page? NULL if unfilled, accessed through userIsWatching() + var $mTextform; # Text form (spaces not underscores) of the main part + var $mUrlform; # URL-encoded form of the main part + var $mDbkeyform; # Main part with underscores + var $mNamespace; # Namespace index, i.e. one of the NS_xxxx constants + var $mInterwiki; # Interwiki prefix (or null string) + var $mFragment; # Title fragment (i.e. the bit after the #) + var $mArticleID; # Article ID, fetched from the link cache on demand + var $mLatestID; # ID of most recent revision + var $mRestrictions; # Array of groups allowed to edit this article + var $mCascadeRestriction; # Cascade restrictions on this page to included templates and images? + var $mRestrictionsExpiry; # When do the restrictions on this page expire? + var $mHasCascadingRestrictions; # Are cascading restrictions in effect on this page? + var $mCascadeRestrictionSources;# Where are the cascading restrictions coming from on this page? + var $mRestrictionsLoaded; # Boolean for initialisation on demand + var $mPrefixedText; # Text form including namespace/interwiki, initialised on demand + var $mDefaultNamespace; # Namespace index when there is no namespace + # Zero except in {{transclusion}} tags + var $mWatched; # Is $wgUser watching this page? NULL if unfilled, accessed through userIsWatching() /**#@-*/ @@ -63,7 +69,7 @@ class Title { * Constructor * @private */ - /* private */ function Title() { + /* private */ function __construct() { $this->mInterwiki = $this->mUrlform = $this->mTextform = $this->mDbkeyform = ''; $this->mArticleID = -1; @@ -75,6 +81,7 @@ class Title { $this->mDefaultNamespace = NS_MAIN; $this->mWatched = NULL; $this->mLatestID = false; + $this->mOldRestrictions = false; } /** @@ -83,10 +90,8 @@ class Title { * instead of spaces, possibly including namespace and * interwiki prefixes * @return Title the new object, or NULL on an error - * @static - * @access public */ - /* static */ function newFromDBkey( $key ) { + public static function newFromDBkey( $key ) { $t = new Title(); $t->mDbkeyform = $key; if( $t->secureAndSplit() ) @@ -105,8 +110,6 @@ class Title { * @param int $defaultNamespace the namespace to use if * none is specified by a prefix * @return Title the new object, or NULL on an error - * @static - * @access public */ public static function newFromText( $text, $defaultNamespace = NS_MAIN ) { if( is_object( $text ) ) { @@ -157,8 +160,6 @@ class Title { * the given title's length does not exceed the maximum. * @param string $url the title, as might be taken from a URL * @return Title the new object, or NULL on an error - * @static - * @access public */ public static function newFromURL( $url ) { global $wgLegalTitleChars; @@ -187,12 +188,10 @@ class Title { * * @param int $id the page_id corresponding to the Title to create * @return Title the new object, or NULL on an error - * @access public - * @static */ public static function newFromID( $id ) { $fname = 'Title::newFromID'; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'page', array( 'page_namespace', 'page_title' ), array( 'page_id' => $id ), $fname ); if ( $row !== false ) { @@ -206,8 +205,8 @@ class Title { /** * Make an array of titles from an array of IDs */ - function newFromIDs( $ids ) { - $dbr =& wfGetDB( DB_SLAVE ); + public static function newFromIDs( $ids ) { + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'page', array( 'page_namespace', 'page_title' ), 'page_id IN (' . $dbr->makeList( $ids ) . ')', __METHOD__ ); @@ -228,8 +227,6 @@ class Title { * @param int $ns the namespace of the article * @param string $title the unprefixed database key form * @return Title the new object - * @static - * @access public */ public static function &makeTitle( $ns, $title ) { $t = new Title(); @@ -251,8 +248,6 @@ class Title { * @param int $ns the namespace of the article * @param string $title the database key form * @return Title the new object, or NULL on an error - * @static - * @access public */ public static function makeTitleSafe( $ns, $title ) { $t = new Title(); @@ -266,10 +261,7 @@ class Title { /** * Create a new Title for the Main Page - * - * @static * @return Title the new object - * @access public */ public static function newMainPage() { return Title::newFromText( wfMsgForContent( 'mainpage' ) ); @@ -280,8 +272,6 @@ class Title { * @param string $text the redirect title text * @return Title the new object, or NULL if the text is not a * valid redirect - * @static - * @access public */ public static function newFromRedirect( $text ) { $mwRedir = MagicWord::get( 'redirect' ); @@ -320,7 +310,7 @@ class Title { */ function nameOf( $id ) { $fname = 'Title::nameOf'; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $s = $dbr->selectRow( 'page', array( 'page_namespace','page_title' ), array( 'page_id' => $id ), $fname ); if ( $s === false ) { return NULL; } @@ -332,8 +322,6 @@ class Title { /** * Get a regex character class describing the legal characters in a link * @return string the list of characters, not delimited - * @static - * @access public */ public static function legalChars() { global $wgLegalTitleChars; @@ -349,7 +337,7 @@ class Title { * @return string a stripped-down title string ready for the * search index */ - /* static */ function indexTitle( $ns, $title ) { + public static function indexTitle( $ns, $title ) { global $wgContLang; $lc = SearchEngine::legalSearchChars() . '&#;'; @@ -388,9 +376,8 @@ class Title { * @return the associated URL, containing "$1", which should be * replaced by an article title * @static (arguably) - * @access public */ - function getInterwikiLink( $key ) { + public function getInterwikiLink( $key ) { global $wgMemc, $wgInterwikiExpiry; global $wgInterwikiCache, $wgContLang; $fname = 'Title::getInterwikiLink'; @@ -413,7 +400,7 @@ class Title { return $s->iw_url; } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'interwiki', array( 'iw_url', 'iw_local', 'iw_trans' ), array( 'iw_prefix' => $key ), $fname ); @@ -441,9 +428,8 @@ class Title { * More logic is explained in DefaultSettings * * @return string URL of interwiki site - * @access public */ - function getInterwikiCached( $key ) { + public static function getInterwikiCached( $key ) { global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite; static $db, $site; @@ -484,9 +470,8 @@ class Title { * * @return bool TRUE if this is an in-project interwiki link * or a wikilink, FALSE otherwise - * @access public */ - function isLocal() { + public function isLocal() { if ( $this->mInterwiki != '' ) { # Make sure key is loaded into cache $this->getInterwikiLink( $this->mInterwiki ); @@ -502,9 +487,8 @@ class Title { * this project and is transcludable. * * @return bool TRUE if this is transcludable - * @access public */ - function isTrans() { + public function isTrans() { if ($this->mInterwiki == '') return false; # Make sure key is loaded into cache @@ -514,60 +498,6 @@ class Title { } /** - * Update the page_touched field for an array of title objects - * @todo Inefficient unless the IDs are already loaded into the - * link cache - * @param array $titles an array of Title objects to be touched - * @param string $timestamp the timestamp to use instead of the - * default current time - * @static - * @access public - */ - function touchArray( $titles, $timestamp = '' ) { - - if ( count( $titles ) == 0 ) { - return; - } - $dbw =& wfGetDB( DB_MASTER ); - if ( $timestamp == '' ) { - $timestamp = $dbw->timestamp(); - } - /* - $page = $dbw->tableName( 'page' ); - $sql = "UPDATE $page SET page_touched='{$timestamp}' WHERE page_id IN ("; - $first = true; - - foreach ( $titles as $title ) { - if ( $wgUseFileCache ) { - $cm = new HTMLFileCache($title); - @unlink($cm->fileCacheName()); - } - - if ( ! $first ) { - $sql .= ','; - } - $first = false; - $sql .= $title->getArticleID(); - } - $sql .= ')'; - if ( ! $first ) { - $dbw->query( $sql, 'Title::touchArray' ); - } - */ - // hack hack hack -- brion 2005-07-11. this was unfriendly to db. - // do them in small chunks: - $fname = 'Title::touchArray'; - foreach( $titles as $title ) { - $dbw->update( 'page', - array( 'page_touched' => $timestamp ), - array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBkey() ), - $fname ); - } - } - - /** * Escape a text fragment, say from a link, for a URL */ static function escapeFragmentForURL( $fragment ) { @@ -588,33 +518,28 @@ class Title { /** * Get the text form (spaces not underscores) of the main part * @return string - * @access public */ - function getText() { return $this->mTextform; } + public function getText() { return $this->mTextform; } /** * Get the URL-encoded form of the main part * @return string - * @access public */ - function getPartialURL() { return $this->mUrlform; } + public function getPartialURL() { return $this->mUrlform; } /** * Get the main part with underscores * @return string - * @access public */ - function getDBkey() { return $this->mDbkeyform; } + public function getDBkey() { return $this->mDbkeyform; } /** * Get the namespace index, i.e. one of the NS_xxxx constants * @return int - * @access public */ - function getNamespace() { return $this->mNamespace; } + public function getNamespace() { return $this->mNamespace; } /** * Get the namespace text * @return string - * @access public */ - function getNsText() { + public function getNsText() { global $wgContLang, $wgCanonicalNamespaceNames; if ( '' != $this->mInterwiki ) { @@ -633,9 +558,8 @@ class Title { /** * Get the namespace text of the subject (rather than talk) page * @return string - * @access public */ - function getSubjectNsText() { + public function getSubjectNsText() { global $wgContLang; return $wgContLang->getNsText( Namespace::getSubject( $this->mNamespace ) ); } @@ -644,38 +568,34 @@ class Title { * Get the namespace text of the talk page * @return string */ - function getTalkNsText() { + public function getTalkNsText() { global $wgContLang; return( $wgContLang->getNsText( Namespace::getTalk( $this->mNamespace ) ) ); } - + /** * Could this title have a corresponding talk page? * @return bool */ - function canTalk() { + public function canTalk() { return( Namespace::canTalk( $this->mNamespace ) ); } - + /** * Get the interwiki prefix (or null string) * @return string - * @access public */ - function getInterwiki() { return $this->mInterwiki; } + public function getInterwiki() { return $this->mInterwiki; } /** * Get the Title fragment (i.e. the bit after the #) in text form * @return string - * @access public */ - function getFragment() { return $this->mFragment; } + public function getFragment() { return $this->mFragment; } /** * Get the fragment in URL form, including the "#" character if there is one - * * @return string - * @access public */ - function getFragmentForURL() { + public function getFragmentForURL() { if ( $this->mFragment == '' ) { return ''; } else { @@ -685,16 +605,15 @@ class Title { /** * Get the default namespace index, for when there is no namespace * @return int - * @access public */ - function getDefaultNamespace() { return $this->mDefaultNamespace; } + public function getDefaultNamespace() { return $this->mDefaultNamespace; } /** * Get title for search index * @return string a stripped-down title string ready for the * search index */ - function getIndexTitle() { + public function getIndexTitle() { return Title::indexTitle( $this->mNamespace, $this->mTextform ); } @@ -702,9 +621,8 @@ class Title { * Get the prefixed database key form * @return string the prefixed title, with underscores and * any interwiki and namespace prefixes - * @access public */ - function getPrefixedDBkey() { + public function getPrefixedDBkey() { $s = $this->prefix( $this->mDbkeyform ); $s = str_replace( ' ', '_', $s ); return $s; @@ -714,9 +632,8 @@ class Title { * Get the prefixed title with spaces. * This is the form usually used for display * @return string the prefixed title, with spaces - * @access public */ - function getPrefixedText() { + public function getPrefixedText() { if ( empty( $this->mPrefixedText ) ) { // FIXME: bad usage of empty() ? $s = $this->prefix( $this->mTextform ); $s = str_replace( '_', ' ', $s ); @@ -730,9 +647,8 @@ class Title { * (part beginning with '#') * @return string the prefixed title, with spaces and * the fragment, including '#' - * @access public */ - function getFullText() { + public function getFullText() { $text = $this->getPrefixedText(); if( '' != $this->mFragment ) { $text .= '#' . $this->mFragment; @@ -744,7 +660,7 @@ class Title { * Get the base name, i.e. the leftmost parts before the / * @return string Base name */ - function getBaseText() { + public function getBaseText() { global $wgNamespacesWithSubpages; if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) && $wgNamespacesWithSubpages[ $this->mNamespace ] ) { $parts = explode( '/', $this->getText() ); @@ -761,7 +677,7 @@ class Title { * Get the lowest-level subpage name, i.e. the rightmost part after / * @return string Subpage name */ - function getSubpageText() { + public function getSubpageText() { global $wgNamespacesWithSubpages; if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) && $wgNamespacesWithSubpages[ $this->mNamespace ] ) { $parts = explode( '/', $this->mTextform ); @@ -770,12 +686,12 @@ class Title { return( $this->mTextform ); } } - + /** * Get a URL-encoded form of the subpage text * @return string URL-encoded subpage name */ - function getSubpageUrlForm() { + public function getSubpageUrlForm() { $text = $this->getSubpageText(); $text = wfUrlencode( str_replace( ' ', '_', $text ) ); $text = str_replace( '%28', '(', str_replace( '%29', ')', $text ) ); # Clean up the URL; per below, this might not be safe @@ -785,9 +701,8 @@ class Title { /** * Get a URL-encoded title (not an actual URL) including interwiki * @return string the URL-encoded form - * @access public */ - function getPrefixedURL() { + public function getPrefixedURL() { $s = $this->prefix( $this->mDbkeyform ); $s = str_replace( ' ', '_', $s ); @@ -808,9 +723,8 @@ class Title { * for interwiki links * @param string $variant language variant of url (for sr, zh..) * @return string the URL - * @access public */ - function getFullURL( $query = '', $variant = false ) { + public function getFullURL( $query = '', $variant = false ) { global $wgContLang, $wgServer, $wgRequest; if ( '' == $this->mInterwiki ) { @@ -831,14 +745,7 @@ class Title { $namespace .= ':'; } $url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl ); - if( $query != '' ) { - if( false === strpos( $url, '?' ) ) { - $url .= '?'; - } else { - $url .= '&'; - } - $url .= $query; - } + $url = wfAppendQuery( $url, $query ); } # Finally, add the fragment. @@ -855,9 +762,8 @@ class Title { * $wgArticlePath will be used. * @param string $variant language variant of url (for sr, zh..) * @return string the URL - * @access public */ - function getLocalURL( $query = '', $variant = false ) { + public function getLocalURL( $query = '', $variant = false ) { global $wgArticlePath, $wgScript, $wgServer, $wgRequest; global $wgVariantArticlePath, $wgContLang, $wgUser; @@ -881,17 +787,17 @@ class Title { $dbkey = wfUrlencode( $this->getPrefixedDBkey() ); if ( $query == '' ) { if($variant!=false && $wgContLang->hasVariants()){ - if($wgVariantArticlePath==false) + if($wgVariantArticlePath==false) { $variantArticlePath = "$wgScript?title=$1&variant=$2"; // default - else + } else { $variantArticlePath = $wgVariantArticlePath; - + } $url = str_replace( '$2', urlencode( $variant ), $variantArticlePath ); $url = str_replace( '$1', $dbkey, $url ); - } - else + else { $url = str_replace( '$1', $dbkey, $wgArticlePath ); + } } else { global $wgActionPaths; $url = false; @@ -930,9 +836,8 @@ class Title { * using in a link, without a server name or fragment * @param string $query an optional query string * @return string the URL - * @access public */ - function escapeLocalURL( $query = '' ) { + public function escapeLocalURL( $query = '' ) { return htmlspecialchars( $this->getLocalURL( $query ) ); } @@ -942,9 +847,8 @@ class Title { * * @return string the URL * @param string $query an optional query string - * @access public */ - function escapeFullURL( $query = '' ) { + public function escapeFullURL( $query = '' ) { return htmlspecialchars( $this->getFullURL( $query ) ); } @@ -956,9 +860,8 @@ class Title { * @param string $query an optional query string * @param string $variant language variant of url (for sr, zh..) * @return string the URL - * @access public */ - function getInternalURL( $query = '', $variant = false ) { + public function getInternalURL( $query = '', $variant = false ) { global $wgInternalServer; $url = $wgInternalServer . $this->getLocalURL( $query, $variant ); wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) ); @@ -969,9 +872,8 @@ class Title { * Get the edit URL for this Title * @return string the URL, or a null string if this is an * interwiki link - * @access public */ - function getEditURL() { + public function getEditURL() { if ( '' != $this->mInterwiki ) { return ''; } $s = $this->getLocalURL( 'action=edit' ); @@ -982,18 +884,16 @@ class Title { * Get the HTML-escaped displayable text form. * Used for the title field in <a> tags. * @return string the text, including any prefixes - * @access public */ - function getEscapedText() { + public function getEscapedText() { return htmlspecialchars( $this->getPrefixedText() ); } /** * Is this Title interwiki? * @return boolean - * @access public */ - function isExternal() { return ( '' != $this->mInterwiki ); } + public function isExternal() { return ( '' != $this->mInterwiki ); } /** * Is this page "semi-protected" - the *only* protection is autoconfirm? @@ -1001,7 +901,7 @@ class Title { * @param string Action to check (default: edit) * @return bool */ - function isSemiProtected( $action = 'edit' ) { + public function isSemiProtected( $action = 'edit' ) { if( $this->exists() ) { $restrictions = $this->getRestrictions( $action ); if( count( $restrictions ) > 0 ) { @@ -1025,12 +925,15 @@ class Title { * @param string $what the action the page is protected from, * by default checks move and edit * @return boolean - * @access public */ - function isProtected( $action = '' ) { + public function isProtected( $action = '' ) { global $wgRestrictionLevels; - if ( NS_SPECIAL == $this->mNamespace ) { return true; } - + + # Special pages have inherent protection + if( $this->getNamespace() == NS_SPECIAL ) + return true; + + # Check regular protection levels if( $action == 'edit' || $action == '' ) { $r = $this->getRestrictions( 'edit' ); foreach( $wgRestrictionLevels as $level ) { @@ -1055,9 +958,8 @@ class Title { /** * Is $wgUser is watching this page? * @return boolean - * @access public */ - function userIsWatching() { + public function userIsWatching() { global $wgUser; if ( is_null( $this->mWatched ) ) { @@ -1071,16 +973,32 @@ class Title { } /** - * Can $wgUser perform $action this page? + * Can $wgUser perform $action on this page? + * This skips potentially expensive cascading permission checks. + * + * Suitable for use for nonessential UI controls in common cases, but + * _not_ for functional access control. + * + * May provide false positives, but should never provide a false negative. + * * @param string $action action that permission needs to be checked for * @return boolean - * @private */ - function userCan($action) { + public function quickUserCan( $action ) { + return $this->userCan( $action, false ); + } + + /** + * Can $wgUser perform $action on this page? + * @param string $action action that permission needs to be checked for + * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries. + * @return boolean + */ + public function userCan( $action, $doExpensiveQueries = true ) { $fname = 'Title::userCan'; wfProfileIn( $fname ); - global $wgUser; + global $wgUser, $wgNamespaceProtection; $result = null; wfRunHooks( 'userCan', array( &$this, &$wgUser, $action, &$result ) ); @@ -1093,12 +1011,16 @@ class Title { wfProfileOut( $fname ); return false; } - // XXX: This is the code that prevents unprotecting a page in NS_MEDIAWIKI - // from taking effect -ævar - if( NS_MEDIAWIKI == $this->mNamespace && - !$wgUser->isAllowed('editinterface') ) { - wfProfileOut( $fname ); - return false; + + if ( array_key_exists( $this->mNamespace, $wgNamespaceProtection ) ) { + $nsProt = $wgNamespaceProtection[ $this->mNamespace ]; + if ( !is_array($nsProt) ) $nsProt = array($nsProt); + foreach( $nsProt as $right ) { + if( '' != $right && !$wgUser->isAllowed( $right ) ) { + wfProfileOut( $fname ); + return false; + } + } } if( $this->mDbkeyform == '_' ) { @@ -1116,7 +1038,28 @@ class Title { wfProfileOut( $fname ); return false; } - + + if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) { + # We /could/ use the protection level on the source page, but it's fairly ugly + # as we have to establish a precedence hierarchy for pages included by multiple + # cascade-protected pages. So just restrict it to people with 'protect' permission, + # as they could remove the protection anyway. + list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources(); + # Cascading protection depends on more than this page... + # Several cascading protected pages may include this page... + # Check each cascading level + # This is only for protection restrictions, not for all actions + if( $cascadingSources > 0 && isset($restrictions[$action]) ) { + foreach( $restrictions[$action] as $right ) { + $right = ( $right == 'sysop' ) ? 'protect' : $right; + if( '' != $right && !$wgUser->isAllowed( $right ) ) { + wfProfileOut( $fname ); + return false; + } + } + } + } + foreach( $this->getRestrictions($action) as $right ) { // Backwards compatibility, rewrite sysop -> protect if ( $right == 'sysop' ) { @@ -1149,28 +1092,28 @@ class Title { /** * Can $wgUser edit this page? * @return boolean - * @access public + * @deprecated use userCan('edit') */ - function userCanEdit() { - return $this->userCan('edit'); + public function userCanEdit( $doExpensiveQueries = true ) { + return $this->userCan( 'edit', $doExpensiveQueries ); } /** * Can $wgUser create this page? * @return boolean - * @access public + * @deprecated use userCan('create') */ - function userCanCreate() { - return $this->userCan('create'); + public function userCanCreate( $doExpensiveQueries = true ) { + return $this->userCan( 'create', $doExpensiveQueries ); } /** * Can $wgUser move this page? * @return boolean - * @access public + * @deprecated use userCan('move') */ - function userCanMove() { - return $this->userCan('move'); + public function userCanMove( $doExpensiveQueries = true ) { + return $this->userCan( 'move', $doExpensiveQueries ); } /** @@ -1178,9 +1121,8 @@ class Title { * Some pages just aren't movable. * * @return boolean - * @access public */ - function isMovable() { + public function isMovable() { return Namespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == ''; } @@ -1188,9 +1130,9 @@ class Title { /** * Can $wgUser read this page? * @return boolean - * @access public + * @todo fold these checks into userCan() */ - function userCanRead() { + public function userCanRead() { global $wgUser; $result = null; @@ -1231,18 +1173,16 @@ class Title { /** * Is this a talk page of some sort? * @return bool - * @access public */ - function isTalkPage() { + public function isTalkPage() { return Namespace::isTalk( $this->getNamespace() ); } /** * Is this a subpage? * @return bool - * @access public */ - function isSubpage() { + public function isSubpage() { global $wgNamespacesWithSubpages; if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) ) { @@ -1255,16 +1195,15 @@ class Title { /** * Is this a .css or .js subpage of a user page? * @return bool - * @access public */ - function isCssJsSubpage() { - return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.(css|js)$/", $this->mTextform ) ); + public function isCssJsSubpage() { + return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.(?:css|js)$/", $this->mTextform ) ); } /** * Is this a *valid* .css or .js subpage of a user page? * Check that the corresponding skin exists */ - function isValidCssJsSubpage() { + public function isValidCssJsSubpage() { if ( $this->isCssJsSubpage() ) { $skinNames = Skin::getSkinNames(); return array_key_exists( $this->getSkinFromCssJsSubpage(), $skinNames ); @@ -1275,7 +1214,7 @@ class Title { /** * Trim down a .css or .js subpage title to get the corresponding skin name */ - function getSkinFromCssJsSubpage() { + public function getSkinFromCssJsSubpage() { $subpage = explode( '/', $this->mTextform ); $subpage = $subpage[ count( $subpage ) - 1 ]; return( str_replace( array( '.css', '.js' ), array( '', '' ), $subpage ) ); @@ -1283,17 +1222,15 @@ class Title { /** * Is this a .css subpage of a user page? * @return bool - * @access public */ - function isCssSubpage() { + public function isCssSubpage() { return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.css$/", $this->mTextform ) ); } /** * Is this a .js subpage of a user page? * @return bool - * @access public */ - function isJsSubpage() { + public function isJsSubpage() { return ( NS_USER == $this->mNamespace and preg_match("/\\/.*\\.js$/", $this->mTextform ) ); } /** @@ -1302,54 +1239,223 @@ class Title { * * @return boolean * @todo XXX: this might be better using restrictions - * @access public */ - function userCanEditCssJsSubpage() { + public function userCanEditCssJsSubpage() { global $wgUser; return ( $wgUser->isAllowed('editinterface') or preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) ); } /** + * Cascading protection: Return true if cascading restrictions apply to this page, false if not. + * + * @return bool If the page is subject to cascading restrictions. + */ + public function isCascadeProtected() { + list( $sources, $restrictions ) = $this->getCascadeProtectionSources( false ); + return ( $sources > 0 ); + } + + /** + * Cascading protection: Get the source of any cascading restrictions on this page. + * + * @param $get_pages bool Whether or not to retrieve the actual pages that the restrictions have come from. + * @return array( mixed title array, restriction array) + * Array of the Title objects of the pages from which cascading restrictions have come, false for none, or true if such restrictions exist, but $get_pages was not set. + * The restriction array is an array of each type, each of which contains an array of unique groups + */ + public function getCascadeProtectionSources( $get_pages = true ) { + global $wgEnableCascadingProtection, $wgRestrictionTypes; + + # Define our dimension of restrictions types + $pagerestrictions = array(); + foreach( $wgRestrictionTypes as $action ) + $pagerestrictions[$action] = array(); + + if (!$wgEnableCascadingProtection) + return array( false, $pagerestrictions ); + + if ( isset( $this->mCascadeSources ) && $get_pages ) { + return array( $this->mCascadeSources, $this->mCascadingRestrictions ); + } else if ( isset( $this->mHasCascadingRestrictions ) && !$get_pages ) { + return array( $this->mHasCascadingRestrictions, $pagerestrictions ); + } + + wfProfileIn( __METHOD__ ); + + $dbr = wfGetDb( DB_SLAVE ); + + if ( $this->getNamespace() == NS_IMAGE ) { + $tables = array ('imagelinks', 'page_restrictions'); + $where_clauses = array( + 'il_to' => $this->getDBkey(), + 'il_from=pr_page', + 'pr_cascade' => 1 ); + } else { + $tables = array ('templatelinks', 'page_restrictions'); + $where_clauses = array( + 'tl_namespace' => $this->getNamespace(), + 'tl_title' => $this->getDBkey(), + 'tl_from=pr_page', + 'pr_cascade' => 1 ); + } + + if ( $get_pages ) { + $cols = array('pr_page', 'page_namespace', 'page_title', 'pr_expiry', 'pr_type', 'pr_level' ); + $where_clauses[] = 'page_id=pr_page'; + $tables[] = 'page'; + } else { + $cols = array( 'pr_expiry' ); + } + + $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ ); + + $sources = $get_pages ? array() : false; + $now = wfTimestampNow(); + $purgeExpired = false; + + while( $row = $dbr->fetchObject( $res ) ) { + $expiry = Block::decodeExpiry( $row->pr_expiry ); + if( $expiry > $now ) { + if ($get_pages) { + $page_id = $row->pr_page; + $page_ns = $row->page_namespace; + $page_title = $row->page_title; + $sources[$page_id] = Title::makeTitle($page_ns, $page_title); + # Add groups needed for each restriction type if its not already there + # Make sure this restriction type still exists + if ( isset($pagerestrictions[$row->pr_type]) && !in_array($row->pr_level, $pagerestrictions[$row->pr_type]) ) { + $pagerestrictions[$row->pr_type][]=$row->pr_level; + } + } else { + $sources = true; + } + } else { + // Trigger lazy purge of expired restrictions from the db + $purgeExpired = true; + } + } + if( $purgeExpired ) { + Title::purgeExpiredRestrictions(); + } + + wfProfileOut( __METHOD__ ); + + if ( $get_pages ) { + $this->mCascadeSources = $sources; + $this->mCascadingRestrictions = $pagerestrictions; + } else { + $this->mHasCascadingRestrictions = $sources; + } + + return array( $sources, $pagerestrictions ); + } + + function areRestrictionsCascading() { + if (!$this->mRestrictionsLoaded) { + $this->loadRestrictions(); + } + + return $this->mCascadeRestriction; + } + + /** * Loads a string into mRestrictions array - * @param string $res restrictions in string format - * @access public + * @param resource $res restrictions as an SQL result. */ - function loadRestrictions( $res ) { + private function loadRestrictionsFromRow( $res, $oldFashionedRestrictions = NULL ) { + $dbr = wfGetDb( DB_SLAVE ); + $this->mRestrictions['edit'] = array(); $this->mRestrictions['move'] = array(); - - if( !$res ) { - # No restrictions (page_restrictions blank) - $this->mRestrictionsLoaded = true; - return; + + # Backwards-compatibility: also load the restrictions from the page record (old format). + + if ( $oldFashionedRestrictions == NULL ) { + $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions', array( 'page_id' => $this->getArticleId() ), __METHOD__ ); } - - foreach( explode( ':', trim( $res ) ) as $restrict ) { - $temp = explode( '=', trim( $restrict ) ); - if(count($temp) == 1) { - // old format should be treated as edit/move restriction - $this->mRestrictions["edit"] = explode( ',', trim( $temp[0] ) ); - $this->mRestrictions["move"] = explode( ',', trim( $temp[0] ) ); - } else { - $this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) ); + + if ($oldFashionedRestrictions != '') { + + foreach( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) { + $temp = explode( '=', trim( $restrict ) ); + if(count($temp) == 1) { + // old old format should be treated as edit/move restriction + $this->mRestrictions["edit"] = explode( ',', trim( $temp[0] ) ); + $this->mRestrictions["move"] = explode( ',', trim( $temp[0] ) ); + } else { + $this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) ); + } + } + + $this->mOldRestrictions = true; + $this->mCascadeRestriction = false; + $this->mRestrictionsExpiry = Block::decodeExpiry(''); + + } + + if( $dbr->numRows( $res ) ) { + # Current system - load second to make them override. + $now = wfTimestampNow(); + $purgeExpired = false; + + while ($row = $dbr->fetchObject( $res ) ) { + # Cycle through all the restrictions. + + // This code should be refactored, now that it's being used more generally, + // But I don't really see any harm in leaving it in Block for now -werdna + $expiry = Block::decodeExpiry( $row->pr_expiry ); + + // Only apply the restrictions if they haven't expired! + if ( !$expiry || $expiry > $now ) { + $this->mRestrictionsExpiry = $expiry; + $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) ); + + $this->mCascadeRestriction |= $row->pr_cascade; + } else { + // Trigger a lazy purge of expired restrictions + $purgeExpired = true; + } + } + + if( $purgeExpired ) { + Title::purgeExpiredRestrictions(); } } + $this->mRestrictionsLoaded = true; } + public function loadRestrictions( $oldFashionedRestrictions = NULL ) { + if( !$this->mRestrictionsLoaded ) { + $dbr = wfGetDB( DB_SLAVE ); + + $res = $dbr->select( 'page_restrictions', '*', + array ( 'pr_page' => $this->getArticleId() ), __METHOD__ ); + + $this->loadRestrictionsFromRow( $res, $oldFashionedRestrictions ); + } + } + + /** + * Purge expired restrictions from the page_restrictions table + */ + static function purgeExpiredRestrictions() { + $dbw = wfGetDB( DB_MASTER ); + $dbw->delete( 'page_restrictions', + array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), + __METHOD__ ); + } + /** * Accessor/initialisation for mRestrictions * - * @access public * @param string $action action that permission needs to be checked for * @return array the array of groups allowed to edit this article */ - function getRestrictions( $action ) { + public function getRestrictions( $action ) { if( $this->exists() ) { if( !$this->mRestrictionsLoaded ) { - $dbr =& wfGetDB( DB_SLAVE ); - $res = $dbr->selectField( 'page', 'page_restrictions', array( 'page_id' => $this->getArticleId() ) ); - $this->loadRestrictions( $res ); + $this->loadRestrictions(); } return isset( $this->mRestrictions[$action] ) ? $this->mRestrictions[$action] @@ -1362,14 +1468,13 @@ class Title { /** * Is there a version of this page in the deletion archive? * @return int the number of archived revisions - * @access public */ - function isDeleted() { + public function isDeleted() { $fname = 'Title::isDeleted'; if ( $this->getNamespace() < 0 ) { $n = 0; } else { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), $fname ); if( $this->getNamespace() == NS_IMAGE ) { @@ -1386,9 +1491,8 @@ class Title { * @param int $flags a bit field; may be GAID_FOR_UPDATE to select * for update * @return int the ID - * @access public */ - function getArticleID( $flags = 0 ) { + public function getArticleID( $flags = 0 ) { $linkCache =& LinkCache::singleton(); if ( $flags & GAID_FOR_UPDATE ) { $oldUpdate = $linkCache->forUpdate( true ); @@ -1402,11 +1506,11 @@ class Title { return $this->mArticleID; } - function getLatestRevID() { + public function getLatestRevID() { if ($this->mLatestID !== false) return $this->mLatestID; - $db =& wfGetDB(DB_SLAVE); + $db = wfGetDB(DB_SLAVE); return $this->mLatestID = $db->selectField( 'revision', "max(rev_id)", array('rev_page' => $this->getArticleID()), @@ -1422,9 +1526,8 @@ class Title { * Article::doDeleteArticle() * * @param int $newid the new Article ID - * @access public */ - function resetArticleID( $newid ) { + public function resetArticleID( $newid ) { $linkCache =& LinkCache::singleton(); $linkCache->clearBadLink( $this->getPrefixedDBkey() ); @@ -1437,16 +1540,15 @@ class Title { /** * Updates page_touched for this page; called from LinksUpdate.php * @return bool true if the update succeded - * @access public */ - function invalidateCache() { + public function invalidateCache() { global $wgUseFileCache; if ( wfReadOnly() ) { return; } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $success = $dbw->update( 'page', array( /* SET */ 'page_touched' => $dbw->timestamp() @@ -1492,9 +1594,8 @@ class Title { * namespace prefixes, sets the other forms, and canonicalizes * everything. * @return bool true on success - * @private */ - /* private */ function secureAndSplit() { + private function secureAndSplit() { global $wgContLang, $wgLocalInterwiki, $wgCapitalLinks; # Initialisation @@ -1536,6 +1637,7 @@ class Title { if ( ':' == $dbkey{0} ) { $this->mNamespace = NS_MAIN; $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing + $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace } # Namespace or interwiki prefix @@ -1544,12 +1646,7 @@ class Title { $m = array(); if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) { $p = $m[1]; - $lowerNs = $wgContLang->lc( $p ); - if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) { - # Canonical namespace - $dbkey = $m[2]; - $this->mNamespace = $ns; - } elseif ( $ns = $wgContLang->getNsIndex( $lowerNs )) { + if ( $ns = $wgContLang->getNsIndex( $p )) { # Ordinary namespace $dbkey = $m[2]; $this->mNamespace = $ns; @@ -1623,6 +1720,13 @@ class Title { { return false; } + + /** + * Magic tilde sequences? Nu-uh! + */ + if( strpos( $dbkey, '~~~' ) !== false ) { + return false; + } /** * Limit the size of titles to 255 bytes. @@ -1681,18 +1785,17 @@ class Title { * members directly, which is what Linker::formatComment was doing previously. * * @param string $fragment text - * @access kind of public + * @todo clarify whether access is supposed to be public (was marked as "kind of public") */ - function setFragment( $fragment ) { + public function setFragment( $fragment ) { $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) ); } /** * Get a Title object associated with the talk page of this article * @return Title the object for the talk page - * @access public */ - function getTalkPage() { + public function getTalkPage() { return Title::makeTitle( Namespace::getTalk( $this->getNamespace() ), $this->getDBkey() ); } @@ -1701,9 +1804,8 @@ class Title { * talk page * * @return Title the object for the subject page - * @access public */ - function getSubjectPage() { + public function getSubjectPage() { return Title::makeTitle( Namespace::getSubject( $this->getNamespace() ), $this->getDBkey() ); } @@ -1716,15 +1818,14 @@ class Title { * * @param string $options may be FOR UPDATE * @return array the Title objects linking here - * @access public */ - function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) { + public function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) { $linkCache =& LinkCache::singleton(); if ( $options ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); } $res = $db->select( array( 'page', $table ), @@ -1758,9 +1859,8 @@ class Title { * * @param string $options may be FOR UPDATE * @return array the Title objects linking here - * @access public */ - function getTemplateLinksTo( $options = '' ) { + public function getTemplateLinksTo( $options = '' ) { return $this->getLinksTo( $options, 'templatelinks', 'tl' ); } @@ -1769,13 +1869,12 @@ class Title { * * @param string $options may be FOR UPDATE * @return array the Title objects - * @access public */ - function getBrokenLinksFrom( $options = '' ) { + public function getBrokenLinksFrom( $options = '' ) { if ( $options ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); } $res = $db->safeQuery( @@ -1808,9 +1907,8 @@ class Title { * page changes * * @return array the URLs - * @access public */ - function getSquidURLs() { + public function getSquidURLs() { global $wgContLang; $urls = array( @@ -1830,7 +1928,7 @@ class Title { return $urls; } - function purgeSquid() { + public function purgeSquid() { global $wgUseSquid; if ( $wgUseSquid ) { $urls = $this->getSquidURLs(); @@ -1842,9 +1940,8 @@ class Title { /** * Move this page without authentication * @param Title &$nt the new page Title - * @access public */ - function moveNoAuth( &$nt ) { + public function moveNoAuth( &$nt ) { return $this->moveTo( $nt, false ); } @@ -1856,9 +1953,8 @@ class Title { * @param bool $auth indicates whether $wgUser's permissions * should be checked * @return mixed true on success, message name on failure - * @access public */ - function isValidMoveOperation( &$nt, $auth = true ) { + public function isValidMoveOperation( &$nt, $auth = true ) { if( !$this or !$nt ) { return 'badtitletext'; } @@ -1882,8 +1978,8 @@ class Title { } if ( $auth && ( - !$this->userCanEdit() || !$nt->userCanEdit() || - !$this->userCanMove() || !$nt->userCanMove() ) ) { + !$this->userCan( 'edit' ) || !$nt->userCan( 'edit' ) || + !$this->userCan( 'move' ) || !$nt->userCan( 'move' ) ) ) { return 'protectedpage'; } @@ -1905,9 +2001,8 @@ class Title { * @param bool $auth indicates whether $wgUser's permissions * should be checked * @return mixed true on success, message name on failure - * @access public */ - function moveTo( &$nt, $auth = true, $reason = '' ) { + public function moveTo( &$nt, $auth = true, $reason = '' ) { $err = $this->isValidMoveOperation( $nt, $auth ); if( is_string( $err ) ) { return $err; @@ -1924,7 +2019,7 @@ class Title { $redirid = $this->getArticleID(); # Fixing category links (those without piped 'alternate' names) to be sorted under the new title - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $categorylinks = $dbw->tableName( 'categorylinks' ); $sql = "UPDATE $categorylinks SET cl_sortkey=" . $dbw->addQuotes( $nt->getPrefixedText() ) . " WHERE cl_from=" . $dbw->addQuotes( $pageid ) . @@ -1949,24 +2044,24 @@ class Title { $u->doUpdate(); # Update site_stats - if ( $this->getNamespace() == NS_MAIN and $nt->getNamespace() != NS_MAIN ) { - # Moved out of main namespace - # not viewed, edited, removing - $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange); - } elseif ( $this->getNamespace() != NS_MAIN and $nt->getNamespace() == NS_MAIN ) { - # Moved into main namespace - # not viewed, edited, adding + if( $this->isContentPage() && !$nt->isContentPage() ) { + # No longer a content page + # Not viewed, edited, removing + $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange ); + } elseif( !$this->isContentPage() && $nt->isContentPage() ) { + # Now a content page + # Not viewed, edited, adding $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange ); - } elseif ( $pageCountChange ) { - # Added redirect + } elseif( $pageCountChange ) { + # Redirect added $u = new SiteStatsUpdate( 0, 0, 0, 1 ); - } else{ + } else { + # Nothing special $u = false; } - if ( $u ) { + if( $u ) $u->doUpdate(); - } - + global $wgUser; wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) ); return true; @@ -1978,12 +2073,11 @@ class Title { * * @param Title &$nt the page to move to, which should currently * be a redirect - * @private */ - function moveOverExistingRedirect( &$nt, $reason = '' ) { + private function moveOverExistingRedirect( &$nt, $reason = '' ) { global $wgUseSquid; $fname = 'Title::moveOverExistingRedirect'; - $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); + $comment = wfMsgForContent( '1movedto2_redir', $this->getPrefixedText(), $nt->getPrefixedText() ); if ( $reason ) { $comment .= ": $reason"; @@ -1992,7 +2086,7 @@ class Title { $now = wfTimestampNow(); $newid = $nt->getArticleID(); $oldid = $this->getArticleID(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $linkCache =& LinkCache::singleton(); # Delete the old redirect. We don't save it to history since @@ -2056,9 +2150,8 @@ class Title { /** * Move page to non-existing title. * @param Title &$nt the new Title - * @private */ - function moveToNewTitle( &$nt, $reason = '' ) { + private function moveToNewTitle( &$nt, $reason = '' ) { global $wgUseSquid; $fname = 'MovePageForm::moveToNewTitle'; $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); @@ -2068,7 +2161,7 @@ class Title { $newid = $nt->getArticleID(); $oldid = $this->getArticleID(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $now = $dbw->timestamp(); $linkCache =& LinkCache::singleton(); @@ -2128,12 +2221,11 @@ class Title { * - Selects for update, so don't call it unless you mean business * * @param Title &$nt the new title to check - * @access public */ - function isValidMoveTarget( $nt ) { + public function isValidMoveTarget( $nt ) { $fname = 'Title::isValidMoveTarget'; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); # Is it a redirect? $id = $nt->getArticleID(); @@ -2180,58 +2272,17 @@ class Title { } /** - * Create a redirect; fails if the title already exists; does - * not notify RC - * - * @param Title $dest the destination of the redirect - * @param string $comment the comment string describing the move - * @return bool true on success - * @access public - */ - function createRedirect( $dest, $comment ) { - if ( $this->getArticleID() ) { - return false; - } - - $fname = 'Title::createRedirect'; - $dbw =& wfGetDB( DB_MASTER ); - - $article = new Article( $this ); - $newid = $article->insertOn( $dbw ); - $revision = new Revision( array( - 'page' => $newid, - 'comment' => $comment, - 'text' => "#REDIRECT [[" . $dest->getPrefixedText() . "]]\n", - ) ); - $revision->insertOn( $dbw ); - $article->updateRevisionOn( $dbw, $revision, 0 ); - - # Link table - $dbw->insert( 'pagelinks', - array( - 'pl_from' => $newid, - 'pl_namespace' => $dest->getNamespace(), - 'pl_title' => $dest->getDbKey() - ), $fname - ); - - Article::onArticleCreate( $this ); - return true; - } - - /** * Get categories to which this Title belongs and return an array of * categories' names. * * @return array an array of parents in the form: * $parent => $currentarticle - * @access public */ - function getParentCategories() { + public function getParentCategories() { global $wgContLang; $titlekey = $this->getArticleId(); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $categorylinks = $dbr->tableName( 'categorylinks' ); # NEW SQL @@ -2257,9 +2308,8 @@ class Title { * Get a tree of parent categories * @param array $children an array with the children in the keys, to check for circular refs * @return array - * @access public */ - function getParentCategoryTree( $children = array() ) { + public function getParentCategoryTree( $children = array() ) { $parents = $this->getParentCategories(); if($parents != '') { @@ -2286,9 +2336,8 @@ class Title { * the "page" table * * @return array - * @access public */ - function pageCond() { + public function pageCond() { return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ); } @@ -2298,8 +2347,8 @@ class Title { * @param integer $revision Revision ID. Get the revision that was before this one. * @return integer $oldrevision|false */ - function getPreviousRevisionID( $revision ) { - $dbr =& wfGetDB( DB_SLAVE ); + public function getPreviousRevisionID( $revision ) { + $dbr = wfGetDB( DB_SLAVE ); return $dbr->selectField( 'revision', 'rev_id', 'rev_page=' . intval( $this->getArticleId() ) . ' AND rev_id<' . intval( $revision ) . ' ORDER BY rev_id DESC' ); @@ -2311,8 +2360,8 @@ class Title { * @param integer $revision Revision ID. Get the revision that was after this one. * @return integer $oldrevision|false */ - function getNextRevisionID( $revision ) { - $dbr =& wfGetDB( DB_SLAVE ); + public function getNextRevisionID( $revision ) { + $dbr = wfGetDB( DB_SLAVE ); return $dbr->selectField( 'revision', 'rev_id', 'rev_page=' . intval( $this->getArticleId() ) . ' AND rev_id>' . intval( $revision ) . ' ORDER BY rev_id' ); @@ -2325,8 +2374,8 @@ class Title { * @param integer $new Revision ID. * @return integer Number of revisions between these IDs. */ - function countRevisionsBetween( $old, $new ) { - $dbr =& wfGetDB( DB_SLAVE ); + public function countRevisionsBetween( $old, $new ) { + $dbr = wfGetDB( DB_SLAVE ); return $dbr->selectField( 'revision', 'count(*)', 'rev_page = ' . intval( $this->getArticleId() ) . ' AND rev_id > ' . intval( $old ) . @@ -2339,7 +2388,7 @@ class Title { * @param Title $title * @return bool */ - function equals( $title ) { + public function equals( $title ) { // Note: === is necessary for proper matching of number-like titles. return $this->getInterwiki() === $title->getInterwiki() && $this->getNamespace() == $title->getNamespace() @@ -2350,7 +2399,7 @@ class Title { * Check if page exists * @return bool */ - function exists() { + public function exists() { return $this->getArticleId() != 0; } @@ -2360,7 +2409,7 @@ class Title { * Currently, a self-link with a fragment and special pages are in * this category. Special pages never exist in the database. */ - function isAlwaysKnown() { + public function isAlwaysKnown() { return $this->isExternal() || ( 0 == $this->mNamespace && "" == $this->mDbkeyform ) || NS_SPECIAL == $this->mNamespace; } @@ -2370,7 +2419,7 @@ class Title { * pages linking to this title. May be sent to the job queue depending * on the number of links. Typically called on create and delete. */ - function touchLinks() { + public function touchLinks() { $u = new HTMLCacheUpdate( $this, 'pagelinks' ); $u->doUpdate(); @@ -2383,8 +2432,8 @@ class Title { /** * Get the last touched timestamp */ - function getTouched() { - $dbr =& wfGetDB( DB_SLAVE ); + public function getTouched() { + $dbr = wfGetDB( DB_SLAVE ); $touched = $dbr->selectField( 'page', 'page_touched', array( 'page_namespace' => $this->getNamespace(), @@ -2394,26 +2443,14 @@ class Title { return $touched; } - /** - * Get a cached value from a global cache that is invalidated when this page changes - * @param string $key the key - * @param callback $callback A callback function which generates the value on cache miss - * - * @deprecated use DependencyWrapper - */ - function getRelatedCache( $memc, $key, $expiry, $callback, $params = array() ) { - return DependencyWrapper::getValueFromCache( $memc, $key, $expiry, $callback, - $params, new TitleDependency( $this ) ); - } - - function trackbackURL() { + public function trackbackURL() { global $wgTitle, $wgScriptPath, $wgServer; return "$wgServer$wgScriptPath/trackback.php?article=" . htmlspecialchars(urlencode($wgTitle->getPrefixedDBkey())); } - function trackbackRDF() { + public function trackbackRDF() { $url = htmlspecialchars($this->getFullURL()); $title = htmlspecialchars($this->getText()); $tburl = $this->trackbackURL(); @@ -2434,7 +2471,7 @@ class Title { * Generate strings used for xml 'id' names in monobook tabs * @return string */ - function getNamespaceKey() { + public function getNamespaceKey() { global $wgContLang; switch ($this->getNamespace()) { case NS_MAIN: @@ -2473,9 +2510,8 @@ class Title { /** * Returns true if this title resolves to the named special page * @param string $name The special page name - * @access public */ - function isSpecial( $name ) { + public function isSpecial( $name ) { if ( $this->getNamespace() == NS_SPECIAL ) { list( $thisName, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() ); if ( $name == $thisName ) { @@ -2489,7 +2525,7 @@ class Title { * If the Title refers to a special page alias which is not the local default, * returns a new Title which points to the local default. Otherwise, returns $this. */ - function fixSpecialName() { + public function fixSpecialName() { if ( $this->getNamespace() == NS_SPECIAL ) { $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform ); if ( $canonicalName ) { @@ -2501,5 +2537,18 @@ class Title { } return $this; } + + /** + * Is this Title in a namespace which contains content? + * In other words, is this a content page, for the purposes of calculating + * statistics, etc? + * + * @return bool + */ + public function isContentPage() { + return Namespace::isContent( $this->getNamespace() ); + } + } + ?> diff --git a/includes/User.php b/includes/User.php index 35ff8299..4ecd49de 100644 --- a/includes/User.php +++ b/includes/User.php @@ -2,14 +2,13 @@ /** * See user.txt * - * @package MediaWiki */ # Number of characters in user_token field define( 'USER_TOKEN_LENGTH', 32 ); # Serialized record version -define( 'MW_USER_VERSION', 4 ); +define( 'MW_USER_VERSION', 5 ); # Some punctuation to prevent editing from broken text-mangling proxies. # FIXME: this is embedded unescaped into HTML attributes in various @@ -18,14 +17,21 @@ define( 'EDIT_TOKEN_SUFFIX', '\\' ); /** * Thrown by User::setPassword() on error + * @addtogroup Exception */ class PasswordError extends MWException { // NOP } /** - * - * @package MediaWiki + * The User object encapsulates all of the user-specific settings (user_id, + * name, rights, password, email address, options, last login time). Client + * classes use the getXXX() functions to access these fields. These functions + * do all the work of determining whether the user is logged in, + * whether the requested option can be satisfied from cookies or + * whether a database query is needed. Most of the settings needed + * for rendering normal pages are set in the cookie to minimize use + * of the database. */ class User { @@ -71,6 +77,7 @@ class User { 'watchlisthidebots', 'watchlisthideminor', 'ccmeonemails', + 'diffonly', ); /** @@ -94,7 +101,7 @@ class User { 'mEmailToken', 'mEmailTokenExpires', 'mRegistration', - + 'mEditCount', # user_group table 'mGroups', ); @@ -188,7 +195,6 @@ class User { # Try cache $key = wfMemcKey( 'user', 'id', $this->mId ); $data = $wgMemc->get( $key ); - if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) { # Object is expired, load from DB $data = false; @@ -270,7 +276,7 @@ class User { * @static */ static function newFromConfirmationCode( $code ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $id = $dbr->selectField( 'user', 'user_id', array( 'user_email_token' => md5( $code ), 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ), @@ -302,7 +308,7 @@ class User { * @static */ static function whoIs( $id ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), 'User::whoIs' ); } @@ -313,7 +319,7 @@ class User { * @static */ static function whoIsReal( $id ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), 'User::whoIsReal' ); } @@ -329,7 +335,7 @@ class User { # Illegal name return null; } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), __METHOD__ ); if ( $s === false ) { @@ -352,14 +358,12 @@ class User { * addresses like this, if we allowed accounts like this to be created * new users could get the old edits of these anonymous users. * - * @bug 3631 - * * @static * @param string $name Nickname of a user * @return bool */ static function isIP( $name ) { - return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name); + return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name) || User::isIPv6($name); /*return preg_match("/^ (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\. (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\. @@ -369,6 +373,27 @@ class User { } /** + * Check if $name is an IPv6 IP. + */ + static function isIPv6($name) { + /* + * if it has any non-valid characters, it can't be a valid IPv6 + * address. + */ + if (preg_match("/[^:a-fA-F0-9]/", $name)) + return false; + + $parts = explode(":", $name); + if (count($parts) < 3) + return false; + foreach ($parts as $part) { + if (!preg_match("/^[0-9a-fA-F]{0,4}$/", $part)) + return false; + } + return true; + } + + /** * Is the input a valid username? * * Checks if the input is a valid username, we don't want an empty string, @@ -462,11 +487,15 @@ class User { * * @param string $password * @return bool - * @static */ - static function isValidPassword( $password ) { - global $wgMinimalPasswordLength; - return strlen( $password ) >= $wgMinimalPasswordLength; + function isValidPassword( $password ) { + global $wgMinimalPasswordLength, $wgContLang; + + $result = null; + if( !wfRunHooks( 'isValidPassword', array( $password, &$result ) ) ) return $result; + if ($result === false) return false; + return (strlen( $password ) >= $wgMinimalPasswordLength) && + ($wgContLang->lc( $password ) !== $wgContLang->lc( $this->mName )); } /** @@ -476,8 +505,7 @@ class User { * rejected valid addresses. Actually just check if there is '@' somewhere * in the given address. * - * @todo Check for RFC 2822 compilance - * @bug 959 + * @todo Check for RFC 2822 compilance (bug 959) * * @param string $addr email address * @static @@ -541,22 +569,45 @@ class User { /** * Count the number of edits of a user * + * It should not be static and some day should be merged as proper member function / deprecated -- domas + * * @param int $uid The user ID to check * @return int * @static */ static function edits( $uid ) { - $dbr =& wfGetDB( DB_SLAVE ); - return $dbr->selectField( - 'revision', 'count(*)', - array( 'rev_user' => $uid ), + wfProfileIn( __METHOD__ ); + $dbr = wfGetDB( DB_SLAVE ); + // check if the user_editcount field has been initialized + $field = $dbr->selectField( + 'user', 'user_editcount', + array( 'user_id' => $uid ), __METHOD__ ); + + if( $field === null ) { // it has not been initialized. do so. + $dbw = wfGetDb( DB_MASTER ); + $count = $dbr->selectField( + 'revision', 'count(*)', + array( 'rev_user' => $uid ), + __METHOD__ + ); + $dbw->update( + 'user', + array( 'user_editcount' => $count ), + array( 'user_id' => $uid ), + __METHOD__ + ); + } else { + $count = $field; + } + wfProfileOut( __METHOD__ ); + return $count; } /** * Return a random password. Sourced from mt_rand, so it's not particularly secure. - * @todo: hash random numbers to improve security, like generateToken() + * @todo hash random numbers to improve security, like generateToken() * * @return string * @static @@ -622,10 +673,8 @@ class User { * Load user data from the session or login cookie. If there are no valid * credentials, initialises the user as an anon. * @return true if the user is logged in, false otherwise - * - * @private */ - function loadFromSession() { + private function loadFromSession() { global $wgMemc, $wgCookiePrefix; if ( isset( $_SESSION['wsUserID'] ) ) { @@ -699,7 +748,7 @@ class User { return false; } - $dbr =& wfGetDB( DB_MASTER ); + $dbr = wfGetDB( DB_MASTER ); $s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ ); if ( $s !== false ) { @@ -717,6 +766,8 @@ class User { $this->mEmailToken = $s->user_email_token; $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $s->user_email_token_expires ); $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration ); + $this->mEditCount = $s->user_editcount; + $this->getEditCount(); // revalidation for nulls # Load group data $res = $dbr->select( 'user_groups', @@ -833,7 +884,8 @@ class User { wfProfileIn( __METHOD__ ); wfDebug( __METHOD__.": checking...\n" ); - $this->mBlockedby = 0; + $this->mBlockedby = 0; + $this->mHideName = 0; $ip = wfGetIP(); if ($this->isAllowed( 'ipblock-exempt' ) ) { @@ -848,6 +900,7 @@ class User { wfDebug( __METHOD__.": Found block.\n" ); $this->mBlockedby = $this->mBlock->mBy; $this->mBlockreason = $this->mBlock->mReason; + $this->mHideName = $this->mBlock->mHideName; if ( $this->isLoggedIn() ) { $this->spreadBlock(); } @@ -917,6 +970,16 @@ class User { } /** + * Is this user subject to rate limiting? + * + * @return bool + */ + public function isPingLimitable() { + global $wgRateLimitsExcludedGroups; + return array_intersect($this->getEffectiveGroups(), $wgRateLimitsExcludedGroups) == array(); + } + + /** * Primitive rate limits: enforce maximum actions per time period * to put a brake on flooding. * @@ -927,24 +990,22 @@ class User { * @public */ function pingLimiter( $action='edit' ) { - + # Call the 'PingLimiter' hook $result = false; if( !wfRunHooks( 'PingLimiter', array( &$this, $action, $result ) ) ) { return $result; } - + global $wgRateLimits, $wgRateLimitsExcludedGroups; if( !isset( $wgRateLimits[$action] ) ) { return false; } - + # Some groups shouldn't trigger the ping limiter, ever - foreach( $this->getGroups() as $group ) { - if( array_search( $group, $wgRateLimitsExcludedGroups ) !== false ) - return false; - } - + if( !$this->isPingLimitable() ) + return false; + global $wgMemc, $wgRateLimitLog; wfProfileIn( __METHOD__ ); @@ -1018,14 +1079,13 @@ class User { wfProfileIn( __METHOD__ ); wfDebug( __METHOD__.": enter\n" ); - if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() && - $title->getNamespace() == NS_USER_TALK ) - { + wfDebug( __METHOD__.": asking isBlocked()\n" ); + $blocked = $this->isBlocked( $bFromSlave ); + # If a user's name is suppressed, they cannot make edits anywhere + if ( !$this->mHideName && $wgBlockAllowsUTEdit && $title->getText() === $this->getName() && + $title->getNamespace() == NS_USER_TALK ) { $blocked = false; wfDebug( __METHOD__.": self-talk page, ignoring any blocks\n" ); - } else { - wfDebug( __METHOD__.": asking isBlocked()\n" ); - $blocked = $this->isBlocked( $bFromSlave ); } wfProfileOut( __METHOD__ ); return $blocked; @@ -1076,7 +1136,8 @@ class User { } else { $this->load(); if ( $this->mName === false ) { - $this->mName = wfGetIP(); + # Clean up IPs + $this->mName = IP::sanitizeIP( wfGetIP() ); } return $this->mName; } @@ -1121,11 +1182,11 @@ class User { global $wgMemc; $key = wfMemcKey( 'newtalk', 'ip', $this->getName() ); $newtalk = $wgMemc->get( $key ); - if( is_integer( $newtalk ) ) { + if( $newtalk != "" ) { $this->mNewtalk = (bool)$newtalk; } else { $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() ); - $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 ); + $wgMemc->set( $key, (int)$this->mNewtalk, time() + 1800 ); } } else { $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId ); @@ -1162,7 +1223,7 @@ class User { * @private */ function checkNewtalk( $field, $id ) { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $ok = $dbr->selectField( 'user_newtalk', $field, array( $field => $id ), __METHOD__ ); return $ok !== false; @@ -1179,7 +1240,7 @@ class User { wfDebug( __METHOD__." already set ($field, $id), ignoring\n" ); return false; } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->insert( 'user_newtalk', array( $field => $id ), __METHOD__, @@ -1199,7 +1260,7 @@ class User { wfDebug( __METHOD__.": already gone ($field, $id), ignoring\n" ); return false; } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'user_newtalk', array( $field => $id ), __METHOD__ ); @@ -1284,7 +1345,7 @@ class User { if( $this->mId ) { $this->mTouched = self::newTouchedTimestamp(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'user', array( 'user_touched' => $dbw->timestamp( $this->mTouched ) ), array( 'user_id' => $this->mId ), @@ -1338,11 +1399,23 @@ class User { $wgMinimalPasswordLength ) ); } } - + if( !$wgAuth->setPassword( $this, $str ) ) { throw new PasswordError( wfMsg( 'externaldberror' ) ); } + $this->setInternalPassword( $str ); + + return true; + } + + /** + * Set the password and reset the random token no matter + * what. + * + * @param string $str + */ + function setInternalPassword( $str ) { $this->load(); $this->setToken(); @@ -1354,10 +1427,7 @@ class User { } $this->mNewpassword = ''; $this->mNewpassTime = null; - - return true; } - /** * Set the random token (used for persistent authentication) * Called from loadDefaults() among other places. @@ -1546,12 +1616,12 @@ class User { if( $this->mId ) { $this->mEffectiveGroups[] = 'user'; - global $wgAutoConfirmAge; + global $wgAutoConfirmAge, $wgAutoConfirmCount; + $accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration ); - if( $accountAge >= $wgAutoConfirmAge ) { + if( $accountAge >= $wgAutoConfirmAge && $this->getEditCount() >= $wgAutoConfirmCount ) { $this->mEffectiveGroups[] = 'autoconfirmed'; } - # Implicit group for users whose email addresses are confirmed global $wgEmailAuthentication; if( self::isValidEmailAddr( $this->mEmail ) ) { @@ -1566,15 +1636,29 @@ class User { } return $this->mEffectiveGroups; } - + + /* Return the edit count for the user. This is where User::edits should have been */ + function getEditCount() { + if ($this->mId) { + if ( !isset( $this->mEditCount ) ) { + /* Populate the count, if it has not been populated yet */ + $this->mEditCount = User::edits($this->mId); + } + return $this->mEditCount; + } else { + /* nil */ + return null; + } + } + /** * Add the user to the given group. * This takes immediate effect. - * @string $group + * @param string $group */ function addGroup( $group ) { $this->load(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); if( $this->getId() ) { $dbw->insert( 'user_groups', array( @@ -1594,11 +1678,11 @@ class User { /** * Remove the user from the given group. * This takes immediate effect. - * @string $group + * @param string $group */ function removeGroup( $group ) { $this->load(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'user_groups', array( 'ug_user' => $this->getID(), @@ -1750,7 +1834,7 @@ class User { // If the page is watched by the user (or may be watched), update the timestamp on any // any matching rows if ( $watched ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'watchlist', array( /* SET */ 'wl_notificationtimestamp' => NULL @@ -1781,7 +1865,7 @@ class User { } if( $currentUser != 0 ) { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'watchlist', array( /* SET */ 'wl_notificationtimestamp' => NULL @@ -1865,7 +1949,7 @@ class User { /** * Save object settings into database - * @fixme Only rarely do all these fields need to be set! + * @todo Only rarely do all these fields need to be set! */ function saveSettings() { $this->load(); @@ -1874,7 +1958,7 @@ class User { $this->mTouched = self::newTouchedTimestamp(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'user', array( /* SET */ 'user_name' => $this->mName, @@ -1902,7 +1986,7 @@ class User { $s = trim( $this->getName() ); if ( 0 == strcmp( '', $s ) ) return 0; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__ ); if ( $id === false ) { $id = 0; @@ -1933,7 +2017,7 @@ class User { $user->mOptions = $params['options'] + $user->mOptions; unset( $params['options'] ); } - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); $fields = array( 'user_id' => $seqVal, @@ -1966,7 +2050,7 @@ class User { */ function addToDatabase() { $this->load(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); $dbw->insert( 'user', array( @@ -2096,7 +2180,7 @@ class User { if ( isset( $res ) ) return $res; else { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' ); } } @@ -2272,7 +2356,7 @@ class User { $token = $this->generateToken( $this->mId . $this->mEmail . $expires ); $hash = md5( $token ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'user', array( 'user_email_token' => $hash, 'user_email_token_expires' => $dbw->timestamp( $expires ) ), @@ -2385,13 +2469,12 @@ class User { * @static */ static function getGroupName( $group ) { + MessageCache::loadAllMessages(); $key = "group-$group"; $name = wfMsg( $key ); - if( $name == '' || wfEmptyMsg( $key, $name ) ) { - return $group; - } else { - return $name; - } + return $name == '' || wfEmptyMsg( $key, $name ) + ? $group + : $name; } /** @@ -2400,13 +2483,12 @@ class User { * @static */ static function getGroupMember( $group ) { + MessageCache::loadAllMessages(); $key = "group-$group-member"; $name = wfMsg( $key ); - if( $name == '' || wfEmptyMsg( $key, $name ) ) { - return $group; - } else { - return $name; - } + return $name == '' || wfEmptyMsg( $key, $name ) + ? $group + : $name; } /** @@ -2431,6 +2513,7 @@ class User { * @return mixed */ static function getGroupPage( $group ) { + MessageCache::loadAllMessages(); $page = wfMsgForContent( 'grouppage-' . $group ); if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) { $title = Title::newFromText( $page ); @@ -2455,7 +2538,7 @@ class User { if( $title ) { global $wgUser; $sk = $wgUser->getSkin(); - return $sk->makeLinkObj( $title, $text ); + return $sk->makeLinkObj( $title, htmlspecialchars( $text ) ); } else { return $text; } @@ -2521,6 +2604,8 @@ class User { __METHOD__ ); } } + // edit count in user cache too + $this->invalidateCache(); } } diff --git a/includes/UserMailer.php b/includes/UserMailer.php index 0101f744..9f5f178c 100644 --- a/includes/UserMailer.php +++ b/includes/UserMailer.php @@ -22,7 +22,6 @@ * @author <brion@pobox.com> * @author <mail@tgries.de> * - * @package MediaWiki */ /** @@ -33,12 +32,17 @@ function wfRFC822Phrase( $phrase ) { return '"' . $phrase . '"'; } +/** + * Stores a single person's name and email address. + * These are passed in via the constructor, and will be returned in SMTP + * header format when requested. + */ class MailAddress { /** * @param mixed $address String with an email address, or a User object * @param string $name Human-readable name if a string address is given */ - function MailAddress( $address, $name=null ) { + function __construct( $address, $name=null ) { if( is_object( $address ) && $address instanceof User ) { $this->address = $address->getEmail(); $this->name = $address->getName(); @@ -78,9 +82,9 @@ class MailAddress { * @param $from MailAddress: sender's email * @param $subject String: email's subject. * @param $body String: email's text. - * @param $replyto String: optional reply-to email (default: false). + * @param $replyto String: optional reply-to email (default: null). */ -function userMailer( $to, $from, $subject, $body, $replyto=false ) { +function userMailer( $to, $from, $subject, $body, $replyto=null ) { global $wgUser, $wgSMTP, $wgOutputEncoding, $wgErrorString; if (is_array( $wgSMTP )) { @@ -92,7 +96,7 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) { $headers['From'] = $from->toString(); $headers['To'] = $to->toString(); if ( $replyto ) { - $headers['Reply-To'] = $replyto; + $headers['Reply-To'] = $replyto->toString(); } $headers['Subject'] = wfQuotedPrintable( $subject ); $headers['Date'] = date( 'r' ); @@ -141,7 +145,7 @@ function userMailer( $to, $from, $subject, $body, $replyto=false ) { "X-Mailer: MediaWiki mailer$endl". 'From: ' . $from->toString(); if ($replyto) { - $headers .= "{$endl}Reply-To: $replyto"; + $headers .= "{$endl}Reply-To: " . $replyto->toString(); } $dest = $to->toString(); @@ -189,7 +193,6 @@ function mailErrorHandler( $code, $string ) { * * Visit the documentation pages under http://meta.wikipedia.com/Enotif * - * @package MediaWiki * */ class EmailNotification { @@ -226,6 +229,13 @@ class EmailNotification { $enotifusertalkpage = ($isUserTalkPage && $wgEnotifUserTalk); $enotifwatchlistpage = $wgEnotifWatchlist; + $this->title =& $title; + $this->timestamp = $timestamp; + $this->summary = $summary; + $this->minorEdit = $minorEdit; + $this->oldid = $oldid; + $this->composeCommonMailtext(); + if ( (!$minorEdit || $wgEnotifMinorEdits) ) { if( $wgEnotifWatchlist ) { // Send updates to watchers other than the current editor @@ -247,7 +257,7 @@ class EmailNotification { $userCondition = false; } if( $userCondition ) { - $dbr =& wfGetDB( DB_MASTER ); + $dbr = wfGetDB( DB_MASTER ); $res = $dbr->select( 'watchlist', array( 'wl_user' ), array( @@ -260,13 +270,7 @@ class EmailNotification { # if anyone is watching ... set up the email message text which is # common for all receipients ... if ( $dbr->numRows( $res ) > 0 ) { - $this->title =& $title; - $this->timestamp = $timestamp; - $this->summary = $summary; - $this->minorEdit = $minorEdit; - $this->oldid = $oldid; - $this->composeCommonMailtext(); $watchingUser = new User(); # ... now do for all watching users ... if the options fit @@ -291,10 +295,16 @@ class EmailNotification { } # if anyone is watching } # if $wgEnotifWatchlist = true + global $wgUsersNotifedOnAllChanges; + foreach ( $wgUsersNotifedOnAllChanges as $name ) { + $user = User::newFromName( $name ); + $this->composeAndSendPersonalisedMail( $user ); + } + if ( $wgShowUpdatedMarker || $wgEnotifWatchlist ) { # mark the changed watch-listed page with a timestamp, so that the page is # listed with an "updated since your last visit" icon in the watch list, ... - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $success = $dbw->update( 'watchlist', array( /* SET */ 'wl_notificationtimestamp' => $dbw->timestamp($timestamp) @@ -370,7 +380,7 @@ class EmailNotification { } } else { $from = $adminAddress; - $replyto = $wgNoReplyAddress; + $replyto = new MailAddress( $wgNoReplyAddress ); } if( $wgUser->isIP( $name ) ) { diff --git a/includes/Utf8Case.php b/includes/Utf8Case.php index 8c7fdd0b..279c0e32 100644 --- a/includes/Utf8Case.php +++ b/includes/Utf8Case.php @@ -7,8 +7,7 @@ * * These are pulled from memcached if possible, as this is faster than filling * up a big array manually. - * @package MediaWiki - * @subpackage Language + * @addtogroup Language */ /* diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php index 788774fb..b0376e3d 100644 --- a/includes/WatchedItem.php +++ b/includes/WatchedItem.php @@ -1,12 +1,10 @@ <?php /** * - * @package MediaWiki */ /** * - * @package MediaWiki */ class WatchedItem { var $mTitle, $mUser; @@ -32,30 +30,17 @@ class WatchedItem { } /** - * Returns the memcached key for this item - */ - function watchKey() { - return wfMemcKey( 'watchlist', 'user', $this->id, 'page', $this->ns, $this->ti ); - } - - /** * Is mTitle being watched by mUser? */ function isWatched() { # Pages and their talk pages are considered equivalent for watching; # remember that talk namespaces are numbered as page namespace+1. - global $wgMemc; $fname = 'WatchedItem::isWatched'; - $key = $this->watchKey(); - $iswatched = $wgMemc->get( $key ); - if( is_integer( $iswatched ) ) return $iswatched; - - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'watchlist', 1, array( 'wl_user' => $this->id, 'wl_namespace' => $this->ns, 'wl_title' => $this->ti ), $fname ); $iswatched = ($dbr->numRows( $res ) > 0) ? 1 : 0; - $wgMemc->set( $key, $iswatched ); return $iswatched; } @@ -68,7 +53,7 @@ class WatchedItem { // Use INSERT IGNORE to avoid overwriting the notification timestamp // if there's already an entry for this page - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->insert( 'watchlist', array( 'wl_user' => $this->id, @@ -87,18 +72,15 @@ class WatchedItem { 'wl_notificationtimestamp' => NULL ), $fname, 'IGNORE' ); - global $wgMemc; - $wgMemc->set( $this->watchkey(), 1 ); wfProfileOut( $fname ); return true; } function removeWatch() { - global $wgMemc; $fname = 'WatchedItem::removeWatch'; $success = false; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'watchlist', array( 'wl_user' => $this->id, @@ -125,9 +107,6 @@ class WatchedItem { if ( $dbw->affectedRows() ) { $success = true; } - if ( $success ) { - $wgMemc->set( $this->watchkey(), 0 ); - } return $success; } @@ -155,7 +134,7 @@ class WatchedItem { $oldtitle = $ot->getDBkey(); $newtitle = $nt->getDBkey(); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $res = $dbw->select( 'watchlist', 'wl_user', array( 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ), $fname, 'FOR UPDATE' diff --git a/includes/WebRequest.php b/includes/WebRequest.php index 7648b75f..53273a22 100644 --- a/includes/WebRequest.php +++ b/includes/WebRequest.php @@ -1,7 +1,6 @@ <?php /** * Deal with importing all those nasssty globals and things - * @package MediaWiki */ # Copyright (C) 2003 Brion Vibber <brion@pobox.com> @@ -22,6 +21,15 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html + +/** + * Some entry points may use this file without first enabling the + * autoloader. + */ +if ( !function_exists( '__autoload' ) ) { + require_once( dirname(__FILE__) . '/normal/UtfNormal.php' ); +} + /** * The WebRequest class encapsulates getting at data passed in the * URL or via a POSTed form, handling remove of "magic quotes" slashes, @@ -32,19 +40,9 @@ * you want to pass arbitrary data to some function in place of the web * input. * - * @package MediaWiki - */ - -/** - * Some entry points may use this file without first enabling the - * autoloader. */ -if ( !function_exists( '__autoload' ) ) { - require_once( dirname(__FILE__) . '/normal/UtfNormal.php' ); -} - class WebRequest { - function WebRequest() { + function __construct() { $this->checkMagicQuotes(); global $wgUsePathInfo; if ( $wgUsePathInfo ) { @@ -142,7 +140,9 @@ class WebRequest { /** * Fetch a scalar from the input or return $default if it's not set. - * Returns a string. Arrays are discarded. + * Returns a string. Arrays are discarded. Useful for + * non-freeform text inputs (e.g. predefined internal text keys + * selected by a drop-down menu). For freeform input, see getText(). * * @param string $name * @param string $default optional default (or NULL) @@ -252,7 +252,9 @@ class WebRequest { * Fetch a text string from the given array or return $default if it's not * set. \r is stripped from the text, and with some language modules there * is an input transliteration applied. This should generally be used for - * form <textarea> and <input> fields. + * form <textarea> and <input> fields. Used for user-supplied freeform text + * input (for which input transformations may be required - e.g. Esperanto + * x-coding). * * @param string $name * @param string $default optional @@ -303,10 +305,15 @@ class WebRequest { * Returns true if there is a session cookie set. * This does not necessarily mean that the user is logged in! * + * If you want to check for an open session, use session_id() + * instead; that will also tell you if the session was opened + * during the current request (in which case the cookie will + * be sent back to the client at the end of the script run). + * * @return bool */ function checkSessionCookie() { - return isset( $_COOKIE[ini_get('session.name')] ); + return isset( $_COOKIE[session_name()] ); } /** @@ -328,6 +335,14 @@ class WebRequest { "REQUEST_URI or SCRIPT_NAME. Report details of your " . "web server configuration to http://bugzilla.wikimedia.org/" ); } + // User-agents should not send a fragment with the URI, but + // if they do, and the web server passes it on to us, we + // need to strip it or we get false-positive redirect loops + // or weird output URLs + $hash = strpos( $base, '#' ); + if( $hash !== false ) { + $base = substr( $base, 0, $hash ); + } if( $base{0} == '/' ) { return $base; } else { @@ -483,7 +498,6 @@ class WebRequest { /** * WebRequest clone which takes values from a provided array. * - * @package MediaWiki */ class FauxRequest extends WebRequest { var $data = null; diff --git a/includes/WebResponse.php b/includes/WebResponse.php index e159152e..92343195 100644 --- a/includes/WebResponse.php +++ b/includes/WebResponse.php @@ -1,18 +1,20 @@ <?php - -/* - * Allow programs to request this object from WebRequest::response() and handle all outputting (or lack of outputting) via it. +/** + * Allow programs to request this object from WebRequest::response() + * and handle all outputting (or lack of outputting) via it. */ - class WebResponse { + + /** Output a HTTP header */ function header($string, $replace=true) { header($string,$replace); } - + + /** Set the browser cookie */ function setcookie($name, $value, $expire) { global $wgCookiePath, $wgCookieDomain, $wgCookieSecure; setcookie($name,$value,$expire, $wgCookiePath, $wgCookieDomain, $wgCookieSecure); } } -?>
\ No newline at end of file +?> diff --git a/includes/WebStart.php b/includes/WebStart.php index 37582290..55c96488 100644 --- a/includes/WebStart.php +++ b/includes/WebStart.php @@ -85,6 +85,18 @@ if( !file_exists( './LocalSettings.php' ) ) { # Include this site setttings require_once( './LocalSettings.php' ); wfProfileOut( 'WebStart.php-conf' ); +wfProfileIn( 'WebStart.php-ob_start' ); + +# Initialise output buffering +if ( ob_get_level() ) { + # Someone's been mixing configuration data with code! + # How annoying. +} elseif ( !defined( 'MW_NO_OUTPUT_BUFFER' ) ) { + require_once( './includes/OutputHandler.php' ); + ob_start( 'wfOutputHandler' ); +} + +wfProfileOut( 'WebStart.php-ob_start' ); if ( !defined( 'MW_NO_SETUP' ) ) { require_once( './includes/Setup.php' ); diff --git a/includes/Wiki.php b/includes/Wiki.php index 06248b35..612e58ee 100644 --- a/includes/Wiki.php +++ b/includes/Wiki.php @@ -7,14 +7,12 @@ class MediaWiki { var $GET; /* Stores the $_GET variables at time of creation, can be changed */ var $params = array(); - - /** - * Constructor - */ - function MediaWiki () { + + /** Constructor. It just save the $_GET variable */ + function __construct() { $this->GET = $_GET; } - + /** * Stores key/value pairs to circumvent global variables * Note that keys are case-insensitive! @@ -23,7 +21,7 @@ class MediaWiki { $key = strtolower( $key ); $this->params[$key] =& $value; } - + /** * Retrieves key/value pairs to circumvent global variables * Note that keys are case-insensitive! @@ -35,7 +33,7 @@ class MediaWiki { } return $default; } - + /** * Initialization of ... everything @return Article either the object to become $wgArticle, or NULL @@ -57,7 +55,23 @@ class MediaWiki { wfProfileOut( 'MediaWiki::initialize' ); return $article; } - + + function checkMaxLag( $maxLag ) { + global $wgLoadBalancer; + list( $host, $lag ) = $wgLoadBalancer->getMaxLag(); + if ( $lag > $maxLag ) { + header( 'HTTP/1.1 503 Service Unavailable' ); + header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); + header( 'X-Database-Lag: ' . intval( $lag ) ); + header( 'Content-Type: text/plain' ); + echo "Waiting for $host: $lag seconds lagged\n"; + return false; + } else { + return true; + } + } + + /** * Checks some initial queries * Note that $title here is *not* a Title object, but a string! @@ -66,10 +80,10 @@ class MediaWiki { if ($request->getVal( 'printable' ) == 'yes') { $output->setPrintable(); } - + $ret = NULL ; - - + + if ( '' == $title && 'delete' != $action ) { $ret = Title::newMainPage(); } elseif ( $curid = $request->getInt( 'curid' ) ) { @@ -82,19 +96,19 @@ class MediaWiki { */ if( count($lang->getVariants()) > 1 && !is_null($ret) && $ret->getArticleID() == 0 ) $lang->findVariantLink( $title, $ret ); - + } return $ret ; } - + /** * Checks for search query and anon-cannot-read case */ function preliminaryChecks ( &$title, &$output, $request ) { - + # Debug statement for user levels // print_r($wgUser); - + $search = $request->getText( 'search' ); if( !is_null( $search ) && $search !== '' ) { // Compatibility with old search URLs which didn't use Special:Search @@ -111,16 +125,16 @@ class MediaWiki { $output->output(); exit; } - + } - + /** * Initialize the object to be known as $wgArticle for special cases */ function initializeSpecialCases ( &$title, &$output, $request ) { global $wgRequest; wfProfileIn( 'MediaWiki::initializeSpecialCases' ); - + $search = $this->getVal('Search'); $action = $this->getVal('Action'); if( !$this->getVal('DisableInternalSearch') && !is_null( $search ) && $search !== '' ) { @@ -150,13 +164,13 @@ class MediaWiki { { $targetUrl = $title->getFullURL(); // Redirect to canonical url, make it a 301 to allow caching - global $wgServer, $wgUsePathInfo; + global $wgUsePathInfo; if( $targetUrl == $wgRequest->getFullRequestURL() ) { $message = "Redirect loop detected!\n\n" . "This means the wiki got confused about what page was " . "requested; this sometimes happens when moving a wiki " . "to a new server or changing the server configuration.\n\n"; - + if( $wgUsePathInfo ) { $message .= "The wiki is trying to interpret the page " . "title from the URL path portion (PATH_INFO), which " . @@ -206,7 +220,7 @@ class MediaWiki { // FIXME: where should this go? $title = Title::makeTitle( NS_IMAGE, $title->getDBkey() ); } - + switch( $title->getNamespace() ) { case NS_IMAGE: return new ImagePage( $title ); @@ -216,7 +230,7 @@ class MediaWiki { return new Article( $title ); } } - + /** * Initialize the object to be known as $wgArticle for "standard" actions * Create an Article object for the page, following redirects if needed. @@ -228,17 +242,17 @@ class MediaWiki { function initializeArticle( $title, $request ) { global $wgTitle; wfProfileIn( 'MediaWiki::initializeArticle' ); - + $action = $this->getVal('Action'); $article = $this->articleFromTitle( $title ); - + // Namespace might change when using redirects if( $action == 'view' && !$request->getVal( 'oldid' ) && $request->getVal( 'redirect' ) != 'no' ) { - - $dbr =& wfGetDB(DB_SLAVE); + + $dbr = wfGetDB(DB_SLAVE); $article->loadPageData($article->pageDataFromTitle($dbr, $title)); - + /* Follow redirects only for... redirects */ if ($article->mIsRedirect) { $target = $article->followRedirect(); @@ -290,7 +304,7 @@ class MediaWiki { */ function doUpdates ( &$updates ) { wfProfileIn( 'MediaWiki::doUpdates' ); - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); foreach( $updates as $up ) { $up->doUpdate(); @@ -307,7 +321,7 @@ class MediaWiki { */ function doJobs() { global $wgJobRunRate; - + if ( $wgJobRunRate <= 0 || wfReadOnly() ) { return; } @@ -335,7 +349,7 @@ class MediaWiki { wfDebugLog( 'jobqueue', $output ); } } - + /** * Ends this task peacefully */ @@ -401,24 +415,26 @@ class MediaWiki { showCreditsPage( $article ); break; case 'submit': - if( !$this->getVal( 'CommandLineMode' ) && !$request->checkSessionCookie() ) { + if( session_id() == '' ) { /* Send a cookie so anons get talk message notifications */ - User::SetupSession(); + wfSetupSession(); } /* Continue... */ case 'edit': - $internal = $request->getVal( 'internaledit' ); - $external = $request->getVal( 'externaledit' ); - $section = $request->getVal( 'section' ); - $oldid = $request->getVal( 'oldid' ); - if( !$this->getVal( 'UseExternalEditor' ) || $action=='submit' || $internal || - $section || $oldid || ( !$user->getOption( 'externaleditor' ) && !$external ) ) { - $editor = new EditPage( $article ); - $editor->submit(); - } elseif( $this->getVal( 'UseExternalEditor' ) && ( $external || $user->getOption( 'externaleditor' ) ) ) { - $mode = $request->getVal( 'mode' ); - $extedit = new ExternalEdit( $article, $mode ); - $extedit->edit(); + if( wfRunHooks( 'CustomEditor', array( $article, $user ) ) ) { + $internal = $request->getVal( 'internaledit' ); + $external = $request->getVal( 'externaledit' ); + $section = $request->getVal( 'section' ); + $oldid = $request->getVal( 'oldid' ); + if( !$this->getVal( 'UseExternalEditor' ) || $action=='submit' || $internal || + $section || $oldid || ( !$user->getOption( 'externaleditor' ) && !$external ) ) { + $editor = new EditPage( $article ); + $editor->submit(); + } elseif( $this->getVal( 'UseExternalEditor' ) && ( $external || $user->getOption( 'externaleditor' ) ) ) { + $mode = $request->getVal( 'mode' ); + $extedit = new ExternalEdit( $article, $mode ); + $extedit->edit(); + } } break; case 'history': @@ -440,7 +456,6 @@ class MediaWiki { } wfProfileOut( 'MediaWiki::performAction' ); - } }; /* End of class MediaWiki */ diff --git a/includes/WikiError.php b/includes/WikiError.php index 029184d4..064db61a 100644 --- a/includes/WikiError.php +++ b/includes/WikiError.php @@ -19,19 +19,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * - * @package MediaWiki */ /** * Since PHP4 doesn't have exceptions, here's some error objects * loosely modeled on the standard PEAR_Error model... - * @package MediaWiki + * @addtogroup Exception */ class WikiError { /** * @param string $message */ - function WikiError( $message ) { + function __construct( $message ) { $this->mMessage = $message; } @@ -66,7 +65,7 @@ class WikiError { /** * Localized error message object - * @package MediaWiki + * @addtogroup Exception */ class WikiErrorMsg extends WikiError { /** @@ -81,8 +80,8 @@ class WikiErrorMsg extends WikiError { } /** - * @package MediaWiki * @todo document + * @addtogroup Exception */ class WikiXmlError extends WikiError { /** diff --git a/includes/Xml.php b/includes/Xml.php index 67dda7fe..0fedcfa0 100644 --- a/includes/Xml.php +++ b/includes/Xml.php @@ -50,7 +50,9 @@ class Xml { $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs ); } if( $contents ) { + wfProfileIn( __METHOD__ . '-norm' ); $contents = UtfNormal::cleanUp( $contents ); + wfProfileOut( __METHOD__ . '-norm' ); } return self::element( $element, $attribs, $contents ); } @@ -60,6 +62,14 @@ class Xml { public static function closeElement( $element ) { return "</$element>"; } /** + * Same as <link>element</link>, but does not escape contents. Handy when the + * content you have is already valid xml. + */ + public static function tags( $element, $attribs = null, $contents ) { + return self::element( $element, $attribs, null ) . $contents . "</$element>"; + } + + /** * Create a namespace selector * * @param $selected Mixed: the namespace which should be selected, default '' @@ -67,7 +77,7 @@ class Xml { * @param $includehidden Bool: include hidden namespaces? * @return String: Html string containing the namespace selector */ - public static function &namespaceSelector($selected = '', $allnamespaces = null, $includehidden=false) { + public static function namespaceSelector($selected = '', $allnamespaces = null, $includehidden=false) { global $wgContLang; if( $selected !== '' ) { if( is_null( $selected ) ) { diff --git a/includes/XmlFunctions.php b/includes/XmlFunctions.php index cbdcf5c4..326c4953 100644 --- a/includes/XmlFunctions.php +++ b/includes/XmlFunctions.php @@ -15,7 +15,7 @@ function wfOpenElement( $element, $attribs = null ) { function wfCloseElement( $element ) { return "</$element>"; } -function &HTMLnamespaceselector($selected = '', $allnamespaces = null, $includehidden=false) { +function HTMLnamespaceselector($selected = '', $allnamespaces = null, $includehidden=false) { return Xml::namespaceSelector( $selected, $allnamespaces, $includehidden ); } function wfSpan( $text, $class, $attribs=array() ) { diff --git a/includes/ZhClient.php b/includes/ZhClient.php index 9c9461d5..fe965f65 100644 --- a/includes/ZhClient.php +++ b/includes/ZhClient.php @@ -1,12 +1,10 @@ <?php /** - * @package MediaWiki */ /** * Client for querying zhdaemon * - * @package MediaWiki */ class ZhClient { var $mHost, $mPort, $mFP, $mConnected; diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php index e63281eb..1ccd6a7e 100644 --- a/includes/ZhConversion.php +++ b/includes/ZhConversion.php @@ -5,7 +5,6 @@ * Automatically generated using code and data in includes/zhtable/ * Do not modify directly! * - * @package MediaWiki */ $zh2TW=array( diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 1a9c1e3d..c4218825 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 5, 2006 * @@ -24,6 +23,10 @@ * http://www.gnu.org/copyleft/gpl.html */ +/** + * @todo Document - e.g. Provide top-level description of this class. + * @addtogroup API + */ abstract class ApiBase { // These constants allow modules to specify exactly how to treat incomming parameters. @@ -527,7 +530,7 @@ abstract class ApiBase { public abstract function getVersion(); public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 17880 2006-11-23 08:25:56Z nickj $'; + return __CLASS__ . ': $Id: ApiBase.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php index 7d1c1519..7918ee0e 100644 --- a/includes/api/ApiFeedWatchlist.php +++ b/includes/api/ApiFeedWatchlist.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 13, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ("ApiBase.php"); } +/** + * @addtogroup API + */ class ApiFeedWatchlist extends ApiBase { public function __construct($main, $action) { @@ -119,7 +121,7 @@ class ApiFeedWatchlist extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFeedWatchlist.php 17987 2006-11-29 05:45:03Z nickj $'; + return __CLASS__ . ': $Id: ApiFeedWatchlist.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 338a6c07..192c51a7 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 19, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * @addtogroup API + */ abstract class ApiFormatBase extends ApiBase { private $mIsHtml, $mFormat; @@ -170,12 +172,13 @@ for more information. } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 19434 2007-01-18 02:04:11Z brion $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 21402 2007-04-20 08:55:14Z nickj $'; } } /** * This printer is used to wrap an instance of the Feed class + * @addtogroup API */ class ApiFormatFeedWrapper extends ApiFormatBase { @@ -226,7 +229,7 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 19434 2007-01-18 02:04:11Z brion $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index 45c735c8..dd1847c4 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 19, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiFormatBase.php'); } +/** + * @addtogroup API + */ class ApiFormatJson extends ApiFormatBase { private $mIsRaw; @@ -63,7 +65,7 @@ class ApiFormatJson extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatJson.php 17374 2006-11-03 06:53:47Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatJson.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php index 375de7eb..2cd87930 100644 --- a/includes/api/ApiFormatJson_json.php +++ b/includes/api/ApiFormatJson_json.php @@ -45,15 +45,14 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * -* @category -* @package Services_JSON +* @addtogroup API * @author Michal Migurski <mike-json@teczno.com> * @author Matt Knapp <mdknapp[at]gmail[dot]com> * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> * @copyright 2005 Michal Migurski * @version CVS: $Id: JSON.php,v 1.30 2006/03/08 16:10:20 migurski Exp $ * @license http://www.opensource.org/licenses/bsd-license.php -* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 +* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198 */ /** @@ -92,26 +91,28 @@ define('SERVICES_JSON_LOOSE_TYPE', 16); define('SERVICES_JSON_SUPPRESS_ERRORS', 32); /** -* Converts to and from JSON format. -* -* Brief example of use: -* -* <code> -* // create a new instance of Services_JSON -* $json = new Services_JSON(); -* -* // convert a complexe value to JSON notation, and send it to the browser -* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); -* $output = $json->encode($value); -* -* print($output); -* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] -* -* // accept incoming POST data, assumed to be in JSON notation -* $input = file_get_contents('php://input', 1000000); -* $value = $json->decode($input); -* </code> -*/ + * Converts to and from JSON format. + * + * Brief example of use: + * + * <code> + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * </code> + * + * @addtogroup API + */ class Services_JSON { /** @@ -813,6 +814,9 @@ class Services_JSON if (class_exists('PEAR_Error')) { + /** + * @addtogroup API + */ class Services_JSON_Error extends PEAR_Error { function Services_JSON_Error($message = 'unknown error', $code = null, @@ -826,6 +830,7 @@ if (class_exists('PEAR_Error')) { /** * @todo Ultimately, this class shall be descended from PEAR_Error + * @addtogroup API */ class Services_JSON_Error { diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php index 938ba032..add63362 100644 --- a/includes/api/ApiFormatPhp.php +++ b/includes/api/ApiFormatPhp.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 22, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiFormatBase.php'); } +/** + * @addtogroup API + */ class ApiFormatPhp extends ApiFormatBase { public function __construct($main, $format) { @@ -48,7 +50,7 @@ class ApiFormatPhp extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatPhp.php 17374 2006-11-03 06:53:47Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatPhp.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index e97b996c..bc720490 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 22, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiFormatBase.php'); } +/** + * @addtogroup API + */ class ApiFormatWddx extends ApiFormatBase { public function __construct($main, $format) { @@ -83,7 +85,7 @@ class ApiFormatWddx extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatWddx.php 17374 2006-11-03 06:53:47Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatWddx.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index 2326ba42..7d54b441 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 19, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiFormatBase.php'); } +/** + * @addtogroup API + */ class ApiFormatXml extends ApiFormatBase { private $mRootElemName = 'api'; @@ -139,7 +141,7 @@ class ApiFormatXml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 17374 2006-11-03 06:53:47Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index 2371903f..0107eb2b 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 19, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiFormatBase.php'); } +/** + * @addtogroup API + */ class ApiFormatYaml extends ApiFormatBase { public function __construct($main, $format) { @@ -48,7 +50,7 @@ class ApiFormatYaml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatYaml.php 17374 2006-11-03 06:53:47Z yurik $'; + return __CLASS__ . ': $Id: ApiFormatYaml.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiFormatYaml_spyc.php b/includes/api/ApiFormatYaml_spyc.php index 1ec8af48..a67bbb22 100644 --- a/includes/api/ApiFormatYaml_spyc.php +++ b/includes/api/ApiFormatYaml_spyc.php @@ -3,15 +3,14 @@ * Spyc -- A Simple PHP YAML Class * @version 0.2.3 -- 2006-02-04 * @author Chris Wanstrath <chris@ozmm.org> - * @link http://spyc.sourceforge.net/ + * @see http://spyc.sourceforge.net/ * @copyright Copyright 2005-2006 Chris Wanstrath * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @package Spyc */ /** * A node, used by Spyc for parsing YAML. - * @package Spyc + * @addtogroup API */ class YAMLNode { /**#@+ @@ -20,7 +19,7 @@ */ var $parent; var $id; - /**#@+*/ + /**#@-*/ /** * @access public * @var mixed @@ -59,7 +58,7 @@ * $parser = new Spyc; * $array = $parser->load($file); * </code> - * @package Spyc + * @addtogroup API */ class Spyc { @@ -340,7 +339,7 @@ var $_isInline; var $_dumpIndent; var $_dumpWordWrap; - /**#@+*/ + /**#@-*/ /**** Private Methods ****/ @@ -858,4 +857,4 @@ return $ret; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 33fb67fd..7c5144fd 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 6, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * @addtogroup API + */ class ApiHelp extends ApiBase { public function __construct($main, $action) { @@ -49,7 +51,7 @@ class ApiHelp extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiHelp.php 16757 2006-10-03 05:41:55Z yurik $'; + return __CLASS__ . ': $Id: ApiHelp.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index d9697dc3..147d37a1 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 19, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * @addtogroup API + */ class ApiLogin extends ApiBase { public function __construct($main, $action) { @@ -116,7 +118,7 @@ class ApiLogin extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 17065 2006-10-17 02:11:29Z yurik $'; + return __CLASS__ . ': $Id: ApiLogin.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 606f022b..9a6b0f83 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 4, 2006 * @@ -31,6 +30,7 @@ if (!defined('MEDIAWIKI')) { /** * This is the main API class, used for both external and internal processing. + * @addtogroup API */ class ApiMain extends ApiBase { @@ -336,7 +336,7 @@ class ApiMain extends ApiBase { public function getVersion() { $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiMain.php 17987 2006-11-29 05:45:03Z nickj $'; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 21402 2007-04-20 08:55:14Z nickj $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); @@ -346,8 +346,9 @@ class ApiMain extends ApiBase { } /** -* @desc This exception will be thrown when dieUsage is called to stop module execution. -*/ + * This exception will be thrown when dieUsage is called to stop module execution. + * @addtogroup API + */ class UsageException extends Exception { private $mCodestr; diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php index a5a13a7b..77f8b889 100644 --- a/includes/api/ApiOpenSearch.php +++ b/includes/api/ApiOpenSearch.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 13, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ("ApiBase.php"); } +/** + * @addtogroup API + */ class ApiOpenSearch extends ApiBase { public function __construct($main, $action) { @@ -103,7 +105,7 @@ class ApiOpenSearch extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiOpenSearch.php 17880 2006-11-23 08:25:56Z nickj $'; + return __CLASS__ . ': $Id: ApiOpenSearch.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 4728a9f8..dea87b88 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 24, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiPageSet extends ApiQueryBase { private $mAllPages; // [ns][dbkey] => page_id or 0 when missing @@ -308,7 +310,7 @@ class ApiPageSet extends ApiQueryBase { if($linkBatch->isEmpty()) return; - $db = & $this->getDB(); + $db = $this->getDB(); $set = $linkBatch->constructSet('page', $db); // Get pageIDs data from the `page` table @@ -331,7 +333,7 @@ class ApiPageSet extends ApiQueryBase { 'page_id' => $pageids ); - $db = & $this->getDB(); + $db = $this->getDB(); // Get pageIDs data from the `page` table $this->profileDBIn(); @@ -406,7 +408,7 @@ class ApiPageSet extends ApiQueryBase { if(empty($revids)) return; - $db = & $this->getDB(); + $db = $this->getDB(); $pageids = array(); $remaining = array_flip($revids); @@ -438,7 +440,7 @@ class ApiPageSet extends ApiQueryBase { private function resolvePendingRedirects() { if($this->mResolveRedirects) { - $db = & $this->getDB(); + $db = $this->getDB(); $pageFlds = $this->getPageTableFields(); // Repeat until all redirects have been resolved @@ -470,7 +472,7 @@ class ApiPageSet extends ApiQueryBase { private function getRedirectTargets() { $linkBatch = new LinkBatch(); - $db = & $this->getDB(); + $db = $this->getDB(); // find redirect targets for all redirect pages $this->profileDBIn(); @@ -592,7 +594,7 @@ class ApiPageSet extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPageSet.php 17929 2006-11-25 17:11:58Z tstarling $'; + return __CLASS__ . ': $Id: ApiPageSet.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index e7b7f351..6ee05085 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 7, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * @addtogroup API + */ class ApiQuery extends ApiBase { private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames; @@ -79,10 +81,10 @@ class ApiQuery extends ApiBase { $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames); } - public function & getDB() { + public function getDB() { if (!isset ($this->mSlaveDB)) { $this->profileDBIn(); - $this->mSlaveDB = & wfGetDB(DB_SLAVE); + $this->mSlaveDB = wfGetDB(DB_SLAVE); $this->profileDBOut(); } return $this->mSlaveDB; @@ -370,7 +372,7 @@ class ApiQuery extends ApiBase { public function getVersion() { $psModule = new ApiPageSet($this); $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 17374 2006-11-03 06:53:47Z yurik $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 21402 2007-04-20 08:55:14Z nickj $'; $vers[] = $psModule->getVersion(); return $vers; } diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 9c076e65..494f7707 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 25, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryAllpages extends ApiQueryGeneratorBase { public function __construct($query, $moduleName) { @@ -49,7 +51,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { private function run($resultPageSet = null) { wfProfileIn($this->getModuleProfileName() . '-getDB'); - $db = & $this->getDB(); + $db = $this->getDB(); wfProfileOut($this->getModuleProfileName() . '-getDB'); wfProfileIn($this->getModuleProfileName() . '-parseParams'); @@ -167,7 +169,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 17880 2006-11-23 08:25:56Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index 413068f8..1a6783a9 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 16, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ("ApiQueryBase.php"); } +/** + * @addtogroup API + */ class ApiQueryBacklinks extends ApiQueryGeneratorBase { private $rootTitle, $contRedirs, $contLevel, $contTitle, $contID; @@ -122,7 +124,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if ($redirect) $this->addWhereFld('page_is_redirect', 0); - $db = & $this->getDB(); + $db = $this->getDB(); if (!is_null($continue)) { $plfrm = intval($this->contID); if ($this->contLevel == 0) { @@ -352,7 +354,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBacklinks.php 17880 2006-11-23 08:25:56Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index ae4edf98..da07bb6c 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 7, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * @addtogroup API + */ abstract class ApiQueryBase extends ApiBase { private $mQueryModule, $tables, $where, $fields, $options; @@ -337,10 +339,13 @@ abstract class ApiQueryBase extends ApiBase { } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 17987 2006-11-29 05:45:03Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 21402 2007-04-20 08:55:14Z nickj $'; } } +/** + * @addtogroup API + */ abstract class ApiQueryGeneratorBase extends ApiQueryBase { private $mIsGenerator; diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index d93d37a2..77489a5f 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 25, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryInfo extends ApiQueryBase { public function __construct($query, $moduleName) { @@ -79,7 +81,7 @@ class ApiQueryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 17929 2006-11-25 17:11:58Z tstarling $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 243f96fa..d9f23758 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 16, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryLogEvents extends ApiQueryBase { public function __construct($query, $moduleName) { @@ -39,7 +41,7 @@ class ApiQueryLogEvents extends ApiQueryBase { $limit = $type = $start = $end = $dir = $user = $title = null; extract($this->extractRequestParams()); - $db = & $this->getDB(); + $db = $this->getDB(); list($tbl_logging, $tbl_page, $tbl_user) = $db->tableNamesN('logging', 'page', 'user'); @@ -167,7 +169,7 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLogEvents.php 17952 2006-11-27 08:36:57Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 38f51b05..25f7ff3e 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 19, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryRecentChanges extends ApiQueryBase { public function __construct($query, $moduleName) { @@ -87,7 +89,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $data = array (); $count = 0; - $db = & $this->getDB(); + $db = $this->getDB(); $res = $this->select(__METHOD__); while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { @@ -181,7 +183,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 17880 2006-11-23 08:25:56Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index e92b92c9..fc5f6241 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 7, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryRevisions extends ApiQueryBase { public function __construct($query, $moduleName) { @@ -149,7 +151,7 @@ class ApiQueryRevisions extends ApiQueryBase { $count = 0; $res = $this->select(__METHOD__); - $db = & $this->getDB(); + $db = $this->getDB(); while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { @@ -262,7 +264,7 @@ class ApiQueryRevisions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 19434 2007-01-18 02:04:11Z brion $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 9e8c11ff..fa185c97 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 25, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQuerySiteinfo extends ApiQueryBase { public function __construct($query, $moduleName) { @@ -110,7 +112,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 17265 2006-10-27 03:50:34Z yurik $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index 4f63cadb..05bfbb20 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -1,6 +1,5 @@ <?php - /* * Created on Oct 16, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryContributions extends ApiQueryBase { public function __construct($query, $moduleName) { @@ -44,7 +46,7 @@ class ApiQueryContributions extends ApiQueryBase { extract($this->extractRequestParams()); //Get a database instance - $db = & $this->getDB(); + $db = $this->getDB(); if (is_null($user)) $this->dieUsage("User parameter may not be empty", 'param_user'); @@ -169,7 +171,7 @@ class ApiQueryContributions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserContributions.php 17952 2006-11-27 08:36:57Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 67564d62..73c31abb 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 25, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiQueryBase.php'); } +/** + * @addtogroup API + */ class ApiQueryWatchlist extends ApiQueryGeneratorBase { public function __construct($query, $moduleName) { @@ -228,7 +230,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlist.php 17987 2006-11-29 05:45:03Z nickj $'; + return __CLASS__ . ': $Id: ApiQueryWatchlist.php 21402 2007-04-20 08:55:14Z nickj $'; } } ?> diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index c9bfcfb9..79fd34a1 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -1,6 +1,5 @@ <?php - /* * Created on Sep 4, 2006 * @@ -29,6 +28,9 @@ if (!defined('MEDIAWIKI')) { require_once ('ApiBase.php'); } +/** + * @addtogroup API + */ class ApiResult extends ApiBase { private $mData, $mIsRawMode; @@ -151,7 +153,7 @@ class ApiResult extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 17076 2006-10-18 05:35:24Z yurik $'; + return __CLASS__ . ': $Id: ApiResult.php 21402 2007-04-20 08:55:14Z nickj $'; } } -?>
\ No newline at end of file +?> diff --git a/includes/media/BMP.php b/includes/media/BMP.php new file mode 100644 index 00000000..9917856a --- /dev/null +++ b/includes/media/BMP.php @@ -0,0 +1,31 @@ +<?php +/** + * Handler for Microsoft bitmap format (bmp). It inherits most of the methods + * from ImageHandler, some of them had to be overriden cause gd does not + * support this format. + * + * @addtogroup Media + */ +class BmpHandler extends BitmapHandler { + + /* + * Get width and height from the bmp header. + */ + function getImageSize( $image, $filename ) { + $f = fopen( $filename, 'r' ); + if(!$f) return false; + $header = fread( $f, 54 ); + fclose($f); + + // Extract binary form of width and height from the header + $w = substr( $header, 18, 4); + $h = substr( $header, 22, 4); + + // Convert the unsigned long 32 bits (little endian): + $w = unpack( 'V' , $w ); + $h = unpack( 'V' , $h ); + return array( $w[1], $h[1] ); + } +} + +?> diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php new file mode 100644 index 00000000..3f3aabbf --- /dev/null +++ b/includes/media/Bitmap.php @@ -0,0 +1,236 @@ +<?php + +/** + * @addtogroup Media + */ +class BitmapHandler extends ImageHandler { + function normaliseParams( $image, &$params ) { + global $wgMaxImageArea; + if ( !parent::normaliseParams( $image, $params ) ) { + return false; + } + + $mimeType = $image->getMimeType(); + $srcWidth = $image->getWidth( $params['page'] ); + $srcHeight = $image->getHeight( $params['page'] ); + + # Don't thumbnail an image so big that it will fill hard drives and send servers into swap + # JPEG has the handy property of allowing thumbnailing without full decompression, so we make + # an exception for it. + if ( $mimeType !== 'image/jpeg' && + $srcWidth * $srcHeight > $wgMaxImageArea ) + { + return false; + } + + # Don't make an image bigger than the source + $params['physicalWidth'] = $params['width']; + $params['physicalHeight'] = $params['height']; + + if ( $params['physicalWidth'] >= $srcWidth ) { + $params['physicalWidth'] = $srcWidth; + $params['physicalHeight'] = $srcHeight; + return true; + } + + return true; + } + + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + global $wgUseImageMagick, $wgImageMagickConvertCommand; + global $wgCustomConvertCommand; + global $wgSharpenParameter, $wgSharpenReductionThreshold; + + if ( !$this->normaliseParams( $image, $params ) ) { + return new TransformParameterError( $params ); + } + $physicalWidth = $params['physicalWidth']; + $physicalHeight = $params['physicalHeight']; + $clientWidth = $params['width']; + $clientHeight = $params['height']; + $srcWidth = $image->getWidth(); + $srcHeight = $image->getHeight(); + $mimeType = $image->getMimeType(); + $srcPath = $image->getImagePath(); + $retval = 0; + wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" ); + + if ( $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) { + # normaliseParams (or the user) wants us to return the unscaled image + wfDebug( __METHOD__.": returning unscaled image\n" ); + return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath ); + } + + if ( $wgUseImageMagick ) { + $scaler = 'im'; + } elseif ( $wgCustomConvertCommand ) { + $scaler = 'custom'; + } elseif ( function_exists( 'imagecreatetruecolor' ) ) { + $scaler = 'gd'; + } else { + $scaler = 'client'; + } + + if ( $scaler == 'client' ) { + # Client-side image scaling, use the source URL + # Using the destination URL in a TRANSFORM_LATER request would be incorrect + return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath ); + } + + if ( $flags & self::TRANSFORM_LATER ) { + return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath ); + } + + if ( !wfMkdirParents( dirname( $dstPath ) ) ) { + return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, + wfMsg( 'thumbnail_dest_directory' ) ); + } + + if ( $scaler == 'im' ) { + # use ImageMagick + + $sharpen = ''; + if ( $mimeType == 'image/jpeg' ) { + $quality = "-quality 80"; // 80% + # Sharpening, see bug 6193 + if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) { + $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter ); + } + } elseif ( $mimeType == 'image/png' ) { + $quality = "-quality 95"; // zlib 9, adaptive filtering + } else { + $quality = ''; // default + } + + # Specify white background color, will be used for transparent images + # in Internet Explorer/Windows instead of default black. + + # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}". + # It seems that ImageMagick has a bug wherein it produces thumbnails of + # the wrong size in the second case. + + $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) . + " {$quality} -background white -size {$physicalWidth} ". + wfEscapeShellArg($srcPath) . + // Coalesce is needed to scale animated GIFs properly (bug 1017). + ' -coalesce ' . + // For the -resize option a "!" is needed to force exact size, + // or ImageMagick may decide your ratio is wrong and slice off + // a pixel. + " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) . + " -depth 8 $sharpen " . + wfEscapeShellArg($dstPath) . " 2>&1"; + wfDebug( __METHOD__.": running ImageMagick: $cmd\n"); + wfProfileIn( 'convert' ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'convert' ); + } elseif( $scaler == 'custom' ) { + # Use a custom convert command + # Variables: %s %d %w %h + $src = wfEscapeShellArg( $srcPath ); + $dst = wfEscapeShellArg( $dstPath ); + $cmd = $wgCustomConvertCommand; + $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames + $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size + wfDebug( __METHOD__.": Running custom convert command $cmd\n" ); + wfProfileIn( 'convert' ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'convert' ); + } else /* $scaler == 'gd' */ { + # Use PHP's builtin GD library functions. + # + # First find out what kind of file this is, and select the correct + # input routine for this. + + $typemap = array( + 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), + 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ), + 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), + 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), + 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), + ); + if( !isset( $typemap[$mimeType] ) ) { + $err = 'Image type not supported'; + wfDebug( "$err\n" ); + return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err ); + } + list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType]; + + if( !function_exists( $loader ) ) { + $err = "Incomplete GD library configuration: missing function $loader"; + wfDebug( "$err\n" ); + return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err ); + } + + $src_image = call_user_func( $loader, $srcPath ); + $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight ); + imagecopyresampled( $dst_image, $src_image, + 0,0,0,0, + $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) ); + call_user_func( $saveType, $dst_image, $dstPath ); + imagedestroy( $dst_image ); + imagedestroy( $src_image ); + $retval = 0; + } + + $removed = $this->removeBadFile( $dstPath, $retval ); + if ( $retval != 0 || $removed ) { + wfDebugLog( 'thumbnail', + sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', + wfHostname(), $retval, trim($err), $cmd ) ); + return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err ); + } else { + return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath ); + } + } + + static function imageJpegWrapper( $dst_image, $thumbPath ) { + imageinterlace( $dst_image ); + imagejpeg( $dst_image, $thumbPath, 95 ); + } + + + function getMetadata( $image, $filename ) { + global $wgShowEXIF; + if( $wgShowEXIF && file_exists( $filename ) ) { + $exif = new Exif( $filename ); + $data = $exif->getFilteredData(); + if ( $data ) { + $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version(); + return serialize( $data ); + } else { + return '0'; + } + } else { + return ''; + } + } + + function getMetadataType( $image ) { + return 'exif'; + } + + function isMetadataValid( $image, $metadata ) { + global $wgShowEXIF; + if ( !$wgShowEXIF ) { + # Metadata disabled and so an empty field is expected + return true; + } + if ( $metadata === '0' ) { + # Special value indicating that there is no EXIF data in the file + return true; + } + $exif = @unserialize( $metadata ); + if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) || + $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() ) + { + # Wrong version + wfDebug( __METHOD__.": wrong version\n" ); + return false; + } + return true; + } + +} + +?> diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php new file mode 100644 index 00000000..3c053a0c --- /dev/null +++ b/includes/media/DjVu.php @@ -0,0 +1,206 @@ +<?php + +/** + * @addtogroup Media + */ +class DjVuHandler extends ImageHandler { + function isEnabled() { + global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML; + if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) { + wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" ); + return false; + } else { + return true; + } + } + + function mustRender() { return true; } + function isMultiPage() { return true; } + + function validateParam( $name, $value ) { + if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) { + if ( $value <= 0 ) { + return false; + } else { + return true; + } + } else { + return false; + } + } + + function makeParamString( $params ) { + $page = isset( $params['page'] ) ? $params['page'] : 1; + if ( !isset( $params['width'] ) ) { + return false; + } + return "page{$page}-{$params['width']}px"; + } + + function parseParamString( $str ) { + $m = false; + if ( preg_match( '/^page(\d+)-(\d+)px$/', $str, $m ) ) { + return array( 'width' => $m[2], 'page' => $m[1] ); + } else { + return false; + } + } + + function getScriptParams( $params ) { + return array( + 'width' => $params['width'], + 'page' => $params['page'], + ); + } + + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + global $wgDjvuRenderer, $wgDjvuPostProcessor; + + // Fetch XML and check it, to give a more informative error message than the one which + // normaliseParams will inevitably give. + $xml = $image->getMetadata(); + if ( !$xml ) { + return new MediaTransformError( 'thumbnail_error', @$params['width'], @$params['height'], + wfMsg( 'djvu_no_xml' ) ); + } + + if ( !$this->normaliseParams( $image, $params ) ) { + return new TransformParameterError( $params ); + } + $width = $params['width']; + $height = $params['height']; + $srcPath = $image->getImagePath(); + $page = $params['page']; + $pageCount = $this->pageCount( $image ); + if ( $page > $this->pageCount( $image ) ) { + return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) ); + } + + if ( $flags & self::TRANSFORM_LATER ) { + return new ThumbnailImage( $dstUrl, $width, $height, $dstPath ); + } + + if ( !wfMkdirParents( dirname( $dstPath ) ) ) { + return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'thumbnail_dest_directory' ) ); + } + + # Use a subshell (brackets) to aggregate stderr from both pipeline commands + # before redirecting it to the overall stdout. This works in both Linux and Windows XP. + $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size={$width}x{$height} " . + wfEscapeShellArg( $srcPath ); + if ( $wgDjvuPostProcessor ) { + $cmd .= " | {$wgDjvuPostProcessor}"; + } + $cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1'; + wfProfileIn( 'ddjvu' ); + wfDebug( __METHOD__.": $cmd\n" ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'ddjvu' ); + + $removed = $this->removeBadFile( $dstPath, $retval ); + if ( $retval != 0 || $removed ) { + wfDebugLog( 'thumbnail', + sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', + wfHostname(), $retval, trim($err), $cmd ) ); + return new MediaTransformError( 'thumbnail_error', $width, $height, $err ); + } else { + return new ThumbnailImage( $dstUrl, $width, $height, $dstPath ); + } + } + + /** + * Cache an instance of DjVuImage in an Image object, return that instance + */ + function getDjVuImage( $image, $path ) { + if ( !$image ) { + $deja = new DjVuImage( $path ); + } elseif ( !isset( $image->dejaImage ) ) { + $deja = $image->dejaImage = new DjVuImage( $path ); + } else { + $deja = $image->dejaImage; + } + return $deja; + } + + /** + * Cache a document tree for the DjVu XML metadata + */ + function getMetaTree( $image ) { + if ( isset( $image->dejaMetaTree ) ) { + return $image->dejaMetaTree; + } + + $metadata = $image->getMetadata(); + if ( !$this->isMetadataValid( $image, $metadata ) ) { + wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" ); + return false; + } + wfProfileIn( __METHOD__ ); + + wfSuppressWarnings(); + try { + $image->dejaMetaTree = new SimpleXMLElement( $metadata ); + } catch( Exception $e ) { + wfDebug( "Bogus multipage XML metadata on '$image->name'\n" ); + // Set to false rather than null to avoid further attempts + $image->dejaMetaTree = false; + } + wfRestoreWarnings(); + wfProfileOut( __METHOD__ ); + return $image->dejaMetaTree; + } + + function getImageSize( $image, $path ) { + return $this->getDjVuImage( $image, $path )->getImageSize(); + } + + function getThumbType( $ext, $mime ) { + global $wgDjvuOutputExtension; + static $mime; + if ( !isset( $mime ) ) { + $magic = MimeMagic::singleton(); + $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension ); + } + return array( $wgDjvuOutputExtension, $mime ); + } + + function getMetadata( $image, $path ) { + wfDebug( "Getting DjVu metadata for $path\n" ); + return $this->getDjVuImage( $image, $path )->retrieveMetaData(); + } + + function getMetadataType( $image ) { + return 'djvuxml'; + } + + function isMetadataValid( $image, $metadata ) { + return !empty( $metadata ) && $metadata != serialize(array()); + } + + function pageCount( $image ) { + $tree = $this->getMetaTree( $image ); + if ( !$tree ) { + return false; + } + return count( $tree->xpath( '//OBJECT' ) ); + } + + function getPageDimensions( $image, $page ) { + $tree = $this->getMetaTree( $image ); + if ( !$tree ) { + return false; + } + + $o = $tree->BODY[0]->OBJECT[$page-1]; + if ( $o ) { + return array( + 'width' => intval( $o['width'] ), + 'height' => intval( $o['height'] ) + ); + } else { + return false; + } + } +} + +?> diff --git a/includes/media/Generic.php b/includes/media/Generic.php new file mode 100644 index 00000000..5254e0ea --- /dev/null +++ b/includes/media/Generic.php @@ -0,0 +1,298 @@ +<?php + +/** + * Media-handling base classes and generic functionality + */ + +/** + * Base media handler class + * + * @addtogroup Media + */ +abstract class MediaHandler { + const TRANSFORM_LATER = 1; + + /** + * Instance cache + */ + static $handlers = array(); + + /** + * Get a MediaHandler for a given MIME type from the instance cache + */ + static function getHandler( $type ) { + global $wgMediaHandlers; + if ( !isset( $wgMediaHandlers[$type] ) ) { + wfDebug( __METHOD__ . ": no handler found for $type.\n"); + return false; + } + $class = $wgMediaHandlers[$type]; + if ( !isset( self::$handlers[$class] ) ) { + self::$handlers[$class] = new $class; + if ( !self::$handlers[$class]->isEnabled() ) { + self::$handlers[$class] = false; + } + } + return self::$handlers[$class]; + } + + /* + * Validate a thumbnail parameter at parse time. + * Return true to accept the parameter, and false to reject it. + * If you return false, the parser will do something quiet and forgiving. + */ + abstract function validateParam( $name, $value ); + + /** + * Merge a parameter array into a string appropriate for inclusion in filenames + */ + abstract function makeParamString( $params ); + + /** + * Parse a param string made with makeParamString back into an array + */ + abstract function parseParamString( $str ); + + /** + * Changes the parameter array as necessary, ready for transformation. + * Should be idempotent. + * Returns false if the parameters are unacceptable and the transform should fail + */ + abstract function normaliseParams( $image, &$params ); + + /** + * Get an image size array like that returned by getimagesize(), or false if it + * can't be determined. + * + * @param Image $image The image object, or false if there isn't one + * @param string $fileName The filename + * @return array + */ + abstract function getImageSize( $image, $path ); + + /** + * Get handler-specific metadata which will be saved in the img_metadata field. + * + * @param Image $image The image object, or false if there isn't one + * @param string $fileName The filename + * @return string + */ + function getMetadata( $image, $path ) { return ''; } + + /** + * Get a string describing the type of metadata, for display purposes. + */ + function getMetadataType( $image ) { return false; } + + /** + * Check if the metadata string is valid for this handler. + * If it returns false, Image will reload the metadata from the file and update the database + */ + function isMetadataValid( $image, $metadata ) { return true; } + + /** + * Get a MediaTransformOutput object representing the transformed output. Does not + * actually do the transform. + * + * @param Image $image The image object + * @param string $dstPath Filesystem destination path + * @param string $dstUrl Destination URL to use in output HTML + * @param array $params Arbitrary set of parameters validated by $this->validateParam() + */ + function getTransform( $image, $dstPath, $dstUrl, $params ) { + return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER ); + } + + /** + * Get a MediaTransformOutput object representing the transformed output. Does the + * transform unless $flags contains self::TRANSFORM_LATER. + * + * @param Image $image The image object + * @param string $dstPath Filesystem destination path + * @param string $dstUrl Destination URL to use in output HTML + * @param array $params Arbitrary set of parameters validated by $this->validateParam() + * @param integer $flags A bitfield, may contain self::TRANSFORM_LATER + */ + abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ); + + /** + * Get the thumbnail extension and MIME type for a given source MIME type + * @return array thumbnail extension and MIME type + */ + function getThumbType( $ext, $mime ) { + return array( $ext, $mime ); + } + + /** + * True if the handled types can be transformed + */ + function canRender() { return true; } + /** + * True if handled types cannot be displayed directly in a browser + * but can be rendered + */ + function mustRender() { return false; } + /** + * True if the type has multi-page capabilities + */ + function isMultiPage() { return false; } + /** + * Page count for a multi-page document, false if unsupported or unknown + */ + function pageCount() { return false; } + /** + * False if the handler is disabled for all files + */ + function isEnabled() { return true; } + + /** + * Get an associative array of page dimensions + * Currently "width" and "height" are understood, but this might be + * expanded in the future. + * Returns false if unknown or if the document is not multi-page. + */ + function getPageDimensions( $image, $page ) { + $gis = $this->getImageSize( $image, $image->getImagePath() ); + return array( + 'width' => $gis[0], + 'height' => $gis[1] + ); + } +} + +/** + * Media handler abstract base class for images + * + * @addtogroup Media + */ +abstract class ImageHandler extends MediaHandler { + function validateParam( $name, $value ) { + if ( in_array( $name, array( 'width', 'height' ) ) ) { + if ( $value <= 0 ) { + return false; + } else { + return true; + } + } else { + return false; + } + } + + function makeParamString( $params ) { + if ( isset( $params['physicalWidth'] ) ) { + $width = $params['physicalWidth']; + } else { + $width = $params['width']; + } + # Removed for ProofreadPage + #$width = intval( $width ); + return "{$width}px"; + } + + function parseParamString( $str ) { + $m = false; + if ( preg_match( '/^(\d+)px$/', $str, $m ) ) { + return array( 'width' => $m[1] ); + } else { + return false; + } + } + + function getScriptParams( $params ) { + return array( 'width' => $params['width'] ); + } + + function normaliseParams( $image, &$params ) { + $mimeType = $image->getMimeType(); + + if ( !isset( $params['width'] ) ) { + return false; + } + if ( !isset( $params['page'] ) ) { + $params['page'] = 1; + } + $srcWidth = $image->getWidth( $params['page'] ); + $srcHeight = $image->getHeight( $params['page'] ); + if ( isset( $params['height'] ) && $params['height'] != -1 ) { + if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) { + $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] ); + } + } + $params['height'] = Image::scaleHeight( $srcWidth, $srcHeight, $params['width'] ); + if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) { + return false; + } + return true; + } + + /** + * Get a transform output object without actually doing the transform + */ + function getTransform( $image, $dstPath, $dstUrl, $params ) { + return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER ); + } + + /** + * Validate thumbnail parameters and fill in the correct height + * + * @param integer &$width Specified width (input/output) + * @param integer &$height Height (output only) + * @return false to indicate that an error should be returned to the user. + */ + function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) { + $width = intval( $width ); + + # Sanity check $width + if( $width <= 0) { + wfDebug( __METHOD__.": Invalid destination width: $width\n" ); + return false; + } + if ( $srcWidth <= 0 ) { + wfDebug( __METHOD__.": Invalid source width: $srcWidth\n" ); + return false; + } + + $height = Image::scaleHeight( $srcWidth, $srcHeight, $width ); + return true; + } + + function getScriptedTransform( $image, $script, $params ) { + if ( !$this->normaliseParams( $image, $params ) ) { + return false; + } + $url = $script . '&' . wfArrayToCGI( $this->getScriptParams( $params ) ); + return new ThumbnailImage( $url, $params['width'], $params['height'] ); + } + + /** + * Check for zero-sized thumbnails. These can be generated when + * no disk space is available or some other error occurs + * + * @param $dstPath The location of the suspect file + * @param $retval Return value of some shell process, file will be deleted if this is non-zero + * @return true if removed, false otherwise + */ + function removeBadFile( $dstPath, $retval = 0 ) { + $removed = false; + if( file_exists( $dstPath ) ) { + $thumbstat = stat( $dstPath ); + if( $thumbstat['size'] == 0 || $retval != 0 ) { + wfDebugLog( 'thumbnail', + sprintf( 'Removing bad %d-byte thumbnail "%s"', + $thumbstat['size'], $dstPath ) ); + unlink( $dstPath ); + return true; + } + } + return false; + } + + function getImageSize( $image, $path ) { + wfSuppressWarnings(); + $gis = getimagesize( $path ); + wfRestoreWarnings(); + return $gis; + } +} + +?> diff --git a/includes/media/SVG.php b/includes/media/SVG.php new file mode 100644 index 00000000..5307e269 --- /dev/null +++ b/includes/media/SVG.php @@ -0,0 +1,97 @@ +<?php + +/** + * @addtogroup Media + */ +class SvgHandler extends ImageHandler { + function isEnabled() { + global $wgSVGConverters, $wgSVGConverter; + if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) { + wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" ); + return false; + } else { + return true; + } + } + + function mustRender() { + return true; + } + + function normaliseParams( $image, &$params ) { + global $wgSVGMaxSize; + if ( !parent::normaliseParams( $image, $params ) ) { + return false; + } + + # Don't make an image bigger than wgMaxSVGSize + $params['physicalWidth'] = $params['width']; + $params['physicalHeight'] = $params['height']; + if ( $params['physicalWidth'] > $wgSVGMaxSize ) { + $srcWidth = $image->getWidth( $params['page'] ); + $srcHeight = $image->getHeight( $params['page'] ); + $params['physicalWidth'] = $wgSVGMaxSize; + $params['physicalHeight'] = Image::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize ); + } + return true; + } + + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath; + + if ( !$this->normaliseParams( $image, $params ) ) { + return new TransformParameterError( $params ); + } + $clientWidth = $params['width']; + $clientHeight = $params['height']; + $physicalWidth = $params['physicalWidth']; + $physicalHeight = $params['physicalHeight']; + $srcWidth = $image->getWidth(); + $srcHeight = $image->getHeight(); + $srcPath = $image->getImagePath(); + + if ( $flags & self::TRANSFORM_LATER ) { + return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath ); + } + + if ( !wfMkdirParents( dirname( $dstPath ) ) ) { + return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, + wfMsg( 'thumbnail_dest_directory' ) ); + } + + $err = false; + if( isset( $wgSVGConverters[$wgSVGConverter] ) ) { + $cmd = str_replace( + array( '$path/', '$width', '$height', '$input', '$output' ), + array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "", + intval( $physicalWidth ), + intval( $physicalHeight ), + wfEscapeShellArg( $srcPath ), + wfEscapeShellArg( $dstPath ) ), + $wgSVGConverters[$wgSVGConverter] ) . " 2>&1"; + wfProfileIn( 'rsvg' ); + wfDebug( __METHOD__.": $cmd\n" ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'rsvg' ); + } + + $removed = $this->removeBadFile( $dstPath, $retval ); + if ( $retval != 0 || $removed ) { + wfDebugLog( 'thumbnail', + sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', + wfHostname(), $retval, trim($err), $cmd ) ); + return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err ); + } else { + return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath ); + } + } + + function getImageSize( $image, $path ) { + return wfGetSVGsize( $path ); + } + + function getThumbType( $ext, $mime ) { + return array( 'png', 'image/png' ); + } +} +?> diff --git a/includes/memcached-client.php b/includes/memcached-client.php index 2c5cc6be..1f4bac00 100644 --- a/includes/memcached-client.php +++ b/includes/memcached-client.php @@ -91,7 +91,7 @@ define("COMPRESSION_SAVINGS", 0.20); * memcached client class implemented using (p)fsockopen() * * @author Ryan T. Dean <rtdean@cytherianage.net> - * @package memcached-client + * @addtogroup Cache */ class memcached { diff --git a/includes/mime.info b/includes/mime.info index 9b05f089..a960f023 100644 --- a/includes/mime.info +++ b/includes/mime.info @@ -7,7 +7,7 @@ image/gif [BITMAP] -image/png [BITMAP] +image/png image/x-png [BITMAP] image/ief [BITMAP] image/jpeg [BITMAP] image/xbm [BITMAP] @@ -19,7 +19,7 @@ image/x-portable-graymap image/x-portable-greymap [BITMAP] image/x-bmp image/bmp application/x-bmp application/bmp [BITMAP] image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP] -image/svg image/svg+xml application/svg+xml application/svg [DRAWING] +image/svg+xml application/svg+xml application/svg image/svg [DRAWING] application/postscript [DRAWING] application/x-latex [DRAWING] application/x-tex [DRAWING] @@ -36,6 +36,7 @@ audio/x-realaudio [AUDIO] video/mpeg application/mpeg [VIDEO] video/ogg [VIDEO] video/x-sgi-video [VIDEO] +video/x-flv [VIDEO] application/ogg application/x-ogg audio/ogg audio/x-ogg video/ogg video/x-ogg [MULTIMEDIA] diff --git a/includes/mime.types b/includes/mime.types index 3a7fa39c..19a61517 100644 --- a/includes/mime.types +++ b/includes/mime.types @@ -111,6 +111,7 @@ video/mpeg mpeg mpg mpe video/ogg ogm ogg video/quicktime qt mov video/vnd.mpegurl mxu +video/x-flv flv video/x-msvideo avi video/x-ogg ogm ogg video/x-sgi-movie movie diff --git a/includes/normal/CleanUpTest.php b/includes/normal/CleanUpTest.php index 30ec6a95..cc6f0737 100644 --- a/includes/normal/CleanUpTest.php +++ b/includes/normal/CleanUpTest.php @@ -17,15 +17,6 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html -/** - * Additional tests for UtfNormal::cleanUp() function, inclusion - * regression checks for known problems. - * - * Requires PHPUnit. - * - * @package UtfNormal - * @private - */ if( php_sapi_name() != 'cli' ) { die( "Run me from the command line please.\n" ); @@ -38,20 +29,20 @@ if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) { #ini_set( 'memory_limit', '40M' ); -require_once( 'PHPUnit.php' ); -require_once( 'UtfNormal.php' ); +require_once 'PHPUnit/Framework.php'; +require_once 'PHPUnit/TextUI/TestRunner.php'; + +require_once 'UtfNormal.php'; /** - * @package UtfNormal + * Additional tests for UtfNormal::cleanUp() function, inclusion + * regression checks for known problems. + * Requires PHPUnit. + * + * @addtogroup UtfNormal + * @private */ -class CleanUpTest extends PHPUnit_TestCase { - /** - * @param $name String: FIXME - */ - function CleanUpTest( $name ) { - $this->PHPUnit_TestCase( $name ); - } - +class CleanUpTest extends PHPUnit_Framework_TestCase { /** @todo document */ function setUp() { } @@ -412,9 +403,8 @@ class CleanUpTest extends PHPUnit_TestCase { } -$suite = new PHPUnit_TestSuite( 'CleanUpTest' ); -$result = PHPUnit::run( $suite ); -echo $result->toString(); +$suite = new PHPUnit_Framework_TestSuite( 'CleanUpTest' ); +$result = PHPUnit_TextUI_TestRunner::run( $suite ); if( !$result->wasSuccessful() ) { exit( -1 ); diff --git a/includes/normal/Makefile b/includes/normal/Makefile index fcdf2380..887f3ce6 100644 --- a/includes/normal/Makefile +++ b/includes/normal/Makefile @@ -1,11 +1,21 @@ .PHONY : all test testutf8 testclean icutest bench icubench clean distclean -FETCH=wget -#FETCH=fetch -BASE=http://www.unicode.org/Public/UNIDATA +## Latest greatest version of Unicode +## May cause confusion if running test suite from these files +## when the data was generated from a previous version. +#BASE=http://www.unicode.org/Public/UNIDATA + +# Explicitly using Unicode 5.0 +BASE=http://www.unicode.org/Public/5.0.0/ucd/ + +# Can override to php-cli or php5 or whatevah PHP=php #PHP=php-cli +# Some nice tool to grab URLs with +FETCH=wget +#FETCH=fetch + all : UtfNormalData.inc UtfNormalData.inc : UtfNormalGenerate.php UtfNormalUtil.php UnicodeData.txt CompositionExclusions.txt NormalizationCorrections.txt DerivedNormalizationProps.txt @@ -20,7 +30,7 @@ testutf8 : Utf8Test.php UTF-8-test.txt testclean : CleanUpTest.php $(PHP) CleanUpTest.php -bench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/sociology.txt testdata/bulgakov.txt +bench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/young.txt testdata/bulgakov.txt $(PHP) UtfNormalBench.php icutest : UtfNormalData.inc NormalizationTest.txt @@ -28,14 +38,14 @@ icutest : UtfNormalData.inc NormalizationTest.txt $(PHP) CleanUpTest.php --icu $(PHP) UtfNormalTest.php --icu -icubench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/sociology.txt testdata/bulgakov.txt +icubench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/young.txt testdata/bulgakov.txt $(PHP) UtfNormalBench.php --icu clean : - rm -f UtfNormalData.inc + rm -f UtfNormalData.inc UtfNormalDataK.inc distclean : clean - rm -f CompositionExclusions.txt NormalizationTest.txt NormalizationCorrections.txt UnicodeData.txt DerivedNormalizationProps.txt + rm -f CompositionExclusions.txt NormalizationTest.txt NormalizationCorrections.txt UnicodeData.txt DerivedNormalizationProps.txt UTF-8-test.txt # The Unicode data files... CompositionExclusions.txt : @@ -57,16 +67,16 @@ UTF-8-test.txt : $(FETCH) http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt testdata/berlin.txt : - mkdir -p testdata && wget -U MediaWiki/test -O testdata/berlin.txt "http://de.wikipedia.org/w/wiki.phtml?title=Berlin&oldid=2775712&action=raw" + mkdir -p testdata && wget -U MediaWiki/test -O testdata/berlin.txt "http://de.wikipedia.org/w/index.php?title=Berlin&oldid=2775712&action=raw" testdata/washington.txt : - mkdir -p testdata && wget -U MediaWiki/test -O testdata/washington.txt "http://en.wikipedia.org/w/wiki.phtml?title=Washington%2C_DC&oldid=6370218&action=raw" + mkdir -p testdata && wget -U MediaWiki/test -O testdata/washington.txt "http://en.wikipedia.org/w/index.php?title=Washington%2C_D.C.&oldid=6370218&action=raw" testdata/tokyo.txt : - mkdir -p testdata && wget -U MediaWiki/test -O testdata/tokyo.txt "http://ja.wikipedia.org/w/wiki.phtml?title=%E6%9D%B1%E4%BA%AC%E9%83%BD&oldid=940880&action=raw" + mkdir -p testdata && wget -U MediaWiki/test -O testdata/tokyo.txt "http://ja.wikipedia.org/w/index.php?title=%E6%9D%B1%E4%BA%AC%E9%83%BD&oldid=940880&action=raw" -testdata/sociology.txt : - mkdir -p testdata && wget -U MediaWiki/test -O testdata/sociology.txt "http://ko.wikipedia.org/w/wiki.phtml?title=%EC%82%AC%ED%9A%8C%ED%95%99&oldid=16409&action=raw" +testdata/young.txt : + mkdir -p testdata && wget -U MediaWiki/test -O testdata/young.txt "http://ko.wikipedia.org/w/index.php?title=%EC%9D%B4%EC%88%98%EC%98%81&oldid=627688&action=raw" testdata/bulgakov.txt : - mkdir -p testdata && wget -U MediaWiki/test -O testdata/bulgakov.txt "http://ru.wikipedia.org/w/wiki.phtml?title=%D0%91%D1%83%D0%BB%D0%B3%D0%B0%D0%BA%D0%BE%D0%B2%2C_%D0%A1%D0%B5%D1%80%D0%B3%D0%B5%D0%B9_%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D0%B5%D0%B2%D0%B8%D1%87&oldid=17704&action=raw" + mkdir -p testdata && wget -U MediaWiki/test -O testdata/bulgakov.txt "http://ru.wikipedia.org/w/index.php?title=%D0%91%D1%83%D0%BB%D0%B3%D0%B0%D0%BA%D0%BE%D0%B2%2C_%D0%A1%D0%B5%D1%80%D0%B3%D0%B5%D0%B9_%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D0%B5%D0%B2%D0%B8%D1%87&oldid=17704&action=raw" diff --git a/includes/normal/README b/includes/normal/README index f8207a1b..a17aa7da 100644 --- a/includes/normal/README +++ b/includes/normal/README @@ -32,6 +32,10 @@ have been changed or you remove it. data from from the net if necessary. If it reports failure, something is going wrong! +You may have to set up PHPUnit first. + +$ pear channel-discover pear.phpunit.de +$ pear install phpunit/PHPUnit == Benchmarks == diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php index b86ab7c3..9ccbc01d 100644 --- a/includes/normal/RandomTest.php +++ b/includes/normal/RandomTest.php @@ -22,7 +22,7 @@ * UtfNormal::cleanUp() code paths, and checks to see if there's a * difference. Will run forever until it finds one or you kill it. * - * @package UtfNormal + * @addtogroup UtfNormal * @access private */ @@ -69,7 +69,7 @@ function showDiffs( $a, $b ) { $formatter = new TableDiffFormatter(); $funky = $formatter->format( $diffs ); $matches = array(); - preg_match_all( '/<span class="diffchange">(.*?)<\/span>/', $funky, $matches ); + preg_match_all( '/<(?:ins|del) class="diffchange">(.*?)<\/(?:ins|del)>/', $funky, $matches ); foreach( $matches[1] as $bit ) { $hex = bin2hex( $bit ); echo "\t$hex\n"; diff --git a/includes/normal/Utf8Test.php b/includes/normal/Utf8Test.php index 34ab69c8..fc2e7776 100644 --- a/includes/normal/Utf8Test.php +++ b/includes/normal/Utf8Test.php @@ -21,7 +21,7 @@ * Runs the UTF-8 decoder test at: * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt * - * @package UtfNormal + * @addtogroup UtfNormal * @access private */ diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php index d8eac7b8..43bbafd8 100644 --- a/includes/normal/UtfNormal.php +++ b/includes/normal/UtfNormal.php @@ -17,21 +17,6 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html -/** - * Unicode normalization routines for working with UTF-8 strings. - * Currently assumes that input strings are valid UTF-8! - * - * Not as fast as I'd like, but should be usable for most purposes. - * UtfNormal::toNFC() will bail early if given ASCII text or text - * it can quickly deterimine is already normalized. - * - * All functions can be called static. - * - * See description of forms at http://www.unicode.org/reports/tr15/ - * - * @package UtfNormal - */ - /** */ require_once dirname(__FILE__).'/UtfNormalUtil.php'; @@ -111,8 +96,18 @@ define( 'UNORM_FCD', 6 ); define( 'NORMALIZE_ICU', function_exists( 'utf8_normalize' ) ); /** + * Unicode normalization routines for working with UTF-8 strings. + * Currently assumes that input strings are valid UTF-8! * - * @package MediaWiki + * Not as fast as I'd like, but should be usable for most purposes. + * UtfNormal::toNFC() will bail early if given ASCII text or text + * it can quickly deterimine is already normalized. + * + * All functions can be called static. + * + * See description of forms at http://www.unicode.org/reports/tr15/ + * + * @addtogroup UtfNormal */ class UtfNormal { /** @@ -124,7 +119,7 @@ class UtfNormal { * * @param string $string a UTF-8 string * @return string a clean, shiny, normalized UTF-8 string - * @static + * @static */ static function cleanUp( $string ) { if( NORMALIZE_ICU ) { @@ -226,7 +221,7 @@ class UtfNormal { static function loadData() { global $utfCombiningClass; if( !isset( $utfCombiningClass ) ) { - require_once( 'UtfNormalData.inc' ); + require_once( dirname(__FILE__) . '/UtfNormalData.inc' ); } } @@ -635,7 +630,11 @@ class UtfNormal { } if( isset( $utfCombiningClass[$c] ) ) { $lastClass = $utfCombiningClass[$c]; - @$combiners[$lastClass] .= $c; + if( isset( $combiners[$lastClass] ) ) { + $combiners[$lastClass] .= $c; + } else { + $combiners[$lastClass] = $c; + } continue; } } @@ -805,4 +804,4 @@ class UtfNormal { } } -?>
\ No newline at end of file +?> diff --git a/includes/normal/UtfNormalBench.php b/includes/normal/UtfNormalBench.php index a5eb267e..c394f4d8 100644 --- a/includes/normal/UtfNormalBench.php +++ b/includes/normal/UtfNormalBench.php @@ -20,7 +20,7 @@ /** * Approximate benchmark for some basic operations. * - * @package UtfNormal + * @addtogroup UtfNormal * @access private */ @@ -43,7 +43,7 @@ $testfiles = array( 'testdata/berlin.txt' => 'German text', 'testdata/bulgakov.txt' => 'Russian text', 'testdata/tokyo.txt' => 'Japanese text', - 'testdata/sociology.txt' => 'Korean text' + 'testdata/young.txt' => 'Korean text' ); $normalizer = new UtfNormal; UtfNormal::loadData(); @@ -100,7 +100,11 @@ function benchmarkForm( &$u, &$data, $form ) { $rate = intval( strlen( $data ) / $delta ); $same = (0 == strcmp( $data, $out ) ); - printf( " %20s %6.1fms %8d bytes/s (%s)\n", $form, $delta*1000.0, $rate, ($same ? 'no change' : 'changed' ) ); + printf( " %20s %6.1fms %12s bytes/s (%s)\n", + $form, + $delta*1000.0, + number_format( $rate ), + ($same ? 'no change' : 'changed' ) ); return $out; } diff --git a/includes/normal/UtfNormalData.inc b/includes/normal/UtfNormalData.inc index 6216d1a3..91c15769 100644 --- a/includes/normal/UtfNormalData.inc +++ b/includes/normal/UtfNormalData.inc @@ -2,12 +2,11 @@ /** * This file was automatically generated -- do not edit! * Run UtfNormalGenerate.php to create this file again (make clean && make) - * @package MediaWiki */ /** */ global $utfCombiningClass, $utfCanonicalComp, $utfCanonicalDecomp, $utfCheckNFC; -$utfCombiningClass = unserialize( 'a:384:{s:2:"̀";i:230;s:2:"́";i:230;s:2:"̂";i:230;s:2:"̃";i:230;s:2:"̄";i:230;s:2:"̅";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"̊";i:230;s:2:"̋";i:230;s:2:"̌";i:230;s:2:"̍";i:230;s:2:"̎";i:230;s:2:"̏";i:230;s:2:"̐";i:230;s:2:"̑";i:230;s:2:"̒";i:230;s:2:"̓";i:230;s:2:"̔";i:230;s:2:"̕";i:232;s:2:"̖";i:220;s:2:"̗";i:220;s:2:"̘";i:220;s:2:"̙";i:220;s:2:"̚";i:232;s:2:"̛";i:216;s:2:"̜";i:220;s:2:"̝";i:220;s:2:"̞";i:220;s:2:"̟";i:220;s:2:"̠";i:220;s:2:"̡";i:202;s:2:"̢";i:202;s:2:"̣";i:220;s:2:"̤";i:220;s:2:"̥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"̩";i:220;s:2:"̪";i:220;s:2:"̫";i:220;s:2:"̬";i:220;s:2:"̭";i:220;s:2:"̮";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"̴";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"̷";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"̻";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"̿";i:230;s:2:"̀";i:230;s:2:"́";i:230;s:2:"͂";i:230;s:2:"̓";i:230;s:2:"̈́";i:230;s:2:"ͅ";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"͊";i:230;s:2:"͋";i:230;s:2:"͌";i:230;s:2:"͍";i:220;s:2:"͎";i:220;s:2:"͐";i:230;s:2:"͑";i:230;s:2:"͒";i:230;s:2:"͓";i:220;s:2:"͔";i:220;s:2:"͕";i:220;s:2:"͖";i:220;s:2:"͗";i:230;s:2:"͘";i:232;s:2:"͙";i:220;s:2:"͚";i:220;s:2:"͛";i:230;s:2:"͜";i:233;s:2:"͝";i:234;s:2:"͞";i:234;s:2:"͟";i:233;s:2:"͠";i:234;s:2:"͡";i:234;s:2:"͢";i:233;s:2:"ͣ";i:230;s:2:"ͤ";i:230;s:2:"ͥ";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"ͩ";i:230;s:2:"ͪ";i:230;s:2:"ͫ";i:230;s:2:"ͬ";i:230;s:2:"ͭ";i:230;s:2:"ͮ";i:230;s:2:"ͯ";i:230;s:2:"҃";i:230;s:2:"҄";i:230;s:2:"҅";i:230;s:2:"҆";i:230;s:2:"֑";i:220;s:2:"֒";i:230;s:2:"֓";i:230;s:2:"֔";i:230;s:2:"֕";i:230;s:2:"֖";i:220;s:2:"֗";i:230;s:2:"֘";i:230;s:2:"֙";i:230;s:2:"֚";i:222;s:2:"֛";i:220;s:2:"֜";i:230;s:2:"֝";i:230;s:2:"֞";i:230;s:2:"֟";i:230;s:2:"֠";i:230;s:2:"֡";i:230;s:2:"֢";i:220;s:2:"֣";i:220;s:2:"֤";i:220;s:2:"֥";i:220;s:2:"֦";i:220;s:2:"֧";i:220;s:2:"֨";i:230;s:2:"֩";i:230;s:2:"֪";i:220;s:2:"֫";i:230;s:2:"֬";i:230;s:2:"֭";i:222;s:2:"֮";i:228;s:2:"֯";i:230;s:2:"ְ";i:10;s:2:"ֱ";i:11;s:2:"ֲ";i:12;s:2:"ֳ";i:13;s:2:"ִ";i:14;s:2:"ֵ";i:15;s:2:"ֶ";i:16;s:2:"ַ";i:17;s:2:"ָ";i:18;s:2:"ֹ";i:19;s:2:"ֻ";i:20;s:2:"ּ";i:21;s:2:"ֽ";i:22;s:2:"ֿ";i:23;s:2:"ׁ";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"ׅ";i:220;s:2:"ׇ";i:18;s:2:"ؐ";i:230;s:2:"ؑ";i:230;s:2:"ؒ";i:230;s:2:"ؓ";i:230;s:2:"ؔ";i:230;s:2:"ؕ";i:230;s:2:"ً";i:27;s:2:"ٌ";i:28;s:2:"ٍ";i:29;s:2:"َ";i:30;s:2:"ُ";i:31;s:2:"ِ";i:32;s:2:"ّ";i:33;s:2:"ْ";i:34;s:2:"ٓ";i:230;s:2:"ٔ";i:230;s:2:"ٕ";i:220;s:2:"ٖ";i:220;s:2:"ٗ";i:230;s:2:"٘";i:230;s:2:"ٙ";i:230;s:2:"ٚ";i:230;s:2:"ٛ";i:230;s:2:"ٜ";i:220;s:2:"ٝ";i:230;s:2:"ٞ";i:230;s:2:"ٰ";i:35;s:2:"ۖ";i:230;s:2:"ۗ";i:230;s:2:"ۘ";i:230;s:2:"ۙ";i:230;s:2:"ۚ";i:230;s:2:"ۛ";i:230;s:2:"ۜ";i:230;s:2:"۟";i:230;s:2:"۠";i:230;s:2:"ۡ";i:230;s:2:"ۢ";i:230;s:2:"ۣ";i:220;s:2:"ۤ";i:230;s:2:"ۧ";i:230;s:2:"ۨ";i:230;s:2:"۪";i:220;s:2:"۫";i:230;s:2:"۬";i:230;s:2:"ۭ";i:220;s:2:"ܑ";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"ܴ";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"ܷ";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"ܻ";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"ܿ";i:230;s:2:"݀";i:230;s:2:"݁";i:230;s:2:"݂";i:220;s:2:"݃";i:230;s:2:"݄";i:220;s:2:"݅";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"݊";i:230;s:3:"़";i:7;s:3:"्";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"্";i:9;s:3:"਼";i:7;s:3:"੍";i:9;s:3:"઼";i:7;s:3:"્";i:9;s:3:"଼";i:7;s:3:"୍";i:9;s:3:"்";i:9;s:3:"్";i:9;s:3:"ౕ";i:84;s:3:"ౖ";i:91;s:3:"಼";i:7;s:3:"್";i:9;s:3:"്";i:9;s:3:"්";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"፟";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"៝";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"᷀";i:230;s:3:"᷁";i:230;s:3:"᷂";i:220;s:3:"᷃";i:230;s:3:"⃐";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"゙";i:8;s:3:"゚";i:8;s:3:"꠆";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:4:"𐨍";i:220;s:4:"𐨏";i:230;s:4:"𐨸";i:230;s:4:"𐨹";i:1;s:4:"𐨺";i:220;s:4:"𐨿";i:9;s:4:"𝅥";i:216;s:4:"𝅦";i:216;s:4:"𝅧";i:1;s:4:"𝅨";i:1;s:4:"𝅩";i:1;s:4:"𝅭";i:226;s:4:"𝅮";i:216;s:4:"𝅯";i:216;s:4:"𝅰";i:216;s:4:"𝅱";i:216;s:4:"𝅲";i:216;s:4:"𝅻";i:220;s:4:"𝅼";i:220;s:4:"𝅽";i:220;s:4:"𝅾";i:220;s:4:"𝅿";i:220;s:4:"𝆀";i:220;s:4:"𝆁";i:220;s:4:"𝆂";i:220;s:4:"𝆅";i:230;s:4:"𝆆";i:230;s:4:"𝆇";i:230;s:4:"𝆈";i:230;s:4:"𝆉";i:230;s:4:"𝆊";i:220;s:4:"𝆋";i:220;s:4:"𝆪";i:230;s:4:"𝆫";i:230;s:4:"𝆬";i:230;s:4:"𝆭";i:230;s:4:"𝉂";i:230;s:4:"𝉃";i:230;s:4:"𝉄";i:230;}' ); -$utfCanonicalComp = unserialize( 'a:1851:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s:3:"Ǐ";s:2:"Ǐ";s:3:"ǐ";s:2:"ǐ";s:3:"Ǒ";s:2:"Ǒ";s:3:"ǒ";s:2:"ǒ";s:3:"Ǔ";s:2:"Ǔ";s:3:"ǔ";s:2:"ǔ";s:4:"Ǖ";s:2:"Ǖ";s:4:"ǖ";s:2:"ǖ";s:4:"Ǘ";s:2:"Ǘ";s:4:"ǘ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ǚ";s:4:"ǚ";s:2:"ǚ";s:4:"Ǜ";s:2:"Ǜ";s:4:"ǜ";s:2:"ǜ";s:4:"Ǟ";s:2:"Ǟ";s:4:"ǟ";s:2:"ǟ";s:4:"Ǡ";s:2:"Ǡ";s:4:"ǡ";s:2:"ǡ";s:4:"Ǣ";s:2:"Ǣ";s:4:"ǣ";s:2:"ǣ";s:3:"Ǧ";s:2:"Ǧ";s:3:"ǧ";s:2:"ǧ";s:3:"Ǩ";s:2:"Ǩ";s:3:"ǩ";s:2:"ǩ";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"ǫ";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"ǭ";s:4:"Ǯ";s:2:"Ǯ";s:4:"ǯ";s:2:"ǯ";s:3:"ǰ";s:2:"ǰ";s:3:"Ǵ";s:2:"Ǵ";s:3:"ǵ";s:2:"ǵ";s:3:"Ǹ";s:2:"Ǹ";s:3:"ǹ";s:2:"ǹ";s:4:"Ǻ";s:2:"Ǻ";s:4:"ǻ";s:2:"ǻ";s:4:"Ǽ";s:2:"Ǽ";s:4:"ǽ";s:2:"ǽ";s:4:"Ǿ";s:2:"Ǿ";s:4:"ǿ";s:2:"ǿ";s:3:"Ȁ";s:2:"Ȁ";s:3:"ȁ";s:2:"ȁ";s:3:"Ȃ";s:2:"Ȃ";s:3:"ȃ";s:2:"ȃ";s:3:"Ȅ";s:2:"Ȅ";s:3:"ȅ";s:2:"ȅ";s:3:"Ȇ";s:2:"Ȇ";s:3:"ȇ";s:2:"ȇ";s:3:"Ȉ";s:2:"Ȉ";s:3:"ȉ";s:2:"ȉ";s:3:"Ȋ";s:2:"Ȋ";s:3:"ȋ";s:2:"ȋ";s:3:"Ȍ";s:2:"Ȍ";s:3:"ȍ";s:2:"ȍ";s:3:"Ȏ";s:2:"Ȏ";s:3:"ȏ";s:2:"ȏ";s:3:"Ȑ";s:2:"Ȑ";s:3:"ȑ";s:2:"ȑ";s:3:"Ȓ";s:2:"Ȓ";s:3:"ȓ";s:2:"ȓ";s:3:"Ȕ";s:2:"Ȕ";s:3:"ȕ";s:2:"ȕ";s:3:"Ȗ";s:2:"Ȗ";s:3:"ȗ";s:2:"ȗ";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"ș";s:3:"Ț";s:2:"Ț";s:3:"ț";s:2:"ț";s:3:"Ȟ";s:2:"Ȟ";s:3:"ȟ";s:2:"ȟ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"ȩ";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"ȫ";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"ȭ";s:3:"Ȯ";s:2:"Ȯ";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"Ȳ";s:2:"Ȳ";s:3:"ȳ";s:2:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:4:"̈́";s:2:"̈́";s:2:"ʹ";s:2:"ʹ";s:1:";";s:2:";";s:4:"΅";s:2:"΅";s:4:"Ά";s:2:"Ά";s:2:"·";s:2:"·";s:4:"Έ";s:2:"Έ";s:4:"Ή";s:2:"Ή";s:4:"Ί";s:2:"Ί";s:4:"Ό";s:2:"Ό";s:4:"Ύ";s:2:"Ύ";s:4:"Ώ";s:2:"Ώ";s:4:"ΐ";s:2:"ΐ";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"ά";s:2:"ά";s:4:"έ";s:2:"έ";s:4:"ή";s:2:"ή";s:4:"ί";s:2:"ί";s:4:"ΰ";s:2:"ΰ";s:4:"ϊ";s:2:"ϊ";s:4:"ϋ";s:2:"ϋ";s:4:"ό";s:2:"ό";s:4:"ύ";s:2:"ύ";s:4:"ώ";s:2:"ώ";s:4:"ϓ";s:2:"ϓ";s:4:"ϔ";s:2:"ϔ";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ё";s:4:"Ѓ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"Ќ";s:2:"Ќ";s:4:"Ѝ";s:2:"Ѝ";s:4:"Ў";s:2:"Ў";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"ѐ";s:4:"ё";s:2:"ё";s:4:"ѓ";s:2:"ѓ";s:4:"ї";s:2:"ї";s:4:"ќ";s:2:"ќ";s:4:"ѝ";s:2:"ѝ";s:4:"ў";s:2:"ў";s:4:"Ѷ";s:2:"Ѷ";s:4:"ѷ";s:2:"ѷ";s:4:"Ӂ";s:2:"Ӂ";s:4:"ӂ";s:2:"ӂ";s:4:"Ӑ";s:2:"Ӑ";s:4:"ӑ";s:2:"ӑ";s:4:"Ӓ";s:2:"Ӓ";s:4:"ӓ";s:2:"ӓ";s:4:"Ӗ";s:2:"Ӗ";s:4:"ӗ";s:2:"ӗ";s:4:"Ӛ";s:2:"Ӛ";s:4:"ӛ";s:2:"ӛ";s:4:"Ӝ";s:2:"Ӝ";s:4:"ӝ";s:2:"ӝ";s:4:"Ӟ";s:2:"Ӟ";s:4:"ӟ";s:2:"ӟ";s:4:"Ӣ";s:2:"Ӣ";s:4:"ӣ";s:2:"ӣ";s:4:"Ӥ";s:2:"Ӥ";s:4:"ӥ";s:2:"ӥ";s:4:"Ӧ";s:2:"Ӧ";s:4:"ӧ";s:2:"ӧ";s:4:"Ӫ";s:2:"Ӫ";s:4:"ӫ";s:2:"ӫ";s:4:"Ӭ";s:2:"Ӭ";s:4:"ӭ";s:2:"ӭ";s:4:"Ӯ";s:2:"Ӯ";s:4:"ӯ";s:2:"ӯ";s:4:"Ӱ";s:2:"Ӱ";s:4:"ӱ";s:2:"ӱ";s:4:"Ӳ";s:2:"Ӳ";s:4:"ӳ";s:2:"ӳ";s:4:"Ӵ";s:2:"Ӵ";s:4:"ӵ";s:2:"ӵ";s:4:"Ӹ";s:2:"Ӹ";s:4:"ӹ";s:2:"ӹ";s:4:"آ";s:2:"آ";s:4:"أ";s:2:"أ";s:4:"ؤ";s:2:"ؤ";s:4:"إ";s:2:"إ";s:4:"ئ";s:2:"ئ";s:4:"ۀ";s:2:"ۀ";s:4:"ۂ";s:2:"ۂ";s:4:"ۓ";s:2:"ۓ";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"ো";s:6:"ৌ";s:3:"ৌ";s:6:"ୈ";s:3:"ୈ";s:6:"ୋ";s:3:"ୋ";s:6:"ୌ";s:3:"ୌ";s:6:"ஔ";s:3:"ஔ";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"ೀ";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"ේ";s:6:"ො";s:3:"ො";s:6:"ෝ";s:3:"ෝ";s:6:"ෞ";s:3:"ෞ";s:6:"ཱི";s:3:"ཱི";s:6:"ཱུ";s:3:"ཱུ";s:6:"ཱྀ";s:3:"ཱྀ";s:6:"ဦ";s:3:"ဦ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"Ḉ";s:3:"Ḉ";s:4:"ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:4:"Ḕ";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ḗ";s:3:"Ḗ";s:4:"ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"Ḯ";s:3:"Ḯ";s:4:"ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:4:"Ṍ";s:3:"Ṍ";s:4:"ṍ";s:3:"ṍ";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"ṏ";s:4:"Ṑ";s:3:"Ṑ";s:4:"ṑ";s:3:"ṑ";s:4:"Ṓ";s:3:"Ṓ";s:4:"ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"ṥ";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"ṧ";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:4:"Ṹ";s:3:"Ṹ";s:4:"ṹ";s:3:"ṹ";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"Ấ";s:3:"Ấ";s:4:"ấ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ắ";s:3:"Ắ";s:4:"ắ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"Ế";s:3:"Ế";s:4:"ế";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"ề";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"ễ";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:4:"Ố";s:3:"Ố";s:4:"ố";s:3:"ố";s:4:"Ồ";s:3:"Ồ";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"Ổ";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"Ỗ";s:4:"ỗ";s:3:"ỗ";s:5:"Ộ";s:3:"Ộ";s:5:"ộ";s:3:"ộ";s:4:"Ớ";s:3:"Ớ";s:4:"ớ";s:3:"ớ";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"ờ";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"Ỡ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:4:"Ứ";s:3:"Ứ";s:4:"ứ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"ử";s:4:"Ữ";s:3:"Ữ";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"Ự";s:4:"ự";s:3:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"ἀ";s:4:"ἁ";s:3:"ἁ";s:5:"ἂ";s:3:"ἂ";s:5:"ἃ";s:3:"ἃ";s:5:"ἄ";s:3:"ἄ";s:5:"ἅ";s:3:"ἅ";s:5:"ἆ";s:3:"ἆ";s:5:"ἇ";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"Ἄ";s:3:"Ἄ";s:5:"Ἅ";s:3:"Ἅ";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"Ἇ";s:4:"ἐ";s:3:"ἐ";s:4:"ἑ";s:3:"ἑ";s:5:"ἒ";s:3:"ἒ";s:5:"ἓ";s:3:"ἓ";s:5:"ἔ";s:3:"ἔ";s:5:"ἕ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"Ἑ";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"Ἓ";s:5:"Ἔ";s:3:"Ἔ";s:5:"Ἕ";s:3:"Ἕ";s:4:"ἠ";s:3:"ἠ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"ἢ";s:5:"ἣ";s:3:"ἣ";s:5:"ἤ";s:3:"ἤ";s:5:"ἥ";s:3:"ἥ";s:5:"ἦ";s:3:"ἦ";s:5:"ἧ";s:3:"ἧ";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"Ἤ";s:3:"Ἤ";s:5:"Ἥ";s:3:"Ἥ";s:5:"Ἦ";s:3:"Ἦ";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"ἰ";s:4:"ἱ";s:3:"ἱ";s:5:"ἲ";s:3:"ἲ";s:5:"ἳ";s:3:"ἳ";s:5:"ἴ";s:3:"ἴ";s:5:"ἵ";s:3:"ἵ";s:5:"ἶ";s:3:"ἶ";s:5:"ἷ";s:3:"ἷ";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"Ἱ";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"Ἳ";s:5:"Ἴ";s:3:"Ἴ";s:5:"Ἵ";s:3:"Ἵ";s:5:"Ἶ";s:3:"Ἶ";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"ὀ";s:4:"ὁ";s:3:"ὁ";s:5:"ὂ";s:3:"ὂ";s:5:"ὃ";s:3:"ὃ";s:5:"ὄ";s:3:"ὄ";s:5:"ὅ";s:3:"ὅ";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"Ὄ";s:3:"Ὄ";s:5:"Ὅ";s:3:"Ὅ";s:4:"ὐ";s:3:"ὐ";s:4:"ὑ";s:3:"ὑ";s:5:"ὒ";s:3:"ὒ";s:5:"ὓ";s:3:"ὓ";s:5:"ὔ";s:3:"ὔ";s:5:"ὕ";s:3:"ὕ";s:5:"ὖ";s:3:"ὖ";s:5:"ὗ";s:3:"ὗ";s:4:"Ὑ";s:3:"Ὑ";s:5:"Ὓ";s:3:"Ὓ";s:5:"Ὕ";s:3:"Ὕ";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"ὠ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"ὢ";s:5:"ὣ";s:3:"ὣ";s:5:"ὤ";s:3:"ὤ";s:5:"ὥ";s:3:"ὥ";s:5:"ὦ";s:3:"ὦ";s:5:"ὧ";s:3:"ὧ";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"Ὤ";s:3:"Ὤ";s:5:"Ὥ";s:3:"Ὥ";s:5:"Ὦ";s:3:"Ὦ";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"ὰ";s:2:"ά";s:3:"ά";s:4:"ὲ";s:3:"ὲ";s:2:"έ";s:3:"έ";s:4:"ὴ";s:3:"ὴ";s:2:"ή";s:3:"ή";s:4:"ὶ";s:3:"ὶ";s:2:"ί";s:3:"ί";s:4:"ὸ";s:3:"ὸ";s:2:"ό";s:3:"ό";s:4:"ὺ";s:3:"ὺ";s:2:"ύ";s:3:"ύ";s:4:"ὼ";s:3:"ὼ";s:2:"ώ";s:3:"ώ";s:5:"ᾀ";s:3:"ᾀ";s:5:"ᾁ";s:3:"ᾁ";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"ᾅ";s:3:"ᾅ";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"ᾍ";s:3:"ᾍ";s:5:"ᾎ";s:3:"ᾎ";s:5:"ᾏ";s:3:"ᾏ";s:5:"ᾐ";s:3:"ᾐ";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"ᾒ";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"ᾔ";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"ᾖ";s:5:"ᾗ";s:3:"ᾗ";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"ᾙ";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"ᾛ";s:5:"ᾜ";s:3:"ᾜ";s:5:"ᾝ";s:3:"ᾝ";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"ᾠ";s:3:"ᾠ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"ᾢ";s:5:"ᾣ";s:3:"ᾣ";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"ᾥ";s:5:"ᾦ";s:3:"ᾦ";s:5:"ᾧ";s:3:"ᾧ";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"ᾭ";s:3:"ᾭ";s:5:"ᾮ";s:3:"ᾮ";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"ᾰ";s:4:"ᾱ";s:3:"ᾱ";s:5:"ᾲ";s:3:"ᾲ";s:4:"ᾳ";s:3:"ᾳ";s:4:"ᾴ";s:3:"ᾴ";s:4:"ᾶ";s:3:"ᾶ";s:5:"ᾷ";s:3:"ᾷ";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"Ᾱ";s:4:"Ὰ";s:3:"Ὰ";s:2:"Ά";s:3:"Ά";s:4:"ᾼ";s:3:"ᾼ";s:2:"ι";s:3:"ι";s:4:"῁";s:3:"῁";s:5:"ῂ";s:3:"ῂ";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"ῄ";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:2:"Έ";s:3:"Έ";s:4:"Ὴ";s:3:"Ὴ";s:2:"Ή";s:3:"Ή";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"῍";s:5:"῎";s:3:"῎";s:5:"῏";s:3:"῏";s:4:"ῐ";s:3:"ῐ";s:4:"ῑ";s:3:"ῑ";s:4:"ῒ";s:3:"ῒ";s:2:"ΐ";s:3:"ΐ";s:4:"ῖ";s:3:"ῖ";s:4:"ῗ";s:3:"ῗ";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"Ῑ";s:4:"Ὶ";s:3:"Ὶ";s:2:"Ί";s:3:"Ί";s:5:"῝";s:3:"῝";s:5:"῞";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"ῠ";s:4:"ῡ";s:3:"ῡ";s:4:"ῢ";s:3:"ῢ";s:2:"ΰ";s:3:"ΰ";s:4:"ῤ";s:3:"ῤ";s:4:"ῥ";s:3:"ῥ";s:4:"ῦ";s:3:"ῦ";s:4:"ῧ";s:3:"ῧ";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"Ῡ";s:4:"Ὺ";s:3:"Ὺ";s:2:"Ύ";s:3:"Ύ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"῭";s:2:"΅";s:3:"΅";s:1:"`";s:3:"`";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ῴ";s:3:"ῴ";s:4:"ῶ";s:3:"ῶ";s:5:"ῷ";s:3:"ῷ";s:4:"Ὸ";s:3:"Ὸ";s:2:"Ό";s:3:"Ό";s:4:"Ὼ";s:3:"Ὼ";s:2:"Ώ";s:3:"Ώ";s:4:"ῼ";s:3:"ῼ";s:2:"´";s:3:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:2:"Ω";s:3:"Ω";s:1:"K";s:3:"K";s:2:"Å";s:3:"Å";s:5:"↚";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"⇍";s:3:"⇍";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"⇏";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"≁";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"≭";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"⊁";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"⋠";s:5:"⋡";s:3:"⋡";s:5:"⋢";s:3:"⋢";s:5:"⋣";s:3:"⋣";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"⋫";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:6:"が";s:3:"が";s:6:"ぎ";s:3:"ぎ";s:6:"ぐ";s:3:"ぐ";s:6:"げ";s:3:"げ";s:6:"ご";s:3:"ご";s:6:"ざ";s:3:"ざ";s:6:"じ";s:3:"じ";s:6:"ず";s:3:"ず";s:6:"ぜ";s:3:"ぜ";s:6:"ぞ";s:3:"ぞ";s:6:"だ";s:3:"だ";s:6:"ぢ";s:3:"ぢ";s:6:"づ";s:3:"づ";s:6:"で";s:3:"で";s:6:"ど";s:3:"ど";s:6:"ば";s:3:"ば";s:6:"ぱ";s:3:"ぱ";s:6:"び";s:3:"び";s:6:"ぴ";s:3:"ぴ";s:6:"ぶ";s:3:"ぶ";s:6:"ぷ";s:3:"ぷ";s:6:"べ";s:3:"べ";s:6:"ぺ";s:3:"ぺ";s:6:"ぼ";s:3:"ぼ";s:6:"ぽ";s:3:"ぽ";s:6:"ゔ";s:3:"ゔ";s:6:"ゞ";s:3:"ゞ";s:6:"ガ";s:3:"ガ";s:6:"ギ";s:3:"ギ";s:6:"グ";s:3:"グ";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ゴ";s:6:"ザ";s:3:"ザ";s:6:"ジ";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ダ";s:3:"ダ";s:6:"ヂ";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"バ";s:3:"バ";s:6:"パ";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ポ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:4:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:4:"廊";s:3:"朗";s:4:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:4:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:4:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:4:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:4:"異";s:3:"北";s:4:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:4:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:4:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:4:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:4:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:4:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:4:"侮";s:3:"僧";s:4:"僧";s:3:"免";s:4:"免";s:3:"勉";s:4:"勉";s:3:"勤";s:4:"勤";s:3:"卑";s:4:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:4:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:4:"屮";s:3:"悔";s:4:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:4:"憎";s:3:"懲";s:4:"懲";s:3:"敏";s:4:"敏";s:3:"既";s:3:"既";s:3:"暑";s:4:"暑";s:3:"梅";s:4:"梅";s:3:"海";s:4:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:4:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:4:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:4:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"著";s:4:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"並";s:3:"並";s:3:"况";s:4:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:4:"勇";s:3:"勺";s:4:"勺";s:3:"啕";s:3:"啕";s:3:"喙";s:4:"喙";s:3:"嗢";s:3:"嗢";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:4:"慎";s:3:"愈";s:3:"愈";s:3:"慠";s:3:"慠";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"望";s:4:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"滛";s:3:"滛";s:3:"滋";s:4:"滋";s:3:"瀞";s:4:"瀞";s:3:"瞧";s:3:"瞧";s:3:"爵";s:4:"爵";s:3:"犯";s:3:"犯";s:3:"瑱";s:4:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"盛";s:3:"盛";s:3:"直";s:4:"直";s:3:"睊";s:4:"睊";s:3:"着";s:3:"着";s:3:"磌";s:4:"磌";s:3:"窱";s:3:"窱";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"缾";s:3:"缾";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:4:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"調";s:3:"調";s:3:"請";s:3:"請";s:3:"諭";s:4:"諭";s:3:"變";s:4:"變";s:3:"輸";s:4:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"韛";s:3:"韛";s:3:"頋";s:4:"頋";s:3:"鬒";s:4:"鬒";s:4:"𢡊";s:3:"𢡊";s:4:"𢡄";s:3:"𢡄";s:4:"𣏕";s:3:"𣏕";s:3:"㮝";s:4:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:4:"䀹";s:4:"𥉉";s:3:"𥉉";s:4:"𥳐";s:3:"𥳐";s:4:"𧻓";s:3:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"丽";s:4:"丽";s:3:"丸";s:4:"丸";s:3:"乁";s:4:"乁";s:4:"𠄢";s:4:"𠄢";s:3:"你";s:4:"你";s:3:"侻";s:4:"侻";s:3:"倂";s:4:"倂";s:3:"偺";s:4:"偺";s:3:"備";s:4:"備";s:3:"像";s:4:"像";s:3:"㒞";s:4:"㒞";s:4:"𠘺";s:4:"𠘺";s:3:"兔";s:4:"兔";s:3:"兤";s:4:"兤";s:3:"具";s:4:"具";s:4:"𠔜";s:4:"𠔜";s:3:"㒹";s:4:"㒹";s:3:"內";s:4:"內";s:3:"再";s:4:"再";s:4:"𠕋";s:4:"𠕋";s:3:"冗";s:4:"冗";s:3:"冤";s:4:"冤";s:3:"仌";s:4:"仌";s:3:"冬";s:4:"冬";s:4:"𩇟";s:4:"𩇟";s:3:"凵";s:4:"凵";s:3:"刃";s:4:"刃";s:3:"㓟";s:4:"㓟";s:3:"刻";s:4:"刻";s:3:"剆";s:4:"剆";s:3:"割";s:4:"割";s:3:"剷";s:4:"剷";s:3:"㔕";s:4:"㔕";s:3:"包";s:4:"包";s:3:"匆";s:4:"匆";s:3:"卉";s:4:"卉";s:3:"博";s:4:"博";s:3:"即";s:4:"即";s:3:"卽";s:4:"卽";s:3:"卿";s:4:"卿";s:4:"𠨬";s:4:"𠨬";s:3:"灰";s:4:"灰";s:3:"及";s:4:"及";s:3:"叟";s:4:"叟";s:4:"𠭣";s:4:"𠭣";s:3:"叫";s:4:"叫";s:3:"叱";s:4:"叱";s:3:"吆";s:4:"吆";s:3:"咞";s:4:"咞";s:3:"吸";s:4:"吸";s:3:"呈";s:4:"呈";s:3:"周";s:4:"周";s:3:"咢";s:4:"咢";s:3:"哶";s:4:"哶";s:3:"唐";s:4:"唐";s:3:"啓";s:4:"啓";s:3:"啣";s:4:"啣";s:3:"善";s:4:"善";s:3:"喫";s:4:"喫";s:3:"喳";s:4:"喳";s:3:"嗂";s:4:"嗂";s:3:"圖";s:4:"圖";s:3:"圗";s:4:"圗";s:3:"噑";s:4:"噑";s:3:"噴";s:4:"噴";s:3:"壮";s:4:"壮";s:3:"城";s:4:"城";s:3:"埴";s:4:"埴";s:3:"堍";s:4:"堍";s:3:"型";s:4:"型";s:3:"堲";s:4:"堲";s:3:"報";s:4:"報";s:3:"墬";s:4:"墬";s:4:"𡓤";s:4:"𡓤";s:3:"売";s:4:"売";s:3:"壷";s:4:"壷";s:3:"夆";s:4:"夆";s:3:"多";s:4:"多";s:3:"夢";s:4:"夢";s:3:"奢";s:4:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:3:"姬";s:4:"姬";s:3:"娛";s:4:"娛";s:3:"娧";s:4:"娧";s:3:"姘";s:4:"姘";s:3:"婦";s:4:"婦";s:3:"㛮";s:4:"㛮";s:3:"㛼";s:4:"㛼";s:3:"嬈";s:4:"嬈";s:3:"嬾";s:4:"嬾";s:4:"𡧈";s:4:"𡧈";s:3:"寃";s:4:"寃";s:3:"寘";s:4:"寘";s:3:"寳";s:4:"寳";s:4:"𡬘";s:4:"𡬘";s:3:"寿";s:4:"寿";s:3:"将";s:4:"将";s:3:"当";s:4:"当";s:3:"尢";s:4:"尢";s:3:"㞁";s:4:"㞁";s:3:"屠";s:4:"屠";s:3:"峀";s:4:"峀";s:3:"岍";s:4:"岍";s:4:"𡷤";s:4:"𡷤";s:3:"嵃";s:4:"嵃";s:4:"𡷦";s:4:"𡷦";s:3:"嵮";s:4:"嵮";s:3:"嵫";s:4:"嵫";s:3:"嵼";s:4:"嵼";s:3:"巡";s:4:"巡";s:3:"巢";s:4:"巢";s:3:"㠯";s:4:"㠯";s:3:"巽";s:4:"巽";s:3:"帨";s:4:"帨";s:3:"帽";s:4:"帽";s:3:"幩";s:4:"幩";s:3:"㡢";s:4:"㡢";s:4:"𢆃";s:4:"𢆃";s:3:"㡼";s:4:"㡼";s:3:"庰";s:4:"庰";s:3:"庳";s:4:"庳";s:3:"庶";s:4:"庶";s:4:"𪎒";s:4:"𪎒";s:3:"廾";s:4:"廾";s:4:"𢌱";s:4:"𢌱";s:3:"舁";s:4:"舁";s:3:"弢";s:4:"弢";s:3:"㣇";s:4:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:3:"形";s:4:"形";s:3:"彫";s:4:"彫";s:3:"㣣";s:4:"㣣";s:3:"徚";s:4:"徚";s:3:"忍";s:4:"忍";s:3:"志";s:4:"志";s:3:"忹";s:4:"忹";s:3:"悁";s:4:"悁";s:3:"㤺";s:4:"㤺";s:3:"㤜";s:4:"㤜";s:4:"𢛔";s:4:"𢛔";s:3:"惇";s:4:"惇";s:3:"慈";s:4:"慈";s:3:"慌";s:4:"慌";s:3:"慺";s:4:"慺";s:3:"憲";s:4:"憲";s:3:"憤";s:4:"憤";s:3:"憯";s:4:"憯";s:3:"懞";s:4:"懞";s:3:"成";s:4:"成";s:3:"戛";s:4:"戛";s:3:"扝";s:4:"扝";s:3:"抱";s:4:"抱";s:3:"拔";s:4:"拔";s:3:"捐";s:4:"捐";s:4:"𢬌";s:4:"𢬌";s:3:"挽";s:4:"挽";s:3:"拼";s:4:"拼";s:3:"捨";s:4:"捨";s:3:"掃";s:4:"掃";s:3:"揤";s:4:"揤";s:4:"𢯱";s:4:"𢯱";s:3:"搢";s:4:"搢";s:3:"揅";s:4:"揅";s:3:"掩";s:4:"掩";s:3:"㨮";s:4:"㨮";s:3:"摩";s:4:"摩";s:3:"摾";s:4:"摾";s:3:"撝";s:4:"撝";s:3:"摷";s:4:"摷";s:3:"㩬";s:4:"㩬";s:3:"敬";s:4:"敬";s:4:"𣀊";s:4:"𣀊";s:3:"旣";s:4:"旣";s:3:"書";s:4:"書";s:3:"晉";s:4:"晉";s:3:"㬙";s:4:"㬙";s:3:"㬈";s:4:"㬈";s:3:"㫤";s:4:"㫤";s:3:"冒";s:4:"冒";s:3:"冕";s:4:"冕";s:3:"最";s:4:"最";s:3:"暜";s:4:"暜";s:3:"肭";s:4:"肭";s:3:"䏙";s:4:"䏙";s:3:"朡";s:4:"朡";s:3:"杞";s:4:"杞";s:3:"杓";s:4:"杓";s:4:"𣏃";s:4:"𣏃";s:3:"㭉";s:4:"㭉";s:3:"柺";s:4:"柺";s:3:"枅";s:4:"枅";s:3:"桒";s:4:"桒";s:4:"𣑭";s:4:"𣑭";s:3:"梎";s:4:"梎";s:3:"栟";s:4:"栟";s:3:"椔";s:4:"椔";s:3:"楂";s:4:"楂";s:3:"榣";s:4:"榣";s:3:"槪";s:4:"槪";s:3:"檨";s:4:"檨";s:4:"𣚣";s:4:"𣚣";s:3:"櫛";s:4:"櫛";s:3:"㰘";s:4:"㰘";s:3:"次";s:4:"次";s:4:"𣢧";s:4:"𣢧";s:3:"歔";s:4:"歔";s:3:"㱎";s:4:"㱎";s:3:"歲";s:4:"歲";s:3:"殟";s:4:"殟";s:3:"殻";s:4:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:3:"汎";s:4:"汎";s:4:"𣲼";s:4:"𣲼";s:3:"沿";s:4:"沿";s:3:"泍";s:4:"泍";s:3:"汧";s:4:"汧";s:3:"洖";s:4:"洖";s:3:"派";s:4:"派";s:3:"浩";s:4:"浩";s:3:"浸";s:4:"浸";s:3:"涅";s:4:"涅";s:4:"𣴞";s:4:"𣴞";s:3:"洴";s:4:"洴";s:3:"港";s:4:"港";s:3:"湮";s:4:"湮";s:3:"㴳";s:4:"㴳";s:3:"滇";s:4:"滇";s:4:"𣻑";s:4:"𣻑";s:3:"淹";s:4:"淹";s:3:"潮";s:4:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:3:"濆";s:4:"濆";s:3:"瀹";s:4:"瀹";s:3:"瀛";s:4:"瀛";s:3:"㶖";s:4:"㶖";s:3:"灊";s:4:"灊";s:3:"災";s:4:"災";s:3:"灷";s:4:"灷";s:3:"炭";s:4:"炭";s:4:"𠔥";s:4:"𠔥";s:3:"煅";s:4:"煅";s:4:"𤉣";s:4:"𤉣";s:3:"熜";s:4:"熜";s:4:"𤎫";s:4:"𤎫";s:3:"爨";s:4:"爨";s:3:"牐";s:4:"牐";s:4:"𤘈";s:4:"𤘈";s:3:"犀";s:4:"犀";s:3:"犕";s:4:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:3:"獺";s:4:"獺";s:3:"王";s:4:"王";s:3:"㺬";s:4:"㺬";s:3:"玥";s:4:"玥";s:3:"㺸";s:4:"㺸";s:3:"瑇";s:4:"瑇";s:3:"瑜";s:4:"瑜";s:3:"璅";s:4:"璅";s:3:"瓊";s:4:"瓊";s:3:"㼛";s:4:"㼛";s:3:"甤";s:4:"甤";s:4:"𤰶";s:4:"𤰶";s:3:"甾";s:4:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"𢆟";s:4:"𢆟";s:3:"瘐";s:4:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:3:"㿼";s:4:"㿼";s:3:"䀈";s:4:"䀈";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:3:"眞";s:4:"眞";s:3:"真";s:4:"真";s:3:"瞋";s:4:"瞋";s:3:"䁆";s:4:"䁆";s:3:"䂖";s:4:"䂖";s:4:"𥐝";s:4:"𥐝";s:3:"硎";s:4:"硎";s:3:"䃣";s:4:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:3:"秫";s:4:"秫";s:3:"䄯";s:4:"䄯";s:3:"穊";s:4:"穊";s:3:"穏";s:4:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:3:"竮";s:4:"竮";s:3:"䈂";s:4:"䈂";s:4:"𥮫";s:4:"𥮫";s:3:"篆";s:4:"篆";s:3:"築";s:4:"築";s:3:"䈧";s:4:"䈧";s:4:"𥲀";s:4:"𥲀";s:3:"糒";s:4:"糒";s:3:"䊠";s:4:"䊠";s:3:"糨";s:4:"糨";s:3:"糣";s:4:"糣";s:3:"紀";s:4:"紀";s:4:"𥾆";s:4:"𥾆";s:3:"絣";s:4:"絣";s:3:"䌁";s:4:"䌁";s:3:"緇";s:4:"緇";s:3:"縂";s:4:"縂";s:3:"繅";s:4:"繅";s:3:"䌴";s:4:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:3:"䍙";s:4:"䍙";s:4:"𦋙";s:4:"𦋙";s:3:"罺";s:4:"罺";s:4:"𦌾";s:4:"𦌾";s:3:"羕";s:4:"羕";s:3:"翺";s:4:"翺";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:3:"聠";s:4:"聠";s:4:"𦖨";s:4:"𦖨";s:3:"聰";s:4:"聰";s:4:"𣍟";s:4:"𣍟";s:3:"䏕";s:4:"䏕";s:3:"育";s:4:"育";s:3:"脃";s:4:"脃";s:3:"䐋";s:4:"䐋";s:3:"脾";s:4:"脾";s:3:"媵";s:4:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:3:"舄";s:4:"舄";s:3:"辞";s:4:"辞";s:3:"䑫";s:4:"䑫";s:3:"芑";s:4:"芑";s:3:"芋";s:4:"芋";s:3:"芝";s:4:"芝";s:3:"劳";s:4:"劳";s:3:"花";s:4:"花";s:3:"芳";s:4:"芳";s:3:"芽";s:4:"芽";s:3:"苦";s:4:"苦";s:4:"𦬼";s:4:"𦬼";s:3:"茝";s:4:"茝";s:3:"荣";s:4:"荣";s:3:"莭";s:4:"莭";s:3:"茣";s:4:"茣";s:3:"莽";s:4:"莽";s:3:"菧";s:4:"菧";s:3:"荓";s:4:"荓";s:3:"菊";s:4:"菊";s:3:"菌";s:4:"菌";s:3:"菜";s:4:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:3:"䔫";s:4:"䔫";s:3:"蓱";s:4:"蓱";s:3:"蓳";s:4:"蓳";s:3:"蔖";s:4:"蔖";s:4:"𧏊";s:4:"𧏊";s:3:"蕤";s:4:"蕤";s:4:"𦼬";s:4:"𦼬";s:3:"䕝";s:4:"䕝";s:3:"䕡";s:4:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:3:"䕫";s:4:"䕫";s:3:"虐";s:4:"虐";s:3:"虧";s:4:"虧";s:3:"虩";s:4:"虩";s:3:"蚩";s:4:"蚩";s:3:"蚈";s:4:"蚈";s:3:"蜎";s:4:"蜎";s:3:"蛢";s:4:"蛢";s:3:"蜨";s:4:"蜨";s:3:"蝫";s:4:"蝫";s:3:"螆";s:4:"螆";s:3:"䗗";s:4:"䗗";s:3:"蟡";s:4:"蟡";s:3:"蠁";s:4:"蠁";s:3:"䗹";s:4:"䗹";s:3:"衠";s:4:"衠";s:3:"衣";s:4:"衣";s:4:"𧙧";s:4:"𧙧";s:3:"裗";s:4:"裗";s:3:"裞";s:4:"裞";s:3:"䘵";s:4:"䘵";s:3:"裺";s:4:"裺";s:3:"㒻";s:4:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:3:"䚾";s:4:"䚾";s:3:"䛇";s:4:"䛇";s:3:"誠";s:4:"誠";s:3:"豕";s:4:"豕";s:4:"𧲨";s:4:"𧲨";s:3:"貫";s:4:"貫";s:3:"賁";s:4:"賁";s:3:"贛";s:4:"贛";s:3:"起";s:4:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:3:"跋";s:4:"跋";s:3:"趼";s:4:"趼";s:3:"跰";s:4:"跰";s:4:"𠣞";s:4:"𠣞";s:3:"軔";s:4:"軔";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:3:"邔";s:4:"邔";s:3:"郱";s:4:"郱";s:3:"鄑";s:4:"鄑";s:4:"𨜮";s:4:"𨜮";s:3:"鄛";s:4:"鄛";s:3:"鈸";s:4:"鈸";s:3:"鋗";s:4:"鋗";s:3:"鋘";s:4:"鋘";s:3:"鉼";s:4:"鉼";s:3:"鏹";s:4:"鏹";s:3:"鐕";s:4:"鐕";s:4:"𨯺";s:4:"𨯺";s:3:"開";s:4:"開";s:3:"䦕";s:4:"䦕";s:3:"閷";s:4:"閷";s:4:"𨵷";s:4:"𨵷";s:3:"䧦";s:4:"䧦";s:3:"雃";s:4:"雃";s:3:"嶲";s:4:"嶲";s:3:"霣";s:4:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:3:"䩮";s:4:"䩮";s:3:"䩶";s:4:"䩶";s:3:"韠";s:4:"韠";s:4:"𩐊";s:4:"𩐊";s:3:"䪲";s:4:"䪲";s:4:"𩒖";s:4:"𩒖";s:3:"頩";s:4:"頩";s:4:"𩖶";s:4:"𩖶";s:3:"飢";s:4:"飢";s:3:"䬳";s:4:"䬳";s:3:"餩";s:4:"餩";s:3:"馧";s:4:"馧";s:3:"駂";s:4:"駂";s:3:"駾";s:4:"駾";s:3:"䯎";s:4:"䯎";s:4:"𩬰";s:4:"𩬰";s:3:"鱀";s:4:"鱀";s:3:"鳽";s:4:"鳽";s:3:"䳎";s:4:"䳎";s:3:"䳭";s:4:"䳭";s:3:"鵧";s:4:"鵧";s:4:"𪃎";s:4:"𪃎";s:3:"䳸";s:4:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:3:"麻";s:4:"麻";s:3:"䵖";s:4:"䵖";s:3:"黹";s:4:"黹";s:3:"黾";s:4:"黾";s:3:"鼅";s:4:"鼅";s:3:"鼏";s:4:"鼏";s:3:"鼖";s:4:"鼖";s:3:"鼻";s:4:"鼻";s:4:"𪘀";s:4:"𪘀";}' ); -$utfCanonicalDecomp = unserialize( 'a:2032:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:";";s:1:";";s:2:"΅";s:4:"΅";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϓ";s:4:"ϓ";s:2:"ϔ";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"ι";s:2:"ι";s:3:"῁";s:4:"῁";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:"῍";s:3:"῎";s:5:"῎";s:3:"῏";s:5:"῏";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:"῝";s:3:"῞";s:5:"῞";s:3:"῟";s:5:"῟";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:4:"῭";s:3:"΅";s:4:"΅";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"⫝̸";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"ゞ";s:6:"ゞ";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' ); -$utfCheckNFC = unserialize( 'a:1216:{s:2:"̀";s:1:"N";s:2:"́";s:1:"N";s:2:"̓";s:1:"N";s:2:"̈́";s:1:"N";s:2:"ʹ";s:1:"N";s:2:";";s:1:"N";s:2:"·";s:1:"N";s:3:"क़";s:1:"N";s:3:"ख़";s:1:"N";s:3:"ग़";s:1:"N";s:3:"ज़";s:1:"N";s:3:"ड़";s:1:"N";s:3:"ढ़";s:1:"N";s:3:"फ़";s:1:"N";s:3:"य़";s:1:"N";s:3:"ড়";s:1:"N";s:3:"ঢ়";s:1:"N";s:3:"য়";s:1:"N";s:3:"ਲ਼";s:1:"N";s:3:"ਸ਼";s:1:"N";s:3:"ਖ਼";s:1:"N";s:3:"ਗ਼";s:1:"N";s:3:"ਜ਼";s:1:"N";s:3:"ਫ਼";s:1:"N";s:3:"ଡ଼";s:1:"N";s:3:"ଢ଼";s:1:"N";s:3:"གྷ";s:1:"N";s:3:"ཌྷ";s:1:"N";s:3:"དྷ";s:1:"N";s:3:"བྷ";s:1:"N";s:3:"ཛྷ";s:1:"N";s:3:"ཀྵ";s:1:"N";s:3:"ཱི";s:1:"N";s:3:"ཱུ";s:1:"N";s:3:"ྲྀ";s:1:"N";s:3:"ླྀ";s:1:"N";s:3:"ཱྀ";s:1:"N";s:3:"ྒྷ";s:1:"N";s:3:"ྜྷ";s:1:"N";s:3:"ྡྷ";s:1:"N";s:3:"ྦྷ";s:1:"N";s:3:"ྫྷ";s:1:"N";s:3:"ྐྵ";s:1:"N";s:3:"ά";s:1:"N";s:3:"έ";s:1:"N";s:3:"ή";s:1:"N";s:3:"ί";s:1:"N";s:3:"ό";s:1:"N";s:3:"ύ";s:1:"N";s:3:"ώ";s:1:"N";s:3:"Ά";s:1:"N";s:3:"ι";s:1:"N";s:3:"Έ";s:1:"N";s:3:"Ή";s:1:"N";s:3:"ΐ";s:1:"N";s:3:"Ί";s:1:"N";s:3:"ΰ";s:1:"N";s:3:"Ύ";s:1:"N";s:3:"΅";s:1:"N";s:3:"`";s:1:"N";s:3:"Ό";s:1:"N";s:3:"Ώ";s:1:"N";s:3:"´";s:1:"N";s:3:" ";s:1:"N";s:3:" ";s:1:"N";s:3:"Ω";s:1:"N";s:3:"K";s:1:"N";s:3:"Å";s:1:"N";s:3:"〈";s:1:"N";s:3:"〉";s:1:"N";s:3:"⫝̸";s:1:"N";s:3:"豈";s:1:"N";s:3:"更";s:1:"N";s:3:"車";s:1:"N";s:3:"賈";s:1:"N";s:3:"滑";s:1:"N";s:3:"串";s:1:"N";s:3:"句";s:1:"N";s:3:"龜";s:1:"N";s:3:"龜";s:1:"N";s:3:"契";s:1:"N";s:3:"金";s:1:"N";s:3:"喇";s:1:"N";s:3:"奈";s:1:"N";s:3:"懶";s:1:"N";s:3:"癩";s:1:"N";s:3:"羅";s:1:"N";s:3:"蘿";s:1:"N";s:3:"螺";s:1:"N";s:3:"裸";s:1:"N";s:3:"邏";s:1:"N";s:3:"樂";s:1:"N";s:3:"洛";s:1:"N";s:3:"烙";s:1:"N";s:3:"珞";s:1:"N";s:3:"落";s:1:"N";s:3:"酪";s:1:"N";s:3:"駱";s:1:"N";s:3:"亂";s:1:"N";s:3:"卵";s:1:"N";s:3:"欄";s:1:"N";s:3:"爛";s:1:"N";s:3:"蘭";s:1:"N";s:3:"鸞";s:1:"N";s:3:"嵐";s:1:"N";s:3:"濫";s:1:"N";s:3:"藍";s:1:"N";s:3:"襤";s:1:"N";s:3:"拉";s:1:"N";s:3:"臘";s:1:"N";s:3:"蠟";s:1:"N";s:3:"廊";s:1:"N";s:3:"朗";s:1:"N";s:3:"浪";s:1:"N";s:3:"狼";s:1:"N";s:3:"郎";s:1:"N";s:3:"來";s:1:"N";s:3:"冷";s:1:"N";s:3:"勞";s:1:"N";s:3:"擄";s:1:"N";s:3:"櫓";s:1:"N";s:3:"爐";s:1:"N";s:3:"盧";s:1:"N";s:3:"老";s:1:"N";s:3:"蘆";s:1:"N";s:3:"虜";s:1:"N";s:3:"路";s:1:"N";s:3:"露";s:1:"N";s:3:"魯";s:1:"N";s:3:"鷺";s:1:"N";s:3:"碌";s:1:"N";s:3:"祿";s:1:"N";s:3:"綠";s:1:"N";s:3:"菉";s:1:"N";s:3:"錄";s:1:"N";s:3:"鹿";s:1:"N";s:3:"論";s:1:"N";s:3:"壟";s:1:"N";s:3:"弄";s:1:"N";s:3:"籠";s:1:"N";s:3:"聾";s:1:"N";s:3:"牢";s:1:"N";s:3:"磊";s:1:"N";s:3:"賂";s:1:"N";s:3:"雷";s:1:"N";s:3:"壘";s:1:"N";s:3:"屢";s:1:"N";s:3:"樓";s:1:"N";s:3:"淚";s:1:"N";s:3:"漏";s:1:"N";s:3:"累";s:1:"N";s:3:"縷";s:1:"N";s:3:"陋";s:1:"N";s:3:"勒";s:1:"N";s:3:"肋";s:1:"N";s:3:"凜";s:1:"N";s:3:"凌";s:1:"N";s:3:"稜";s:1:"N";s:3:"綾";s:1:"N";s:3:"菱";s:1:"N";s:3:"陵";s:1:"N";s:3:"讀";s:1:"N";s:3:"拏";s:1:"N";s:3:"樂";s:1:"N";s:3:"諾";s:1:"N";s:3:"丹";s:1:"N";s:3:"寧";s:1:"N";s:3:"怒";s:1:"N";s:3:"率";s:1:"N";s:3:"異";s:1:"N";s:3:"北";s:1:"N";s:3:"磻";s:1:"N";s:3:"便";s:1:"N";s:3:"復";s:1:"N";s:3:"不";s:1:"N";s:3:"泌";s:1:"N";s:3:"數";s:1:"N";s:3:"索";s:1:"N";s:3:"參";s:1:"N";s:3:"塞";s:1:"N";s:3:"省";s:1:"N";s:3:"葉";s:1:"N";s:3:"說";s:1:"N";s:3:"殺";s:1:"N";s:3:"辰";s:1:"N";s:3:"沈";s:1:"N";s:3:"拾";s:1:"N";s:3:"若";s:1:"N";s:3:"掠";s:1:"N";s:3:"略";s:1:"N";s:3:"亮";s:1:"N";s:3:"兩";s:1:"N";s:3:"凉";s:1:"N";s:3:"梁";s:1:"N";s:3:"糧";s:1:"N";s:3:"良";s:1:"N";s:3:"諒";s:1:"N";s:3:"量";s:1:"N";s:3:"勵";s:1:"N";s:3:"呂";s:1:"N";s:3:"女";s:1:"N";s:3:"廬";s:1:"N";s:3:"旅";s:1:"N";s:3:"濾";s:1:"N";s:3:"礪";s:1:"N";s:3:"閭";s:1:"N";s:3:"驪";s:1:"N";s:3:"麗";s:1:"N";s:3:"黎";s:1:"N";s:3:"力";s:1:"N";s:3:"曆";s:1:"N";s:3:"歷";s:1:"N";s:3:"轢";s:1:"N";s:3:"年";s:1:"N";s:3:"憐";s:1:"N";s:3:"戀";s:1:"N";s:3:"撚";s:1:"N";s:3:"漣";s:1:"N";s:3:"煉";s:1:"N";s:3:"璉";s:1:"N";s:3:"秊";s:1:"N";s:3:"練";s:1:"N";s:3:"聯";s:1:"N";s:3:"輦";s:1:"N";s:3:"蓮";s:1:"N";s:3:"連";s:1:"N";s:3:"鍊";s:1:"N";s:3:"列";s:1:"N";s:3:"劣";s:1:"N";s:3:"咽";s:1:"N";s:3:"烈";s:1:"N";s:3:"裂";s:1:"N";s:3:"說";s:1:"N";s:3:"廉";s:1:"N";s:3:"念";s:1:"N";s:3:"捻";s:1:"N";s:3:"殮";s:1:"N";s:3:"簾";s:1:"N";s:3:"獵";s:1:"N";s:3:"令";s:1:"N";s:3:"囹";s:1:"N";s:3:"寧";s:1:"N";s:3:"嶺";s:1:"N";s:3:"怜";s:1:"N";s:3:"玲";s:1:"N";s:3:"瑩";s:1:"N";s:3:"羚";s:1:"N";s:3:"聆";s:1:"N";s:3:"鈴";s:1:"N";s:3:"零";s:1:"N";s:3:"靈";s:1:"N";s:3:"領";s:1:"N";s:3:"例";s:1:"N";s:3:"禮";s:1:"N";s:3:"醴";s:1:"N";s:3:"隸";s:1:"N";s:3:"惡";s:1:"N";s:3:"了";s:1:"N";s:3:"僚";s:1:"N";s:3:"寮";s:1:"N";s:3:"尿";s:1:"N";s:3:"料";s:1:"N";s:3:"樂";s:1:"N";s:3:"燎";s:1:"N";s:3:"療";s:1:"N";s:3:"蓼";s:1:"N";s:3:"遼";s:1:"N";s:3:"龍";s:1:"N";s:3:"暈";s:1:"N";s:3:"阮";s:1:"N";s:3:"劉";s:1:"N";s:3:"杻";s:1:"N";s:3:"柳";s:1:"N";s:3:"流";s:1:"N";s:3:"溜";s:1:"N";s:3:"琉";s:1:"N";s:3:"留";s:1:"N";s:3:"硫";s:1:"N";s:3:"紐";s:1:"N";s:3:"類";s:1:"N";s:3:"六";s:1:"N";s:3:"戮";s:1:"N";s:3:"陸";s:1:"N";s:3:"倫";s:1:"N";s:3:"崙";s:1:"N";s:3:"淪";s:1:"N";s:3:"輪";s:1:"N";s:3:"律";s:1:"N";s:3:"慄";s:1:"N";s:3:"栗";s:1:"N";s:3:"率";s:1:"N";s:3:"隆";s:1:"N";s:3:"利";s:1:"N";s:3:"吏";s:1:"N";s:3:"履";s:1:"N";s:3:"易";s:1:"N";s:3:"李";s:1:"N";s:3:"梨";s:1:"N";s:3:"泥";s:1:"N";s:3:"理";s:1:"N";s:3:"痢";s:1:"N";s:3:"罹";s:1:"N";s:3:"裏";s:1:"N";s:3:"裡";s:1:"N";s:3:"里";s:1:"N";s:3:"離";s:1:"N";s:3:"匿";s:1:"N";s:3:"溺";s:1:"N";s:3:"吝";s:1:"N";s:3:"燐";s:1:"N";s:3:"璘";s:1:"N";s:3:"藺";s:1:"N";s:3:"隣";s:1:"N";s:3:"鱗";s:1:"N";s:3:"麟";s:1:"N";s:3:"林";s:1:"N";s:3:"淋";s:1:"N";s:3:"臨";s:1:"N";s:3:"立";s:1:"N";s:3:"笠";s:1:"N";s:3:"粒";s:1:"N";s:3:"狀";s:1:"N";s:3:"炙";s:1:"N";s:3:"識";s:1:"N";s:3:"什";s:1:"N";s:3:"茶";s:1:"N";s:3:"刺";s:1:"N";s:3:"切";s:1:"N";s:3:"度";s:1:"N";s:3:"拓";s:1:"N";s:3:"糖";s:1:"N";s:3:"宅";s:1:"N";s:3:"洞";s:1:"N";s:3:"暴";s:1:"N";s:3:"輻";s:1:"N";s:3:"行";s:1:"N";s:3:"降";s:1:"N";s:3:"見";s:1:"N";s:3:"廓";s:1:"N";s:3:"兀";s:1:"N";s:3:"嗀";s:1:"N";s:3:"塚";s:1:"N";s:3:"晴";s:1:"N";s:3:"凞";s:1:"N";s:3:"猪";s:1:"N";s:3:"益";s:1:"N";s:3:"礼";s:1:"N";s:3:"神";s:1:"N";s:3:"祥";s:1:"N";s:3:"福";s:1:"N";s:3:"靖";s:1:"N";s:3:"精";s:1:"N";s:3:"羽";s:1:"N";s:3:"蘒";s:1:"N";s:3:"諸";s:1:"N";s:3:"逸";s:1:"N";s:3:"都";s:1:"N";s:3:"飯";s:1:"N";s:3:"飼";s:1:"N";s:3:"館";s:1:"N";s:3:"鶴";s:1:"N";s:3:"侮";s:1:"N";s:3:"僧";s:1:"N";s:3:"免";s:1:"N";s:3:"勉";s:1:"N";s:3:"勤";s:1:"N";s:3:"卑";s:1:"N";s:3:"喝";s:1:"N";s:3:"嘆";s:1:"N";s:3:"器";s:1:"N";s:3:"塀";s:1:"N";s:3:"墨";s:1:"N";s:3:"層";s:1:"N";s:3:"屮";s:1:"N";s:3:"悔";s:1:"N";s:3:"慨";s:1:"N";s:3:"憎";s:1:"N";s:3:"懲";s:1:"N";s:3:"敏";s:1:"N";s:3:"既";s:1:"N";s:3:"暑";s:1:"N";s:3:"梅";s:1:"N";s:3:"海";s:1:"N";s:3:"渚";s:1:"N";s:3:"漢";s:1:"N";s:3:"煮";s:1:"N";s:3:"爫";s:1:"N";s:3:"琢";s:1:"N";s:3:"碑";s:1:"N";s:3:"社";s:1:"N";s:3:"祉";s:1:"N";s:3:"祈";s:1:"N";s:3:"祐";s:1:"N";s:3:"祖";s:1:"N";s:3:"祝";s:1:"N";s:3:"禍";s:1:"N";s:3:"禎";s:1:"N";s:3:"穀";s:1:"N";s:3:"突";s:1:"N";s:3:"節";s:1:"N";s:3:"練";s:1:"N";s:3:"縉";s:1:"N";s:3:"繁";s:1:"N";s:3:"署";s:1:"N";s:3:"者";s:1:"N";s:3:"臭";s:1:"N";s:3:"艹";s:1:"N";s:3:"艹";s:1:"N";s:3:"著";s:1:"N";s:3:"褐";s:1:"N";s:3:"視";s:1:"N";s:3:"謁";s:1:"N";s:3:"謹";s:1:"N";s:3:"賓";s:1:"N";s:3:"贈";s:1:"N";s:3:"辶";s:1:"N";s:3:"逸";s:1:"N";s:3:"難";s:1:"N";s:3:"響";s:1:"N";s:3:"頻";s:1:"N";s:3:"並";s:1:"N";s:3:"况";s:1:"N";s:3:"全";s:1:"N";s:3:"侀";s:1:"N";s:3:"充";s:1:"N";s:3:"冀";s:1:"N";s:3:"勇";s:1:"N";s:3:"勺";s:1:"N";s:3:"喝";s:1:"N";s:3:"啕";s:1:"N";s:3:"喙";s:1:"N";s:3:"嗢";s:1:"N";s:3:"塚";s:1:"N";s:3:"墳";s:1:"N";s:3:"奄";s:1:"N";s:3:"奔";s:1:"N";s:3:"婢";s:1:"N";s:3:"嬨";s:1:"N";s:3:"廒";s:1:"N";s:3:"廙";s:1:"N";s:3:"彩";s:1:"N";s:3:"徭";s:1:"N";s:3:"惘";s:1:"N";s:3:"慎";s:1:"N";s:3:"愈";s:1:"N";s:3:"憎";s:1:"N";s:3:"慠";s:1:"N";s:3:"懲";s:1:"N";s:3:"戴";s:1:"N";s:3:"揄";s:1:"N";s:3:"搜";s:1:"N";s:3:"摒";s:1:"N";s:3:"敖";s:1:"N";s:3:"晴";s:1:"N";s:3:"朗";s:1:"N";s:3:"望";s:1:"N";s:3:"杖";s:1:"N";s:3:"歹";s:1:"N";s:3:"殺";s:1:"N";s:3:"流";s:1:"N";s:3:"滛";s:1:"N";s:3:"滋";s:1:"N";s:3:"漢";s:1:"N";s:3:"瀞";s:1:"N";s:3:"煮";s:1:"N";s:3:"瞧";s:1:"N";s:3:"爵";s:1:"N";s:3:"犯";s:1:"N";s:3:"猪";s:1:"N";s:3:"瑱";s:1:"N";s:3:"甆";s:1:"N";s:3:"画";s:1:"N";s:3:"瘝";s:1:"N";s:3:"瘟";s:1:"N";s:3:"益";s:1:"N";s:3:"盛";s:1:"N";s:3:"直";s:1:"N";s:3:"睊";s:1:"N";s:3:"着";s:1:"N";s:3:"磌";s:1:"N";s:3:"窱";s:1:"N";s:3:"節";s:1:"N";s:3:"类";s:1:"N";s:3:"絛";s:1:"N";s:3:"練";s:1:"N";s:3:"缾";s:1:"N";s:3:"者";s:1:"N";s:3:"荒";s:1:"N";s:3:"華";s:1:"N";s:3:"蝹";s:1:"N";s:3:"襁";s:1:"N";s:3:"覆";s:1:"N";s:3:"視";s:1:"N";s:3:"調";s:1:"N";s:3:"諸";s:1:"N";s:3:"請";s:1:"N";s:3:"謁";s:1:"N";s:3:"諾";s:1:"N";s:3:"諭";s:1:"N";s:3:"謹";s:1:"N";s:3:"變";s:1:"N";s:3:"贈";s:1:"N";s:3:"輸";s:1:"N";s:3:"遲";s:1:"N";s:3:"醙";s:1:"N";s:3:"鉶";s:1:"N";s:3:"陼";s:1:"N";s:3:"難";s:1:"N";s:3:"靖";s:1:"N";s:3:"韛";s:1:"N";s:3:"響";s:1:"N";s:3:"頋";s:1:"N";s:3:"頻";s:1:"N";s:3:"鬒";s:1:"N";s:3:"龜";s:1:"N";s:3:"𢡊";s:1:"N";s:3:"𢡄";s:1:"N";s:3:"𣏕";s:1:"N";s:3:"㮝";s:1:"N";s:3:"䀘";s:1:"N";s:3:"䀹";s:1:"N";s:3:"𥉉";s:1:"N";s:3:"𥳐";s:1:"N";s:3:"𧻓";s:1:"N";s:3:"齃";s:1:"N";s:3:"龎";s:1:"N";s:3:"יִ";s:1:"N";s:3:"ײַ";s:1:"N";s:3:"שׁ";s:1:"N";s:3:"שׂ";s:1:"N";s:3:"שּׁ";s:1:"N";s:3:"שּׂ";s:1:"N";s:3:"אַ";s:1:"N";s:3:"אָ";s:1:"N";s:3:"אּ";s:1:"N";s:3:"בּ";s:1:"N";s:3:"גּ";s:1:"N";s:3:"דּ";s:1:"N";s:3:"הּ";s:1:"N";s:3:"וּ";s:1:"N";s:3:"זּ";s:1:"N";s:3:"טּ";s:1:"N";s:3:"יּ";s:1:"N";s:3:"ךּ";s:1:"N";s:3:"כּ";s:1:"N";s:3:"לּ";s:1:"N";s:3:"מּ";s:1:"N";s:3:"נּ";s:1:"N";s:3:"סּ";s:1:"N";s:3:"ףּ";s:1:"N";s:3:"פּ";s:1:"N";s:3:"צּ";s:1:"N";s:3:"קּ";s:1:"N";s:3:"רּ";s:1:"N";s:3:"שּ";s:1:"N";s:3:"תּ";s:1:"N";s:3:"וֹ";s:1:"N";s:3:"בֿ";s:1:"N";s:3:"כֿ";s:1:"N";s:3:"פֿ";s:1:"N";s:4:"𝅗𝅥";s:1:"N";s:4:"𝅘𝅥";s:1:"N";s:4:"𝅘𝅥𝅮";s:1:"N";s:4:"𝅘𝅥𝅯";s:1:"N";s:4:"𝅘𝅥𝅰";s:1:"N";s:4:"𝅘𝅥𝅱";s:1:"N";s:4:"𝅘𝅥𝅲";s:1:"N";s:4:"𝆹𝅥";s:1:"N";s:4:"𝆺𝅥";s:1:"N";s:4:"𝆹𝅥𝅮";s:1:"N";s:4:"𝆺𝅥𝅮";s:1:"N";s:4:"𝆹𝅥𝅯";s:1:"N";s:4:"𝆺𝅥𝅯";s:1:"N";s:4:"丽";s:1:"N";s:4:"丸";s:1:"N";s:4:"乁";s:1:"N";s:4:"𠄢";s:1:"N";s:4:"你";s:1:"N";s:4:"侮";s:1:"N";s:4:"侻";s:1:"N";s:4:"倂";s:1:"N";s:4:"偺";s:1:"N";s:4:"備";s:1:"N";s:4:"僧";s:1:"N";s:4:"像";s:1:"N";s:4:"㒞";s:1:"N";s:4:"𠘺";s:1:"N";s:4:"免";s:1:"N";s:4:"兔";s:1:"N";s:4:"兤";s:1:"N";s:4:"具";s:1:"N";s:4:"𠔜";s:1:"N";s:4:"㒹";s:1:"N";s:4:"內";s:1:"N";s:4:"再";s:1:"N";s:4:"𠕋";s:1:"N";s:4:"冗";s:1:"N";s:4:"冤";s:1:"N";s:4:"仌";s:1:"N";s:4:"冬";s:1:"N";s:4:"况";s:1:"N";s:4:"𩇟";s:1:"N";s:4:"凵";s:1:"N";s:4:"刃";s:1:"N";s:4:"㓟";s:1:"N";s:4:"刻";s:1:"N";s:4:"剆";s:1:"N";s:4:"割";s:1:"N";s:4:"剷";s:1:"N";s:4:"㔕";s:1:"N";s:4:"勇";s:1:"N";s:4:"勉";s:1:"N";s:4:"勤";s:1:"N";s:4:"勺";s:1:"N";s:4:"包";s:1:"N";s:4:"匆";s:1:"N";s:4:"北";s:1:"N";s:4:"卉";s:1:"N";s:4:"卑";s:1:"N";s:4:"博";s:1:"N";s:4:"即";s:1:"N";s:4:"卽";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"𠨬";s:1:"N";s:4:"灰";s:1:"N";s:4:"及";s:1:"N";s:4:"叟";s:1:"N";s:4:"𠭣";s:1:"N";s:4:"叫";s:1:"N";s:4:"叱";s:1:"N";s:4:"吆";s:1:"N";s:4:"咞";s:1:"N";s:4:"吸";s:1:"N";s:4:"呈";s:1:"N";s:4:"周";s:1:"N";s:4:"咢";s:1:"N";s:4:"哶";s:1:"N";s:4:"唐";s:1:"N";s:4:"啓";s:1:"N";s:4:"啣";s:1:"N";s:4:"善";s:1:"N";s:4:"善";s:1:"N";s:4:"喙";s:1:"N";s:4:"喫";s:1:"N";s:4:"喳";s:1:"N";s:4:"嗂";s:1:"N";s:4:"圖";s:1:"N";s:4:"嘆";s:1:"N";s:4:"圗";s:1:"N";s:4:"噑";s:1:"N";s:4:"噴";s:1:"N";s:4:"切";s:1:"N";s:4:"壮";s:1:"N";s:4:"城";s:1:"N";s:4:"埴";s:1:"N";s:4:"堍";s:1:"N";s:4:"型";s:1:"N";s:4:"堲";s:1:"N";s:4:"報";s:1:"N";s:4:"墬";s:1:"N";s:4:"𡓤";s:1:"N";s:4:"売";s:1:"N";s:4:"壷";s:1:"N";s:4:"夆";s:1:"N";s:4:"多";s:1:"N";s:4:"夢";s:1:"N";s:4:"奢";s:1:"N";s:4:"𡚨";s:1:"N";s:4:"𡛪";s:1:"N";s:4:"姬";s:1:"N";s:4:"娛";s:1:"N";s:4:"娧";s:1:"N";s:4:"姘";s:1:"N";s:4:"婦";s:1:"N";s:4:"㛮";s:1:"N";s:4:"㛼";s:1:"N";s:4:"嬈";s:1:"N";s:4:"嬾";s:1:"N";s:4:"嬾";s:1:"N";s:4:"𡧈";s:1:"N";s:4:"寃";s:1:"N";s:4:"寘";s:1:"N";s:4:"寧";s:1:"N";s:4:"寳";s:1:"N";s:4:"𡬘";s:1:"N";s:4:"寿";s:1:"N";s:4:"将";s:1:"N";s:4:"当";s:1:"N";s:4:"尢";s:1:"N";s:4:"㞁";s:1:"N";s:4:"屠";s:1:"N";s:4:"屮";s:1:"N";s:4:"峀";s:1:"N";s:4:"岍";s:1:"N";s:4:"𡷤";s:1:"N";s:4:"嵃";s:1:"N";s:4:"𡷦";s:1:"N";s:4:"嵮";s:1:"N";s:4:"嵫";s:1:"N";s:4:"嵼";s:1:"N";s:4:"巡";s:1:"N";s:4:"巢";s:1:"N";s:4:"㠯";s:1:"N";s:4:"巽";s:1:"N";s:4:"帨";s:1:"N";s:4:"帽";s:1:"N";s:4:"幩";s:1:"N";s:4:"㡢";s:1:"N";s:4:"𢆃";s:1:"N";s:4:"㡼";s:1:"N";s:4:"庰";s:1:"N";s:4:"庳";s:1:"N";s:4:"庶";s:1:"N";s:4:"廊";s:1:"N";s:4:"𪎒";s:1:"N";s:4:"廾";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"舁";s:1:"N";s:4:"弢";s:1:"N";s:4:"弢";s:1:"N";s:4:"㣇";s:1:"N";s:4:"𣊸";s:1:"N";s:4:"𦇚";s:1:"N";s:4:"形";s:1:"N";s:4:"彫";s:1:"N";s:4:"㣣";s:1:"N";s:4:"徚";s:1:"N";s:4:"忍";s:1:"N";s:4:"志";s:1:"N";s:4:"忹";s:1:"N";s:4:"悁";s:1:"N";s:4:"㤺";s:1:"N";s:4:"㤜";s:1:"N";s:4:"悔";s:1:"N";s:4:"𢛔";s:1:"N";s:4:"惇";s:1:"N";s:4:"慈";s:1:"N";s:4:"慌";s:1:"N";s:4:"慎";s:1:"N";s:4:"慌";s:1:"N";s:4:"慺";s:1:"N";s:4:"憎";s:1:"N";s:4:"憲";s:1:"N";s:4:"憤";s:1:"N";s:4:"憯";s:1:"N";s:4:"懞";s:1:"N";s:4:"懲";s:1:"N";s:4:"懶";s:1:"N";s:4:"成";s:1:"N";s:4:"戛";s:1:"N";s:4:"扝";s:1:"N";s:4:"抱";s:1:"N";s:4:"拔";s:1:"N";s:4:"捐";s:1:"N";s:4:"𢬌";s:1:"N";s:4:"挽";s:1:"N";s:4:"拼";s:1:"N";s:4:"捨";s:1:"N";s:4:"掃";s:1:"N";s:4:"揤";s:1:"N";s:4:"𢯱";s:1:"N";s:4:"搢";s:1:"N";s:4:"揅";s:1:"N";s:4:"掩";s:1:"N";s:4:"㨮";s:1:"N";s:4:"摩";s:1:"N";s:4:"摾";s:1:"N";s:4:"撝";s:1:"N";s:4:"摷";s:1:"N";s:4:"㩬";s:1:"N";s:4:"敏";s:1:"N";s:4:"敬";s:1:"N";s:4:"𣀊";s:1:"N";s:4:"旣";s:1:"N";s:4:"書";s:1:"N";s:4:"晉";s:1:"N";s:4:"㬙";s:1:"N";s:4:"暑";s:1:"N";s:4:"㬈";s:1:"N";s:4:"㫤";s:1:"N";s:4:"冒";s:1:"N";s:4:"冕";s:1:"N";s:4:"最";s:1:"N";s:4:"暜";s:1:"N";s:4:"肭";s:1:"N";s:4:"䏙";s:1:"N";s:4:"朗";s:1:"N";s:4:"望";s:1:"N";s:4:"朡";s:1:"N";s:4:"杞";s:1:"N";s:4:"杓";s:1:"N";s:4:"𣏃";s:1:"N";s:4:"㭉";s:1:"N";s:4:"柺";s:1:"N";s:4:"枅";s:1:"N";s:4:"桒";s:1:"N";s:4:"梅";s:1:"N";s:4:"𣑭";s:1:"N";s:4:"梎";s:1:"N";s:4:"栟";s:1:"N";s:4:"椔";s:1:"N";s:4:"㮝";s:1:"N";s:4:"楂";s:1:"N";s:4:"榣";s:1:"N";s:4:"槪";s:1:"N";s:4:"檨";s:1:"N";s:4:"𣚣";s:1:"N";s:4:"櫛";s:1:"N";s:4:"㰘";s:1:"N";s:4:"次";s:1:"N";s:4:"𣢧";s:1:"N";s:4:"歔";s:1:"N";s:4:"㱎";s:1:"N";s:4:"歲";s:1:"N";s:4:"殟";s:1:"N";s:4:"殺";s:1:"N";s:4:"殻";s:1:"N";s:4:"𣪍";s:1:"N";s:4:"𡴋";s:1:"N";s:4:"𣫺";s:1:"N";s:4:"汎";s:1:"N";s:4:"𣲼";s:1:"N";s:4:"沿";s:1:"N";s:4:"泍";s:1:"N";s:4:"汧";s:1:"N";s:4:"洖";s:1:"N";s:4:"派";s:1:"N";s:4:"海";s:1:"N";s:4:"流";s:1:"N";s:4:"浩";s:1:"N";s:4:"浸";s:1:"N";s:4:"涅";s:1:"N";s:4:"𣴞";s:1:"N";s:4:"洴";s:1:"N";s:4:"港";s:1:"N";s:4:"湮";s:1:"N";s:4:"㴳";s:1:"N";s:4:"滋";s:1:"N";s:4:"滇";s:1:"N";s:4:"𣻑";s:1:"N";s:4:"淹";s:1:"N";s:4:"潮";s:1:"N";s:4:"𣽞";s:1:"N";s:4:"𣾎";s:1:"N";s:4:"濆";s:1:"N";s:4:"瀹";s:1:"N";s:4:"瀞";s:1:"N";s:4:"瀛";s:1:"N";s:4:"㶖";s:1:"N";s:4:"灊";s:1:"N";s:4:"災";s:1:"N";s:4:"灷";s:1:"N";s:4:"炭";s:1:"N";s:4:"𠔥";s:1:"N";s:4:"煅";s:1:"N";s:4:"𤉣";s:1:"N";s:4:"熜";s:1:"N";s:4:"𤎫";s:1:"N";s:4:"爨";s:1:"N";s:4:"爵";s:1:"N";s:4:"牐";s:1:"N";s:4:"𤘈";s:1:"N";s:4:"犀";s:1:"N";s:4:"犕";s:1:"N";s:4:"𤜵";s:1:"N";s:4:"𤠔";s:1:"N";s:4:"獺";s:1:"N";s:4:"王";s:1:"N";s:4:"㺬";s:1:"N";s:4:"玥";s:1:"N";s:4:"㺸";s:1:"N";s:4:"㺸";s:1:"N";s:4:"瑇";s:1:"N";s:4:"瑜";s:1:"N";s:4:"瑱";s:1:"N";s:4:"璅";s:1:"N";s:4:"瓊";s:1:"N";s:4:"㼛";s:1:"N";s:4:"甤";s:1:"N";s:4:"𤰶";s:1:"N";s:4:"甾";s:1:"N";s:4:"𤲒";s:1:"N";s:4:"異";s:1:"N";s:4:"𢆟";s:1:"N";s:4:"瘐";s:1:"N";s:4:"𤾡";s:1:"N";s:4:"𤾸";s:1:"N";s:4:"𥁄";s:1:"N";s:4:"㿼";s:1:"N";s:4:"䀈";s:1:"N";s:4:"直";s:1:"N";s:4:"𥃳";s:1:"N";s:4:"𥃲";s:1:"N";s:4:"𥄙";s:1:"N";s:4:"𥄳";s:1:"N";s:4:"眞";s:1:"N";s:4:"真";s:1:"N";s:4:"真";s:1:"N";s:4:"睊";s:1:"N";s:4:"䀹";s:1:"N";s:4:"瞋";s:1:"N";s:4:"䁆";s:1:"N";s:4:"䂖";s:1:"N";s:4:"𥐝";s:1:"N";s:4:"硎";s:1:"N";s:4:"碌";s:1:"N";s:4:"磌";s:1:"N";s:4:"䃣";s:1:"N";s:4:"𥘦";s:1:"N";s:4:"祖";s:1:"N";s:4:"𥚚";s:1:"N";s:4:"𥛅";s:1:"N";s:4:"福";s:1:"N";s:4:"秫";s:1:"N";s:4:"䄯";s:1:"N";s:4:"穀";s:1:"N";s:4:"穊";s:1:"N";s:4:"穏";s:1:"N";s:4:"𥥼";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"竮";s:1:"N";s:4:"䈂";s:1:"N";s:4:"𥮫";s:1:"N";s:4:"篆";s:1:"N";s:4:"築";s:1:"N";s:4:"䈧";s:1:"N";s:4:"𥲀";s:1:"N";s:4:"糒";s:1:"N";s:4:"䊠";s:1:"N";s:4:"糨";s:1:"N";s:4:"糣";s:1:"N";s:4:"紀";s:1:"N";s:4:"𥾆";s:1:"N";s:4:"絣";s:1:"N";s:4:"䌁";s:1:"N";s:4:"緇";s:1:"N";s:4:"縂";s:1:"N";s:4:"繅";s:1:"N";s:4:"䌴";s:1:"N";s:4:"𦈨";s:1:"N";s:4:"𦉇";s:1:"N";s:4:"䍙";s:1:"N";s:4:"𦋙";s:1:"N";s:4:"罺";s:1:"N";s:4:"𦌾";s:1:"N";s:4:"羕";s:1:"N";s:4:"翺";s:1:"N";s:4:"者";s:1:"N";s:4:"𦓚";s:1:"N";s:4:"𦔣";s:1:"N";s:4:"聠";s:1:"N";s:4:"𦖨";s:1:"N";s:4:"聰";s:1:"N";s:4:"𣍟";s:1:"N";s:4:"䏕";s:1:"N";s:4:"育";s:1:"N";s:4:"脃";s:1:"N";s:4:"䐋";s:1:"N";s:4:"脾";s:1:"N";s:4:"媵";s:1:"N";s:4:"𦞧";s:1:"N";s:4:"𦞵";s:1:"N";s:4:"𣎓";s:1:"N";s:4:"𣎜";s:1:"N";s:4:"舁";s:1:"N";s:4:"舄";s:1:"N";s:4:"辞";s:1:"N";s:4:"䑫";s:1:"N";s:4:"芑";s:1:"N";s:4:"芋";s:1:"N";s:4:"芝";s:1:"N";s:4:"劳";s:1:"N";s:4:"花";s:1:"N";s:4:"芳";s:1:"N";s:4:"芽";s:1:"N";s:4:"苦";s:1:"N";s:4:"𦬼";s:1:"N";s:4:"若";s:1:"N";s:4:"茝";s:1:"N";s:4:"荣";s:1:"N";s:4:"莭";s:1:"N";s:4:"茣";s:1:"N";s:4:"莽";s:1:"N";s:4:"菧";s:1:"N";s:4:"著";s:1:"N";s:4:"荓";s:1:"N";s:4:"菊";s:1:"N";s:4:"菌";s:1:"N";s:4:"菜";s:1:"N";s:4:"𦰶";s:1:"N";s:4:"𦵫";s:1:"N";s:4:"𦳕";s:1:"N";s:4:"䔫";s:1:"N";s:4:"蓱";s:1:"N";s:4:"蓳";s:1:"N";s:4:"蔖";s:1:"N";s:4:"𧏊";s:1:"N";s:4:"蕤";s:1:"N";s:4:"𦼬";s:1:"N";s:4:"䕝";s:1:"N";s:4:"䕡";s:1:"N";s:4:"𦾱";s:1:"N";s:4:"𧃒";s:1:"N";s:4:"䕫";s:1:"N";s:4:"虐";s:1:"N";s:4:"虜";s:1:"N";s:4:"虧";s:1:"N";s:4:"虩";s:1:"N";s:4:"蚩";s:1:"N";s:4:"蚈";s:1:"N";s:4:"蜎";s:1:"N";s:4:"蛢";s:1:"N";s:4:"蝹";s:1:"N";s:4:"蜨";s:1:"N";s:4:"蝫";s:1:"N";s:4:"螆";s:1:"N";s:4:"䗗";s:1:"N";s:4:"蟡";s:1:"N";s:4:"蠁";s:1:"N";s:4:"䗹";s:1:"N";s:4:"衠";s:1:"N";s:4:"衣";s:1:"N";s:4:"𧙧";s:1:"N";s:4:"裗";s:1:"N";s:4:"裞";s:1:"N";s:4:"䘵";s:1:"N";s:4:"裺";s:1:"N";s:4:"㒻";s:1:"N";s:4:"𧢮";s:1:"N";s:4:"𧥦";s:1:"N";s:4:"䚾";s:1:"N";s:4:"䛇";s:1:"N";s:4:"誠";s:1:"N";s:4:"諭";s:1:"N";s:4:"變";s:1:"N";s:4:"豕";s:1:"N";s:4:"𧲨";s:1:"N";s:4:"貫";s:1:"N";s:4:"賁";s:1:"N";s:4:"贛";s:1:"N";s:4:"起";s:1:"N";s:4:"𧼯";s:1:"N";s:4:"𠠄";s:1:"N";s:4:"跋";s:1:"N";s:4:"趼";s:1:"N";s:4:"跰";s:1:"N";s:4:"𠣞";s:1:"N";s:4:"軔";s:1:"N";s:4:"輸";s:1:"N";s:4:"𨗒";s:1:"N";s:4:"𨗭";s:1:"N";s:4:"邔";s:1:"N";s:4:"郱";s:1:"N";s:4:"鄑";s:1:"N";s:4:"𨜮";s:1:"N";s:4:"鄛";s:1:"N";s:4:"鈸";s:1:"N";s:4:"鋗";s:1:"N";s:4:"鋘";s:1:"N";s:4:"鉼";s:1:"N";s:4:"鏹";s:1:"N";s:4:"鐕";s:1:"N";s:4:"𨯺";s:1:"N";s:4:"開";s:1:"N";s:4:"䦕";s:1:"N";s:4:"閷";s:1:"N";s:4:"𨵷";s:1:"N";s:4:"䧦";s:1:"N";s:4:"雃";s:1:"N";s:4:"嶲";s:1:"N";s:4:"霣";s:1:"N";s:4:"𩅅";s:1:"N";s:4:"𩈚";s:1:"N";s:4:"䩮";s:1:"N";s:4:"䩶";s:1:"N";s:4:"韠";s:1:"N";s:4:"𩐊";s:1:"N";s:4:"䪲";s:1:"N";s:4:"𩒖";s:1:"N";s:4:"頋";s:1:"N";s:4:"頋";s:1:"N";s:4:"頩";s:1:"N";s:4:"𩖶";s:1:"N";s:4:"飢";s:1:"N";s:4:"䬳";s:1:"N";s:4:"餩";s:1:"N";s:4:"馧";s:1:"N";s:4:"駂";s:1:"N";s:4:"駾";s:1:"N";s:4:"䯎";s:1:"N";s:4:"𩬰";s:1:"N";s:4:"鬒";s:1:"N";s:4:"鱀";s:1:"N";s:4:"鳽";s:1:"N";s:4:"䳎";s:1:"N";s:4:"䳭";s:1:"N";s:4:"鵧";s:1:"N";s:4:"𪃎";s:1:"N";s:4:"䳸";s:1:"N";s:4:"𪄅";s:1:"N";s:4:"𪈎";s:1:"N";s:4:"𪊑";s:1:"N";s:4:"麻";s:1:"N";s:4:"䵖";s:1:"N";s:4:"黹";s:1:"N";s:4:"黾";s:1:"N";s:4:"鼅";s:1:"N";s:4:"鼏";s:1:"N";s:4:"鼖";s:1:"N";s:4:"鼻";s:1:"N";s:4:"𪘀";s:1:"N";s:2:"̀";s:1:"M";s:2:"́";s:1:"M";s:2:"̂";s:1:"M";s:2:"̃";s:1:"M";s:2:"̄";s:1:"M";s:2:"̆";s:1:"M";s:2:"̇";s:1:"M";s:2:"̈";s:1:"M";s:2:"̉";s:1:"M";s:2:"̊";s:1:"M";s:2:"̋";s:1:"M";s:2:"̌";s:1:"M";s:2:"̏";s:1:"M";s:2:"̑";s:1:"M";s:2:"̓";s:1:"M";s:2:"̔";s:1:"M";s:2:"̛";s:1:"M";s:2:"̣";s:1:"M";s:2:"̤";s:1:"M";s:2:"̥";s:1:"M";s:2:"̦";s:1:"M";s:2:"̧";s:1:"M";s:2:"̨";s:1:"M";s:2:"̭";s:1:"M";s:2:"̮";s:1:"M";s:2:"̰";s:1:"M";s:2:"̱";s:1:"M";s:2:"̸";s:1:"M";s:2:"͂";s:1:"M";s:2:"ͅ";s:1:"M";s:2:"ٓ";s:1:"M";s:2:"ٔ";s:1:"M";s:2:"ٕ";s:1:"M";s:3:"़";s:1:"M";s:3:"া";s:1:"M";s:3:"ৗ";s:1:"M";s:3:"ା";s:1:"M";s:3:"ୖ";s:1:"M";s:3:"ୗ";s:1:"M";s:3:"ா";s:1:"M";s:3:"ௗ";s:1:"M";s:3:"ౖ";s:1:"M";s:3:"ೂ";s:1:"M";s:3:"ೕ";s:1:"M";s:3:"ೖ";s:1:"M";s:3:"ാ";s:1:"M";s:3:"ൗ";s:1:"M";s:3:"්";s:1:"M";s:3:"ා";s:1:"M";s:3:"ෟ";s:1:"M";s:3:"ီ";s:1:"M";s:3:"ᅡ";s:1:"M";s:3:"ᅢ";s:1:"M";s:3:"ᅣ";s:1:"M";s:3:"ᅤ";s:1:"M";s:3:"ᅥ";s:1:"M";s:3:"ᅦ";s:1:"M";s:3:"ᅧ";s:1:"M";s:3:"ᅨ";s:1:"M";s:3:"ᅩ";s:1:"M";s:3:"ᅪ";s:1:"M";s:3:"ᅫ";s:1:"M";s:3:"ᅬ";s:1:"M";s:3:"ᅭ";s:1:"M";s:3:"ᅮ";s:1:"M";s:3:"ᅯ";s:1:"M";s:3:"ᅰ";s:1:"M";s:3:"ᅱ";s:1:"M";s:3:"ᅲ";s:1:"M";s:3:"ᅳ";s:1:"M";s:3:"ᅴ";s:1:"M";s:3:"ᅵ";s:1:"M";s:3:"ᆨ";s:1:"M";s:3:"ᆩ";s:1:"M";s:3:"ᆪ";s:1:"M";s:3:"ᆫ";s:1:"M";s:3:"ᆬ";s:1:"M";s:3:"ᆭ";s:1:"M";s:3:"ᆮ";s:1:"M";s:3:"ᆯ";s:1:"M";s:3:"ᆰ";s:1:"M";s:3:"ᆱ";s:1:"M";s:3:"ᆲ";s:1:"M";s:3:"ᆳ";s:1:"M";s:3:"ᆴ";s:1:"M";s:3:"ᆵ";s:1:"M";s:3:"ᆶ";s:1:"M";s:3:"ᆷ";s:1:"M";s:3:"ᆸ";s:1:"M";s:3:"ᆹ";s:1:"M";s:3:"ᆺ";s:1:"M";s:3:"ᆻ";s:1:"M";s:3:"ᆼ";s:1:"M";s:3:"ᆽ";s:1:"M";s:3:"ᆾ";s:1:"M";s:3:"ᆿ";s:1:"M";s:3:"ᇀ";s:1:"M";s:3:"ᇁ";s:1:"M";s:3:"ᇂ";s:1:"M";s:3:"゙";s:1:"M";s:3:"゚";s:1:"M";}' ); +$utfCombiningClass = unserialize( 'a:418:{s:2:"̀";i:230;s:2:"́";i:230;s:2:"̂";i:230;s:2:"̃";i:230;s:2:"̄";i:230;s:2:"̅";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"̊";i:230;s:2:"̋";i:230;s:2:"̌";i:230;s:2:"̍";i:230;s:2:"̎";i:230;s:2:"̏";i:230;s:2:"̐";i:230;s:2:"̑";i:230;s:2:"̒";i:230;s:2:"̓";i:230;s:2:"̔";i:230;s:2:"̕";i:232;s:2:"̖";i:220;s:2:"̗";i:220;s:2:"̘";i:220;s:2:"̙";i:220;s:2:"̚";i:232;s:2:"̛";i:216;s:2:"̜";i:220;s:2:"̝";i:220;s:2:"̞";i:220;s:2:"̟";i:220;s:2:"̠";i:220;s:2:"̡";i:202;s:2:"̢";i:202;s:2:"̣";i:220;s:2:"̤";i:220;s:2:"̥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"̩";i:220;s:2:"̪";i:220;s:2:"̫";i:220;s:2:"̬";i:220;s:2:"̭";i:220;s:2:"̮";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"̴";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"̷";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"̻";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"̿";i:230;s:2:"̀";i:230;s:2:"́";i:230;s:2:"͂";i:230;s:2:"̓";i:230;s:2:"̈́";i:230;s:2:"ͅ";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"͊";i:230;s:2:"͋";i:230;s:2:"͌";i:230;s:2:"͍";i:220;s:2:"͎";i:220;s:2:"͐";i:230;s:2:"͑";i:230;s:2:"͒";i:230;s:2:"͓";i:220;s:2:"͔";i:220;s:2:"͕";i:220;s:2:"͖";i:220;s:2:"͗";i:230;s:2:"͘";i:232;s:2:"͙";i:220;s:2:"͚";i:220;s:2:"͛";i:230;s:2:"͜";i:233;s:2:"͝";i:234;s:2:"͞";i:234;s:2:"͟";i:233;s:2:"͠";i:234;s:2:"͡";i:234;s:2:"͢";i:233;s:2:"ͣ";i:230;s:2:"ͤ";i:230;s:2:"ͥ";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"ͩ";i:230;s:2:"ͪ";i:230;s:2:"ͫ";i:230;s:2:"ͬ";i:230;s:2:"ͭ";i:230;s:2:"ͮ";i:230;s:2:"ͯ";i:230;s:2:"҃";i:230;s:2:"҄";i:230;s:2:"҅";i:230;s:2:"҆";i:230;s:2:"֑";i:220;s:2:"֒";i:230;s:2:"֓";i:230;s:2:"֔";i:230;s:2:"֕";i:230;s:2:"֖";i:220;s:2:"֗";i:230;s:2:"֘";i:230;s:2:"֙";i:230;s:2:"֚";i:222;s:2:"֛";i:220;s:2:"֜";i:230;s:2:"֝";i:230;s:2:"֞";i:230;s:2:"֟";i:230;s:2:"֠";i:230;s:2:"֡";i:230;s:2:"֢";i:220;s:2:"֣";i:220;s:2:"֤";i:220;s:2:"֥";i:220;s:2:"֦";i:220;s:2:"֧";i:220;s:2:"֨";i:230;s:2:"֩";i:230;s:2:"֪";i:220;s:2:"֫";i:230;s:2:"֬";i:230;s:2:"֭";i:222;s:2:"֮";i:228;s:2:"֯";i:230;s:2:"ְ";i:10;s:2:"ֱ";i:11;s:2:"ֲ";i:12;s:2:"ֳ";i:13;s:2:"ִ";i:14;s:2:"ֵ";i:15;s:2:"ֶ";i:16;s:2:"ַ";i:17;s:2:"ָ";i:18;s:2:"ֹ";i:19;s:2:"ֺ";i:19;s:2:"ֻ";i:20;s:2:"ּ";i:21;s:2:"ֽ";i:22;s:2:"ֿ";i:23;s:2:"ׁ";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"ׅ";i:220;s:2:"ׇ";i:18;s:2:"ؐ";i:230;s:2:"ؑ";i:230;s:2:"ؒ";i:230;s:2:"ؓ";i:230;s:2:"ؔ";i:230;s:2:"ؕ";i:230;s:2:"ً";i:27;s:2:"ٌ";i:28;s:2:"ٍ";i:29;s:2:"َ";i:30;s:2:"ُ";i:31;s:2:"ِ";i:32;s:2:"ّ";i:33;s:2:"ْ";i:34;s:2:"ٓ";i:230;s:2:"ٔ";i:230;s:2:"ٕ";i:220;s:2:"ٖ";i:220;s:2:"ٗ";i:230;s:2:"٘";i:230;s:2:"ٙ";i:230;s:2:"ٚ";i:230;s:2:"ٛ";i:230;s:2:"ٜ";i:220;s:2:"ٝ";i:230;s:2:"ٞ";i:230;s:2:"ٰ";i:35;s:2:"ۖ";i:230;s:2:"ۗ";i:230;s:2:"ۘ";i:230;s:2:"ۙ";i:230;s:2:"ۚ";i:230;s:2:"ۛ";i:230;s:2:"ۜ";i:230;s:2:"۟";i:230;s:2:"۠";i:230;s:2:"ۡ";i:230;s:2:"ۢ";i:230;s:2:"ۣ";i:220;s:2:"ۤ";i:230;s:2:"ۧ";i:230;s:2:"ۨ";i:230;s:2:"۪";i:220;s:2:"۫";i:230;s:2:"۬";i:230;s:2:"ۭ";i:220;s:2:"ܑ";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"ܴ";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"ܷ";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"ܻ";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"ܿ";i:230;s:2:"݀";i:230;s:2:"݁";i:230;s:2:"݂";i:220;s:2:"݃";i:230;s:2:"݄";i:220;s:2:"݅";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"݊";i:230;s:2:"߫";i:230;s:2:"߬";i:230;s:2:"߭";i:230;s:2:"߮";i:230;s:2:"߯";i:230;s:2:"߰";i:230;s:2:"߱";i:230;s:2:"߲";i:220;s:2:"߳";i:230;s:3:"़";i:7;s:3:"्";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"্";i:9;s:3:"਼";i:7;s:3:"੍";i:9;s:3:"઼";i:7;s:3:"્";i:9;s:3:"଼";i:7;s:3:"୍";i:9;s:3:"்";i:9;s:3:"్";i:9;s:3:"ౕ";i:84;s:3:"ౖ";i:91;s:3:"಼";i:7;s:3:"್";i:9;s:3:"്";i:9;s:3:"්";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"፟";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"៝";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"᬴";i:7;s:3:"᭄";i:9;s:3:"᭫";i:230;s:3:"᭬";i:220;s:3:"᭭";i:230;s:3:"᭮";i:230;s:3:"᭯";i:230;s:3:"᭰";i:230;s:3:"᭱";i:230;s:3:"᭲";i:230;s:3:"᭳";i:230;s:3:"᷀";i:230;s:3:"᷁";i:230;s:3:"᷂";i:220;s:3:"᷃";i:230;s:3:"᷄";i:230;s:3:"᷅";i:230;s:3:"᷆";i:230;s:3:"᷇";i:230;s:3:"᷈";i:230;s:3:"᷉";i:230;s:3:"᷊";i:220;s:3:"᷾";i:230;s:3:"᷿";i:220;s:3:"⃐";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"⃬";i:220;s:3:"⃭";i:220;s:3:"⃮";i:220;s:3:"⃯";i:220;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"゙";i:8;s:3:"゚";i:8;s:3:"꠆";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:4:"𐨍";i:220;s:4:"𐨏";i:230;s:4:"𐨸";i:230;s:4:"𐨹";i:1;s:4:"𐨺";i:220;s:4:"𐨿";i:9;s:4:"𝅥";i:216;s:4:"𝅦";i:216;s:4:"𝅧";i:1;s:4:"𝅨";i:1;s:4:"𝅩";i:1;s:4:"𝅭";i:226;s:4:"𝅮";i:216;s:4:"𝅯";i:216;s:4:"𝅰";i:216;s:4:"𝅱";i:216;s:4:"𝅲";i:216;s:4:"𝅻";i:220;s:4:"𝅼";i:220;s:4:"𝅽";i:220;s:4:"𝅾";i:220;s:4:"𝅿";i:220;s:4:"𝆀";i:220;s:4:"𝆁";i:220;s:4:"𝆂";i:220;s:4:"𝆅";i:230;s:4:"𝆆";i:230;s:4:"𝆇";i:230;s:4:"𝆈";i:230;s:4:"𝆉";i:230;s:4:"𝆊";i:220;s:4:"𝆋";i:220;s:4:"𝆪";i:230;s:4:"𝆫";i:230;s:4:"𝆬";i:230;s:4:"𝆭";i:230;s:4:"𝉂";i:230;s:4:"𝉃";i:230;s:4:"𝉄";i:230;}' ); +$utfCanonicalComp = unserialize( 'a:1862:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s:3:"Ǐ";s:2:"Ǐ";s:3:"ǐ";s:2:"ǐ";s:3:"Ǒ";s:2:"Ǒ";s:3:"ǒ";s:2:"ǒ";s:3:"Ǔ";s:2:"Ǔ";s:3:"ǔ";s:2:"ǔ";s:4:"Ǖ";s:2:"Ǖ";s:4:"ǖ";s:2:"ǖ";s:4:"Ǘ";s:2:"Ǘ";s:4:"ǘ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ǚ";s:4:"ǚ";s:2:"ǚ";s:4:"Ǜ";s:2:"Ǜ";s:4:"ǜ";s:2:"ǜ";s:4:"Ǟ";s:2:"Ǟ";s:4:"ǟ";s:2:"ǟ";s:4:"Ǡ";s:2:"Ǡ";s:4:"ǡ";s:2:"ǡ";s:4:"Ǣ";s:2:"Ǣ";s:4:"ǣ";s:2:"ǣ";s:3:"Ǧ";s:2:"Ǧ";s:3:"ǧ";s:2:"ǧ";s:3:"Ǩ";s:2:"Ǩ";s:3:"ǩ";s:2:"ǩ";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"ǫ";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"ǭ";s:4:"Ǯ";s:2:"Ǯ";s:4:"ǯ";s:2:"ǯ";s:3:"ǰ";s:2:"ǰ";s:3:"Ǵ";s:2:"Ǵ";s:3:"ǵ";s:2:"ǵ";s:3:"Ǹ";s:2:"Ǹ";s:3:"ǹ";s:2:"ǹ";s:4:"Ǻ";s:2:"Ǻ";s:4:"ǻ";s:2:"ǻ";s:4:"Ǽ";s:2:"Ǽ";s:4:"ǽ";s:2:"ǽ";s:4:"Ǿ";s:2:"Ǿ";s:4:"ǿ";s:2:"ǿ";s:3:"Ȁ";s:2:"Ȁ";s:3:"ȁ";s:2:"ȁ";s:3:"Ȃ";s:2:"Ȃ";s:3:"ȃ";s:2:"ȃ";s:3:"Ȅ";s:2:"Ȅ";s:3:"ȅ";s:2:"ȅ";s:3:"Ȇ";s:2:"Ȇ";s:3:"ȇ";s:2:"ȇ";s:3:"Ȉ";s:2:"Ȉ";s:3:"ȉ";s:2:"ȉ";s:3:"Ȋ";s:2:"Ȋ";s:3:"ȋ";s:2:"ȋ";s:3:"Ȍ";s:2:"Ȍ";s:3:"ȍ";s:2:"ȍ";s:3:"Ȏ";s:2:"Ȏ";s:3:"ȏ";s:2:"ȏ";s:3:"Ȑ";s:2:"Ȑ";s:3:"ȑ";s:2:"ȑ";s:3:"Ȓ";s:2:"Ȓ";s:3:"ȓ";s:2:"ȓ";s:3:"Ȕ";s:2:"Ȕ";s:3:"ȕ";s:2:"ȕ";s:3:"Ȗ";s:2:"Ȗ";s:3:"ȗ";s:2:"ȗ";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"ș";s:3:"Ț";s:2:"Ț";s:3:"ț";s:2:"ț";s:3:"Ȟ";s:2:"Ȟ";s:3:"ȟ";s:2:"ȟ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"ȩ";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"ȫ";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"ȭ";s:3:"Ȯ";s:2:"Ȯ";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"Ȳ";s:2:"Ȳ";s:3:"ȳ";s:2:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:4:"̈́";s:2:"̈́";s:2:"ʹ";s:2:"ʹ";s:1:";";s:2:";";s:4:"΅";s:2:"΅";s:4:"Ά";s:2:"Ά";s:2:"·";s:2:"·";s:4:"Έ";s:2:"Έ";s:4:"Ή";s:2:"Ή";s:4:"Ί";s:2:"Ί";s:4:"Ό";s:2:"Ό";s:4:"Ύ";s:2:"Ύ";s:4:"Ώ";s:2:"Ώ";s:4:"ΐ";s:2:"ΐ";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"ά";s:2:"ά";s:4:"έ";s:2:"έ";s:4:"ή";s:2:"ή";s:4:"ί";s:2:"ί";s:4:"ΰ";s:2:"ΰ";s:4:"ϊ";s:2:"ϊ";s:4:"ϋ";s:2:"ϋ";s:4:"ό";s:2:"ό";s:4:"ύ";s:2:"ύ";s:4:"ώ";s:2:"ώ";s:4:"ϓ";s:2:"ϓ";s:4:"ϔ";s:2:"ϔ";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ё";s:4:"Ѓ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"Ќ";s:2:"Ќ";s:4:"Ѝ";s:2:"Ѝ";s:4:"Ў";s:2:"Ў";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"ѐ";s:4:"ё";s:2:"ё";s:4:"ѓ";s:2:"ѓ";s:4:"ї";s:2:"ї";s:4:"ќ";s:2:"ќ";s:4:"ѝ";s:2:"ѝ";s:4:"ў";s:2:"ў";s:4:"Ѷ";s:2:"Ѷ";s:4:"ѷ";s:2:"ѷ";s:4:"Ӂ";s:2:"Ӂ";s:4:"ӂ";s:2:"ӂ";s:4:"Ӑ";s:2:"Ӑ";s:4:"ӑ";s:2:"ӑ";s:4:"Ӓ";s:2:"Ӓ";s:4:"ӓ";s:2:"ӓ";s:4:"Ӗ";s:2:"Ӗ";s:4:"ӗ";s:2:"ӗ";s:4:"Ӛ";s:2:"Ӛ";s:4:"ӛ";s:2:"ӛ";s:4:"Ӝ";s:2:"Ӝ";s:4:"ӝ";s:2:"ӝ";s:4:"Ӟ";s:2:"Ӟ";s:4:"ӟ";s:2:"ӟ";s:4:"Ӣ";s:2:"Ӣ";s:4:"ӣ";s:2:"ӣ";s:4:"Ӥ";s:2:"Ӥ";s:4:"ӥ";s:2:"ӥ";s:4:"Ӧ";s:2:"Ӧ";s:4:"ӧ";s:2:"ӧ";s:4:"Ӫ";s:2:"Ӫ";s:4:"ӫ";s:2:"ӫ";s:4:"Ӭ";s:2:"Ӭ";s:4:"ӭ";s:2:"ӭ";s:4:"Ӯ";s:2:"Ӯ";s:4:"ӯ";s:2:"ӯ";s:4:"Ӱ";s:2:"Ӱ";s:4:"ӱ";s:2:"ӱ";s:4:"Ӳ";s:2:"Ӳ";s:4:"ӳ";s:2:"ӳ";s:4:"Ӵ";s:2:"Ӵ";s:4:"ӵ";s:2:"ӵ";s:4:"Ӹ";s:2:"Ӹ";s:4:"ӹ";s:2:"ӹ";s:4:"آ";s:2:"آ";s:4:"أ";s:2:"أ";s:4:"ؤ";s:2:"ؤ";s:4:"إ";s:2:"إ";s:4:"ئ";s:2:"ئ";s:4:"ۀ";s:2:"ۀ";s:4:"ۂ";s:2:"ۂ";s:4:"ۓ";s:2:"ۓ";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"ো";s:6:"ৌ";s:3:"ৌ";s:6:"ୈ";s:3:"ୈ";s:6:"ୋ";s:3:"ୋ";s:6:"ୌ";s:3:"ୌ";s:6:"ஔ";s:3:"ஔ";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"ೀ";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"ේ";s:6:"ො";s:3:"ො";s:6:"ෝ";s:3:"ෝ";s:6:"ෞ";s:3:"ෞ";s:6:"ཱི";s:3:"ཱི";s:6:"ཱུ";s:3:"ཱུ";s:6:"ཱྀ";s:3:"ཱྀ";s:6:"ဦ";s:3:"ဦ";s:6:"ᬆ";s:3:"ᬆ";s:6:"ᬈ";s:3:"ᬈ";s:6:"ᬊ";s:3:"ᬊ";s:6:"ᬌ";s:3:"ᬌ";s:6:"ᬎ";s:3:"ᬎ";s:6:"ᬒ";s:3:"ᬒ";s:6:"ᬻ";s:3:"ᬻ";s:6:"ᬽ";s:3:"ᬽ";s:6:"ᭀ";s:3:"ᭀ";s:6:"ᭁ";s:3:"ᭁ";s:6:"ᭃ";s:3:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"Ḉ";s:3:"Ḉ";s:4:"ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:4:"Ḕ";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ḗ";s:3:"Ḗ";s:4:"ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"Ḯ";s:3:"Ḯ";s:4:"ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:4:"Ṍ";s:3:"Ṍ";s:4:"ṍ";s:3:"ṍ";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"ṏ";s:4:"Ṑ";s:3:"Ṑ";s:4:"ṑ";s:3:"ṑ";s:4:"Ṓ";s:3:"Ṓ";s:4:"ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"ṥ";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"ṧ";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:4:"Ṹ";s:3:"Ṹ";s:4:"ṹ";s:3:"ṹ";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"Ấ";s:3:"Ấ";s:4:"ấ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ắ";s:3:"Ắ";s:4:"ắ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"Ế";s:3:"Ế";s:4:"ế";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"ề";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"ễ";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:4:"Ố";s:3:"Ố";s:4:"ố";s:3:"ố";s:4:"Ồ";s:3:"Ồ";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"Ổ";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"Ỗ";s:4:"ỗ";s:3:"ỗ";s:5:"Ộ";s:3:"Ộ";s:5:"ộ";s:3:"ộ";s:4:"Ớ";s:3:"Ớ";s:4:"ớ";s:3:"ớ";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"ờ";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"Ỡ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:4:"Ứ";s:3:"Ứ";s:4:"ứ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"ử";s:4:"Ữ";s:3:"Ữ";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"Ự";s:4:"ự";s:3:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"ἀ";s:4:"ἁ";s:3:"ἁ";s:5:"ἂ";s:3:"ἂ";s:5:"ἃ";s:3:"ἃ";s:5:"ἄ";s:3:"ἄ";s:5:"ἅ";s:3:"ἅ";s:5:"ἆ";s:3:"ἆ";s:5:"ἇ";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"Ἄ";s:3:"Ἄ";s:5:"Ἅ";s:3:"Ἅ";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"Ἇ";s:4:"ἐ";s:3:"ἐ";s:4:"ἑ";s:3:"ἑ";s:5:"ἒ";s:3:"ἒ";s:5:"ἓ";s:3:"ἓ";s:5:"ἔ";s:3:"ἔ";s:5:"ἕ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"Ἑ";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"Ἓ";s:5:"Ἔ";s:3:"Ἔ";s:5:"Ἕ";s:3:"Ἕ";s:4:"ἠ";s:3:"ἠ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"ἢ";s:5:"ἣ";s:3:"ἣ";s:5:"ἤ";s:3:"ἤ";s:5:"ἥ";s:3:"ἥ";s:5:"ἦ";s:3:"ἦ";s:5:"ἧ";s:3:"ἧ";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"Ἤ";s:3:"Ἤ";s:5:"Ἥ";s:3:"Ἥ";s:5:"Ἦ";s:3:"Ἦ";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"ἰ";s:4:"ἱ";s:3:"ἱ";s:5:"ἲ";s:3:"ἲ";s:5:"ἳ";s:3:"ἳ";s:5:"ἴ";s:3:"ἴ";s:5:"ἵ";s:3:"ἵ";s:5:"ἶ";s:3:"ἶ";s:5:"ἷ";s:3:"ἷ";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"Ἱ";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"Ἳ";s:5:"Ἴ";s:3:"Ἴ";s:5:"Ἵ";s:3:"Ἵ";s:5:"Ἶ";s:3:"Ἶ";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"ὀ";s:4:"ὁ";s:3:"ὁ";s:5:"ὂ";s:3:"ὂ";s:5:"ὃ";s:3:"ὃ";s:5:"ὄ";s:3:"ὄ";s:5:"ὅ";s:3:"ὅ";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"Ὄ";s:3:"Ὄ";s:5:"Ὅ";s:3:"Ὅ";s:4:"ὐ";s:3:"ὐ";s:4:"ὑ";s:3:"ὑ";s:5:"ὒ";s:3:"ὒ";s:5:"ὓ";s:3:"ὓ";s:5:"ὔ";s:3:"ὔ";s:5:"ὕ";s:3:"ὕ";s:5:"ὖ";s:3:"ὖ";s:5:"ὗ";s:3:"ὗ";s:4:"Ὑ";s:3:"Ὑ";s:5:"Ὓ";s:3:"Ὓ";s:5:"Ὕ";s:3:"Ὕ";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"ὠ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"ὢ";s:5:"ὣ";s:3:"ὣ";s:5:"ὤ";s:3:"ὤ";s:5:"ὥ";s:3:"ὥ";s:5:"ὦ";s:3:"ὦ";s:5:"ὧ";s:3:"ὧ";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"Ὤ";s:3:"Ὤ";s:5:"Ὥ";s:3:"Ὥ";s:5:"Ὦ";s:3:"Ὦ";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"ὰ";s:2:"ά";s:3:"ά";s:4:"ὲ";s:3:"ὲ";s:2:"έ";s:3:"έ";s:4:"ὴ";s:3:"ὴ";s:2:"ή";s:3:"ή";s:4:"ὶ";s:3:"ὶ";s:2:"ί";s:3:"ί";s:4:"ὸ";s:3:"ὸ";s:2:"ό";s:3:"ό";s:4:"ὺ";s:3:"ὺ";s:2:"ύ";s:3:"ύ";s:4:"ὼ";s:3:"ὼ";s:2:"ώ";s:3:"ώ";s:5:"ᾀ";s:3:"ᾀ";s:5:"ᾁ";s:3:"ᾁ";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"ᾅ";s:3:"ᾅ";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"ᾍ";s:3:"ᾍ";s:5:"ᾎ";s:3:"ᾎ";s:5:"ᾏ";s:3:"ᾏ";s:5:"ᾐ";s:3:"ᾐ";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"ᾒ";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"ᾔ";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"ᾖ";s:5:"ᾗ";s:3:"ᾗ";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"ᾙ";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"ᾛ";s:5:"ᾜ";s:3:"ᾜ";s:5:"ᾝ";s:3:"ᾝ";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"ᾠ";s:3:"ᾠ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"ᾢ";s:5:"ᾣ";s:3:"ᾣ";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"ᾥ";s:5:"ᾦ";s:3:"ᾦ";s:5:"ᾧ";s:3:"ᾧ";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"ᾭ";s:3:"ᾭ";s:5:"ᾮ";s:3:"ᾮ";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"ᾰ";s:4:"ᾱ";s:3:"ᾱ";s:5:"ᾲ";s:3:"ᾲ";s:4:"ᾳ";s:3:"ᾳ";s:4:"ᾴ";s:3:"ᾴ";s:4:"ᾶ";s:3:"ᾶ";s:5:"ᾷ";s:3:"ᾷ";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"Ᾱ";s:4:"Ὰ";s:3:"Ὰ";s:2:"Ά";s:3:"Ά";s:4:"ᾼ";s:3:"ᾼ";s:2:"ι";s:3:"ι";s:4:"῁";s:3:"῁";s:5:"ῂ";s:3:"ῂ";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"ῄ";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:2:"Έ";s:3:"Έ";s:4:"Ὴ";s:3:"Ὴ";s:2:"Ή";s:3:"Ή";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"῍";s:5:"῎";s:3:"῎";s:5:"῏";s:3:"῏";s:4:"ῐ";s:3:"ῐ";s:4:"ῑ";s:3:"ῑ";s:4:"ῒ";s:3:"ῒ";s:2:"ΐ";s:3:"ΐ";s:4:"ῖ";s:3:"ῖ";s:4:"ῗ";s:3:"ῗ";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"Ῑ";s:4:"Ὶ";s:3:"Ὶ";s:2:"Ί";s:3:"Ί";s:5:"῝";s:3:"῝";s:5:"῞";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"ῠ";s:4:"ῡ";s:3:"ῡ";s:4:"ῢ";s:3:"ῢ";s:2:"ΰ";s:3:"ΰ";s:4:"ῤ";s:3:"ῤ";s:4:"ῥ";s:3:"ῥ";s:4:"ῦ";s:3:"ῦ";s:4:"ῧ";s:3:"ῧ";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"Ῡ";s:4:"Ὺ";s:3:"Ὺ";s:2:"Ύ";s:3:"Ύ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"῭";s:2:"΅";s:3:"΅";s:1:"`";s:3:"`";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ῴ";s:3:"ῴ";s:4:"ῶ";s:3:"ῶ";s:5:"ῷ";s:3:"ῷ";s:4:"Ὸ";s:3:"Ὸ";s:2:"Ό";s:3:"Ό";s:4:"Ὼ";s:3:"Ὼ";s:2:"Ώ";s:3:"Ώ";s:4:"ῼ";s:3:"ῼ";s:2:"´";s:3:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:2:"Ω";s:3:"Ω";s:1:"K";s:3:"K";s:2:"Å";s:3:"Å";s:5:"↚";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"⇍";s:3:"⇍";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"⇏";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"≁";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"≭";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"⊁";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"⋠";s:5:"⋡";s:3:"⋡";s:5:"⋢";s:3:"⋢";s:5:"⋣";s:3:"⋣";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"⋫";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:6:"が";s:3:"が";s:6:"ぎ";s:3:"ぎ";s:6:"ぐ";s:3:"ぐ";s:6:"げ";s:3:"げ";s:6:"ご";s:3:"ご";s:6:"ざ";s:3:"ざ";s:6:"じ";s:3:"じ";s:6:"ず";s:3:"ず";s:6:"ぜ";s:3:"ぜ";s:6:"ぞ";s:3:"ぞ";s:6:"だ";s:3:"だ";s:6:"ぢ";s:3:"ぢ";s:6:"づ";s:3:"づ";s:6:"で";s:3:"で";s:6:"ど";s:3:"ど";s:6:"ば";s:3:"ば";s:6:"ぱ";s:3:"ぱ";s:6:"び";s:3:"び";s:6:"ぴ";s:3:"ぴ";s:6:"ぶ";s:3:"ぶ";s:6:"ぷ";s:3:"ぷ";s:6:"べ";s:3:"べ";s:6:"ぺ";s:3:"ぺ";s:6:"ぼ";s:3:"ぼ";s:6:"ぽ";s:3:"ぽ";s:6:"ゔ";s:3:"ゔ";s:6:"ゞ";s:3:"ゞ";s:6:"ガ";s:3:"ガ";s:6:"ギ";s:3:"ギ";s:6:"グ";s:3:"グ";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ゴ";s:6:"ザ";s:3:"ザ";s:6:"ジ";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ダ";s:3:"ダ";s:6:"ヂ";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"バ";s:3:"バ";s:6:"パ";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ポ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:4:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:4:"廊";s:3:"朗";s:4:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:4:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:4:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:4:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:4:"異";s:3:"北";s:4:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:4:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:4:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:4:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:4:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:4:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:4:"侮";s:3:"僧";s:4:"僧";s:3:"免";s:4:"免";s:3:"勉";s:4:"勉";s:3:"勤";s:4:"勤";s:3:"卑";s:4:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:4:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:4:"屮";s:3:"悔";s:4:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:4:"憎";s:3:"懲";s:4:"懲";s:3:"敏";s:4:"敏";s:3:"既";s:3:"既";s:3:"暑";s:4:"暑";s:3:"梅";s:4:"梅";s:3:"海";s:4:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:4:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:4:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:4:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"著";s:4:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"並";s:3:"並";s:3:"况";s:4:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:4:"勇";s:3:"勺";s:4:"勺";s:3:"啕";s:3:"啕";s:3:"喙";s:4:"喙";s:3:"嗢";s:3:"嗢";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:4:"慎";s:3:"愈";s:3:"愈";s:3:"慠";s:3:"慠";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"望";s:4:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"滛";s:3:"滛";s:3:"滋";s:4:"滋";s:3:"瀞";s:4:"瀞";s:3:"瞧";s:3:"瞧";s:3:"爵";s:4:"爵";s:3:"犯";s:3:"犯";s:3:"瑱";s:4:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"盛";s:3:"盛";s:3:"直";s:4:"直";s:3:"睊";s:4:"睊";s:3:"着";s:3:"着";s:3:"磌";s:4:"磌";s:3:"窱";s:3:"窱";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"缾";s:3:"缾";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:4:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"調";s:3:"調";s:3:"請";s:3:"請";s:3:"諭";s:4:"諭";s:3:"變";s:4:"變";s:3:"輸";s:4:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"韛";s:3:"韛";s:3:"頋";s:4:"頋";s:3:"鬒";s:4:"鬒";s:4:"𢡊";s:3:"𢡊";s:4:"𢡄";s:3:"𢡄";s:4:"𣏕";s:3:"𣏕";s:3:"㮝";s:4:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:4:"䀹";s:4:"𥉉";s:3:"𥉉";s:4:"𥳐";s:3:"𥳐";s:4:"𧻓";s:3:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"丽";s:4:"丽";s:3:"丸";s:4:"丸";s:3:"乁";s:4:"乁";s:4:"𠄢";s:4:"𠄢";s:3:"你";s:4:"你";s:3:"侻";s:4:"侻";s:3:"倂";s:4:"倂";s:3:"偺";s:4:"偺";s:3:"備";s:4:"備";s:3:"像";s:4:"像";s:3:"㒞";s:4:"㒞";s:4:"𠘺";s:4:"𠘺";s:3:"兔";s:4:"兔";s:3:"兤";s:4:"兤";s:3:"具";s:4:"具";s:4:"𠔜";s:4:"𠔜";s:3:"㒹";s:4:"㒹";s:3:"內";s:4:"內";s:3:"再";s:4:"再";s:4:"𠕋";s:4:"𠕋";s:3:"冗";s:4:"冗";s:3:"冤";s:4:"冤";s:3:"仌";s:4:"仌";s:3:"冬";s:4:"冬";s:4:"𩇟";s:4:"𩇟";s:3:"凵";s:4:"凵";s:3:"刃";s:4:"刃";s:3:"㓟";s:4:"㓟";s:3:"刻";s:4:"刻";s:3:"剆";s:4:"剆";s:3:"割";s:4:"割";s:3:"剷";s:4:"剷";s:3:"㔕";s:4:"㔕";s:3:"包";s:4:"包";s:3:"匆";s:4:"匆";s:3:"卉";s:4:"卉";s:3:"博";s:4:"博";s:3:"即";s:4:"即";s:3:"卽";s:4:"卽";s:3:"卿";s:4:"卿";s:4:"𠨬";s:4:"𠨬";s:3:"灰";s:4:"灰";s:3:"及";s:4:"及";s:3:"叟";s:4:"叟";s:4:"𠭣";s:4:"𠭣";s:3:"叫";s:4:"叫";s:3:"叱";s:4:"叱";s:3:"吆";s:4:"吆";s:3:"咞";s:4:"咞";s:3:"吸";s:4:"吸";s:3:"呈";s:4:"呈";s:3:"周";s:4:"周";s:3:"咢";s:4:"咢";s:3:"哶";s:4:"哶";s:3:"唐";s:4:"唐";s:3:"啓";s:4:"啓";s:3:"啣";s:4:"啣";s:3:"善";s:4:"善";s:3:"喫";s:4:"喫";s:3:"喳";s:4:"喳";s:3:"嗂";s:4:"嗂";s:3:"圖";s:4:"圖";s:3:"圗";s:4:"圗";s:3:"噑";s:4:"噑";s:3:"噴";s:4:"噴";s:3:"壮";s:4:"壮";s:3:"城";s:4:"城";s:3:"埴";s:4:"埴";s:3:"堍";s:4:"堍";s:3:"型";s:4:"型";s:3:"堲";s:4:"堲";s:3:"報";s:4:"報";s:3:"墬";s:4:"墬";s:4:"𡓤";s:4:"𡓤";s:3:"売";s:4:"売";s:3:"壷";s:4:"壷";s:3:"夆";s:4:"夆";s:3:"多";s:4:"多";s:3:"夢";s:4:"夢";s:3:"奢";s:4:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:3:"姬";s:4:"姬";s:3:"娛";s:4:"娛";s:3:"娧";s:4:"娧";s:3:"姘";s:4:"姘";s:3:"婦";s:4:"婦";s:3:"㛮";s:4:"㛮";s:3:"㛼";s:4:"㛼";s:3:"嬈";s:4:"嬈";s:3:"嬾";s:4:"嬾";s:4:"𡧈";s:4:"𡧈";s:3:"寃";s:4:"寃";s:3:"寘";s:4:"寘";s:3:"寳";s:4:"寳";s:4:"𡬘";s:4:"𡬘";s:3:"寿";s:4:"寿";s:3:"将";s:4:"将";s:3:"当";s:4:"当";s:3:"尢";s:4:"尢";s:3:"㞁";s:4:"㞁";s:3:"屠";s:4:"屠";s:3:"峀";s:4:"峀";s:3:"岍";s:4:"岍";s:4:"𡷤";s:4:"𡷤";s:3:"嵃";s:4:"嵃";s:4:"𡷦";s:4:"𡷦";s:3:"嵮";s:4:"嵮";s:3:"嵫";s:4:"嵫";s:3:"嵼";s:4:"嵼";s:3:"巡";s:4:"巡";s:3:"巢";s:4:"巢";s:3:"㠯";s:4:"㠯";s:3:"巽";s:4:"巽";s:3:"帨";s:4:"帨";s:3:"帽";s:4:"帽";s:3:"幩";s:4:"幩";s:3:"㡢";s:4:"㡢";s:4:"𢆃";s:4:"𢆃";s:3:"㡼";s:4:"㡼";s:3:"庰";s:4:"庰";s:3:"庳";s:4:"庳";s:3:"庶";s:4:"庶";s:4:"𪎒";s:4:"𪎒";s:3:"廾";s:4:"廾";s:4:"𢌱";s:4:"𢌱";s:3:"舁";s:4:"舁";s:3:"弢";s:4:"弢";s:3:"㣇";s:4:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:3:"形";s:4:"形";s:3:"彫";s:4:"彫";s:3:"㣣";s:4:"㣣";s:3:"徚";s:4:"徚";s:3:"忍";s:4:"忍";s:3:"志";s:4:"志";s:3:"忹";s:4:"忹";s:3:"悁";s:4:"悁";s:3:"㤺";s:4:"㤺";s:3:"㤜";s:4:"㤜";s:4:"𢛔";s:4:"𢛔";s:3:"惇";s:4:"惇";s:3:"慈";s:4:"慈";s:3:"慌";s:4:"慌";s:3:"慺";s:4:"慺";s:3:"憲";s:4:"憲";s:3:"憤";s:4:"憤";s:3:"憯";s:4:"憯";s:3:"懞";s:4:"懞";s:3:"成";s:4:"成";s:3:"戛";s:4:"戛";s:3:"扝";s:4:"扝";s:3:"抱";s:4:"抱";s:3:"拔";s:4:"拔";s:3:"捐";s:4:"捐";s:4:"𢬌";s:4:"𢬌";s:3:"挽";s:4:"挽";s:3:"拼";s:4:"拼";s:3:"捨";s:4:"捨";s:3:"掃";s:4:"掃";s:3:"揤";s:4:"揤";s:4:"𢯱";s:4:"𢯱";s:3:"搢";s:4:"搢";s:3:"揅";s:4:"揅";s:3:"掩";s:4:"掩";s:3:"㨮";s:4:"㨮";s:3:"摩";s:4:"摩";s:3:"摾";s:4:"摾";s:3:"撝";s:4:"撝";s:3:"摷";s:4:"摷";s:3:"㩬";s:4:"㩬";s:3:"敬";s:4:"敬";s:4:"𣀊";s:4:"𣀊";s:3:"旣";s:4:"旣";s:3:"書";s:4:"書";s:3:"晉";s:4:"晉";s:3:"㬙";s:4:"㬙";s:3:"㬈";s:4:"㬈";s:3:"㫤";s:4:"㫤";s:3:"冒";s:4:"冒";s:3:"冕";s:4:"冕";s:3:"最";s:4:"最";s:3:"暜";s:4:"暜";s:3:"肭";s:4:"肭";s:3:"䏙";s:4:"䏙";s:3:"朡";s:4:"朡";s:3:"杞";s:4:"杞";s:3:"杓";s:4:"杓";s:4:"𣏃";s:4:"𣏃";s:3:"㭉";s:4:"㭉";s:3:"柺";s:4:"柺";s:3:"枅";s:4:"枅";s:3:"桒";s:4:"桒";s:4:"𣑭";s:4:"𣑭";s:3:"梎";s:4:"梎";s:3:"栟";s:4:"栟";s:3:"椔";s:4:"椔";s:3:"楂";s:4:"楂";s:3:"榣";s:4:"榣";s:3:"槪";s:4:"槪";s:3:"檨";s:4:"檨";s:4:"𣚣";s:4:"𣚣";s:3:"櫛";s:4:"櫛";s:3:"㰘";s:4:"㰘";s:3:"次";s:4:"次";s:4:"𣢧";s:4:"𣢧";s:3:"歔";s:4:"歔";s:3:"㱎";s:4:"㱎";s:3:"歲";s:4:"歲";s:3:"殟";s:4:"殟";s:3:"殻";s:4:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:3:"汎";s:4:"汎";s:4:"𣲼";s:4:"𣲼";s:3:"沿";s:4:"沿";s:3:"泍";s:4:"泍";s:3:"汧";s:4:"汧";s:3:"洖";s:4:"洖";s:3:"派";s:4:"派";s:3:"浩";s:4:"浩";s:3:"浸";s:4:"浸";s:3:"涅";s:4:"涅";s:4:"𣴞";s:4:"𣴞";s:3:"洴";s:4:"洴";s:3:"港";s:4:"港";s:3:"湮";s:4:"湮";s:3:"㴳";s:4:"㴳";s:3:"滇";s:4:"滇";s:4:"𣻑";s:4:"𣻑";s:3:"淹";s:4:"淹";s:3:"潮";s:4:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:3:"濆";s:4:"濆";s:3:"瀹";s:4:"瀹";s:3:"瀛";s:4:"瀛";s:3:"㶖";s:4:"㶖";s:3:"灊";s:4:"灊";s:3:"災";s:4:"災";s:3:"灷";s:4:"灷";s:3:"炭";s:4:"炭";s:4:"𠔥";s:4:"𠔥";s:3:"煅";s:4:"煅";s:4:"𤉣";s:4:"𤉣";s:3:"熜";s:4:"熜";s:4:"𤎫";s:4:"𤎫";s:3:"爨";s:4:"爨";s:3:"牐";s:4:"牐";s:4:"𤘈";s:4:"𤘈";s:3:"犀";s:4:"犀";s:3:"犕";s:4:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:3:"獺";s:4:"獺";s:3:"王";s:4:"王";s:3:"㺬";s:4:"㺬";s:3:"玥";s:4:"玥";s:3:"㺸";s:4:"㺸";s:3:"瑇";s:4:"瑇";s:3:"瑜";s:4:"瑜";s:3:"璅";s:4:"璅";s:3:"瓊";s:4:"瓊";s:3:"㼛";s:4:"㼛";s:3:"甤";s:4:"甤";s:4:"𤰶";s:4:"𤰶";s:3:"甾";s:4:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"𢆟";s:4:"𢆟";s:3:"瘐";s:4:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:3:"㿼";s:4:"㿼";s:3:"䀈";s:4:"䀈";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:3:"眞";s:4:"眞";s:3:"真";s:4:"真";s:3:"瞋";s:4:"瞋";s:3:"䁆";s:4:"䁆";s:3:"䂖";s:4:"䂖";s:4:"𥐝";s:4:"𥐝";s:3:"硎";s:4:"硎";s:3:"䃣";s:4:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:3:"秫";s:4:"秫";s:3:"䄯";s:4:"䄯";s:3:"穊";s:4:"穊";s:3:"穏";s:4:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:3:"竮";s:4:"竮";s:3:"䈂";s:4:"䈂";s:4:"𥮫";s:4:"𥮫";s:3:"篆";s:4:"篆";s:3:"築";s:4:"築";s:3:"䈧";s:4:"䈧";s:4:"𥲀";s:4:"𥲀";s:3:"糒";s:4:"糒";s:3:"䊠";s:4:"䊠";s:3:"糨";s:4:"糨";s:3:"糣";s:4:"糣";s:3:"紀";s:4:"紀";s:4:"𥾆";s:4:"𥾆";s:3:"絣";s:4:"絣";s:3:"䌁";s:4:"䌁";s:3:"緇";s:4:"緇";s:3:"縂";s:4:"縂";s:3:"繅";s:4:"繅";s:3:"䌴";s:4:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:3:"䍙";s:4:"䍙";s:4:"𦋙";s:4:"𦋙";s:3:"罺";s:4:"罺";s:4:"𦌾";s:4:"𦌾";s:3:"羕";s:4:"羕";s:3:"翺";s:4:"翺";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:3:"聠";s:4:"聠";s:4:"𦖨";s:4:"𦖨";s:3:"聰";s:4:"聰";s:4:"𣍟";s:4:"𣍟";s:3:"䏕";s:4:"䏕";s:3:"育";s:4:"育";s:3:"脃";s:4:"脃";s:3:"䐋";s:4:"䐋";s:3:"脾";s:4:"脾";s:3:"媵";s:4:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:3:"舄";s:4:"舄";s:3:"辞";s:4:"辞";s:3:"䑫";s:4:"䑫";s:3:"芑";s:4:"芑";s:3:"芋";s:4:"芋";s:3:"芝";s:4:"芝";s:3:"劳";s:4:"劳";s:3:"花";s:4:"花";s:3:"芳";s:4:"芳";s:3:"芽";s:4:"芽";s:3:"苦";s:4:"苦";s:4:"𦬼";s:4:"𦬼";s:3:"茝";s:4:"茝";s:3:"荣";s:4:"荣";s:3:"莭";s:4:"莭";s:3:"茣";s:4:"茣";s:3:"莽";s:4:"莽";s:3:"菧";s:4:"菧";s:3:"荓";s:4:"荓";s:3:"菊";s:4:"菊";s:3:"菌";s:4:"菌";s:3:"菜";s:4:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:3:"䔫";s:4:"䔫";s:3:"蓱";s:4:"蓱";s:3:"蓳";s:4:"蓳";s:3:"蔖";s:4:"蔖";s:4:"𧏊";s:4:"𧏊";s:3:"蕤";s:4:"蕤";s:4:"𦼬";s:4:"𦼬";s:3:"䕝";s:4:"䕝";s:3:"䕡";s:4:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:3:"䕫";s:4:"䕫";s:3:"虐";s:4:"虐";s:3:"虧";s:4:"虧";s:3:"虩";s:4:"虩";s:3:"蚩";s:4:"蚩";s:3:"蚈";s:4:"蚈";s:3:"蜎";s:4:"蜎";s:3:"蛢";s:4:"蛢";s:3:"蜨";s:4:"蜨";s:3:"蝫";s:4:"蝫";s:3:"螆";s:4:"螆";s:3:"䗗";s:4:"䗗";s:3:"蟡";s:4:"蟡";s:3:"蠁";s:4:"蠁";s:3:"䗹";s:4:"䗹";s:3:"衠";s:4:"衠";s:3:"衣";s:4:"衣";s:4:"𧙧";s:4:"𧙧";s:3:"裗";s:4:"裗";s:3:"裞";s:4:"裞";s:3:"䘵";s:4:"䘵";s:3:"裺";s:4:"裺";s:3:"㒻";s:4:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:3:"䚾";s:4:"䚾";s:3:"䛇";s:4:"䛇";s:3:"誠";s:4:"誠";s:3:"豕";s:4:"豕";s:4:"𧲨";s:4:"𧲨";s:3:"貫";s:4:"貫";s:3:"賁";s:4:"賁";s:3:"贛";s:4:"贛";s:3:"起";s:4:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:3:"跋";s:4:"跋";s:3:"趼";s:4:"趼";s:3:"跰";s:4:"跰";s:4:"𠣞";s:4:"𠣞";s:3:"軔";s:4:"軔";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:3:"邔";s:4:"邔";s:3:"郱";s:4:"郱";s:3:"鄑";s:4:"鄑";s:4:"𨜮";s:4:"𨜮";s:3:"鄛";s:4:"鄛";s:3:"鈸";s:4:"鈸";s:3:"鋗";s:4:"鋗";s:3:"鋘";s:4:"鋘";s:3:"鉼";s:4:"鉼";s:3:"鏹";s:4:"鏹";s:3:"鐕";s:4:"鐕";s:4:"𨯺";s:4:"𨯺";s:3:"開";s:4:"開";s:3:"䦕";s:4:"䦕";s:3:"閷";s:4:"閷";s:4:"𨵷";s:4:"𨵷";s:3:"䧦";s:4:"䧦";s:3:"雃";s:4:"雃";s:3:"嶲";s:4:"嶲";s:3:"霣";s:4:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:3:"䩮";s:4:"䩮";s:3:"䩶";s:4:"䩶";s:3:"韠";s:4:"韠";s:4:"𩐊";s:4:"𩐊";s:3:"䪲";s:4:"䪲";s:4:"𩒖";s:4:"𩒖";s:3:"頩";s:4:"頩";s:4:"𩖶";s:4:"𩖶";s:3:"飢";s:4:"飢";s:3:"䬳";s:4:"䬳";s:3:"餩";s:4:"餩";s:3:"馧";s:4:"馧";s:3:"駂";s:4:"駂";s:3:"駾";s:4:"駾";s:3:"䯎";s:4:"䯎";s:4:"𩬰";s:4:"𩬰";s:3:"鱀";s:4:"鱀";s:3:"鳽";s:4:"鳽";s:3:"䳎";s:4:"䳎";s:3:"䳭";s:4:"䳭";s:3:"鵧";s:4:"鵧";s:4:"𪃎";s:4:"𪃎";s:3:"䳸";s:4:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:3:"麻";s:4:"麻";s:3:"䵖";s:4:"䵖";s:3:"黹";s:4:"黹";s:3:"黾";s:4:"黾";s:3:"鼅";s:4:"鼅";s:3:"鼏";s:4:"鼏";s:3:"鼖";s:4:"鼖";s:3:"鼻";s:4:"鼻";s:4:"𪘀";s:4:"𪘀";}' ); +$utfCanonicalDecomp = unserialize( 'a:2043:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:";";s:1:";";s:2:"΅";s:4:"΅";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϓ";s:4:"ϓ";s:2:"ϔ";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"ι";s:2:"ι";s:3:"῁";s:4:"῁";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:"῍";s:3:"῎";s:5:"῎";s:3:"῏";s:5:"῏";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:"῝";s:3:"῞";s:5:"῞";s:3:"῟";s:5:"῟";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:4:"῭";s:3:"΅";s:4:"΅";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"⫝̸";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"ゞ";s:6:"ゞ";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' ); +$utfCheckNFC = unserialize( 'a:1217:{s:2:"̀";s:1:"N";s:2:"́";s:1:"N";s:2:"̓";s:1:"N";s:2:"̈́";s:1:"N";s:2:"ʹ";s:1:"N";s:2:";";s:1:"N";s:2:"·";s:1:"N";s:3:"क़";s:1:"N";s:3:"ख़";s:1:"N";s:3:"ग़";s:1:"N";s:3:"ज़";s:1:"N";s:3:"ड़";s:1:"N";s:3:"ढ़";s:1:"N";s:3:"फ़";s:1:"N";s:3:"य़";s:1:"N";s:3:"ড়";s:1:"N";s:3:"ঢ়";s:1:"N";s:3:"য়";s:1:"N";s:3:"ਲ਼";s:1:"N";s:3:"ਸ਼";s:1:"N";s:3:"ਖ਼";s:1:"N";s:3:"ਗ਼";s:1:"N";s:3:"ਜ਼";s:1:"N";s:3:"ਫ਼";s:1:"N";s:3:"ଡ଼";s:1:"N";s:3:"ଢ଼";s:1:"N";s:3:"གྷ";s:1:"N";s:3:"ཌྷ";s:1:"N";s:3:"དྷ";s:1:"N";s:3:"བྷ";s:1:"N";s:3:"ཛྷ";s:1:"N";s:3:"ཀྵ";s:1:"N";s:3:"ཱི";s:1:"N";s:3:"ཱུ";s:1:"N";s:3:"ྲྀ";s:1:"N";s:3:"ླྀ";s:1:"N";s:3:"ཱྀ";s:1:"N";s:3:"ྒྷ";s:1:"N";s:3:"ྜྷ";s:1:"N";s:3:"ྡྷ";s:1:"N";s:3:"ྦྷ";s:1:"N";s:3:"ྫྷ";s:1:"N";s:3:"ྐྵ";s:1:"N";s:3:"ά";s:1:"N";s:3:"έ";s:1:"N";s:3:"ή";s:1:"N";s:3:"ί";s:1:"N";s:3:"ό";s:1:"N";s:3:"ύ";s:1:"N";s:3:"ώ";s:1:"N";s:3:"Ά";s:1:"N";s:3:"ι";s:1:"N";s:3:"Έ";s:1:"N";s:3:"Ή";s:1:"N";s:3:"ΐ";s:1:"N";s:3:"Ί";s:1:"N";s:3:"ΰ";s:1:"N";s:3:"Ύ";s:1:"N";s:3:"΅";s:1:"N";s:3:"`";s:1:"N";s:3:"Ό";s:1:"N";s:3:"Ώ";s:1:"N";s:3:"´";s:1:"N";s:3:" ";s:1:"N";s:3:" ";s:1:"N";s:3:"Ω";s:1:"N";s:3:"K";s:1:"N";s:3:"Å";s:1:"N";s:3:"〈";s:1:"N";s:3:"〉";s:1:"N";s:3:"⫝̸";s:1:"N";s:3:"豈";s:1:"N";s:3:"更";s:1:"N";s:3:"車";s:1:"N";s:3:"賈";s:1:"N";s:3:"滑";s:1:"N";s:3:"串";s:1:"N";s:3:"句";s:1:"N";s:3:"龜";s:1:"N";s:3:"龜";s:1:"N";s:3:"契";s:1:"N";s:3:"金";s:1:"N";s:3:"喇";s:1:"N";s:3:"奈";s:1:"N";s:3:"懶";s:1:"N";s:3:"癩";s:1:"N";s:3:"羅";s:1:"N";s:3:"蘿";s:1:"N";s:3:"螺";s:1:"N";s:3:"裸";s:1:"N";s:3:"邏";s:1:"N";s:3:"樂";s:1:"N";s:3:"洛";s:1:"N";s:3:"烙";s:1:"N";s:3:"珞";s:1:"N";s:3:"落";s:1:"N";s:3:"酪";s:1:"N";s:3:"駱";s:1:"N";s:3:"亂";s:1:"N";s:3:"卵";s:1:"N";s:3:"欄";s:1:"N";s:3:"爛";s:1:"N";s:3:"蘭";s:1:"N";s:3:"鸞";s:1:"N";s:3:"嵐";s:1:"N";s:3:"濫";s:1:"N";s:3:"藍";s:1:"N";s:3:"襤";s:1:"N";s:3:"拉";s:1:"N";s:3:"臘";s:1:"N";s:3:"蠟";s:1:"N";s:3:"廊";s:1:"N";s:3:"朗";s:1:"N";s:3:"浪";s:1:"N";s:3:"狼";s:1:"N";s:3:"郎";s:1:"N";s:3:"來";s:1:"N";s:3:"冷";s:1:"N";s:3:"勞";s:1:"N";s:3:"擄";s:1:"N";s:3:"櫓";s:1:"N";s:3:"爐";s:1:"N";s:3:"盧";s:1:"N";s:3:"老";s:1:"N";s:3:"蘆";s:1:"N";s:3:"虜";s:1:"N";s:3:"路";s:1:"N";s:3:"露";s:1:"N";s:3:"魯";s:1:"N";s:3:"鷺";s:1:"N";s:3:"碌";s:1:"N";s:3:"祿";s:1:"N";s:3:"綠";s:1:"N";s:3:"菉";s:1:"N";s:3:"錄";s:1:"N";s:3:"鹿";s:1:"N";s:3:"論";s:1:"N";s:3:"壟";s:1:"N";s:3:"弄";s:1:"N";s:3:"籠";s:1:"N";s:3:"聾";s:1:"N";s:3:"牢";s:1:"N";s:3:"磊";s:1:"N";s:3:"賂";s:1:"N";s:3:"雷";s:1:"N";s:3:"壘";s:1:"N";s:3:"屢";s:1:"N";s:3:"樓";s:1:"N";s:3:"淚";s:1:"N";s:3:"漏";s:1:"N";s:3:"累";s:1:"N";s:3:"縷";s:1:"N";s:3:"陋";s:1:"N";s:3:"勒";s:1:"N";s:3:"肋";s:1:"N";s:3:"凜";s:1:"N";s:3:"凌";s:1:"N";s:3:"稜";s:1:"N";s:3:"綾";s:1:"N";s:3:"菱";s:1:"N";s:3:"陵";s:1:"N";s:3:"讀";s:1:"N";s:3:"拏";s:1:"N";s:3:"樂";s:1:"N";s:3:"諾";s:1:"N";s:3:"丹";s:1:"N";s:3:"寧";s:1:"N";s:3:"怒";s:1:"N";s:3:"率";s:1:"N";s:3:"異";s:1:"N";s:3:"北";s:1:"N";s:3:"磻";s:1:"N";s:3:"便";s:1:"N";s:3:"復";s:1:"N";s:3:"不";s:1:"N";s:3:"泌";s:1:"N";s:3:"數";s:1:"N";s:3:"索";s:1:"N";s:3:"參";s:1:"N";s:3:"塞";s:1:"N";s:3:"省";s:1:"N";s:3:"葉";s:1:"N";s:3:"說";s:1:"N";s:3:"殺";s:1:"N";s:3:"辰";s:1:"N";s:3:"沈";s:1:"N";s:3:"拾";s:1:"N";s:3:"若";s:1:"N";s:3:"掠";s:1:"N";s:3:"略";s:1:"N";s:3:"亮";s:1:"N";s:3:"兩";s:1:"N";s:3:"凉";s:1:"N";s:3:"梁";s:1:"N";s:3:"糧";s:1:"N";s:3:"良";s:1:"N";s:3:"諒";s:1:"N";s:3:"量";s:1:"N";s:3:"勵";s:1:"N";s:3:"呂";s:1:"N";s:3:"女";s:1:"N";s:3:"廬";s:1:"N";s:3:"旅";s:1:"N";s:3:"濾";s:1:"N";s:3:"礪";s:1:"N";s:3:"閭";s:1:"N";s:3:"驪";s:1:"N";s:3:"麗";s:1:"N";s:3:"黎";s:1:"N";s:3:"力";s:1:"N";s:3:"曆";s:1:"N";s:3:"歷";s:1:"N";s:3:"轢";s:1:"N";s:3:"年";s:1:"N";s:3:"憐";s:1:"N";s:3:"戀";s:1:"N";s:3:"撚";s:1:"N";s:3:"漣";s:1:"N";s:3:"煉";s:1:"N";s:3:"璉";s:1:"N";s:3:"秊";s:1:"N";s:3:"練";s:1:"N";s:3:"聯";s:1:"N";s:3:"輦";s:1:"N";s:3:"蓮";s:1:"N";s:3:"連";s:1:"N";s:3:"鍊";s:1:"N";s:3:"列";s:1:"N";s:3:"劣";s:1:"N";s:3:"咽";s:1:"N";s:3:"烈";s:1:"N";s:3:"裂";s:1:"N";s:3:"說";s:1:"N";s:3:"廉";s:1:"N";s:3:"念";s:1:"N";s:3:"捻";s:1:"N";s:3:"殮";s:1:"N";s:3:"簾";s:1:"N";s:3:"獵";s:1:"N";s:3:"令";s:1:"N";s:3:"囹";s:1:"N";s:3:"寧";s:1:"N";s:3:"嶺";s:1:"N";s:3:"怜";s:1:"N";s:3:"玲";s:1:"N";s:3:"瑩";s:1:"N";s:3:"羚";s:1:"N";s:3:"聆";s:1:"N";s:3:"鈴";s:1:"N";s:3:"零";s:1:"N";s:3:"靈";s:1:"N";s:3:"領";s:1:"N";s:3:"例";s:1:"N";s:3:"禮";s:1:"N";s:3:"醴";s:1:"N";s:3:"隸";s:1:"N";s:3:"惡";s:1:"N";s:3:"了";s:1:"N";s:3:"僚";s:1:"N";s:3:"寮";s:1:"N";s:3:"尿";s:1:"N";s:3:"料";s:1:"N";s:3:"樂";s:1:"N";s:3:"燎";s:1:"N";s:3:"療";s:1:"N";s:3:"蓼";s:1:"N";s:3:"遼";s:1:"N";s:3:"龍";s:1:"N";s:3:"暈";s:1:"N";s:3:"阮";s:1:"N";s:3:"劉";s:1:"N";s:3:"杻";s:1:"N";s:3:"柳";s:1:"N";s:3:"流";s:1:"N";s:3:"溜";s:1:"N";s:3:"琉";s:1:"N";s:3:"留";s:1:"N";s:3:"硫";s:1:"N";s:3:"紐";s:1:"N";s:3:"類";s:1:"N";s:3:"六";s:1:"N";s:3:"戮";s:1:"N";s:3:"陸";s:1:"N";s:3:"倫";s:1:"N";s:3:"崙";s:1:"N";s:3:"淪";s:1:"N";s:3:"輪";s:1:"N";s:3:"律";s:1:"N";s:3:"慄";s:1:"N";s:3:"栗";s:1:"N";s:3:"率";s:1:"N";s:3:"隆";s:1:"N";s:3:"利";s:1:"N";s:3:"吏";s:1:"N";s:3:"履";s:1:"N";s:3:"易";s:1:"N";s:3:"李";s:1:"N";s:3:"梨";s:1:"N";s:3:"泥";s:1:"N";s:3:"理";s:1:"N";s:3:"痢";s:1:"N";s:3:"罹";s:1:"N";s:3:"裏";s:1:"N";s:3:"裡";s:1:"N";s:3:"里";s:1:"N";s:3:"離";s:1:"N";s:3:"匿";s:1:"N";s:3:"溺";s:1:"N";s:3:"吝";s:1:"N";s:3:"燐";s:1:"N";s:3:"璘";s:1:"N";s:3:"藺";s:1:"N";s:3:"隣";s:1:"N";s:3:"鱗";s:1:"N";s:3:"麟";s:1:"N";s:3:"林";s:1:"N";s:3:"淋";s:1:"N";s:3:"臨";s:1:"N";s:3:"立";s:1:"N";s:3:"笠";s:1:"N";s:3:"粒";s:1:"N";s:3:"狀";s:1:"N";s:3:"炙";s:1:"N";s:3:"識";s:1:"N";s:3:"什";s:1:"N";s:3:"茶";s:1:"N";s:3:"刺";s:1:"N";s:3:"切";s:1:"N";s:3:"度";s:1:"N";s:3:"拓";s:1:"N";s:3:"糖";s:1:"N";s:3:"宅";s:1:"N";s:3:"洞";s:1:"N";s:3:"暴";s:1:"N";s:3:"輻";s:1:"N";s:3:"行";s:1:"N";s:3:"降";s:1:"N";s:3:"見";s:1:"N";s:3:"廓";s:1:"N";s:3:"兀";s:1:"N";s:3:"嗀";s:1:"N";s:3:"塚";s:1:"N";s:3:"晴";s:1:"N";s:3:"凞";s:1:"N";s:3:"猪";s:1:"N";s:3:"益";s:1:"N";s:3:"礼";s:1:"N";s:3:"神";s:1:"N";s:3:"祥";s:1:"N";s:3:"福";s:1:"N";s:3:"靖";s:1:"N";s:3:"精";s:1:"N";s:3:"羽";s:1:"N";s:3:"蘒";s:1:"N";s:3:"諸";s:1:"N";s:3:"逸";s:1:"N";s:3:"都";s:1:"N";s:3:"飯";s:1:"N";s:3:"飼";s:1:"N";s:3:"館";s:1:"N";s:3:"鶴";s:1:"N";s:3:"侮";s:1:"N";s:3:"僧";s:1:"N";s:3:"免";s:1:"N";s:3:"勉";s:1:"N";s:3:"勤";s:1:"N";s:3:"卑";s:1:"N";s:3:"喝";s:1:"N";s:3:"嘆";s:1:"N";s:3:"器";s:1:"N";s:3:"塀";s:1:"N";s:3:"墨";s:1:"N";s:3:"層";s:1:"N";s:3:"屮";s:1:"N";s:3:"悔";s:1:"N";s:3:"慨";s:1:"N";s:3:"憎";s:1:"N";s:3:"懲";s:1:"N";s:3:"敏";s:1:"N";s:3:"既";s:1:"N";s:3:"暑";s:1:"N";s:3:"梅";s:1:"N";s:3:"海";s:1:"N";s:3:"渚";s:1:"N";s:3:"漢";s:1:"N";s:3:"煮";s:1:"N";s:3:"爫";s:1:"N";s:3:"琢";s:1:"N";s:3:"碑";s:1:"N";s:3:"社";s:1:"N";s:3:"祉";s:1:"N";s:3:"祈";s:1:"N";s:3:"祐";s:1:"N";s:3:"祖";s:1:"N";s:3:"祝";s:1:"N";s:3:"禍";s:1:"N";s:3:"禎";s:1:"N";s:3:"穀";s:1:"N";s:3:"突";s:1:"N";s:3:"節";s:1:"N";s:3:"練";s:1:"N";s:3:"縉";s:1:"N";s:3:"繁";s:1:"N";s:3:"署";s:1:"N";s:3:"者";s:1:"N";s:3:"臭";s:1:"N";s:3:"艹";s:1:"N";s:3:"艹";s:1:"N";s:3:"著";s:1:"N";s:3:"褐";s:1:"N";s:3:"視";s:1:"N";s:3:"謁";s:1:"N";s:3:"謹";s:1:"N";s:3:"賓";s:1:"N";s:3:"贈";s:1:"N";s:3:"辶";s:1:"N";s:3:"逸";s:1:"N";s:3:"難";s:1:"N";s:3:"響";s:1:"N";s:3:"頻";s:1:"N";s:3:"並";s:1:"N";s:3:"况";s:1:"N";s:3:"全";s:1:"N";s:3:"侀";s:1:"N";s:3:"充";s:1:"N";s:3:"冀";s:1:"N";s:3:"勇";s:1:"N";s:3:"勺";s:1:"N";s:3:"喝";s:1:"N";s:3:"啕";s:1:"N";s:3:"喙";s:1:"N";s:3:"嗢";s:1:"N";s:3:"塚";s:1:"N";s:3:"墳";s:1:"N";s:3:"奄";s:1:"N";s:3:"奔";s:1:"N";s:3:"婢";s:1:"N";s:3:"嬨";s:1:"N";s:3:"廒";s:1:"N";s:3:"廙";s:1:"N";s:3:"彩";s:1:"N";s:3:"徭";s:1:"N";s:3:"惘";s:1:"N";s:3:"慎";s:1:"N";s:3:"愈";s:1:"N";s:3:"憎";s:1:"N";s:3:"慠";s:1:"N";s:3:"懲";s:1:"N";s:3:"戴";s:1:"N";s:3:"揄";s:1:"N";s:3:"搜";s:1:"N";s:3:"摒";s:1:"N";s:3:"敖";s:1:"N";s:3:"晴";s:1:"N";s:3:"朗";s:1:"N";s:3:"望";s:1:"N";s:3:"杖";s:1:"N";s:3:"歹";s:1:"N";s:3:"殺";s:1:"N";s:3:"流";s:1:"N";s:3:"滛";s:1:"N";s:3:"滋";s:1:"N";s:3:"漢";s:1:"N";s:3:"瀞";s:1:"N";s:3:"煮";s:1:"N";s:3:"瞧";s:1:"N";s:3:"爵";s:1:"N";s:3:"犯";s:1:"N";s:3:"猪";s:1:"N";s:3:"瑱";s:1:"N";s:3:"甆";s:1:"N";s:3:"画";s:1:"N";s:3:"瘝";s:1:"N";s:3:"瘟";s:1:"N";s:3:"益";s:1:"N";s:3:"盛";s:1:"N";s:3:"直";s:1:"N";s:3:"睊";s:1:"N";s:3:"着";s:1:"N";s:3:"磌";s:1:"N";s:3:"窱";s:1:"N";s:3:"節";s:1:"N";s:3:"类";s:1:"N";s:3:"絛";s:1:"N";s:3:"練";s:1:"N";s:3:"缾";s:1:"N";s:3:"者";s:1:"N";s:3:"荒";s:1:"N";s:3:"華";s:1:"N";s:3:"蝹";s:1:"N";s:3:"襁";s:1:"N";s:3:"覆";s:1:"N";s:3:"視";s:1:"N";s:3:"調";s:1:"N";s:3:"諸";s:1:"N";s:3:"請";s:1:"N";s:3:"謁";s:1:"N";s:3:"諾";s:1:"N";s:3:"諭";s:1:"N";s:3:"謹";s:1:"N";s:3:"變";s:1:"N";s:3:"贈";s:1:"N";s:3:"輸";s:1:"N";s:3:"遲";s:1:"N";s:3:"醙";s:1:"N";s:3:"鉶";s:1:"N";s:3:"陼";s:1:"N";s:3:"難";s:1:"N";s:3:"靖";s:1:"N";s:3:"韛";s:1:"N";s:3:"響";s:1:"N";s:3:"頋";s:1:"N";s:3:"頻";s:1:"N";s:3:"鬒";s:1:"N";s:3:"龜";s:1:"N";s:3:"𢡊";s:1:"N";s:3:"𢡄";s:1:"N";s:3:"𣏕";s:1:"N";s:3:"㮝";s:1:"N";s:3:"䀘";s:1:"N";s:3:"䀹";s:1:"N";s:3:"𥉉";s:1:"N";s:3:"𥳐";s:1:"N";s:3:"𧻓";s:1:"N";s:3:"齃";s:1:"N";s:3:"龎";s:1:"N";s:3:"יִ";s:1:"N";s:3:"ײַ";s:1:"N";s:3:"שׁ";s:1:"N";s:3:"שׂ";s:1:"N";s:3:"שּׁ";s:1:"N";s:3:"שּׂ";s:1:"N";s:3:"אַ";s:1:"N";s:3:"אָ";s:1:"N";s:3:"אּ";s:1:"N";s:3:"בּ";s:1:"N";s:3:"גּ";s:1:"N";s:3:"דּ";s:1:"N";s:3:"הּ";s:1:"N";s:3:"וּ";s:1:"N";s:3:"זּ";s:1:"N";s:3:"טּ";s:1:"N";s:3:"יּ";s:1:"N";s:3:"ךּ";s:1:"N";s:3:"כּ";s:1:"N";s:3:"לּ";s:1:"N";s:3:"מּ";s:1:"N";s:3:"נּ";s:1:"N";s:3:"סּ";s:1:"N";s:3:"ףּ";s:1:"N";s:3:"פּ";s:1:"N";s:3:"צּ";s:1:"N";s:3:"קּ";s:1:"N";s:3:"רּ";s:1:"N";s:3:"שּ";s:1:"N";s:3:"תּ";s:1:"N";s:3:"וֹ";s:1:"N";s:3:"בֿ";s:1:"N";s:3:"כֿ";s:1:"N";s:3:"פֿ";s:1:"N";s:4:"𝅗𝅥";s:1:"N";s:4:"𝅘𝅥";s:1:"N";s:4:"𝅘𝅥𝅮";s:1:"N";s:4:"𝅘𝅥𝅯";s:1:"N";s:4:"𝅘𝅥𝅰";s:1:"N";s:4:"𝅘𝅥𝅱";s:1:"N";s:4:"𝅘𝅥𝅲";s:1:"N";s:4:"𝆹𝅥";s:1:"N";s:4:"𝆺𝅥";s:1:"N";s:4:"𝆹𝅥𝅮";s:1:"N";s:4:"𝆺𝅥𝅮";s:1:"N";s:4:"𝆹𝅥𝅯";s:1:"N";s:4:"𝆺𝅥𝅯";s:1:"N";s:4:"丽";s:1:"N";s:4:"丸";s:1:"N";s:4:"乁";s:1:"N";s:4:"𠄢";s:1:"N";s:4:"你";s:1:"N";s:4:"侮";s:1:"N";s:4:"侻";s:1:"N";s:4:"倂";s:1:"N";s:4:"偺";s:1:"N";s:4:"備";s:1:"N";s:4:"僧";s:1:"N";s:4:"像";s:1:"N";s:4:"㒞";s:1:"N";s:4:"𠘺";s:1:"N";s:4:"免";s:1:"N";s:4:"兔";s:1:"N";s:4:"兤";s:1:"N";s:4:"具";s:1:"N";s:4:"𠔜";s:1:"N";s:4:"㒹";s:1:"N";s:4:"內";s:1:"N";s:4:"再";s:1:"N";s:4:"𠕋";s:1:"N";s:4:"冗";s:1:"N";s:4:"冤";s:1:"N";s:4:"仌";s:1:"N";s:4:"冬";s:1:"N";s:4:"况";s:1:"N";s:4:"𩇟";s:1:"N";s:4:"凵";s:1:"N";s:4:"刃";s:1:"N";s:4:"㓟";s:1:"N";s:4:"刻";s:1:"N";s:4:"剆";s:1:"N";s:4:"割";s:1:"N";s:4:"剷";s:1:"N";s:4:"㔕";s:1:"N";s:4:"勇";s:1:"N";s:4:"勉";s:1:"N";s:4:"勤";s:1:"N";s:4:"勺";s:1:"N";s:4:"包";s:1:"N";s:4:"匆";s:1:"N";s:4:"北";s:1:"N";s:4:"卉";s:1:"N";s:4:"卑";s:1:"N";s:4:"博";s:1:"N";s:4:"即";s:1:"N";s:4:"卽";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"𠨬";s:1:"N";s:4:"灰";s:1:"N";s:4:"及";s:1:"N";s:4:"叟";s:1:"N";s:4:"𠭣";s:1:"N";s:4:"叫";s:1:"N";s:4:"叱";s:1:"N";s:4:"吆";s:1:"N";s:4:"咞";s:1:"N";s:4:"吸";s:1:"N";s:4:"呈";s:1:"N";s:4:"周";s:1:"N";s:4:"咢";s:1:"N";s:4:"哶";s:1:"N";s:4:"唐";s:1:"N";s:4:"啓";s:1:"N";s:4:"啣";s:1:"N";s:4:"善";s:1:"N";s:4:"善";s:1:"N";s:4:"喙";s:1:"N";s:4:"喫";s:1:"N";s:4:"喳";s:1:"N";s:4:"嗂";s:1:"N";s:4:"圖";s:1:"N";s:4:"嘆";s:1:"N";s:4:"圗";s:1:"N";s:4:"噑";s:1:"N";s:4:"噴";s:1:"N";s:4:"切";s:1:"N";s:4:"壮";s:1:"N";s:4:"城";s:1:"N";s:4:"埴";s:1:"N";s:4:"堍";s:1:"N";s:4:"型";s:1:"N";s:4:"堲";s:1:"N";s:4:"報";s:1:"N";s:4:"墬";s:1:"N";s:4:"𡓤";s:1:"N";s:4:"売";s:1:"N";s:4:"壷";s:1:"N";s:4:"夆";s:1:"N";s:4:"多";s:1:"N";s:4:"夢";s:1:"N";s:4:"奢";s:1:"N";s:4:"𡚨";s:1:"N";s:4:"𡛪";s:1:"N";s:4:"姬";s:1:"N";s:4:"娛";s:1:"N";s:4:"娧";s:1:"N";s:4:"姘";s:1:"N";s:4:"婦";s:1:"N";s:4:"㛮";s:1:"N";s:4:"㛼";s:1:"N";s:4:"嬈";s:1:"N";s:4:"嬾";s:1:"N";s:4:"嬾";s:1:"N";s:4:"𡧈";s:1:"N";s:4:"寃";s:1:"N";s:4:"寘";s:1:"N";s:4:"寧";s:1:"N";s:4:"寳";s:1:"N";s:4:"𡬘";s:1:"N";s:4:"寿";s:1:"N";s:4:"将";s:1:"N";s:4:"当";s:1:"N";s:4:"尢";s:1:"N";s:4:"㞁";s:1:"N";s:4:"屠";s:1:"N";s:4:"屮";s:1:"N";s:4:"峀";s:1:"N";s:4:"岍";s:1:"N";s:4:"𡷤";s:1:"N";s:4:"嵃";s:1:"N";s:4:"𡷦";s:1:"N";s:4:"嵮";s:1:"N";s:4:"嵫";s:1:"N";s:4:"嵼";s:1:"N";s:4:"巡";s:1:"N";s:4:"巢";s:1:"N";s:4:"㠯";s:1:"N";s:4:"巽";s:1:"N";s:4:"帨";s:1:"N";s:4:"帽";s:1:"N";s:4:"幩";s:1:"N";s:4:"㡢";s:1:"N";s:4:"𢆃";s:1:"N";s:4:"㡼";s:1:"N";s:4:"庰";s:1:"N";s:4:"庳";s:1:"N";s:4:"庶";s:1:"N";s:4:"廊";s:1:"N";s:4:"𪎒";s:1:"N";s:4:"廾";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"舁";s:1:"N";s:4:"弢";s:1:"N";s:4:"弢";s:1:"N";s:4:"㣇";s:1:"N";s:4:"𣊸";s:1:"N";s:4:"𦇚";s:1:"N";s:4:"形";s:1:"N";s:4:"彫";s:1:"N";s:4:"㣣";s:1:"N";s:4:"徚";s:1:"N";s:4:"忍";s:1:"N";s:4:"志";s:1:"N";s:4:"忹";s:1:"N";s:4:"悁";s:1:"N";s:4:"㤺";s:1:"N";s:4:"㤜";s:1:"N";s:4:"悔";s:1:"N";s:4:"𢛔";s:1:"N";s:4:"惇";s:1:"N";s:4:"慈";s:1:"N";s:4:"慌";s:1:"N";s:4:"慎";s:1:"N";s:4:"慌";s:1:"N";s:4:"慺";s:1:"N";s:4:"憎";s:1:"N";s:4:"憲";s:1:"N";s:4:"憤";s:1:"N";s:4:"憯";s:1:"N";s:4:"懞";s:1:"N";s:4:"懲";s:1:"N";s:4:"懶";s:1:"N";s:4:"成";s:1:"N";s:4:"戛";s:1:"N";s:4:"扝";s:1:"N";s:4:"抱";s:1:"N";s:4:"拔";s:1:"N";s:4:"捐";s:1:"N";s:4:"𢬌";s:1:"N";s:4:"挽";s:1:"N";s:4:"拼";s:1:"N";s:4:"捨";s:1:"N";s:4:"掃";s:1:"N";s:4:"揤";s:1:"N";s:4:"𢯱";s:1:"N";s:4:"搢";s:1:"N";s:4:"揅";s:1:"N";s:4:"掩";s:1:"N";s:4:"㨮";s:1:"N";s:4:"摩";s:1:"N";s:4:"摾";s:1:"N";s:4:"撝";s:1:"N";s:4:"摷";s:1:"N";s:4:"㩬";s:1:"N";s:4:"敏";s:1:"N";s:4:"敬";s:1:"N";s:4:"𣀊";s:1:"N";s:4:"旣";s:1:"N";s:4:"書";s:1:"N";s:4:"晉";s:1:"N";s:4:"㬙";s:1:"N";s:4:"暑";s:1:"N";s:4:"㬈";s:1:"N";s:4:"㫤";s:1:"N";s:4:"冒";s:1:"N";s:4:"冕";s:1:"N";s:4:"最";s:1:"N";s:4:"暜";s:1:"N";s:4:"肭";s:1:"N";s:4:"䏙";s:1:"N";s:4:"朗";s:1:"N";s:4:"望";s:1:"N";s:4:"朡";s:1:"N";s:4:"杞";s:1:"N";s:4:"杓";s:1:"N";s:4:"𣏃";s:1:"N";s:4:"㭉";s:1:"N";s:4:"柺";s:1:"N";s:4:"枅";s:1:"N";s:4:"桒";s:1:"N";s:4:"梅";s:1:"N";s:4:"𣑭";s:1:"N";s:4:"梎";s:1:"N";s:4:"栟";s:1:"N";s:4:"椔";s:1:"N";s:4:"㮝";s:1:"N";s:4:"楂";s:1:"N";s:4:"榣";s:1:"N";s:4:"槪";s:1:"N";s:4:"檨";s:1:"N";s:4:"𣚣";s:1:"N";s:4:"櫛";s:1:"N";s:4:"㰘";s:1:"N";s:4:"次";s:1:"N";s:4:"𣢧";s:1:"N";s:4:"歔";s:1:"N";s:4:"㱎";s:1:"N";s:4:"歲";s:1:"N";s:4:"殟";s:1:"N";s:4:"殺";s:1:"N";s:4:"殻";s:1:"N";s:4:"𣪍";s:1:"N";s:4:"𡴋";s:1:"N";s:4:"𣫺";s:1:"N";s:4:"汎";s:1:"N";s:4:"𣲼";s:1:"N";s:4:"沿";s:1:"N";s:4:"泍";s:1:"N";s:4:"汧";s:1:"N";s:4:"洖";s:1:"N";s:4:"派";s:1:"N";s:4:"海";s:1:"N";s:4:"流";s:1:"N";s:4:"浩";s:1:"N";s:4:"浸";s:1:"N";s:4:"涅";s:1:"N";s:4:"𣴞";s:1:"N";s:4:"洴";s:1:"N";s:4:"港";s:1:"N";s:4:"湮";s:1:"N";s:4:"㴳";s:1:"N";s:4:"滋";s:1:"N";s:4:"滇";s:1:"N";s:4:"𣻑";s:1:"N";s:4:"淹";s:1:"N";s:4:"潮";s:1:"N";s:4:"𣽞";s:1:"N";s:4:"𣾎";s:1:"N";s:4:"濆";s:1:"N";s:4:"瀹";s:1:"N";s:4:"瀞";s:1:"N";s:4:"瀛";s:1:"N";s:4:"㶖";s:1:"N";s:4:"灊";s:1:"N";s:4:"災";s:1:"N";s:4:"灷";s:1:"N";s:4:"炭";s:1:"N";s:4:"𠔥";s:1:"N";s:4:"煅";s:1:"N";s:4:"𤉣";s:1:"N";s:4:"熜";s:1:"N";s:4:"𤎫";s:1:"N";s:4:"爨";s:1:"N";s:4:"爵";s:1:"N";s:4:"牐";s:1:"N";s:4:"𤘈";s:1:"N";s:4:"犀";s:1:"N";s:4:"犕";s:1:"N";s:4:"𤜵";s:1:"N";s:4:"𤠔";s:1:"N";s:4:"獺";s:1:"N";s:4:"王";s:1:"N";s:4:"㺬";s:1:"N";s:4:"玥";s:1:"N";s:4:"㺸";s:1:"N";s:4:"㺸";s:1:"N";s:4:"瑇";s:1:"N";s:4:"瑜";s:1:"N";s:4:"瑱";s:1:"N";s:4:"璅";s:1:"N";s:4:"瓊";s:1:"N";s:4:"㼛";s:1:"N";s:4:"甤";s:1:"N";s:4:"𤰶";s:1:"N";s:4:"甾";s:1:"N";s:4:"𤲒";s:1:"N";s:4:"異";s:1:"N";s:4:"𢆟";s:1:"N";s:4:"瘐";s:1:"N";s:4:"𤾡";s:1:"N";s:4:"𤾸";s:1:"N";s:4:"𥁄";s:1:"N";s:4:"㿼";s:1:"N";s:4:"䀈";s:1:"N";s:4:"直";s:1:"N";s:4:"𥃳";s:1:"N";s:4:"𥃲";s:1:"N";s:4:"𥄙";s:1:"N";s:4:"𥄳";s:1:"N";s:4:"眞";s:1:"N";s:4:"真";s:1:"N";s:4:"真";s:1:"N";s:4:"睊";s:1:"N";s:4:"䀹";s:1:"N";s:4:"瞋";s:1:"N";s:4:"䁆";s:1:"N";s:4:"䂖";s:1:"N";s:4:"𥐝";s:1:"N";s:4:"硎";s:1:"N";s:4:"碌";s:1:"N";s:4:"磌";s:1:"N";s:4:"䃣";s:1:"N";s:4:"𥘦";s:1:"N";s:4:"祖";s:1:"N";s:4:"𥚚";s:1:"N";s:4:"𥛅";s:1:"N";s:4:"福";s:1:"N";s:4:"秫";s:1:"N";s:4:"䄯";s:1:"N";s:4:"穀";s:1:"N";s:4:"穊";s:1:"N";s:4:"穏";s:1:"N";s:4:"𥥼";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"竮";s:1:"N";s:4:"䈂";s:1:"N";s:4:"𥮫";s:1:"N";s:4:"篆";s:1:"N";s:4:"築";s:1:"N";s:4:"䈧";s:1:"N";s:4:"𥲀";s:1:"N";s:4:"糒";s:1:"N";s:4:"䊠";s:1:"N";s:4:"糨";s:1:"N";s:4:"糣";s:1:"N";s:4:"紀";s:1:"N";s:4:"𥾆";s:1:"N";s:4:"絣";s:1:"N";s:4:"䌁";s:1:"N";s:4:"緇";s:1:"N";s:4:"縂";s:1:"N";s:4:"繅";s:1:"N";s:4:"䌴";s:1:"N";s:4:"𦈨";s:1:"N";s:4:"𦉇";s:1:"N";s:4:"䍙";s:1:"N";s:4:"𦋙";s:1:"N";s:4:"罺";s:1:"N";s:4:"𦌾";s:1:"N";s:4:"羕";s:1:"N";s:4:"翺";s:1:"N";s:4:"者";s:1:"N";s:4:"𦓚";s:1:"N";s:4:"𦔣";s:1:"N";s:4:"聠";s:1:"N";s:4:"𦖨";s:1:"N";s:4:"聰";s:1:"N";s:4:"𣍟";s:1:"N";s:4:"䏕";s:1:"N";s:4:"育";s:1:"N";s:4:"脃";s:1:"N";s:4:"䐋";s:1:"N";s:4:"脾";s:1:"N";s:4:"媵";s:1:"N";s:4:"𦞧";s:1:"N";s:4:"𦞵";s:1:"N";s:4:"𣎓";s:1:"N";s:4:"𣎜";s:1:"N";s:4:"舁";s:1:"N";s:4:"舄";s:1:"N";s:4:"辞";s:1:"N";s:4:"䑫";s:1:"N";s:4:"芑";s:1:"N";s:4:"芋";s:1:"N";s:4:"芝";s:1:"N";s:4:"劳";s:1:"N";s:4:"花";s:1:"N";s:4:"芳";s:1:"N";s:4:"芽";s:1:"N";s:4:"苦";s:1:"N";s:4:"𦬼";s:1:"N";s:4:"若";s:1:"N";s:4:"茝";s:1:"N";s:4:"荣";s:1:"N";s:4:"莭";s:1:"N";s:4:"茣";s:1:"N";s:4:"莽";s:1:"N";s:4:"菧";s:1:"N";s:4:"著";s:1:"N";s:4:"荓";s:1:"N";s:4:"菊";s:1:"N";s:4:"菌";s:1:"N";s:4:"菜";s:1:"N";s:4:"𦰶";s:1:"N";s:4:"𦵫";s:1:"N";s:4:"𦳕";s:1:"N";s:4:"䔫";s:1:"N";s:4:"蓱";s:1:"N";s:4:"蓳";s:1:"N";s:4:"蔖";s:1:"N";s:4:"𧏊";s:1:"N";s:4:"蕤";s:1:"N";s:4:"𦼬";s:1:"N";s:4:"䕝";s:1:"N";s:4:"䕡";s:1:"N";s:4:"𦾱";s:1:"N";s:4:"𧃒";s:1:"N";s:4:"䕫";s:1:"N";s:4:"虐";s:1:"N";s:4:"虜";s:1:"N";s:4:"虧";s:1:"N";s:4:"虩";s:1:"N";s:4:"蚩";s:1:"N";s:4:"蚈";s:1:"N";s:4:"蜎";s:1:"N";s:4:"蛢";s:1:"N";s:4:"蝹";s:1:"N";s:4:"蜨";s:1:"N";s:4:"蝫";s:1:"N";s:4:"螆";s:1:"N";s:4:"䗗";s:1:"N";s:4:"蟡";s:1:"N";s:4:"蠁";s:1:"N";s:4:"䗹";s:1:"N";s:4:"衠";s:1:"N";s:4:"衣";s:1:"N";s:4:"𧙧";s:1:"N";s:4:"裗";s:1:"N";s:4:"裞";s:1:"N";s:4:"䘵";s:1:"N";s:4:"裺";s:1:"N";s:4:"㒻";s:1:"N";s:4:"𧢮";s:1:"N";s:4:"𧥦";s:1:"N";s:4:"䚾";s:1:"N";s:4:"䛇";s:1:"N";s:4:"誠";s:1:"N";s:4:"諭";s:1:"N";s:4:"變";s:1:"N";s:4:"豕";s:1:"N";s:4:"𧲨";s:1:"N";s:4:"貫";s:1:"N";s:4:"賁";s:1:"N";s:4:"贛";s:1:"N";s:4:"起";s:1:"N";s:4:"𧼯";s:1:"N";s:4:"𠠄";s:1:"N";s:4:"跋";s:1:"N";s:4:"趼";s:1:"N";s:4:"跰";s:1:"N";s:4:"𠣞";s:1:"N";s:4:"軔";s:1:"N";s:4:"輸";s:1:"N";s:4:"𨗒";s:1:"N";s:4:"𨗭";s:1:"N";s:4:"邔";s:1:"N";s:4:"郱";s:1:"N";s:4:"鄑";s:1:"N";s:4:"𨜮";s:1:"N";s:4:"鄛";s:1:"N";s:4:"鈸";s:1:"N";s:4:"鋗";s:1:"N";s:4:"鋘";s:1:"N";s:4:"鉼";s:1:"N";s:4:"鏹";s:1:"N";s:4:"鐕";s:1:"N";s:4:"𨯺";s:1:"N";s:4:"開";s:1:"N";s:4:"䦕";s:1:"N";s:4:"閷";s:1:"N";s:4:"𨵷";s:1:"N";s:4:"䧦";s:1:"N";s:4:"雃";s:1:"N";s:4:"嶲";s:1:"N";s:4:"霣";s:1:"N";s:4:"𩅅";s:1:"N";s:4:"𩈚";s:1:"N";s:4:"䩮";s:1:"N";s:4:"䩶";s:1:"N";s:4:"韠";s:1:"N";s:4:"𩐊";s:1:"N";s:4:"䪲";s:1:"N";s:4:"𩒖";s:1:"N";s:4:"頋";s:1:"N";s:4:"頋";s:1:"N";s:4:"頩";s:1:"N";s:4:"𩖶";s:1:"N";s:4:"飢";s:1:"N";s:4:"䬳";s:1:"N";s:4:"餩";s:1:"N";s:4:"馧";s:1:"N";s:4:"駂";s:1:"N";s:4:"駾";s:1:"N";s:4:"䯎";s:1:"N";s:4:"𩬰";s:1:"N";s:4:"鬒";s:1:"N";s:4:"鱀";s:1:"N";s:4:"鳽";s:1:"N";s:4:"䳎";s:1:"N";s:4:"䳭";s:1:"N";s:4:"鵧";s:1:"N";s:4:"𪃎";s:1:"N";s:4:"䳸";s:1:"N";s:4:"𪄅";s:1:"N";s:4:"𪈎";s:1:"N";s:4:"𪊑";s:1:"N";s:4:"麻";s:1:"N";s:4:"䵖";s:1:"N";s:4:"黹";s:1:"N";s:4:"黾";s:1:"N";s:4:"鼅";s:1:"N";s:4:"鼏";s:1:"N";s:4:"鼖";s:1:"N";s:4:"鼻";s:1:"N";s:4:"𪘀";s:1:"N";s:2:"̀";s:1:"M";s:2:"́";s:1:"M";s:2:"̂";s:1:"M";s:2:"̃";s:1:"M";s:2:"̄";s:1:"M";s:2:"̆";s:1:"M";s:2:"̇";s:1:"M";s:2:"̈";s:1:"M";s:2:"̉";s:1:"M";s:2:"̊";s:1:"M";s:2:"̋";s:1:"M";s:2:"̌";s:1:"M";s:2:"̏";s:1:"M";s:2:"̑";s:1:"M";s:2:"̓";s:1:"M";s:2:"̔";s:1:"M";s:2:"̛";s:1:"M";s:2:"̣";s:1:"M";s:2:"̤";s:1:"M";s:2:"̥";s:1:"M";s:2:"̦";s:1:"M";s:2:"̧";s:1:"M";s:2:"̨";s:1:"M";s:2:"̭";s:1:"M";s:2:"̮";s:1:"M";s:2:"̰";s:1:"M";s:2:"̱";s:1:"M";s:2:"̸";s:1:"M";s:2:"͂";s:1:"M";s:2:"ͅ";s:1:"M";s:2:"ٓ";s:1:"M";s:2:"ٔ";s:1:"M";s:2:"ٕ";s:1:"M";s:3:"़";s:1:"M";s:3:"া";s:1:"M";s:3:"ৗ";s:1:"M";s:3:"ା";s:1:"M";s:3:"ୖ";s:1:"M";s:3:"ୗ";s:1:"M";s:3:"ா";s:1:"M";s:3:"ௗ";s:1:"M";s:3:"ౖ";s:1:"M";s:3:"ೂ";s:1:"M";s:3:"ೕ";s:1:"M";s:3:"ೖ";s:1:"M";s:3:"ാ";s:1:"M";s:3:"ൗ";s:1:"M";s:3:"්";s:1:"M";s:3:"ා";s:1:"M";s:3:"ෟ";s:1:"M";s:3:"ီ";s:1:"M";s:3:"ᅡ";s:1:"M";s:3:"ᅢ";s:1:"M";s:3:"ᅣ";s:1:"M";s:3:"ᅤ";s:1:"M";s:3:"ᅥ";s:1:"M";s:3:"ᅦ";s:1:"M";s:3:"ᅧ";s:1:"M";s:3:"ᅨ";s:1:"M";s:3:"ᅩ";s:1:"M";s:3:"ᅪ";s:1:"M";s:3:"ᅫ";s:1:"M";s:3:"ᅬ";s:1:"M";s:3:"ᅭ";s:1:"M";s:3:"ᅮ";s:1:"M";s:3:"ᅯ";s:1:"M";s:3:"ᅰ";s:1:"M";s:3:"ᅱ";s:1:"M";s:3:"ᅲ";s:1:"M";s:3:"ᅳ";s:1:"M";s:3:"ᅴ";s:1:"M";s:3:"ᅵ";s:1:"M";s:3:"ᆨ";s:1:"M";s:3:"ᆩ";s:1:"M";s:3:"ᆪ";s:1:"M";s:3:"ᆫ";s:1:"M";s:3:"ᆬ";s:1:"M";s:3:"ᆭ";s:1:"M";s:3:"ᆮ";s:1:"M";s:3:"ᆯ";s:1:"M";s:3:"ᆰ";s:1:"M";s:3:"ᆱ";s:1:"M";s:3:"ᆲ";s:1:"M";s:3:"ᆳ";s:1:"M";s:3:"ᆴ";s:1:"M";s:3:"ᆵ";s:1:"M";s:3:"ᆶ";s:1:"M";s:3:"ᆷ";s:1:"M";s:3:"ᆸ";s:1:"M";s:3:"ᆹ";s:1:"M";s:3:"ᆺ";s:1:"M";s:3:"ᆻ";s:1:"M";s:3:"ᆼ";s:1:"M";s:3:"ᆽ";s:1:"M";s:3:"ᆾ";s:1:"M";s:3:"ᆿ";s:1:"M";s:3:"ᇀ";s:1:"M";s:3:"ᇁ";s:1:"M";s:3:"ᇂ";s:1:"M";s:3:"ᬵ";s:1:"M";s:3:"゙";s:1:"M";s:3:"゚";s:1:"M";}' ); ?> diff --git a/includes/normal/UtfNormalDataK.inc b/includes/normal/UtfNormalDataK.inc index 0f4cd7a5..5f112e02 100644 --- a/includes/normal/UtfNormalDataK.inc +++ b/includes/normal/UtfNormalDataK.inc @@ -2,9 +2,8 @@ /** * This file was automatically generated -- do not edit! * Run UtfNormalGenerate.php to create this file again (make clean && make) - * @package MediaWiki */ /** */ global $utfCompatibilityDecomp; -$utfCompatibilityDecomp = unserialize( 'a:5389:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" ̄";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" ́";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1⁄4";s:2:"½";s:5:"1⁄2";s:2:"¾";s:5:"3⁄4";s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"ʼn";s:3:"ʼn";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"ſ";s:1:"s";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"DŽ";s:4:"DŽ";s:2:"Dž";s:4:"Dž";s:2:"dž";s:4:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"ʴ";s:2:"ɹ";s:2:"ʵ";s:2:"ɻ";s:2:"ʶ";s:2:"ʁ";s:2:"ʷ";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"˙";s:3:" ̇";s:2:"˚";s:3:" ̊";s:2:"˛";s:3:" ̨";s:2:"˜";s:3:" ̃";s:2:"˝";s:3:" ̋";s:2:"ˠ";s:2:"ɣ";s:2:"ˡ";s:1:"l";s:2:"ˢ";s:1:"s";s:2:"ˣ";s:1:"x";s:2:"ˤ";s:2:"ʕ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:"ͺ";s:3:" ͅ";s:2:";";s:1:";";s:2:"΄";s:3:" ́";s:2:"΅";s:5:" ̈́";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϓ";s:4:"Ύ";s:2:"ϔ";s:4:"Ϋ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"և";s:4:"եւ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ჼ";s:3:"ნ";s:3:"ᴬ";s:1:"A";s:3:"ᴭ";s:2:"Æ";s:3:"ᴮ";s:1:"B";s:3:"ᴰ";s:1:"D";s:3:"ᴱ";s:1:"E";s:3:"ᴲ";s:2:"Ǝ";s:3:"ᴳ";s:1:"G";s:3:"ᴴ";s:1:"H";s:3:"ᴵ";s:1:"I";s:3:"ᴶ";s:1:"J";s:3:"ᴷ";s:1:"K";s:3:"ᴸ";s:1:"L";s:3:"ᴹ";s:1:"M";s:3:"ᴺ";s:1:"N";s:3:"ᴼ";s:1:"O";s:3:"ᴽ";s:2:"Ȣ";s:3:"ᴾ";s:1:"P";s:3:"ᴿ";s:1:"R";s:3:"ᵀ";s:1:"T";s:3:"ᵁ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"ɐ";s:3:"ᵅ";s:2:"ɑ";s:3:"ᵆ";s:3:"ᴂ";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"ə";s:3:"ᵋ";s:2:"ɛ";s:3:"ᵌ";s:2:"ɜ";s:3:"ᵍ";s:1:"g";s:3:"ᵏ";s:1:"k";s:3:"ᵐ";s:1:"m";s:3:"ᵑ";s:2:"ŋ";s:3:"ᵒ";s:1:"o";s:3:"ᵓ";s:2:"ɔ";s:3:"ᵔ";s:3:"ᴖ";s:3:"ᵕ";s:3:"ᴗ";s:3:"ᵖ";s:1:"p";s:3:"ᵗ";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"ᵙ";s:3:"ᴝ";s:3:"ᵚ";s:2:"ɯ";s:3:"ᵛ";s:1:"v";s:3:"ᵜ";s:3:"ᴥ";s:3:"ᵝ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"ᵠ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"ᵢ";s:1:"i";s:3:"ᵣ";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"ᵥ";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"ᵧ";s:2:"γ";s:3:"ᵨ";s:2:"ρ";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"ᶛ";s:2:"ɒ";s:3:"ᶜ";s:1:"c";s:3:"ᶝ";s:2:"ɕ";s:3:"ᶞ";s:2:"ð";s:3:"ᶟ";s:2:"ɜ";s:3:"ᶠ";s:1:"f";s:3:"ᶡ";s:2:"ɟ";s:3:"ᶢ";s:2:"ɡ";s:3:"ᶣ";s:2:"ɥ";s:3:"ᶤ";s:2:"ɨ";s:3:"ᶥ";s:2:"ɩ";s:3:"ᶦ";s:2:"ɪ";s:3:"ᶧ";s:3:"ᵻ";s:3:"ᶨ";s:2:"ʝ";s:3:"ᶩ";s:2:"ɭ";s:3:"ᶪ";s:3:"ᶅ";s:3:"ᶫ";s:2:"ʟ";s:3:"ᶬ";s:2:"ɱ";s:3:"ᶭ";s:2:"ɰ";s:3:"ᶮ";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"ᶰ";s:2:"ɴ";s:3:"ᶱ";s:2:"ɵ";s:3:"ᶲ";s:2:"ɸ";s:3:"ᶳ";s:2:"ʂ";s:3:"ᶴ";s:2:"ʃ";s:3:"ᶵ";s:2:"ƫ";s:3:"ᶶ";s:2:"ʉ";s:3:"ᶷ";s:2:"ʊ";s:3:"ᶸ";s:3:"ᴜ";s:3:"ᶹ";s:2:"ʋ";s:3:"ᶺ";s:2:"ʌ";s:3:"ᶻ";s:1:"z";s:3:"ᶼ";s:2:"ʐ";s:3:"ᶽ";s:2:"ʑ";s:3:"ᶾ";s:2:"ʒ";s:3:"ᶿ";s:2:"θ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"᾽";s:3:" ̓";s:3:"ι";s:2:"ι";s:3:"᾿";s:3:" ̓";s:3:"῀";s:3:" ͂";s:3:"῁";s:5:" ̈͂";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:" ̓̀";s:3:"῎";s:5:" ̓́";s:3:"῏";s:5:" ̓͂";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:" ̔̀";s:3:"῞";s:5:" ̔́";s:3:"῟";s:5:" ̔͂";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:5:" ̈̀";s:3:"΅";s:5:" ̈́";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:3:" ́";s:3:"῾";s:3:" ̔";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"‐";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" ̅";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:" ";s:1:" ";s:3:"⁰";s:1:"0";s:3:"ⁱ";s:1:"i";s:3:"⁴";s:1:"4";s:3:"⁵";s:1:"5";s:3:"⁶";s:1:"6";s:3:"⁷";s:1:"7";s:3:"⁸";s:1:"8";s:3:"⁹";s:1:"9";s:3:"⁺";s:1:"+";s:3:"⁻";s:3:"−";s:3:"⁼";s:1:"=";s:3:"⁽";s:1:"(";s:3:"⁾";s:1:")";s:3:"ⁿ";s:1:"n";s:3:"₀";s:1:"0";s:3:"₁";s:1:"1";s:3:"₂";s:1:"2";s:3:"₃";s:1:"3";s:3:"₄";s:1:"4";s:3:"₅";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"₋";s:3:"−";s:3:"₌";s:1:"=";s:3:"₍";s:1:"(";s:3:"₎";s:1:")";s:3:"ₐ";s:1:"a";s:3:"ₑ";s:1:"e";s:3:"ₒ";s:1:"o";s:3:"ₓ";s:1:"x";s:3:"ₔ";s:2:"ə";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℠";s:2:"SM";s:3:"℡";s:3:"TEL";s:3:"™";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"Ω";s:2:"Ω";s:3:"ℨ";s:1:"Z";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅓";s:5:"1⁄3";s:3:"⅔";s:5:"2⁄3";s:3:"⅕";s:5:"1⁄5";s:3:"⅖";s:5:"2⁄5";s:3:"⅗";s:5:"3⁄5";s:3:"⅘";s:5:"4⁄5";s:3:"⅙";s:5:"1⁄6";s:3:"⅚";s:5:"5⁄6";s:3:"⅛";s:5:"1⁄8";s:3:"⅜";s:5:"3⁄8";s:3:"⅝";s:5:"5⁄8";s:3:"⅞";s:5:"7⁄8";s:3:"⅟";s:4:"1⁄";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"①";s:1:"1";s:3:"②";s:1:"2";s:3:"③";s:1:"3";s:3:"④";s:1:"4";s:3:"⑤";s:1:"5";s:3:"⑥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"⑧";s:1:"8";s:3:"⑨";s:1:"9";s:3:"⑩";s:2:"10";s:3:"⑪";s:2:"11";s:3:"⑫";s:2:"12";s:3:"⑬";s:2:"13";s:3:"⑭";s:2:"14";s:3:"⑮";s:2:"15";s:3:"⑯";s:2:"16";s:3:"⑰";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:1:"A";s:3:"Ⓑ";s:1:"B";s:3:"Ⓒ";s:1:"C";s:3:"Ⓓ";s:1:"D";s:3:"Ⓔ";s:1:"E";s:3:"Ⓕ";s:1:"F";s:3:"Ⓖ";s:1:"G";s:3:"Ⓗ";s:1:"H";s:3:"Ⓘ";s:1:"I";s:3:"Ⓙ";s:1:"J";s:3:"Ⓚ";s:1:"K";s:3:"Ⓛ";s:1:"L";s:3:"Ⓜ";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"Ⓞ";s:1:"O";s:3:"Ⓟ";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"Ⓥ";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"Ⓧ";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"Ⓩ";s:1:"Z";s:3:"ⓐ";s:1:"a";s:3:"ⓑ";s:1:"b";s:3:"ⓒ";s:1:"c";s:3:"ⓓ";s:1:"d";s:3:"ⓔ";s:1:"e";s:3:"ⓕ";s:1:"f";s:3:"ⓖ";s:1:"g";s:3:"ⓗ";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"ⓙ";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"ⓛ";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"ⓝ";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"ⓠ";s:1:"q";s:3:"ⓡ";s:1:"r";s:3:"ⓢ";s:1:"s";s:3:"ⓣ";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"ⓥ";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"ⓧ";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"ⓩ";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"⫝̸";s:5:"⫝̸";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"゛";s:4:" ゙";s:3:"゜";s:4:" ゚";s:3:"ゞ";s:6:"ゞ";s:3:"ゟ";s:6:"より";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"四";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"乙";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"丁";s:3:"㆝";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"㉝";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"ᄀ";s:3:"㉡";s:3:"ᄂ";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"ᄅ";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"ᄋ";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"ᄏ";s:3:"㉫";s:3:"ᄐ";s:3:"㉬";s:3:"ᄑ";s:3:"㉭";s:3:"ᄒ";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"나";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"라";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"아";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"카";s:3:"㉹";s:6:"타";s:3:"㉺";s:6:"파";s:3:"㉻";s:6:"하";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"우";s:3:"㊀";s:3:"一";s:3:"㊁";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"四";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"六";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"八";s:3:"㊈";s:3:"九";s:3:"㊉";s:3:"十";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"火";s:3:"㊌";s:3:"水";s:3:"㊍";s:3:"木";s:3:"㊎";s:3:"金";s:3:"㊏";s:3:"土";s:3:"㊐";s:3:"日";s:3:"㊑";s:3:"株";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"名";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"祝";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"男";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"適";s:3:"㊝";s:3:"優";s:3:"㊞";s:3:"印";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"項";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"正";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"左";s:3:"㊨";s:3:"右";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"宗";s:3:"㊫";s:3:"学";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"企";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"協";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:3:"ア";s:3:"㋑";s:3:"イ";s:3:"㋒";s:3:"ウ";s:3:"㋓";s:3:"エ";s:3:"㋔";s:3:"オ";s:3:"㋕";s:3:"カ";s:3:"㋖";s:3:"キ";s:3:"㋗";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"㋙";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"㋛";s:3:"シ";s:3:"㋜";s:3:"ス";s:3:"㋝";s:3:"セ";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"タ";s:3:"㋠";s:3:"チ";s:3:"㋡";s:3:"ツ";s:3:"㋢";s:3:"テ";s:3:"㋣";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"㋥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"㋧";s:3:"ネ";s:3:"㋨";s:3:"ノ";s:3:"㋩";s:3:"ハ";s:3:"㋪";s:3:"ヒ";s:3:"㋫";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"㋭";s:3:"ホ";s:3:"㋮";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"㋰";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"㋴";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"㋶";s:3:"ラ";s:3:"㋷";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"㋻";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"㌏";s:12:"ガンマ";s:3:"㌐";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:18:"パーセント";s:3:"㌬";s:12:"パーツ";s:3:"㌭";s:15:"バーレル";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:15:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:18:"ミリバール";s:3:"㍋";s:9:"メガ";s:3:"㍌";s:15:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:12:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:12:"ルピー";s:3:"㍔";s:15:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:18:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:3:"dm2";s:3:"㍹";s:3:"dm3";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:3:"ﭏ";s:4:"אל";s:3:"ﭐ";s:2:"ٱ";s:3:"ﭑ";s:2:"ٱ";s:3:"ﭒ";s:2:"ٻ";s:3:"ﭓ";s:2:"ٻ";s:3:"ﭔ";s:2:"ٻ";s:3:"ﭕ";s:2:"ٻ";s:3:"ﭖ";s:2:"پ";s:3:"ﭗ";s:2:"پ";s:3:"ﭘ";s:2:"پ";s:3:"ﭙ";s:2:"پ";s:3:"ﭚ";s:2:"ڀ";s:3:"ﭛ";s:2:"ڀ";s:3:"ﭜ";s:2:"ڀ";s:3:"ﭝ";s:2:"ڀ";s:3:"ﭞ";s:2:"ٺ";s:3:"ﭟ";s:2:"ٺ";s:3:"ﭠ";s:2:"ٺ";s:3:"ﭡ";s:2:"ٺ";s:3:"ﭢ";s:2:"ٿ";s:3:"ﭣ";s:2:"ٿ";s:3:"ﭤ";s:2:"ٿ";s:3:"ﭥ";s:2:"ٿ";s:3:"ﭦ";s:2:"ٹ";s:3:"ﭧ";s:2:"ٹ";s:3:"ﭨ";s:2:"ٹ";s:3:"ﭩ";s:2:"ٹ";s:3:"ﭪ";s:2:"ڤ";s:3:"ﭫ";s:2:"ڤ";s:3:"ﭬ";s:2:"ڤ";s:3:"ﭭ";s:2:"ڤ";s:3:"ﭮ";s:2:"ڦ";s:3:"ﭯ";s:2:"ڦ";s:3:"ﭰ";s:2:"ڦ";s:3:"ﭱ";s:2:"ڦ";s:3:"ﭲ";s:2:"ڄ";s:3:"ﭳ";s:2:"ڄ";s:3:"ﭴ";s:2:"ڄ";s:3:"ﭵ";s:2:"ڄ";s:3:"ﭶ";s:2:"ڃ";s:3:"ﭷ";s:2:"ڃ";s:3:"ﭸ";s:2:"ڃ";s:3:"ﭹ";s:2:"ڃ";s:3:"ﭺ";s:2:"چ";s:3:"ﭻ";s:2:"چ";s:3:"ﭼ";s:2:"چ";s:3:"ﭽ";s:2:"چ";s:3:"ﭾ";s:2:"ڇ";s:3:"ﭿ";s:2:"ڇ";s:3:"ﮀ";s:2:"ڇ";s:3:"ﮁ";s:2:"ڇ";s:3:"ﮂ";s:2:"ڍ";s:3:"ﮃ";s:2:"ڍ";s:3:"ﮄ";s:2:"ڌ";s:3:"ﮅ";s:2:"ڌ";s:3:"ﮆ";s:2:"ڎ";s:3:"ﮇ";s:2:"ڎ";s:3:"ﮈ";s:2:"ڈ";s:3:"ﮉ";s:2:"ڈ";s:3:"ﮊ";s:2:"ژ";s:3:"ﮋ";s:2:"ژ";s:3:"ﮌ";s:2:"ڑ";s:3:"ﮍ";s:2:"ڑ";s:3:"ﮎ";s:2:"ک";s:3:"ﮏ";s:2:"ک";s:3:"ﮐ";s:2:"ک";s:3:"ﮑ";s:2:"ک";s:3:"ﮒ";s:2:"گ";s:3:"ﮓ";s:2:"گ";s:3:"ﮔ";s:2:"گ";s:3:"ﮕ";s:2:"گ";s:3:"ﮖ";s:2:"ڳ";s:3:"ﮗ";s:2:"ڳ";s:3:"ﮘ";s:2:"ڳ";s:3:"ﮙ";s:2:"ڳ";s:3:"ﮚ";s:2:"ڱ";s:3:"ﮛ";s:2:"ڱ";s:3:"ﮜ";s:2:"ڱ";s:3:"ﮝ";s:2:"ڱ";s:3:"ﮞ";s:2:"ں";s:3:"ﮟ";s:2:"ں";s:3:"ﮠ";s:2:"ڻ";s:3:"ﮡ";s:2:"ڻ";s:3:"ﮢ";s:2:"ڻ";s:3:"ﮣ";s:2:"ڻ";s:3:"ﮤ";s:4:"ۀ";s:3:"ﮥ";s:4:"ۀ";s:3:"ﮦ";s:2:"ہ";s:3:"ﮧ";s:2:"ہ";s:3:"ﮨ";s:2:"ہ";s:3:"ﮩ";s:2:"ہ";s:3:"ﮪ";s:2:"ھ";s:3:"ﮫ";s:2:"ھ";s:3:"ﮬ";s:2:"ھ";s:3:"ﮭ";s:2:"ھ";s:3:"ﮮ";s:2:"ے";s:3:"ﮯ";s:2:"ے";s:3:"ﮰ";s:4:"ۓ";s:3:"ﮱ";s:4:"ۓ";s:3:"ﯓ";s:2:"ڭ";s:3:"ﯔ";s:2:"ڭ";s:3:"ﯕ";s:2:"ڭ";s:3:"ﯖ";s:2:"ڭ";s:3:"ﯗ";s:2:"ۇ";s:3:"ﯘ";s:2:"ۇ";s:3:"ﯙ";s:2:"ۆ";s:3:"ﯚ";s:2:"ۆ";s:3:"ﯛ";s:2:"ۈ";s:3:"ﯜ";s:2:"ۈ";s:3:"ﯝ";s:4:"ۇٴ";s:3:"ﯞ";s:2:"ۋ";s:3:"ﯟ";s:2:"ۋ";s:3:"ﯠ";s:2:"ۅ";s:3:"ﯡ";s:2:"ۅ";s:3:"ﯢ";s:2:"ۉ";s:3:"ﯣ";s:2:"ۉ";s:3:"ﯤ";s:2:"ې";s:3:"ﯥ";s:2:"ې";s:3:"ﯦ";s:2:"ې";s:3:"ﯧ";s:2:"ې";s:3:"ﯨ";s:2:"ى";s:3:"ﯩ";s:2:"ى";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ئە";s:3:"ﯭ";s:6:"ئە";s:3:"ﯮ";s:6:"ئو";s:3:"ﯯ";s:6:"ئو";s:3:"ﯰ";s:6:"ئۇ";s:3:"ﯱ";s:6:"ئۇ";s:3:"ﯲ";s:6:"ئۆ";s:3:"ﯳ";s:6:"ئۆ";s:3:"ﯴ";s:6:"ئۈ";s:3:"ﯵ";s:6:"ئۈ";s:3:"ﯶ";s:6:"ئې";s:3:"ﯷ";s:6:"ئې";s:3:"ﯸ";s:6:"ئې";s:3:"ﯹ";s:6:"ئى";s:3:"ﯺ";s:6:"ئى";s:3:"ﯻ";s:6:"ئى";s:3:"ﯼ";s:2:"ی";s:3:"ﯽ";s:2:"ی";s:3:"ﯾ";s:2:"ی";s:3:"ﯿ";s:2:"ی";s:3:"ﰀ";s:6:"ئج";s:3:"ﰁ";s:6:"ئح";s:3:"ﰂ";s:6:"ئم";s:3:"ﰃ";s:6:"ئى";s:3:"ﰄ";s:6:"ئي";s:3:"ﰅ";s:4:"بج";s:3:"ﰆ";s:4:"بح";s:3:"ﰇ";s:4:"بخ";s:3:"ﰈ";s:4:"بم";s:3:"ﰉ";s:4:"بى";s:3:"ﰊ";s:4:"بي";s:3:"ﰋ";s:4:"تج";s:3:"ﰌ";s:4:"تح";s:3:"ﰍ";s:4:"تخ";s:3:"ﰎ";s:4:"تم";s:3:"ﰏ";s:4:"تى";s:3:"ﰐ";s:4:"تي";s:3:"ﰑ";s:4:"ثج";s:3:"ﰒ";s:4:"ثم";s:3:"ﰓ";s:4:"ثى";s:3:"ﰔ";s:4:"ثي";s:3:"ﰕ";s:4:"جح";s:3:"ﰖ";s:4:"جم";s:3:"ﰗ";s:4:"حج";s:3:"ﰘ";s:4:"حم";s:3:"ﰙ";s:4:"خج";s:3:"ﰚ";s:4:"خح";s:3:"ﰛ";s:4:"خم";s:3:"ﰜ";s:4:"سج";s:3:"ﰝ";s:4:"سح";s:3:"ﰞ";s:4:"سخ";s:3:"ﰟ";s:4:"سم";s:3:"ﰠ";s:4:"صح";s:3:"ﰡ";s:4:"صم";s:3:"ﰢ";s:4:"ضج";s:3:"ﰣ";s:4:"ضح";s:3:"ﰤ";s:4:"ضخ";s:3:"ﰥ";s:4:"ضم";s:3:"ﰦ";s:4:"طح";s:3:"ﰧ";s:4:"طم";s:3:"ﰨ";s:4:"ظم";s:3:"ﰩ";s:4:"عج";s:3:"ﰪ";s:4:"عم";s:3:"ﰫ";s:4:"غج";s:3:"ﰬ";s:4:"غم";s:3:"ﰭ";s:4:"فج";s:3:"ﰮ";s:4:"فح";s:3:"ﰯ";s:4:"فخ";s:3:"ﰰ";s:4:"فم";s:3:"ﰱ";s:4:"فى";s:3:"ﰲ";s:4:"في";s:3:"ﰳ";s:4:"قح";s:3:"ﰴ";s:4:"قم";s:3:"ﰵ";s:4:"قى";s:3:"ﰶ";s:4:"قي";s:3:"ﰷ";s:4:"كا";s:3:"ﰸ";s:4:"كج";s:3:"ﰹ";s:4:"كح";s:3:"ﰺ";s:4:"كخ";s:3:"ﰻ";s:4:"كل";s:3:"ﰼ";s:4:"كم";s:3:"ﰽ";s:4:"كى";s:3:"ﰾ";s:4:"كي";s:3:"ﰿ";s:4:"لج";s:3:"ﱀ";s:4:"لح";s:3:"ﱁ";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ﱅ";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ﱍ";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ﱏ";s:4:"نى";s:3:"ﱐ";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ﱒ";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ﱔ";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ﱖ";s:4:"يح";s:3:"ﱗ";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ﱙ";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ﱛ";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ﱝ";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ٍّ";s:3:"ﱠ";s:5:" َّ";s:3:"ﱡ";s:5:" ُّ";s:3:"ﱢ";s:5:" ِّ";s:3:"ﱣ";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ﱥ";s:6:"ئز";s:3:"ﱦ";s:6:"ئم";s:3:"ﱧ";s:6:"ئن";s:3:"ﱨ";s:6:"ئى";s:3:"ﱩ";s:6:"ئي";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ﱭ";s:4:"بن";s:3:"ﱮ";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ﱰ";s:4:"تر";s:3:"ﱱ";s:4:"تز";s:3:"ﱲ";s:4:"تم";s:3:"ﱳ";s:4:"تن";s:3:"ﱴ";s:4:"تى";s:3:"ﱵ";s:4:"تي";s:3:"ﱶ";s:4:"ثر";s:3:"ﱷ";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ﱹ";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ﱻ";s:4:"ثي";s:3:"ﱼ";s:4:"فى";s:3:"ﱽ";s:4:"في";s:3:"ﱾ";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ﲀ";s:4:"كا";s:3:"ﲁ";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ﲅ";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ﲍ";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ﲏ";s:4:"ني";s:3:"ﲐ";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ﲒ";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ﲔ";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ﲖ";s:4:"يي";s:3:"ﲗ";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ﲙ";s:6:"ئخ";s:3:"ﲚ";s:6:"ئم";s:3:"ﲛ";s:6:"ئه";s:3:"ﲜ";s:4:"بج";s:3:"ﲝ";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ﲠ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ﲢ";s:4:"تح";s:3:"ﲣ";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ﲥ";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ﲧ";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ﲭ";s:4:"سج";s:3:"ﲮ";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ﲰ";s:4:"سم";s:3:"ﲱ";s:4:"صح";s:3:"ﲲ";s:4:"صخ";s:3:"ﲳ";s:4:"صم";s:3:"ﲴ";s:4:"ضج";s:3:"ﲵ";s:4:"ضح";s:3:"ﲶ";s:4:"ضخ";s:3:"ﲷ";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ﲹ";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ﲻ";s:4:"عم";s:3:"ﲼ";s:4:"غج";s:3:"ﲽ";s:4:"غم";s:3:"ﲾ";s:4:"فج";s:3:"ﲿ";s:4:"فح";s:3:"ﳀ";s:4:"فخ";s:3:"ﳁ";s:4:"فم";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ﳅ";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ﳍ";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ﳏ";s:4:"مح";s:3:"ﳐ";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ﳒ";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ﳔ";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ﳖ";s:4:"نه";s:3:"ﳗ";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ﳙ";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ﳛ";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ﳝ";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ئم";s:3:"ﳠ";s:6:"ئه";s:3:"ﳡ";s:4:"بم";s:3:"ﳢ";s:4:"به";s:3:"ﳣ";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ﳥ";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ﳧ";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ﳭ";s:4:"لم";s:3:"ﳮ";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ﳰ";s:4:"يم";s:3:"ﳱ";s:4:"يه";s:3:"ﳲ";s:6:"ـَّ";s:3:"ﳳ";s:6:"ـُّ";s:3:"ﳴ";s:6:"ـِّ";s:3:"ﳵ";s:4:"طى";s:3:"ﳶ";s:4:"طي";s:3:"ﳷ";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ﳹ";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ﳻ";s:4:"سى";s:3:"ﳼ";s:4:"سي";s:3:"ﳽ";s:4:"شى";s:3:"ﳾ";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ﴀ";s:4:"حي";s:3:"ﴁ";s:4:"جى";s:3:"ﴂ";s:4:"جي";s:3:"ﴃ";s:4:"خى";s:3:"ﴄ";s:4:"خي";s:3:"ﴅ";s:4:"صى";s:3:"ﴆ";s:4:"صي";s:3:"ﴇ";s:4:"ضى";s:3:"ﴈ";s:4:"ضي";s:3:"ﴉ";s:4:"شج";s:3:"ﴊ";s:4:"شح";s:3:"ﴋ";s:4:"شخ";s:3:"ﴌ";s:4:"شم";s:3:"ﴍ";s:4:"شر";s:3:"ﴎ";s:4:"سر";s:3:"ﴏ";s:4:"صر";s:3:"ﴐ";s:4:"ضر";s:3:"ﴑ";s:4:"طى";s:3:"ﴒ";s:4:"طي";s:3:"ﴓ";s:4:"عى";s:3:"ﴔ";s:4:"عي";s:3:"ﴕ";s:4:"غى";s:3:"ﴖ";s:4:"غي";s:3:"ﴗ";s:4:"سى";s:3:"ﴘ";s:4:"سي";s:3:"ﴙ";s:4:"شى";s:3:"ﴚ";s:4:"شي";s:3:"ﴛ";s:4:"حى";s:3:"ﴜ";s:4:"حي";s:3:"ﴝ";s:4:"جى";s:3:"ﴞ";s:4:"جي";s:3:"ﴟ";s:4:"خى";s:3:"ﴠ";s:4:"خي";s:3:"ﴡ";s:4:"صى";s:3:"ﴢ";s:4:"صي";s:3:"ﴣ";s:4:"ضى";s:3:"ﴤ";s:4:"ضي";s:3:"ﴥ";s:4:"شج";s:3:"ﴦ";s:4:"شح";s:3:"ﴧ";s:4:"شخ";s:3:"ﴨ";s:4:"شم";s:3:"ﴩ";s:4:"شر";s:3:"ﴪ";s:4:"سر";s:3:"ﴫ";s:4:"صر";s:3:"ﴬ";s:4:"ضر";s:3:"ﴭ";s:4:"شج";s:3:"ﴮ";s:4:"شح";s:3:"ﴯ";s:4:"شخ";s:3:"ﴰ";s:4:"شم";s:3:"ﴱ";s:4:"سه";s:3:"ﴲ";s:4:"شه";s:3:"ﴳ";s:4:"طم";s:3:"ﴴ";s:4:"سج";s:3:"ﴵ";s:4:"سح";s:3:"ﴶ";s:4:"سخ";s:3:"ﴷ";s:4:"شج";s:3:"ﴸ";s:4:"شح";s:3:"ﴹ";s:4:"شخ";s:3:"ﴺ";s:4:"طم";s:3:"ﴻ";s:4:"ظم";s:3:"ﴼ";s:4:"اً";s:3:"ﴽ";s:4:"اً";s:3:"ﵐ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ﵒ";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ﵔ";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ﵖ";s:6:"تمح";s:3:"ﵗ";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ﵙ";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ﵛ";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ﵝ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ﵠ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ﵢ";s:6:"سمم";s:3:"ﵣ";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ﵥ";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ﵧ";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ﵭ";s:6:"شمم";s:3:"ﵮ";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ﵰ";s:6:"ضخم";s:3:"ﵱ";s:6:"طمح";s:3:"ﵲ";s:6:"طمح";s:3:"ﵳ";s:6:"طمم";s:3:"ﵴ";s:6:"طمي";s:3:"ﵵ";s:6:"عجم";s:3:"ﵶ";s:6:"عمم";s:3:"ﵷ";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ﵹ";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ﵻ";s:6:"غمى";s:3:"ﵼ";s:6:"فخم";s:3:"ﵽ";s:6:"فخم";s:3:"ﵾ";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ﶀ";s:6:"لحم";s:3:"ﶁ";s:6:"لحي";s:3:"ﶂ";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ﶄ";s:6:"لجج";s:3:"ﶅ";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ﶊ";s:6:"محم";s:3:"ﶋ";s:6:"محي";s:3:"ﶌ";s:6:"مجح";s:3:"ﶍ";s:6:"مجم";s:3:"ﶎ";s:6:"مخج";s:3:"ﶏ";s:6:"مخم";s:3:"ﶒ";s:6:"مجخ";s:3:"ﶓ";s:6:"همج";s:3:"ﶔ";s:6:"همم";s:3:"ﶕ";s:6:"نحم";s:3:"ﶖ";s:6:"نحى";s:3:"ﶗ";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ﶙ";s:6:"نجى";s:3:"ﶚ";s:6:"نمي";s:3:"ﶛ";s:6:"نمى";s:3:"ﶜ";s:6:"يمم";s:3:"ﶝ";s:6:"يمم";s:3:"ﶞ";s:6:"بخي";s:3:"ﶟ";s:6:"تجي";s:3:"ﶠ";s:6:"تجى";s:3:"ﶡ";s:6:"تخي";s:3:"ﶢ";s:6:"تخى";s:3:"ﶣ";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ﶥ";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ﶧ";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ﶩ";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ﶫ";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ﶭ";s:6:"لمي";s:3:"ﶮ";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ﶰ";s:6:"يمي";s:3:"ﶱ";s:6:"ممي";s:3:"ﶲ";s:6:"قمي";s:3:"ﶳ";s:6:"نحي";s:3:"ﶴ";s:6:"قمح";s:3:"ﶵ";s:6:"لحم";s:3:"ﶶ";s:6:"عمي";s:3:"ﶷ";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ﶹ";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ﶻ";s:6:"كمم";s:3:"ﶼ";s:6:"لجم";s:3:"ﶽ";s:6:"نجح";s:3:"ﶾ";s:6:"جحي";s:3:"ﶿ";s:6:"حجي";s:3:"ﷀ";s:6:"مجي";s:3:"ﷁ";s:6:"فمي";s:3:"ﷂ";s:6:"بحي";s:3:"ﷃ";s:6:"كمم";s:3:"ﷄ";s:6:"عجم";s:3:"ﷅ";s:6:"صمم";s:3:"ﷆ";s:6:"سخي";s:3:"ﷇ";s:6:"نجي";s:3:"ﷰ";s:6:"صلے";s:3:"ﷱ";s:6:"قلے";s:3:"ﷲ";s:8:"الله";s:3:"ﷳ";s:8:"اكبر";s:3:"ﷴ";s:8:"محمد";s:3:"ﷵ";s:8:"صلعم";s:3:"ﷶ";s:8:"رسول";s:3:"ﷷ";s:8:"عليه";s:3:"ﷸ";s:8:"وسلم";s:3:"ﷹ";s:6:"صلى";s:3:"ﷺ";s:33:"صلى الله عليه وسلم";s:3:"ﷻ";s:15:"جل جلاله";s:3:"﷼";s:8:"ریال";s:3:"︐";s:1:",";s:3:"︑";s:3:"、";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"【";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"﹀";s:3:"〉";s:3:"﹁";s:3:"「";s:3:"﹂";s:3:"」";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"』";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" ̅";s:3:"﹊";s:3:" ̅";s:3:"﹋";s:3:" ̅";s:3:"﹌";s:3:" ̅";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ﹰ";s:3:" ً";s:3:"ﹱ";s:4:"ـً";s:3:"ﹲ";s:3:" ٌ";s:3:"ﹴ";s:3:" ٍ";s:3:"ﹶ";s:3:" َ";s:3:"ﹷ";s:4:"ـَ";s:3:"ﹸ";s:3:" ُ";s:3:"ﹹ";s:4:"ـُ";s:3:"ﹺ";s:3:" ِ";s:3:"ﹻ";s:4:"ـِ";s:3:"ﹼ";s:3:" ّ";s:3:"ﹽ";s:4:"ـّ";s:3:"ﹾ";s:3:" ْ";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"ء";s:3:"ﺁ";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ؤ";s:3:"ﺆ";s:4:"ؤ";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ئ";s:3:"ﺊ";s:4:"ئ";s:3:"ﺋ";s:4:"ئ";s:3:"ﺌ";s:4:"ئ";s:3:"ﺍ";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ﺏ";s:2:"ب";s:3:"ﺐ";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"ة";s:3:"ﺔ";s:2:"ة";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"ث";s:3:"ﺚ";s:2:"ث";s:3:"ﺛ";s:2:"ث";s:3:"ﺜ";s:2:"ث";s:3:"ﺝ";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"ح";s:3:"ﺢ";s:2:"ح";s:3:"ﺣ";s:2:"ح";s:3:"ﺤ";s:2:"ح";s:3:"ﺥ";s:2:"خ";s:3:"ﺦ";s:2:"خ";s:3:"ﺧ";s:2:"خ";s:3:"ﺨ";s:2:"خ";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"ش";s:3:"ﺶ";s:2:"ش";s:3:"ﺷ";s:2:"ش";s:3:"ﺸ";s:2:"ش";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ﻁ";s:2:"ط";s:3:"ﻂ";s:2:"ط";s:3:"ﻃ";s:2:"ط";s:3:"ﻄ";s:2:"ط";s:3:"ﻅ";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ﻍ";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ﻏ";s:2:"غ";s:3:"ﻐ";s:2:"غ";s:3:"ﻑ";s:2:"ف";s:3:"ﻒ";s:2:"ف";s:3:"ﻓ";s:2:"ف";s:3:"ﻔ";s:2:"ف";s:3:"ﻕ";s:2:"ق";s:3:"ﻖ";s:2:"ق";s:3:"ﻗ";s:2:"ق";s:3:"ﻘ";s:2:"ق";s:3:"ﻙ";s:2:"ك";s:3:"ﻚ";s:2:"ك";s:3:"ﻛ";s:2:"ك";s:3:"ﻜ";s:2:"ك";s:3:"ﻝ";s:2:"ل";s:3:"ﻞ";s:2:"ل";s:3:"ﻟ";s:2:"ل";s:3:"ﻠ";s:2:"ل";s:3:"ﻡ";s:2:"م";s:3:"ﻢ";s:2:"م";s:3:"ﻣ";s:2:"م";s:3:"ﻤ";s:2:"م";s:3:"ﻥ";s:2:"ن";s:3:"ﻦ";s:2:"ن";s:3:"ﻧ";s:2:"ن";s:3:"ﻨ";s:2:"ن";s:3:"ﻩ";s:2:"ه";s:3:"ﻪ";s:2:"ه";s:3:"ﻫ";s:2:"ه";s:3:"ﻬ";s:2:"ه";s:3:"ﻭ";s:2:"و";s:3:"ﻮ";s:2:"و";s:3:"ﻯ";s:2:"ى";s:3:"ﻰ";s:2:"ى";s:3:"ﻱ";s:2:"ي";s:3:"ﻲ";s:2:"ي";s:3:"ﻳ";s:2:"ي";s:3:"ﻴ";s:2:"ي";s:3:"ﻵ";s:6:"لآ";s:3:"ﻶ";s:6:"لآ";s:3:"ﻷ";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ﻻ";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"\'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ᅠ";s:3:"ᄀ";s:3:"ᄀ";s:3:"ᄁ";s:3:"ᄁ";s:3:"ᆪ";s:3:"ᆪ";s:3:"ᄂ";s:3:"ᄂ";s:3:"ᆬ";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ᄃ";s:3:"ᄃ";s:3:"ᄄ";s:3:"ᄄ";s:3:"ᄅ";s:3:"ᄅ";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ᆳ";s:3:"ᆳ";s:3:"ᆴ";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ᄚ";s:3:"ᄚ";s:3:"ᄆ";s:3:"ᄆ";s:3:"ᄇ";s:3:"ᄇ";s:3:"ᄈ";s:3:"ᄈ";s:3:"ᄡ";s:3:"ᄡ";s:3:"ᄉ";s:3:"ᄉ";s:3:"ᄊ";s:3:"ᄊ";s:3:"ᄋ";s:3:"ᄋ";s:3:"ᄌ";s:3:"ᄌ";s:3:"ᄍ";s:3:"ᄍ";s:3:"ᄎ";s:3:"ᄎ";s:3:"ᄏ";s:3:"ᄏ";s:3:"ᄐ";s:3:"ᄐ";s:3:"ᄑ";s:3:"ᄑ";s:3:"ᄒ";s:3:"ᄒ";s:3:"ᅡ";s:3:"ᅡ";s:3:"ᅢ";s:3:"ᅢ";s:3:"ᅣ";s:3:"ᅣ";s:3:"ᅤ";s:3:"ᅤ";s:3:"ᅥ";s:3:"ᅥ";s:3:"ᅦ";s:3:"ᅦ";s:3:"ᅧ";s:3:"ᅧ";s:3:"ᅨ";s:3:"ᅨ";s:3:"ᅩ";s:3:"ᅩ";s:3:"ᅪ";s:3:"ᅪ";s:3:"ᅫ";s:3:"ᅫ";s:3:"ᅬ";s:3:"ᅬ";s:3:"ᅭ";s:3:"ᅭ";s:3:"ᅮ";s:3:"ᅮ";s:3:"ᅯ";s:3:"ᅯ";s:3:"ᅰ";s:3:"ᅰ";s:3:"ᅱ";s:3:"ᅱ";s:3:"ᅲ";s:3:"ᅲ";s:3:"ᅳ";s:3:"ᅳ";s:3:"ᅴ";s:3:"ᅴ";s:3:"ᅵ";s:3:"ᅵ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:3:" ̄";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"Θ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ε";s:4:"𝛝";s:2:"θ";s:4:"𝛞";s:2:"κ";s:4:"𝛟";s:2:"φ";s:4:"𝛠";s:2:"ρ";s:4:"𝛡";s:2:"π";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"Θ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ε";s:4:"𝜗";s:2:"θ";s:4:"𝜘";s:2:"κ";s:4:"𝜙";s:2:"φ";s:4:"𝜚";s:2:"ρ";s:4:"𝜛";s:2:"π";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"Θ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ε";s:4:"𝝑";s:2:"θ";s:4:"𝝒";s:2:"κ";s:4:"𝝓";s:2:"φ";s:4:"𝝔";s:2:"ρ";s:4:"𝝕";s:2:"π";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"Θ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ε";s:4:"𝞋";s:2:"θ";s:4:"𝞌";s:2:"κ";s:4:"𝞍";s:2:"φ";s:4:"𝞎";s:2:"ρ";s:4:"𝞏";s:2:"π";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"Θ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ε";s:4:"𝟅";s:2:"θ";s:4:"𝟆";s:2:"κ";s:4:"𝟇";s:2:"φ";s:4:"𝟈";s:2:"ρ";s:4:"𝟉";s:2:"π";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' ); +$utfCompatibilityDecomp = unserialize( 'a:5402:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" ̄";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" ́";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1⁄4";s:2:"½";s:5:"1⁄2";s:2:"¾";s:5:"3⁄4";s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"ʼn";s:3:"ʼn";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"ſ";s:1:"s";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"DŽ";s:4:"DŽ";s:2:"Dž";s:4:"Dž";s:2:"dž";s:4:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"ʴ";s:2:"ɹ";s:2:"ʵ";s:2:"ɻ";s:2:"ʶ";s:2:"ʁ";s:2:"ʷ";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"˙";s:3:" ̇";s:2:"˚";s:3:" ̊";s:2:"˛";s:3:" ̨";s:2:"˜";s:3:" ̃";s:2:"˝";s:3:" ̋";s:2:"ˠ";s:2:"ɣ";s:2:"ˡ";s:1:"l";s:2:"ˢ";s:1:"s";s:2:"ˣ";s:1:"x";s:2:"ˤ";s:2:"ʕ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:"ͺ";s:3:" ͅ";s:2:";";s:1:";";s:2:"΄";s:3:" ́";s:2:"΅";s:5:" ̈́";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϓ";s:4:"Ύ";s:2:"ϔ";s:4:"Ϋ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"և";s:4:"եւ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ჼ";s:3:"ნ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"ᴬ";s:1:"A";s:3:"ᴭ";s:2:"Æ";s:3:"ᴮ";s:1:"B";s:3:"ᴰ";s:1:"D";s:3:"ᴱ";s:1:"E";s:3:"ᴲ";s:2:"Ǝ";s:3:"ᴳ";s:1:"G";s:3:"ᴴ";s:1:"H";s:3:"ᴵ";s:1:"I";s:3:"ᴶ";s:1:"J";s:3:"ᴷ";s:1:"K";s:3:"ᴸ";s:1:"L";s:3:"ᴹ";s:1:"M";s:3:"ᴺ";s:1:"N";s:3:"ᴼ";s:1:"O";s:3:"ᴽ";s:2:"Ȣ";s:3:"ᴾ";s:1:"P";s:3:"ᴿ";s:1:"R";s:3:"ᵀ";s:1:"T";s:3:"ᵁ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"ɐ";s:3:"ᵅ";s:2:"ɑ";s:3:"ᵆ";s:3:"ᴂ";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"ə";s:3:"ᵋ";s:2:"ɛ";s:3:"ᵌ";s:2:"ɜ";s:3:"ᵍ";s:1:"g";s:3:"ᵏ";s:1:"k";s:3:"ᵐ";s:1:"m";s:3:"ᵑ";s:2:"ŋ";s:3:"ᵒ";s:1:"o";s:3:"ᵓ";s:2:"ɔ";s:3:"ᵔ";s:3:"ᴖ";s:3:"ᵕ";s:3:"ᴗ";s:3:"ᵖ";s:1:"p";s:3:"ᵗ";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"ᵙ";s:3:"ᴝ";s:3:"ᵚ";s:2:"ɯ";s:3:"ᵛ";s:1:"v";s:3:"ᵜ";s:3:"ᴥ";s:3:"ᵝ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"ᵠ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"ᵢ";s:1:"i";s:3:"ᵣ";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"ᵥ";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"ᵧ";s:2:"γ";s:3:"ᵨ";s:2:"ρ";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"ᶛ";s:2:"ɒ";s:3:"ᶜ";s:1:"c";s:3:"ᶝ";s:2:"ɕ";s:3:"ᶞ";s:2:"ð";s:3:"ᶟ";s:2:"ɜ";s:3:"ᶠ";s:1:"f";s:3:"ᶡ";s:2:"ɟ";s:3:"ᶢ";s:2:"ɡ";s:3:"ᶣ";s:2:"ɥ";s:3:"ᶤ";s:2:"ɨ";s:3:"ᶥ";s:2:"ɩ";s:3:"ᶦ";s:2:"ɪ";s:3:"ᶧ";s:3:"ᵻ";s:3:"ᶨ";s:2:"ʝ";s:3:"ᶩ";s:2:"ɭ";s:3:"ᶪ";s:3:"ᶅ";s:3:"ᶫ";s:2:"ʟ";s:3:"ᶬ";s:2:"ɱ";s:3:"ᶭ";s:2:"ɰ";s:3:"ᶮ";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"ᶰ";s:2:"ɴ";s:3:"ᶱ";s:2:"ɵ";s:3:"ᶲ";s:2:"ɸ";s:3:"ᶳ";s:2:"ʂ";s:3:"ᶴ";s:2:"ʃ";s:3:"ᶵ";s:2:"ƫ";s:3:"ᶶ";s:2:"ʉ";s:3:"ᶷ";s:2:"ʊ";s:3:"ᶸ";s:3:"ᴜ";s:3:"ᶹ";s:2:"ʋ";s:3:"ᶺ";s:2:"ʌ";s:3:"ᶻ";s:1:"z";s:3:"ᶼ";s:2:"ʐ";s:3:"ᶽ";s:2:"ʑ";s:3:"ᶾ";s:2:"ʒ";s:3:"ᶿ";s:2:"θ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"᾽";s:3:" ̓";s:3:"ι";s:2:"ι";s:3:"᾿";s:3:" ̓";s:3:"῀";s:3:" ͂";s:3:"῁";s:5:" ̈͂";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:" ̓̀";s:3:"῎";s:5:" ̓́";s:3:"῏";s:5:" ̓͂";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:" ̔̀";s:3:"῞";s:5:" ̔́";s:3:"῟";s:5:" ̔͂";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:5:" ̈̀";s:3:"΅";s:5:" ̈́";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:3:" ́";s:3:"῾";s:3:" ̔";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"‐";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" ̅";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:" ";s:1:" ";s:3:"⁰";s:1:"0";s:3:"ⁱ";s:1:"i";s:3:"⁴";s:1:"4";s:3:"⁵";s:1:"5";s:3:"⁶";s:1:"6";s:3:"⁷";s:1:"7";s:3:"⁸";s:1:"8";s:3:"⁹";s:1:"9";s:3:"⁺";s:1:"+";s:3:"⁻";s:3:"−";s:3:"⁼";s:1:"=";s:3:"⁽";s:1:"(";s:3:"⁾";s:1:")";s:3:"ⁿ";s:1:"n";s:3:"₀";s:1:"0";s:3:"₁";s:1:"1";s:3:"₂";s:1:"2";s:3:"₃";s:1:"3";s:3:"₄";s:1:"4";s:3:"₅";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"₋";s:3:"−";s:3:"₌";s:1:"=";s:3:"₍";s:1:"(";s:3:"₎";s:1:")";s:3:"ₐ";s:1:"a";s:3:"ₑ";s:1:"e";s:3:"ₒ";s:1:"o";s:3:"ₓ";s:1:"x";s:3:"ₔ";s:2:"ə";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℠";s:2:"SM";s:3:"℡";s:3:"TEL";s:3:"™";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"Ω";s:2:"Ω";s:3:"ℨ";s:1:"Z";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅓";s:5:"1⁄3";s:3:"⅔";s:5:"2⁄3";s:3:"⅕";s:5:"1⁄5";s:3:"⅖";s:5:"2⁄5";s:3:"⅗";s:5:"3⁄5";s:3:"⅘";s:5:"4⁄5";s:3:"⅙";s:5:"1⁄6";s:3:"⅚";s:5:"5⁄6";s:3:"⅛";s:5:"1⁄8";s:3:"⅜";s:5:"3⁄8";s:3:"⅝";s:5:"5⁄8";s:3:"⅞";s:5:"7⁄8";s:3:"⅟";s:4:"1⁄";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"①";s:1:"1";s:3:"②";s:1:"2";s:3:"③";s:1:"3";s:3:"④";s:1:"4";s:3:"⑤";s:1:"5";s:3:"⑥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"⑧";s:1:"8";s:3:"⑨";s:1:"9";s:3:"⑩";s:2:"10";s:3:"⑪";s:2:"11";s:3:"⑫";s:2:"12";s:3:"⑬";s:2:"13";s:3:"⑭";s:2:"14";s:3:"⑮";s:2:"15";s:3:"⑯";s:2:"16";s:3:"⑰";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:1:"A";s:3:"Ⓑ";s:1:"B";s:3:"Ⓒ";s:1:"C";s:3:"Ⓓ";s:1:"D";s:3:"Ⓔ";s:1:"E";s:3:"Ⓕ";s:1:"F";s:3:"Ⓖ";s:1:"G";s:3:"Ⓗ";s:1:"H";s:3:"Ⓘ";s:1:"I";s:3:"Ⓙ";s:1:"J";s:3:"Ⓚ";s:1:"K";s:3:"Ⓛ";s:1:"L";s:3:"Ⓜ";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"Ⓞ";s:1:"O";s:3:"Ⓟ";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"Ⓥ";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"Ⓧ";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"Ⓩ";s:1:"Z";s:3:"ⓐ";s:1:"a";s:3:"ⓑ";s:1:"b";s:3:"ⓒ";s:1:"c";s:3:"ⓓ";s:1:"d";s:3:"ⓔ";s:1:"e";s:3:"ⓕ";s:1:"f";s:3:"ⓖ";s:1:"g";s:3:"ⓗ";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"ⓙ";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"ⓛ";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"ⓝ";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"ⓠ";s:1:"q";s:3:"ⓡ";s:1:"r";s:3:"ⓢ";s:1:"s";s:3:"ⓣ";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"ⓥ";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"ⓧ";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"ⓩ";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"⫝̸";s:5:"⫝̸";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"゛";s:4:" ゙";s:3:"゜";s:4:" ゚";s:3:"ゞ";s:6:"ゞ";s:3:"ゟ";s:6:"より";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"四";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"乙";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"丁";s:3:"㆝";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"㉝";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"ᄀ";s:3:"㉡";s:3:"ᄂ";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"ᄅ";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"ᄋ";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"ᄏ";s:3:"㉫";s:3:"ᄐ";s:3:"㉬";s:3:"ᄑ";s:3:"㉭";s:3:"ᄒ";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"나";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"라";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"아";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"카";s:3:"㉹";s:6:"타";s:3:"㉺";s:6:"파";s:3:"㉻";s:6:"하";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"우";s:3:"㊀";s:3:"一";s:3:"㊁";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"四";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"六";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"八";s:3:"㊈";s:3:"九";s:3:"㊉";s:3:"十";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"火";s:3:"㊌";s:3:"水";s:3:"㊍";s:3:"木";s:3:"㊎";s:3:"金";s:3:"㊏";s:3:"土";s:3:"㊐";s:3:"日";s:3:"㊑";s:3:"株";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"名";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"祝";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"男";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"適";s:3:"㊝";s:3:"優";s:3:"㊞";s:3:"印";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"項";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"正";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"左";s:3:"㊨";s:3:"右";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"宗";s:3:"㊫";s:3:"学";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"企";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"協";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:3:"ア";s:3:"㋑";s:3:"イ";s:3:"㋒";s:3:"ウ";s:3:"㋓";s:3:"エ";s:3:"㋔";s:3:"オ";s:3:"㋕";s:3:"カ";s:3:"㋖";s:3:"キ";s:3:"㋗";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"㋙";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"㋛";s:3:"シ";s:3:"㋜";s:3:"ス";s:3:"㋝";s:3:"セ";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"タ";s:3:"㋠";s:3:"チ";s:3:"㋡";s:3:"ツ";s:3:"㋢";s:3:"テ";s:3:"㋣";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"㋥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"㋧";s:3:"ネ";s:3:"㋨";s:3:"ノ";s:3:"㋩";s:3:"ハ";s:3:"㋪";s:3:"ヒ";s:3:"㋫";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"㋭";s:3:"ホ";s:3:"㋮";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"㋰";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"㋴";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"㋶";s:3:"ラ";s:3:"㋷";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"㋻";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"㌏";s:12:"ガンマ";s:3:"㌐";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:18:"パーセント";s:3:"㌬";s:12:"パーツ";s:3:"㌭";s:15:"バーレル";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:15:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:18:"ミリバール";s:3:"㍋";s:9:"メガ";s:3:"㍌";s:15:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:12:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:12:"ルピー";s:3:"㍔";s:15:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:18:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:3:"dm2";s:3:"㍹";s:3:"dm3";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:3:"ﭏ";s:4:"אל";s:3:"ﭐ";s:2:"ٱ";s:3:"ﭑ";s:2:"ٱ";s:3:"ﭒ";s:2:"ٻ";s:3:"ﭓ";s:2:"ٻ";s:3:"ﭔ";s:2:"ٻ";s:3:"ﭕ";s:2:"ٻ";s:3:"ﭖ";s:2:"پ";s:3:"ﭗ";s:2:"پ";s:3:"ﭘ";s:2:"پ";s:3:"ﭙ";s:2:"پ";s:3:"ﭚ";s:2:"ڀ";s:3:"ﭛ";s:2:"ڀ";s:3:"ﭜ";s:2:"ڀ";s:3:"ﭝ";s:2:"ڀ";s:3:"ﭞ";s:2:"ٺ";s:3:"ﭟ";s:2:"ٺ";s:3:"ﭠ";s:2:"ٺ";s:3:"ﭡ";s:2:"ٺ";s:3:"ﭢ";s:2:"ٿ";s:3:"ﭣ";s:2:"ٿ";s:3:"ﭤ";s:2:"ٿ";s:3:"ﭥ";s:2:"ٿ";s:3:"ﭦ";s:2:"ٹ";s:3:"ﭧ";s:2:"ٹ";s:3:"ﭨ";s:2:"ٹ";s:3:"ﭩ";s:2:"ٹ";s:3:"ﭪ";s:2:"ڤ";s:3:"ﭫ";s:2:"ڤ";s:3:"ﭬ";s:2:"ڤ";s:3:"ﭭ";s:2:"ڤ";s:3:"ﭮ";s:2:"ڦ";s:3:"ﭯ";s:2:"ڦ";s:3:"ﭰ";s:2:"ڦ";s:3:"ﭱ";s:2:"ڦ";s:3:"ﭲ";s:2:"ڄ";s:3:"ﭳ";s:2:"ڄ";s:3:"ﭴ";s:2:"ڄ";s:3:"ﭵ";s:2:"ڄ";s:3:"ﭶ";s:2:"ڃ";s:3:"ﭷ";s:2:"ڃ";s:3:"ﭸ";s:2:"ڃ";s:3:"ﭹ";s:2:"ڃ";s:3:"ﭺ";s:2:"چ";s:3:"ﭻ";s:2:"چ";s:3:"ﭼ";s:2:"چ";s:3:"ﭽ";s:2:"چ";s:3:"ﭾ";s:2:"ڇ";s:3:"ﭿ";s:2:"ڇ";s:3:"ﮀ";s:2:"ڇ";s:3:"ﮁ";s:2:"ڇ";s:3:"ﮂ";s:2:"ڍ";s:3:"ﮃ";s:2:"ڍ";s:3:"ﮄ";s:2:"ڌ";s:3:"ﮅ";s:2:"ڌ";s:3:"ﮆ";s:2:"ڎ";s:3:"ﮇ";s:2:"ڎ";s:3:"ﮈ";s:2:"ڈ";s:3:"ﮉ";s:2:"ڈ";s:3:"ﮊ";s:2:"ژ";s:3:"ﮋ";s:2:"ژ";s:3:"ﮌ";s:2:"ڑ";s:3:"ﮍ";s:2:"ڑ";s:3:"ﮎ";s:2:"ک";s:3:"ﮏ";s:2:"ک";s:3:"ﮐ";s:2:"ک";s:3:"ﮑ";s:2:"ک";s:3:"ﮒ";s:2:"گ";s:3:"ﮓ";s:2:"گ";s:3:"ﮔ";s:2:"گ";s:3:"ﮕ";s:2:"گ";s:3:"ﮖ";s:2:"ڳ";s:3:"ﮗ";s:2:"ڳ";s:3:"ﮘ";s:2:"ڳ";s:3:"ﮙ";s:2:"ڳ";s:3:"ﮚ";s:2:"ڱ";s:3:"ﮛ";s:2:"ڱ";s:3:"ﮜ";s:2:"ڱ";s:3:"ﮝ";s:2:"ڱ";s:3:"ﮞ";s:2:"ں";s:3:"ﮟ";s:2:"ں";s:3:"ﮠ";s:2:"ڻ";s:3:"ﮡ";s:2:"ڻ";s:3:"ﮢ";s:2:"ڻ";s:3:"ﮣ";s:2:"ڻ";s:3:"ﮤ";s:4:"ۀ";s:3:"ﮥ";s:4:"ۀ";s:3:"ﮦ";s:2:"ہ";s:3:"ﮧ";s:2:"ہ";s:3:"ﮨ";s:2:"ہ";s:3:"ﮩ";s:2:"ہ";s:3:"ﮪ";s:2:"ھ";s:3:"ﮫ";s:2:"ھ";s:3:"ﮬ";s:2:"ھ";s:3:"ﮭ";s:2:"ھ";s:3:"ﮮ";s:2:"ے";s:3:"ﮯ";s:2:"ے";s:3:"ﮰ";s:4:"ۓ";s:3:"ﮱ";s:4:"ۓ";s:3:"ﯓ";s:2:"ڭ";s:3:"ﯔ";s:2:"ڭ";s:3:"ﯕ";s:2:"ڭ";s:3:"ﯖ";s:2:"ڭ";s:3:"ﯗ";s:2:"ۇ";s:3:"ﯘ";s:2:"ۇ";s:3:"ﯙ";s:2:"ۆ";s:3:"ﯚ";s:2:"ۆ";s:3:"ﯛ";s:2:"ۈ";s:3:"ﯜ";s:2:"ۈ";s:3:"ﯝ";s:4:"ۇٴ";s:3:"ﯞ";s:2:"ۋ";s:3:"ﯟ";s:2:"ۋ";s:3:"ﯠ";s:2:"ۅ";s:3:"ﯡ";s:2:"ۅ";s:3:"ﯢ";s:2:"ۉ";s:3:"ﯣ";s:2:"ۉ";s:3:"ﯤ";s:2:"ې";s:3:"ﯥ";s:2:"ې";s:3:"ﯦ";s:2:"ې";s:3:"ﯧ";s:2:"ې";s:3:"ﯨ";s:2:"ى";s:3:"ﯩ";s:2:"ى";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ئە";s:3:"ﯭ";s:6:"ئە";s:3:"ﯮ";s:6:"ئو";s:3:"ﯯ";s:6:"ئو";s:3:"ﯰ";s:6:"ئۇ";s:3:"ﯱ";s:6:"ئۇ";s:3:"ﯲ";s:6:"ئۆ";s:3:"ﯳ";s:6:"ئۆ";s:3:"ﯴ";s:6:"ئۈ";s:3:"ﯵ";s:6:"ئۈ";s:3:"ﯶ";s:6:"ئې";s:3:"ﯷ";s:6:"ئې";s:3:"ﯸ";s:6:"ئې";s:3:"ﯹ";s:6:"ئى";s:3:"ﯺ";s:6:"ئى";s:3:"ﯻ";s:6:"ئى";s:3:"ﯼ";s:2:"ی";s:3:"ﯽ";s:2:"ی";s:3:"ﯾ";s:2:"ی";s:3:"ﯿ";s:2:"ی";s:3:"ﰀ";s:6:"ئج";s:3:"ﰁ";s:6:"ئح";s:3:"ﰂ";s:6:"ئم";s:3:"ﰃ";s:6:"ئى";s:3:"ﰄ";s:6:"ئي";s:3:"ﰅ";s:4:"بج";s:3:"ﰆ";s:4:"بح";s:3:"ﰇ";s:4:"بخ";s:3:"ﰈ";s:4:"بم";s:3:"ﰉ";s:4:"بى";s:3:"ﰊ";s:4:"بي";s:3:"ﰋ";s:4:"تج";s:3:"ﰌ";s:4:"تح";s:3:"ﰍ";s:4:"تخ";s:3:"ﰎ";s:4:"تم";s:3:"ﰏ";s:4:"تى";s:3:"ﰐ";s:4:"تي";s:3:"ﰑ";s:4:"ثج";s:3:"ﰒ";s:4:"ثم";s:3:"ﰓ";s:4:"ثى";s:3:"ﰔ";s:4:"ثي";s:3:"ﰕ";s:4:"جح";s:3:"ﰖ";s:4:"جم";s:3:"ﰗ";s:4:"حج";s:3:"ﰘ";s:4:"حم";s:3:"ﰙ";s:4:"خج";s:3:"ﰚ";s:4:"خح";s:3:"ﰛ";s:4:"خم";s:3:"ﰜ";s:4:"سج";s:3:"ﰝ";s:4:"سح";s:3:"ﰞ";s:4:"سخ";s:3:"ﰟ";s:4:"سم";s:3:"ﰠ";s:4:"صح";s:3:"ﰡ";s:4:"صم";s:3:"ﰢ";s:4:"ضج";s:3:"ﰣ";s:4:"ضح";s:3:"ﰤ";s:4:"ضخ";s:3:"ﰥ";s:4:"ضم";s:3:"ﰦ";s:4:"طح";s:3:"ﰧ";s:4:"طم";s:3:"ﰨ";s:4:"ظم";s:3:"ﰩ";s:4:"عج";s:3:"ﰪ";s:4:"عم";s:3:"ﰫ";s:4:"غج";s:3:"ﰬ";s:4:"غم";s:3:"ﰭ";s:4:"فج";s:3:"ﰮ";s:4:"فح";s:3:"ﰯ";s:4:"فخ";s:3:"ﰰ";s:4:"فم";s:3:"ﰱ";s:4:"فى";s:3:"ﰲ";s:4:"في";s:3:"ﰳ";s:4:"قح";s:3:"ﰴ";s:4:"قم";s:3:"ﰵ";s:4:"قى";s:3:"ﰶ";s:4:"قي";s:3:"ﰷ";s:4:"كا";s:3:"ﰸ";s:4:"كج";s:3:"ﰹ";s:4:"كح";s:3:"ﰺ";s:4:"كخ";s:3:"ﰻ";s:4:"كل";s:3:"ﰼ";s:4:"كم";s:3:"ﰽ";s:4:"كى";s:3:"ﰾ";s:4:"كي";s:3:"ﰿ";s:4:"لج";s:3:"ﱀ";s:4:"لح";s:3:"ﱁ";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ﱅ";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ﱍ";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ﱏ";s:4:"نى";s:3:"ﱐ";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ﱒ";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ﱔ";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ﱖ";s:4:"يح";s:3:"ﱗ";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ﱙ";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ﱛ";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ﱝ";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ٍّ";s:3:"ﱠ";s:5:" َّ";s:3:"ﱡ";s:5:" ُّ";s:3:"ﱢ";s:5:" ِّ";s:3:"ﱣ";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ﱥ";s:6:"ئز";s:3:"ﱦ";s:6:"ئم";s:3:"ﱧ";s:6:"ئن";s:3:"ﱨ";s:6:"ئى";s:3:"ﱩ";s:6:"ئي";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ﱭ";s:4:"بن";s:3:"ﱮ";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ﱰ";s:4:"تر";s:3:"ﱱ";s:4:"تز";s:3:"ﱲ";s:4:"تم";s:3:"ﱳ";s:4:"تن";s:3:"ﱴ";s:4:"تى";s:3:"ﱵ";s:4:"تي";s:3:"ﱶ";s:4:"ثر";s:3:"ﱷ";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ﱹ";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ﱻ";s:4:"ثي";s:3:"ﱼ";s:4:"فى";s:3:"ﱽ";s:4:"في";s:3:"ﱾ";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ﲀ";s:4:"كا";s:3:"ﲁ";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ﲅ";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ﲍ";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ﲏ";s:4:"ني";s:3:"ﲐ";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ﲒ";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ﲔ";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ﲖ";s:4:"يي";s:3:"ﲗ";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ﲙ";s:6:"ئخ";s:3:"ﲚ";s:6:"ئم";s:3:"ﲛ";s:6:"ئه";s:3:"ﲜ";s:4:"بج";s:3:"ﲝ";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ﲠ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ﲢ";s:4:"تح";s:3:"ﲣ";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ﲥ";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ﲧ";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ﲭ";s:4:"سج";s:3:"ﲮ";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ﲰ";s:4:"سم";s:3:"ﲱ";s:4:"صح";s:3:"ﲲ";s:4:"صخ";s:3:"ﲳ";s:4:"صم";s:3:"ﲴ";s:4:"ضج";s:3:"ﲵ";s:4:"ضح";s:3:"ﲶ";s:4:"ضخ";s:3:"ﲷ";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ﲹ";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ﲻ";s:4:"عم";s:3:"ﲼ";s:4:"غج";s:3:"ﲽ";s:4:"غم";s:3:"ﲾ";s:4:"فج";s:3:"ﲿ";s:4:"فح";s:3:"ﳀ";s:4:"فخ";s:3:"ﳁ";s:4:"فم";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ﳅ";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ﳍ";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ﳏ";s:4:"مح";s:3:"ﳐ";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ﳒ";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ﳔ";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ﳖ";s:4:"نه";s:3:"ﳗ";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ﳙ";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ﳛ";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ﳝ";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ئم";s:3:"ﳠ";s:6:"ئه";s:3:"ﳡ";s:4:"بم";s:3:"ﳢ";s:4:"به";s:3:"ﳣ";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ﳥ";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ﳧ";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ﳭ";s:4:"لم";s:3:"ﳮ";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ﳰ";s:4:"يم";s:3:"ﳱ";s:4:"يه";s:3:"ﳲ";s:6:"ـَّ";s:3:"ﳳ";s:6:"ـُّ";s:3:"ﳴ";s:6:"ـِّ";s:3:"ﳵ";s:4:"طى";s:3:"ﳶ";s:4:"طي";s:3:"ﳷ";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ﳹ";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ﳻ";s:4:"سى";s:3:"ﳼ";s:4:"سي";s:3:"ﳽ";s:4:"شى";s:3:"ﳾ";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ﴀ";s:4:"حي";s:3:"ﴁ";s:4:"جى";s:3:"ﴂ";s:4:"جي";s:3:"ﴃ";s:4:"خى";s:3:"ﴄ";s:4:"خي";s:3:"ﴅ";s:4:"صى";s:3:"ﴆ";s:4:"صي";s:3:"ﴇ";s:4:"ضى";s:3:"ﴈ";s:4:"ضي";s:3:"ﴉ";s:4:"شج";s:3:"ﴊ";s:4:"شح";s:3:"ﴋ";s:4:"شخ";s:3:"ﴌ";s:4:"شم";s:3:"ﴍ";s:4:"شر";s:3:"ﴎ";s:4:"سر";s:3:"ﴏ";s:4:"صر";s:3:"ﴐ";s:4:"ضر";s:3:"ﴑ";s:4:"طى";s:3:"ﴒ";s:4:"طي";s:3:"ﴓ";s:4:"عى";s:3:"ﴔ";s:4:"عي";s:3:"ﴕ";s:4:"غى";s:3:"ﴖ";s:4:"غي";s:3:"ﴗ";s:4:"سى";s:3:"ﴘ";s:4:"سي";s:3:"ﴙ";s:4:"شى";s:3:"ﴚ";s:4:"شي";s:3:"ﴛ";s:4:"حى";s:3:"ﴜ";s:4:"حي";s:3:"ﴝ";s:4:"جى";s:3:"ﴞ";s:4:"جي";s:3:"ﴟ";s:4:"خى";s:3:"ﴠ";s:4:"خي";s:3:"ﴡ";s:4:"صى";s:3:"ﴢ";s:4:"صي";s:3:"ﴣ";s:4:"ضى";s:3:"ﴤ";s:4:"ضي";s:3:"ﴥ";s:4:"شج";s:3:"ﴦ";s:4:"شح";s:3:"ﴧ";s:4:"شخ";s:3:"ﴨ";s:4:"شم";s:3:"ﴩ";s:4:"شر";s:3:"ﴪ";s:4:"سر";s:3:"ﴫ";s:4:"صر";s:3:"ﴬ";s:4:"ضر";s:3:"ﴭ";s:4:"شج";s:3:"ﴮ";s:4:"شح";s:3:"ﴯ";s:4:"شخ";s:3:"ﴰ";s:4:"شم";s:3:"ﴱ";s:4:"سه";s:3:"ﴲ";s:4:"شه";s:3:"ﴳ";s:4:"طم";s:3:"ﴴ";s:4:"سج";s:3:"ﴵ";s:4:"سح";s:3:"ﴶ";s:4:"سخ";s:3:"ﴷ";s:4:"شج";s:3:"ﴸ";s:4:"شح";s:3:"ﴹ";s:4:"شخ";s:3:"ﴺ";s:4:"طم";s:3:"ﴻ";s:4:"ظم";s:3:"ﴼ";s:4:"اً";s:3:"ﴽ";s:4:"اً";s:3:"ﵐ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ﵒ";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ﵔ";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ﵖ";s:6:"تمح";s:3:"ﵗ";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ﵙ";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ﵛ";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ﵝ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ﵠ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ﵢ";s:6:"سمم";s:3:"ﵣ";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ﵥ";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ﵧ";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ﵭ";s:6:"شمم";s:3:"ﵮ";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ﵰ";s:6:"ضخم";s:3:"ﵱ";s:6:"طمح";s:3:"ﵲ";s:6:"طمح";s:3:"ﵳ";s:6:"طمم";s:3:"ﵴ";s:6:"طمي";s:3:"ﵵ";s:6:"عجم";s:3:"ﵶ";s:6:"عمم";s:3:"ﵷ";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ﵹ";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ﵻ";s:6:"غمى";s:3:"ﵼ";s:6:"فخم";s:3:"ﵽ";s:6:"فخم";s:3:"ﵾ";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ﶀ";s:6:"لحم";s:3:"ﶁ";s:6:"لحي";s:3:"ﶂ";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ﶄ";s:6:"لجج";s:3:"ﶅ";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ﶊ";s:6:"محم";s:3:"ﶋ";s:6:"محي";s:3:"ﶌ";s:6:"مجح";s:3:"ﶍ";s:6:"مجم";s:3:"ﶎ";s:6:"مخج";s:3:"ﶏ";s:6:"مخم";s:3:"ﶒ";s:6:"مجخ";s:3:"ﶓ";s:6:"همج";s:3:"ﶔ";s:6:"همم";s:3:"ﶕ";s:6:"نحم";s:3:"ﶖ";s:6:"نحى";s:3:"ﶗ";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ﶙ";s:6:"نجى";s:3:"ﶚ";s:6:"نمي";s:3:"ﶛ";s:6:"نمى";s:3:"ﶜ";s:6:"يمم";s:3:"ﶝ";s:6:"يمم";s:3:"ﶞ";s:6:"بخي";s:3:"ﶟ";s:6:"تجي";s:3:"ﶠ";s:6:"تجى";s:3:"ﶡ";s:6:"تخي";s:3:"ﶢ";s:6:"تخى";s:3:"ﶣ";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ﶥ";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ﶧ";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ﶩ";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ﶫ";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ﶭ";s:6:"لمي";s:3:"ﶮ";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ﶰ";s:6:"يمي";s:3:"ﶱ";s:6:"ممي";s:3:"ﶲ";s:6:"قمي";s:3:"ﶳ";s:6:"نحي";s:3:"ﶴ";s:6:"قمح";s:3:"ﶵ";s:6:"لحم";s:3:"ﶶ";s:6:"عمي";s:3:"ﶷ";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ﶹ";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ﶻ";s:6:"كمم";s:3:"ﶼ";s:6:"لجم";s:3:"ﶽ";s:6:"نجح";s:3:"ﶾ";s:6:"جحي";s:3:"ﶿ";s:6:"حجي";s:3:"ﷀ";s:6:"مجي";s:3:"ﷁ";s:6:"فمي";s:3:"ﷂ";s:6:"بحي";s:3:"ﷃ";s:6:"كمم";s:3:"ﷄ";s:6:"عجم";s:3:"ﷅ";s:6:"صمم";s:3:"ﷆ";s:6:"سخي";s:3:"ﷇ";s:6:"نجي";s:3:"ﷰ";s:6:"صلے";s:3:"ﷱ";s:6:"قلے";s:3:"ﷲ";s:8:"الله";s:3:"ﷳ";s:8:"اكبر";s:3:"ﷴ";s:8:"محمد";s:3:"ﷵ";s:8:"صلعم";s:3:"ﷶ";s:8:"رسول";s:3:"ﷷ";s:8:"عليه";s:3:"ﷸ";s:8:"وسلم";s:3:"ﷹ";s:6:"صلى";s:3:"ﷺ";s:33:"صلى الله عليه وسلم";s:3:"ﷻ";s:15:"جل جلاله";s:3:"﷼";s:8:"ریال";s:3:"︐";s:1:",";s:3:"︑";s:3:"、";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"【";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"﹀";s:3:"〉";s:3:"﹁";s:3:"「";s:3:"﹂";s:3:"」";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"』";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" ̅";s:3:"﹊";s:3:" ̅";s:3:"﹋";s:3:" ̅";s:3:"﹌";s:3:" ̅";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ﹰ";s:3:" ً";s:3:"ﹱ";s:4:"ـً";s:3:"ﹲ";s:3:" ٌ";s:3:"ﹴ";s:3:" ٍ";s:3:"ﹶ";s:3:" َ";s:3:"ﹷ";s:4:"ـَ";s:3:"ﹸ";s:3:" ُ";s:3:"ﹹ";s:4:"ـُ";s:3:"ﹺ";s:3:" ِ";s:3:"ﹻ";s:4:"ـِ";s:3:"ﹼ";s:3:" ّ";s:3:"ﹽ";s:4:"ـّ";s:3:"ﹾ";s:3:" ْ";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"ء";s:3:"ﺁ";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ؤ";s:3:"ﺆ";s:4:"ؤ";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ئ";s:3:"ﺊ";s:4:"ئ";s:3:"ﺋ";s:4:"ئ";s:3:"ﺌ";s:4:"ئ";s:3:"ﺍ";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ﺏ";s:2:"ب";s:3:"ﺐ";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"ة";s:3:"ﺔ";s:2:"ة";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"ث";s:3:"ﺚ";s:2:"ث";s:3:"ﺛ";s:2:"ث";s:3:"ﺜ";s:2:"ث";s:3:"ﺝ";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"ح";s:3:"ﺢ";s:2:"ح";s:3:"ﺣ";s:2:"ح";s:3:"ﺤ";s:2:"ح";s:3:"ﺥ";s:2:"خ";s:3:"ﺦ";s:2:"خ";s:3:"ﺧ";s:2:"خ";s:3:"ﺨ";s:2:"خ";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"ش";s:3:"ﺶ";s:2:"ش";s:3:"ﺷ";s:2:"ش";s:3:"ﺸ";s:2:"ش";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ﻁ";s:2:"ط";s:3:"ﻂ";s:2:"ط";s:3:"ﻃ";s:2:"ط";s:3:"ﻄ";s:2:"ط";s:3:"ﻅ";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ﻍ";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ﻏ";s:2:"غ";s:3:"ﻐ";s:2:"غ";s:3:"ﻑ";s:2:"ف";s:3:"ﻒ";s:2:"ف";s:3:"ﻓ";s:2:"ف";s:3:"ﻔ";s:2:"ف";s:3:"ﻕ";s:2:"ق";s:3:"ﻖ";s:2:"ق";s:3:"ﻗ";s:2:"ق";s:3:"ﻘ";s:2:"ق";s:3:"ﻙ";s:2:"ك";s:3:"ﻚ";s:2:"ك";s:3:"ﻛ";s:2:"ك";s:3:"ﻜ";s:2:"ك";s:3:"ﻝ";s:2:"ل";s:3:"ﻞ";s:2:"ل";s:3:"ﻟ";s:2:"ل";s:3:"ﻠ";s:2:"ل";s:3:"ﻡ";s:2:"م";s:3:"ﻢ";s:2:"م";s:3:"ﻣ";s:2:"م";s:3:"ﻤ";s:2:"م";s:3:"ﻥ";s:2:"ن";s:3:"ﻦ";s:2:"ن";s:3:"ﻧ";s:2:"ن";s:3:"ﻨ";s:2:"ن";s:3:"ﻩ";s:2:"ه";s:3:"ﻪ";s:2:"ه";s:3:"ﻫ";s:2:"ه";s:3:"ﻬ";s:2:"ه";s:3:"ﻭ";s:2:"و";s:3:"ﻮ";s:2:"و";s:3:"ﻯ";s:2:"ى";s:3:"ﻰ";s:2:"ى";s:3:"ﻱ";s:2:"ي";s:3:"ﻲ";s:2:"ي";s:3:"ﻳ";s:2:"ي";s:3:"ﻴ";s:2:"ي";s:3:"ﻵ";s:6:"لآ";s:3:"ﻶ";s:6:"لآ";s:3:"ﻷ";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ﻻ";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"\'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ᅠ";s:3:"ᄀ";s:3:"ᄀ";s:3:"ᄁ";s:3:"ᄁ";s:3:"ᆪ";s:3:"ᆪ";s:3:"ᄂ";s:3:"ᄂ";s:3:"ᆬ";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ᄃ";s:3:"ᄃ";s:3:"ᄄ";s:3:"ᄄ";s:3:"ᄅ";s:3:"ᄅ";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ᆳ";s:3:"ᆳ";s:3:"ᆴ";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ᄚ";s:3:"ᄚ";s:3:"ᄆ";s:3:"ᄆ";s:3:"ᄇ";s:3:"ᄇ";s:3:"ᄈ";s:3:"ᄈ";s:3:"ᄡ";s:3:"ᄡ";s:3:"ᄉ";s:3:"ᄉ";s:3:"ᄊ";s:3:"ᄊ";s:3:"ᄋ";s:3:"ᄋ";s:3:"ᄌ";s:3:"ᄌ";s:3:"ᄍ";s:3:"ᄍ";s:3:"ᄎ";s:3:"ᄎ";s:3:"ᄏ";s:3:"ᄏ";s:3:"ᄐ";s:3:"ᄐ";s:3:"ᄑ";s:3:"ᄑ";s:3:"ᄒ";s:3:"ᄒ";s:3:"ᅡ";s:3:"ᅡ";s:3:"ᅢ";s:3:"ᅢ";s:3:"ᅣ";s:3:"ᅣ";s:3:"ᅤ";s:3:"ᅤ";s:3:"ᅥ";s:3:"ᅥ";s:3:"ᅦ";s:3:"ᅦ";s:3:"ᅧ";s:3:"ᅧ";s:3:"ᅨ";s:3:"ᅨ";s:3:"ᅩ";s:3:"ᅩ";s:3:"ᅪ";s:3:"ᅪ";s:3:"ᅫ";s:3:"ᅫ";s:3:"ᅬ";s:3:"ᅬ";s:3:"ᅭ";s:3:"ᅭ";s:3:"ᅮ";s:3:"ᅮ";s:3:"ᅯ";s:3:"ᅯ";s:3:"ᅰ";s:3:"ᅰ";s:3:"ᅱ";s:3:"ᅱ";s:3:"ᅲ";s:3:"ᅲ";s:3:"ᅳ";s:3:"ᅳ";s:3:"ᅴ";s:3:"ᅴ";s:3:"ᅵ";s:3:"ᅵ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:3:" ̄";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"Θ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ε";s:4:"𝛝";s:2:"θ";s:4:"𝛞";s:2:"κ";s:4:"𝛟";s:2:"φ";s:4:"𝛠";s:2:"ρ";s:4:"𝛡";s:2:"π";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"Θ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ε";s:4:"𝜗";s:2:"θ";s:4:"𝜘";s:2:"κ";s:4:"𝜙";s:2:"φ";s:4:"𝜚";s:2:"ρ";s:4:"𝜛";s:2:"π";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"Θ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ε";s:4:"𝝑";s:2:"θ";s:4:"𝝒";s:2:"κ";s:4:"𝝓";s:2:"φ";s:4:"𝝔";s:2:"ρ";s:4:"𝝕";s:2:"π";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"Θ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ε";s:4:"𝞋";s:2:"θ";s:4:"𝞌";s:2:"κ";s:4:"𝞍";s:2:"φ";s:4:"𝞎";s:2:"ρ";s:4:"𝞏";s:2:"π";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"Θ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ε";s:4:"𝟅";s:2:"θ";s:4:"𝟆";s:2:"κ";s:4:"𝟇";s:2:"φ";s:4:"𝟈";s:2:"ρ";s:4:"𝟉";s:2:"π";s:4:"𝟊";s:2:"Ϝ";s:4:"𝟋";s:2:"ϝ";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' ); ?> diff --git a/includes/normal/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php index f0eb5330..30f18675 100644 --- a/includes/normal/UtfNormalGenerate.php +++ b/includes/normal/UtfNormalGenerate.php @@ -21,7 +21,7 @@ * This script generates UniNormalData.inc from the Unicode Character Database * and supplementary files. * - * @package UtfNormal + * @addtogroup UtfNormal * @access private */ @@ -175,7 +175,6 @@ if( $out ) { /** * This file was automatically generated -- do not edit! * Run UtfNormalGenerate.php to create this file again (make clean && make) - * @package MediaWiki */ /** */ global \$utfCombiningClass, \$utfCanonicalComp, \$utfCanonicalDecomp, \$utfCheckNFC; @@ -200,7 +199,6 @@ if( $out ) { /** * This file was automatically generated -- do not edit! * Run UtfNormalGenerate.php to create this file again (make clean && make) - * @package MediaWiki */ /** */ global \$utfCompatibilityDecomp; diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php index 1181b633..6d0dce25 100644 --- a/includes/normal/UtfNormalTest.php +++ b/includes/normal/UtfNormalTest.php @@ -20,7 +20,7 @@ /** * Implements the conformance test at: * http://www.unicode.org/Public/UNIDATA/NormalizationTest.txt - * @package UtfNormal + * @addtogroup UtfNormal */ /** */ diff --git a/includes/normal/UtfNormalUtil.php b/includes/normal/UtfNormalUtil.php index 94224e3d..4ba05693 100644 --- a/includes/normal/UtfNormalUtil.php +++ b/includes/normal/UtfNormalUtil.php @@ -21,7 +21,7 @@ * Some of these functions are adapted from places in MediaWiki. * Should probably merge them for consistency. * - * @package UtfNormal + * @addtogroup UtfNormal * @public */ diff --git a/includes/proxy_check.php b/includes/proxy_check.php index fb7fdb50..4c672760 100644 --- a/includes/proxy_check.php +++ b/includes/proxy_check.php @@ -1,7 +1,6 @@ <?php /** * Command line script to check for an open proxy at a specified location - * @package MediaWiki */ if( php_sapi_name() != 'cli' ) { diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php index 953fbd47..ccddfa66 100644 --- a/includes/templates/Userlogin.php +++ b/includes/templates/Userlogin.php @@ -1,7 +1,6 @@ <?php /** - * @package MediaWiki - * @subpackage Templates + * @addtogroup Templates */ if( !defined( 'MEDIAWIKI' ) ) die( -1 ); @@ -10,8 +9,7 @@ require_once( 'includes/SkinTemplate.php' ); /** * HTML template for Special:Userlogin form - * @package MediaWiki - * @subpackage Templates + * @addtogroup Templates */ class UserloginTemplate extends QuickTemplate { function execute() { @@ -94,6 +92,9 @@ class UserloginTemplate extends QuickTemplate { } } +/** + * @addtogroup Templates + */ class UsercreateTemplate extends QuickTemplate { function execute() { if( $this->data['message'] ) { diff --git a/includes/tidy.conf b/includes/tidy.conf new file mode 100644 index 00000000..3cefcf8f --- /dev/null +++ b/includes/tidy.conf @@ -0,0 +1,18 @@ +# html tidy (http://tidy.sf.net) configuration +# tidy - validate, correct, and pretty-print HTML files +# see: man 1 tidy, http://tidy.sourceforge.net/docs/quickref.html + +show-body-only: yes +force-output: yes +tidy-mark: no +wrap: 0 +wrap-attributes: no +literal-attributes: yes +output-xhtml: yes +numeric-entities: yes +enclose-text: yes +enclose-block-text: yes +quiet: yes +quote-nbsp: yes +fix-backslash: no +fix-uri: no |