DPL3 with 1.35 support

This commit is contained in:
Universal Omega 2020-11-22 13:00:48 -07:00 committed by GitHub
parent 8f2e28312e
commit e99da9ab44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 7888 additions and 0 deletions

395
classes/Article.php Normal file
View file

@ -0,0 +1,395 @@
<?php
/**
* DynamicPageList3
* DPL Article Class
*
* @author IlyaHaykinson, Unendlich, Dangerville, Algorithmix, Theaitetos, Alexia E. Smith
* @license GPL-2.0-or-later
* @package DynamicPageList3
*
**/
namespace DPL;
use User;
class Article {
/**
* Title
*
* @var object
*/
public $mTitle = null;
/**
* Namespace ID
*
* @var string
*/
public $mNamespace = -1;
/**
* Page ID
*
* @var integer
*/
public $mID = 0;
/**
* Selected title of initial page.
*
* @var string
*/
public $mSelTitle = null;
/**
* Selected namespace ID of initial page.
*
* @var string
*/
public $mSelNamespace = -1;
/**
* Selected title of image.
*
* @var string
*/
public $mImageSelTitle = null;
/**
* HTML link to page.
*
* @var string
*/
public $mLink = '';
/**
* External link on the page.
*
* @var string
*/
public $mExternalLink = null;
/**
* First character of the page title.
*
* @var string
*/
public $mStartChar = null;
/**
* Heading (link to the associated page) that page belongs to in the list (default '' means no heading)
*
* @var string
*/
public $mParentHLink = ''; // heading (link to the associated page) that page belongs to in the list (default '' means no heading)
/**
* Category links on the page.
*
* @var array
*/
public $mCategoryLinks = [];
/**
* Category names (without link) in the page.
*
* @var array
*/
public $mCategoryTexts = [];
/**
* Number of times this page has been viewed.
*
* @var integer
*/
public $mCounter = null;
/**
* Article length in bytes of wiki text
*
* @var integer
*/
public $mSize = null;
/**
* Timestamp depending on the user's request (can be first/last edit, page_touched, ...)
*
* @var string
*/
public $mDate = null;
/**
* Timestamp depending on the user's request, based on user format definition.
*
* @var string
*/
public $myDate = null;
/**
* Revision ID
*
* @var integer
*/
public $mRevision = null;
/**
* Link to editor (first/last, depending on user's request) 's page or contributions if not registered.
*
* @var string
*/
public $mUserLink = null;
/**
* Name of editor (first/last, depending on user's request) or contributions if not registered.
*
* @var string
*/
public $mUser = null;
/**
* Edit Summary(Revision Comment)
*
* @var string
*/
public $mComment = null;
/**
* Number of bytes changed.
*
* @var integer
*/
public $mContribution = 0;
/**
* Short string indicating the size of a contribution.
*
* @var string
*/
public $mContrib = '';
/**
* User text of who made the changes.
*
* @var string
*/
public $mContributor = null;
/**
* Article Headings - Maps heading to count (# of pages under each heading).
*
* @var array
*/
static private $headings = [];
/**
* Main Constructor
*
* @access public
* @param string Title
* @param integer Namespace
* @return void
*/
public function __construct($title, $namespace) {
$this->mTitle = $title;
$this->mNamespace = $namespace;
}
/**
* Initialize a new instance from a database row.
*
* @access public
* @param array Database Row
* @param object \DPL\Parameters Object
* @param object Mediawiki Title Object
* @param integer Page Namespace ID
* @param string Page Title as Selected from Query
* @return object \DPL\Article Object
*/
public static function newFromRow($row, Parameters $parameters, \Title $title, $pageNamespace, $pageTitle) {
global $wgLang, $wgContLang;
$article = new Article($title, $pageNamespace);
$revActorName = User::newFromActorId( $row['rev_actor'] )->getName();
$titleText = $title->getText();
if ($parameters->getParameter('shownamespace') === true) {
$titleText = $title->getPrefixedText();
}
$replaceInTitle = $parameters->getParameter('replaceintitle');
if (is_array($replaceInTitle) && count($replaceInTitle) === 2) {
$titleText = preg_replace($replaceInTitle[0], $replaceInTitle[1], $titleText);
}
//Chop off title if longer than the 'titlemaxlen' parameter.
if ($parameters->getParameter('titlemaxlen') !== null && strlen($titleText) > $parameters->getParameter('titlemaxlen')) {
$titleText = substr($titleText, 0, $parameters->getParameter('titlemaxlen')) . '...';
}
if ($parameters->getParameter('showcurid') === true && isset($row['page_id'])) {
$articleLink = '[' . $title->getLinkURL(['curid' => $row['page_id']]) . ' ' . htmlspecialchars($titleText) . ']';
} else {
$articleLink = '[[' . ($parameters->getParameter('escapelinks') && ($pageNamespace == NS_CATEGORY || $pageNamespace == NS_FILE) ? ':' : '') . $title->getFullText() . '|' . htmlspecialchars($titleText) . ']]';
}
$article->mLink = $articleLink;
//get first char used for category-style output
if (isset($row['sortkey'])) {
$article->mStartChar = $wgContLang->convert($wgContLang->firstChar($row['sortkey']));
} else {
$article->mStartChar = $wgContLang->convert($wgContLang->firstChar($pageTitle));
}
$article->mID = intval($row['page_id']);
//External link
if (isset($row['el_to'])) {
$article->mExternalLink = $row['el_to'];
}
//SHOW PAGE_COUNTER
if (isset($row['page_counter'])) {
$article->mCounter = intval($row['page_counter']);
}
//SHOW PAGE_SIZE
if (isset($row['page_len'])) {
$article->mSize = intval($row['page_len']);
}
//STORE initially selected PAGE
if (is_array($parameters->getParameter('linksto')) && (count($parameters->getParameter('linksto')) || count($parameters->getParameter('linksfrom')))) {
if (!isset($row['sel_title'])) {
$article->mSelTitle = 'unknown page';
$article->mSelNamespace = 0;
} else {
$article->mSelTitle = $row['sel_title'];
$article->mSelNamespace = $row['sel_ns'];
}
}
//STORE selected image
if (is_array($parameters->getParameter('imageused')) && count($parameters->getParameter('imageused')) > 0) {
if (!isset($row['image_sel_title'])) {
$article->mImageSelTitle = 'unknown image';
} else {
$article->mImageSelTitle = $row['image_sel_title'];
}
}
if ($parameters->getParameter('goal') != 'categories') {
//REVISION SPECIFIED
if ($parameters->getParameter('lastrevisionbefore') || $parameters->getParameter('allrevisionsbefore') || $parameters->getParameter('firstrevisionsince') || $parameters->getParameter('allrevisionssince')) {
$article->mRevision = $row['rev_id'];
$article->mUser = $revActorName;
$article->mDate = $row['rev_timestamp'];
$article->mComment = $row['rev_comment_id'];
}
//SHOW "PAGE_TOUCHED" DATE, "FIRSTCATEGORYDATE" OR (FIRST/LAST) EDIT DATE
if ($parameters->getParameter('addpagetoucheddate')) {
$article->mDate = $row['page_touched'];
} elseif ($parameters->getParameter('addfirstcategorydate')) {
$article->mDate = $row['cl_timestamp'];
} elseif ($parameters->getParameter('addeditdate') && isset($row['rev_timestamp'])) {
$article->mDate = $row['rev_timestamp'];
} elseif ($parameters->getParameter('addeditdate') && isset($row['page_touched'])) {
$article->mDate = $row['page_touched'];
}
//Time zone adjustment
if ($article->mDate) {
$article->mDate = $wgLang->userAdjust($article->mDate);
}
if ($article->mDate && $parameters->getParameter('userdateformat')) {
//Apply the userdateformat
$article->myDate = gmdate($parameters->getParameter('userdateformat'), wfTimeStamp(TS_UNIX, $article->mDate));
}
// CONTRIBUTION, CONTRIBUTOR
if ($parameters->getParameter('addcontribution')) {
$article->mContribution = $row['contribution'];
$article->mContributor = User::newFromActorId( $row['contributor'] )->getName();
$article->mContrib = substr('*****************', 0, (int) round(log($row['contribution'])));
}
//USER/AUTHOR(S)
// because we are going to do a recursive parse at the end of the output phase
// we have to generate wiki syntax for linking to a user´s homepage
if ($parameters->getParameter('adduser') || $parameters->getParameter('addauthor') || $parameters->getParameter('addlasteditor')) {
$article->mUserLink = '[[User:' . $revActorName . '|' . $revActorName . ']]';
$article->mUser = $revActorName;
}
//CATEGORY LINKS FROM CURRENT PAGE
if ($parameters->getParameter('addcategories') && ($row['cats'])) {
$artCatNames = explode(' | ', $row['cats']);
foreach ($artCatNames as $artCatName) {
$article->mCategoryLinks[] = '[[:Category:' . $artCatName . '|' . str_replace('_', ' ', $artCatName) . ']]';
$article->mCategoryTexts[] = str_replace('_', ' ', $artCatName);
}
}
// PARENT HEADING (category of the page, editor (user) of the page, etc. Depends on ordermethod param)
if ($parameters->getParameter('headingmode') != 'none') {
switch ($parameters->getParameter('ordermethod')[0]) {
case 'category':
//Count one more page in this heading
self::$headings[$row['cl_to']] = (isset(self::$headings[$row['cl_to']]) ? self::$headings[$row['cl_to']] + 1 : 1);
if ($row['cl_to'] == '') {
//uncategorized page (used if ordermethod=category,...)
$article->mParentHLink = '[[:Special:Uncategorizedpages|' . wfMessage('uncategorizedpages') . ']]';
} else {
$article->mParentHLink = '[[:Category:' . $row['cl_to'] . '|' . str_replace('_', ' ', $row['cl_to']) . ']]';
}
break;
case 'user':
self::$headings[$revActorName] = (isset(self::$headings[$revActorName]) ? self::$headings[$revActorName] + 1 : 1);
if ($row['rev_actor'] == 0) { //anonymous user
$article->mParentHLink = '[[User:' . $revActorName . '|' . $revActorName . ']]';
} else {
$article->mParentHLink = '[[User:' . $revActorName . '|' . $revActorName . ']]';
}
break;
}
}
}
return $article;
}
/**
* Returns all heading information processed from all newly instantiated article objects.
*
* @access public
* @return array Headings
*/
public static function getHeadings() {
return self::$headings;
}
/**
* Reset the headings to their initial state.
* Ideally this Article class should not exist and be handled by the built in MediaWiki class.
* Bug: https://jira/browse/HYD-913
*
* @access public
* @return void
*/
public static function resetHeadings() {
self::$headings = [];
}
/**
* Get the formatted date for this article if available.
*
* @access public
* @return mixed Formatted string or null for none set.
*/
public function getDate() {
global $wgLang;
if ($this->myDate !== null) {
return $this->myDate;
} elseif ($this->mDate !== null) {
return $wgLang->timeanddate($article->mDate, true);
}
return null;
}
}

