X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FEditPage.php;h=d6d714d794250eb0a2965fbff0866cd0c185113f;hb=8e7ea374a79e19f97a09f09631a0775639d8b3ea;hp=1291a4c59ea68f2bdcadb4e0a412a4f9d30e0e51;hpb=a4c147a63ec1e2fd6f5e92020da08d310817ad25;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/EditPage.php b/includes/EditPage.php index 1291a4c59e..d6d714d794 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -17,14 +17,21 @@ class EditPage { var $mArticle; var $mTitle; var $mMetaData = ''; - + var $isConflict = false; + var $isCssJsSubpage = false; + var $deletedSinceEdit = false; + var $formtype; + var $firsttime; + var $lastDelete; + var $mTokenOk = true; + # Form values var $save = false, $preview = false, $diff = false; - var $minoredit = false, $watchthis = false; + var $minoredit = false, $watchthis = false, $recreate = false; var $textbox1 = '', $textbox2 = '', $summary = ''; - var $edittime = '', $section = ''; - var $oldid = 0; - + var $edittime = '', $section = '', $starttime = ''; + var $oldid = 0, $editintro = '', $scrolltop = null; + /** * @todo document * @param $article @@ -97,7 +104,7 @@ class EditPage { { $sat[] = strtolower ( $x ) ; } - + } # Templates, but only some @@ -130,125 +137,263 @@ class EditPage { $this->mMetaData = $s ; } + function submit() { + $this->edit(); + } + /** - * This is the function that gets called for "action=edit". + * This is the function that gets called for "action=edit". It + * sets up various member variables, then passes execution to + * another function, usually showEditForm() + * + * The edit form is self-submitting, so that when things like + * preview and edit conflicts occur, we get the same form back + * with the extra stuff added. Only when the final submission + * is made and all is well do we actually save and redirect to + * the newly-edited page. */ function edit() { - global $wgOut, $wgUser, $wgRequest; + global $wgOut, $wgUser, $wgRequest, $wgTitle; + + if ( ! wfRunHooks( 'AlternateEdit', array( &$this ) ) ) + return; + + $fname = 'EditPage::edit'; + wfProfileIn( $fname ); + wfDebug( "$fname: enter\n" ); + // this is not an article $wgOut->setArticleFlag(false); $this->importFormData( $wgRequest ); - + $this->firsttime = false; + if( $this->live ) { $this->livePreview(); + wfProfileOut( $fname ); return; } if ( ! $this->mTitle->userCanEdit() ) { + wfDebug( "$fname: user can't edit\n" ); $wgOut->readOnlyPage( $this->mArticle->getContent( true ), true ); + wfProfileOut( $fname ); return; } - if ( !$this->preview && !$this->diff && $wgUser->isBlocked( !$this->save ) ) { + wfDebug( "$fname: Checking blocks\n" ); + if ( !$this->preview && !$this->diff && $wgUser->isBlockedFrom( $this->mTitle, !$this->save ) ) { # When previewing, don't check blocked state - will get caught at save time. # Also, check when starting edition is done against slave to improve performance. + wfDebug( "$fname: user is blocked\n" ); $this->blockedIPpage(); + wfProfileOut( $fname ); return; } if ( !$wgUser->isAllowed('edit') ) { if ( $wgUser->isAnon() ) { + wfDebug( "$fname: user must log in\n" ); $this->userNotLoggedInPage(); + wfProfileOut( $fname ); return; } else { + wfDebug( "$fname: read-only page\n" ); $wgOut->readOnlyPage( $this->mArticle->getContent( true ), true ); + wfProfileOut( $fname ); return; } } + if ( !$this->mTitle->userCan( 'create' ) && !$this->mTitle->exists() ) { + wfDebug( "$fname: no create permission\n" ); + $this->noCreatePermission(); + wfProfileOut( $fname ); + return; + } if ( wfReadOnly() ) { + wfDebug( "$fname: read-only mode is engaged\n" ); if( $this->save || $this->preview ) { - $this->editForm( 'preview' ); + $this->formtype = 'preview'; } else if ( $this->diff ) { - $this->editForm( 'diff' ); + $this->formtype = 'diff'; } else { $wgOut->readOnlyPage( $this->mArticle->getContent( true ) ); + wfProfileOut( $fname ); + return; + } + } else { + if ( $this->save ) { + $this->formtype = 'save'; + } else if ( $this->preview ) { + $this->formtype = 'preview'; + } else if ( $this->diff ) { + $this->formtype = 'diff'; + } else { # First time through + $this->firsttime = true; + if( $this->previewOnOpen() ) { + $this->formtype = 'preview'; + } else { + $this->extractMetaDataFromArticle () ; + $this->formtype = 'initial'; + } } - return; } - if ( $this->save ) { - $this->editForm( 'save' ); - } else if ( $this->preview ) { - $this->editForm( 'preview' ); - } else if ( $this->diff ) { - $this->editForm( 'diff' ); - } else { # First time through - if( $wgUser->getOption('previewonfirst') - or $this->mTitle->getNamespace() == NS_CATEGORY ) { - $this->editForm( 'preview', true ); - } else { - $this->extractMetaDataFromArticle () ; - $this->editForm( 'initial', true ); + + wfProfileIn( "$fname-business-end" ); + + $this->isConflict = false; + // css / js subpages of user pages get a special treatment + $this->isCssJsSubpage = $wgTitle->isCssJsSubpage(); + + /* Notice that we can't use isDeleted, because it returns true if article is ever deleted + * no matter it's current state + */ + $this->deletedSinceEdit = false; + if ( $this->edittime != '' ) { + /* Note that we rely on logging table, which hasn't been always there, + * but that doesn't matter, because this only applies to brand new + * deletes. This is done on every preview and save request. Move it further down + * to only perform it on saves + */ + if ( $this->mTitle->isDeleted() ) { + $this->lastDelete = $this->getLastDelete(); + if ( !is_null($this->lastDelete) ) { + $deletetime = $this->lastDelete->log_timestamp; + if ( ($deletetime - $this->starttime) > 0 ) { + $this->deletedSinceEdit = true; + } + } } } + + if(!$this->mTitle->getArticleID() && ('initial' == $this->formtype || $this->firsttime )) { # new article + $this->showIntro(); + } + if( $this->mTitle->isTalkPage() ) { + $wgOut->addWikiText( wfMsg( 'talkpagetext' ) ); + } + + # Attempt submission here. This will check for edit conflicts, + # and redundantly check for locked database, blocked IPs, etc. + # that edit() already checked just in case someone tries to sneak + # in the back door with a hand-edited submission URL. + + if ( 'save' == $this->formtype ) { + if ( !$this->attemptSave() ) { + wfProfileOut( "$fname-business-end" ); + wfProfileOut( $fname ); + return; + } + } + + # First time through: get contents, set time for conflict + # checking, etc. + if ( 'initial' == $this->formtype || $this->firsttime ) { + $this->initialiseForm(); + } + + $this->showEditForm(); + wfProfileOut( "$fname-business-end" ); + wfProfileOut( $fname ); + } + + /** + * Return true if this page should be previewed when the edit form + * is initially opened. + * @return bool + * @access private + */ + function previewOnOpen() { + global $wgUser; + return $wgUser->getOption( 'previewonfirst' ) || + ( $this->mTitle->getNamespace() == NS_CATEGORY && + !$this->mTitle->exists() ); } /** * @todo document */ function importFormData( &$request ) { + global $wgLang ; + $fname = 'EditPage::importFormData'; + wfProfileIn( $fname ); + if( $request->wasPosted() ) { # These fields need to be checked for encoding. # Also remove trailing whitespace, but don't remove _initial_ # whitespace from the text boxes. This may be significant formatting. - $this->textbox1 = rtrim( $request->getText( 'wpTextbox1' ) ); - $this->textbox2 = rtrim( $request->getText( 'wpTextbox2' ) ); + $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' ); + $this->textbox2 = $this->safeUnicodeInput( $request, 'wpTextbox2' ); $this->mMetaData = rtrim( $request->getText( 'metadata' ) ); - $this->summary = $request->getText( 'wpSummary' ); - + # Truncate for whole multibyte characters. +5 bytes for ellipsis + $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250 ); + $this->edittime = $request->getVal( 'wpEdittime' ); + $this->starttime = $request->getVal( 'wpStarttime' ); + + $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' ); + if( is_null( $this->edittime ) ) { # If the form is incomplete, force to preview. + wfDebug( "$fname: Form data appears to be incomplete\n" ); + wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" ); $this->preview = true; } else { - if( $this->tokenOk( $request ) ) { - # Some browsers will not report any submit button - # if the user hits enter in the comment box. - # The unmarked state will be assumed to be a save, - # if the form seems otherwise complete. - $this->preview = $request->getCheck( 'wpPreview' ); - $this->diff = $request->getCheck( 'wpDiff' ); - } else { - # Page might be a hack attempt posted from - # an external site. Preview instead of saving. - $this->preview = true; + $this->preview = $request->getCheck( 'wpPreview' ); + $this->diff = $request->getCheck( 'wpDiff' ); + + if( !$this->preview ) { + if ( $this->tokenOk( $request ) ) { + # Some browsers will not report any submit button + # if the user hits enter in the comment box. + # The unmarked state will be assumed to be a save, + # if the form seems otherwise complete. + wfDebug( "$fname: Passed token check.\n" ); + } else { + # Page might be a hack attempt posted from + # an external site. Preview instead of saving. + wfDebug( "$fname: Failed token check; forcing preview\n" ); + $this->preview = true; + } } } $this->save = ! ( $this->preview OR $this->diff ); if( !preg_match( '/^\d{14}$/', $this->edittime )) { $this->edittime = null; } + + if( !preg_match( '/^\d{14}$/', $this->starttime )) { + $this->starttime = null; + } + $this->recreate = $request->getCheck( 'wpRecreate' ); + $this->minoredit = $request->getCheck( 'wpMinoredit' ); $this->watchthis = $request->getCheck( 'wpWatchthis' ); } else { # Not a posted form? Start with nothing. + wfDebug( "$fname: Not a posted form.\n" ); $this->textbox1 = ''; $this->textbox2 = ''; $this->mMetaData = ''; $this->summary = ''; $this->edittime = ''; + $this->starttime = wfTimestampNow(); $this->preview = false; $this->save = false; $this->diff = false; $this->minoredit = false; $this->watchthis = false; + $this->recreate = false; } $this->oldid = $request->getInt( 'oldid' ); # Section edit can come from either the form or a link $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) ); - + $this->live = $request->getCheck( 'live' ); + $this->editintro = $request->getText( 'editintro' ); + + wfProfileOut( $fname ); } /** @@ -263,203 +408,262 @@ class EditPage { if( $wgUser->isAnon() ) { # Anonymous users may not have a session # open. Don't tokenize. - return true; + $this->mTokenOk = true; } else { - return $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); + $this->mTokenOk = $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); } + return $this->mTokenOk; } - - function submit() { - $this->edit(); + + function showIntro() { + global $wgOut, $wgUser; + $addstandardintro=true; + if($this->editintro) { + $introtitle=Title::newFromText($this->editintro); + if(isset($introtitle) && $introtitle->userCanRead()) { + $rev=Revision::newFromTitle($introtitle); + if($rev) { + $wgOut->addWikiText($rev->getText()); + $addstandardintro=false; + } + } + } + if($addstandardintro) { + if ( $wgUser->isLoggedIn() ) + $wgOut->addWikiText( wfMsg( 'newarticletext' ) ); + else + $wgOut->addWikiText( wfMsg( 'newarticletextanon' ) ); + } } /** - * The edit form is self-submitting, so that when things like - * preview and edit conflicts occur, we get the same form back - * with the extra stuff added. Only when the final submission - * is made and all is well do we actually save and redirect to - * the newly-edited page. - * - * @param string $formtype Type of form either : save, initial, diff or preview - * @param bool $firsttime True to load form data from db + * Attempt submission + * @return bool false if output is done, true if the rest of the form should be displayed */ - function editForm( $formtype, $firsttime = false ) { - global $wgOut, $wgUser; - global $wgLang, $wgContLang, $wgParser, $wgTitle; - global $wgAllowAnonymousMinor; - global $wgSpamRegex, $wgFilterCallback; - - $sk = $wgUser->getSkin(); - $isConflict = false; - // css / js subpages of user pages get a special treatment - $isCssJsSubpage = $wgTitle->isCssJsSubpage(); + function attemptSave() { + global $wgSpamRegex, $wgFilterCallback, $wgUser, $wgOut; + $fname = 'EditPage::attemptSave'; + wfProfileIn( $fname ); + wfProfileIn( "$fname-checks" ); - if(!$this->mTitle->getArticleID()) { # new article - $wgOut->addWikiText(wfmsg('newarticletext')); - } + # Reintegrate metadata + if ( $this->mMetaData != '' ) $this->textbox1 .= "\n" . $this->mMetaData ; + $this->mMetaData = '' ; - if( $this->mTitle->isTalkPage() ) { - $wgOut->addWikiText(wfmsg('talkpagetext')); + # Check for spam + if ( $wgSpamRegex && preg_match( $wgSpamRegex, $this->textbox1, $matches ) ) { + $this->spamPage ( $matches[0] ); + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return false; + } + if ( $wgFilterCallback && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section ) ) { + # Error messages or other handling should be performed by the filter function + wfProfileOut( $fname ); + wfProfileOut( "$fname-checks" ); + return false; + } + if ( !wfRunHooks( 'EditFilter', array( &$this, $this->textbox1, $this->section ) ) ) { + # Error messages or other handling should be performed by the filter function + wfProfileOut( $fname ); + wfProfileOut( "$fname-checks" ); + return false; + } + if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) { + # Check block state against master, thus 'false'. + $this->blockedIPpage(); + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return false; } - # Attempt submission here. This will check for edit conflicts, - # and redundantly check for locked database, blocked IPs, etc. - # that edit() already checked just in case someone tries to sneak - # in the back door with a hand-edited submission URL. - - if ( 'save' == $formtype ) { - # Reintegrate metadata - if ( $this->mMetaData != '' ) $this->textbox1 .= "\n" . $this->mMetaData ; - $this->mMetaData = '' ; - - # Check for spam - if ( $wgSpamRegex && preg_match( $wgSpamRegex, $this->textbox1, $matches ) ) { - $this->spamPage ( $matches[0] ); - return; - } - if ( $wgFilterCallback && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section ) ) { - # Error messages or other handling should be performed by the filter function - return; - } - if ( $wgUser->isBlocked( false ) ) { - # Check block state against master, thus 'false'. - $this->blockedIPpage(); - return; - } - - if ( !$wgUser->isAllowed('edit') ) { - if ( $wgUser->isAnon() ) { + if ( !$wgUser->isAllowed('edit') ) { + if ( $wgUser->isAnon() ) { $this->userNotLoggedInPage(); - return; - } - else { - $wgOut->readOnlyPage(); - return; - } + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return false; } - - if ( wfReadOnly() ) { + else { $wgOut->readOnlyPage(); - return; + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return false; } - if ( $wgUser->pingLimiter() ) { - $wgOut->rateLimited(); + } + + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return false; + } + if ( $wgUser->pingLimiter() ) { + $wgOut->rateLimited(); + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return false; + } + + # If the article has been deleted while editing, don't save it without + # confirmation + if ( $this->deletedSinceEdit && !$this->recreate ) { + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return true; + } + + wfProfileOut( "$fname-checks" ); + + # If article is new, insert it. + $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE ); + if ( 0 == $aid ) { + // Late check for create permission, just in case *PARANOIA* + if ( !$this->mTitle->userCan( 'create' ) ) { + wfDebug( "$fname: no create permission\n" ); + $this->noCreatePermission(); + wfProfileOut( $fname ); return; } - - # If article is new, insert it. - $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE ); - if ( 0 == $aid ) { - # Don't save a new article if it's blank. - if ( ( '' == $this->textbox1 ) || - ( wfMsg( 'newarticletext' ) == $this->textbox1 ) ) { + + # Don't save a new article if it's blank. + if ( ( '' == $this->textbox1 ) ) { $wgOut->redirect( $this->mTitle->getFullURL() ); - return; - } - if (wfRunHooks('ArticleSave', array(&$this->mArticle, &$wgUser, &$this->textbox1, - &$this->summary, &$this->minoredit, &$this->watchthis, NULL))) - { - $this->mArticle->insertNewArticle( $this->textbox1, $this->summary, - $this->minoredit, $this->watchthis ); - wfRunHooks('ArticleSaveComplete', array(&$this->mArticle, &$wgUser, $this->textbox1, - $this->summary, $this->minoredit, - $this->watchthis, NULL)); - } - return; + wfProfileOut( $fname ); + return false; } - # Article exists. Check for edit conflict. + $isComment=($this->section=='new'); + $this->mArticle->insertNewArticle( $this->textbox1, $this->summary, + $this->minoredit, $this->watchthis, false, $isComment); + + wfProfileOut( $fname ); + return false; + } - $this->mArticle->clear(); # Force reload of dates, etc. - $this->mArticle->forUpdate( true ); # Lock the article + # Article exists. Check for edit conflict. - if( ( $this->section != 'new' ) && - ($this->mArticle->getTimestamp() != $this->edittime ) ) { - $isConflict = true; - } - $userid = $wgUser->getID(); + $this->mArticle->clear(); # Force reload of dates, etc. + $this->mArticle->forUpdate( true ); # Lock the article - if ( $isConflict) { - wfDebug( "EditPage::editForm conflict! getting section '$this->section' for time '$this->edittime'\n" ); - $text = $this->mArticle->getTextOfLastEditWithSectionReplacedOrAdded( - $this->section, $this->textbox1, $this->summary, $this->edittime); - } - else { - wfDebug( "EditPage::editForm getting section '$this->section'\n" ); - $text = $this->mArticle->getTextOfLastEditWithSectionReplacedOrAdded( - $this->section, $this->textbox1, $this->summary); - } - # Suppress edit conflict with self + if( ( $this->section != 'new' ) && + ($this->mArticle->getTimestamp() != $this->edittime ) ) + { + $this->isConflict = true; + } + $userid = $wgUser->getID(); - if ( ( 0 != $userid ) && ( $this->mArticle->getUser() == $userid ) ) { - $isConflict = false; - } else { - # switch from section editing to normal editing in edit conflict - if($isConflict) { - # Attempt merge - if( $this->mergeChangesInto( $text ) ){ - // Successful merge! Maybe we should tell the user the good news? - $isConflict = false; - } else { - $this->section = ''; - $this->textbox1 = $text; - } + if ( $this->isConflict) { + wfDebug( "EditPage::editForm conflict! getting section '$this->section' for time '$this->edittime' (article time '" . + $this->mArticle->getTimestamp() . "'\n" ); + $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime); + } + else { + wfDebug( "EditPage::editForm getting section '$this->section'\n" ); + $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary); + } + if( is_null( $text ) ) { + wfDebug( "EditPage::editForm activating conflict; section replace failed.\n" ); + $this->isConflict = true; + $text = $this->textbox1; + } + + # Suppress edit conflict with self, except for section edits where merging is required. + if ( ( $this->section == '' ) && ( 0 != $userid ) && ( $this->mArticle->getUser() == $userid ) ) { + wfDebug( "Suppressing edit conflict, same user.\n" ); + $this->isConflict = false; + } else { + # switch from section editing to normal editing in edit conflict + if($this->isConflict) { + # Attempt merge + if( $this->mergeChangesInto( $text ) ){ + // Successful merge! Maybe we should tell the user the good news? + $this->isConflict = false; + wfDebug( "Suppressing edit conflict, successful merge.\n" ); + } else { + $this->section = ''; + $this->textbox1 = $text; + wfDebug( "Keeping edit conflict, failed merge.\n" ); } } - if ( ! $isConflict ) { - # All's well - $sectionanchor = ''; - if( $this->section == 'new' ) { - if( $this->summary != '' ) { - $sectionanchor = $this->sectionAnchor( $this->summary ); - } - } elseif( $this->section != '' ) { - # Try to get a section anchor from the section source, redirect to edited section if header found - # XXX: might be better to integrate this into Article::getTextOfLastEditWithSectionReplacedOrAdded - # for duplicate heading checking and maybe parsing - $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches ); - # we can't deal with anchors, includes, html etc in the header for now, - # headline would need to be parsed to improve this - #if($hasmatch and strlen($matches[2]) > 0 and !preg_match( "/[\\['{<>]/", $matches[2])) { - if($hasmatch and strlen($matches[2]) > 0) { - $sectionanchor = $this->sectionAnchor( $matches[2] ); - } - } - - if (wfRunHooks('ArticleSave', array(&$this->mArticle, &$wgUser, &$text, - &$this->summary, &$this->minoredit, - &$this->watchthis, &$sectionanchor))) - { - # update the article here - if($this->mArticle->updateArticle( $text, $this->summary, $this->minoredit, - $this->watchthis, '', $sectionanchor )) - { - wfRunHooks('ArticleSaveComplete', array(&$this->mArticle, &$wgUser, $text, - $this->summary, $this->minoredit, - $this->watchthis, $sectionanchor)); - return; - } - else - $isConflict = true; - } + } + + if ( $this->isConflict ) { + wfProfileOut( $fname ); + return true; + } + + # All's well + wfProfileIn( "$fname-sectionanchor" ); + $sectionanchor = ''; + if( $this->section == 'new' ) { + if( $this->summary != '' ) { + $sectionanchor = $this->sectionAnchor( $this->summary ); + } + } elseif( $this->section != '' ) { + # Try to get a section anchor from the section source, redirect to edited section if header found + # XXX: might be better to integrate this into Article::replaceSection + # for duplicate heading checking and maybe parsing + $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches ); + # we can't deal with anchors, includes, html etc in the header for now, + # headline would need to be parsed to improve this + if($hasmatch and strlen($matches[2]) > 0) { + $sectionanchor = $this->sectionAnchor( $matches[2] ); } } - # First time through: get contents, set time for conflict - # checking, etc. - - if ( 'initial' == $formtype || $firsttime ) { - $this->edittime = $this->mArticle->getTimestamp(); - $this->textbox1 = $this->mArticle->getContent( true ); - $this->summary = ''; - $this->proxyCheck(); + wfProfileOut( "$fname-sectionanchor" ); + + // Save errors may fall down to the edit form, but we've now + // merged the section into full text. Clear the section field + // so that later submission of conflict forms won't try to + // replace that into a duplicated mess. + $this->textbox1 = $text; + $this->section = ''; + + # update the article here + if( $this->mArticle->updateArticle( $text, $this->summary, $this->minoredit, + $this->watchthis, '', $sectionanchor ) ) { + wfProfileOut( $fname ); + return false; + } else { + $this->isConflict = true; } + wfProfileOut( $fname ); + return true; + } + + /** + * Initialise form fields in the object + * Called on the first invocation, e.g. when a user clicks an edit link + */ + function initialiseForm() { + $this->edittime = $this->mArticle->getTimestamp(); + $this->textbox1 = $this->mArticle->getContent( true ); + $this->summary = ''; + wfProxyCheck(); + } + + /** + * Send the edit form and related headers to $wgOut + * @param $formCallback Optional callable that takes an OutputPage + * parameter; will be called during form output + * near the top, for captchas and the like. + */ + function showEditForm( $formCallback=null ) { + global $wgOut, $wgUser, $wgAllowAnonymousMinor, $wgLang, $wgContLang; + + $fname = 'EditPage::showEditForm'; + wfProfileIn( $fname ); + + $sk =& $wgUser->getSkin(); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Enabled article-related sidebar, toplinks, etc. $wgOut->setArticleRelated( true ); - if ( $isConflict ) { + if ( $this->isConflict ) { $s = wfMsg( 'editconflict', $this->mTitle->getPrefixedText() ); $wgOut->setPageTitle( $s ); $wgOut->addWikiText( wfMsg( 'explainconflict' ) ); @@ -474,32 +678,33 @@ class EditPage { $s = wfMsg('editingcomment', $this->mTitle->getPrefixedText() ); } else { $s = wfMsg('editingsection', $this->mTitle->getPrefixedText() ); - } - if(!$this->preview) { - preg_match( "/^(=+)(.+)\\1/mi", - $this->textbox1, - $matches ); - if( !empty( $matches[2] ) ) { - $this->summary = "/* ". trim($matches[2])." */ "; - } + if( !$this->preview && !$this->diff ) { + preg_match( "/^(=+)(.+)\\1/mi", + $this->textbox1, + $matches ); + if( !empty( $matches[2] ) ) { + $this->summary = "/* ". trim($matches[2])." */ "; + } + } } } else { $s = wfMsg( 'editing', $this->mTitle->getPrefixedText() ); } $wgOut->setPageTitle( $s ); if ( !$this->checkUnicodeCompliantBrowser() ) { - $this->mArticle->setOldSubtitle(); $wgOut->addWikiText( wfMsg( 'nonunicodebrowser') ); } - if ( $this->oldid ) { - $this->mArticle->setOldSubtitle(); + if ( isset( $this->mArticle ) + && isset( $this->mArticle->mRevision ) + && !$this->mArticle->mRevision->isCurrent() ) { + $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() ); $wgOut->addWikiText( wfMsg( 'editingold' ) ); } } if( wfReadOnly() ) { $wgOut->addWikiText( wfMsg( 'readonlywarning' ) ); - } else if ( $isCssJsSubpage and 'preview' != $formtype) { + } else if ( $this->isCssJsSubpage and 'preview' != $this->formtype) { $wgOut->addWikiText( wfMsg( 'usercssjsyoucanpreview' )); } if( $this->mTitle->isProtected('edit') ) { @@ -543,7 +748,7 @@ class EditPage { '[[' . wfMsgForContent( 'copyrightpage' ) . ']]', $wgRightsText ) . "\n"; - if( $wgUser->getOption('showtoolbar') and !$isCssJsSubpage ) { + if( $wgUser->getOption('showtoolbar') and !$this->isCssJsSubpage ) { # prepare toolbar for edit buttons $toolbar = $this->getEditToolbar(); } else { @@ -573,26 +778,19 @@ class EditPage { $watchhtml = ''; if ( $wgUser->isLoggedIn() ) { - $watchhtml = "watchthis?" checked='checked'":""). - " accesskey='".wfMsg('accesskey-watch')."' id='wpWatchthis' />". - ""; + $watchhtml = "watchthis?" checked='checked'":""). + " accesskey=\"".htmlspecialchars(wfMsg('accesskey-watch'))."\" id='wpWatchthis' />". + ""; } - $checkboxhtml = $minoredithtml . $watchhtml . '
'; + $checkboxhtml = $minoredithtml . $watchhtml; - $wgOut->addHTML( '
' ); - if ( 'preview' == $formtype) { - $previewOutput = $this->getPreviewText( $isConflict, $isCssJsSubpage ); - if ( $wgUser->getOption('previewontop' ) ) { - $wgOut->addHTML( $previewOutput ); - if($this->mTitle->getNamespace() == NS_CATEGORY) { - $this->mArticle->closeShowCategory(); - } - $wgOut->addHTML( "
\n" ); - } + if ( 'preview' == $this->formtype && $wgUser->getOption( 'previewontop' ) ) { + $this->showPreview(); } - $wgOut->addHTML( '
' ); - if ( 'diff' == $formtype ) { + if ( 'diff' == $this->formtype ) { if ( $wgUser->getOption('previewontop' ) ) { $wgOut->addHTML( $this->getDiff() ); } @@ -602,89 +800,114 @@ class EditPage { # if this is a comment, show a subject line at the top, which is also the edit summary. # Otherwise, show a summary field at the bottom $summarytext = htmlspecialchars( $wgContLang->recodeForEdit( $this->summary ) ); # FIXME - if( $this->section == 'new' ) { - $commentsubject="{$subject}:
"; - $editsummary = ''; - } else { - $commentsubject = ''; - $editsummary="{$summary}:
"; - } + if( $this->section == 'new' ) { + $commentsubject="

"; + $editsummary = ''; + } else { + $commentsubject = ''; + $editsummary="

"; + } + # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display if( !$this->preview && !$this->diff ) { - # Don't select the edit box on preview; this interferes with seeing what's going on. $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus()' ); } - # Prepare a list of templates used by this page - $templates = ''; - $articleTemplates = $this->mArticle->getUsedTemplates(); - if ( count( $articleTemplates ) > 0 ) { - $templates = '
'. wfMsg( 'templatesused' ) . ''; - } - - global $wgLivePreview, $wgStylePath; - /** - * Live Preview lets us fetch rendered preview page content and - * add it to the page without refreshing the whole page. - * Set up the button for it; if not supported by the browser - * it will fall through to the normal form submission method. - */ - if( $wgLivePreview ) { - global $wgJsMimeType; - $wgOut->addHTML( '' . "\n" ); - $liveAction = $wgTitle->getLocalUrl( 'action=submit&wpPreview=true&live=true' ); - $liveOnclick = 'onclick="return !livePreview('. - 'getElementById(\'wikiPreview\'),' . - 'editform.wpTextbox1.value,' . - htmlspecialchars( '"' . $liveAction . '"' ) . ')"'; + $templates = $this->getTemplatesUsed(); + + global $wgLivePreview; + if ( $wgLivePreview ) { + $liveOnclick = $this->doLivePreviewScript(); } else { $liveOnclick = ''; } - + global $wgUseMetadataEdit ; - if ( $wgUseMetadataEdit ) - { + if ( $wgUseMetadataEdit ) { $metadata = $this->mMetaData ; $metadata = htmlspecialchars( $wgContLang->recodeForEdit( $metadata ) ) ; - $helppage = Title::newFromText ( wfmsg("metadata_page") ) ; - $top = str_replace ( "$1" , $helppage->getInternalURL() , wfmsg("metadata") ) ; + $helppage = Title::newFromText( wfMsg( "metadata_page" ) ) ; + $top = wfMsg( 'metadata', $helppage->getInternalURL() ); $metadata = $top . "" ; } else $metadata = "" ; + $hidden = ''; + $recreate = ''; + if ($this->deletedSinceEdit) { + if ( 'save' != $this->formtype ) { + $wgOut->addWikiText( wfMsg('deletedwhileediting')); + } else { + // Hide the toolbar and edit area, use can click preview to get it back + // Add an confirmation checkbox and explanation. + $toolbar = ''; + $hidden = 'type="hidden" style="display:none;"'; + $recreate = $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment )); + $recreate .= + "
". + ""; + } + } + + $safemodehtml = $this->checkUnicodeCompliantBrowser() + ? "" + : "\n"; $wgOut->addHTML( << +END +); + if( is_callable( $formCallback ) ) { + call_user_func_array( $formCallback, array( &$wgOut ) ); + } + + // Put these up at the top to ensure they aren't lost on early form submission + $wgOut->addHTML( " +section ) . "\" name=\"wpSection\" /> +starttime}\" name=\"wpStarttime\" />\n +edittime}\" name=\"wpEdittime\" />\n +scrolltop}\" name=\"wpScrolltop\" id=\"wpScrolltop\" />\n" ); + + $wgOut->addHTML( << + + + " ); + + $wgOut->addWikiText( $copywarn ); + + $wgOut->addHTML( " {$metadata} -
{$editsummary} +{$editsummary} {$checkboxhtml} +{$safemodehtml} +"); + + $wgOut->addHTML( " +
-{$cancel} | {$edithelp}{$templates}" ); - $wgOut->addWikiText( $copywarn ); +" title=\"".wfMsg('tooltip-diff')."\"/> {$cancel} | {$edithelp}
+
+" ); + + $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); + $wgOut->addHTML( " -section ) . "\" name=\"wpSection\" /> -edittime}\" name=\"wpEdittime\" />\n" ); +
+{$templates} +
+" ); if ( $wgUser->isLoggedIn() ) { /** @@ -696,43 +919,152 @@ END * CSS previews. */ $token = htmlspecialchars( $wgUser->editToken() ); - $wgOut->addHTML( " -\n" ); + $wgOut->addHTML( "\n\n" ); } - - - if ( $isConflict ) { + + + if ( $this->isConflict ) { require_once( "DifferenceEngine.php" ); $wgOut->addWikiText( '==' . wfMsg( "yourdiff" ) . '==' ); - DifferenceEngine::showDiff( $this->textbox2, $this->textbox1, - wfMsg( "yourtext" ), wfMsg( "storedversion" ) ); + + $de = new DifferenceEngine( $this->mTitle ); + $de->setText( $this->textbox2, $this->textbox1 ); + $de->showDiff( wfMsg( "yourtext" ), wfMsg( "storedversion" ) ); $wgOut->addWikiText( '==' . wfMsg( "yourtext" ) . '==' ); $wgOut->addHTML( "" ); + . htmlspecialchars( $this->safeUnicodeOutput( $this->textbox2 ) ) . "\n" ); } $wgOut->addHTML( "\n" ); - if ( $formtype == 'preview' && !$wgUser->getOption( 'previewontop' ) ) { - $wgOut->addHTML( '
' . $previewOutput . '
' ); + if ( $this->formtype == 'preview' && !$wgUser->getOption( 'previewontop' ) ) { + $this->showPreview(); } - if ( $formtype == 'diff' && !$wgUser->getOption( 'previewontop' ) ) { + if ( $this->formtype == 'diff' && !$wgUser->getOption( 'previewontop' ) ) { #$wgOut->addHTML( '
' . $difftext . '
' ); $wgOut->addHTML( $this->getDiff() ); } + + wfProfileOut( $fname ); + } + + /** + * Append preview output to $wgOut. + * Includes category rendering if this is a category page. + * @access private + */ + function showPreview() { + global $wgOut; + $wgOut->addHTML( '
' ); + if($this->mTitle->getNamespace() == NS_CATEGORY) { + $this->mArticle->openShowCategory(); + } + $previewOutput = $this->getPreviewText(); + $wgOut->addHTML( $previewOutput ); + if($this->mTitle->getNamespace() == NS_CATEGORY) { + $this->mArticle->closeShowCategory(); + } + $wgOut->addHTML( "
\n" ); + $wgOut->addHTML( '
' ); + } + + /** + * Prepare a list of templates used by this page. Returns HTML. + */ + function getTemplatesUsed() { + global $wgUser; + + $fname = 'EditPage::getTemplatesUsed'; + wfProfileIn( $fname ); + + $sk =& $wgUser->getSkin(); + + $templates = ''; + $articleTemplates = $this->mArticle->getUsedTemplates(); + if ( count( $articleTemplates ) > 0 ) { + $templates = '
'. wfMsg( 'templatesused' ) . ''; + } + wfProfileOut( $fname ); + return $templates; + } + + /** + * Live Preview lets us fetch rendered preview page content and + * add it to the page without refreshing the whole page. + * If not supported by the browser it will fall through to the normal form + * submission method. + * + * This function outputs a script tag to support live preview, and + * returns an onclick handler which should be added to the attributes + * of the preview button + */ + function doLivePreviewScript() { + global $wgStylePath, $wgJsMimeType, $wgOut; + $wgOut->addHTML( '' . "\n" ); + $liveAction = $wgTitle->getLocalUrl( 'action=submit&wpPreview=true&live=true' ); + return 'onclick="return !livePreview('. + 'getElementById(\'wikiPreview\'),' . + 'editform.wpTextbox1.value,' . + htmlspecialchars( '"' . $liveAction . '"' ) . ')"'; + } + + function getLastDelete() { + $dbr =& wfGetDB( DB_SLAVE ); + $fname = 'EditPage::getLastDelete'; + $res = $dbr->select( + array( 'logging', 'user' ), + array( 'log_type', + 'log_action', + 'log_timestamp', + 'log_user', + 'log_namespace', + 'log_title', + 'log_comment', + 'log_params', + 'user_name', ), + array( 'log_namespace' => $this->mTitle->getNamespace(), + 'log_title' => $this->mTitle->getDBkey(), + 'log_type' => 'delete', + 'log_action' => 'delete', + 'user_id=log_user' ), + $fname, + array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ) ); + + if($dbr->numRows($res) == 1) { + while ( $x = $dbr->fetchObject ( $res ) ) + $data = $x; + $dbr->freeResult ( $res ) ; + } else { + $data = null; + } + return $data; } /** * @todo document */ - function getPreviewText( $isConflict, $isCssJsSubpage ) { - global $wgOut, $wgUser, $wgTitle, $wgParser, $wgAllowDiffPreview, $wgEnableDiffPreviewPreference; + function getPreviewText() { + global $wgOut, $wgUser, $wgTitle, $wgParser; + + $fname = 'EditPage::getPreviewText'; + wfProfileIn( $fname ); + + if ( $this->mTokenOk ) { + $msg = 'previewnote'; + } else { + $msg = 'session_fail_preview'; + } $previewhead = '

