Page MenuHomePhabricator

Provision of article skeleton
Closed, DeclinedPublic

Assigned To
None
Authored By
bzimport
Aug 19 2005, 3:24 PM
Referenced Files
F2247: EditPage.diff
Nov 21 2014, 8:46 PM
F2246: EditPahe.php.udiff
Nov 21 2014, 8:46 PM
F2245: EditPage.php
Nov 21 2014, 8:46 PM

Description

Author: atimgraves

Description:
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;

  1. 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() ));

  1. START TIMG
  2. 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

  1. though I have added comments
  2. 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;

  1. execute the query
			$timsSkeletonRes = $timsSkeletonDb->query( $timsSkeletonSql,

"EditPage::editform" );

  1. 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" ;

  1. 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 ) ) {

  1. 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>" ;

  1. 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>" ;

  1. 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 ) ) {

  1. 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

  1. XXX: might be better to integrate this into

Article::getTextOfLastEditWithSectionReplacedOrAdded

  1. for duplicate heading checking and maybe parsing
					$hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,

$matches );

  1. we can't deal with anchors, includes, html etc in the header for now,
  2. 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.

  1. 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;

}

}

?>


Version: 1.4.x
Severity: enhancement
URL: https://www.mediawiki.org/wiki/Manual:Creating_pages_with_preloaded_text

Details

Reference
bz3201

Event Timeline

bzimport raised the priority of this task from to Lowest.Nov 21 2014, 8:46 PM
bzimport set Reference to bz3201.
bzimport added a subscriber: Unknown Object (MLST).

atimgraves wrote:

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

Attached:

jburos wrote:

Any chance this could be updated for version 1.5.4?

this would be very useful for us, as well.

robchur wrote:

Let's have it as a unified diff, please, not the whole file.

atimgraves wrote:

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

Attached:

robchur wrote:

I don't like to shoot this down but

  • A diff against 1.4.x isn't a huge amount of help to putting this in 1.5

branch, or in HEAD.

  • 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).

atimgraves wrote:

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

robchur wrote:

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?

jburos wrote:

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.

mikiher wrote:

EditPage.php - patch

Attached:

mikiher wrote:

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";

  1. Purpose : Declare parser extensions.

function wfEditPageExtension() {

global $wgParser;
$wgParser->setHook( "EditPage", "editPageHook" );

}

  1. Purpose : Edit a new page with input from
  2. a template or skeleton #
  3. The aim of this extension is to create a new wiki page
  4. that is structured according to an existing template or
  5. skeletion. It outputs a form with a single text box and
  6. a submit button. Click the button to edit the page specified
  7. in the text box. If the page is a new page, the edit page
  8. will be loaded with the specified template. Any text
  9. specified within the <EditPage> element will appear right
  10. before the text box. #
  11. <EditPage> element attributes:
  12. * template - the title of the page to be used as the new page template
  13. * buttonlabel (optional) - the label on the Submit button (default is "Edit")
  14. Example: #
  15. <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.

conrad.irwin wrote:

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. ***

villes01 wrote:

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.