77
classes/Config.php Normal file
View file

@ -0,0 +1,77 @@
<?php
/**
* DynamicPageList3
* DPL Config Class
*
* @author IlyaHaykinson, Unendlich, Dangerville, Algorithmix, Theaitetos, Alexia E. Smith
* @license GPL-2.0-or-later
* @package DynamicPageList3
*
**/
namespace DPL;
class Config {
/**
* Configuration Settings
*
* @var array
*/
static private $settings = [];
/**
* Initialize the static object with settings.
*
* @access public
* @param array Settings to initialize for DPL.
* @return void
*/
public static function init($settings = false) {
if ($settings === false) {
global $wgDplSettings;
$settings = $wgDplSettings;
}
if (!is_array($settings)) {
throw new MWException(__METHOD__ . ": Invalid settings passed.");
}
self::$settings = array_merge(self::$settings, $settings);
}
/**
* Return a single setting.
*
* @access public
* @param string Setting Key
* @return mixed The setting's actual setting or null if it does not exist.
*/
public static function getSetting($setting) {
return (array_key_exists($setting, self::$settings) ? self::$settings[$setting] : null);
}
/**
* Return a all settings.
*
* @access public
* @return array All settings
*/
public static function getAllSettings() {
return self::$settings;
}
/**
* Set a single setting.
*
* @access public
* @param string Setting Key
* @param mixed [Optional] Appropriate value for the setting key.
* @return void
*/
public static function setSetting($setting, $value = null) {
if (empty($setting) || !is_string($setting)) {
throw new MWException(__METHOD__ . ": Setting keys can not be blank.");
}
self::$settings[$setting] = $value;
}
}

718
classes/LST.php Normal file
View file