' . htmlspecialchars( wfMsg( 'preview' ) ) . "

\n" . - "

" . htmlspecialchars( wfMsg( 'previewnote' ) ) . "

\n"; - if ( $isConflict ) { - $previewhead.='

' . htmlspecialchars( wfMsg( 'previewconflict' ) ) . - "

\n"; + "
" . $wgOut->parse( wfMsg( $msg ) ) . "
\n"; + if ( $this->isConflict ) { + $previewhead.='

' . htmlspecialchars( wfMsg( 'previewconflict' ) ) . "

\n"; } $parserOptions = ParserOptions::newFromUser( $wgUser ); @@ -741,7 +1073,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 ( $isCssJsSubpage ) { + if ( $this->isCssJsSubpage ) { if(preg_match("/\\.css$/", $wgTitle->getText() ) ) { $previewtext = wfMsg('usercsspreview'); } else if(preg_match("/\\.js$/", $wgTitle->getText() ) ) { @@ -749,6 +1081,7 @@ END } $parserOutput = $wgParser->parse( $previewtext , $wgTitle, $parserOptions ); $wgOut->addHTML( $parserOutput->mText ); + wfProfileOut( $fname ); return $previewhead; } else { # if user want to see preview when he edit an article @@ -756,25 +1089,34 @@ END $this->textbox1 = $this->mArticle->getContent(true); } - $toparse = $this->textbox1 ; - if ( $this->mMetaData != "" ) $toparse .= "\n" . $this->mMetaData ; + $toparse = $this->textbox1; - $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ) ."\n\n", - $wgTitle, $parserOptions ); + # If we're adding a comment, we need to show the + # summary as the headline + if($this->section=="new" && $this->summary!="") { + $toparse="== {$this->summary} ==\n\n".$toparse; + } + if ( $this->mMetaData != "" ) $toparse .= "\n" . $this->mMetaData ; + + $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ) ."\n\n", + $wgTitle, $parserOptions ); + $previewHTML = $parserOutput->mText; - + $wgOut->addCategoryLinks($parserOutput->getCategoryLinks()); $wgOut->addLanguageLinks($parserOutput->getLanguageLinks()); + + wfProfileOut( $fname ); return $previewhead . $previewHTML; } } - + /** * @todo document */ function blockedIPpage() { - global $wgOut, $wgUser, $wgContLang, $wgIP; + global $wgOut, $wgUser, $wgContLang; $wgOut->setPageTitle( wfMsg( 'blockedtitle' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); @@ -782,8 +1124,8 @@ END $id = $wgUser->blockedBy(); $reason = $wgUser->blockedFor(); - $ip = $wgIP; - + $ip = wfGetIP(); + if ( is_numeric( $id ) ) { $name = User::whoIs( $id ); } else { @@ -828,72 +1170,56 @@ END } /** - * Forks processes to scan the originating IP for an open proxy server - * MemCached can be used to skip IPs that have already been scanned + * @access private + * @todo document */ - function proxyCheck() { - global $wgBlockOpenProxies, $wgProxyPorts, $wgProxyScriptPath; - global $wgIP, $wgUseMemCached, $wgMemc, $wgDBname, $wgProxyMemcExpiry; - - if ( !$wgBlockOpenProxies ) { - return; - } - - # Get MemCached key - $skip = false; - if ( $wgUseMemCached ) { - $mcKey = $wgDBname.':proxy:ip:'.$wgIP; - $mcValue = $wgMemc->get( $mcKey ); - if ( $mcValue ) { - $skip = true; - } + function mergeChangesInto( &$editText ){ + $fname = 'EditPage::mergeChangesInto'; + wfProfileIn( $fname ); + + $db =& wfGetDB( DB_MASTER ); + + // This is the revision the editor started from + $baseRevision = Revision::loadFromTimestamp( + $db, $this->mArticle->mTitle, $this->edittime ); + if( is_null( $baseRevision ) ) { + wfProfileOut( $fname ); + return false; } + $baseText = $baseRevision->getText(); - # Fork the processes - if ( !$skip ) { - $title = Title::makeTitle( NS_SPECIAL, 'Blockme' ); - $iphash = md5( $wgIP . $wgProxyKey ); - $url = $title->getFullURL( 'ip='.$iphash ); - - foreach ( $wgProxyPorts as $port ) { - $params = implode( ' ', array( - escapeshellarg( $wgProxyScriptPath ), - escapeshellarg( $wgIP ), - escapeshellarg( $port ), - escapeshellarg( $url ) - )); - exec( "php $params &>/dev/null &" ); - } - # Set MemCached key - if ( $wgUseMemCached ) { - $wgMemc->set( $mcKey, 1, $wgProxyMemcExpiry ); - } + // The current state, we want to merge updates into it + $currentRevision = Revision::loadFromTitle( + $db, $this->mArticle->mTitle ); + if( is_null( $currentRevision ) ) { + wfProfileOut( $fname ); + return false; } - } + $currentText = $currentRevision->getText(); - /** - * @access private - * @todo document - */ - function mergeChangesInto( &$text ){ - $yourtext = $this->mArticle->fetchRevisionText(); - - $db =& wfGetDB( DB_MASTER ); - $oldText = $this->mArticle->fetchRevisionText( - $db->timestamp( $this->edittime ), - 'rev_timestamp' ); - - if(wfMerge($oldText, $text, $yourtext, $result)){ - $text = $result; + if( wfMerge( $baseText, $editText, $currentText, $result ) ){ + $editText = $result; + wfProfileOut( $fname ); return true; } else { + wfProfileOut( $fname ); return false; } } - + /** + * Check if the browser is on a blacklist of user-agents known to + * mangle UTF-8 data on form submission. Returns true if Unicode + * should make it through, false if it's known to be a problem. + * @return bool + * @access private + */ function checkUnicodeCompliantBrowser() { global $wgBrowserBlackList; + if( empty( $_SERVER["HTTP_USER_AGENT"] ) ) { + // No User-Agent header sent? Trust it by default... + return true; + } $currentbrowser = $_SERVER["HTTP_USER_AGENT"]; foreach ( $wgBrowserBlackList as $browser ) { if ( preg_match($browser, $currentbrowser) ) { @@ -911,7 +1237,7 @@ END */ function sectionAnchor( $text ) { $headline = Sanitizer::decodeCharReferences( $text ); - # strip out HTML + # strip out HTML $headline = preg_replace( '/<.*?' . '>/', '', $headline ); $headline = trim( $headline ); $sectionanchor = '#' . urlencode( str_replace( ' ', '_', $headline ) ); @@ -931,7 +1257,7 @@ END * The necessary JavaScript code can be found in style/wikibits.js. */ function getEditToolbar() { - global $wgStylePath, $wgLang, $wgMimeType, $wgJsMimeType; + global $wgStylePath, $wgLang, $wgJsMimeType; /** * toolarray an array of arrays which each include the filename of @@ -1044,14 +1370,11 @@ END $toolbar.="addButton('$image','$tip','$open','$close','$sample');\n"; } - $toolbar.="addInfobox('" . wfEscapeJsString( wfMsg( "infobox" ) ) . - "','" . wfEscapeJsString( wfMsg( "infobox_alert" ) ) . "');\n"; $toolbar.="document.writeln(\"
\");\n"; - $toolbar.="/*]]>*/\n"; return $toolbar; } - + /** * Output preview text only. This can be sucked into the edit page * via JavaScript, and saves the server time rendering the skin as @@ -1084,19 +1407,146 @@ END * @return string HTML */ function getDiff() { + global $wgUser; + require_once( 'DifferenceEngine.php' ); - $oldtext = $this->mArticle->getContent( true ); - $newtext = $this->mArticle->getTextOfLastEditWithSectionReplacedOrAdded( + $oldtext = $this->mArticle->fetchContent(); + $newtext = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime ); $oldtitle = wfMsg( 'currentrev' ); $newtitle = wfMsg( 'yourtext' ); - if ( $oldtext != wfMsg( 'noarticletext' ) || $newtext != '' ) { - $difftext = DifferenceEngine::getDiff( $oldtext, $newtext, $oldtitle, $newtitle ); + if ( $oldtext !== false || $newtext != '' ) { + $de = new DifferenceEngine( $this->mTitle ); + $de->setText( $oldtext, $newtext ); + $difftext = $de->getDiff( $oldtitle, $newtitle ); + } else { + $difftext = ''; } - + return '
' . $difftext . '
'; } + /** + * Filter an input field through a Unicode de-armoring process if it + * came from an old browser with known broken Unicode editing issues. + * + * @param WebRequest $request + * @param string $field + * @return string + * @access private + */ + function safeUnicodeInput( $request, $field ) { + $text = rtrim( $request->getText( $field ) ); + return $request->getBool( 'safemode' ) + ? $this->unmakesafe( $text ) + : $text; + } + + /** + * Filter an output field through a Unicode armoring process if it is + * going to an old browser with known broken Unicode editing issues. + * + * @param string $text + * @return string + * @access private + */ + function safeUnicodeOutput( $text ) { + global $wgContLang; + $codedText = $wgContLang->recodeForEdit( $text ); + return $this->checkUnicodeCompliantBrowser() + ? $codedText + : $this->makesafe( $codedText ); + } + + /** + * A number of web browsers are known to corrupt non-ASCII characters + * in a UTF-8 text editing environment. To protect against this, + * detected browsers will be served an armored version of the text, + * with non-ASCII chars converted to numeric HTML character references. + * + * Preexisting such character references will have a 0 added to them + * to ensure that round-trips do not alter the original data. + * + * @param string $invalue + * @return string + * @access private + */ + function makesafe( $invalue ) { + // Armor existing references for reversability. + $invalue = strtr( $invalue, array( "&#x" => "�" ) ); + + $bytesleft = 0; + $result = ""; + $working = 0; + for( $i = 0; $i < strlen( $invalue ); $i++ ) { + $bytevalue = ord( $invalue{$i} ); + if( $bytevalue <= 0x7F ) { //0xxx xxxx + $result .= chr( $bytevalue ); + $bytesleft = 0; + } elseif( $bytevalue <= 0xBF ) { //10xx xxxx + $working = $working << 6; + $working += ($bytevalue & 0x3F); + $bytesleft--; + if( $bytesleft <= 0 ) { + $result .= "&#x" . strtoupper( dechex( $working ) ) . ";"; + } + } elseif( $bytevalue <= 0xDF ) { //110x xxxx + $working = $bytevalue & 0x1F; + $bytesleft = 1; + } elseif( $bytevalue <= 0xEF ) { //1110 xxxx + $working = $bytevalue & 0x0F; + $bytesleft = 2; + } else { //1111 0xxx + $working = $bytevalue & 0x07; + $bytesleft = 3; + } + } + return $result; + } + + /** + * Reverse the previously applied transliteration of non-ASCII characters + * back to UTF-8. Used to protect data from corruption by broken web browsers + * as listed in $wgBrowserBlackList. + * + * @param string $invalue + * @return string + * @access private + */ + function unmakesafe( $invalue ) { + $result = ""; + for( $i = 0; $i < strlen( $invalue ); $i++ ) { + if( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue{$i+3} != '0' ) ) { + $i += 3; + $hexstring = ""; + do { + $hexstring .= $invalue{$i}; + $i++; + } while( ctype_xdigit( $invalue{$i} ) && ( $i < strlen( $invalue ) ) ); + + // Do some sanity checks. These aren't needed for reversability, + // but should help keep the breakage down if the editor + // breaks one of the entities whilst editing. + if ((substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6)) { + $codepoint = hexdec($hexstring); + $result .= codepointToUtf8( $codepoint ); + } else { + $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 ); + } + } else { + $result .= substr( $invalue, $i, 1 ); + } + } + // reverse the transform that we made for reversability reasons. + return strtr( $result, array( "�" => "&#x" ) ); + } + + function noCreatePermission() { + global $wgOut; + $wgOut->setPageTitle( wfMsg( 'nocreatetitle' ) ); + $wgOut->addWikiText( wfMsg( 'nocreatetext' ) ); + } + } ?>