Last modified: 2013-11-11 09:40:10 UTC

Wikimedia Bugzilla is closed!

Wikimedia migrated from Bugzilla to Phabricator. Bug reports are handled in Wikimedia Phabricator.
This static website is read-only and for historical purposes. It is not possible to log in and except for displaying bug reports and their history, links might be broken. See T5201, the corresponding Phabricator task for complete and up-to-date bug report information.
Bug 3201 - Provision of article skeleton
Provision of article skeleton
Status: RESOLVED WONTFIX
Product: MediaWiki
Classification: Unclassified
Page editing (Other open bugs)
1.4.x
All All
: Lowest enhancement with 4 votes (vote)
: ---
Assigned To: Nobody - You can work on this!
https://www.mediawiki.org/wiki/Manual...
:
: 4670 25348 (view as bug list)
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2005-08-19 15:24 UTC by Tim Graves
Modified: 2013-11-11 09:40 UTC (History)
5 users (show)

See Also:
Web browser: ---
Mobile Platform: ---
Assignee Huggle Beta Tester: ---


Attachments
Modified version of EditPage.php (31.07 KB, text/plain)
2005-08-19 15:26 UTC, Tim Graves
Details
Unified diff of the skeleton code form the 1.4.5 EditPage.php (4.86 KB, text/plain)
2006-01-11 15:40 UTC, Tim Graves
Details
EditPage.php - patch (1.43 KB, patch)
2006-01-23 17:08 UTC, Mikos
Details

Description Tim Graves 2005-08-19 15:24:28 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;
	}

}

?>
Comment 1 Tim Graves 2005-08-19 15:26:22 UTC
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
Comment 2 jacki buros 2006-01-04 18:39:58 UTC
Any chance this could be updated for version 1.5.4?

this would be very useful for us, as well.
Comment 3 Rob Church 2006-01-04 20:08:07 UTC
Let's have it as a unified diff, please, not the whole file.
Comment 4 Tim Graves 2006-01-11 15:40:59 UTC
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
Comment 5 Rob Church 2006-01-11 19:50:35 UTC
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).
Comment 6 Tim Graves 2006-01-11 23:28:03 UTC
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
Comment 7 Rob Church 2006-01-12 13:41:21 UTC
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?
Comment 8 jacki buros 2006-01-19 02:18:36 UTC
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.
Comment 9 Mikos 2006-01-23 17:08:24 UTC
Created attachment 1322 [details]
EditPage.php - patch
Comment 10 Mikos 2006-01-23 17:12:33 UTC
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;
	
}

?>


Comment 11 Jesse (Pathoschild) 2007-06-27 03:36:28 UTC
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.
Comment 12 Conrad Irwin 2010-02-17 23:33:26 UTC
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)
Comment 13 Chad H. 2010-02-23 20:15:22 UTC
*** Bug 4670 has been marked as a duplicate of this bug. ***
Comment 14 Chad H. 2010-09-28 16:04:29 UTC
*** Bug 25348 has been marked as a duplicate of this bug. ***
Comment 15 Ville Saalo 2010-09-29 22:14:59 UTC
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
Comment 16 John Mark Vandenberg 2013-11-11 09:40:10 UTC
(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.

Note You need to log in before you can comment on or make changes to this bug.


Navigation
Links