Last modified: 2013-11-11 09:40:10 UTC
We are using MediaWIki to hold some semi structured data and needed a way to provide a consistent article format (actually several formats) As we only needed a structure outline the template mechanism was not applicable. I have made some minor changes to the EditPage.php that enable a list of article Skeletons to be provided when a page is first created, selecting one of the skeletons pulls the text of the skeleton article into the edit page, replacing what was there before. Because this is a potentially destructive action the skeletons are only available when initially creating a page - thus preventing existing content being accidentaly replaced. There is a known flaw in this implementation in that it stores the sekeleton articles in the main namespace wheras a full implementation would have used a separate namespace. The modified version of the 1.4.5 EditPage.php is listed below, (Sorry, I couldn't spot a file upload mechanism) - for identification the three blocks of changes are bracketed between START TIMG and END TIMG comments <?php /** * Contain the EditPage class * @package MediaWiki */ /** * Splitting edit page/HTML interface 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; # Form values var $save = false, $preview = false; var $minoredit = false, $watchthis = false; var $textbox1 = '', $textbox2 = '', $summary = ''; var $edittime = '', $section = ''; var $oldid = 0; /** * @todo document * @param $article */ function EditPage( $article ) { $this->mArticle =& $article; global $wgTitle; $this->mTitle =& $wgTitle; } /** * This is the function that gets called for "action=edit". */ function edit() { global $wgOut, $wgUser, $wgWhitelistEdit, $wgRequest; // this is not an article $wgOut->setArticleFlag(false); $this->importFormData( $wgRequest ); if ( ! $this->mTitle->userCanEdit() ) { $wgOut->readOnlyPage( $this->mArticle->getContent( true ), true ); return; } if ( $wgUser->isBlocked() ) { $this->blockedIPpage(); return; } if ( !$wgUser->getID() && $wgWhitelistEdit ) { $this->userNotLoggedInPage(); return; } if ( wfReadOnly() ) { if( $this->save || $this->preview ) { $this->editForm( 'preview' ); } else { $wgOut->readOnlyPage( $this->mArticle->getContent( true ) ); } return; } if ( $this->save ) { $this->editForm( 'save' ); } else if ( $this->preview ) { $this->editForm( 'preview' ); } else { # First time through if( $wgUser->getOption('previewonfirst') ) { $this->editForm( 'preview', true ); } else { $this->editForm( 'initial', true ); } } } /** * @todo document */ function importFormData( &$request ) { 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->summary = trim( $request->getText( 'wpSummary' ) ); $this->edittime = $request->getVal( 'wpEdittime' ); if( is_null( $this->edittime ) ) { # If the form is incomplete, force to preview. $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' ); } else { # Page might be a hack attempt posted from # an external site. Preview instead of saving. $this->preview = true; } } $this->save = !$this->preview; if( !preg_match( '/^\d{14}$/', $this->edittime )) { $this->edittime = null; } $this->minoredit = $request->getCheck( 'wpMinoredit' ); $this->watchthis = $request->getCheck( 'wpWatchthis' ); } else { # Not a posted form? Start with nothing. $this->textbox1 = ''; $this->textbox2 = ''; $this->summary = ''; $this->edittime = ''; $this->preview = false; $this->save = false; $this->minoredit = false; $this->watchthis = 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' ) ); } /** * Make sure the form isn't faking a user's credentials. * * @param WebRequest $request * @return bool * @access private */ function tokenOk( &$request ) { global $wgUser; if( $wgUser->getId() == 0 ) { # Anonymous users may not have a session # open. Don't tokenize. return true; } else { return $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); } } function submit() { $this->edit(); } /** * 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 or preview * @param bool $firsttime True to load form data from db */ function editForm( $formtype, $firsttime = false ) { global $wgOut, $wgUser; global $wgLang, $wgContLang, $wgParser, $wgTitle; global $wgAllowAnonymousMinor; global $wgWhitelistEdit; global $wgSpamRegex, $wgFilterCallback; global $wgUseLatin1; $sk = $wgUser->getSkin(); $isConflict = false; // css / js subpages of user pages get a special treatment $isCssJsSubpage = (Namespace::getUser() == $wgTitle->getNamespace() and preg_match("/\\.(css|js)$/", $wgTitle->getText() )); # START TIMG # setup the variables for later use $timsSkeletonEnabled = false ; $timsSkeletonText = '' ; # END TIMG if(!$this->mTitle->getArticleID()) { # new article $wgOut->addWikiText(wfmsg('newarticletext')); # START TIMG # Get the details for the skeleton URLs $timsTitleEditURL=$this->mTitle->getEditURL() ; # List the possible skeletons # This code is based on the code below that pulls out the list of templates used in the document # though I have added comments # Get the database $timsSkeletonDb =& wfGetDB( DB_SLAVE ); # get the current table detials $timsSkeletonCurrentTable = $timsSkeletonDb->tableName( 'cur' ); # build the SQL for the query # Note that this used the main namespace, in the future a namespace fot Skeletons might be a safer bet $timsSkeletonSql = "SELECT cur_namespace, cur_title ". "FROM $timsSkeletonCurrentTable WHERE cur_title LIKE('Skeleton:%') AND cur_namespace=".NS_MAIN; # execute the query $timsSkeletonRes = $timsSkeletonDb->query( $timsSkeletonSql, "EditPage::editform" ); # if there was a result if ( false !== $timsSkeletonRes ) { # there was a result, are there any rows ? if ( $timsSkeletonDb->numRows( $timsSkeletonRes ) ) { # yes there are rows # initialise the Skeleton seleciton option. Note that this shoudl be # an international type message but I havent got that far $timsSkeletonList = "<H1>Skeletons list</H1><P>You can use one of these skeletons to provide the basic structure for you entry" ; # For each of the rows we got back while ( $timsSkeletonRow = $timsSkeletonDb->fetchObject( $timsSkeletonRes ) ) { # make sure that there is an associated title if ( $timsSkeletonTitleObj = Title::makeTitle( $timsSkeletonRow ->cur_namespace, $timsSkeletonRow ->cur_title ) ) { # yes there is, get the text of the title $timsSkeletonTitleName = $timsSkeletonTitleObj -> getText() ; # and make it into a link using the title as the visible name and also embedding the name of the skeleton in the link $timsSkeletonList .= ", <A HREF=\"$timsTitleEditURL&skeleton=$timsSkeletonTitleName \"> $timsSkeletonTitleName </A>"; } } # append the close paragraph to the skeletons list output $timsSkeletonList .= ".</P>" ; # Append the skeleton none option $timsSkeletonList .= "<P>If you want to remove the skeleton chose <A HREF=\"$timsTitleEditURL&skeleton=none\">none</A></P>" ; # remind people that specifying a skeleton will wipe out any existing edited text $timsSkeletonList .= "<P>Reminder, Chosing a Skeleton (or none) will overwrite any existing edit</P>" ; # and output it $wgOut->addHTML($timsSkeletonList) ; } # release the results row set $timsSkeletonDb->freeResult( $timsSkeletonRes ); } # see if there is a skeleton specified to use # get the request - need this to get the attributes in the URL global $wgRequest ; # get the value of the skeleton attribute from the URL or none is there is none $timsSkeletonName = $wgRequest->getVal( 'skeleton' , 'none') ; # Have we got the skeleton attribute ? # provided that the skeleton is not value none # try and get the text if ( 'none' == $timsSkeletonName) { # No # Just make sure that the variables are not set $timsSkeletonEnabled = false ; $timsSkeletonText = '' ; } else { # Yes, there is a skeleton attribute # set the variables to use $timsSkeletonEnabled = true ; # get tge title of the skeleton page $timsSkeletonTitle = Title::newFromText($timsSkeletonName) ; # get the associated article $timsSkeletonArticle = new Article ( $timsSkeletonTitle ) ; $timsSkeletonText = $timsSkeletonArticle->getContent( true ); $wgOut->addHTML("Using $timsSkeletonName If this is incorrect select a different skeleton above. If you don't want to use a skeleton at all select Skeleton:none") ; } # END TIMG } if( Namespace::isTalk( $this->mTitle->getNamespace() ) ) { $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' == $formtype ) { # 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() ) { $this->blockedIPpage(); return; } if ( !$wgUser->getID() && $wgWhitelistEdit ) { $this->userNotLoggedInPage(); return; } if ( wfReadOnly() ) { $wgOut->readOnlyPage(); return; } if ( $wgUser->pingLimiter() ) { $wgOut->rateLimited() ; 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 ) ) { $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; } # Article exists. Check for edit conflict. $this->mArticle->clear(); # Force reload of dates, etc. $this->mArticle->forUpdate( true ); # Lock the article if( ( $this->section != 'new' ) && ($this->mArticle->getTimestamp() != $this->edittime ) ) { $isConflict = true; } $userid = $wgUser->getID(); if ( $isConflict) { $text = $this->mArticle->getTextOfLastEditWithSectionReplacedOrAdded( $this->section, $this->textbox1, $this->summary, $this->edittime); } else { $text = $this->mArticle->getTextOfLastEditWithSectionReplacedOrAdded( $this->section, $this->textbox1, $this->summary); } # Suppress edit conflict with self 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 ( ! $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; } } } # 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(); } $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Enabled article-related sidebar, toplinks, etc. $wgOut->setArticleRelated( true ); if ( $isConflict ) { $s = wfMsg( 'editconflict', $this->mTitle->getPrefixedText() ); $wgOut->setPageTitle( $s ); $wgOut->addHTML( wfMsg( 'explainconflict' ) ); $this->textbox2 = $this->textbox1; $this->textbox1 = $this->mArticle->getContent( true ); $this->edittime = $this->mArticle->getTimestamp(); } else { if( $this->section != '' ) { if( $this->section == 'new' ) { $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])." */ "; } } } else { $s = wfMsg( 'editing', $this->mTitle->getPrefixedText() ); } $wgOut->setPageTitle( $s ); if ( !$wgUseLatin1 && !$this->checkUnicodeCompliantBrowser() ) { $this->mArticle->setOldSubtitle(); $wgOut->addWikiText( wfMsg( 'nonunicodebrowser') ); } if ( $this->oldid ) { $this->mArticle->setOldSubtitle(); $wgOut->addHTML( wfMsg( 'editingold' ) ); } } if( wfReadOnly() ) { $wgOut->addHTML( '<strong>' . wfMsg( 'readonlywarning' ) . "</strong>" ); } else if ( $isCssJsSubpage and 'preview' != $formtype) { $wgOut->addHTML( wfMsg( 'usercssjsyoucanpreview' )); } if( $this->mTitle->isProtected('edit') ) { $wgOut->addHTML( '<strong>' . wfMsg( 'protectedpagewarning' ) . "</strong><br />\n" ); } $kblength = (int)(strlen( $this->textbox1 ) / 1024); if( $kblength > 29 ) { $wgOut->addHTML( '<strong>' . wfMsg( 'longpagewarning', $wgLang->formatNum( $kblength ) ) . '</strong>' ); } $rows = $wgUser->getOption( 'rows' ); $cols = $wgUser->getOption( 'cols' ); $ew = $wgUser->getOption( 'editwidth' ); if ( $ew ) $ew = " style=\"width:100%\""; else $ew = ''; $q = 'action=submit'; #if ( "no" == $redirect ) { $q .= "&redirect=no"; } $action = $this->mTitle->escapeLocalURL( $q ); $summary = wfMsg('summary'); $subject = wfMsg('subject'); $minor = wfMsg('minoredit'); $watchthis = wfMsg ('watchthis'); $save = wfMsg('savearticle'); $prev = wfMsg('showpreview'); $cancel = $sk->makeKnownLink( $this->mTitle->getPrefixedText(), wfMsg('cancel') ); $edithelpurl = $sk->makeUrl( wfMsg( 'edithelppage' )); $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'. htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '. htmlspecialchars( wfMsg( 'newwindow' ) ); global $wgRightsText; $copywarn = "<div id=\"editpage-copywarn\">\n" . wfMsg( $wgRightsText ? 'copyrightwarning' : 'copyrightwarning2', '[[' . wfMsg( 'copyrightpage' ) . ']]', $wgRightsText ) . "\n</div>"; if( $wgUser->getOption('showtoolbar') and !$isCssJsSubpage ) { # prepare toolbar for edit buttons $toolbar = $this->getEditToolbar(); } else { $toolbar = ''; } // activate checkboxes if user wants them to be always active if( !$this->preview ) { if( $wgUser->getOption( 'watchdefault' ) ) $this->watchthis = true; if( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true; // activate checkbox also if user is already watching the page, // require wpWatchthis to be unset so that second condition is not // checked unnecessarily if( !$this->watchthis && $this->mTitle->userIsWatching() ) $this->watchthis = true; } $minoredithtml = ''; if ( 0 != $wgUser->getID() || $wgAllowAnonymousMinor ) { $minoredithtml = "<input tabindex='3' type='checkbox' value='1' name='wpMinoredit'".($this->minoredit?" checked='checked'":""). " accesskey='".wfMsg('accesskey-minoredit')."' id='wpMinoredit' />". "<label for='wpMinoredit' title='".wfMsg('tooltip-minoredit')."'>{$minor}</label>"; } $watchhtml = ''; if ( 0 != $wgUser->getID() ) { $watchhtml = "<input tabindex='4' type='checkbox' name='wpWatchthis'".($this->watchthis?" checked='checked'":""). " accesskey='".wfMsg('accesskey-watch')."' id='wpWatchthis' />". "<label for='wpWatchthis' title='".wfMsg('tooltip-watch')."'>{$watchthis}</label>"; } $checkboxhtml = $minoredithtml . $watchhtml . '<br />'; if ( 'preview' == $formtype) { $previewhead='<h2>' . wfMsg( 'preview' ) . "</h2>\n<p><center><font color=\"#cc0000\">" . wfMsg( 'note' ) . wfMsg( 'previewnote' ) . "</font></center></p>\n"; if ( $isConflict ) { $previewhead.='<h2>' . wfMsg( 'previewconflict' ) . "</h2>\n"; } $parserOptions = ParserOptions::newFromUser( $wgUser ); $parserOptions->setEditSection( false ); $parserOptions->setEditSectionOnRightClick( false ); # 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(preg_match("/\\.css$/", $wgTitle->getText() ) ) { $previewtext = wfMsg('usercsspreview'); } else if(preg_match("/\\.js$/", $wgTitle->getText() ) ) { $previewtext = wfMsg('userjspreview'); } $parserOutput = $wgParser->parse( $previewtext , $wgTitle, $parserOptions ); $wgOut->addHTML( $parserOutput->mText ); } else { $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $this->textbox1 ) ."\n\n", $wgTitle, $parserOptions ); $previewHTML = $parserOutput->mText; if($wgUser->getOption('previewontop')) { $wgOut->addHTML($previewhead); $wgOut->addHTML($previewHTML); } $wgOut->addCategoryLinks($parserOutput->getCategoryLinks()); $wgOut->addLanguageLinks($parserOutput->getLanguageLinks()); $wgOut->addHTML( "<br style=\"clear:both;\" />\n" ); } } # 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}: <input tabindex='1' type='text' value=\"$summarytext\" name=\"wpSummary\" maxlength='200' size='60' /><br />"; $editsummary = ''; } else { $commentsubject = ''; $editsummary="{$summary}: <input tabindex='2' type='text' value=\"$summarytext\" name=\"wpSummary\" maxlength='200' size='60' /><br />"; } if( !$this->preview ) { # 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 = ''; $id = $this->mTitle->getArticleID(); if ( 0 !== $id ) { $db =& wfGetDB( DB_SLAVE ); $cur = $db->tableName( 'cur' ); $links = $db->tableName( 'links' ); $sql = "SELECT cur_namespace,cur_title,cur_id ". "FROM $cur,$links WHERE l_to=cur_id AND l_from={$id} and cur_namespace=".NS_TEMPLATE; $res = $db->query( $sql, "EditPage::editform" ); if ( false !== $res ) { if ( $db->numRows( $res ) ) { $templates = '<br />'. wfMsg( 'templatesused' ) . '<ul>'; while ( $row = $db->fetchObject( $res ) ) { if ( $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title ) ) { $templates .= '<li>' . $sk->makeLinkObj( $titleObj ) . '</li>'; } } $templates .= '</ul>'; } $db->freeResult( $res ); } } # START TIMG # if there is a skeleton # make use of it if ($timsSkeletonEnabled) { $this->textbox1 = $timsSkeletonText ; } # END TIMG $wgOut->addHTML( " {$toolbar} <form id=\"editform\" name=\"editform\" method=\"post\" action=\"$action\" enctype=\"multipart/form-data\"> {$commentsubject} <textarea tabindex='1' accesskey=\",\" name=\"wpTextbox1\" rows='{$rows}' cols='{$cols}'{$ew}>" . htmlspecialchars( $wgContLang->recodeForEdit( $this->textbox1 ) ) . " </textarea> <br />{$editsummary} {$checkboxhtml} <input tabindex='5' id='wpSave' type='submit' value=\"{$save}\" name=\"wpSave\" accesskey=\"".wfMsg('accesskey-save')."\"". " title=\"".wfMsg('tooltip-save')."\"/> <input tabindex='6' id='wpPreview' type='submit' value=\"{$prev}\" name=\"wpPreview\" accesskey=\"".wfMsg('accesskey-preview')."\"". " title=\"".wfMsg('tooltip-preview')."\"/> <em>{$cancel}</em> | <em>{$edithelp}</em>{$templates}" ); $wgOut->addWikiText( $copywarn ); $wgOut->addHTML( " <input type='hidden' value=\"" . htmlspecialchars( $this->section ) . "\" name=\"wpSection\" /> <input type='hidden' value=\"{$this->edittime}\" name=\"wpEdittime\" />\n" ); if ( 0 != $wgUser->getID() ) { /** * To make it harder for someone to slip a user a page * which submits an edit form to the wiki without their * knowledge, a random token is associated with the login * session. If it's not passed back with the submission, * we won't save the page, or render user JavaScript and * CSS previews. */ $token = htmlspecialchars( $wgUser->editToken() ); $wgOut->addHTML( " <input type='hidden' value=\"$token\" name=\"wpEditToken\" />\n" ); } if ( $isConflict ) { require_once( "DifferenceEngine.php" ); $wgOut->addHTML( "<h2>" . wfMsg( "yourdiff" ) . "</h2>\n" ); DifferenceEngine::showDiff( $this->textbox2, $this->textbox1, wfMsg( "yourtext" ), wfMsg( "storedversion" ) ); $wgOut->addHTML( "<h2>" . wfMsg( "yourtext" ) . "</h2> <textarea tabindex=6 id='wpTextbox2' name=\"wpTextbox2\" rows='{$rows}' cols='{$cols}' wrap='virtual'>" . htmlspecialchars( $wgContLang->recodeForEdit( $this->textbox2 ) ) . " </textarea>" ); } $wgOut->addHTML( "</form>\n" ); if($formtype =="preview" && !$wgUser->getOption("previewontop")) { $wgOut->addHTML($previewhead); $wgOut->addHTML($previewHTML); } } /** * @todo document */ function blockedIPpage() { global $wgOut, $wgUser, $wgContLang, $wgIP; $wgOut->setPageTitle( wfMsg( 'blockedtitle' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $id = $wgUser->blockedBy(); $reason = $wgUser->blockedFor(); $ip = $wgIP; if ( is_numeric( $id ) ) { $name = User::whoIs( $id ); } else { $name = $id; } $link = '[[' . $wgContLang->getNsText( Namespace::getUser() ) . ":{$name}|{$name}]]"; $wgOut->addWikiText( wfMsg( 'blockedtext', $link, $reason, $ip, $name ) ); $wgOut->returnToMain( false ); } /** * @todo document */ function userNotLoggedInPage() { global $wgOut, $wgUser; $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $wgOut->addWikiText( wfMsg( 'whitelistedittext' ) ); $wgOut->returnToMain( false ); } /** * @todo document */ function spamPage ( $match = false ) { global $wgOut; $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $wgOut->addWikiText( wfMsg( 'spamprotectiontext' ) ); if ( $match ) { $wgOut->addWikiText( wfMsg( 'spamprotectionmatch', "<nowiki>{$match}</nowiki>" ) ); } $wgOut->returnToMain( false ); } /** * Forks processes to scan the originating IP for an open proxy server * MemCached can be used to skip IPs that have already been scanned */ 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; } } # 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 ); } } } /** * @access private * @todo document */ function mergeChangesInto( &$text ){ $fname = 'EditPage::mergeChangesInto'; $oldDate = $this->edittime; $dbw =& wfGetDB( DB_MASTER ); $obj = $dbw->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mTitle->getArticleID() ), $fname, 'FOR UPDATE' ); $yourtext = $obj->cur_text; $ns = $this->mTitle->getNamespace(); $title = $this->mTitle->getDBkey(); $obj = $dbw->selectRow( 'old', array( 'old_text','old_flags'), array( 'old_namespace' => $ns, 'old_title' => $title, 'old_timestamp' => $dbw->timestamp($oldDate)), $fname ); $oldText = Article::getRevisionText( $obj ); if(wfMerge($oldText, $text, $yourtext, $result)){ $text = $result; return true; } else { return false; } } function checkUnicodeCompliantBrowser() { global $wgBrowserBlackList; $currentbrowser = $_SERVER["HTTP_USER_AGENT"]; foreach ( $wgBrowserBlackList as $browser ) { if ( preg_match($browser, $currentbrowser) ) { return false; } } return true; } /** * Format an anchor fragment as it would appear for a given section name * @param string $text * @return string * @access private */ function sectionAnchor( $text ) { global $wgInputEncoding; $headline = do_html_entity_decode( $text, ENT_COMPAT, $wgInputEncoding ); # strip out HTML $headline = preg_replace( '/<.*?' . '>/', '', $headline ); $headline = trim( $headline ); $sectionanchor = '#' . urlencode( str_replace( ' ', '_', $headline ) ); $replacearray = array( '%3A' => ':', '%' => '.' ); return str_replace( array_keys( $replacearray ), array_values( $replacearray ), $sectionanchor ); } /** * Shows a bulletin board style toolbar for common editing functions. * It can be disabled in the user preferences. * The necessary JavaScript code can be found in style/wikibits.js. */ function getEditToolbar() { global $wgStylePath, $wgLang, $wgMimeType; /** * toolarray an array of arrays which each include the filename of * the button image (without path), the opening tag, the closing tag, * and optionally a sample text that is inserted between the two when no * selection is highlighted. * The tip text is shown when the user moves the mouse over the button. * * Already here are accesskeys (key), which are not used yet until someone * 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' => '[['.$wgLang->getNsText(NS_IMAGE).":", 'close' => ']]', 'sample'=> wfMsg('image_sample'), 'tip' => wfMsg('image_tip'), 'key' => 'D' ), array( 'image' => 'button_media.png', 'open' => '[['.$wgLang->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' ) ); $toolbar ="<script type='text/javascript'>\n/*<![CDATA[*/\n"; $toolbar.="document.writeln(\"<div id='toolbar'>\");\n"; foreach($toolarray as $tool) { $image=$wgStylePath.'/common/images/'.$tool['image']; $open=$tool['open']; $close=$tool['close']; $sample = addslashes( $tool['sample'] ); // Note that we use the tip both for the ALT tag and the TITLE tag of the image. // Older browsers show a "speedtip" type message only for ALT. // Ideally these should be different, realistically they // probably don't need to be. $tip = addslashes( $tool['tip'] ); #$key = $tool["key"]; $toolbar.="addButton('$image','$tip','$open','$close','$sample');\n"; } $toolbar.="addInfobox('" . addslashes( wfMsg( "infobox" ) ) . "','" . addslashes(wfMsg("infobox_alert")) . "');\n"; $toolbar.="document.writeln(\"</div>\");\n"; $toolbar.="/*]]>*/\n</script>"; return $toolbar; } } ?>
Created attachment 811 [details] Modified version of EditPage.php Now I find how to add an attachment ! This is a modified version of the EditPage.php file with support for skeletons as described in the RFE
Any chance this could be updated for version 1.5.4? this would be very useful for us, as well.
Let's have it as a unified diff, please, not the whole file.
Created attachment 1287 [details] Unified diff of the skeleton code form the 1.4.5 EditPage.php This is the unified diff of the origional 1.4.5 EditPage.php and the version I modified to handle skeletons. Note that this stores the skeletons in the main namespace with the prefix "Skeleton:" (I didn't have the time to figure out how to create a new namespace) I suspect that a separate namespace for the skeletons may be more sensible for a formal release
I don't like to shoot this down but [a] A diff against 1.4.x isn't a huge amount of help to putting this in 1.5 branch, or in HEAD. [b] Wouldn't it make more sense to use the template namespace? I'll put my money where my mouth is at some point and have a go at implementing this myself, however, since it's one of those little ideas I quite like the sound of, on principle of it being geared towards helping people edit (and that's what wikis are for).
I'm sorry for the 1,4 based diff, I don't have any 1.5 based Wiki's up and running at the moment. I'll prob be updating one of my wikis to 1.5 in a few weeks so I'll update the code then and provide updated diffs I think that the skeletons should prob have a separate namespace (though I don't know how to create it), I'm not sure if the template one is appropriate as they perform different functions. The skeletons are outline text that can be used as the basis of a page, subsequent changes to the skeleton does not effect existing pages built on the skeleton. The templates are different in thet they are reused every time, a change to the skeleton will show up everywhere the template is used. There is also a terminology issue, the skeletons are actually similar to templates in a word processor whereas I think templates in media wiki are almost like macros in C
Actually, we need it against CVS HEAD. ;-) As for the namespace use, I disagree. Templates are templates, whether we're transcluding them or dumping their text into an edit box. OTOH, having a separate namespace makes sense insofar as keeping proper boilerplate text separate from transcludables (macros, as you call them) in any drop-down list, which is the way I quite fancied going... Any other thoughts on this?
A separate namespace seems cleaner, since more items in a list cause more confusion. And, in the case of a user's choosing the wrong template, could result in what a user may perceive as unexpected behavior. for our use, we don't need to replace all article text, so the box could instead insert boilerplate text for any template as a cut-and-paste operation. This could include both skeletons and more traditional wiki-templates (e.g., references, footnotes, etc), similar to the way the toolbar buttons insert apostrophes for bold, etc.
Created attachment 1322 [details] EditPage.php - patch
Hello, this discussion is very interesting, and I have the same need as well. I thought of approaching the same need from a different angle, thoguh. instead of providing a list of skeleton to pick from in the edit page, I thought I would just add a parameter to the edit action which specifies a page that would be preloaded into the text box on page creation. This feature would be hidden from regular edit actions, but would provide extension writers with the capability to write an extension that generates simple forms for new page creation. I created a patch (see above) with with the changes I was thinking about. Using the patched version, you could use the following extension: <?php $wgExtensionFunctions[] = "wfEditPageExtension"; #-----------------------------------------------# # Purpose : Declare parser extensions. function wfEditPageExtension() { global $wgParser; $wgParser->setHook( "EditPage", "editPageHook" ); } #-----------------------------------------------# # Purpose : Edit a new page with input from # a template or skeleton # # The aim of this extension is to create a new wiki page # that is structured according to an existing template or # skeletion. It outputs a form with a single text box and # a submit button. Click the button to edit the page specified # in the text box. If the page is a new page, the edit page # will be loaded with the specified template. Any text # specified within the <EditPage> element will appear right # before the text box. # # <EditPage> element attributes: # * template - the title of the page to be used as the new page template # * buttonlabel (optional) - the label on the Submit button (default is "Edit") # # Example: # # <EditPage template="Template:Form" buttonlabel="Edit">Edit this Form:</EditPage> function editPageHook( $input, $args ) { global $wgScript; $action = htmlspecialchars( $wgScript ); $template = $args["template"]; $buttonlabel = $args["buttonlabel"] ? $args["buttonlabel"] : "Edit"; $editform=<<<ENDFORM <form name="editbox" action="$action" method="get" class="editbox"> <input type='hidden' name="action" value="edit"> $input <input class="editboxInput" name="title" type="text" /> <input type="hidden" name="loadFromPage" value="$template" /> <button type='submit' class="editboxButton">$buttonlabel</button> </form> ENDFORM; return $editform; } ?>
The relatively recent preload parameter is similar; it inserts the content of a template into a page. You can use the editintro parameter to provide a list of templates, using javascript links to a function changing 'editbox.value'. For more information, see [[mw:Manual:Parameters to index.php#Edit_and_submit]]. If you want a list that appears on any new page (not just with specific URLs), a JavaScript tool would be best. For example, [[m:User:Pathoschild/Script:TemplateScript]] could be slightly modified to only show up when the editbox is empty on load.
I shudder to think what the code in the first patch does, this is better implemented by extensions, of which there are already many: http://www.mediawiki.org/wiki/Extension:Preloader and http://www.mediawiki.org/wiki/Extension:InputBox seem to link to most of them. The second patch has been obsoleted by the ?preload= parameter many years ago. If there are still specific pieces of functionality missing, please open new bugs (after checking through the similar ones)
*** Bug 4670 has been marked as a duplicate of this bug. ***
*** Bug 25348 has been marked as a duplicate of this bug. ***
I looked through those extensions after my bug 25348 was marked a duplicate with this one but still haven't found a solution. All those extensions seem to do is that they provide preloaded per-namespace texts. However, I'd like to have multiple choices in the main namespace. You know, more options into this box, dynamically, without having to edit any .php files: http://help.wikia.com/wiki/File:Createpagebox.jpg
(In reply to comment #15) > I'd like to have multiple choices in the main namespace. These extensions do that with different features/design limitations. https://www.mediawiki.org/wiki/Extension:MultiBoilerplate https://www.mediawiki.org/wiki/Extension:BoilerRoom https://www.mediawiki.org/wiki/Extension:BoilerplateSelection https://www.mediawiki.org/wiki/Extension:MultiBoilerplate The last one is, by far, the most promising IMO as it implements most of the logic in JavaScript. With a bit of development, those extensions could be converted into a JavaScript-only solution, which any user can write and use on Wikipedia (and any mediawiki), requiring no extensions to be installed. Once tested, it can be made into a gadget for easy installation by all users on the wiki.