@ -0,0 +1,718 @@
<?php
/**
* This is a modified and enhanced copy of a mediawiki extension called
*
* LabeledSectionTransclusion
*
* @link http://www.mediawiki.org/wiki/Extension:Labeled_Section_Transclusion Documentation
*
*
* @author Steve Sanbeg
* @copyright Copyright © 2006, Steve Sanbeg
* @license GPL-2.0-or-later
*
*
* This copy was made to avoid version conflicts between the two extensions.
* In this copy names were changed (wfLst.. --> wfDplLst..).
* So any version of LabeledSectionTransclusion can be installed together with DPL
*
* Enhancements were made to
* - allow inclusion of templates ("template swapping")
* - reduce the size of the transcluded text to a limit of <n> characters
*
*
* Thanks to Steve for his great work!
* -- Algorithmix
*/
namespace DPL;
use DPL\Lister\Lister;
class LST {
##############################################################
# To do transclusion from an extension, we need to interact with the parser
# at a low level. This is the general transclusion functionality
##############################################################
/**
* Register what we're working on in the parser, so we don't fall into a trap.
* @param $parser Parser
* @param $part1
* @return bool
*/
public static function open($parser, $part1) {
// Infinite loop test
if (isset($parser->mTemplatePath[$part1])) {
wfDebug(__METHOD__ . ": template loop broken at '$part1'\n");
return false;
} else {
$parser->mTemplatePath[$part1] = 1;
return true;
}
}
/**
* Finish processing the function.
* @param $parser Parser
* @param $part1
* @return bool
*/
public static function close($parser, $part1) {
// Infinite loop test
if (isset($parser->mTemplatePath[$part1])) {
unset($parser->mTemplatePath[$part1]);
} else {
wfDebug(__METHOD__ . ": close unopened template loop at '$part1'\n");
}
}
/**
* Handle recursive substitution here, so we can break cycles, and set up
* return values so that edit sections will resolve correctly.
**/
private static function parse($parser, $title, $text, $part1, $skiphead = 0, $recursionCheck = true, $maxLength = -1, $link = '', $trim = false, $skipPattern = []) {
// if someone tries something like<section begin=blah>lst only</section>
// text, may as well do the right thing.
$text = str_replace('</section>', '', $text);
// if desired we remove portions of the text, esp. template calls
foreach ($skipPattern as $skipPat) {
$text = preg_replace($skipPat, '', $text);
}
if (self::open($parser, $part1)) {
//Handle recursion here, so we can break cycles.
if ($recursionCheck == false) {
$text = $parser->preprocess($text, $parser->mTitle, $parser->mOptions);
self::close($parser, $part1);
}
if ($maxLength > 0) {
$text = self::limitTranscludedText($text, $maxLength, $link);
}
if ($trim) {
return trim($text);
} else {
return $text;
}
} else {
return "[[" . $title->getPrefixedText() . "]]" . "<!-- WARNING: LST loop detected -->";
}
}
##############################################################
# And now, the labeled section transclusion
##############################################################
/**
* Parser tag hook for <section>.
* The section markers aren't paired, so we only need to remove them.
*
* @param string $in
* @param array $assocArgs
* @param Parser $parser
* @return string HTML output
*/
private static function noop($in, $assocArgs = [], $parser = null) {
return '';
}
///Generate a regex to match the section(s) we're interested in.
private static function createSectionPattern($sec, $to, &$any) {
$any = false;
$to_sec = ($to == '') ? $sec : $to;
if ($sec[0] == '*') {
$any = true;
if ($sec == '**') {
$sec = '[^\/>"' . "']+";
} else {
$sec = str_replace('/', '\/', substr($sec, 1));
}
} else {
$sec = preg_quote($sec, '/');
}
if ($to_sec[0] == '*') {
if ($to_sec == '**') {
$to_sec = '[^\/>"' . "']+";
} else {
$to_sec = str_replace('/', '\/', substr($to_sec, 1));
}
} else {
$to_sec = preg_quote($to_sec, '/');
}
$ws = "(?:\s+[^>]+)?"; //was like $ws="\s*"
return "/<section$ws\s+(?i:begin)=['\"]?" . "($sec)" . "['\"]?$ws\/?>(.*?)\n?<section$ws\s+(?:[^>]+\s+)?(?i:end)=" . "['\"]?\\1['\"]?" . "$ws\/?>/s";
}
/**
* Count headings in skipped text.
*
* Count skipped headings, so parser (as of r18218) can skip them, to
* prevent wrong heading links (see bug 6563).
*
* @param string $text
* @param int $limit Cutoff point in the text to stop searching
* @return int Number of matches
* @private
*/
private static function countHeadings($text, $limit) {
$pat = '^(={1,6}).+\1\s*$()';
$count = 0;
$offset = 0;
$m = [];
while (preg_match("/$pat/im", $text, $m, PREG_OFFSET_CAPTURE, $offset)) {
if ($m[2][1] > $limit) {
break;
}
$count++;
$offset = $m[2][1];
}
return $count;
}
public static function text($parser, $page, &$title, &$text) {
$title = \Title::newFromText($page);
if (is_null($title)) {
$text = '';
return true;
} else {
$text = $parser->fetchTemplate($title);
}
//if article doesn't exist, return a red link.
if ($text == false) {
$text = "[[" . $title->getPrefixedText() . "]]";
return false;
} else {
return true;
}
}
///section inclusion - include all matching sections
public static function includeSection($parser, $page = '', $sec = '', $to = '', $recursionCheck = true, $trim = false, $skipPattern = []) {
$output = [];
if (self::text($parser, $page, $title, $text) == false) {
$output[] = $text;
return $output;
}
$any = false;
$pat = self::createSectionPattern($sec, $to, $any);
preg_match_all($pat, $text, $m, PREG_PATTERN_ORDER);
foreach ($m[2] as $nr => $piece) {
$piece = self::parse($parser, $title, $piece, "#lst:${page}|${sec}", 0, $recursionCheck, $trim, $skipPattern);
if ($any) {
$output[] = $m[1][$nr] . '::' . $piece;
} else {
$output[] = $piece;
}
}
return $output;
}
/**
* Truncate a portion of wikitext so that ..
* ... does not contain (open) html comments
* ... it is not larger that $lim characters
* ... it is balanced in terms of braces, brackets and tags
* ... it is cut at a word boundary (white space) if possible
* ... can be used as content of a wikitable field without spoiling the whole surrounding wikitext structure
* @param $lim limit of character count for the result
* @param $text the wikitext to be truncated
* @param $link an optional link which will be appended to the text if it was truncatedt
* @return the truncated text;
* note that the returned text may be longer than the limit if this is necessary
* to return something at all. We do not want to return an empty string if the input is not empty
* if the text is already shorter than the limit, the text
* will be returned without any checks for balance of tags
*/
public static function limitTranscludedText($text, $limit, $link = '') {
// if text is smaller than limit return complete text
if ($limit >= strlen($text)) {
return $text;
}
// otherwise strip html comments and check again
$text = preg_replace('/<!--.*?-->/s', '', $text);
if ($limit >= strlen($text)) {
return $text;
}
// search latest position with balanced brackets/braces
// store also the position of the last preceding space
$brackets = 0;
$cbrackets = 0;
$n0 = -1;
$nb = 0;
for ($i = 0; $i < $limit; $i++) {
$c = $text[$i];
if ($c == '[') {
$brackets++;
}
if ($c == ']') {
$brackets--;
}
if ($c == '{') {
$cbrackets++;
}
if ($c == '}') {
$cbrackets--;
}
// we store the position if it is valid in terms of parentheses balancing
if ($brackets == 0 && $cbrackets == 0) {
$n0 = $i;
if ($c == ' ') {
$nb = $i;
}
}
}
// if there is a valid cut-off point we use it; it will be the largest one which is not above the limit
if ($n0 >= 0) {
// we try to cut off at a word boundary, this may lead to a shortening of max. 15 chars
if ($nb > 0 && $nb + 15 > $n0) {
$n0 = $nb;
}
$cut = substr($text, 0, $n0 + 1);
// an open html comment would be fatal, but this should not happen as we already have
// eliminated html comments at the beginning
// some tags are critical: ref, pre, nowiki
// if these tags were not balanced they would spoil the result completely
// we enforce balance by appending the necessary amount of corresponding closing tags
// currently we ignore the nesting, i.e. all closing tags are appended at the end.
// This simple approach may fail in some cases ...
$matches = [];
$noMatches = preg_match_all('#<\s*(/?ref|/?pre|/?nowiki)(\s+[^>]*?)*>#im', $cut, $matches);
$tags = [
'ref' => 0,
'pre' => 0,
'nowiki' => 0
];
if ($noMatches > 0) {
// calculate tag count (ignoring nesting)
foreach ($matches[1] as $mm) {
if ($mm[0] == '/') {
$tags[substr($mm, 1)]--;
} else {
$tags[$mm]++;
}
}
// append missing closing tags - should the tags be ordered by precedence ?
foreach ($tags as $tagName => $level) {
while ($level > 0) {
// avoid empty ref tag
if ($tagName == 'ref' && substr($cut, strlen($cut) - 5) == '<ref>') {
$cut = substr($cut, 0, strlen($cut) - 5);
} else {
$cut .= '</' . $tagName . '>';
}
$level--;
}
}
}
return $cut . $link;
} elseif ($limit == 0) {
return $link;
} else {
// otherwise we recurse and try again with twice the limit size; this will lead to bigger output but
// it will at least produce some output at all; otherwise the reader might think that there
// is no information at all
return self::limitTranscludedText($text, $limit * 2, $link);
}
}
public static function includeHeading($parser, $page = '', $sec = '', $to = '', &$sectionHeading, $recursionCheck = true, $maxLength = -1, $link = 'default', $trim = false, $skipPattern = []) {
$output = [];
if (self::text($parser, $page, $title, $text) == false) {
$output[0] = $text;
return $output;
}
/* throw away comments */
$text = preg_replace('/<!--.*?-->/s', '', $text);
return self::extractHeadingFromText($parser, $page, $title, $text, $sec, $to, $sectionHeading, $recursionCheck, $maxLength, $link, $trim, $skipPattern);
}
//section inclusion - include all matching sections (return array)
public static function extractHeadingFromText($parser, $page, $title, $text, $sec = '', $to = '', &$sectionHeading, $recursionCheck = true, $maxLength = -1, $cLink = 'default', $trim = false, $skipPattern = []) {
$continueSearch = true;
$n = 0;
$output[$n] = '';
$nr = 0;
// check if we are going to fetch the n-th section
if (preg_match('/^%-?[1-9][0-9]*$/', $sec)) {
$nr = substr($sec, 1);
}
if (preg_match('/^%0$/', $sec)) {
$nr = -2; // transclude text before the first section
}
// if the section name starts with a # or with a @ we use it as regexp, otherwise as plain string
$isPlain = true;
if ($sec != '' && ($sec[0] == '#' || $sec[0] == '@')) {
$sec = substr($sec, 1);
$isPlain = false;
}
do {
//Generate a regex to match the === classical heading section(s) === we're
//interested in.
$headLine = '';
if ($sec == '') {
$begin_off = 0;
$head_len = 6;
} else {
if ($nr != 0) {
$pat = '^(={1,6})\s*[^=\s\n][^\n=]*\s*\1\s*($)';
} elseif ($isPlain) {
$pat = '^(={1,6})\s*' . preg_quote($sec, '/') . '\s*\1\s*($)';
} else {
$pat = '^(={1,6})\s*' . str_replace('/', '\/', $sec) . '\s*\1\s*($)';
}
if (preg_match("/$pat/im", $text, $m, PREG_OFFSET_CAPTURE)) {
$mata = [];
$no_parenthesis = preg_match_all('/\(/', $pat, $mata);
$begin_off = $m[$no_parenthesis][1];
$head_len = strlen($m[1][0]);
$headLine = trim($m[0][0], "\n =\t");
} elseif ($nr == -2) {
$m[1][1] = strlen($text) + 1; // take whole article if no heading found
} else {
// match failed
return $output;
}
}
// create a link symbol (arrow, img, ...) in case we have to cut the text block to maxLength
$link = $cLink;
if ($link == 'default') {
$link = ' [[' . $page . '#' . $headLine . '|..→]]';
} elseif (strstr($link, 'img=') != false) {
$link = str_replace('img=', "<linkedimage>page=" . $page . '#' . $headLine . "\nimg=Image:", $link) . "\n</linkedimage>";
} elseif (strstr($link, '%SECTION%') == false) {
$link = ' [[' . $page . '#' . $headLine . '|' . $link . ']]';
} else {
$link = str_replace('%SECTION%', $page . '#' . $headLine, $link);
}
if ($nr == -2) {
// output text before first section and done
$piece = substr($text, 0, $m[1][1] - 1);
$output[0] = self::parse($parser, $title, $piece, "#lsth:${page}|${sec}", 0, $recursionCheck, $maxLength, $link, $trim, $skipPattern);
return $output;
}
if (isset($end_off)) {
unset($end_off);
}
if ($to != '') {
//if $to is supplied, try and match it. If we don't match, just ignore it.
if ($isPlain) {
$pat = '^(={1,6})\s*' . preg_quote($to, '/') . '\s*\1\s*$';
} else {
$pat = '^(={1,6})\s*' . str_replace('/', '\/', $to) . '\s*\1\s*$';
}
if (preg_match("/$pat/im", $text, $mm, PREG_OFFSET_CAPTURE, $begin_off)) {
$end_off = $mm[0][1] - 1;
}
}
if (!isset($end_off)) {
if ($nr != 0) {
$pat = '^(={1,6})\s*[^\s\n=][^\n=]*\s*\1\s*$';
} else {
$pat = '^(={1,' . $head_len . '})(?!=)\s*.*?\1\s*$';
}
if (preg_match("/$pat/im", $text, $mm, PREG_OFFSET_CAPTURE, $begin_off)) {
$end_off = $mm[0][1] - 1;
} elseif ($sec == '') {
$end_off = -1;
}
}
$nhead = self::countHeadings($text, $begin_off);
wfDebug("LSTH: head offset = $nhead");
if (isset($end_off)) {
if ($end_off == -1) {
return $output;
}
$piece = substr($text, $begin_off, $end_off - $begin_off);
if ($sec == '') {
$continueSearch = false;
} else {
$text = substr($text, $end_off);
}
} else {
$piece = substr($text, $begin_off);
$continueSearch = false;
}
if ($nr > 1) {
// skip until we reach the n-th section
$nr--;
continue;
}
if (isset($m[0][0])) {
$sectionHeading[$n] = $headLine;
//$sectionHeading[$n]=preg_replace("/^=+\s*/","",$m[0][0]);
//$sectionHeading[$n]=preg_replace("/\s*=+\s*$/","",$sectionHeading[$n]);
} else {
// $sectionHeading[$n] = '';
$sectionHeading[0] = $headLine;
}
if ($nr == 1) {
// output n-th section and done
$output[0] = self::parse($parser, $title, $piece, "#lsth:${page}|${sec}", $nhead, $recursionCheck, $maxLength, $link, $trim, $skipPattern);
break;
}
if ($nr == -1) {
if (!isset($end_off)) {
// output last section and done
$output[0] = self::parse($parser, $title, $piece, "#lsth:${page}|${sec}", $nhead, $recursionCheck, $maxLength, $link, $trim, $skipPattern);
break;
}
} else {
// output section by name and continue search for another section with the same name
$output[$n++] = self::parse($parser, $title, $piece, "#lsth:${page}|${sec}", $nhead, $recursionCheck, $maxLength, $link, $trim, $skipPattern);
}
} while ($continueSearch);
return $output;
}
// template inclusion - find the place(s) where template1 is called,
// replace its name by template2, then expand template2 and return the result
// we return an array containing all occurences of the template call which match the condition "$mustMatch"
// and do NOT match the condition "$mustNotMatch" (if specified)
// we use a callback function to format retrieved parameters, accessible via $lister->formatTemplateArg()
public static function includeTemplate($parser, Lister $lister, $dplNr, $article, $template1 = '', $template2 = '', $defaultTemplate, $mustMatch, $mustNotMatch, $matchParsed, $catlist) {
$page = $article->mTitle->getPrefixedText();
$date = $article->myDate;
$user = $article->mUserLink;
$title = \Title::newFromText($page);
/* get text and throw away html comments */
$text = preg_replace('/<!--.*?-->/s', '', $parser->fetchTemplate($title));
if ($template1 != '' && $template1[0] == '#') {
// --------------------------------------------- looking for a parser function call
$template1 = substr($template1, 1);
$template2 = substr($template2, 1);
$defaultTemplate = substr($defaultTemplate, 1);
// when looking for parser function calls we accept regexp search patterns
$text2 = preg_replace("/\{\{\s*#(" . $template1 . ')(\s*[:}])/i', '°³²|%PFUNC%=\1\2|', $text);
$tCalls = preg_split('/°³²/', ' ' . $text2);
foreach ($tCalls as $i => $tCall) {
if (($n = strpos($tCall, ':')) !== false) {
$tCalls[$i][$n] = ' ';
}
}
} elseif ($template1 != '' && $template1[0] == '~') {
// --------------------------------------------- looking for an xml-tag extension call
$template1 = substr($template1, 1);
$template2 = substr($template2, 1);
$defaultTemplate = substr($defaultTemplate, 1);
// looking for tags
$text2 = preg_replace('/\<\s*(' . $template1 . ')\s*\>/i', '°³²|%TAG%=\1|%TAGBODY%=', $text);
$tCalls = preg_split('/°³²/', ' ' . $text2);
foreach ($tCalls as $i => $tCall) {
$tCalls[$i] = preg_replace('/\<\s*\/' . $template1 . '\s*\>.*/is', '}}', $tCall);
}
} else {
// --------------------------------------------- looking for template call
// we accept plain text as a template name, space or underscore are the same
// the localized name for "Template:" may preceed the template name
// the name may start with a different namespace for the surrogate template, followed by ::
global $wgContLang;
$nsNames = $wgContLang->getNamespaces();
$tCalls = preg_split('/\{\{\s*(Template:|' . $nsNames[10] . ':)?' . self::spaceOrUnderscore(preg_quote($template1, '/')) . '\s*[|}]/i', ' ' . $text);
// We restore the first separator symbol (we had to include that symbol into the SPLIT, because we must make
// sure that we only accept exact matches of the complete template name
// (e.g. when looking for "foo" we must not accept "foo xyz")
foreach ($tCalls as $nr => $tCall) {
if ($tCall[0] == '}') {
$tCalls[$nr] = '}' . $tCall;
} else {
$tCalls[$nr] = '|' . $tCall;
}
}
}
$output = [];
$extractParm = [];
// check if we want to extract parameters directly from the call
// in that case we won´t invoke template2 but will directly return the extracted parameters
// as a sequence of table columns;
if (strlen($template2) > strlen($template1) && substr($template2, 0, strlen($template1) + 1) == ($template1 . ':')) {
$extractParm = preg_split('/:\s*/s', trim(substr($template2, strlen($template1) + 1)));
}
if (count($tCalls) <= 1) {
// template was not called (note that count will be 1 if there is no template invocation)
if (count($extractParm) > 0) {
// if parameters are required directly: return empty columns
if (count($extractParm) > 1) {
$output[0] = $lister->formatTemplateArg('', $dplNr, 0, true, -1, $article);
for ($i = 1; $i < count($extractParm); $i++) {
$output[0] .= "\n|" . $lister->formatTemplateArg('', $dplNr, $i, true, -1, $article);
}
} else {
$output[0] = $lister->formatTemplateArg('', $dplNr, 0, true, -1, $article);
}
} else {
// put a red link into the output
$output[0] = $parser->preprocess('{{' . $defaultTemplate . '|%PAGE%=' . $page . '|%TITLE%=' . $title->getText() . '|%DATE%=' . $date . '|%USER%=' . $user . '}}', $parser->mTitle, $parser->mOptions);
}
unset($title);
return $output;
}
$output[0] = '';
$n = -2;
// loop for all template invocations
$firstCall = true;
foreach ($tCalls as $iii => $tCall) {
if ($n == -2) {
$n++;
continue;
}
$c = $tCall[0];
// normally we construct a call for template2 with the parameters of template1
if (count($extractParm) == 0) {
// find the end of the call: bracket level must be zero
$cbrackets = 0;
$templateCall = '{{' . $template2 . $tCall;
$size = strlen($templateCall);
for ($i = 0; $i < $size; $i++) {
$c = $templateCall[$i];
if ($c == '{') {
$cbrackets++;
}
if ($c == '}') {
$cbrackets--;
}
if ($cbrackets == 0) {
// if we must match a condition: test against it
if (($mustMatch == '' || preg_match($mustMatch, substr($templateCall, 0, $i - 1))) && ($mustNotMatch == '' || !preg_match($mustNotMatch, substr($templateCall, 0, $i - 1)))) {
$invocation = substr($templateCall, 0, $i - 1);
$argChain = $invocation . '|%PAGE%=' . $page . '|%TITLE%=' . $title->getText();
if ($catlist != '') {
$argChain .= "|%CATLIST%=$catlist";
}
$argChain .= '|%DATE%=' . $date . '|%USER%=' . $user . '|%ARGS%=' . str_replace('|', '§', preg_replace('/[}]+/', '}', preg_replace('/[{]+/', '{', substr($invocation, strlen($template2) + 2)))) . '}}';
$output[++$n] = $parser->preprocess($argChain, $parser->mTitle, $parser->mOptions);
}
break;
}
}
} else {
// if the user wants parameters directly from the call line of template1 we return just those
$cbrackets = 2;
$templateCall = $tCall;
$size = strlen($templateCall);
$parms = [];
$parm = '';
$hasParm = false;
for ($i = 0; $i < $size; $i++) {
$c = $templateCall[$i];
if ($c == '{' || $c == '[') {
$cbrackets++; // we count both types of brackets
}
if ($c == '}' || $c == ']') {
$cbrackets--;
}
if ($cbrackets == 2 && $c == '|') {
$parms[] = trim($parm);
$hasParm = true;
$parm = '';
} else {
$parm .= $c;
}
if ($cbrackets == 0) {
if ($hasParm) {
$parms[] = trim(substr($parm, 0, strlen($parm) - 2));
}
array_splice($parms, 0, 1); // remove artifact;
// if we must match a condition: test against it
$callText = substr($templateCall, 0, $i - 1);
if (($mustMatch == '' || (($matchParsed && preg_match($mustMatch, $parser->recursiveTagParse($callText))) || (!$matchParsed && preg_match($mustMatch, $callText)))) && ($mustNotMatch == '' || (($matchParsed && !preg_match($mustNotMatch, $parser->recursiveTagParse($callText))) || (!$matchParsed && !preg_match($mustNotMatch, $callText))))) {
$output[++$n] = '';
$second = false;
foreach ($extractParm as $exParmKey => $exParm) {
$maxlen = -1;
if (($limpos = strpos($exParm, '[')) > 0 && $exParm[strlen($exParm) - 1] == ']') {
$maxlen = intval(substr($exParm, $limpos + 1, strlen($exParm) - $limpos - 2));
$exParm = substr($exParm, 0, $limpos);
}
if ($second) {
if ($output[$n] == '' || $output[$n][strlen($output[$n]) - 1] != "\n") {
$output[$n] .= "\n";
}
$output[$n] .= "|"; // \n";
}
$found = false;
// % in parameter name
if (strpos($exParm, '%') !== false) {
// %% is a short form for inclusion of %PAGE% and %TITLE%
$found = true;
$output[$n] .= $lister->formatTemplateArg($exParm, $dplNr, $exParmKey, $firstCall, $maxlen, $article);
}
if (!$found) {
// named parameter
$exParmQuote = str_replace('/', '\/', $exParm);
foreach ($parms as $parm) {
if (!preg_match("/^\s*$exParmQuote\s*=/", $parm)) {
continue;
}
$found = true;
$output[$n] .= $lister->formatTemplateArg(preg_replace("/^$exParmQuote\s*=\s*/", "", $parm), $dplNr, $exParmKey, $firstCall, $maxlen, $article);
break;
}
}
if (!$found && is_numeric($exParm) && intval($exParm) == $exParm) {
// numeric parameter
$np = 0;
foreach ($parms as $parm) {
if (strstr($parm, '=') === false) {
++$np;
}
if ($np != $exParm) {
continue;
}
$found = true;
$output[$n] .= $lister->formatTemplateArg($parm, $dplNr, $exParmKey, $firstCall, $maxlen, $article);
break;
}
}
if (!$found) {
$output[$n] .= $lister->formatTemplateArg('', $dplNr, $exParmKey, $firstCall, $maxlen, $article);
}
$second = true;
}
}
break;
}
}
}
$firstCall = false;
}
return $output;
}
public static function spaceOrUnderscore($pattern) {
// returns a pettern that matches underscores as well as spaces
return str_replace(' ', '[ _]', $pattern);
}
}

