diff --git a/.github/workflows/mediawiki-tests.yml b/.github/workflows/mediawiki-tests.yml index 102c1f9f..da01f76e 100644 --- a/.github/workflows/mediawiki-tests.yml +++ b/.github/workflows/mediawiki-tests.yml @@ -203,10 +203,10 @@ jobs: stage: qunit # Latest stable MediaWiki - PHP 7.3 (composer-test) - - mw: 'REL1_36' + - mw: 'REL1_37' php: 7.3 php-docker: 73 - experimental: true + experimental: false stage: composer-test runs-on: ubuntu-latest @@ -351,17 +351,19 @@ jobs: if [ -e composer.json ]; then composer install --prefer-dist --no-progress --no-interaction composer fix - rm composer.lock - - git config --global user.name "github-actions" - git config --global user.email "github-actions@users.noreply.github.com" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git checkout -b ${GITHUB_HEAD_REF} - git add . - git commit -am "CI: lint code to MediaWiki standards" -m "Check commit and GitHub actions for more details" || echo "No changes to commit" - git pull origin ${GITHUB_HEAD_REF} --rebase - git push --set-upstream origin ${GITHUB_HEAD_REF} + if ! git diff --exit-code --quiet; then + git config --global user.name "github-actions" + git config --global user.email "github-actions@users.noreply.github.com" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git checkout -b ${GITHUB_HEAD_REF} + git add . + git commit -am "CI: lint code to MediaWiki standards" -m "Check commit and GitHub actions for more details" + git pull origin ${GITHUB_HEAD_REF} --rebase + git push --set-upstream origin ${GITHUB_HEAD_REF} + else + echo "No changes to commit" + fi fi - name: Main Test diff --git a/.phpcs.xml b/.phpcs.xml index 8e445048..c3ca0fa9 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -8,7 +8,6 @@ - diff --git a/README.md b/README.md index ba3330f4..06d8b382 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ The **DynamicPageList3** extension is a reporting tool for MediaWiki, listing category members and intersections with various formats and details. For full documentation, see the [manual](https://help.fandom.com/Extension:DPL3/Manual). -When invoked with a basic set of selection parameters DPL displays a list of pages in one or more categories. Selections may also be based on factors such as author, namespace, date, name pattern, usage of templates, or references to other articles. Output takes a variety of forms, some of which incorporate elements of selected articles. +When invoked with a basic set of selection parameters DPL3 displays a list of pages in one or more categories. Selections may also be based on factors such as author, namespace, date, name pattern, usage of templates, or references to other articles. Output takes a variety of forms, some of which incorporate elements of selected articles. -This extension is invoked with the parser function {{#dpl: .... }} or parser tag <DPL>. A [Wikimedia](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:DynamicPageList_(Wikimedia))-compatible implementation of certain features can be invoked with <DynamicPageList>. +This extension is invoked with the parser function {{#dpl: .... }} or parser tag <DPL>. A [Wikimedia](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:DynamicPageList_(Wikimedia))-compatible implementation of certain features can be invoked with <DynamicPageList>. -Complex look ups can result in computationally expensive database queries. However, by default all output is cached for a period of one hour to reduce the need to rerun the query every page load. The [DPL:Parameters: Other Parameters](https://help.fandom.com/Extension:DPL3/Parameters:_Other_parameters#cacheperiod) manual page contains information on parameters that can be used to disable the cache and allow instant updates. +Complex look ups can result in computationally expensive database queries. However, by default all output is cached for a period of one hour to reduce the need to rerun the query every page load. The [DPL:Parameters: Other Parameters](https://help.fandom.com/Extension:DPL3/Parameters:_Other_parameters#cacheperiod) manual page contains information on parameters that can be used to disable the cache and allow instant updates. * Manual and Complete Documentation: [Documentation](https://help.fandom.com/Extension:DPL3/Manual) * Source Code: [Source code at GitHub](https://github.com/Universal-Omega/DynamicPageList3) @@ -18,49 +18,51 @@ Complex look ups can result in computationally expensive database queries. Howe Please see the [releases page](https://github.com/Universal-Omega/DynamicPageList3/releases) for the latest releases. ## Configuration -These are DPL's configuration settings and along with their default values. To change them make sure they are defined before including the extension on the wiki. More configuration information is available on the **[MediaWiki extension page](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:DynamicPageList3#Configuration)**. +These are DPL3's configuration settings and along with their default values. To change them make sure they are defined before including the extension on the wiki. More configuration information is available on the **[MediaWiki extension page](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:DynamicPageList3#Configuration)**. -**Note:** In release 3.0.4 the configuration variable name was changed from $dplSettings to $wgDplSettings. This was to faciliate compatibility with Mediawiki 1.25's extension registration change. +**Note:** In release 3.0.4 the configuration variable name was changed from $dplSettings to $wgDplSettings. This was to faciliate compatibility with Mediawiki 1.25's extension registration change. | Setting | Default | Description | |:--------------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| $wgDplSettings['allowedNamespaces'] | null | By default all existing namespaces are used when DPL initializes. Customize this setting with an array of namespace constants to restrict DPL to work only in those namespaces. | +| $wgDplSettings['allowedNamespaces'] | null | By default all existing namespaces are used when DPL3 initializes. Customize this setting with an array of namespace constants to restrict DPL3 to work only in those namespaces. | | $wgDplSettings['allowUnlimitedCategories'] | false | Set this to true to ignore 'maxCategoryCount' and allow unlimited categories. Please note that large amounts of categories in a query can slow down or crash servers. | | $wgDplSettings['allowUnlimitedResults'] | false | Set this to true to ignore 'maxResultCount' and allow unlimited results. Please note that large result sets may result in slow or failed page loads. | -| $wgDplSettings['behavingLikeIntersection'] | false | Set DPL to always behave like Extension:Intersection. | +| $wgDplSettings['behavingLikeIntersection'] | false | Set DPL3 to always behave like Extension:Intersection. | | $wgDplSettings['categoryStyleListCutoff'] | 6 | Maximum number of items in a category list before being cut off. | -| $wgDplSettings['fixedCategories'] | [] | This does something with preventing DPL from "looking" at these categories. | +| $wgDplSettings['fixedCategories'] | [] | This does something with preventing DPL3 from "looking" at these categories. | | $wgDplSettings['functionalRichness'] | 3 | Set the level of parameters available to end users. | | $wgDplSettings['maxCategoryCount'] | 4 | Maximum number of categories to allow in queries. | | $wgDplSettings['minCategoryCount'] | 0 | Minimum number of categories to allow in queries. | | $wgDplSettings['maxResultCount'] | 500 | Maximum number of results to return from a query. | -| $wgDplSettings['recursiveTagParse'] | false | Do recursive tag parsing on parser tags converting tags and functions such as magic words like {{PAGENAME}}. This is similar to the {{#dpl}} parser function call, but may not work exactly the same in all cases. | -| $wgDplSettings['runFromProtectedPagesOnly'] | false | Set this to true to allow DPL to run from protected pages only. This is recommend if wiki administrators are having issues with malicious users creating computationally intensive queries. | -| $wgDplSettings['handleSectionTag'] | false | Set this to true to have DPL handle
tags outside of DPL parser tags. | +| $wgDplSettings['recursiveTagParse'] | false | Do recursive tag parsing on parser tags converting tags and functions such as magic words like {{PAGENAME}}. This is similar to the {{#dpl}} parser function call, but may not work exactly the same in all cases. | +| $wgDplSettings['runFromProtectedPagesOnly'] | false | Set this to true to allow DPL3 to run from protected pages only. This is recommend if wiki administrators are having issues with malicious users creating computationally intensive queries. | +| $wgDplSettings['handleSectionTag'] | false | Set this to true to have DPL3 handle
tags outside of the parser tags provided by DPL3. | +| $wgDplSettings['maxQueryTime'] | 10000 | Maximum allowed time for database queries in milliseconds. | +| $wgDplSettings['queryCacheTime'] | 0 | Can help with situations where you have a template with the same query used on a large number of pages all being refreshed at once. The query cache cannot be purged. Suggested value between 30 to 600. | -The global variable {{manual|$wgNonincludableNamespaces}} is automatically respected by DPL. It will prevent the contents of the listed namespaces from appearing in DPL's output. +The global variable $wgNonincludableNamespaces is automatically respected by DPL3. It will prevent the contents of the listed namespaces from appearing in DPL3's output. -**Note: $wgDplSettings['maxResultCount'] is a LIMIT *on the SQL query itself*. Some DPL query parameters like includematch are applied *after* the SQL query, however, so results here may easily be misleading.** +**Note: $wgDplSettings['maxResultCount'] is a LIMIT *on the SQL query itself*. Some DPL3 query parameters like includematch are applied *after* the SQL query, however, so results here may easily be misleading.** ### Functional Richness -DynamicPageList3 has many features which are unlocked based on the maximum functional richness level. There are some that can cause high CPU or database load and should be used sparingly. +DynamicPageList3 has many features which are unlocked based on the maximum functional richness level. There are some that can cause high CPU or database load and should be used sparingly. * $wgDplSettings['functionalRichness'] = 0 is equivalent to Wikimedia's [DynamicPageList](https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:DynamicPageList_(Wikimedia)) * $wgDplSettings['functionalRichness'] = 1 adds additional formatting parameters * $wgDplSettings['functionalRichness'] = 2 adds performance equivalent features for templates and pagelinks * $wgDplSettings['functionalRichness'] = 3 allows more-expensive page inclusion features and regular expression queries. -* $wgDplSettings['functionalRichness'] = 4 permits exotic and potentially dangerous batch update and delete operations; not recommended for public websites. Includes debugging parameters for testing and development. +* $wgDplSettings['functionalRichness'] = 4 permits exotic and potentially dangerous batch update and delete operations; not recommended for public websites. Includes debugging parameters for testing and development. ## Usage -### Extended DPL Functionality -Extended DPL is invoked by using the parser function {{#dpl: .... }}, or the parser extension tag .... . +### Extended DPL3 Functionality +Extended DPL3 is invoked by using the parser function {{#dpl: .... }}, or the parser extension tag <DPL> .... </DPL>. *See [Manual - **General Usage and Invocation Syntax**](https://help.fandom.com/Extension:DPL3/General_usage_and_invocation_syntax) and [DPL:Parameters: **Criteria for Page Selection**](https://help.fandom.com/Extension:DPL3/Parameters:_Criteria_for_page_selection)* ### Backwards Compatibility -Functionality compatible with Wikimedia's DPL extension can be invoked with <DynamicPageList> .... </DynamicPageList>. Further information can be found on the [Compatibility manual page](https://help.fandom.com/Extension:DPL3/Compatibility). +Functionality compatible with Wikimedia's DPL extension (Intersection) can be invoked with <DynamicPageList> .... </DynamicPageList>. Further information can be found on the [Compatibility manual page](https://help.fandom.com/Extension:DPL3/Compatibility). ## Usage Philosophy and Overview With the assumption there are some articles writtne about *countries* those articles will typically have three things in common: @@ -77,7 +79,7 @@ category=countries }} -With DPL one could: +With DPL3 one could: * Generate a list of all those articles (or a random sample) * Show metadata of the articles (popularity, date of last update, ..) * Show one or more chapters of the articles ('transclude' content) @@ -132,8 +134,8 @@ With DPL one could: ## Considerations ### Performance -DPL's code execution and database access is typically fast for typical category and article look ups. However, using loose LIKE and REGEXP match parameters and/or requesting large data sets can result in long database access times. Parser time should also be kept in consideration. For example, having the query of image results go into a template that displays them will result in a parser media transform for each one. This can quickly eat up 2MBs of RAM per media transform. +DPL3's code execution and database access is typically fast for typical category and article look ups. However, using loose LIKE and REGEXP match parameters and/or requesting large data sets can result in long database access times. Parser time should also be kept in consideration. For example, having the query of image results go into a template that displays them will result in a parser media transform for each one. This can quickly eat up 2MBs of RAM per media transform. ## See Also ### Further Reading -DPL can do much more than we can explain here. A complete **[manual](https://help.fandom.com/Extension:DPL3/Manual)** is available with full parameter documentation. +DPL3 can do much more than we can explain here. A complete **[manual](https://help.fandom.com/Extension:DPL3/Manual)** is available with full parameter documentation. diff --git a/extension.json b/extension.json index caf80e22..f5700662 100644 --- a/extension.json +++ b/extension.json @@ -83,7 +83,9 @@ "recursiveTagParse": false, "runFromProtectedPagesOnly": false, "handleSectionTag": false, - "alwaysCacheResults": false + "alwaysCacheResults": false, + "maxQueryTime": 10000, + "queryCacheTime": 0 } }, "TrackingCategories": [ diff --git a/i18n/en.json b/i18n/en.json index d51623ea..18ea4007 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -10,6 +10,7 @@ "intersection_toofewcats": "Error: Too few categories!", "intersection_noresults": "Error: No results!", "intersection_noincludecats": "Error: You need to include at least one category, or specify a namespace!", + "intersection_pcerror": "This list cannot be displayed because the servers are overloaded. Please try again later.", "dpl-desc": "A highly flexible report generator for MediaWikis", "dpl_log_1": "Error: Wrong '$1' parameter: '$2'! Help: $1= empty string (Main)$3.", "dpl_log_2": "Error: Wrong '$1' parameter: '$2'! Help: $1= full pagename.", @@ -33,6 +34,7 @@ "dpl_log_20": "Warning: An infinite transclusion loop is created by page '$0'.", "dpl_log_21": "Query: $0", "dpl_log_22": "Warning: No parameter option supplied for '$1'. (Missing '=')", + "dpl_log_23": "Error: List cannot be displayed because the servers are overloaded. Please try again later.", "dpl_articlecount": "There {{PLURAL:$1|is one article|are $1 articles}} in this heading.", "action-dpl_param_update_rules": "to use the parameter 'updaterules'", "action-dpl_param_delete_rules": "to use the parameter 'deleterules'", diff --git a/includes/Article.php b/includes/Article.php index 93a0624f..e804dc98 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -4,6 +4,7 @@ use MediaWiki\MediaWikiServices; use RequestContext; +use stdClass; use Title; class Article { @@ -187,24 +188,30 @@ public function __construct( Title $title, $namespace ) { /** * Initialize a new instance from a database row. * - * @param array $row + * @param stdClass $row * @param Parameters $parameters - * @param Title $title + * @param Title $title * @param int $pageNamespace * @param string $pageTitle - * @return Article + * @return self */ - public static function newFromRow( $row, Parameters $parameters, Title $title, $pageNamespace, $pageTitle ) { + public static function newFromRow( + stdClass $row, + Parameters $parameters, + Title $title, + int $pageNamespace, + string $pageTitle + ): self { $services = MediaWikiServices::getInstance(); $contentLanguage = $services->getContentLanguage(); $userFactory = $services->getUserFactory(); - $article = new Article( $title, $pageNamespace ); + $article = new self( $title, $pageNamespace ); $revActorName = null; - if ( isset( $row['revactor_actor'] ) ) { - $revActorName = $userFactory->newFromActorId( $row['revactor_actor'] )->getName(); + if ( isset( $row->revactor_actor ) ) { + $revActorName = $userFactory->newFromActorId( $row->revactor_actor )->getName(); } $titleText = $title->getText(); @@ -222,8 +229,8 @@ public static function newFromRow( $row, Parameters $parameters, Title $title, $ $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 ) . ']'; + 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 ) . ']]'; } @@ -233,68 +240,68 @@ public static function newFromRow( $row, Parameters $parameters, Title $title, $ $languageConverter = $services->getLanguageConverterFactory()->getLanguageConverter(); // get first char used for category-style output - if ( isset( $row['sortkey'] ) ) { - $article->mStartChar = $languageConverter->convert( $contentLanguage->firstChar( $row['sortkey'] ) ); + if ( isset( $row->sortkey ) ) { + $article->mStartChar = $languageConverter->convert( $contentLanguage->firstChar( $row->sortkey ) ); } else { $article->mStartChar = $languageConverter->convert( $contentLanguage->firstChar( $pageTitle ) ); } - $article->mID = intval( $row['page_id'] ); + $article->mID = intval( $row->page_id ); // External link - if ( isset( $row['el_to'] ) ) { - $article->mExternalLink = $row['el_to']; + if ( isset( $row->el_to ) ) { + $article->mExternalLink = $row->el_to; } // SHOW PAGE_COUNTER - if ( isset( $row['page_counter'] ) ) { - $article->mCounter = intval( $row['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'] ); + 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'] ) ) { + if ( !isset( $row->sel_title ) ) { $article->mSelTitle = 'unknown page'; $article->mSelNamespace = 0; } else { - $article->mSelTitle = $row['sel_title']; - $article->mSelNamespace = $row['sel_ns']; + $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'] ) ) { + if ( !isset( $row->image_sel_title ) ) { $article->mImageSelTitle = 'unknown image'; } else { - $article->mImageSelTitle = $row['image_sel_title']; + $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['revactor_rev']; + $article->mRevision = $row->revactor_rev; $article->mUser = $revActorName; - $article->mDate = $row['revactor_timestamp']; + $article->mDate = $row->revactor_timestamp; - // $article->mComment = $row['rev_comment']; + // $article->mComment = $row->rev_comment; } // SHOW "PAGE_TOUCHED" DATE, "FIRSTCATEGORYDATE" OR (FIRST/LAST) EDIT DATE if ( $parameters->getParameter( 'addpagetoucheddate' ) ) { - $article->mDate = $row['page_touched']; + $article->mDate = $row->page_touched; } elseif ( $parameters->getParameter( 'addfirstcategorydate' ) ) { - $article->mDate = $row['cl_timestamp']; - } elseif ( $parameters->getParameter( 'addeditdate' ) && isset( $row['revactor_timestamp'] ) ) { - $article->mDate = $row['revactor_timestamp']; - } elseif ( $parameters->getParameter( 'addeditdate' ) && isset( $row['page_touched'] ) ) { - $article->mDate = $row['page_touched']; + $article->mDate = $row->cl_timestamp; + } elseif ( $parameters->getParameter( 'addeditdate' ) && isset( $row->revactor_timestamp ) ) { + $article->mDate = $row->revactor_timestamp; + } elseif ( $parameters->getParameter( 'addeditdate' ) && isset( $row->page_touched ) ) { + $article->mDate = $row->page_touched; } // Time zone adjustment @@ -311,11 +318,11 @@ public static function newFromRow( $row, Parameters $parameters, Title $title, $ // CONTRIBUTION, CONTRIBUTOR if ( $parameters->getParameter( 'addcontribution' ) ) { - $article->mContribution = $row['contribution']; + $article->mContribution = $row->contribution; - $article->mContributor = $userFactory->newFromActorId( $row['contributor'] )->getName(); + $article->mContributor = $userFactory->newFromActorId( $row->contributor )->getName(); - $article->mContrib = substr( '*****************', 0, (int)round( log( $row['contribution'] ) ) ); + $article->mContrib = substr( '*****************', 0, (int)round( log( $row->contribution ) ) ); } // USER/AUTHOR(S) @@ -327,8 +334,8 @@ public static function newFromRow( $row, Parameters $parameters, Title $title, $ } // CATEGORY LINKS FROM CURRENT PAGE - if ( $parameters->getParameter( 'addcategories' ) && ( $row['cats'] ) ) { - $artCatNames = explode( ' | ', $row['cats'] ); + 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 ); @@ -340,12 +347,12 @@ public static function newFromRow( $row, Parameters $parameters, Title $title, $ 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'] == '' ) { + 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'] ) . ']]'; + $article->mParentHLink = '[[:Category:' . $row->cl_to . '|' . str_replace( '_', ' ', $row->cl_to ) . ']]'; } break; diff --git a/includes/DynamicPageListHooks.php b/includes/DynamicPageListHooks.php index 0509ad8e..74218f7c 100644 --- a/includes/DynamicPageListHooks.php +++ b/includes/DynamicPageListHooks.php @@ -32,9 +32,11 @@ class DynamicPageListHooks { public const FATAL_MISSINGPARAMFUNCTION = 1022; - public const FATAL_NOTPROTECTED = 1023; + public const FATAL_POOLCOUNTER = 1023; - public const FATAL_SQLBUILDERROR = 1024; + public const FATAL_NOTPROTECTED = 1024; + + public const FATAL_SQLBUILDERROR = 1025; public const WARN_UNKNOWNPARAM = 2013; diff --git a/includes/LST.php b/includes/LST.php index 7a1405a7..0a90cb54 100644 --- a/includes/LST.php +++ b/includes/LST.php @@ -573,7 +573,9 @@ public static function includeTemplate( $parser, Lister $lister, $dplNr, $articl $tCalls = preg_split( '/°³²/', ' ' . $text2 ); foreach ( $tCalls as $i => $tCall ) { - if ( ( $n = strpos( $tCall, ':' ) ) !== false ) { + $n = strpos( $tCall, ':' ); + + if ( $n !== false ) { $tCalls[$i][$n] = ' '; } } @@ -735,7 +737,9 @@ public static function includeTemplate( $parser, Lister $lister, $dplNr, $articl foreach ( $extractParm as $exParmKey => $exParm ) { $maxlen = -1; - if ( ( $limpos = strpos( $exParm, '[' ) ) > 0 && $exParm[strlen( $exParm ) - 1] == ']' ) { + $limpos = strpos( $exParm, '[' ); + + if ( $limpos > 0 && $exParm[strlen( $exParm ) - 1] == ']' ) { $maxlen = intval( substr( $exParm, $limpos + 1, strlen( $exParm ) - $limpos - 2 ) ); $exParm = substr( $exParm, 0, $limpos ); } diff --git a/includes/Logger.php b/includes/Logger.php index 28bb9651..e20ff6d6 100644 --- a/includes/Logger.php +++ b/includes/Logger.php @@ -53,6 +53,8 @@ public function msg() { $text = wfMessage( 'intersection_noresults', $args )->text(); } elseif ( $errorId == DynamicPageListHooks::FATAL_NOSELECTION ) { $text = wfMessage( 'intersection_noincludecats', $args )->text(); + } elseif ( $errorId == DynamicPageListHooks::FATAL_POOLCOUNTER ) { + $text = wfMessage( 'intersection_pcerror', $args )->text(); } } diff --git a/includes/Parse.php b/includes/Parse.php index 60befd1b..dd8996af 100644 --- a/includes/Parse.php +++ b/includes/Parse.php @@ -11,16 +11,8 @@ use RequestContext; use Title; use WebRequest; -use Wikimedia\Rdbms\IDatabase; class Parse { - /** - * Mediawiki Database Object - * - * @var IDatabase - */ - private $DB = null; - /** * Parameters Object * @@ -98,7 +90,6 @@ class Parse { ]; public function __construct() { - $this->DB = wfGetDB( DB_REPLICA, 'dpl' ); $this->parameters = new Parameters(); $this->logger = new Logger(); $this->tableNames = Query::getTableNames(); @@ -215,14 +206,23 @@ public function parse( $input, Parser $parser, &$reset, &$eliminate, $isParserTa /*********/ try { $query = new Query( $this->parameters ); - $result = $query->buildAndSelect( $calcRows ); + $rows = $query->buildAndSelect( $calcRows ); + if ( $rows === false ) { + // This error path is very fast (We exit immediately if poolcounter is full) + // Thus it should be safe to try again in ~5 minutes. + $parser->getOutput()->updateCacheExpiry( 4 * 60 + mt_rand( 0, 120 ) ); + + // Pool counter all threads in use. + $this->logger->addMessage( DynamicPageListHooks::FATAL_POOLCOUNTER ); + return $this->getFullOutput( true ); + } } catch ( MWException $e ) { $this->logger->addMessage( DynamicPageListHooks::FATAL_SQLBUILDERROR, $e->getMessage() ); return $this->getFullOutput(); } - $numRows = $result->numRows(); - $articles = $this->processQueryResults( $result, $parser ); + $numRows = count( $rows ); + $articles = $this->processQueryResults( $rows, $parser ); global $wgDebugDumpSql; if ( DynamicPageListHooks::getDebugLevel() >= 4 && $wgDebugDumpSql ) { @@ -239,9 +239,7 @@ public function parse( $input, Parser $parser, &$reset, &$eliminate, $isParserTa /*********************/ /* Handle No Results */ /*********************/ - if ( $numRows <= 0 || empty( $articles ) ) { - // Shortcut out since there is no processing to do. - $result->free(); + if ( $numRows == 0 || empty( $articles ) ) { return $this->getFullOutput( 0, false ); } @@ -341,28 +339,29 @@ public function parse( $input, Parser $parser, &$reset, &$eliminate, $isParserTa /** * Process Query Results * - * @param $result + * @param $rows * @param Parser $parser * @return array */ - private function processQueryResults( $result, Parser $parser ) { + private function processQueryResults( $rows, Parser $parser ) { /*******************************/ /* Random Count Pick Generator */ /*******************************/ $randomCount = $this->parameters->getParameter( 'randomcount' ); if ( $randomCount > 0 ) { - $nResults = $result->numRows(); - // mt_srand() seeding was removed due to PHP 5.2.1 and above no longer generating the same sequence for the same seed. - //Constrain the total amount of random results to not be greater than the total results. + $nResults = count( $rows ); + + // Constrain the total amount of random results to not be greater than the total results. if ( $randomCount > $nResults ) { $randomCount = $nResults; } - // This is 50% to 150% faster than the old while (true) version that could keep rechecking the same random key over and over again. // Generate pick numbers for results. $pick = range( 1, $nResults ); + // Shuffle the pick numbers. shuffle( $pick ); + // Select pick numbers from the beginning to the maximum of $randomCount. $pick = array_slice( $pick, 0, $randomCount ); } @@ -373,7 +372,7 @@ private function processQueryResults( $result, Parser $parser ) { /* Article Processing */ /**********************/ $i = 0; - while ( $row = $result->fetchRow() ) { + foreach ( $rows as $row ) { $i++; // In random mode skip articles which were not chosen. @@ -383,15 +382,15 @@ private function processQueryResults( $result, Parser $parser ) { if ( $this->parameters->getParameter( 'goal' ) == 'categories' ) { $pageNamespace = NS_CATEGORY; - $pageTitle = $row['cl_to']; + $pageTitle = $row->cl_to; } elseif ( $this->parameters->getParameter( 'openreferences' ) ) { if ( count( $this->parameters->getParameter( 'imagecontainer' ) ) > 0 ) { $pageNamespace = NS_FILE; - $pageTitle = $row['il_to']; + $pageTitle = $row->il_to; } else { // Maybe non-existing title - $pageNamespace = $row['pl_namespace']; - $pageTitle = $row['pl_title']; + $pageNamespace = $row->pl_namespace; + $pageTitle = $row->pl_title; } if ( @@ -402,8 +401,8 @@ private function processQueryResults( $result, Parser $parser ) { } } else { // Existing PAGE TITLE - $pageNamespace = $row['page_namespace']; - $pageTitle = $row['page_title']; + $pageNamespace = $row->page_namespace; + $pageTitle = $row->page_title; } // if subpages are to be excluded: skip them @@ -422,8 +421,6 @@ private function processQueryResults( $result, Parser $parser ) { $articles[] = Article::newFromRow( $row, $this->parameters, $title, $pageNamespace, $pageTitle ); } - $result->free(); - return $articles; } diff --git a/includes/Query.php b/includes/Query.php index 4e1847cb..075610de 100644 --- a/includes/Query.php +++ b/includes/Query.php @@ -8,6 +8,10 @@ use MediaWiki\MediaWikiServices; use MediaWiki\User\UserFactory; use MWException; +use PoolCounterWorkViaCallback; +use WANObjectCache; +use WikiMap; +use Wikimedia\Rdbms\Database; use Wikimedia\Rdbms\IDatabase; class Query { @@ -23,7 +27,7 @@ class Query { * * @var IDatabase */ - private $DB; + private $dbr; /** * Array of prefixed and escaped table names. @@ -159,18 +163,18 @@ public function __construct( Parameters $parameters ) { $this->tableNames = self::getTableNames(); - $this->DB = wfGetDB( DB_REPLICA, 'dpl' ); + $this->dbr = wfGetDB( DB_REPLICA, 'dpl' ); $this->userFactory = MediaWikiServices::getInstance()->getUserFactory(); } /** - * Start a query build. + * Start a query build. Returns found rows. * * @param bool $calcRows - * @return mixed Mediawiki Result Object or False + * @return array|bool */ - public function buildAndSelect( $calcRows = false ) { + public function buildAndSelect( bool $calcRows = false ) { global $wgNonincludableNamespaces; $options = []; @@ -224,12 +228,10 @@ public function buildAndSelect( $calcRows = false ) { if ( $this->parameters->getParameter( 'openreferences' ) ) { if ( count( $this->parameters->getParameter( 'imagecontainer' ) ) > 0 ) { - // $sSqlSelectFrom = $sSqlCl_to.'ic.il_to, '.$sSqlSelPage."ic.il_to AS sortkey".' FROM '.$this->tableNames['imagelinks'].' AS ic'; $tables = [ 'ic' => 'imagelinks' ]; } else { - // $sSqlSelectFrom = "SELECT $sSqlCalcFoundRows $sSqlDistinct ".$sSqlCl_to.'pl_namespace, pl_title'.$sSqlSelPage.$sSqlSortkey.' FROM '.$this->tableNames['pagelinks']; $this->addSelect( [ 'pl_namespace', @@ -255,7 +257,7 @@ public function buildAndSelect( $calcRows = false ) { } if ( $this->parameters->getParameter( 'goal' ) == 'categories' ) { $categoriesGoal = true; - $select = [ + $fields = [ $this->tableNames['page'] . '.page_id' ]; @@ -270,15 +272,15 @@ public function buildAndSelect( $calcRows = false ) { } $categoriesGoal = false; - $select = $this->select; + $fields = $this->select; } $queryError = false; try { if ( $categoriesGoal ) { - $result = $this->DB->select( + $res = $this->dbr->select( $tables, - $select, + $fields, $this->where, __METHOD__, $options, @@ -287,11 +289,11 @@ public function buildAndSelect( $calcRows = false ) { $pageIds = []; - while ( $row = $result->fetchRow() ) { - $pageIds[] = $row['page_id']; + foreach ( $res as $row ) { + $pageIds[] = $row->page_id; } - $sql = $this->DB->selectSQLText( + $query = $this->dbr->selectSQLText( [ 'clgoal' => 'categorylinks' ], @@ -307,9 +309,9 @@ public function buildAndSelect( $calcRows = false ) { ] ); } else { - $sql = $this->DB->selectSQLText( + $query = $this->dbr->selectSQLText( $tables, - $select, + $fields, $this->where, __METHOD__, $options, @@ -317,24 +319,71 @@ public function buildAndSelect( $calcRows = false ) { ); } - $this->sqlQuery = $sql; - $result = $this->DB->query( $sql, __METHOD__ ); + $this->sqlQuery = $query; if ( $calcRows ) { - $calcRowsResult = $this->DB->query( 'SELECT FOUND_ROWS() AS rowcount', __METHOD__ ); + $calcRowsResult = $this->dbr->query( 'SELECT FOUND_ROWS() AS rowcount', __METHOD__ ); $total = $calcRowsResult->fetchRow(); $this->foundRows = intval( $total['rowcount'] ); $calcRowsResult->free(); } } catch ( Exception $e ) { - $queryError = true; + throw new MWException( __METHOD__ . ': ' . wfMessage( 'dpl_query_error', DynamicPageListHooks::getVersion(), $this->dbr->lastError() )->text() ); } - if ( $queryError || $result === false ) { - throw new MWException( __METHOD__ . ": " . wfMessage( 'dpl_query_error', DynamicPageListHooks::getVersion(), $this->DB->lastError() )->text() ); + // Partially taken from intersection + $queryCacheTime = Config::getSetting( 'queryCacheTime' ); + $maxQueryTime = Config::getSetting( 'maxQueryTime' ); + + if ( $maxQueryTime ) { + $options['MAX_EXECUTION_TIME'] = $maxQueryTime; + } + + $parser = MediaWikiServices::getInstance()->getParser(); + $pageName = str_replace( [ '*', '/' ], '-', $parser->getTitle()->getPrefixedDBkey() ); + + $qname = __METHOD__ . ' - ' . $pageName; + $where = $this->where; + $join = $this->join; + $dbr = $this->dbr; + + $doQuery = static function () use ( $qname, $dbr, $tables, $fields, $where, $options, $join ) { + $res = $dbr->select( $tables, $fields, $where, $qname, $options, $join ); + return iterator_to_array( $res ); + }; + + $poolCounterKey = 'nowait:dpl3-query:' . WikiMap::getCurrentWikiId(); + $worker = new PoolCounterWorkViaCallback( 'DPL3', $poolCounterKey, [ + 'doWork' => $doQuery, + ] ); + + if ( $queryCacheTime <= 0 ) { + return $worker->execute(); } - return $result; + $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); + + return $cache->getWithSetCallback( + $cache->makeKey( 'DPL3Query', hash( 'sha256', $query ) ), + $queryCacheTime, + static function ( $oldVal, &$ttl, &$setOpts ) use ( $worker, $dbr ){ + $setOpts += Database::getCacheSetOptions( $dbr ); + $res = $worker->execute(); + if ( $res === false ) { + // Do not cache errors. + $ttl = WANObjectCache::TTL_UNCACHEABLE; + // If we have oldVal, prefer it to error + if ( is_array( $oldVal ) ) { + return $oldVal; + } + } + return $res; + }, + [ + 'lowTTL' => min( $cache::TTL_MINUTE, floor( $queryCacheTime * 0.75 ) ), + 'pcTTL' => min( $cache::TTL_PROC_LONG, $queryCacheTime ) + ] + ); } /** @@ -361,7 +410,7 @@ public function getSqlQuery() { * @return array */ public static function getTableNames() { - $DB = wfGetDB( DB_REPLICA, 'dpl' ); + $dbr = wfGetDB( DB_REPLICA, 'dpl' ); $tables = [ 'categorylinks', @@ -379,7 +428,7 @@ public static function getTableNames() { $tableNames = []; foreach ( $tables as $table ) { - $tableNames[$table] = $DB->tableName( $table ); + $tableNames[$table] = $dbr->tableName( $table ); } return $tableNames; @@ -402,7 +451,7 @@ public function addTable( $table, $alias ) { } if ( !isset( $this->tables[$alias] ) ) { - $this->tables[$alias] = $this->DB->tableName( $table ); + $this->tables[$alias] = $this->dbr->tableName( $table ); return true; } else { @@ -457,7 +506,7 @@ public function addNotWhere( $where ) { if ( is_array( $where ) ) { foreach ( $where as $field => $values ) { - $this->where[] = $field . ( count( $values ) > 1 ? ' NOT IN(' . $this->DB->makeList( $values ) . ')' : ' != ' . $this->DB->addQuotes( current( $values ) ) ); + $this->where[] = $field . ( count( $values ) > 1 ? ' NOT IN(' . $this->dbr->makeList( $values ) . ')' : ' != ' . $this->dbr->addQuotes( current( $values ) ) ); } } else { throw new MWException( __METHOD__ . ': An invalid NOT WHERE clause was passed.' ); @@ -628,7 +677,7 @@ public function getCollateSQL() { * @return array */ public static function getSubcategories( $categoryName, $depth = 1 ) { - $DB = wfGetDB( DB_REPLICA, 'dpl' ); + $dbr = wfGetDB( DB_REPLICA, 'dpl' ); if ( $depth > 2 ) { // Hard constrain depth because lots of recursion is bad. @@ -636,7 +685,7 @@ public static function getSubcategories( $categoryName, $depth = 1 ) { } $categories = []; - $result = $DB->select( + $res = $dbr->select( [ 'page', 'categorylinks' ], [ 'page_title' ], [ @@ -653,15 +702,15 @@ public static function getSubcategories( $categoryName, $depth = 1 ) { ] ); - while ( $row = $result->fetchRow() ) { - $categories[] = $row['page_title']; + foreach ( $res as $row ) { + $categories[] = $row->page_title; if ( $depth > 1 ) { - $categories = array_merge( $categories, self::getSubcategories( $row['page_title'], $depth - 1 ) ); + $categories = array_merge( $categories, self::getSubcategories( $row->page_title, $depth - 1 ) ); } } $categories = array_unique( $categories ); - $result->free(); + $res->free(); return $categories; } @@ -706,7 +755,7 @@ private function convertTimestamp( $inputDate ) { } if ( is_numeric( $timestamp ) ) { - return $this->DB->addQuotes( $timestamp ); + return $this->dbr->addQuotes( $timestamp ); } return 0; @@ -955,7 +1004,7 @@ private function _allrevisionssince( $option ) { * @param mixed $option */ private function _articlecategory( $option ) { - $this->addWhere( "{$this->tableNames['page']}.page_title IN (SELECT p2.page_title FROM {$this->tableNames['page']} p2 INNER JOIN {$this->tableNames['categorylinks']} clstc ON (clstc.cl_from = p2.page_id AND clstc.cl_to = " . $this->DB->addQuotes( $option ) . ") WHERE p2.page_namespace = 0)" ); + $this->addWhere( "{$this->tableNames['page']}.page_title IN (SELECT p2.page_title FROM {$this->tableNames['page']} p2 INNER JOIN {$this->tableNames['categorylinks']} clstc ON (clstc.cl_from = p2.page_id AND clstc.cl_to = " . $this->dbr->addQuotes( $option ) . ") WHERE p2.page_namespace = 0)" ); } /** @@ -998,7 +1047,7 @@ private function _category( $option ) { $tableAlias, [ 'INNER JOIN', - "{$this->tableNames['page']}.page_id = {$tableAlias}.cl_from AND $tableAlias.cl_to {$comparisonType} " . $this->DB->addQuotes( str_replace( ' ', '_', $category ) ) + "{$this->tableNames['page']}.page_id = {$tableAlias}.cl_from AND $tableAlias.cl_to {$comparisonType} " . $this->dbr->addQuotes( str_replace( ' ', '_', $category ) ) ] ); } @@ -1011,7 +1060,7 @@ private function _category( $option ) { $ors = []; foreach ( $categories as $category ) { - $ors[] = "{$tableAlias}.cl_to {$comparisonType} " . $this->DB->addQuotes( str_replace( ' ', '_', $category ) ); + $ors[] = "{$tableAlias}.cl_to {$comparisonType} " . $this->dbr->addQuotes( str_replace( ' ', '_', $category ) ); } $joinOn .= implode( " {$operatorType} ", $ors ); @@ -1048,7 +1097,7 @@ private function _notcategory( $option ) { $tableAlias, [ 'LEFT OUTER JOIN', - "{$this->tableNames['page']}.page_id = {$tableAlias}.cl_from AND {$tableAlias}.cl_to {$operatorType}" . $this->DB->addQuotes( str_replace( ' ', '_', $category ) ) + "{$this->tableNames['page']}.page_id = {$tableAlias}.cl_from AND {$tableAlias}.cl_to {$operatorType}" . $this->dbr->addQuotes( str_replace( ' ', '_', $category ) ) ] ); @@ -1073,7 +1122,7 @@ private function _createdby( $option ) { $this->addWhere( [ - $this->DB->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' = creation_rev_actor.revactor_actor', + $this->dbr->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' = creation_rev_actor.revactor_actor', 'creation_rev_actor.revactor_page = page_id', 'creation_rev.rev_parent_id = 0' ] @@ -1111,7 +1160,7 @@ private function _firstrevisionsince( $option ) { $this->addWhere( [ $this->tableNames['page'] . '.page_id = rev.revactor_page', - 'rev.revactor_timestamp >= ' . $this->DB->addQuotes( $option ) + 'rev.revactor_timestamp >= ' . $this->dbr->addQuotes( $option ) ] ); @@ -1170,9 +1219,9 @@ private function _imagecontainer( $option ) { foreach ( $option as $linkGroup ) { foreach ( $linkGroup as $link ) { if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $ors[] = 'LOWER(CAST(ic.il_from AS char) = LOWER(' . $this->DB->addQuotes( $link->getArticleID() ) . ')'; + $ors[] = 'LOWER(CAST(ic.il_from AS char) = LOWER(' . $this->dbr->addQuotes( $link->getArticleID() ) . ')'; } else { - $ors[] = 'ic.il_from = ' . $this->DB->addQuotes( $link->getArticleID() ); + $ors[] = 'ic.il_from = ' . $this->dbr->addQuotes( $link->getArticleID() ); } } } @@ -1206,9 +1255,9 @@ private function _imageused( $option ) { foreach ( $option as $linkGroup ) { foreach ( $linkGroup as $link ) { if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $ors[] = 'LOWER(CAST(il.il_to AS char)) = LOWER(' . $this->DB->addQuotes( $link->getDBkey() ) . ')'; + $ors[] = 'LOWER(CAST(il.il_to AS char)) = LOWER(' . $this->dbr->addQuotes( $link->getDBkey() ) . ')'; } else { - $ors[] = 'il.il_to = ' . $this->DB->addQuotes( $link->getDBkey() ); + $ors[] = 'il.il_to = ' . $this->dbr->addQuotes( $link->getDBkey() ); } } } @@ -1223,7 +1272,7 @@ private function _imageused( $option ) { * @param mixed $option */ private function _lastmodifiedby( $option ) { - $this->addWhere( $this->DB->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' = (SELECT revactor_actor FROM ' . $this->tableNames['revision_actor_temp'] . ' WHERE ' . $this->tableNames['revision_actor_temp'] . '.revactor_page=page_id ORDER BY ' . $this->tableNames['revision_actor_temp'] . '.revactor_timestamp DESC LIMIT 1)' ); + $this->addWhere( $this->dbr->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' = (SELECT revactor_actor FROM ' . $this->tableNames['revision_actor_temp'] . ' WHERE ' . $this->tableNames['revision_actor_temp'] . '.revactor_page=page_id ORDER BY ' . $this->tableNames['revision_actor_temp'] . '.revactor_timestamp DESC LIMIT 1)' ); } /** @@ -1329,9 +1378,9 @@ private function _linksto( $option ) { } if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or .= ' AND LOWER(CAST(pl.pl_title AS char)) ' . $operator . ' LOWER(' . $this->DB->addQuotes( $link->getDBkey() ) . ')'; + $_or .= ' AND LOWER(CAST(pl.pl_title AS char)) ' . $operator . ' LOWER(' . $this->dbr->addQuotes( $link->getDBkey() ) . ')'; } else { - $_or .= ' AND pl.pl_title ' . $operator . ' ' . $this->DB->addQuotes( $link->getDBkey() ); + $_or .= ' AND pl.pl_title ' . $operator . ' ' . $this->dbr->addQuotes( $link->getDBkey() ); } $_or .= ')'; @@ -1352,9 +1401,9 @@ private function _linksto( $option ) { } if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or .= ' AND LOWER(CAST(' . $this->tableNames['pagelinks'] . '.pl_title AS char)) ' . $operator . ' LOWER(' . $this->DB->addQuotes( $link->getDBkey() ) . ')'; + $_or .= ' AND LOWER(CAST(' . $this->tableNames['pagelinks'] . '.pl_title AS char)) ' . $operator . ' LOWER(' . $this->dbr->addQuotes( $link->getDBkey() ) . ')'; } else { - $_or .= ' AND ' . $this->tableNames['pagelinks'] . '.pl_title ' . $operator . ' ' . $this->DB->addQuotes( $link->getDBkey() ); + $_or .= ' AND ' . $this->tableNames['pagelinks'] . '.pl_title ' . $operator . ' ' . $this->dbr->addQuotes( $link->getDBkey() ); } $_or .= ')'; @@ -1429,9 +1478,9 @@ private function _notlinksto( $option ) { } if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or .= ' AND LOWER(CAST(' . $this->tableNames['pagelinks'] . '.pl_title AS char)) ' . $operator . ' LOWER(' . $this->DB->addQuotes( $link->getDBkey() ) . '))'; + $_or .= ' AND LOWER(CAST(' . $this->tableNames['pagelinks'] . '.pl_title AS char)) ' . $operator . ' LOWER(' . $this->dbr->addQuotes( $link->getDBkey() ) . '))'; } else { - $_or .= ' AND ' . $this->tableNames['pagelinks'] . '.pl_title ' . $operator . ' ' . $this->DB->addQuotes( $link->getDBkey() ) . ')'; + $_or .= ' AND ' . $this->tableNames['pagelinks'] . '.pl_title ' . $operator . ' ' . $this->dbr->addQuotes( $link->getDBkey() ) . ')'; } $ors[] = $_or; @@ -1464,7 +1513,7 @@ private function _linkstoexternal( $option ) { $ors = []; foreach ( $linkGroup as $link ) { - $ors[] = 'el.el_to LIKE ' . $this->DB->addQuotes( $link ); + $ors[] = 'el.el_to LIKE ' . $this->dbr->addQuotes( $link ); } $where .= '(' . implode( ' OR ', $ors ) . ')'; @@ -1473,7 +1522,7 @@ private function _linkstoexternal( $option ) { $ors = []; foreach ( $linkGroup as $link ) { - $ors[] = $this->tableNames['externallinks'] . '.el_to LIKE ' . $this->DB->addQuotes( $link ); + $ors[] = $this->tableNames['externallinks'] . '.el_to LIKE ' . $this->dbr->addQuotes( $link ); } $where .= '(' . implode( ' OR ', $ors ) . ')'; @@ -1523,7 +1572,7 @@ private function _minrevisions( $option ) { private function _modifiedby( $option ) { $this->addTable( 'revision_actor_temp', 'change_rev' ); - $this->addWhere( $this->DB->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' = change_rev.revactor_actor AND change_rev.revactor_page = page_id' ); + $this->addWhere( $this->dbr->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' = change_rev.revactor_actor AND change_rev.revactor_page = page_id' ); } /** @@ -1558,7 +1607,7 @@ private function _notcreatedby( $option ) { $this->addTable( 'revision', 'no_creation_rev' ); $this->addTable( 'revision_actor_temp', 'no_creation_rev_actor' ); - $this->addWhere( $this->DB->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' != no_creation_rev_actor.revactor_actor AND no_creation_rev_actor.revactor_page = page_id AND no_creation_rev.rev_parent_id = 0' ); + $this->addWhere( $this->dbr->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' != no_creation_rev_actor.revactor_actor AND no_creation_rev_actor.revactor_page = page_id AND no_creation_rev.rev_parent_id = 0' ); } /** @@ -1567,7 +1616,7 @@ private function _notcreatedby( $option ) { * @param mixed $option */ private function _notlastmodifiedby( $option ) { - $this->addWhere( $this->DB->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' != (SELECT revactor_actor FROM ' . $this->tableNames['revision_actor_temp'] . ' WHERE ' . $this->tableNames['revision_actor_temp'] . '.revactor_page=page_id ORDER BY ' . $this->tableNames['revision_actor_temp'] . '.revactor_timestamp DESC LIMIT 1)' ); + $this->addWhere( $this->dbr->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' != (SELECT revactor_actor FROM ' . $this->tableNames['revision_actor_temp'] . ' WHERE ' . $this->tableNames['revision_actor_temp'] . '.revactor_page=page_id ORDER BY ' . $this->tableNames['revision_actor_temp'] . '.revactor_timestamp DESC LIMIT 1)' ); } /** @@ -1576,7 +1625,7 @@ private function _notlastmodifiedby( $option ) { * @param mixed $option */ private function _notmodifiedby( $option ) { - $this->addWhere( 'NOT EXISTS (SELECT 1 FROM ' . $this->tableNames['revision_actor_temp'] . ' WHERE ' . $this->tableNames['revision_actor_temp'] . '.revactor_page=page_id AND ' . $this->tableNames['revision_actor_temp'] . '.revactor_actor = ' . $this->DB->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' LIMIT 1)' ); + $this->addWhere( 'NOT EXISTS (SELECT 1 FROM ' . $this->tableNames['revision_actor_temp'] . ' WHERE ' . $this->tableNames['revision_actor_temp'] . '.revactor_page=page_id AND ' . $this->tableNames['revision_actor_temp'] . '.revactor_actor = ' . $this->dbr->addQuotes( $this->userFactory->newFromName( $option )->getActorId() ) . ' LIMIT 1)' ); } /** @@ -1646,13 +1695,13 @@ private function _order( $option ) { private function _ordercollation( $option ) { $option = mb_strtolower( $option ); - $results = $this->DB->query( 'SHOW CHARACTER SET' ); - if ( !$results ) { + $res = $this->dbr->query( 'SHOW CHARACTER SET' ); + if ( !$res ) { return false; } - while ( $row = $results->fetchRow() ) { - if ( $option == $row['Default collation'] ) { + foreach ( $res as $row ) { + if ( $option == $row->{'Default collation'} ) { $this->setCollation( $option ); break; } @@ -1681,7 +1730,7 @@ private function _ordermethod( $option ) { $_namespaceIdToText = "CASE {$this->tableNames['page']}.page_namespace"; foreach ( $namespaces as $id => $name ) { - $_namespaceIdToText .= ' WHEN ' . intval( $id ) . ' THEN ' . $this->DB->addQuotes( $name . ':' ); + $_namespaceIdToText .= ' WHEN ' . intval( $id ) . ' THEN ' . $this->dbr->addQuotes( $name . ':' ); } $_namespaceIdToText .= ' END'; @@ -1994,15 +2043,15 @@ private function _title( $option ) { foreach ( $titles as $title ) { if ( $this->parameters->getParameter( 'openreferences' ) ) { if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or = "LOWER(CAST(pl_title AS char)) {$comparisonType}" . strtolower( $this->DB->addQuotes( $title ) ); + $_or = "LOWER(CAST(pl_title AS char)) {$comparisonType}" . strtolower( $this->dbr->addQuotes( $title ) ); } else { - $_or = "pl_title {$comparisonType} " . $this->DB->addQuotes( $title ); + $_or = "pl_title {$comparisonType} " . $this->dbr->addQuotes( $title ); } } else { if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or = "LOWER(CAST({$this->tableNames['page']}.page_title AS char)) {$comparisonType}" . strtolower( $this->DB->addQuotes( $title ) ); + $_or = "LOWER(CAST({$this->tableNames['page']}.page_title AS char)) {$comparisonType}" . strtolower( $this->dbr->addQuotes( $title ) ); } else { - $_or = "{$this->tableNames['page']}.page_title {$comparisonType}" . $this->DB->addQuotes( $title ); + $_or = "{$this->tableNames['page']}.page_title {$comparisonType}" . $this->dbr->addQuotes( $title ); } } @@ -2026,15 +2075,15 @@ private function _nottitle( $option ) { foreach ( $titles as $title ) { if ( $this->parameters->getParameter( 'openreferences' ) ) { if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or = "LOWER(CAST(pl_title AS char)) {$comparisonType}" . strtolower( $this->DB->addQuotes( $title ) ); + $_or = "LOWER(CAST(pl_title AS char)) {$comparisonType}" . strtolower( $this->dbr->addQuotes( $title ) ); } else { - $_or = "pl_title {$comparisonType} " . $this->DB->addQuotes( $title ); + $_or = "pl_title {$comparisonType} " . $this->dbr->addQuotes( $title ); } } else { if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or = "LOWER(CAST({$this->tableNames['page']}.page_title AS char)) {$comparisonType}" . strtolower( $this->DB->addQuotes( $title ) ); + $_or = "LOWER(CAST({$this->tableNames['page']}.page_title AS char)) {$comparisonType}" . strtolower( $this->dbr->addQuotes( $title ) ); } else { - $_or = "{$this->tableNames['page']}.page_title {$comparisonType}" . $this->DB->addQuotes( $title ); + $_or = "{$this->tableNames['page']}.page_title {$comparisonType}" . $this->dbr->addQuotes( $title ); } } @@ -2062,7 +2111,7 @@ private function _titlegt( $option ) { $operator = 'LIKE'; $option = '%'; } else { - $option = $this->DB->addQuotes( $option ); + $option = $this->dbr->addQuotes( $option ); } if ( $this->parameters->getParameter( 'openreferences' ) ) { @@ -2090,7 +2139,7 @@ private function _titlelt( $option ) { $operator = 'LIKE'; $option = '%'; } else { - $option = $this->DB->addQuotes( $option ); + $option = $this->dbr->addQuotes( $option ); } if ( $this->parameters->getParameter( 'openreferences' ) ) { @@ -2153,9 +2202,9 @@ private function _uses( $option ) { $_or = '(tl.tl_namespace=' . intval( $link->getNamespace() ); if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or .= ' AND LOWER(CAST(tl.tl_title AS char)) = LOWER(' . $this->DB->addQuotes( $link->getDBkey() ) . '))'; + $_or .= ' AND LOWER(CAST(tl.tl_title AS char)) = LOWER(' . $this->dbr->addQuotes( $link->getDBkey() ) . '))'; } else { - $_or .= ' AND tl.tl_title = ' . $this->DB->addQuotes( $link->getDBkey() ) . ')'; + $_or .= ' AND tl.tl_title = ' . $this->dbr->addQuotes( $link->getDBkey() ) . ')'; } $ors[] = $_or; @@ -2181,9 +2230,9 @@ private function _notuses( $option ) { $_or = '(' . $this->tableNames['templatelinks'] . '.tl_namespace=' . intval( $link->getNamespace() ); if ( $this->parameters->getParameter( 'ignorecase' ) ) { - $_or .= ' AND LOWER(CAST(' . $this->tableNames['templatelinks'] . '.tl_title AS char)) = LOWER(' . $this->DB->addQuotes( $link->getDBkey() ) . '))'; + $_or .= ' AND LOWER(CAST(' . $this->tableNames['templatelinks'] . '.tl_title AS char)) = LOWER(' . $this->dbr->addQuotes( $link->getDBkey() ) . '))'; } else { - $_or .= ' AND ' . $this->tableNames['templatelinks'] . '.tl_title = ' . $this->DB->addQuotes( $link->getDBkey() ) . ')'; + $_or .= ' AND ' . $this->tableNames['templatelinks'] . '.tl_title = ' . $this->dbr->addQuotes( $link->getDBkey() ) . ')'; } $ors[] = $_or; }