DynamicPageList3/includes/Lister/Lister.php
2023-01-13 14:12:14 -07:00

1305 lines
36 KiB
PHP

<?php
namespace MediaWiki\Extension\DynamicPageList3\Lister;
use MediaWiki\Extension\DynamicPageList3\Article;
use MediaWiki\Extension\DynamicPageList3\LST;
use MediaWiki\Extension\DynamicPageList3\Parameters;
use MediaWiki\Extension\DynamicPageList3\UpdateArticle;
use MediaWiki\MediaWikiServices;
use Parser;
use RequestContext;
use Sanitizer;
use Title;
class Lister {
public const LIST_DEFINITION = 1;
public const LIST_GALLERY = 2;
public const LIST_HEADING = 3;
public const LIST_INLINE = 4;
public const LIST_ORDERED = 5;
public const LIST_UNORDERED = 6;
public const LIST_CATEGORY = 7;
public const LIST_USERFORMAT = 8;
/**
* Listing style for this class.
*
* @var int|null
*/
public $style = null;
/**
* Heading List Start
* Use %s for attribute placement. Example: <div%s>
*
* @var string
*/
public $headListStart = '';
/**
* Heading List End
*
* @var string
*/
public $headListEnd = '';
/**
* Heading List Start
* Use %s for attribute placement. Example: <div%s>
*
* @var string
*/
public $headItemStart = '';
/**
* Heading List End
*
* @var string
*/
public $headItemEnd = '';
/**
* List(Section) Start
* Use %s for attribute placement. Example: <div%s>
*
* @var string
*/
public $listStart = '';
/**
* List(Section) End
*
* @var string
*/
public $listEnd = '';
/**
* Item Start
* Use %s for attribute placement. Example: <div%s>
*
* @var string
*/
public $itemStart = '';
/**
* Item End
*
* @var string
*/
public $itemEnd = '';
/**
* Extra head list HTML attributes.
*
* @var string
*/
public $headListAttributes = '';
/**
* Extra head item HTML attributes.
*
* @var string
*/
public $headItemAttributes = '';
/**
* Extra list HTML attributes.
*
* @var string
*/
public $listAttributes = '';
/**
* Extra item HTML attributes.
*
* @var string
*/
public $itemAttributes = '';
/**
* Count tipping point to mark a section as dominant.
*
* @var int
*/
protected $dominantSectionCount = -1;
/**
* Template Suffix
*
* @var string
*/
protected $templateSuffix = '';
/**
* Trim included wiki text.
*
* @var bool
*/
protected $trimIncluded = false;
/**
* Trim included wiki text.
*
* @var bool
*/
protected $escapeLinks = true;
/**
* Index of the table column to sort by.
*
* @var int|null
*/
protected $tableSortColumn = null;
/**
* @var string|null
*/
protected $tableSortMethod = null;
/**
* Maximum title length.
*
* @var int|null
*/
protected $titleMaxLength = null;
/**
* Section separators that separate transcluded pages/sections of wiki text.
*
* @var array
*/
protected $sectionSeparators = [];
/**
* Section separators that separate transcluded pages/sections that refer to the same chapter or tempalte of wiki text.
*
* @var array
*/
protected $multiSectionSeparators = [];
/**
* Include page text in output.
*
* @var bool
*/
protected $includePageText = false;
/**
* Maximum length before truncated included wiki text.
*
* @var int|null
*/
protected $includePageMaxLength = null;
/**
* Array of plain text matches for page transclusion. (include)
*
* @var array
*/
protected $pageTextMatch;
/**
* Array of regex text matches for page transclusion. (includematch)
*
* @var array
*/
protected $pageTextMatchRegex;
/**
* Array of not regex text matches for page transclusion. (includenotmatch)
*
* @var array
*/
protected $pageTextMatchNotRegex;
/**
* Parsed wiki text into HTML before running include/includematch/includenotmatch.
*
* @var bool
*/
protected $includePageParsed = false;
/**
* Total result count after parsing, transcluding, and such.
*
* @var int
*/
public $rowCount = 0;
/**
* Parameters
*
* @var Parameters
*/
protected $parameters;
/**
* Parser
*
* @var Parser
*/
protected $parser;
/**
* @param Parameters $parameters
* @param Parser $parser
*/
public function __construct( Parameters $parameters, Parser $parser ) {
$this->setHeadListAttributes( $parameters->getParameter( 'hlistattr' ) ?? '' );
$this->setHeadItemAttributes( $parameters->getParameter( 'hitemattr' ) ?? '' );
$this->setListAttributes( $parameters->getParameter( 'listattr' ) ?? '' );
$this->setItemAttributes( $parameters->getParameter( 'itemattr' ) ?? '' );
$this->setDominantSectionCount( $parameters->getParameter( 'dominantsection' ) );
$this->setTemplateSuffix( $parameters->getParameter( 'defaulttemplatesuffix' ) );
$this->setTrimIncluded( $parameters->getParameter( 'includetrim' ) );
$this->setTableSortColumn( $parameters->getParameter( 'tablesortcol' ) );
$this->setTableSortMethod( $parameters->getParameter( 'tablesortmethod' ) );
$this->setTitleMaxLength( $parameters->getParameter( 'titlemaxlen' ) );
$this->setEscapeLinks( $parameters->getParameter( 'escapelinks' ) );
$this->setSectionSeparators( $parameters->getParameter( 'secseparators' ) );
$this->setMultiSectionSeparators( $parameters->getParameter( 'multisecseparators' ) );
$this->setIncludePageText( $parameters->getParameter( 'incpage' ) );
$this->setIncludePageMaxLength( $parameters->getParameter( 'includemaxlen' ) );
$this->setPageTextMatch( (array)$parameters->getParameter( 'seclabels' ) );
$this->setPageTextMatchRegex( (array)$parameters->getParameter( 'seclabelsmatch' ) );
$this->setPageTextMatchNotRegex( (array)$parameters->getParameter( 'seclabelsnotmatch' ) );
$this->setIncludePageParsed( $parameters->getParameter( 'incparsed' ) );
$this->parameters = $parameters;
$this->parser = clone $parser;
}
/**
* Get a new List subclass based on user selection.
*
* @param string $style
* @param Parameters $parameters
* @param Parser $parser
* @return mixed
*/
public static function newFromStyle( $style, Parameters $parameters, Parser $parser ) {
$style = strtolower( $style );
switch ( $style ) {
case 'category':
$class = CategoryList::class;
break;
case 'definition':
$class = DefinitionList::class;
break;
case 'gallery':
$class = GalleryList::class;
break;
case 'inline':
$class = InlineList::class;
break;
case 'ordered':
$class = OrderedList::class;
break;
case 'subpage':
$class = SubPageList::class;
break;
default:
case 'unordered':
$class = UnorderedList::class;
break;
case 'userformat':
$class = UserFormatList::class;
break;
}
return new $class( $parameters, $parser );
}
/**
* Get the Parameters object this object was constructed with.
*
* @return Parameters
*/
public function getParameters() {
return $this->parameters;
}
/**
* Set extra list attributes for header wraps.
*
* @param string $attributes
*/
public function setHeadListAttributes( $attributes ) {
$this->headListAttributes = Sanitizer::fixTagAttributes( $attributes, 'ul' );
}
/**
* Set extra item attributes for header items.
*
* @param string $attributes
*/
public function setHeadItemAttributes( $attributes ) {
$this->headItemAttributes = Sanitizer::fixTagAttributes( $attributes, 'li' );
}
/**
* Set extra list attributes.
*
* @param string $attributes
*/
public function setListAttributes( $attributes ) {
$this->listAttributes = Sanitizer::fixTagAttributes( $attributes, 'ul' );
}
/**
* Set extra item attributes.
*
* @param string $attributes
*/
public function setItemAttributes( $attributes ) {
$this->itemAttributes = Sanitizer::fixTagAttributes( $attributes, 'li' );
}
/**
* Set the count of items to trigger a section as dominant.
*
* @param int $count
*/
public function setDominantSectionCount( $count = -1 ) {
$this->dominantSectionCount = intval( $count );
}
/**
* Get the count of items to trigger a section as dominant.
*
* @return int
*/
public function getDominantSectionCount() {
return $this->dominantSectionCount;
}
/**
* Return the list style.
*
* @return int
*/
public function getStyle() {
return $this->style;
}
/**
* Set the template suffix for whatever the hell uses it.
*
* @param string $suffix
*/
public function setTemplateSuffix( $suffix = '.default' ) {
$this->templateSuffix = $suffix;
}
/**
* Get the template suffix for whatever the hell uses it.
*
* @return string
*/
public function getTemplateSuffix() {
return $this->templateSuffix;
}
/**
* Set if included wiki text should be trimmed.
*
* @param bool $trim
*/
public function setTrimIncluded( $trim = false ) {
$this->trimIncluded = boolval( $trim );
}
/**
* Get if included wiki text should be trimmed.
*
* @return bool
*/
public function getTrimIncluded() {
return $this->trimIncluded;
}
/**
* Set if links should be escaped?
* @todo The naming of this parameter is weird and I am not sure what it does.
*
* @param bool $escape
*/
public function setEscapeLinks( $escape = true ) {
$this->escapeLinks = boolval( $escape );
}
/**
* Get if links should be escaped.
*
* @return bool
*/
public function getEscapeLinks() {
return $this->escapeLinks;
}
/**
* Set the index of the table column to sort by.
*
* @param int|null $index
*/
public function setTableSortColumn( $index = null ) {
$this->tableSortColumn = $index === null ? null : intval( $index );
}
/**
* Get the index of the table column to sort by.
*
* @return int|null
*/
public function getTableSortColumn() {
return $this->tableSortColumn;
}
/**
* Set the algorithm for table sorting
*
* @param string|null $method
*/
public function setTableSortMethod( $method = null ) {
$this->tableSortMethod = $method === null ? 'standard' : $method;
}
/**
* Get the algorithm for table sorting
*
* @return string
*/
public function getTableSortMethod() {
return $this->tableSortMethod;
}
/**
* Set the maximum title length for display.
*
* @param int|null $length
*/
public function setTitleMaxLength( $length = null ) {
$this->titleMaxLength = $length === null ? null : intval( $length );
}
/**
* Get the maximum title length for display.
*
* @return int|null
*/
public function getTitleMaxLength() {
return $this->titleMaxLength;
}
/**
* Set the separators that separate sections of matched page text.
*
* @param ?array $separators
*/
public function setSectionSeparators( ?array $separators ) {
$this->sectionSeparators = $separators ?? [];
}
/**
* Set the separators that separate related sections of matched page text.
*
* @param ?array $separators
*/
public function setMultiSectionSeparators( ?array $separators ) {
$this->multiSectionSeparators = $separators ?? [];
}
/**
* Set if wiki text should be included in output.
*
* @param bool $include
*/
public function setIncludePageText( $include = false ) {
$this->includePageText = boolval( $include );
}
/**
* Set the maximum included page text length before truncating.
*
* @param int|null $length
*/
public function setIncludePageMaxLength( $length = null ) {
$this->includePageMaxLength = $length === null ? null : intval( $length );
}
/**
* Set the plain string text matching for page transclusion.
*
* @param array $pageTextMatch
*/
public function setPageTextMatch( array $pageTextMatch = [] ) {
$this->pageTextMatch = $pageTextMatch;
}
/**
* Set the regex text matching for page transclusion.
*
* @param array $pageTextMatchRegex
*/
public function setPageTextMatchRegex( array $pageTextMatchRegex = [] ) {
$this->pageTextMatchRegex = $pageTextMatchRegex;
}
/**
* Set the not regex text matching for page transclusion.
*
* @param array $pageTextMatchNotRegex
*/
public function setPageTextMatchNotRegex( array $pageTextMatchNotRegex = [] ) {
$this->pageTextMatchNotRegex = $pageTextMatchNotRegex;
}
/**
* Set if included wiki text should be parsed before being matched against.
*
* @param bool $parse
*/
public function setIncludePageParsed( $parse = false ) {
$this->includePageParsed = boolval( $parse );
}
/**
* Shortcut to format all articles into a single formatted list.
*
* @param array $articles
* @return string
*/
public function format( $articles ) {
return $this->formatList( $articles, 0, count( $articles ) );
}
/**
* Format a list of articles into a singular list.
*
* @param array $articles
* @param int $start
* @param int $count
* @return string
*/
public function formatList( $articles, $start, $count ) {
$filteredCount = 0;
$items = [];
for ( $i = $start; $i < $start + $count; $i++ ) {
$article = $articles[$i];
if ( empty( $article ) || empty( $article->mTitle ) ) {
continue;
}
$pageText = null;
if ( $this->includePageText ) {
$pageText = $this->transcludePage( $article, $filteredCount );
} else {
$filteredCount++;
}
$this->rowCount = $filteredCount;
$items[] = $this->formatItem( $article, $pageText );
}
$this->rowCount = $filteredCount;
return $this->getListStart() . $this->implodeItems( $items ) . $this->listEnd;
}
/**
* Format a single item.
*
* @param Article $article
* @param string|null $pageText
* @return string
*/
public function formatItem( Article $article, $pageText = null ) {
$lang = RequestContext::getMain()->getLanguage();
$item = '';
$date = $article->getDate();
if ( $date ) {
$item .= $date . ' ';
if ( $article->mRevision > 0 ) {
$item .= '[{{fullurl:' . $article->mTitle . '|oldid=' . $article->mRevision . '}} ' . htmlspecialchars( $article->mTitle ) . ']';
} else {
$item .= $article->mLink;
}
} else {
// output the link to the article
$item .= $article->mLink;
}
if ( $article->mSize > 0 ) {
$byte = 'B';
$pageLength = $lang->formatNum( $article->mSize );
$item .= " [{$pageLength} {$byte}]";
}
if ( $article->mCounter > 0 ) {
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
$item .= ' ' . $contLang->getDirMark() . '(' . wfMessage( 'hitcounters-nviews', $lang->formatNum( $article->mCounter ) )->escaped() . ')';
}
if ( $article->mUserLink ) {
$item .= ' . . [[User:' . $article->mUser . '|' . $article->mUser . ']]';
if ( $article->mComment != '' ) {
$item .= ' { ' . $article->mComment . ' }';
}
}
if ( $article->mContributor ) {
$item .= ' . . [[User:' . $article->mContributor . '|' . $article->mContributor . " $article->mContrib]]";
}
if ( !empty( $article->mCategoryLinks ) ) {
$item .= ' . . <small>' . wfMessage( 'categories' ) . ': ' . implode( ' | ', $article->mCategoryLinks ) . '</small>';
}
if ( $this->getParameters()->getParameter( 'addexternallink' ) && $article->mExternalLink ) {
$item .= ' → ' . $article->mExternalLink;
}
if ( $pageText !== null ) {
// Include parsed/processed wiki markup content after each item before the closing tag.
$item .= $pageText;
}
$item = $this->getItemStart() . $item . $this->getItemEnd();
$item = $this->replaceTagParameters( $item, $article );
return $item;
}
/**
* Return $this->headListStart with attributes replaced.
*
* @return string
*/
public function getHeadListStart() {
return sprintf( $this->headListStart, $this->headListAttributes );
}
/**
* Return $this->headItemStart with attributes replaced.
*
* @return string
*/
public function getHeadItemStart() {
return sprintf( $this->headItemStart, $this->headItemAttributes );
}
/**
* Return $this->headItemStart with attributes replaced.
*
* @return string
*/
public function getHeadItemEnd() {
return $this->headItemEnd;
}
/**
* Return $this->listStart with attributes replaced.
*
* @return string
*/
public function getListStart() {
return sprintf( $this->listStart, $this->listAttributes );
}
/**
* Return $this->itemStart with attributes replaced.
*
* @return string
*/
public function getItemStart() {
return sprintf( $this->itemStart, $this->itemAttributes );
}
/**
* Return $this->itemEnd with attributes replaced.
*
* @return string
*/
public function getItemEnd() {
return $this->itemEnd;
}
/**
* Join together items after being processed by formatItem().
*
* @param array $items
* @return string
*/
protected function implodeItems( $items ) {
return implode( '', $items );
}
/**
* Replace user tag parameters.
*
* @param string $tag
* @param Article $article
* @return string
*/
protected function replaceTagParameters( $tag, Article $article ) {
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
$namespaces = $contLang->getNamespaces();
if ( strpos( $tag, '%' ) === false ) {
return $tag;
}
$imageUrl = $this->parseImageUrlWithPath( $article );
$pagename = $article->mTitle->getPrefixedText();
if ( $this->getEscapeLinks() && ( $article->mNamespace == NS_CATEGORY || $article->mNamespace == NS_FILE ) ) {
// links to categories or images need an additional ":"
$pagename = ':' . $pagename;
}
$tag = str_replace( '%PAGE%', $pagename, $tag );
$tag = str_replace( '%PAGEID%', (string)$article->mID, $tag );
$tag = str_replace( '%NAMESPACE%', $namespaces[$article->mNamespace], $tag );
$tag = str_replace( '%IMAGE%', $imageUrl, $tag );
$tag = str_replace( '%EXTERNALLINK%', $article->mExternalLink, $tag );
$tag = str_replace( '%EDITSUMMARY%', $article->mComment, $tag );
$title = $article->mTitle->getText();
$replaceInTitle = $this->getParameters()->getParameter( 'replaceintitle' );
if ( is_array( $replaceInTitle ) && count( $replaceInTitle ) === 2 ) {
$title = preg_replace( $replaceInTitle[0], $replaceInTitle[1], $title );
}
$titleMaxLength = $this->getTitleMaxLength();
if ( $titleMaxLength !== null && ( strlen( $title ) > $titleMaxLength ) ) {
$title = substr( $title, 0, $titleMaxLength ) . '...';
}
$tag = str_replace( '%TITLE%', $title, $tag );
$tag = str_replace( '%COUNT%', (string)$article->mCounter, $tag );
$tag = str_replace( '%COUNTFS%', (string)( floor( log( $article->mCounter ) * 0.7 ) ), $tag );
$tag = str_replace( '%COUNTFS2%', (string)( floor( sqrt( log( $article->mCounter ) ) ) ), $tag );
$tag = str_replace( '%SIZE%', (string)$article->mSize, $tag );
$tag = str_replace( '%SIZEFS%', (string)( floor( sqrt( log( $article->mSize ) ) * 2.5 - 5 ) ), $tag );
$tag = str_replace( '%DATE%', $article->getDate(), $tag );
$tag = str_replace( '%REVISION%', (string)$article->mRevision, $tag );
$tag = str_replace( '%CONTRIBUTION%', (string)$article->mContribution, $tag );
$tag = str_replace( '%CONTRIB%', $article->mContrib, $tag );
$tag = str_replace( '%CONTRIBUTOR%', $article->mContributor, $tag );
$tag = str_replace( '%USER%', $article->mUser, $tag );
if ( $article->mSelTitle ) {
if ( $article->mSelNamespace == 0 ) {
$tag = str_replace( '%PAGESEL%', str_replace( '_', ' ', $article->mSelTitle ), $tag );
} else {
$tag = str_replace( '%PAGESEL%', $namespaces[$article->mSelNamespace] . ':' . str_replace( '_', ' ', $article->mSelTitle ), $tag );
}
}
$tag = str_replace( '%IMAGESEL%', str_replace( '_', ' ', $article->mImageSelTitle ), $tag );
$tag = $this->replaceTagCategory( $tag, $article );
return $tag;
}
/**
* Replace user tag parameters for categories.
*
* @param string $tag
* @param Article $article
* @return string
*/
protected function replaceTagCategory( $tag, Article $article ) {
if ( !empty( $article->mCategoryLinks ) ) {
$tag = str_replace( '%CATLIST%', implode( ', ', $article->mCategoryLinks ), $tag );
$tag = str_replace( '%CATBULLETS%', '* ' . implode( "\n* ", $article->mCategoryLinks ), $tag );
$tag = str_replace( '%CATNAMES%', implode( ', ', $article->mCategoryTexts ), $tag );
} else {
$tag = str_replace( '%CATLIST%', '', $tag );
$tag = str_replace( '%CATBULLETS%', '', $tag );
$tag = str_replace( '%CATNAMES%', '', $tag );
}
return $tag;
}
/**
* Replace the %NR%(current article sequence number) in text.
*
* @param string $tag
* @param int $nr
* @return string
*/
protected function replaceTagCount( $tag, $nr ) {
return str_replace( '%NR%', (string)$nr, $tag );
}
/**
* Format one single item of an entry in the output list (i.e. one occurence of one item from the include parameter).
*
* @param array &$pieces
* @param mixed $s Index of the table row position.
* @param Article $article
*/
private function replaceTagTableRow( &$pieces, $s, Article $article ) {
$tableFormat = $this->getParameters()->getParameter( 'tablerow' );
$firstCall = true;
foreach ( $pieces as $key => $val ) {
if ( isset( $tableFormat[$s] ) ) {
if ( $s == 0 || $firstCall ) {
$pieces[$key] = str_replace( '%%', $val, $tableFormat[$s] );
} else {
$n = strpos( $tableFormat[$s], '|' );
if ( $n === false || !( strpos( substr( $tableFormat[$s], 0, $n ), '{' ) === false ) || !( strpos( substr( $tableFormat[$s], 0, $n ), '[' ) === false ) ) {
$pieces[$key] = str_replace( '%%', $val, $tableFormat[$s] );
} else {
$pieces[$key] = str_replace( '%%', $val, substr( $tableFormat[$s], $n + 1 ) );
}
}
$pieces[$key] = str_replace( '%IMAGE%', $this->parseImageUrlWithPath( $val ), $pieces[$key] );
$pieces[$key] = str_replace( '%PAGE%', $article->mTitle->getPrefixedText(), $pieces[$key] );
$pieces[$key] = $this->replaceTagCategory( $pieces[$key], $article );
}
$firstCall = false;
}
}
/**
* Format one single template argument of one occurence of one item from the include parameter. This is called via a backlink from LST::includeTemplate().
*
* @param string $arg
* @param mixed $s Index of the table row position.
* @param mixed $argNr Other part of the index of the table row position?
* @param bool $firstCall
* @param int $maxLength
* @param Article $article
* @return string
*/
public function formatTemplateArg( $arg, $s, $argNr, $firstCall, $maxLength, Article $article ) {
$tableFormat = $this->getParameters()->getParameter( 'tablerow' );
// we could try to format fields differently within the first call of a template
// currently we do not make such a difference
// if the result starts with a '-' we add a leading space; thus we avoid a misinterpretation of |- as
// a start of a new row (wiki table syntax)
if ( array_key_exists( "$s.$argNr", $tableFormat ) ) {
$n = -1;
if ( $s >= 1 && $argNr == 0 && !$firstCall ) {
$n = strpos( $tableFormat["$s.$argNr"], '|' );
if ( $n === false || !( strpos( substr( $tableFormat["$s.$argNr"], 0, $n ), '{' ) === false ) || !( strpos( substr( $tableFormat["$s.$argNr"], 0, $n ), '[' ) === false ) ) {
$n = -1;
}
}
$result = str_replace( '%%', $arg, substr( $tableFormat["$s.$argNr"], $n + 1 ) );
$result = str_replace( '%PAGE%', $article->mTitle->getPrefixedText(), $result );
// @TODO: This just blindly passes the argument through hoping it is an image.
$result = str_replace( '%IMAGE%', $this->parseImageUrlWithPath( $arg ), $result );
$result = $this->cutAt( $maxLength, $result );
if ( strlen( $result ) > 0 && $result[0] == '-' ) {
return ' ' . $result;
} else {
return $result;
}
}
$result = $this->cutAt( $maxLength, $arg );
if ( strlen( $result ) > 0 && $result[0] == '-' ) {
return ' ' . $result;
} else {
return $result;
}
}
/**
* Truncate a portion of wikitext so that ..
* ... it is not larger that $lim characters
* ... it is balanced in terms of braces, brackets and tags
* ... can be used as content of a wikitable field without spoiling the whole surrounding wikitext structure
*
* @param int $lim
* @param string $text
*
* @return string the truncated text; note that in some cases it may be slightly longer than the given limit
* if the text is alread shorter than the limit or if the limit is negative, the text
* will be returned without any checks for balance of tags
*/
private function cutAt( $lim, $text ) {
if ( $lim < 0 ) {
return $text;
}
return LST::limitTranscludedText( $text, $lim );
}
/**
* Prepends an image name with its hash path.
*
* @param Article|string $article
* @return string
*/
protected function parseImageUrlWithPath( $article ) {
$repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
$imageUrl = '';
if ( $article instanceof Article ) {
if ( $article->mNamespace == NS_FILE ) {
// calculate URL for existing images
// $img = Image::newFromName( $article->mTitle->getText() );
$img = $repoGroup->findFile( Title::makeTitle( NS_FILE, $article->mTitle->getText() ) );
if ( $img && $img->exists() ) {
$imageUrl = $img->getURL();
} else {
$fileTitle = Title::makeTitleSafe( NS_FILE, $article->mTitle->getDBKey() );
$imageUrl = $repoGroup->getLocalRepo()->newFile( $fileTitle )->getPath();
}
}
} else {
$title = Title::newfromText( 'File:' . $article );
if ( $title !== null ) {
$fileTitle = Title::makeTitleSafe( 6, $title->getDBKey() );
$imageUrl = $repoGroup->getLocalRepo()->newFile( $fileTitle )->getPath();
}
}
// @TODO: Check this preg_replace. Probably only works for stock file repositories.
$imageUrl = preg_replace( '~^.*images/(.*)~', '\1', $imageUrl );
return $imageUrl;
}
/**
* Transclude a page contents.
*
* @param Article $article
* @param int &$filteredCount
* @return string
*/
public function transcludePage( Article $article, &$filteredCount ) {
$matchFailed = false;
$septag = [];
// include whole article
if ( empty( $this->pageTextMatch ) || $this->pageTextMatch[0] == '*' ) {
$title = $article->mTitle->getPrefixedText();
if ( $this->getStyle() == self::LIST_USERFORMAT ) {
$pageText = '';
} else {
$pageText = '<br/>';
}
$text = $this->parser->fetchTemplateAndTitle( Title::newFromText( $title ) )[0];
if ( ( count( $this->pageTextMatchRegex ) <= 0 || $this->pageTextMatchRegex[0] == '' || !( !preg_match( $this->pageTextMatchRegex[0], $text ) ) ) && ( count( $this->pageTextMatchNotRegex ) <= 0 || $this->pageTextMatchNotRegex[0] == '' || preg_match( $this->pageTextMatchNotRegex[0], $text ) == false ) ) {
if ( $this->includePageMaxLength > 0 && ( strlen( $text ) > $this->includePageMaxLength ) ) {
$text = LST::limitTranscludedText( $text, $this->includePageMaxLength, ' [[' . $title . '|..→]]' );
}
$filteredCount++;
// update article if include=* and updaterules are given
$updateRules = $this->getParameters()->getParameter( 'updaterules' );
$deleteRules = $this->getParameters()->getParameter( 'deleterules' );
if ( !empty( $updateRules ) ) {
$ruleOutput = UpdateArticle::updateArticleByRule( $title, $text, $updateRules );
// append update message to output
$pageText .= $ruleOutput;
} elseif ( !empty( $deleteRules ) ) {
$ruleOutput = UpdateArticle::deleteArticleByRule( $title, $text, $deleteRules );
// append delete message to output
$pageText .= $ruleOutput;
} else {
// append full text to output
if ( is_array( $this->sectionSeparators ) && array_key_exists( '0', $this->sectionSeparators ) ) {
$pageText .= $this->replaceTagCount( $this->sectionSeparators[0], $filteredCount );
$pieces = [
0 => $text
];
$this->replaceTagTableRow( $pieces, 0, $article );
$pageText .= $pieces[0];
} else {
$pageText .= $text;
}
}
} else {
return '';
}
} else {
// identify section pieces
$secPiece = [];
$dominantPieces = false;
// ONE section can be marked as "dominant"; if this section contains multiple entries
// we will create a separate output row for each value of the dominant section
// the values of all other columns will be repeated
foreach ( $this->pageTextMatch as $s => $sSecLabel ) {
$sSecLabel = trim( $sSecLabel );
if ( $sSecLabel == '' ) {
break;
}
// if sections are identified by number we have a % at the beginning
if ( $sSecLabel[0] == '%' ) {
$sSecLabel = '#' . $sSecLabel;
}
$maxLength = -1;
if ( $sSecLabel == '-' ) {
// '-' is used as a dummy parameter which will produce no output
// if maxlen was 0 we suppress all output; note that for matching we used the full text
$secPieces = [
''
];
$this->replaceTagTableRow( $secPieces, $s, $article );
} elseif ( $sSecLabel[0] != '{' ) {
$limpos = strpos( $sSecLabel, '[' );
$cutLink = 'default';
$skipPattern = [];
if ( $limpos > 0 && $sSecLabel[strlen( $sSecLabel ) - 1] == ']' ) {
// regular expressions which define a skip pattern may precede the text
$fmtSec = explode( '~', substr( $sSecLabel, $limpos + 1, strlen( $sSecLabel ) - $limpos - 2 ) );
$sSecLabel = substr( $sSecLabel, 0, $limpos );
$cutInfo = explode( ' ', $fmtSec[count( $fmtSec ) - 1], 2 );
$maxLength = intval( $cutInfo[0] );
if ( array_key_exists( '1', $cutInfo ) ) {
$cutLink = $cutInfo[1];
}
foreach ( $fmtSec as $skipKey => $skipPat ) {
if ( $skipKey == count( $fmtSec ) - 1 ) {
continue;
}
$skipPattern[] = $skipPat;
}
}
if ( $maxLength < 0 ) {
// without valid limit include whole section
$maxLength = -1;
}
}
// find out if the user specified an includematch / includenotmatch condition
if ( is_array( $this->pageTextMatchRegex ) && count( $this->pageTextMatchRegex ) > $s && !empty( $this->pageTextMatchRegex[$s] ) ) {
$mustMatch = $this->pageTextMatchRegex[$s];
} else {
$mustMatch = '';
}
if ( is_array( $this->pageTextMatchNotRegex ) && count( $this->pageTextMatchNotRegex ) > $s && !empty( $this->pageTextMatchNotRegex[$s] ) ) {
$mustNotMatch = $this->pageTextMatchNotRegex[$s];
} else {
$mustNotMatch = '';
}
// if chapters are selected by number, text or regexp we get the heading from LST::includeHeading
$sectionHeading = [];
$sectionHeading[0] = '';
if ( $sSecLabel == '-' ) {
$secPiece[$s] = $secPieces[0];
} elseif ( $sSecLabel[0] == '#' || $sSecLabel[0] == '@' ) {
$sectionHeading[0] = substr( $sSecLabel, 1 );
// Uses LST::includeHeading() from LabeledSectionTransclusion extension to include headings from the page
$secPieces = LST::includeHeading( $this->parser, $article->mTitle->getPrefixedText(), substr( $sSecLabel, 1 ), '', $sectionHeading, false, $maxLength, $cutLink ?? 'default', $this->getTrimIncluded(), $skipPattern ?? [] );
if ( $mustMatch != '' || $mustNotMatch != '' ) {
$secPiecesTmp = $secPieces;
$offset = 0;
foreach ( $secPiecesTmp as $nr => $onePiece ) {
if ( ( $mustMatch != '' && preg_match( $mustMatch, $onePiece ) == false ) || ( $mustNotMatch != '' && preg_match( $mustNotMatch, $onePiece ) != false ) ) {
array_splice( $secPieces, $nr - $offset, 1 );
$offset++;
}
}
}
// if maxlen was 0 we suppress all output; note that for matching we used the full text
if ( $maxLength == 0 ) {
$secPieces = [
''
];
}
$this->replaceTagTableRow( $secPieces, $s, $article );
if ( !array_key_exists( 0, $secPieces ) ) {
// avoid matching against a non-existing array element
// and skip the article if there was a match condition
if ( $mustMatch != '' || $mustNotMatch != '' ) {
$matchFailed = true;
}
break;
}
$secPiece[$s] = $secPieces[0];
for ( $sp = 1; $sp < count( $secPieces ); $sp++ ) {
if ( isset( $this->multiSectionSeparators[$s] ) ) {
$secPiece[$s] .= str_replace( '%SECTION%', $sectionHeading[$sp] ?? '', $this->replaceTagCount( $this->multiSectionSeparators[$s], $filteredCount ) );
}
$secPiece[$s] .= $secPieces[$sp];
}
if ( $this->getDominantSectionCount() >= 0 && $s == $this->getDominantSectionCount() && count( $secPieces ) > 1 ) {
$dominantPieces = $secPieces;
}
if ( ( $mustMatch != '' || $mustNotMatch != '' ) && count( $secPieces ) <= 0 ) {
$matchFailed = true;
break;
}
} elseif ( $sSecLabel[0] == '{' ) {
// Uses LST::includeTemplate() from LabeledSectionTransclusion extension to include templates from the page
// primary syntax {template}suffix
$template1 = trim( substr( $sSecLabel, 1, strpos( $sSecLabel, '}' ) - 1 ) );
$template2 = trim( str_replace( '}', '', substr( $sSecLabel, 1 ) ) );
// alternate syntax: {template|surrogate}
if ( $template2 == $template1 && strpos( $template1, '|' ) > 0 ) {
$template1 = preg_replace( '/\|.*/', '', $template1 );
$template2 = preg_replace( '/^.+\|/', '', $template2 );
}
// Why was defaultTemplateSuffix passed all over the place for just here?
$secPieces = LST::includeTemplate( $this->parser, $this, $s, $article, $template1, $template2, $template2 . $this->getTemplateSuffix(), $mustMatch, $mustNotMatch, $this->includePageParsed, implode( ', ', $article->mCategoryLinks ) );
$secPiece[$s] = implode( isset( $this->multiSectionSeparators[$s] ) ? $this->replaceTagCount( $this->multiSectionSeparators[$s], $filteredCount ) : '', $secPieces );
if ( $this->getDominantSectionCount() >= 0 && $s == $this->getDominantSectionCount() && count( $secPieces ) > 1 ) {
$dominantPieces = $secPieces;
}
if ( ( $mustMatch != '' || $mustNotMatch != '' ) && count( $secPieces ) <= 1 && $secPieces[0] == '' ) {
$matchFailed = true;
break;
}
} else {
// Uses LST::includeSection() from LabeledSectionTransclusion extension to include labeled sections from the page
$secPieces = LST::includeSection( $this->parser, $article->mTitle->getPrefixedText(), $sSecLabel, '', false, $this->getTrimIncluded(), $skipPattern ?? [] );
$secPiece[$s] = implode( isset( $this->multiSectionSeparators[$s] ) ? $this->replaceTagCount( $this->multiSectionSeparators[$s], $filteredCount ) : '', $secPieces );
if ( $this->getDominantSectionCount() >= 0 && $s == $this->getDominantSectionCount() && count( $secPieces ) > 1 ) {
$dominantPieces = $secPieces;
}
if ( ( $mustMatch != '' && preg_match( $mustMatch, $secPiece[$s] ) == false ) || ( $mustNotMatch != '' && preg_match( $mustNotMatch, $secPiece[$s] ) != false ) ) {
$matchFailed = true;
break;
}
}
// separator tags
if ( is_array( $this->sectionSeparators ) && count( $this->sectionSeparators ) == 1 ) {
// If there is only one separator tag use it always
$septag[$s * 2] = str_replace( '%SECTION%', $sectionHeading[0], $this->replaceTagCount( $this->sectionSeparators[0], $filteredCount ) );
} elseif ( isset( $this->sectionSeparators[$s * 2] ) ) {
$septag[$s * 2] = str_replace( '%SECTION%', $sectionHeading[0], $this->replaceTagCount( $this->sectionSeparators[$s * 2], $filteredCount ) );
} else {
$septag[$s * 2] = '';
}
if ( isset( $this->sectionSeparators[$s * 2 + 1] ) ) {
$septag[$s * 2 + 1] = str_replace( '%SECTION%', $sectionHeading[0], $this->replaceTagCount( $this->sectionSeparators[$s * 2 + 1], $filteredCount ) );
} else {
$septag[$s * 2 + 1] = '';
}
}
// if there was a match condition on included contents which failed we skip the whole page
if ( $matchFailed ) {
return '';
}
$filteredCount++;
// assemble parts with separators
$pageText = '';
if ( $dominantPieces != false ) {
foreach ( $dominantPieces as $dominantPiece ) {
foreach ( $secPiece as $s => $piece ) {
if ( $s == $this->getDominantSectionCount() ) {
$pageText .= $this->joinSectionTagPieces( $dominantPiece, $septag[$s * 2], $septag[$s * 2 + 1] );
} else {
$pageText .= $this->joinSectionTagPieces( $piece, $septag[$s * 2], $septag[$s * 2 + 1] );
}
}
}
} else {
foreach ( $secPiece as $s => $piece ) {
$pageText .= $this->joinSectionTagPieces( $piece, $septag[$s * 2], $septag[$s * 2 + 1] );
}
}
}
return $pageText;
}
/**
* Wrap seciton pieces with start and end tags.
*
* @param string $piece
* @param string $start
* @param string $end
* @return string
*/
protected function joinSectionTagPieces( $piece, $start, $end ) {
return $start . $piece . $end;
}
/**
* Get the count of listed items after formatting, transcluding, and such.
*
* @return int
*/
public function getRowCount() {
return $this->rowCount;
}
}