80
classes/Logger.php Normal file
View file

@ -0,0 +1,80 @@
<?php
/**
* DynamicPageList3
* DPL Logger Class
*
* @author IlyaHaykinson, Unendlich, Dangerville, Algorithmix, Theaitetos, Alexia E. Smith
* @license GPL-2.0-or-later
* @package DynamicPageList3
*
**/
namespace DPL;
class Logger {
/**
* Buffer of debug messages.
*
* @var array
*/
private $buffer = [];
/**
* Function Documentation
*
* @access public
* @return void
*/
public function addMessage($errorId) {
$args = func_get_args();
$args = array_map('htmlspecialchars', $args);
return call_user_func_array([$this, 'msg'], $args);
}
/**
* Return the buffer of messages.
*
* @access public
* @param boolean [Optional] Clear the message buffer.
* @return array Messages in the order added.
*/
public function getMessages($clearBuffer = true) {
$buffer = $this->buffer;
if ($clearBuffer === true) {
$this->buffer = [];
}
return $buffer;
}
/**
* Get a message, with optional parameters
* Parameters from user input must be escaped for HTML *before* passing to this function
*
* @access public
* @param integer Message ID
* @return string
*/
public function msg() {
$args = func_get_args();
$errorId = array_shift($args);
$errorLevel = floor($errorId / 1000);
$errorMessageId = $errorId % 1000;
if (\DynamicPageListHooks::getDebugLevel() >= $errorLevel) {
if (\DynamicPageListHooks::isLikeIntersection()) {
if ($errorId == \DynamicPageListHooks::FATAL_TOOMANYCATS) {
$text = wfMessage('intersection_toomanycats', $args)->text();
} elseif ($errorId == \DynamicPageListHooks::FATAL_TOOFEWCATS) {
$text = wfMessage('intersection_toofewcats', $args)->text();
} elseif ($errorId == \DynamicPageListHooks::WARN_NORESULTS) {
$text = wfMessage('intersection_noresults', $args)->text();
} elseif ($errorId == \DynamicPageListHooks::FATAL_NOSELECTION) {
$text = wfMessage('intersection_noincludecats', $args)->text();
}
}
if (empty($text)) {
$text = wfMessage('dpl_log_' . $errorMessageId, $args)->text();
}
$this->buffer[] = '<p>Extension:DynamicPageList (DPL), version ' . DPL_VERSION . ': ' . $text . '</p>';
}
return false;
}
}

