mirror of
https://github.com/Universal-Omega/DynamicPageList3
synced 2024-12-12 16:25:15 +00:00
1305 lines
36 KiB
PHP
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 !== null ) {
|
|
$item .= $date . ' ';
|
|
|
|
if ( $article->mRevision !== null ) {
|
|
$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 != null ) {
|
|
$byte = 'B';
|
|
$pageLength = $lang->formatNum( $article->mSize );
|
|
$item .= " [{$pageLength} {$byte}]";
|
|
}
|
|
|
|
if ( $article->mCounter !== null ) {
|
|
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
|
|
|
|
$item .= ' ' . $contLang->getDirMark() . '(' . wfMessage( 'hitcounters-nviews', $lang->formatNum( $article->mCounter ) )->escaped() . ')';
|
|
}
|
|
|
|
if ( $article->mUserLink !== null ) {
|
|
$item .= ' . . [[User:' . $article->mUser . '|' . $article->mUser . ']]';
|
|
|
|
if ( $article->mComment != '' ) {
|
|
$item .= ' { ' . $article->mComment . ' }';
|
|
}
|
|
}
|
|
|
|
if ( $article->mContributor !== null ) {
|
|
$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 !== null ) {
|
|
$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 != null ) {
|
|
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;
|
|
}
|
|
}
|