1332
classes/Parameters.php Normal file

File diff suppressed because it is too large Load diff

1379
classes/ParametersData.php Normal file

File diff suppressed because it is too large Load diff

1006
classes/Parse.php Normal file

File diff suppressed because it is too large Load diff

2142
classes/Query.php Normal file

File diff suppressed because it is too large Load diff

623
classes/UpdateArticle.php Normal file
View file

@ -0,0 +1,623 @@
<?php
/**
* DynamicPageList3
* DPL UpdateArticle Class
*
* @license GPL-2.0-or-later
* @package DynamicPageList3
*
**/
namespace DPL;
class UpdateArticle {
/**
* this fucntion hast three tasks (depending on $exec):
* (1) show an edit dialogue for template fields (exec = edit)
* (2) set template parameters to values specified in the query (exec=set)v
* (2) preview the source code including any changes of these parameters made in the edit form or with other changes (exec=preview)
* (3) save the article with the changed value set or with other changes (exec=save)
* "other changes" means that a regexp can be applied to the source text or arbitrary text can be
* inserted before or after a pattern occuring in the text
*/
public static function updateArticleByRule($title, $text, $rulesText) {
// we use ; as command delimiter; \; stands for a semicolon
// \n is translated to a real linefeed
$rulesText = str_replace(";", '°', $rulesText);
$rulesText = str_replace('\°', ';', $rulesText);
$rulesText = str_replace("\\n", "\n", $rulesText);
$rules = explode('°', $rulesText);
$exec = 'edit';
$replaceThis = '';
$replacement = '';
$after = '';
$insertionAfter = '';
$before = '';
$insertionBefore = '';
$template = '';
$parameter = [];
$value = [];
$afterparm = [];
$format = [];
$preview = [];
$save = [];
$tooltip = [];
$optional = [];
$lastCmd = '';
$message = '';
$summary = '';
$editForm = false;
$action = '';
$hidden = [];
$legendPage = '';
$instructionPage = '';
$table = '';
$fieldFormat = '';
// $message .= 'updaterules=<pre><nowiki>';
$nr = -1;
foreach ($rules as $rule) {
if (preg_match('/^\s*#/', $rule) > 0) {
continue; // # is comment symbol
}
$rule = preg_replace('/^[\s]*/', '', $rule); // strip leading white space
$cmd = preg_split("/ +/", $rule, 2);
if (count($cmd) > 1) {
$arg = $cmd[1];
} else {
$arg = '';
}
$cmd[0] = trim($cmd[0]);
// after ... insert ... , before ... insert ...
if ($cmd[0] == 'before') {
$before = $arg;
$lastCmd = 'B';
}
if ($cmd[0] == 'after') {
$after = $arg;
$lastCmd = 'A';
}
if ($cmd[0] == 'insert' && $lastCmd != '') {
if ($lastCmd == 'A') {
$insertionAfter = $arg;
}
if ($lastCmd == 'B') {
$insertionBefore = $arg;
}
}
if ($cmd[0] == 'template') {
$template = $arg;
}
if ($cmd[0] == 'parameter') {
$nr++;
$parameter[$nr] = $arg;
if ($nr > 0) {
$afterparm[$nr] = [
$parameter[$nr - 1]
];
$n = $nr - 1;
while ($n > 0 && array_key_exists($n, $optional)) {
$n--;
$afterparm[$nr][] = $parameter[$n];
}
}
}
if ($cmd[0] == 'value') {
$value[$nr] = $arg;
}
if ($cmd[0] == 'format') {
$format[$nr] = $arg;
}
if ($cmd[0] == 'tooltip') {
$tooltip[$nr] = $arg;
}
if ($cmd[0] == 'optional') {
$optional[$nr] = true;
}
if ($cmd[0] == 'afterparm') {
$afterparm[$nr] = [
$arg
];
}
if ($cmd[0] == 'legend') {
$legendPage = $arg;
}
if ($cmd[0] == 'instruction') {
$instructionPage = $arg;
}
if ($cmd[0] == 'table') {
$table = $arg;
}
if ($cmd[0] == 'field') {
$fieldFormat = $arg;
}
if ($cmd[0] == 'replace') {
$replaceThis = $arg;
}
if ($cmd[0] == 'by') {
$replacement = $arg;
}
if ($cmd[0] == 'editform') {
$editForm = $arg;
}
if ($cmd[0] == 'action') {
$action = $arg;
}
if ($cmd[0] == 'hidden') {
$hidden[] = $arg;
}
if ($cmd[0] == 'preview') {
$preview[] = $arg;
}
if ($cmd[0] == 'save') {
$save[] = $arg;
}
if ($cmd[0] == 'summary') {
$summary = $arg;
}
if ($cmd[0] == 'exec') {
$exec = $arg; // desired action (set or edit or preview)
}
}
if ($summary == '') {
$summary .= "\nbulk update:";
if ($replaceThis != '') {
$summary .= "\n replace $replaceThis\n by $replacement";
}
if ($before != '') {
$summary .= "\n before $before\n insertionBefore";
}
if ($after != '') {
$summary .= "\n after $after\n insertionAfter";
}
}
// $message.= '</nowiki></pre>';
// perform changes to the wiki source text =======================================
if ($replaceThis != '') {
$text = preg_replace("$replaceThis", $replacement, $text);
}
if ($insertionBefore != '' && $before != '') {
$text = preg_replace("/($before)/", $insertionBefore . '\1', $text);
}
if ($insertionAfter != '' && $after != '') {
$text = preg_replace("/($after)/", '\1' . $insertionAfter, $text);
}
// deal with template parameters =================================================
global $wgRequest, $wgUser;
if ($template != '') {
if ($exec == 'edit') {
$tpv = self::getTemplateParmValues($text, $template);
$legendText = '';
if ($legendPage != '') {
$legendTitle = '';
global $wgParser, $wgUser;
$parser = clone $wgParser;
LST::text($parser, $legendPage, $legendTitle, $legendText);
$legendText = preg_replace('/^.*?\<section\s+begin\s*=\s*legend\s*\/\>/s', '', $legendText);
$legendText = preg_replace('/\<section\s+end\s*=\s*legend\s*\/\>.*/s', '', $legendText);
}
$instructionText = '';
$instructions = [];
if ($instructionPage != '') {
$instructionTitle = '';
global $wgParser, $wgUser;
$parser = clone $wgParser;
LST::text($parser, $instructionPage, $instructionTitle, $instructionText);
$instructions = self::getTemplateParmValues($instructionText, 'Template field');
}
// construct an edit form containing all template invocations
$form = "<html><form method=post action=\"$action\" $editForm>\n";
foreach ($tpv as $call => $tplValues) {
$form .= "<table $table>\n";
foreach ($parameter as $nr => $parm) {
// try to extract legend from the docs of the template
$myToolTip = '';
if (array_key_exists($nr, $tooltip)) {
$myToolTip = $tooltip[$nr];
}
$myInstruction = '';
$myType = '';
foreach ($instructions as $instruct) {
if (array_key_exists('field', $instruct) && $instruct['field'] == $parm) {
if (array_key_exists('doc', $instruct)) {
$myInstruction = $instruct['doc'];
}
if (array_key_exists('type', $instruct)) {
$myType = $instruct['type'];
}
break;
}
}
$myFormat = '';
if (array_key_exists($nr, $format)) {
$myFormat = $format[$nr];
}
$myOptional = array_key_exists($nr, $optional);
if ($legendText != '' && $myToolTip == '') {
$myToolTip = preg_replace('/^.*\<section\s+begin\s*=\s*' . preg_quote($parm, '/') . '\s*\/\>/s', '', $legendText);
if (strlen($myToolTip) == strlen($legendText)) {
$myToolTip = '';
} else {
$myToolTip = preg_replace('/\<section\s+end\s*=\s*' . preg_quote($parm, '/') . '\s*\/\>.*/s', '', $myToolTip);
}
}
$myValue = '';
if (array_key_exists($parm, $tpv[$call])) {
$myValue = $tpv[$call][$parm];
}
$form .= self::editTemplateCall($text, $template, $call, $parm, $myType, $myValue, $myFormat, $myToolTip, $myInstruction, $myOptional, $fieldFormat);
}
$form .= "</table>\n<br/><br/>";
}
foreach ($hidden as $hide) {
$form .= "<input type='hidden' " . $hide . " />";
}
$form .= "<input type='hidden' name='wpEditToken' value='{$wgUser->getEditToken()}'/>";
foreach ($preview as $prev) {
$form .= "<input type='submit' " . $prev . " /> ";
}
$form .= "</form></html>\n";
return $form;
} elseif ($exec == 'set' || $exec == 'preview') {
// loop over all invocations and parameters, this could be improved to enhance performance
$matchCount = 10;
for ($call = 0; $call < 10; $call++) {
foreach ($parameter as $nr => $parm) {
// set parameters to values specified in the dpl source or get them from the http request
if ($exec == 'set') {
$myvalue = $value[$nr];
} else {
if ($call >= $matchCount) {
break;
}
$myValue = $wgRequest->getVal(urlencode($call . '_' . $parm), '');
}
$myOptional = array_key_exists($nr, $optional);
$myAfterParm = [];
if (array_key_exists($nr, $afterparm)) {
$myAfterParm = $afterparm[$nr];
}
$text = self::updateTemplateCall($matchCount, $text, $template, $call, $parm, $myValue, $myAfterParm, $myOptional);
}
if ($exec == 'set') {
break; // values taken from dpl text only populate the first invocation
}
}
}
}
if ($exec == 'set') {
return self::updateArticle($title, $text, $summary);
} elseif ($exec == 'preview') {
global $wgScriptPath, $wgRequest;
$titleX = \Title::newFromText($title);
$articleX = new \Article($titleX);
$form = '<html>
<form id="editform" name="editform" method="post" action="' . $wgScriptPath . '/index.php?title=' . urlencode($title) . '&action=submit" enctype="multipart/form-data">
<input type="hidden" value="" name="wpSection" />
<input type="hidden" value="' . wfTimestampNow() . '" name="wpStarttime" />
<input type="hidden" value="' . $articleX->getTimestamp() . '" name="wpEdittime" />
<input type="hidden" value="" name="wpScrolltop" id="wpScrolltop" />
<textarea tabindex="1" accesskey="," name="wpTextbox1" id="wpTextbox1" rows="' . $wgUser->getIntOption('rows') . '" cols="' . $wgUser->getIntOption('cols') . '" >' . htmlspecialchars($text) . '</textarea>
<input type="hidden" name="wpSummary value="' . $summary . '" id="wpSummary" />
<input name="wpAutoSummary" type="hidden" value="" />
<input id="wpSave" name="wpSave" type="submit" value="Save page" accesskey="s" title="Save your changes [s]" />
<input type="hidden" value="' . $wgRequest->getVal('token') . '" name="wpEditToken" />
</form>
</html>';
return $form;
}
return "exec must be one of the following: edit, preview, set";
}
private static function updateArticle($title, $text, $summary) {
global $wgUser, $wgRequest, $wgOut;
if (!$wgUser->matchEditToken($wgRequest->getVal('wpEditToken'))) {
$wgOut->addWikiMsg('sessionfailure');
return 'session failure';
}
$titleX = \Title::newFromText($title);
$permission_errors = $titleX->getUserPermissionsErrors('edit', $wgUser);
if (count($permission_errors) == 0) {
$articleX = \WikiPage::factory($titleX);
$articleXContent = \ContentHandler::makeContent($text, $titleX);
$articleX->doEditContent($articleXContent, $summary, EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY);
$wgOut->redirect($titleX->getFullUrl($articleX->isRedirect() ? 'redirect=no' : ''));
return '';
} else {
$wgOut->showPermissionsErrorPage($permission_errors);
return 'permission error';
}
}
private static function editTemplateCall($text, $template, $call, $parameter, $type, $value, $format, $legend, $instruction, $optional, $fieldFormat) {
$matches = [];
$nlCount = preg_match_all('/\n/', $value, $matches);
if ($nlCount > 0) {
$rows = $nlCount + 1;
} else {
$rows = floor(strlen($value) / 50) + 1;
}
if (preg_match('/rows\s*=/', $format) <= 0) {
$format .= " rows=$rows";
}
$cols = 50;
if (preg_match('/cols\s*=/', $format) <= 0) {
$format .= " cols=$cols";
}
$textArea = "<textarea name=\"" . urlencode($call . '_' . $parameter) . "\" $format/>" . htmlspecialchars($value) . "</textarea>";
return str_replace('%NAME%', htmlspecialchars(str_replace('_', ' ', $parameter)), str_replace('%TYPE%', $type, str_replace('%INPUT%', $textArea, str_replace('%LEGEND%', "</html>" . htmlspecialchars($legend) . "<html>", str_replace('%INSTRUCTION%', "</html>" . htmlspecialchars($instruction) . "<html>", $fieldFormat)))));
}
/**
* return an array of template invocations; each element is an associative array of parameter and value
*/
private static function getTemplateParmValues($text, $template) {
$matches = [];
$noMatches = preg_match_all('/\{\{\s*' . preg_quote($template, '/') . '\s*[|}]/i', $text, $matches, PREG_OFFSET_CAPTURE);
if ($noMatches <= 0) {
return '';
}
$textLen = strlen($text);
$tval = []; // the result array of template values
$call = -1; // index for tval
foreach ($matches as $matchA) {
foreach ($matchA as $matchB) {
$match = $matchB[0];
$start = $matchB[1];
$tval[++$call] = [];
$nr = 0; // number of parameter if no name given
$parmValue = '';
$parmName = '';
$parm = '';
if ($match[strlen($match) - 1] == '}') {
break; // template was called without parameters, continue with next invocation
}
// search to the end of the template call
$cbrackets = 2;
for ($i = $start + strlen($match); $i < $textLen; $i++) {
$c = $text[$i];
if ($c == '{' || $c == '[') {
$cbrackets++; // we count both types of brackets
}
if ($c == '}' || $c == ']') {
$cbrackets--;
}
if (($cbrackets == 2 && $c == '|') || ($cbrackets == 1 && $c == '}')) {
// parameter (name or value) found
if ($parmName == '') {
$tval[$call][++$nr] = trim($parm);
} else {
$tval[$call][$parmName] = trim($parmValue);
}
$parmName = '';
$parmValue = '';
$parm = '';
continue;
} else {
if ($parmName == '') {
if ($c == '=') {
$parmName = trim($parm);
}
} else {
$parmValue .= $c;
}
}
$parm .= $c;
if ($cbrackets == 0) {
break; // end of parameter list
}
}
}
}
return $tval;
}
/*
* Changes a single parameter value within a certain call of a template
*/
private static function updateTemplateCall(&$matchCount, $text, $template, $call, $parameter, $value, $afterParm, $optional) {
// if parameter is optional and value is empty we leave everything as it is (i.e. we do not remove the parm)
if ($optional && $value == '') {
return $text;
}
$matches = [];
$noMatches = preg_match_all('/\{\{\s*' . preg_quote($template, '/') . '\s*[|}]/i', $text, $matches, PREG_OFFSET_CAPTURE);
if ($noMatches <= 0) {
return $text;
}
$beginSubst = -1;
$endSubst = -1;
$posInsertAt = 0;
$apNrLast = 1000; // last (optional) predecessor
foreach ($matches as $matchA) {
$matchCount = count($matchA);
foreach ($matchA as $occurence => $matchB) {
if ($occurence < $call) {
continue;
}
$match = $matchB[0];
$start = $matchB[1];
if ($match[strlen($match) - 1] == '}') {
// template was called without parameters, add new parameter and value
// append parameter and value
$beginSubst = $i;
$endSubst = $i;
$substitution = "|$parameter = $value";
break;
} else {
// there is already a list of parameters; we search to the end of the template call
$cbrackets = 2;
$parm = '';
$pos = $start + strlen($match) - 1;
$textLen = strlen($text);
for ($i = $pos + 1; $i < $textLen; $i++) {
$c = $text[$i];
if ($c == '{' || $c == '[') {
$cbrackets++; // we count both types of brackets
}
if ($c == '}' || $c == ']') {
$cbrackets--;
}
if (($cbrackets == 2 && $c == '|') || ($cbrackets == 1 && $c == '}')) {
// parameter (name / value) found
$token = explode('=', $parm, 2);
if (count($token) == 2) {
// we need a pair of name / value
$parmName = trim($token[0]);
if ($parmName == $parameter) {
// we found the parameter, now replace the current value
$parmValue = trim($token[1]);
if ($parmValue == $value) {
break; // no need to change when values are identical
}
// keep spaces;
if ($parmValue == '') {
if (strlen($token[1]) > 0 && $token[1][strlen($token[1]) - 1] == "\n") {
$substitution = str_replace("\n", $value . "\n", $token[1]);
} else {
$substitution = $value . $token[1];
}
} else {
$substitution = str_replace($parmValue, $value, $token[1]);
}
$beginSubst = $pos + strlen($token[0]) + 2;
$endSubst = $i;
break;
} else {
foreach ($afterParm as $apNr => $ap) {
// store position for insertion
if ($parmName == $ap && $apNr < $apNrLast) {
$posInsertAt = $i;
$apNrLast = $apNr;
break;
}
}
}
}
if ($c == '}') {
// end of template call reached, insert at stored position or here
if ($posInsertAt != 0) {
$beginSubst = $posInsertAt;
} else {
$beginSubst = $i;
}
$substitution = "|$parameter = $value";
if ($text[$beginSubst - 1] == "\n") {
--$beginSubst;
$substitution = "\n" . $substitution;
}
$endSubst = $beginSubst;
break;
}
$pos = $i;
$parm = '';
} else {
$parm .= $c;
}
if ($cbrackets == 0) {
break;
}
}
}
break;
}
break;
}
if ($beginSubst < 0) {
return $text;
}
return substr($text, 0, $beginSubst) . $substitution . substr($text, $endSubst);
}
public function deleteArticleByRule($title, $text, $rulesText) {
global $wgUser, $wgOut;
// return "deletion of articles by DPL is disabled.";
// we use ; as command delimiter; \; stands for a semicolon
// \n is translated to a real linefeed
$rulesText = str_replace(";", '°', $rulesText);
$rulesText = str_replace('\°', ';', $rulesText);
$rulesText = str_replace("\\n", "\n", $rulesText);
$rules = explode('°', $rulesText);
$exec = false;
$message = '';
$reason = '';
foreach ($rules as $rule) {
if (preg_match('/^\s*#/', $rule) > 0) {
continue; // # is comment symbol
}
$rule = preg_replace('/^[\s]*/', '', $rule); // strip leading white space
$cmd = preg_split("/ +/", $rule, 2);
if (count($cmd) > 1) {
$arg = $cmd[1];
} else {
$arg = '';
}
$cmd[0] = trim($cmd[0]);
if ($cmd[0] == 'reason') {
$reason = $arg;
}
// we execute only if "exec" is given, otherwise we merely show what would be done
if ($cmd[0] == 'exec') {
$exec = true;
}
}
$reason .= "\nbulk delete by DPL query";
$titleX = \Title::newFromText($title);
if ($exec) {
# Check permissions
$permission_errors = $titleX->getUserPermissionsErrors('delete', $wgUser);
if (count($permission_errors) > 0) {
$wgOut->showPermissionsErrorPage($permission_errors);
return 'permission error';
} elseif (wfReadOnly()) {
$wgOut->readOnlyPage();
return 'DPL: read only mode';
} else {
$articleX = new \Article($titleX);
$articleX->doDelete($reason);
}
} else {
$message .= "set 'exec yes' to delete &#160; &#160; <big>'''$title'''</big>\n";
}
$message .= "<pre><nowiki>\n{$text}</nowiki></pre>"; // <pre><nowiki>\n"; // .$text."\n</nowiki></pre>\n";
return $message;
}
}

136
classes/Variables.php Normal file
View file

@ -0,0 +1,136 @@
<?php
/**
* DynamicPageList3
* DPL Variables Class
*
* @author IlyaHaykinson, Unendlich, Dangerville, Algorithmix, Theaitetos, Alexia E. Smith
* @license GPL-2.0-or-later
* @package DynamicPageList3
*
**/
namespace DPL;
class Variables {
/**
* Memory storage for variables.
*
* @var array
*/
static public $memoryVar = [];
/**
* Memory storage for arrays of variables.
*
* @var array
*/
static public $memoryArray = [];
// expects pairs of 'variable name' and 'value'
// if the first parameter is empty it will be ignored {{#vardefine:|a|b}} is the same as {{#vardefine:a|b}}
public static function setVar($arg) {
$numargs = count($arg);
if ($numargs >= 3 && $arg[2] == '') {
$start = 3;
} else {
$start = 2;
}
for ($i = $start; $i < $numargs; $i++) {
$var = $arg[$i];
if (++$i <= $numargs - 1) {
self::$memoryVar[$var] = $arg[$i];
} else {
self::$memoryVar[$var] = '';
}
}
return '';
}
public static function setVarDefault($arg) {
$numargs = count($arg);
if ($numargs > 3) {
$value = $arg[3];
} else {
return '';
}
$var = $arg[2];
if (!array_key_exists($var, self::$memoryVar) || self::$memoryVar[$var] == '') {
self::$memoryVar[$var] = $value;
}
return '';
}
public static function getVar($var) {
if (array_key_exists($var, self::$memoryVar)) {
return self::$memoryVar[$var];
}
return '';
}
public static function setArray($arg) {
$numargs = count($arg);
if ($numargs < 5) {
return '';
}
$var = trim($arg[2]);
$value = $arg[3];
$delimiter = $arg[4];
if ($var == '') {
return '';
}
if ($value == '') {
self::$memoryArray[$var] = [];
return;
}
if ($delimiter == '') {
self::$memoryArray[$var] = [
$value
];
return;
}
if (0 !== strpos($delimiter, '/') || (strlen($delimiter) - 1) !== strrpos($delimiter, '/')) {
$delimiter = '/\s*' . $delimiter . '\s*/';
}
self::$memoryArray[$var] = preg_split($delimiter, $value);
return "value={$value}, delimiter={$delimiter}," . count(self::$memoryArray[$var]);
}
public static function dumpArray($arg) {
$numargs = count($arg);
if ($numargs < 3) {
return '';
}
$var = trim($arg[2]);
$text = " array {$var} = {";
$n = 0;
if (array_key_exists($var, self::$memoryArray)) {
foreach (self::$memoryArray[$var] as $value) {
if ($n++ > 0) {
$text .= ', ';
}
$text .= "{$value}";
}
}
return $text . "}\n";
}
public static function printArray($var, $delimiter, $search, $subject) {
$var = trim($var);
if ($var == '') {
return '';
}
if (!array_key_exists($var, self::$memoryArray)) {
return '';
}
$values = self::$memoryArray[$var];
$rendered_values = [];
foreach ($values as $v) {
$temp_result_value = str_replace($search, $v, $subject);
$rendered_values[] = $temp_result_value;
}
return [
implode($delimiter, $rendered_values),
'noparse' => false,
'isHTML' => false
];
}
}