From 7b009189411266eb8f0e8efcbe006e617f17cda6 Mon Sep 17 00:00:00 2001 From: gnosygnu Date: Sun, 26 Apr 2020 08:08:39 -0400 Subject: [PATCH] XOMW: Rename XomwTitle to XomwTitleOld [#632] --- .../src/gplx/xowa/mediawiki/XophpInt_.java | 27 +- .../src/gplx/xowa/mediawiki/XophpObject_.java | 37 +- .../XomwWikiPagePropertyOrderProvider.java | 34 +- .../includes/Revision/XomwRevisionRecord.java | 539 + .../xowa/mediawiki/includes/XomwLinker.java | 42 +- .../XomwLinker_NormalizeSubpageLink_tst.java | 32 +- .../xowa/mediawiki/includes/XomwMessage.java | 32 +- .../xowa/mediawiki/includes/XomwRevision.java | 538 +- .../xowa/mediawiki/includes/XomwTitle.java | 9226 ++++++++--------- .../xowa/mediawiki/includes/XomwTitleOld.java | 4949 +++++++++ .../mediawiki/includes/XomwTitle_tst.java | 26 +- .../includes/content/XomwAbstractContent.java | 56 +- .../includes/content/XomwContent.java | 46 +- .../includes/content/XomwContentHandler.java | 32 +- .../includes/filerepo/XomwFileRepo.java | 32 +- .../includes/filerepo/file/XomwFile.java | 36 +- .../filerepo/file/XomwFileFinder.java | 8 +- .../filerepo/file/XomwFileFinderMock.java | 34 +- .../filerepo/file/XomwFileFinderNoop.java | 8 +- .../includes/filerepo/file/XomwLocalFile.java | 32 +- .../libs/rdbms/database/XomwIDatabase.java | 2160 ++++ .../includes/linkers/XomwLinkRenderer.java | 48 +- .../includes/linkers/XomwLinkTarget.java | 112 + .../includes/media/XomwImageHandler_tst.java | 32 +- .../includes/page/XomwWikiCategoryPage.java | 32 +- .../includes/page/XomwWikiFilePage.java | 32 +- .../mediawiki/includes/page/XomwWikiPage.java | 36 +- .../includes/parsers/XomwLinkHolderArray.java | 38 +- .../parsers/XomwLinkHolderArray_tst.java | 32 +- .../includes/parsers/XomwParser.java | 8705 ++++++++++------ .../includes/parsers/XomwParserCtx.java | 30 +- .../includes/parsers/XomwParser_tst.java | 34 +- .../includes/parsers/lnkis/Xomw_lnki_wkr.java | 50 +- .../lnkis/Xomw_lnki_wkr__file__tst.java | 32 +- .../magiclinks/Xomw_magiclinks_wkr__tst.java | 32 +- .../parsers/preprocessors/XomwPPFrame.java | 38 +- .../preprocessors/XomwPPFrame_Hash.java | 38 +- .../XomwPPTemplateFrame_Hash.java | 32 +- .../preprocessors_new/XomwPPFrame.java | 36 +- .../preprocessors_new/XomwPPFrame_Hash.java | 38 +- .../XomwPPTemplateFrame_Hash.java | 32 +- .../title/XomwMediaWikiTitleCodec.java | 32 +- .../includes/title/XomwTitleFormatter.java | 30 +- .../includes/title/XomwTitleParser.java | 32 +- 44 files changed, 18366 insertions(+), 9113 deletions(-) create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/Revision/XomwRevisionRecord.java create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitleOld.java create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/libs/rdbms/database/XomwIDatabase.java create mode 100644 400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkTarget.java diff --git a/400_xowa/src/gplx/xowa/mediawiki/XophpInt_.java b/400_xowa/src/gplx/xowa/mediawiki/XophpInt_.java index dce2666eb..827499d00 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/XophpInt_.java +++ b/400_xowa/src/gplx/xowa/mediawiki/XophpInt_.java @@ -13,15 +13,18 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.mediawiki; import gplx.*; import gplx.xowa.*; -public class XophpInt_ { - public static final int False = 0; // REF.PHP:https://www.php.net/manual/en/language.types.boolean.php - public static boolean is_true(int val) {return val != 0;} // handles code like "if ($var)" where var is an Object; - public static boolean is_false(int val) {return val < 0;} // handles XophpInt_.False as well as strpos.notFound (-1) - public static String strval(int number) { - return Int_.To_str(number); - } - public static int intval(String val) { - return Int_.Parse_or(val, 0); - } -} +package gplx.xowa.mediawiki; import gplx.*; import gplx.xowa.*; +public class XophpInt_ { + public static final int False = 0; // REF.PHP:https://www.php.net/manual/en/language.types.boolean.php + public static boolean is_true(int val) {return val != 0;} // handles code like "if ($var)" where var is an Object; + public static boolean is_false(int val) {return val < 0;} // handles XophpInt_.False as well as strpos.notFound (-1) + public static String strval(int number) { + return Int_.To_str(number); + } + public static int intval(String val) { + return Int_.Parse_or(val, 0); + } + public static int cast(Object val) { + return Int_.Cast(val); + } +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/XophpObject_.java b/400_xowa/src/gplx/xowa/mediawiki/XophpObject_.java index ba7e83cd9..28ad20bb9 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/XophpObject_.java +++ b/400_xowa/src/gplx/xowa/mediawiki/XophpObject_.java @@ -1,19 +1,21 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki; import gplx.*; import gplx.xowa.*; +import gplx.xowa.mediawiki.includes.XomwTitleOld; + public class XophpObject_ { public static final Object False = null; // handles code like "if ($var === false)" where var is an Object; public static boolean is_true(Object val) {return val != null;} @@ -82,4 +84,9 @@ public class XophpObject_ { public static final double NULL_DOUBLE = Double_.MinValue; public static final byte[] NULL_BRY = null; public static Object coalesce(Object val, Object if_null) {return val == null ? if_null : val;} + + // REF.PHP:https://www.php.net/manual/en/function.is-object.php + public static boolean is_object(Object o) { + return o != null; + } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/extensions/Wikibase/lib/includes/Store/XomwWikiPagePropertyOrderProvider.java b/400_xowa/src/gplx/xowa/mediawiki/extensions/Wikibase/lib/includes/Store/XomwWikiPagePropertyOrderProvider.java index 75179b4a7..2d0c57a36 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/extensions/Wikibase/lib/includes/Store/XomwWikiPagePropertyOrderProvider.java +++ b/400_xowa/src/gplx/xowa/mediawiki/extensions/Wikibase/lib/includes/Store/XomwWikiPagePropertyOrderProvider.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.extensions.Wikibase.lib.includes.Store; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.Wikibase.*; import gplx.xowa.mediawiki.extensions.Wikibase.lib.*; import gplx.xowa.mediawiki.extensions.Wikibase.lib.includes.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.page.*; @@ -27,12 +27,12 @@ class XomwWikiPagePropertyOrderProvider extends XomwWikiTextPropertyOrderProvide /** * @var Title */ - private XomwTitle pageTitle; + private XomwTitleOld pageTitle; /** * @param Title pageTitle page name the ordered property list is on */ - public XomwWikiPagePropertyOrderProvider(XomwTitle pageTitle) { + public XomwWikiPagePropertyOrderProvider(XomwTitleOld pageTitle) { this.pageTitle = pageTitle; } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/Revision/XomwRevisionRecord.java b/400_xowa/src/gplx/xowa/mediawiki/includes/Revision/XomwRevisionRecord.java new file mode 100644 index 000000000..c0f1dbc23 --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/Revision/XomwRevisionRecord.java @@ -0,0 +1,539 @@ +package gplx.xowa.mediawiki.includes.Revision; + +// MW.SRC:1.33.1 + +import gplx.xowa.mediawiki.XophpString_; +import gplx.xowa.mediawiki.includes.XomwTitleOld; + +/** + * Page revision base class. + * + * RevisionRecords are considered value objects, but they may use callbacks for lazy loading. + * Note that while the base class has no setters, subclasses may offer a mutable interface. + * + * @since 1.31 + * @since 1.32 Renamed from MediaWiki\Storage\RevisionRecord + */ +abstract class XomwRevisionRecord { + + // RevisionRecord deletion constants + public static final int DELETED_TEXT = 1; + public static final int DELETED_COMMENT = 2; + public static final int DELETED_USER = 4; + public static final int DELETED_RESTRICTED = 8; + public static final int SUPPRESSED_USER = XomwRevisionRecord.DELETED_USER | XomwRevisionRecord.DELETED_RESTRICTED; // convenience + public static final int SUPPRESSED_ALL = XomwRevisionRecord.DELETED_TEXT | XomwRevisionRecord.DELETED_COMMENT | XomwRevisionRecord.DELETED_USER | + XomwRevisionRecord.DELETED_RESTRICTED; // convenience + + // Audience options for accessors + public static final int FOR_PUBLIC = 1; + public static final int FOR_THIS_USER = 2; + public static final int RAW = 3; + + /** @var string Wiki ID; false means the current wiki */ + protected String mWiki = XophpString_.False; + /** @var int|null */ + protected int mId; + /** @var int */ + protected int mPageId; + /** @var UserIdentity|null */ +// protected $mUser; + /** @var bool */ + protected boolean mMinorEdit = false; + /** @var string|null */ + protected String mTimestamp; + /** @var int using the DELETED_XXX and SUPPRESSED_XXX flags */ + protected int mDeleted = 0; + /** @var int|null */ + protected int mSize; + /** @var string|null */ + protected String mSha1; + /** @var int|null */ + protected int mParentId; + /** @var CommentStoreComment|null */ +// protected mComment; + + /** @var Title */ + protected XomwTitleOld mTitle; // TODO: we only need the title for permission checks! + +// /** @var RevisionSlots */ +// protected $mSlots; +// +// /** +// * @note Avoid calling this constructor directly. Use the appropriate methods +// * in RevisionStore instead. +// * +// * @param Title $title The title of the page this Revision is associated with. +// * @param RevisionSlots $slots The slots of this revision. +// * @param bool|string $wikiId the wiki ID of the site this Revision belongs to, +// * or false for the local site. +// * +// * @throws MWException +// */ +// function __construct( Title $title, RevisionSlots $slots, $wikiId = false ) { +// Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' ); +// +// this.mTitle = $title; +// this.mSlots = $slots; +// this.mWiki = $wikiId; +// +// // XXX: this is a sensible default, but we may not have a Title object here in the future. +// this.mPageId = $title->getArticleID(); +// } +// +// /** +// * Implemented to defy serialization. +// * +// * @throws LogicException always +// */ +// public function __sleep() { +// throw new LogicException( __CLASS__ . ' is not serializable.' ); +// } +// +// /** +// * @param RevisionRecord $rec +// * +// * @return bool True if this RevisionRecord is known to have same content as $rec. +// * False if the content is different (or not known to be the same). +// */ +// public function hasSameContent( RevisionRecord $rec ) { +// if ( $rec === $this ) { +// return true; +// } +// +// if ( this.getId() !== null && this.getId() === $rec->getId() ) { +// return true; +// } +// +// // check size before hash, since size is quicker to compute +// if ( this.getSize() !== $rec->getSize() ) { +// return false; +// } +// +// // instead of checking the hash, we could also check the content addresses of all slots. +// +// if ( this.getSha1() === $rec->getSha1() ) { +// return true; +// } +// +// return false; +// } +// +// /** +// * Returns the Content of the given slot of this revision. +// * Call getSlotNames() to get a list of available slots. +// * +// * Note that for mutable Content objects, each call to this method will return a +// * fresh clone. +// * +// * MCR migration note: this replaces Revision::getContent +// * +// * @param string $role The role name of the desired slot +// * @param int $audience +// * @param User|null $user +// * +// * @throws RevisionAccessException if the slot does not exist or slot data +// * could not be lazy-loaded. +// * @return Content|null The content of the given slot, or null if access is forbidden. +// */ +// public function getContent( $role, $audience = XomwRevisionRecord.FOR_PUBLIC, User $user = null ) { +// // XXX: throwing an exception would be nicer, but would a further +// // departure from the signature of Revision::getContent(), and thus +// // more complex and error prone refactoring. +// if ( !this.audienceCan( XomwRevisionRecord.DELETED_TEXT, $audience, $user ) ) { +// return null; +// } +// +// $content = this.getSlot( $role, $audience, $user )->getContent(); +// return $content->copy(); +// } +// +// /** +// * Returns meta-data for the given slot. +// * +// * @param string $role The role name of the desired slot +// * @param int $audience +// * @param User|null $user +// * +// * @throws RevisionAccessException if the slot does not exist or slot data +// * could not be lazy-loaded. +// * @return SlotRecord The slot meta-data. If access to the slot content is forbidden, +// * calling getContent() on the SlotRecord will throw an exception. +// */ +// public function getSlot( $role, $audience = XomwRevisionRecord.FOR_PUBLIC, User $user = null ) { +// $slot = this.mSlots->getSlot( $role ); +// +// if ( !this.audienceCan( XomwRevisionRecord.DELETED_TEXT, $audience, $user ) ) { +// return SlotRecord::newWithSuppressedContent( $slot ); +// } +// +// return $slot; +// } +// +// /** +// * Returns whether the given slot is defined in this revision. +// * +// * @param string $role The role name of the desired slot +// * +// * @return bool +// */ +// public function hasSlot( $role ) { +// return this.mSlots->hasSlot( $role ); +// } +// +// /** +// * Returns the slot names (roles) of all slots present in this revision. +// * getContent() will succeed only for the names returned by this method. +// * +// * @return string[] +// */ +// public function getSlotRoles() { +// return this.mSlots->getSlotRoles(); +// } +// +// /** +// * Returns the slots defined for this revision. +// * +// * @return RevisionSlots +// */ +// public function getSlots() { +// return this.mSlots; +// } +// +// /** +// * Returns the slots that originate in this revision. +// * +// * Note that this does not include any slots inherited from some earlier revision, +// * even if they are different from the slots in the immediate parent revision. +// * This is the case for rollbacks: slots of a rollback revision are inherited from +// * the rollback target, and are different from the slots in the parent revision, +// * which was rolled back. +// * +// * To find all slots modified by this revision against its immediate parent +// * revision, use RevisionSlotsUpdate::newFromRevisionSlots(). +// * +// * @return RevisionSlots +// */ +// public function getOriginalSlots() { +// return new RevisionSlots( this.mSlots->getOriginalSlots() ); +// } +// +// /** +// * Returns slots inherited from some previous revision. +// * +// * "Inherited" slots are all slots that do not originate in this revision. +// * Note that these slots may still differ from the one in the parent revision. +// * This is the case for rollbacks: slots of a rollback revision are inherited from +// * the rollback target, and are different from the slots in the parent revision, +// * which was rolled back. +// * +// * @return RevisionSlots +// */ +// public function getInheritedSlots() { +// return new RevisionSlots( this.mSlots->getInheritedSlots() ); +// } +// +// /** +// * Get revision ID. Depending on the concrete subclass, this may return null if +// * the revision ID is not known (e.g. because the revision does not yet exist +// * in the database). +// * +// * MCR migration note: this replaces Revision::getId +// * +// * @return int|null +// */ +// public function getId() { +// return this.mId; +// } +// +// /** +// * Get parent revision ID (the original previous page revision). +// * If there is no parent revision, this returns 0. +// * If the parent revision is undefined or unknown, this returns null. +// * +// * @note As of MW 1.31, the database schema allows the parent ID to be +// * NULL to indicate that it is unknown. +// * +// * MCR migration note: this replaces Revision::getParentId +// * +// * @return int|null +// */ +// public function getParentId() { +// return this.mParentId; +// } +// +// /** +// * Returns the nominal size of this revision, in bogo-bytes. +// * May be calculated on the fly if not known, which may in the worst +// * case may involve loading all content. +// * +// * MCR migration note: this replaces Revision::getSize +// * +// * @throws RevisionAccessException if the size was unknown and could not be calculated. +// * @return int +// */ +// abstract public function getSize(); +// +// /** +// * Returns the base36 sha1 of this revision. This hash is derived from the +// * hashes of all slots associated with the revision. +// * May be calculated on the fly if not known, which may in the worst +// * case may involve loading all content. +// * +// * MCR migration note: this replaces Revision::getSha1 +// * +// * @throws RevisionAccessException if the hash was unknown and could not be calculated. +// * @return string +// */ +// abstract public function getSha1(); + + /** + * Get the page ID. If the page does not yet exist, the page ID is 0. + * + * MCR migration note: this replaces Revision::getPage + * + * @return int + */ + public int getPageId() { + return this.mPageId; + } +// +// /** +// * Get the ID of the wiki this revision belongs to. +// * +// * @return string|false The wiki's logical name, of false to indicate the local wiki. +// */ +// public function getWikiId() { +// return this.mWiki; +// } +// +// /** +// * Returns the title of the page this revision is associated with as a LinkTarget object. +// * +// * MCR migration note: this replaces Revision::getTitle +// * +// * @return LinkTarget +// */ +// public function getPageAsLinkTarget() { +// return this.mTitle; +// } +// +// /** +// * Fetch revision's author's user identity, if it's available to the specified audience. +// * If the specified audience does not have access to it, null will be +// * returned. Depending on the concrete subclass, null may also be returned if the user is +// * not yet specified. +// * +// * MCR migration note: this replaces Revision::getUser +// * +// * @param int $audience One of: +// * RevisionRecord::FOR_PUBLIC to be displayed to all users +// * RevisionRecord::FOR_THIS_USER to be displayed to the given user +// * RevisionRecord::RAW get the ID regardless of permissions +// * @param User|null $user User object to check for, only if FOR_THIS_USER is passed +// * to the $audience parameter +// * @return UserIdentity|null +// */ +// public function getUser( $audience = XomwRevisionRecord.FOR_PUBLIC, User $user = null ) { +// if ( !this.audienceCan( XomwRevisionRecord.DELETED_USER, $audience, $user ) ) { +// return null; +// } else { +// return this.mUser; +// } +// } +// +// /** +// * Fetch revision comment, if it's available to the specified audience. +// * If the specified audience does not have access to the comment, +// * this will return null. Depending on the concrete subclass, null may also be returned +// * if the comment is not yet specified. +// * +// * MCR migration note: this replaces Revision::getComment +// * +// * @param int $audience One of: +// * RevisionRecord::FOR_PUBLIC to be displayed to all users +// * RevisionRecord::FOR_THIS_USER to be displayed to the given user +// * RevisionRecord::RAW get the text regardless of permissions +// * @param User|null $user User object to check for, only if FOR_THIS_USER is passed +// * to the $audience parameter +// * +// * @return CommentStoreComment|null +// */ +// public function getComment( $audience = XomwRevisionRecord.FOR_PUBLIC, User $user = null ) { +// if ( !this.audienceCan( XomwRevisionRecord.DELETED_COMMENT, $audience, $user ) ) { +// return null; +// } else { +// return this.mComment; +// } +// } +// +// /** +// * MCR migration note: this replaces Revision::isMinor +// * +// * @return bool +// */ +// public function isMinor() { +// return (bool)this.mMinorEdit; +// } +// +// /** +// * MCR migration note: this replaces Revision::isDeleted +// * +// * @param int $field One of DELETED_* bitfield constants +// * +// * @return bool +// */ +// public function isDeleted( $field ) { +// return ( this.getVisibility() & $field ) == $field; +// } +// +// /** +// * Get the deletion bitfield of the revision +// * +// * MCR migration note: this replaces Revision::getVisibility +// * +// * @return int +// */ +// public function getVisibility() { +// return (int)this.mDeleted; +// } +// +// /** +// * MCR migration note: this replaces Revision::getTimestamp. +// * +// * May return null if the timestamp was not specified. +// * +// * @return string|null +// */ +// public function getTimestamp() { +// return this.mTimestamp; +// } +// +// /** +// * Check that the given audience has access to the given field. +// * +// * MCR migration note: this corresponds to Revision::userCan +// * +// * @param int $field One of XomwRevisionRecord.DELETED_TEXT, +// * XomwRevisionRecord.DELETED_COMMENT, +// * XomwRevisionRecord.DELETED_USER +// * @param int $audience One of: +// * RevisionRecord::FOR_PUBLIC to be displayed to all users +// * RevisionRecord::FOR_THIS_USER to be displayed to the given user +// * RevisionRecord::RAW get the text regardless of permissions +// * @param User|null $user User object to check. Required if $audience is FOR_THIS_USER, +// * ignored otherwise. +// * +// * @return bool +// */ +// public function audienceCan( $field, $audience, User $user = null ) { +// if ( $audience == XomwRevisionRecord.FOR_PUBLIC && this.isDeleted( $field ) ) { +// return false; +// } elseif ( $audience == XomwRevisionRecord.FOR_THIS_USER ) { +// if ( !$user ) { +// throw new InvalidArgumentException( +// 'A User object must be given when checking FOR_THIS_USER audience.' +// ); +// } +// +// if ( !this.userCan( $field, $user ) ) { +// return false; +// } +// } +// +// return true; +// } +// +// /** +// * Determine if the current user is allowed to view a particular +// * field of this revision, if it's marked as deleted. +// * +// * MCR migration note: this corresponds to Revision::userCan +// * +// * @param int $field One of XomwRevisionRecord.DELETED_TEXT, +// * XomwRevisionRecord.DELETED_COMMENT, +// * XomwRevisionRecord.DELETED_USER +// * @param User $user User object to check +// * @return bool +// */ +// protected function userCan( $field, User $user ) { +// // TODO: use callback for permission checks, so we don't need to know a Title object! +// return XomwRevisionRecord.userCanBitfield( this.getVisibility(), $field, $user, this.mTitle ); +// } +// +// /** +// * Determine if the current user is allowed to view a particular +// * field of this revision, if it's marked as deleted. This is used +// * by various classes to avoid duplication. +// * +// * MCR migration note: this replaces Revision::userCanBitfield +// * +// * @param int $bitfield Current field +// * @param int $field One of XomwRevisionRecord.DELETED_TEXT = File::DELETED_FILE, +// * XomwRevisionRecord.DELETED_COMMENT = File::DELETED_COMMENT, +// * XomwRevisionRecord.DELETED_USER = File::DELETED_USER +// * @param User $user User object to check +// * @param Title|null $title A Title object to check for per-page restrictions on, +// * instead of just plain userrights +// * @return bool +// */ +// public static function userCanBitfield( $bitfield, $field, User $user, Title $title = null ) { +// if ( $bitfield & $field ) { // aspect is deleted +// if ( $bitfield & XomwRevisionRecord.DELETED_RESTRICTED ) { +// $permissions = [ 'suppressrevision', 'viewsuppressed' ]; +// } elseif ( $field & XomwRevisionRecord.DELETED_TEXT ) { +// $permissions = [ 'deletedtext' ]; +// } else { +// $permissions = [ 'deletedhistory' ]; +// } +// $permissionlist = implode( ', ', $permissions ); +// if ( $title === null ) { +// wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" ); +// return $user->isAllowedAny( ...$permissions ); +// } else { +// $text = $title->getPrefixedText(); +// wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" ); +// foreach ( $permissions as $perm ) { +// if ( $title->userCan( $perm, $user ) ) { +// return true; +// } +// } +// return false; +// } +// } else { +// return true; +// } +// } +// +// /** +// * Returns whether this RevisionRecord is ready for insertion, that is, whether it contains all +// * information needed to save it to the database. This should trivially be true for +// * RevisionRecords loaded from the database. +// * +// * Note that this may return true even if getId() or getPage() return null or 0, since these +// * are generally assigned while the revision is saved to the database, and may not be available +// * before. +// * +// * @return bool +// */ +// public function isReadyForInsertion() { +// // NOTE: don't check getSize() and getSha1(), since that may cause the full content to +// // be loaded in order to calculate the values. Just assume these methods will not return +// // null if mSlots is not empty. +// +// // NOTE: getId() and getPageId() may return null before a revision is saved, so don't +// //check them. +// +// return this.getTimestamp() !== null +// && this.getComment( XomwRevisionRecord.RAW ) !== null +// && this.getUser( XomwRevisionRecord.RAW ) !== null +// && this.mSlots->getSlotRoles() !== []; +// } +// +} + +///** +// * Retain the old class name for backwards compatibility. +// * @deprecated since 1.32 +// */ +//class_alias( RevisionRecord::class, 'MediaWiki\Storage\RevisionRecord' ); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker.java index 98ab88038..4596e5158 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.core.btries.*; import gplx.langs.htmls.*; @@ -124,7 +124,7 @@ public class XomwLinker { // 'https': Force a full URL with https:// as the scheme. // 'stubThreshold' => (int): Stub threshold to use when determining link classes. // @return String HTML attribute - public void Link(Bry_bfr bfr, XomwTitle target, byte[] html, Xomw_atr_mgr custom_attribs, Xomw_qry_mgr query, Xomw_opt_mgr options) { + public void Link(Bry_bfr bfr, XomwTitleOld target, byte[] html, Xomw_atr_mgr custom_attribs, Xomw_qry_mgr query, Xomw_opt_mgr options) { // XO.MW.UNSUPPORTED:MW has different renderers -- presumably for forcing "https:" and others; XO only has one //if (options != null) { // // Custom options, create new LinkRenderer @@ -190,7 +190,7 @@ public class XomwLinker { * @return String */ // XO.MW:SYNC:1.29; DATE:2017-02-08 - public void makeSelfLinkObj(Bry_bfr bfr, XomwTitle nt, byte[] html, byte[] query, byte[] trail, byte[] prefix) { + public void makeSelfLinkObj(Bry_bfr bfr, XomwTitleOld nt, byte[] html, byte[] query, byte[] trail, byte[] prefix) { // MW.HOOK:SelfLinkBegin if (html == Bry_.Empty) { html = tmp.Add_bry_escape_html(nt.getPrefixedText()).To_bry_and_clear(); @@ -236,7 +236,7 @@ public class XomwLinker { // * @param LinkTarget $target // * @return LinkTarget // */ - public static XomwTitle normaliseSpecialPage(XomwTitle target) { + public static XomwTitleOld normaliseSpecialPage(XomwTitleOld target) { // if (target.Ns().Id_is_special() && !target.Is_external()) { // list($name, $subpage) = SpecialPageFactory::resolveAlias($target->getDBkey()); // if (!$name) { @@ -331,7 +331,7 @@ public class XomwLinker { // @since 1.20 // @return String HTML for an image, with links, wrappers, etc. // XO.MW:SYNC:1.29; DATE:2017-02-08 - public void makeImageLink(Bry_bfr bfr, XomwEnv env, XomwParserCtx pctx, XomwParserIface parser, XomwTitle title, XomwFile file, Xomw_params_frame frameParams, Xomw_params_handler handlerParams, Object time, byte[] query, int widthOption) { + public void makeImageLink(Bry_bfr bfr, XomwEnv env, XomwParserCtx pctx, XomwParserIface parser, XomwTitleOld title, XomwFile file, Xomw_params_frame frameParams, Xomw_params_handler handlerParams, Object time, byte[] query, int widthOption) { // XO.MW.HOOK:ImageBeforeProduceHTML if (file != null && !file.allowInlineDisplay()) { @@ -534,7 +534,7 @@ public class XomwLinker { * @return String */ // XO.MW:SYNC:1.29; DATE:2017-02-08 - private void makeThumbLink2(Bry_bfr bfr, XomwEnv env, XomwParserCtx pctx, XomwTitle title, XomwFile file, Xomw_params_frame frameParams, Xomw_params_handler handlerParams, Object time, byte[] query) { + private void makeThumbLink2(Bry_bfr bfr, XomwEnv env, XomwParserCtx pctx, XomwTitleOld title, XomwFile file, Xomw_params_frame frameParams, Xomw_params_handler handlerParams, Object time, byte[] query) { boolean exists = file != null && file.exists(); int page = handlerParams.page; @@ -1367,7 +1367,7 @@ public class XomwLinker { * @return String */ // XO.MW:SYNC:1.29; DATE:2017-02-08 - public void normalizeSubpageLink(XomwLinker_NormalizeSubpageLink rv, XomwTitle context_title, byte[] target, byte[] text) { + public void normalizeSubpageLink(XomwLinker_NormalizeSubpageLink rv, XomwTitleOld context_title, byte[] target, byte[] text) { // Valid link forms: // Foobar -- normal // :Foobar -- override special treatment of prefix (images, language links) diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker_NormalizeSubpageLink_tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker_NormalizeSubpageLink_tst.java index 92d9fded0..28ad4aff0 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker_NormalizeSubpageLink_tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwLinker_NormalizeSubpageLink_tst.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import org.junit.*; import gplx.core.tests.*; public class XomwLinker_NormalizeSubpageLink_tst { @@ -33,7 +33,7 @@ class XomwLinker_NormalizeSubpageLink_fxt { this.env = XomwEnv_fxt.NewTest(); } public void Test__normalize_subpage_link(String page_title_str, String link, String text, String expd_link, String expd_text) { - mgr.normalizeSubpageLink(normalize_subpage_link, XomwTitle.newFromText(env, Bry_.new_u8(page_title_str)), Bry_.new_u8(link), Bry_.new_u8(text)); + mgr.normalizeSubpageLink(normalize_subpage_link, XomwTitleOld.newFromText(env, Bry_.new_u8(page_title_str)), Bry_.new_u8(link), Bry_.new_u8(text)); Gftest.Eq__str(expd_link, String_.new_u8(normalize_subpage_link.link)); Gftest.Eq__str(expd_text, String_.new_u8(normalize_subpage_link.text)); } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMessage.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMessage.java index 257f96f42..01f3ce4f5 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMessage.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwMessage.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.languages.*; import gplx.xowa.mediawiki.includes.content.*; @@ -224,7 +224,7 @@ public class XomwMessage { /** * @var Title Title Object to use as context. */ - private XomwTitle title = null; + private XomwTitleOld title = null; /** * @var Content Content Object representing the message. diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwRevision.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwRevision.java index 0b596e45c..72eae4d38 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwRevision.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwRevision.java @@ -1,20 +1,22 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.dao.*; +import gplx.xowa.mediawiki.includes.libs.rdbms.database.XomwIDatabase; +import gplx.xowa.mediawiki.includes.linkers.XomwLinkTarget; // MW.SRC:1.33.1 /** * @+deprecated since 1.31, use RevisionRecord, RevisionStore, and BlobStore instead. @@ -42,10 +44,10 @@ public class XomwRevision implements XomwIDBAccessObject { // /** // * @return RevisionStore // */ -// protected static function getRevisionStore( $wiki = false ) { -// if ( $wiki ) { +// protected static function getRevisionStore($wiki = false) { +// if ($wiki) { // return MediaWikiServices::getInstance()->getRevisionStoreFactory() -// ->getRevisionStore( $wiki ); +// ->getRevisionStore($wiki); // } else { // return MediaWikiServices::getInstance()->getRevisionStore(); // } @@ -70,57 +72,61 @@ public class XomwRevision implements XomwIDBAccessObject { // * // * @return SqlBlobStore // */ -// protected static function getBlobStore( $wiki = false ) { +// protected static function getBlobStore($wiki = false) { // $store = MediaWikiServices::getInstance() // ->getBlobStoreFactory() -// ->newSqlBlobStore( $wiki ); +// ->newSqlBlobStore($wiki); // -// if ( !$store instanceof SqlBlobStore ) { +// if (!$store instanceof SqlBlobStore) { // throw new RuntimeException( // 'The backwards compatibility code in Revision currently requires the BlobStore ' -// . 'service to be an SqlBlobStore instance, but it is a ' . get_class( $store ) +// . 'service to be an SqlBlobStore instance, but it is a ' . get_class($store) // ); // } // // return $store; // } // -// /** -// * Load a page revision from a given revision ID number. -// * Returns null if no such revision can be found. -// * -// * $flags include: -// * Revision::READ_LATEST : Select the data from the master -// * Revision::READ_LOCKING : Select & synchronized the data from the master -// * -// * @param int $id -// * @param int $flags (optional) -// * @return Revision|null -// */ -// public static function newFromId( $id, $flags = 0 ) { -// $rec = self::getRevisionLookup()->getRevisionById( $id, $flags ); -// return $rec ? new Revision( $rec, $flags ) : null; -// } -// -// /** -// * Load either the current, or a specified, revision -// * that's attached to a given link target. If not attached -// * to that link target, will return null. -// * -// * $flags include: -// * Revision::READ_LATEST : Select the data from the master -// * Revision::READ_LOCKING : Select & synchronized the data from the master -// * -// * @param LinkTarget $linkTarget -// * @param int $id (optional) -// * @param int $flags Bitfield (optional) -// * @return Revision|null -// */ -// public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) { -// $rec = self::getRevisionLookup()->getRevisionByTitle( $linkTarget, $id, $flags ); -// return $rec ? new Revision( $rec, $flags ) : null; -// } -// + /** + * Load a page revision from a given revision ID number. + * Returns null if no such revision can be found. + * + * $flags include: + * Revision::READ_LATEST : Select the data from the master + * Revision::READ_LOCKING : Select & synchronized the data from the master + * + * @param int $id + * @param int $flags (optional) + * @return Revision|null + */ + public static XomwRevision newFromId(int id) {return newFromId(id, 0);} + public static XomwRevision newFromId(int id, int flags) { +// $rec = self::getRevisionLookup()->getRevisionById($id, $flags); +// return $rec ? new Revision($rec, $flags) : null; + return null; + } + + /** + * Load either the current, or a specified, revision + * that's attached to a given link target. If not attached + * to that link target, will return null. + * + * $flags include: + * Revision::READ_LATEST : Select the data from the master + * Revision::READ_LOCKING : Select & synchronized the data from the master + * + * @param LinkTarget $linkTarget + * @param int $id (optional) + * @param int $flags Bitfield (optional) + * @return Revision|null + */ + public static XomwRevision newFromTitle(XomwLinkTarget linkTarget) {return newFromTitle(linkTarget, 0, 0);} + public static XomwRevision newFromTitle(XomwLinkTarget linkTarget, int id, int flags) { +// $rec = self::getRevisionLookup()->getRevisionByTitle($linkTarget, $id, $flags); +// return $rec ? new Revision($rec, $flags) : null; + return null; + } + // /** // * Load either the current, or a specified, revision // * that's attached to a given page ID. @@ -135,9 +141,9 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int $flags Bitfield (optional) // * @return Revision|null // */ -// public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) { -// $rec = self::getRevisionLookup()->getRevisionByPageId( $pageId, $revId, $flags ); -// return $rec ? new Revision( $rec, $flags ) : null; +// public static function newFromPageId($pageId, $revId = 0, $flags = 0) { +// $rec = self::getRevisionLookup()->getRevisionByPageId($pageId, $revId, $flags); +// return $rec ? new Revision($rec, $flags) : null; // } // // /** @@ -150,16 +156,16 @@ public class XomwRevision implements XomwIDBAccessObject { // * @throws MWException // * @return Revision // */ -// public static function newFromArchiveRow( $row, $overrides = [] ) { +// public static function newFromArchiveRow($row, $overrides = []) { // /** // * MCR Migration: https://phabricator.wikimedia.org/T183564 // * This method used to overwrite attributes, then passed to Revision::__construct // * RevisionStore::newRevisionFromArchiveRow instead overrides row field names // * So do a conversion here. // */ -// if ( array_key_exists( 'page', $overrides ) ) { +// if (array_key_exists('page', $overrides)) { // $overrides['page_id'] = $overrides['page']; -// unset( $overrides['page'] ); +// unset($overrides['page']); // } // // /** @@ -168,16 +174,16 @@ public class XomwRevision implements XomwIDBAccessObject { // * to fetch a title in order pass it into the Revision Object. // */ // $title = null; -// if ( isset( $overrides['title'] ) ) { -// if ( !( $overrides['title'] instanceof Title ) ) { -// throw new MWException( 'title field override must contain a Title Object.' ); +// if (isset($overrides['title'])) { +// if (!($overrides['title'] instanceof Title)) { +// throw new MWException('title field override must contain a Title Object.'); // } // // $title = $overrides['title']; // } -// if ( $title !== null ) { -// if ( isset( $row->ar_namespace ) && isset( $row->ar_title ) ) { -// $title = Title::makeTitle( $row->ar_namespace, $row->ar_title ); +// if ($title !== null) { +// if (isset($row->ar_namespace) && isset($row->ar_title)) { +// $title = Title::makeTitle($row->ar_namespace, $row->ar_title); // } else { // throw new InvalidArgumentException( // 'A Title or ar_namespace and ar_title must be given' @@ -185,8 +191,8 @@ public class XomwRevision implements XomwIDBAccessObject { // } // } // -// $rec = self::getRevisionFactory()->newRevisionFromArchiveRow( $row, 0, $title, $overrides ); -// return new Revision( $rec, self::READ_NORMAL, $title ); +// $rec = self::getRevisionFactory()->newRevisionFromArchiveRow($row, 0, $title, $overrides); +// return new Revision($rec, self::READ_NORMAL, $title); // } // // /** @@ -201,14 +207,14 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param Object|array $row // * @return Revision // */ -// public static function newFromRow( $row ) { -// if ( is_array( $row ) ) { -// $rec = self::getRevisionFactory()->newMutableRevisionFromArray( $row ); +// public static function newFromRow($row) { +// if (is_array($row)) { +// $rec = self::getRevisionFactory()->newMutableRevisionFromArray($row); // } else { -// $rec = self::getRevisionFactory()->newRevisionFromRow( $row ); +// $rec = self::getRevisionFactory()->newRevisionFromRow($row); // } // -// return new Revision( $rec ); +// return new Revision($rec); // } // // /** @@ -221,10 +227,10 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int $id // * @return Revision|null // */ -// public static function loadFromId( $db, $id ) { -// wfDeprecated( __METHOD__, '1.31' ); // no known callers -// $rec = self::getRevisionStore()->loadRevisionFromId( $db, $id ); -// return $rec ? new Revision( $rec ) : null; +// public static function loadFromId($db, $id) { +// wfDeprecated(__METHOD__, '1.31'); // no known callers +// $rec = self::getRevisionStore()->loadRevisionFromId($db, $id); +// return $rec ? new Revision($rec) : null; // } // // /** @@ -239,9 +245,9 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int $id // * @return Revision|null // */ -// public static function loadFromPageId( $db, $pageid, $id = 0 ) { -// $rec = self::getRevisionStore()->loadRevisionFromPageId( $db, $pageid, $id ); -// return $rec ? new Revision( $rec ) : null; +// public static function loadFromPageId($db, $pageid, $id = 0) { +// $rec = self::getRevisionStore()->loadRevisionFromPageId($db, $pageid, $id); +// return $rec ? new Revision($rec) : null; // } // // /** @@ -256,9 +262,9 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int $id // * @return Revision|null // */ -// public static function loadFromTitle( $db, $title, $id = 0 ) { -// $rec = self::getRevisionStore()->loadRevisionFromTitle( $db, $title, $id ); -// return $rec ? new Revision( $rec ) : null; +// public static function loadFromTitle($db, $title, $id = 0) { +// $rec = self::getRevisionStore()->loadRevisionFromTitle($db, $title, $id); +// return $rec ? new Revision($rec) : null; // } // // /** @@ -274,23 +280,23 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param String $timestamp // * @return Revision|null // */ -// public static function loadFromTimestamp( $db, $title, $timestamp ) { -// $rec = self::getRevisionStore()->loadRevisionFromTimestamp( $db, $title, $timestamp ); -// return $rec ? new Revision( $rec ) : null; +// public static function loadFromTimestamp($db, $title, $timestamp) { +// $rec = self::getRevisionStore()->loadRevisionFromTimestamp($db, $title, $timestamp); +// return $rec ? new Revision($rec) : null; // } // // /** // * Return the value of a select() JOIN conds array for the user table. // * This will get user table rows for logged-in users. // * @since 1.19 -// * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead. +// * @deprecated since 1.31, use RevisionStore::getQueryInfo([ 'user' ]) instead. // * @return array // */ // public static function userJoinCond() { // global $wgActorTableSchemaMigrationStage; // -// wfDeprecated( __METHOD__, '1.31' ); -// if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) { +// wfDeprecated(__METHOD__, '1.31'); +// if ($wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW) { // // If code is using this instead of self::getQueryInfo(), there's // // no way the join it's trying to do can work once the old fields // // aren't being used anymore. @@ -307,11 +313,11 @@ public class XomwRevision implements XomwIDBAccessObject { // * Return the value of a select() page conds array for the page table. // * This will assure that the revision(s) are not orphaned from live pages. // * @since 1.19 -// * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead. +// * @deprecated since 1.31, use RevisionStore::getQueryInfo([ 'page' ]) instead. // * @return array // */ // public static function pageJoinCond() { -// wfDeprecated( __METHOD__, '1.31' ); +// wfDeprecated(__METHOD__, '1.31'); // return [ 'JOIN', [ 'page_id = rev_page' ] ]; // } // @@ -325,7 +331,7 @@ public class XomwRevision implements XomwIDBAccessObject { // global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage; // global $wgMultiContentRevisionSchemaMigrationStage; // -// if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) { +// if ($wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW) { // // If code is using this instead of self::getQueryInfo(), there's a // // decent chance it's going to try to directly access // // $row->rev_user or $row->rev_user_text and we can't give it @@ -336,7 +342,7 @@ public class XomwRevision implements XomwIDBAccessObject { // ); // } // -// if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { +// if (!($wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD)) { // // If code is using this instead of self::getQueryInfo(), there's a // // decent chance it's going to try to directly access // // $row->rev_text_id or $row->rev_content_model and we can't give it @@ -348,7 +354,7 @@ public class XomwRevision implements XomwIDBAccessObject { // ); // } // -// wfDeprecated( __METHOD__, '1.31' ); +// wfDeprecated(__METHOD__, '1.31'); // // $fields = [ // 'rev_id', @@ -365,9 +371,9 @@ public class XomwRevision implements XomwIDBAccessObject { // 'rev_sha1', // ]; // -// $fields += CommentStore::getStore()->getFields( 'rev_comment' ); +// $fields += CommentStore::getStore()->getFields('rev_comment'); // -// if ( $wgContentHandlerUseDB ) { +// if ($wgContentHandlerUseDB) { // $fields[] = 'rev_content_format'; // $fields[] = 'rev_content_model'; // } @@ -385,7 +391,7 @@ public class XomwRevision implements XomwIDBAccessObject { // global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage; // global $wgMultiContentRevisionSchemaMigrationStage; // -// if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) { +// if ($wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW) { // // If code is using this instead of self::getQueryInfo(), there's a // // decent chance it's going to try to directly access // // $row->ar_user or $row->ar_user_text and we can't give it @@ -396,7 +402,7 @@ public class XomwRevision implements XomwIDBAccessObject { // ); // } // -// if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { +// if (!($wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD)) { // // If code is using this instead of self::getQueryInfo(), there's a // // decent chance it's going to try to directly access // // $row->ar_text_id or $row->ar_content_model and we can't give it @@ -408,7 +414,7 @@ public class XomwRevision implements XomwIDBAccessObject { // ); // } // -// wfDeprecated( __METHOD__, '1.31' ); +// wfDeprecated(__METHOD__, '1.31'); // // $fields = [ // 'ar_id', @@ -426,9 +432,9 @@ public class XomwRevision implements XomwIDBAccessObject { // 'ar_sha1', // ]; // -// $fields += CommentStore::getStore()->getFields( 'ar_comment' ); +// $fields += CommentStore::getStore()->getFields('ar_comment'); // -// if ( $wgContentHandlerUseDB ) { +// if ($wgContentHandlerUseDB) { // $fields[] = 'ar_content_format'; // $fields[] = 'ar_content_model'; // } @@ -438,11 +444,11 @@ public class XomwRevision implements XomwIDBAccessObject { // /** // * Return the list of text fields that should be selected to read the // * revision text -// * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'text' ] ) instead. +// * @deprecated since 1.31, use RevisionStore::getQueryInfo([ 'text' ]) instead. // * @return array // */ // public static function selectTextFields() { -// wfDeprecated( __METHOD__, '1.31' ); +// wfDeprecated(__METHOD__, '1.31'); // return [ // 'old_text', // 'old_flags' @@ -451,11 +457,11 @@ public class XomwRevision implements XomwIDBAccessObject { // // /** // * Return the list of page fields that should be selected from page table -// * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'page' ] ) instead. +// * @deprecated since 1.31, use RevisionStore::getQueryInfo([ 'page' ]) instead. // * @return array // */ // public static function selectPageFields() { -// wfDeprecated( __METHOD__, '1.31' ); +// wfDeprecated(__METHOD__, '1.31'); // return [ // 'page_namespace', // 'page_title', @@ -468,11 +474,11 @@ public class XomwRevision implements XomwIDBAccessObject { // // /** // * Return the list of user fields that should be selected from user table -// * @deprecated since 1.31, use RevisionStore::getQueryInfo( [ 'user' ] ) instead. +// * @deprecated since 1.31, use RevisionStore::getQueryInfo([ 'user' ]) instead. // * @return array // */ // public static function selectUserFields() { -// wfDeprecated( __METHOD__, '1.31' ); +// wfDeprecated(__METHOD__, '1.31'); // return [ 'user_name' ]; // } // @@ -490,8 +496,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * - fields: (String[]) to include in the `$vars` to `IDatabase->select()` // * - joins: (array) to include in the `$join_conds` to `IDatabase->select()` // */ -// public static function getQueryInfo( $options = [] ) { -// return self::getRevisionStore()->getQueryInfo( $options ); +// public static function getQueryInfo($options = []) { +// return self::getRevisionStore()->getQueryInfo($options); // } // // /** @@ -517,8 +523,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param array $revIds // * @return array // */ -// public static function getParentLengths( $db, array $revIds ) { -// return self::getRevisionStore()->listRevisionSizes( $db, $revIds ); +// public static function getParentLengths($db, array $revIds) { +// return self::getRevisionStore()->listRevisionSizes($db, $revIds); // } // // /** @@ -528,28 +534,28 @@ public class XomwRevision implements XomwIDBAccessObject { // * // * @private // */ -// function __construct( $row, $queryFlags = 0, Title $title = null ) { +// function __construct($row, $queryFlags = 0, Title $title = null) { // global $wgUser; // -// if ( $row instanceof RevisionRecord ) { +// if ($row instanceof RevisionRecord) { // $this->mRecord = $row; -// } elseif ( is_array( $row ) ) { +// } elseif (is_array($row)) { // // If no user is specified, fall back to using the global user Object, to stay // // compatible with pre-1.31 behavior. -// if ( !isset( $row['user'] ) && !isset( $row['user_text'] ) ) { +// if (!isset($row['user']) && !isset($row['user_text'])) { // $row['user'] = $wgUser; // } // // $this->mRecord = self::getRevisionFactory()->newMutableRevisionFromArray( // $row, // $queryFlags, -// $this->ensureTitle( $row, $queryFlags, $title ) +// $this->ensureTitle($row, $queryFlags, $title) // ); -// } elseif ( is_object( $row ) ) { +// } elseif (is_object($row)) { // $this->mRecord = self::getRevisionFactory()->newRevisionFromRow( // $row, // $queryFlags, -// $this->ensureTitle( $row, $queryFlags, $title ) +// $this->ensureTitle($row, $queryFlags, $title) // ); // } else { // throw new InvalidArgumentException( @@ -557,7 +563,7 @@ public class XomwRevision implements XomwIDBAccessObject { // ); // } // -// Assert::postcondition( $this->mRecord !== null, 'Failed to construct a RevisionRecord' ); +// Assert::postcondition($this->mRecord !== null, 'Failed to construct a RevisionRecord'); // } // // /** @@ -570,15 +576,15 @@ public class XomwRevision implements XomwIDBAccessObject { // * // * @return Title $title if not null, or a Title constructed from information in $row. // */ -// private function ensureTitle( $row, $queryFlags, $title = null ) { -// if ( $title ) { +// private function ensureTitle($row, $queryFlags, $title = null) { +// if ($title) { // return $title; // } // -// if ( is_array( $row ) ) { -// if ( isset( $row['title'] ) ) { -// if ( !( $row['title'] instanceof Title ) ) { -// throw new MWException( 'title field must contain a Title Object.' ); +// if (is_array($row)) { +// if (isset($row['title'])) { +// if (!($row['title'] instanceof Title)) { +// throw new MWException('title field must contain a Title Object.'); // } // // return $row['title']; @@ -592,14 +598,14 @@ public class XomwRevision implements XomwIDBAccessObject { // } // // try { -// $title = self::getRevisionStore()->getTitle( $pageId, $revId, $queryFlags ); -// } catch ( RevisionAccessException $ex ) { +// $title = self::getRevisionStore()->getTitle($pageId, $revId, $queryFlags); +// } catch (RevisionAccessException $ex) { // // construct a dummy title! -// wfLogWarning( __METHOD__ . ': ' . $ex->getMessage() ); +// wfLogWarning(__METHOD__ . ': ' . $ex->getMessage()); // // // NOTE: this Title will only be used inside RevisionRecord -// $title = Title::makeTitleSafe( NS_SPECIAL, "Badtitle/ID=$pageId" ); -// $title->resetArticleID( $pageId ); +// $title = Title::makeTitleSafe(NS_SPECIAL, "Badtitle/ID=$pageId"); +// $title->resetArticleID($pageId); // } // // return $title; @@ -611,15 +617,16 @@ public class XomwRevision implements XomwIDBAccessObject { // public function getRevisionRecord() { // return $this->mRecord; // } -// -// /** -// * Get revision ID -// * -// * @return int|null -// */ -// public function getId() { + + /** + * Get revision ID + * + * @return int|null + */ + public int getId() { // return $this->mRecord->getId(); -// } + return -1; + } // // /** // * Set the revision ID @@ -633,11 +640,11 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int|String $id // * @throws MWException // */ -// public function setId( $id ) { -// if ( $this->mRecord instanceof MutableRevisionRecord ) { -// $this->mRecord->setId( intval( $id ) ); +// public function setId($id) { +// if ($this->mRecord instanceof MutableRevisionRecord) { +// $this->mRecord->setId(intval($id)); // } else { -// throw new MWException( __METHOD__ . ' is not supported on this instance' ); +// throw new MWException(__METHOD__ . ' is not supported on this instance'); // } // } // @@ -655,12 +662,12 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param String $name User name // * @throws MWException // */ -// public function setUserIdAndName( $id, $name ) { -// if ( $this->mRecord instanceof MutableRevisionRecord ) { -// $user = User::newFromAnyId( intval( $id ), $name, null ); -// $this->mRecord->setUser( $user ); +// public function setUserIdAndName($id, $name) { +// if ($this->mRecord instanceof MutableRevisionRecord) { +// $user = User::newFromAnyId(intval($id), $name, null); +// $this->mRecord->setUser($user); // } else { -// throw new MWException( __METHOD__ . ' is not supported on this instance' ); +// throw new MWException(__METHOD__ . ' is not supported on this instance'); // } // } // @@ -668,7 +675,7 @@ public class XomwRevision implements XomwIDBAccessObject { // * @return SlotRecord // */ // private function getMainSlotRaw() { -// return $this->mRecord->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ); +// return $this->mRecord->getSlot(SlotRecord::MAIN, RevisionRecord::RAW); // } // // /** @@ -686,7 +693,7 @@ public class XomwRevision implements XomwIDBAccessObject { // public function getTextId() { // $slot = $this->getMainSlotRaw(); // return $slot->hasAddress() -// ? self::getBlobStore()->getTextIdFromAddress( $slot->getAddress() ) +// ? self::getBlobStore()->getTextIdFromAddress($slot->getAddress()) // : null; // } // @@ -708,7 +715,7 @@ public class XomwRevision implements XomwIDBAccessObject { // public function getSize() { // try { // return $this->mRecord->getSize(); -// } catch ( RevisionAccessException $ex ) { +// } catch (RevisionAccessException $ex) { // return null; // } // } @@ -721,7 +728,7 @@ public class XomwRevision implements XomwIDBAccessObject { // public function getSha1() { // try { // return $this->mRecord->getSha1(); -// } catch ( RevisionAccessException $ex ) { +// } catch (RevisionAccessException $ex) { // return null; // } // } @@ -736,7 +743,7 @@ public class XomwRevision implements XomwIDBAccessObject { // */ // public function getTitle() { // $linkTarget = $this->mRecord->getPageAsLinkTarget(); -// return Title::newFromLinkTarget( $linkTarget ); +// return Title::newFromLinkTarget($linkTarget); // } // // /** @@ -746,8 +753,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * // * @param Title $title // */ -// public function setTitle( $title ) { -// if ( !$title->equals( $this->getTitle() ) ) { +// public function setTitle($title) { +// if (!$title->equals($this->getTitle())) { // throw new InvalidArgumentException( // $title->getPrefixedText() // . ' is not the same as ' @@ -778,14 +785,14 @@ public class XomwRevision implements XomwIDBAccessObject { // * to the $audience parameter // * @return int // */ -// public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) { +// public function getUser($audience = self::FOR_PUBLIC, User $user = null) { // global $wgUser; // -// if ( $audience === self::FOR_THIS_USER && !$user ) { +// if ($audience === self::FOR_THIS_USER && !$user) { // $user = $wgUser; // } // -// $user = $this->mRecord->getUser( $audience, $user ); +// $user = $this->mRecord->getUser($audience, $user); // return $user ? $user->getId() : 0; // } // @@ -802,14 +809,14 @@ public class XomwRevision implements XomwIDBAccessObject { // * to the $audience parameter // * @return String // */ -// public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) { +// public function getUserText($audience = self::FOR_PUBLIC, User $user = null) { // global $wgUser; // -// if ( $audience === self::FOR_THIS_USER && !$user ) { +// if ($audience === self::FOR_THIS_USER && !$user) { // $user = $wgUser; // } // -// $user = $this->mRecord->getUser( $audience, $user ); +// $user = $this->mRecord->getUser($audience, $user); // return $user ? $user->getName() : ''; // } // @@ -824,14 +831,14 @@ public class XomwRevision implements XomwIDBAccessObject { // * @return String|null Returns null if the specified audience does not have access to the // * comment. // */ -// function getComment( $audience = self::FOR_PUBLIC, User $user = null ) { +// function getComment($audience = self::FOR_PUBLIC, User $user = null) { // global $wgUser; // -// if ( $audience === self::FOR_THIS_USER && !$user ) { +// if ($audience === self::FOR_THIS_USER && !$user) { // $user = $wgUser; // } // -// $comment = $this->mRecord->getComment( $audience, $user ); +// $comment = $this->mRecord->getComment($audience, $user); // return $comment === null ? null : $comment->text; // } // @@ -846,7 +853,7 @@ public class XomwRevision implements XomwIDBAccessObject { // * @return int Rcid of the unpatrolled row, zero if there isn't one // */ // public function isUnpatrolled() { -// return self::getRevisionStore()->getRcIdIfUnpatrolled( $this->mRecord ); +// return self::getRevisionStore()->getRcIdIfUnpatrolled($this->mRecord); // } // // /** @@ -858,8 +865,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @since 1.22 // * @return RecentChange|null // */ -// public function getRecentChange( $flags = 0 ) { -// return self::getRevisionStore()->getRecentChange( $this->mRecord, $flags ); +// public function getRecentChange($flags = 0) { +// return self::getRevisionStore()->getRecentChange($this->mRecord, $flags); // } // // /** @@ -867,8 +874,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * // * @return boolean // */ -// public function isDeleted( $field ) { -// return $this->mRecord->isDeleted( $field ); +// public function isDeleted($field) { +// return $this->mRecord->isDeleted($field); // } // // /** @@ -894,17 +901,17 @@ public class XomwRevision implements XomwIDBAccessObject { // * @since 1.21 // * @return Content|null // */ -// public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) { +// public function getContent($audience = self::FOR_PUBLIC, User $user = null) { // global $wgUser; // -// if ( $audience === self::FOR_THIS_USER && !$user ) { +// if ($audience === self::FOR_THIS_USER && !$user) { // $user = $wgUser; // } // // try { -// return $this->mRecord->getContent( SlotRecord::MAIN, $audience, $user ); +// return $this->mRecord->getContent(SlotRecord::MAIN, $audience, $user); // } -// catch ( RevisionAccessException $e ) { +// catch (RevisionAccessException $e) { // return null; // } // } @@ -952,7 +959,7 @@ public class XomwRevision implements XomwIDBAccessObject { // public function getContentFormat() { // $format = $this->getMainSlotRaw()->getFormat(); // -// if ( $format === null ) { +// if ($format === null) { // // if no format was stored along with the blob, fall back to default format // $format = $this->getContentHandler()->getDefaultFormat(); // } @@ -967,7 +974,7 @@ public class XomwRevision implements XomwIDBAccessObject { // * @return ContentHandler // */ // public function getContentHandler() { -// return ContentHandler::getForModelID( $this->getContentModel() ); +// return ContentHandler::getForModelID($this->getContentModel()); // } // // /** @@ -981,7 +988,7 @@ public class XomwRevision implements XomwIDBAccessObject { // * @return boolean // */ // public function isCurrent() { -// return ( $this->mRecord instanceof RevisionStoreRecord ) && $this->mRecord->isCurrent(); +// return ($this->mRecord instanceof RevisionStoreRecord) && $this->mRecord->isCurrent(); // } // // /** @@ -991,8 +998,8 @@ public class XomwRevision implements XomwIDBAccessObject { // */ // public function getPrevious() { // $title = $this->getTitle(); -// $rec = self::getRevisionLookup()->getPreviousRevision( $this->mRecord, $title ); -// return $rec ? new Revision( $rec, self::READ_NORMAL, $title ) : null; +// $rec = self::getRevisionLookup()->getPreviousRevision($this->mRecord, $title); +// return $rec ? new Revision($rec, self::READ_NORMAL, $title) : null; // } // // /** @@ -1002,8 +1009,8 @@ public class XomwRevision implements XomwIDBAccessObject { // */ // public function getNext() { // $title = $this->getTitle(); -// $rec = self::getRevisionLookup()->getNextRevision( $this->mRecord, $title ); -// return $rec ? new Revision( $rec, self::READ_NORMAL, $title ) : null; +// $rec = self::getRevisionLookup()->getNextRevision($this->mRecord, $title); +// return $rec ? new Revision($rec, self::READ_NORMAL, $title) : null; // } // // /** @@ -1027,18 +1034,18 @@ public class XomwRevision implements XomwIDBAccessObject { // * identifier as understood by the LoadBalancer class. // * @return String|false Text the text requested or false on failure // */ -// public static function getRevisionText( $row, $prefix = 'old_', $wiki = false ) { +// public static function getRevisionText($row, $prefix = 'old_', $wiki = false) { // global $wgMultiContentRevisionSchemaMigrationStage; // -// if ( !$row ) { +// if (!$row) { // return false; // } // // $textField = $prefix . 'text'; // $flagsField = $prefix . 'flags'; // -// if ( isset( $row->$textField ) ) { -// if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { +// if (isset($row->$textField)) { +// if (!($wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD)) { // // The text field was read, but it's no longer being populated! // // We could gloss over this by using the text when it's there and loading // // if when it's not, but it seems preferable to complain loudly about a @@ -1054,38 +1061,38 @@ public class XomwRevision implements XomwIDBAccessObject { // } else { // // Missing text field, we are probably looking at the MCR-enabled DB schema. // -// if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { +// if (!($wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD)) { // // This method should no longer be used with the new schema. Ideally, we // // would already trigger a deprecation warning when SCHEMA_COMPAT_READ_NEW is set. -// wfDeprecated( __METHOD__ . ' (MCR without SCHEMA_COMPAT_WRITE_OLD)', '1.32' ); +// wfDeprecated(__METHOD__ . ' (MCR without SCHEMA_COMPAT_WRITE_OLD)', '1.32'); // } // -// $store = self::getRevisionStore( $wiki ); +// $store = self::getRevisionStore($wiki); // $rev = $prefix === 'ar_' -// ? $store->newRevisionFromArchiveRow( $row ) -// : $store->newRevisionFromRow( $row ); +// ? $store->newRevisionFromArchiveRow($row) +// : $store->newRevisionFromRow($row); // -// $content = $rev->getContent( SlotRecord::MAIN ); +// $content = $rev->getContent(SlotRecord::MAIN); // return $content ? $content->serialize() : false; // } // -// if ( isset( $row->$flagsField ) ) { -// $flags = explode( ',', $row->$flagsField ); +// if (isset($row->$flagsField)) { +// $flags = explode(',', $row->$flagsField); // } else { // $flags = []; // } // -// $cacheKey = isset( $row->old_id ) -// ? SqlBlobStore::makeAddressFromTextId( $row->old_id ) +// $cacheKey = isset($row->old_id) +// ? SqlBlobStore::makeAddressFromTextId($row->old_id) // : null; // -// $revisionText = self::getBlobStore( $wiki )->expandBlob( $text, $flags, $cacheKey ); +// $revisionText = self::getBlobStore($wiki)->expandBlob($text, $flags, $cacheKey); // -// if ( $revisionText === false ) { -// if ( isset( $row->old_id ) ) { -// wfLogWarning( __METHOD__ . ": Bad data in text row {$row->old_id}! " ); +// if ($revisionText === false) { +// if (isset($row->old_id)) { +// wfLogWarning(__METHOD__ . ": Bad data in text row {$row->old_id}! "); // } else { -// wfLogWarning( __METHOD__ . ": Bad data in text row! " ); +// wfLogWarning(__METHOD__ . ": Bad data in text row! "); // } // return false; // } @@ -1103,8 +1110,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param mixed &$text Reference to a text // * @return String // */ -// public static function compressRevisionText( &$text ) { -// return self::getBlobStore()->compressData( $text ); +// public static function compressRevisionText(&$text) { +// return self::getBlobStore()->compressData($text); // } // // /** @@ -1114,13 +1121,13 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param array $flags Compression flags // * @return String|boolean Decompressed text, or false on failure // */ -// public static function decompressRevisionText( $text, $flags ) { -// if ( $text === false ) { +// public static function decompressRevisionText($text, $flags) { +// if ($text === false) { // // Text failed to be fetched; nothing to do // return false; // } // -// return self::getBlobStore()->decompressData( $text, $flags ); +// return self::getBlobStore()->decompressData($text, $flags); // } // // /** @@ -1131,24 +1138,24 @@ public class XomwRevision implements XomwIDBAccessObject { // * @throws MWException // * @return int The revision ID // */ -// public function insertOn( $dbw ) { +// public function insertOn($dbw) { // global $wgUser; // // // Note that $this->mRecord->getId() will typically return null here, but not always, // // e.g. not when restoring a revision. // -// if ( $this->mRecord->getUser( RevisionRecord::RAW ) === null ) { -// if ( $this->mRecord instanceof MutableRevisionRecord ) { -// $this->mRecord->setUser( $wgUser ); +// if ($this->mRecord->getUser(RevisionRecord::RAW) === null) { +// if ($this->mRecord instanceof MutableRevisionRecord) { +// $this->mRecord->setUser($wgUser); // } else { -// throw new MWException( 'Cannot insert revision with no associated user.' ); +// throw new MWException('Cannot insert revision with no associated user.'); // } // } // -// $rec = self::getRevisionStore()->insertRevisionOn( $this->mRecord, $dbw ); +// $rec = self::getRevisionStore()->insertRevisionOn($this->mRecord, $dbw); // // $this->mRecord = $rec; -// Assert::postcondition( $this->mRecord !== null, 'Failed to acquire a RevisionRecord' ); +// Assert::postcondition($this->mRecord !== null, 'Failed to acquire a RevisionRecord'); // // return $rec->getId(); // } @@ -1158,8 +1165,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param String $text // * @return String // */ -// public static function base36Sha1( $text ) { -// return SlotRecord::base36Sha1( $text ); +// public static function base36Sha1($text) { +// return SlotRecord::base36Sha1($text); // } // // /** @@ -1177,22 +1184,22 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param User|null $user User Object to use or null for $wgUser // * @return Revision|null Revision or null on error // */ -// public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) { +// public static function newNullRevision($dbw, $pageId, $summary, $minor, $user = null) { // global $wgUser; -// if ( !$user ) { +// if (!$user) { // $user = $wgUser; // } // -// $comment = CommentStoreComment::newUnsavedComment( $summary, null ); +// $comment = CommentStoreComment::newUnsavedComment($summary, null); // -// $title = Title::newFromID( $pageId, Title::GAID_FOR_UPDATE ); -// if ( $title === null ) { +// $title = Title::newFromID($pageId, Title::GAID_FOR_UPDATE); +// if ($title === null) { // return null; // } // -// $rec = self::getRevisionStore()->newNullRevision( $dbw, $title, $comment, $minor, $user ); +// $rec = self::getRevisionStore()->newNullRevision($dbw, $title, $comment, $minor, $user); // -// return $rec ? new Revision( $rec ) : null; +// return $rec ? new Revision($rec) : null; // } // // /** @@ -1205,8 +1212,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param User|null $user User Object to check, or null to use $wgUser // * @return boolean // */ -// public function userCan( $field, User $user = null ) { -// return self::userCanBitfield( $this->getVisibility(), $field, $user ); +// public function userCan($field, User $user = null) { +// return self::userCanBitfield($this->getVisibility(), $field, $user); // } // // /** @@ -1223,16 +1230,16 @@ public class XomwRevision implements XomwIDBAccessObject { // * instead of just plain userrights // * @return boolean // */ -// public static function userCanBitfield( $bitfield, $field, User $user = null, +// public static function userCanBitfield($bitfield, $field, User $user = null, // Title $title = null // ) { // global $wgUser; // -// if ( !$user ) { +// if (!$user) { // $user = $wgUser; // } // -// return RevisionRecord::userCanBitfield( $bitfield, $field, $user, $title ); +// return RevisionRecord::userCanBitfield($bitfield, $field, $user, $title); // } // // /** @@ -1243,8 +1250,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int $flags // * @return String|boolean False if not found // */ -// static function getTimestampFromId( $title, $id, $flags = 0 ) { -// return self::getRevisionStore()->getTimestampFromId( $title, $id, $flags ); +// static function getTimestampFromId($title, $id, $flags = 0) { +// return self::getRevisionStore()->getTimestampFromId($title, $id, $flags); // } // // /** @@ -1254,8 +1261,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param int $id Page id // * @return int // */ -// static function countByPageId( $db, $id ) { -// return self::getRevisionStore()->countRevisionsByPageId( $db, $id ); +// static function countByPageId($db, $id) { +// return self::getRevisionStore()->countRevisionsByPageId($db, $id); // } // // /** @@ -1265,8 +1272,8 @@ public class XomwRevision implements XomwIDBAccessObject { // * @param Title $title // * @return int // */ -// static function countByTitle( $db, $title ) { -// return self::getRevisionStore()->countRevisionsByTitle( $db, $title ); +// static function countByTitle($db, $title) { +// return self::getRevisionStore()->countRevisionsByTitle($db, $title); // } // // /** @@ -1285,37 +1292,38 @@ public class XomwRevision implements XomwIDBAccessObject { // * // * @return boolean True if the given user was the only one to edit since the given timestamp // */ -// public static function userWasLastToEdit( $db, $pageId, $userId, $since ) { -// if ( is_int( $db ) ) { -// $db = wfGetDB( $db ); -// } -// -// return self::getRevisionStore()->userWasLastToEdit( $db, $pageId, $userId, $since ); -// } -// -// /** -// * Load a revision based on a known page ID and current revision ID from the DB -// * -// * This method allows for the use of caching, though accessing anything that normally -// * requires permission checks (aside from the text) will trigger a small DB lookup. -// * The title will also be loaded if $pageIdOrTitle is an integer ID. -// * -// * @param IDatabase $db ignored! -// * @param int|Title $pageIdOrTitle Page ID or Title Object -// * @param int $revId Known current revision of this page. Determined automatically if not given. -// * @return Revision|boolean Returns false if missing -// * @since 1.28 -// */ -// public static function newKnownCurrent( IDatabase $db, $pageIdOrTitle, $revId = 0 ) { -// $title = $pageIdOrTitle instanceof Title -// ? $pageIdOrTitle -// : Title::newFromID( $pageIdOrTitle ); -// -// if ( !$title ) { -// return false; +// public static function userWasLastToEdit($db, $pageId, $userId, $since) { +// if (is_int($db)) { +// $db = wfGetDB($db); // } // -// $record = self::getRevisionLookup()->getKnownCurrentRevision( $title, $revId ); -// return $record ? new Revision( $record ) : false; -// } +// return self::getRevisionStore()->userWasLastToEdit($db, $pageId, $userId, $since); +// } + + /** + * Load a revision based on a known page ID and current revision ID from the DB + * + * This method allows for the use of caching, though accessing anything that normally + * requires permission checks (aside from the text) will trigger a small DB lookup. + * The title will also be loaded if $pageIdOrTitle is an integer ID. + * + * @param IDatabase $db ignored! + * @param int|Title $pageIdOrTitle Page ID or Title Object + * @param int $revId Known current revision of this page. Determined automatically if not given. + * @return Revision|boolean Returns false if missing + * @since 1.28 + */ + public static XomwRevision newKnownCurrent(XomwIDatabase $db, Object pageIdOrTitle, int revId) { // $revId = 0 + XomwTitleOld title = XophpType_.instance_of(pageIdOrTitle, XomwTitleOld.class) + ? (XomwTitleOld)pageIdOrTitle + : XomwTitleOld.newFromID(XophpInt_.cast(pageIdOrTitle)); + + if (!XophpObject_.is_true(title)) { + return null; + } + +// $record = self::getRevisionLookup()->getKnownCurrentRevision($title, $revId); +// return $record ? new Revision($record) : false; + return null; + } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle.java index 8884fde3e..55ea8913d 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle.java @@ -1,4944 +1,4614 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ -package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; -import gplx.xowa.mediawiki.includes.title.*; /** -* Represents a title within MediaWiki. -* Optionally may contain an interwiki designation or namespace. -* @note This class can fetch various kinds of data from the database; -* however, it does so inefficiently. -* @note Consider using a TitleValue Object instead. TitleValue is more lightweight -* and does not rely on global state or the database. -*/ -public class XomwTitle { -// /** @var HashBagOStuff */ -// static private $titleCache = null; -// -// /** -// * Title::newFromText maintains a cache to avoid expensive re-normalization of -// * commonly used titles. On a batch operation this can become a memory leak -// * if not bounded. After hitting this many titles reset the cache. -// */ -// static final CACHE_MAX = 1000; -// -// /** -// * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends -// * to use the master DB -// */ -// static final GAID_FOR_UPDATE = 1; -// -// /** -// * @name Private member variables -// * Please use the accessor functions instead. -// * @private -// */ -// // @{ - - /** @var String Text form (spaces not underscores) of the main part */ - private byte[] mTextform = Bry_.Empty; - - /** @var String URL-encoded form of the main part */ - private byte[] mUrlform = Bry_.Empty; - - /** @var String Main part with underscores */ - // XO: EX: "Help_talk:A_b" . "A_b" - private byte[] mDbkeyform = Bry_.Empty; - - /** @var String Database key with the initial letter in the case specified by the user */ - protected byte[] mUserCaseDBKey; - - /** @var int Namespace index, i.e. one of the NS_xxxx constants */ - public int mNamespace = 0; - - /** @var String Interwiki prefix */ - public byte[] mInterwiki = Bry_.Empty; - - /** @var boolean Was this Title created from a String with a local interwiki prefix? */ - private boolean mLocalInterwiki = false; - - /** @var String Title fragment (i.e. the bit after the #) */ - private byte[] mFragment = Bry_.Empty; - - /** @var int Article ID, fetched from the link cache on demand */ - public int mArticleID = -1; - -// /** @var boolean|int ID of most recent revision */ -// protected $mLatestID = false; -// -// /** -// * @var boolean|String ID of the page's content model, i.e. one of the -// * CONTENT_MODEL_XXX constants -// */ -// private $mContentModel = false; -// -// /** -// * @var boolean If a content model was forced via setContentModel() -// * this will be true to avoid having other code paths reset it -// */ -// private $mForcedContentModel = false; -// -// /** @var int Estimated number of revisions; null of not loaded */ -// private $mEstimateRevisions; -// -// /** @var array Array of groups allowed to edit this article */ -// public $mRestrictions = []; -// -// /** @var String|boolean */ -// protected $mOldRestrictions = false; -// -// /** @var boolean Cascade restrictions on this page to included templates and images? */ -// public $mCascadeRestriction; -// -// /** Caching the results of getCascadeProtectionSources */ -// public $mCascadingRestrictions; -// -// /** @var array When do the restrictions on this page expire? */ -// protected $mRestrictionsExpiry = []; -// -// /** @var boolean Are cascading restrictions in effect on this page? */ -// protected $mHasCascadingRestrictions; -// -// /** @var array Where are the cascading restrictions coming from on this page? */ -// public $mCascadeSources; -// -// /** @var boolean Boolean for initialisation on demand */ -// public $mRestrictionsLoaded = false; - - /** @var String Text form including namespace/interwiki, initialised on demand */ - private byte[] mPrefixedText = null; - -// /** @var mixed Cached value for getTitleProtection (create protection) */ -// public $mTitleProtection; - - /** - * @var int Namespace index when there is no namespace. Don't change the - * following default, NS_MAIN is hardcoded in several places. See bug 696. - * Zero except in {{transclusion}} tags. - */ - public int mDefaultNamespace = XomwDefines.NS_MAIN; - -// /** @var int The page length, 0 for special pages */ -// protected $mLength = -1; -// -// /** @var null Is the article at this title a redirect? */ -// public $mRedirect = null; -// -// /** @var array Associative array of user ID . timestamp/false */ -// private $mNotificationTimestamp = []; -// -// /** @var boolean Whether a page has any subpages */ -// private $mHasSubpages; -// -// /** @var boolean The (String) language code of the page's language and content code. */ -// private $mPageLanguage = false; -// -// /** @var String|boolean|null The page language code from the database, null if not saved in -// * the database or false if not loaded, yet. */ -// private $mDbPageLanguage = false; -// -// /** @var TitleValue A corresponding TitleValue Object */ -// private $mTitleValue = null; -// -// /** @var boolean Would deleting this page be a big deletion? */ -// private $mIsBigDeletion = null; -// // @} - - private final XomwEnv env; - /** - * B/C kludge: provide a TitleParser for use by Title. - * Ideally, Title would have no methods that need this. - * Avoid usage of this singleton by using TitleValue - * and the associated services when possible. - * - * @return TitleFormatter - */ - // private static XomwMediaWikiTitleCodec getTitleFormatter() { - // return XomwMediaWikiServices.getInstance().getTitleFormatter(); - // } - -// /** -// * B/C kludge: provide an InterwikiLookup for use by Title. -// * Ideally, Title would have no methods that need this. -// * Avoid usage of this singleton by using TitleValue -// * and the associated services when possible. -// * -// * @return InterwikiLookup -// */ -// private static function getInterwikiLookup() { -// return MediaWikiServices::getInstance().getInterwikiLookup(); -// } - - /** - * @access protected - */ - XomwTitle(XomwEnv env) { - this.env = env; - } - -// /** -// * Create a new Title from a prefixed DB key -// * -// * @param String $key The database key, which has underscores -// * instead of spaces, possibly including namespace and -// * interwiki prefixes -// * @return Title|null Title, or null on an error -// */ -// public static function newFromDBkey($key) { -// t = new Title(); -// t.mDbkeyform = $key; -// -// try { -// t.secureAndSplit(); -// return t; -// } catch (XomwMalformedTitleException $ex) { -// return null; -// } -// } -// -// /** -// * Create a new Title from a TitleValue -// * -// * @param TitleValue $titleValue Assumed to be safe. -// * -// * @return Title -// */ -// public static function newFromTitleValue(TitleValue $titleValue) { -// return self::newFromLinkTarget($titleValue); -// } -// -// /** -// * Create a new Title from a LinkTarget -// * -// * @param LinkTarget $linkTarget Assumed to be safe. -// * -// * @return Title -// */ -// public static function newFromLinkTarget(LinkTarget $linkTarget) { -// if ($linkTarget instanceof Title) { -// // Special case if it's already a Title Object -// return $linkTarget; -// } -// return self::makeTitle( -// $linkTarget.getNamespace(), -// $linkTarget.getText(), -// $linkTarget.getFragment(), -// $linkTarget.getInterwiki() -// ); -// } - - /** - * Create a new Title from text, such as what one would find in a link. De- - * codes any HTML entities in the text. - * - * @param String|int|null $text The link text; spaces, prefixes, and an - * initial ':' indicating the main namespace are accepted. - * @param int $defaultNamespace The namespace to use if none is specified - * by a prefix. If you want to force a specific namespace even if - * $text might begin with a namespace prefix, use makeTitle() or - * makeTitleSafe(). - * @throws InvalidArgumentException - * @return Title|null Title or null on an error. - */ - public static XomwTitle newFromText(XomwEnv env, byte[] text) {return newFromText(env, text, XomwDefines.NS_MAIN);} - public static XomwTitle newFromText(XomwEnv env, String text, int defaultNamespace) {return newFromText(env, Bry_.new_u8(text));} - private static XomwTitle newFromText(XomwEnv env, byte[] text, int defaultNamespace) { - // DWIM: Integers can be passed in here when page titles are used as array keys. - // XO.MW.SKIP:STRONGCAST - // if ($text != null && !is_string($text) && !is_int($text)) { - // throw new InvalidArgumentException('$text must be a String.'); - // } - if (text == null) { - return null; - } - - try { - return XomwTitle.newFromTextThrow(env, text, defaultNamespace); - } catch (XomwMalformedTitleException ex) { - Err_.Noop(ex); - return null; - } - } - - /** - * Like Title::newFromText(), but throws XomwMalformedTitleException when the title is invalid, - * rather than returning null. - * - * The exception subclasses encode detailed information about why the title is invalid. - * - * @see Title::newFromText - * - * @since 1.25 - * @param String $text Title text to check - * @param int $defaultNamespace - * @throws XomwMalformedTitleException If the title is invalid - * @return Title - */ - private static XomwTitle newFromTextThrow(XomwEnv env, byte[] text, int defaultNamespace) { - // if (is_object($text)) { - // throw new MWException('$text must be a String, given an Object'); - // } - - // XO.MW.SKIP:CACHE - // $titleCache = self::getTitleCache(); - - // Wiki pages often contain multiple links to the same page. - // Title normalization and parsing can become expensive on pages with many - // links, so we can save a little time by caching them. - // In theory these are value objects and won't get changed... - // if ($defaultNamespace == NS_MAIN) { - // t = $titleCache.get($text); - // if (t) { - // return t; - // } - // } - - // Convert things like é ā or 〗 into normalized (bug 14952) text -// $filteredText = Sanitizer::decodeCharReferencesAndNormalize($text); - byte[] filteredText = text; - - XomwTitle t = new XomwTitle(env); - t.mDbkeyform = XophpString_.strtr(filteredText, Byte_ascii.Space, Byte_ascii.Underline); - t.mDefaultNamespace = defaultNamespace; - - t.secureAndSplit(env); - // XO.MW.SKIP:CACHE - // if ($defaultNamespace == NS_MAIN) { - // $titleCache.set($text, t); - // } - return t; - } - -// /** -// * THIS IS NOT THE FUNCTION YOU WANT. Use Title::newFromText(). -// * -// * Example of wrong and broken code: -// * $title = Title::newFromURL($wgRequest.getVal('title')); -// * -// * Example of right code: -// * $title = Title::newFromText($wgRequest.getVal('title')); -// * -// * Create a new Title from URL-encoded text. Ensures that -// * the given title's length does not exceed the maximum. -// * -// * @param String $url The title, as might be taken from a URL -// * @return Title|null The new Object, or null on an error -// */ -// public static function newFromURL($url) { -// t = new Title(); -// -// // For compatibility with old buggy URLs. "+" is usually not valid in titles, -// // but some URLs used it as a space replacement and they still come -// // from some external search tools. -// if (strpos(self::legalChars(), '+') == false) { -// $url = strtr($url, '+', ' '); -// } -// -// t.mDbkeyform = strtr($url, ' ', '_'); -// -// try { -// t.secureAndSplit(); -// return t; -// } catch (XomwMalformedTitleException $ex) { -// return null; -// } -// } -// -// /** -// * @return HashBagOStuff -// */ -// private static function getTitleCache() { -// if (self::$titleCache == null) { -// self::$titleCache = new HashBagOStuff([ 'maxKeys' => self::CACHE_MAX ]); -// } -// return self::$titleCache; -// } -// -// /** -// * Returns a list of fields that are to be selected for initializing Title -// * objects or LinkCache entries. Uses $wgContentHandlerUseDB to determine -// * whether to include page_content_model. -// * -// * @return array -// */ -// protected static function getSelectFields() { -// global $wgContentHandlerUseDB, $wgPageLanguageUseDB; -// -// $fields = [ -// 'page_namespace', 'page_title', 'page_id', -// 'page_len', 'page_is_redirect', 'page_latest', -// ]; -// -// if ($wgContentHandlerUseDB) { -// $fields[] = 'page_content_model'; -// } -// -// if ($wgPageLanguageUseDB) { -// $fields[] = 'page_lang'; -// } -// -// return $fields; -// } -// -// /** -// * Create a new Title from an article ID -// * -// * @param int $id The page_id corresponding to the Title to create -// * @param int $flags Use Title::GAID_FOR_UPDATE to use master -// * @return Title|null The new Object, or null on an error -// */ -// public static function newFromID($id, $flags = 0) { -// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); -// $row = $db.selectRow( -// 'page', -// self::getSelectFields(), -// [ 'page_id' => $id ], -// __METHOD__ -// ); -// if ($row != false) { -// $title = Title::newFromRow($row); -// } else { -// $title = null; -// } -// return $title; -// } -// -// /** -// * Make an array of titles from an array of IDs -// * -// * @param int[] $ids Array of IDs -// * @return Title[] Array of Titles -// */ -// public static function newFromIDs($ids) { -// if (!count($ids)) { -// return []; -// } -// $dbr = wfGetDB(DB_REPLICA); -// -// $res = $dbr.select( -// 'page', -// self::getSelectFields(), -// [ 'page_id' => $ids ], -// __METHOD__ -// ); -// -// $titles = []; -// foreach ($res as $row) { -// $titles[] = Title::newFromRow($row); -// } -// return $titles; -// } -// -// /** -// * Make a Title Object from a DB row -// * -// * @param stdClass $row Object database row (needs at least page_title,page_namespace) -// * @return Title Corresponding Title -// */ -// public static function newFromRow($row) { -// t = self::makeTitle($row.page_namespace, $row.page_title); -// t.loadFromRow($row); -// return t; -// } -// -// /** -// * Load Title Object fields from a DB row. -// * If false is given, the title will be treated as non-existing. -// * -// * @param stdClass|boolean $row Database row -// */ -// public function loadFromRow($row) { -// if ($row) { // page found -// if (isset($row.page_id)) { -// this.mArticleID = (int)$row.page_id; -// } -// if (isset($row.page_len)) { -// this.mLength = (int)$row.page_len; -// } -// if (isset($row.page_is_redirect)) { -// this.mRedirect = (boolean)$row.page_is_redirect; -// } -// if (isset($row.page_latest)) { -// this.mLatestID = (int)$row.page_latest; -// } -// if (!this.mForcedContentModel && isset($row.page_content_model)) { -// this.mContentModel = strval($row.page_content_model); -// } elseif (!this.mForcedContentModel) { -// this.mContentModel = false; # initialized lazily in getContentModel() -// } -// if (isset($row.page_lang)) { -// this.mDbPageLanguage = (String)$row.page_lang; -// } -// if (isset($row.page_restrictions)) { -// this.mOldRestrictions = $row.page_restrictions; -// } -// } else { // page not found -// this.mArticleID = 0; -// this.mLength = 0; -// this.mRedirect = false; -// this.mLatestID = 0; -// if (!this.mForcedContentModel) { -// this.mContentModel = false; # initialized lazily in getContentModel() -// } -// } -// } -// -// /** -// * Create a new Title from a namespace index and a DB key. -// * It's assumed that $ns and $title are *valid*, for instance when -// * they came directly from the database or a special page name. -// * For convenience, spaces are converted to underscores so that -// * eg user_text fields can be used directly. -// * -// * @param int $ns The namespace of the article -// * @param String $title The unprefixed database key form -// * @param String $fragment The link fragment (after the "#") -// * @param String $interwiki The interwiki prefix -// * @return Title The new Object -// */ -// public static function makeTitle($ns, $title, $fragment = Bry_.Empty, $interwiki = Bry_.Empty) { -// t = new Title(); -// t.mInterwiki = $interwiki; -// t.mFragment = $fragment; -// t.mNamespace = $ns = intval($ns); -// t.mDbkeyform = strtr($title, ' ', '_'); -// t.mArticleID = ($ns >= 0) ? -1 : 0; -// t.mUrlform = wfUrlencode(t.mDbkeyform); -// t.mTextform = strtr($title, '_', ' '); -// t.mContentModel = false; # initialized lazily in getContentModel() -// return t; -// } -// -// /** -// * Create a new Title from a namespace index and a DB key. -// * The parameters will be checked for validity, which is a bit slower -// * than makeTitle() but safer for user-provided data. -// * -// * @param int $ns The namespace of the article -// * @param String $title Database key form -// * @param String $fragment The link fragment (after the "#") -// * @param String $interwiki Interwiki prefix -// * @return Title|null The new Object, or null on an error -// */ -// public static function makeTitleSafe($ns, $title, $fragment = Bry_.Empty, $interwiki = Bry_.Empty) { -// if (!XomwNamespace::exists($ns)) { -// return null; -// } -// -// t = new Title(); -// t.mDbkeyform = Title::makeName($ns, $title, $fragment, $interwiki, true); -// -// try { -// t.secureAndSplit(); -// return t; -// } catch (XomwMalformedTitleException $ex) { -// return null; -// } -// } - - /** - * Create a new Title for the Main Page - * - * @return Title The new Object - */ - public static XomwTitle newMainPage(XomwEnv env) { - XomwTitle title = XomwTitle.newFromText(env, XomwGlobalFunctions.wfMessage(env, "mainpage").inContentLanguage().text()); - // Don't give fatal errors if the message is broken - if (title == null) { - title = XomwTitle.newFromText(env, Bry_.new_a7("Main Page")); - } - return title; - } - -// /** -// * Get the prefixed DB key associated with an ID -// * -// * @param int $id The page_id of the article -// * @return Title|null An Object representing the article, or null if no such article was found -// */ -// public static function nameOf($id) { -// $dbr = wfGetDB(DB_REPLICA); -// -// $s = $dbr.selectRow( -// 'page', -// [ 'page_namespace', 'page_title' ], -// [ 'page_id' => $id ], -// __METHOD__ -// ); -// if ($s == false) { -// return null; -// } -// -// $n = self::makeName($s.page_namespace, $s.page_title); -// return $n; -// } -// -// /** -// * Get a regex character class describing the legal characters in a link -// * -// * @return String The list of characters, not delimited -// */ -// public static function legalChars() { -// global $wgLegalTitleChars; -// return $wgLegalTitleChars; -// } -// -// /** -// * Returns a simple regex that will match on characters and sequences invalid in titles. -// * Note that this doesn't pick up many things that could be wrong with titles, but that -// * replacing this regex with something valid will make many titles valid. -// * -// * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead -// * -// * @return String Regex String -// */ -// static function getTitleInvalidRegex() { -// wfDeprecated(__METHOD__, '1.25'); -// return MediaWikiTitleCodec::getTitleInvalidRegex(); -// } -// -// /** -// * Utility method for converting a character sequence from bytes to Unicode. -// * -// * Primary usecase being converting $wgLegalTitleChars to a sequence usable in -// * javascript, as PHP uses UTF-8 bytes where javascript uses Unicode code units. -// * -// * @param String $byteClass -// * @return String -// */ -// public static function convertByteClassToUnicodeClass($byteClass) { -// $length = strlen($byteClass); -// // Input token queue -// $x0 = $x1 = $x2 = Bry_.Empty; -// // Decoded queue -// $d0 = $d1 = $d2 = Bry_.Empty; -// // Decoded integer codepoints -// $ord0 = $ord1 = $ord2 = 0; -// // Re-encoded queue -// $r0 = $r1 = $r2 = Bry_.Empty; -// // Output -// $out = Bry_.Empty; -// // Flags -// $allowUnicode = false; -// for ($pos = 0; $pos < $length; $pos++) { -// // Shift the queues down -// $x2 = $x1; -// $x1 = $x0; -// $d2 = $d1; -// $d1 = $d0; -// $ord2 = $ord1; -// $ord1 = $ord0; -// $r2 = $r1; -// $r1 = $r0; -// // Load the current input token and decoded values -// $inChar = $byteClass[$pos]; -// if ($inChar == '\\') { -// if (preg_match('/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1)) { -// $x0 = $inChar . $m[0]; -// $d0 = chr(hexdec($m[1])); -// $pos += strlen($m[0]); -// } elseif (preg_match('/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1)) { -// $x0 = $inChar . $m[0]; -// $d0 = chr(octdec($m[0])); -// $pos += strlen($m[0]); -// } elseif ($pos + 1 >= $length) { -// $x0 = $d0 = '\\'; -// } else { -// $d0 = $byteClass[$pos + 1]; -// $x0 = $inChar . $d0; -// $pos += 1; -// } -// } else { -// $x0 = $d0 = $inChar; -// } -// $ord0 = ord($d0); -// // Load the current re-encoded value -// if ($ord0 < 32 || $ord0 == 0x7f) { -// $r0 = sprintf('\x%02x', $ord0); -// } elseif ($ord0 >= 0x80) { -// // Allow unicode if a single high-bit character appears -// $r0 = sprintf('\x%02x', $ord0); -// $allowUnicode = true; -// } elseif (strpos('-\\[]^', $d0) != false) { -// $r0 = '\\' . $d0; -// } else { -// $r0 = $d0; -// } -// // Do the output -// if ($x0 != Bry_.Empty && $x1 == '-' && $x2 != Bry_.Empty) { -// // Range -// if ($ord2 > $ord0) { -// // Empty range -// } elseif ($ord0 >= 0x80) { -// // Unicode range -// $allowUnicode = true; -// if ($ord2 < 0x80) { -// // Keep the non-unicode section of the range -// $out .= "$r2-\\x7F"; -// } -// } else { -// // Normal range -// $out .= "$r2-$r0"; -// } -// // Reset state to the initial value -// $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = Bry_.Empty; -// } elseif ($ord2 < 0x80) { -// // ASCII character -// $out .= $r2; -// } -// } -// if ($ord1 < 0x80) { -// $out .= $r1; -// } -// if ($ord0 < 0x80) { -// $out .= $r0; -// } -// if ($allowUnicode) { -// $out .= '\u0080-\uFFFF'; -// } -// return $out; -// } -// -// /** -// * Make a prefixed DB key from a DB key and a namespace index -// * -// * @param int $ns Numerical representation of the namespace -// * @param String $title The DB key form the title -// * @param String $fragment The link fragment (after the "#") -// * @param String $interwiki The interwiki prefix -// * @param boolean $canonicalNamespace If true, use the canonical name for -// * $ns instead of the localized version. -// * @return String The prefixed form of the title -// */ -// public static function makeName($ns, $title, $fragment = Bry_.Empty, $interwiki = Bry_.Empty, -// $canonicalNamespace = false -// ) { -// global $wgContLang; -// -// if ($canonicalNamespace) { -// $namespace = XomwNamespace::getCanonicalName($ns); -// } else { -// $namespace = $wgContLang.getNsText($ns); -// } -// $name = $namespace == Bry_.Empty ? $title : "$namespace:$title"; -// if (strval($interwiki) != Bry_.Empty) { -// $name = "$interwiki:$name"; -// } -// if (strval($fragment) != Bry_.Empty) { -// $name .= '#' . $fragment; -// } -// return $name; -// } -// -// /** -// * Escape a text fragment, say from a link, for a URL -// * -// * @param String $fragment Containing a URL or link fragment (after the "#") -// * @return String Escaped String -// */ -// static function escapeFragmentForURL($fragment) { -// // Note that we don't urlencode the fragment. urlencoded Unicode -// // fragments appear not to work in IE (at least up to 7) or in at least -// // one version of Opera 9.x. The W3C validator, for one, doesn't seem -// // to care if they aren't encoded. -// return Sanitizer::escapeId($fragment, 'noninitial'); -// } -// -// /** -// * Callback for usort() to do title sorts by (namespace, title) -// * -// * @param LinkTarget $a -// * @param LinkTarget $b -// * -// * @return int Result of String comparison, or namespace comparison -// */ -// public static function compare(LinkTarget $a, LinkTarget $b) { -// if ($a.getNamespace() == $b.getNamespace()) { -// return strcmp($a.getText(), $b.getText()); -// } else { -// return $a.getNamespace() - $b.getNamespace(); -// } -// } -// -// /** -// * Determine whether the Object refers to a page within -// * this project (either this wiki or a wiki with a local -// * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local) -// * -// * @return boolean True if this is an in-project interwiki link or a wikilink, false otherwise -// */ -// public function isLocal() { -// if (this.isExternal()) { -// $iw = self::getInterwikiLookup().fetch(this.mInterwiki); -// if ($iw) { -// return $iw.isLocal(); -// } -// } -// return true; -// } - - /** - * Is this Title interwiki? - * - * @return boolean - */ - public boolean isExternal() { - return this.mInterwiki != Bry_.Empty; - } - - /** - * Get the interwiki prefix - * - * Use Title::isExternal to check if a interwiki is set - * - * @return String Interwiki prefix - */ - public byte[] getInterwiki() { - return this.mInterwiki; - } - - /** - * Was this a local interwiki link? - * - * @return boolean - */ - public boolean wasLocalInterwiki() { - return this.mLocalInterwiki; - } - -// /** -// * Determine whether the Object refers to a page within -// * this project and is transcludable. -// * -// * @return boolean True if this is transcludable -// */ -// public function isTrans() { -// if (!this.isExternal()) { -// return false; -// } -// -// return self::getInterwikiLookup().fetch(this.mInterwiki).isTranscludable(); -// } -// -// /** -// * Returns the DB name of the distant wiki which owns the Object. -// * -// * @return String|false The DB name -// */ -// public function getTransWikiID() { -// if (!this.isExternal()) { -// return false; -// } -// -// return self::getInterwikiLookup().fetch(this.mInterwiki).getWikiID(); -// } -// -// /** -// * Get a TitleValue Object representing this Title. -// * -// * @note Not all valid Titles have a corresponding valid TitleValue -// * (e.g. TitleValues cannot represent page-local links that have a -// * fragment but no title text). -// * -// * @return TitleValue|null -// */ -// public function getTitleValue() { -// if (this.mTitleValue == null) { -// try { -// this.mTitleValue = new TitleValue( -// this.getNamespace(), -// this.getDBkey(), -// this.getFragment(), -// this.getInterwiki() -// ); -// } catch (InvalidArgumentException $ex) { -// wfDebug(__METHOD__ . ': Can\'t create a TitleValue for [[' . -// this.getPrefixedText() . ']]: ' . $ex.getMessage() . "\n"); -// } -// } -// -// return this.mTitleValue; -// } - - /** - * Get the text form (spaces not underscores) of the main part - * - * @return String Main part of the title - */ - public byte[] getText() { - return this.mTextform; - } - - /** - * Get the URL-encoded form of the main part - * - * @return String Main part of the title, URL-encoded - */ - public byte[] getPartialURL() { - return this.mUrlform; - } - - /** - * Get the main part with underscores - * - * @return String Main part of the title, with underscores - */ - public byte[] getDBkey() { - return this.mDbkeyform; - } - - /** - * Get the DB key with the initial letter case as specified by the user - * - * @return String DB key - */ - public byte[] getUserCaseDBKey() { - if (this.mUserCaseDBKey != null) { - return this.mUserCaseDBKey; - } else { - // If created via makeTitle(), this.mUserCaseDBKey is not set. - return this.mDbkeyform; - } - } - - /** - * Get the namespace index, i.e. one of the NS_xxxx constants. - * - * @return int Namespace index - */ - public int getNamespace() { - return this.mNamespace; - } - -// /** -// * Get the page's content model id, see the CONTENT_MODEL_XXX constants. -// * -// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update -// * @return String Content model id -// */ -// public function getContentModel($flags = 0) { -// if (!this.mForcedContentModel -// && (!this.mContentModel || $flags == Title::GAID_FOR_UPDATE) -// && this.getArticleID($flags) -// ) { -// $linkCache = LinkCache::singleton(); -// $linkCache.addLinkObj(this); # in case we already had an article ID -// this.mContentModel = $linkCache.getGoodLinkFieldObj(this, 'model'); -// } -// -// if (!this.mContentModel) { -// this.mContentModel = ContentHandler::getDefaultModelFor(this); -// } -// -// return this.mContentModel; -// } -// -// /** -// * Convenience method for checking a title's content model name -// * -// * @param String $id The content model ID (use the CONTENT_MODEL_XXX constants). -// * @return boolean True if this.getContentModel() == $id -// */ -// public function hasContentModel($id) { -// return this.getContentModel() == $id; -// } -// -// /** -// * Set a proposed content model for the page for permissions -// * checking. This does not actually change the content model -// * of a title! -// * -// * Additionally, you should make sure you've checked -// * ContentHandler::canBeUsedOn() first. -// * -// * @since 1.28 -// * @param String $model CONTENT_MODEL_XXX constant -// */ -// public function setContentModel($model) { -// this.mContentModel = $model; -// this.mForcedContentModel = true; -// } - - /** - * Get the namespace text - * - * @return String|false Namespace text - */ - public byte[] getNsText() { - if (this.isExternal()) { -// // This probably shouldn't even happen, -// // but for interwiki transclusion it sometimes does. -// // Use the canonical namespaces if possible to try to -// // resolve a foreign namespace. -// if (XomwNamespace::exists(this.mNamespace)) { -// return XomwNamespace::getCanonicalName(this.mNamespace); -// } - } - -// try { - XomwTitleFormatter formatter = env.MediaWikiServices().getTitleFormatter(); - return formatter.getNamespaceName(this.mNamespace, this.mDbkeyform); -// } catch (InvalidArgumentException $ex) { -// wfDebug(__METHOD__ . ': ' . $ex.getMessage() . "\n"); -// return false; -// } - } - -// /** -// * Get the namespace text of the subject (rather than talk) page -// * -// * @return String Namespace text -// */ -// public function getSubjectNsText() { -// global $wgContLang; -// return $wgContLang.getNsText(XomwNamespace::getSubject(this.mNamespace)); -// } -// -// /** -// * Get the namespace text of the talk page -// * -// * @return String Namespace text -// */ -// public function getTalkNsText() { -// global $wgContLang; -// return $wgContLang.getNsText(XomwNamespace::getTalk(this.mNamespace)); -// } -// -// /** -// * Could this title have a corresponding talk page? -// * -// * @return boolean -// */ -// public function canTalk() { -// return XomwNamespace::canTalk(this.mNamespace); -// } -// -// /** -// * Is this in a namespace that allows actual pages? -// * -// * @return boolean -// */ -// public function canExist() { -// return this.mNamespace >= NS_MAIN; -// } -// -// /** -// * Can this title be added to a user's watchlist? -// * -// * @return boolean -// */ -// public function isWatchable() { -// return !this.isExternal() && XomwNamespace::isWatchable(this.getNamespace()); -// } -// -// /** -// * Returns true if this is a special page. -// * -// * @return boolean -// */ -// public function isSpecialPage() { -// return this.getNamespace() == NS_SPECIAL; -// } -// -// /** -// * Returns true if this title resolves to the named special page -// * -// * @param String $name The special page name -// * @return boolean -// */ -// public function isSpecial($name) { -// if (this.isSpecialPage()) { -// list($thisName, /* $subpage */) = SpecialPageFactory::resolveAlias(this.getDBkey()); -// if ($name == $thisName) { -// return true; -// } -// } -// return false; -// } -// -// /** -// * If the Title refers to a special page alias which is not the local default, resolve -// * the alias, and localise the name as necessary. Otherwise, return this -// * -// * @return Title -// */ -// public function fixSpecialName() { -// if (this.isSpecialPage()) { -// list($canonicalName, $par) = SpecialPageFactory::resolveAlias(this.mDbkeyform); -// if ($canonicalName) { -// $localName = SpecialPageFactory::getLocalNameFor($canonicalName, $par); -// if ($localName != this.mDbkeyform) { -// return Title::makeTitle(NS_SPECIAL, $localName); -// } -// } -// } -// return this; -// } -// -// /** -// * Returns true if the title is inside the specified namespace. -// * -// * Please make use of this instead of comparing to getNamespace() -// * This function is much more resistant to changes we may make -// * to namespaces than code that makes direct comparisons. -// * @param int $ns The namespace -// * @return boolean -// * @since 1.19 -// */ -// public function inNamespace($ns) { -// return XomwNamespace::equals(this.getNamespace(), $ns); -// } -// -// /** -// * Returns true if the title is inside one of the specified namespaces. -// * -// * @param int|int[] $namespaces,... The namespaces to check for -// * @return boolean -// * @since 1.19 -// */ -// public function inNamespaces(/* ... */) { -// $namespaces = func_get_args(); -// if (count($namespaces) > 0 && is_array($namespaces[0])) { -// $namespaces = $namespaces[0]; -// } -// -// foreach ($namespaces as $ns) { -// if (this.inNamespace($ns)) { -// return true; -// } -// } -// -// return false; -// } -// -// /** -// * Returns true if the title has the same subject namespace as the -// * namespace specified. -// * For example this method will take NS_USER and return true if namespace -// * is either NS_USER or NS_USER_TALK since both of them have NS_USER -// * as their subject namespace. -// * -// * This is MUCH simpler than individually testing for equivalence -// * against both NS_USER and NS_USER_TALK, and is also forward compatible. -// * @since 1.19 -// * @param int $ns -// * @return boolean -// */ -// public function hasSubjectNamespace($ns) { -// return XomwNamespace::subjectEquals(this.getNamespace(), $ns); -// } -// -// /** -// * Is this Title in a namespace which contains content? -// * In other words, is this a content page, for the purposes of calculating -// * statistics, etc? -// * -// * @return boolean -// */ -// public function isContentPage() { -// return XomwNamespace::isContent(this.getNamespace()); -// } -// -// /** -// * Would anybody with sufficient privileges be able to move this page? -// * Some pages just aren't movable. -// * -// * @return boolean -// */ -// public function isMovable() { -// if (!XomwNamespace::isMovable(this.getNamespace()) || this.isExternal()) { -// // Interwiki title or immovable namespace. Hooks don't get to override here -// return false; -// } -// -// $result = true; -// Hooks::run('TitleIsMovable', [ this, &$result ]); -// return $result; -// } -// -// /** -// * Is this the mainpage? -// * @note Title::newFromText seems to be sufficiently optimized by the title -// * cache that we don't need to over-optimize by doing direct comparisons and -// * accidentally creating new bugs where $title.equals(Title::newFromText()) -// * ends up reporting something differently than $title.isMainPage(); -// * -// * @since 1.18 -// * @return boolean -// */ -// public function isMainPage() { -// return this.equals(Title::newMainPage()); -// } -// -// /** -// * Is this a subpage? -// * -// * @return boolean -// */ -// public function isSubpage() { -// return XomwNamespace::hasSubpages(this.mNamespace) -// ? strpos(this.getText(), '/') != false -// : false; -// } -// -// /** -// * Is this a conversion table for the LanguageConverter? -// * -// * @return boolean -// */ -// public function isConversionTable() { -// // @todo ConversionTable should become a separate content model. -// -// return this.getNamespace() == NS_MEDIAWIKI && -// strpos(this.getText(), 'Conversiontable/') == 0; -// } -// -// /** -// * Does that page contain wikitext, or it is JS, CSS or whatever? -// * -// * @return boolean -// */ -// public function isWikitextPage() { -// return this.hasContentModel(CONTENT_MODEL_WIKITEXT); -// } -// -// /** -// * Could this page contain custom CSS or JavaScript for the global UI. -// * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS -// * or CONTENT_MODEL_JAVASCRIPT. -// * -// * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() -// * for that! -// * -// * Note that this method should not return true for pages that contain and -// * show "inactive" CSS or JS. -// * -// * @return boolean -// * @todo FIXME: Rename to isSiteConfigPage() and remove deprecated hook -// */ -// public function isCssOrJsPage() { -// $isCssOrJsPage = NS_MEDIAWIKI == this.mNamespace -// && (this.hasContentModel(CONTENT_MODEL_CSS) -// || this.hasContentModel(CONTENT_MODEL_JAVASCRIPT)); -// -// return $isCssOrJsPage; -// } -// -// /** -// * Is this a .css or .js subpage of a user page? -// * @return boolean -// * @todo FIXME: Rename to isUserConfigPage() -// */ -// public function isCssJsSubpage() { -// return (NS_USER == this.mNamespace && this.isSubpage() -// && (this.hasContentModel(CONTENT_MODEL_CSS) -// || this.hasContentModel(CONTENT_MODEL_JAVASCRIPT))); -// } -// -// /** -// * Trim down a .css or .js subpage title to get the corresponding skin name -// * -// * @return String Containing skin name from .css or .js subpage title -// */ -// public function getSkinFromCssJsSubpage() { -// $subpage = explode('/', this.mTextform); -// $subpage = $subpage[count($subpage) - 1]; -// $lastdot = strrpos($subpage, '.'); -// if ($lastdot == false) { -// return $subpage; # Never happens: only called for names ending in '.css' or '.js' -// } -// return substr($subpage, 0, $lastdot); -// } -// -// /** -// * Is this a .css subpage of a user page? -// * -// * @return boolean -// */ -// public function isCssSubpage() { -// return (NS_USER == this.mNamespace && this.isSubpage() -// && this.hasContentModel(CONTENT_MODEL_CSS)); -// } -// -// /** -// * Is this a .js subpage of a user page? -// * -// * @return boolean -// */ -// public function isJsSubpage() { -// return (NS_USER == this.mNamespace && this.isSubpage() -// && this.hasContentModel(CONTENT_MODEL_JAVASCRIPT)); -// } -// -// /** -// * Is this a talk page of some sort? -// * -// * @return boolean -// */ -// public function isTalkPage() { -// return XomwNamespace::isTalk(this.getNamespace()); -// } -// -// /** -// * Get a Title Object associated with the talk page of this article -// * -// * @return Title The Object for the talk page -// */ -// public function getTalkPage() { -// return Title::makeTitle(XomwNamespace::getTalk(this.getNamespace()), this.getDBkey()); -// } -// -// /** -// * Get a title Object associated with the subject page of this -// * talk page -// * -// * @return Title The Object for the subject page -// */ -// public function getSubjectPage() { -// // Is this the same title? -// $subjectNS = XomwNamespace::getSubject(this.getNamespace()); -// if (this.getNamespace() == $subjectNS) { -// return this; -// } -// return Title::makeTitle($subjectNS, this.getDBkey()); -// } -// -// /** -// * Get the other title for this page, if this is a subject page -// * get the talk page, if it is a subject page get the talk page -// * -// * @since 1.25 -// * @throws MWException -// * @return Title -// */ -// public function getOtherPage() { -// if (this.isSpecialPage()) { -// throw new MWException('Special pages cannot have other pages'); -// } -// if (this.isTalkPage()) { -// return this.getSubjectPage(); -// } else { -// return this.getTalkPage(); -// } -// } - - /** - * Get the default namespace index, for when there is no namespace - * - * @return int Default namespace index - */ - public int getDefaultNamespace() { - return this.mDefaultNamespace; - } - - /** - * Get the Title fragment (i.e.\ the bit after the #) in text form - * - * Use Title::hasFragment to check for a fragment - * - * @return String Title fragment - */ - public byte[] getFragment() { - return this.mFragment; - } - - /** - * Check if a Title fragment is set - * - * @return boolean - * @since 1.23 - */ - public boolean hasFragment() { - return this.mFragment != Bry_.Empty; - } - -// /** -// * Get the fragment in URL form, including the "#" character if there is one -// * @return String Fragment in URL form -// */ -// public function getFragmentForURL() { -// if (!this.hasFragment()) { -// return Bry_.Empty; -// } else { -// return '#' . Title::escapeFragmentForURL(this.getFragment()); -// } -// } -// -// /** -// * Set the fragment for this title. Removes the first character from the -// * specified fragment before setting, so it assumes you're passing it with -// * an initial "#". -// * -// * Deprecated for public use, use Title::makeTitle() with fragment parameter, -// * or Title::createFragmentTarget(). -// * Still in active use privately. -// * -// * @private -// * @param String $fragment Text -// */ -// public function setFragment($fragment) { -// this.mFragment = strtr(substr($fragment, 1), '_', ' '); -// } - - /** - * Creates a new Title for a different fragment of the same page. - * - * @since 1.27 - * @param String $fragment - * @return Title - */ - public XomwTitle createFragmentTarget(byte[] fragment) { - return null; -// return self::makeTitle( -// this.getNamespace(), -// this.getText(), -// $fragment, -// this.getInterwiki() -// ); - } - - /** - * Prefix some arbitrary text with the namespace or interwiki prefix - * of this Object - * - * @param String $name The text - * @return String The prefixed text - */ - private byte[] prefix(byte[] name) { - byte[] p = Bry_.Empty; -// if (this.isExternal()) { -// p = this.mInterwiki . ':'; -// } - - if (0 != this.mNamespace) { - p = Bry_.Add(p, this.getNsText(), Byte_ascii.Colon_bry); - } - return Bry_.Add(p, name); - } - - /** - * Get the prefixed database key form - * - * @return String The prefixed title, with underscores and - * any interwiki and namespace prefixes - */ - public byte[] getPrefixedDBkey() { - byte[] s = this.prefix(this.mDbkeyform); - s = XophpString_.strtr(s, Byte_ascii.Space, Byte_ascii.Underline); - return s; - } - public String getPrefixedDBkeyStr() {return String_.new_u8(getPrefixedDBkey());} - - /** - * Get the prefixed title with spaces. - * This is the form usually used for display - * - * @return String The prefixed title, with spaces - */ - public byte[] getPrefixedText() { - if (this.mPrefixedText == null) { - byte[] s = this.prefix(this.mTextform); - s = XophpString_.strtr(s, Byte_ascii.Underline, Byte_ascii.Space); - this.mPrefixedText = s; - } - return this.mPrefixedText; - } - public String getPrefixedTextStr() {return String_.new_u8(getPrefixedText());} - -// /** -// * Return a String representation of this title -// * -// * @return String Representation of this title -// */ -// public function __toString() { -// return this.getPrefixedText(); -// } -// -// /** -// * Get the prefixed title with spaces, plus any fragment -// * (part beginning with '#') -// * -// * @return String The prefixed title, with spaces and the fragment, including '#' -// */ -// public function getFullText() { -// $text = this.getPrefixedText(); -// if (this.hasFragment()) { -// $text .= '#' . this.getFragment(); -// } -// return $text; -// } -// -// /** -// * Get the root page name text without a namespace, i.e. the leftmost part before any slashes -// * -// * @par Example: -// * @code -// * Title::newFromText('User:Foo/Bar/Baz').getRootText(); -// * # returns: 'Foo' -// * @endcode -// * -// * @return String Root name -// * @since 1.20 -// */ -// public function getRootText() { -// if (!XomwNamespace::hasSubpages(this.mNamespace)) { -// return this.getText(); -// } -// -// return strtok(this.getText(), '/'); -// } -// -// /** -// * Get the root page name title, i.e. the leftmost part before any slashes -// * -// * @par Example: -// * @code -// * Title::newFromText('User:Foo/Bar/Baz').getRootTitle(); -// * # returns: Title{User:Foo} -// * @endcode -// * -// * @return Title Root title -// * @since 1.20 -// */ -// public function getRootTitle() { -// return Title::makeTitle(this.getNamespace(), this.getRootText()); -// } -// -// /** -// * Get the super page name without a namespace, i.e. the part before the subpage name -// * -// * @par Example: -// * @code -// * Title::newFromText('User:Foo/Bar/Baz').getBaseText(); -// * # returns: 'Foo/Bar' -// * @endcode -// * -// * @return String Base name -// */ -// public function getBaseText() { -// if (!XomwNamespace::hasSubpages(this.mNamespace)) { -// return this.getText(); -// } -// -// $parts = explode('/', this.getText()); -// // Don't discard the real title if there's no subpage involved -// if (count($parts) > 1) { -// unset($parts[count($parts) - 1]); -// } -// return implode('/', $parts); -// } -// -// /** -// * Get the super page name title, i.e. the part before the subpage name -// * -// * @par Example: -// * @code -// * Title::newFromText('User:Foo/Bar/Baz').getBaseTitle(); -// * # returns: Title{User:Foo/Bar} -// * @endcode -// * -// * @return Title Base title -// * @since 1.20 -// */ -// public function getBaseTitle() { -// return Title::makeTitle(this.getNamespace(), this.getBaseText()); -// } -// -// /** -// * Get the lowest-level subpage name, i.e. the rightmost part after any slashes -// * -// * @par Example: -// * @code -// * Title::newFromText('User:Foo/Bar/Baz').getSubpageText(); -// * # returns: "Baz" -// * @endcode -// * -// * @return String Subpage name -// */ -// public function getSubpageText() { -// if (!XomwNamespace::hasSubpages(this.mNamespace)) { -// return this.mTextform; -// } -// $parts = explode('/', this.mTextform); -// return $parts[count($parts) - 1]; -// } -// -// /** -// * Get the title for a subpage of the current page -// * -// * @par Example: -// * @code -// * Title::newFromText('User:Foo/Bar/Baz').getSubpage("Asdf"); -// * # returns: Title{User:Foo/Bar/Baz/Asdf} -// * @endcode -// * -// * @param String $text The subpage name to add to the title -// * @return Title Subpage title -// * @since 1.20 -// */ -// public function getSubpage($text) { -// return Title::makeTitleSafe(this.getNamespace(), this.getText() . '/' . $text); -// } -// -// /** -// * Get a URL-encoded form of the subpage text -// * -// * @return String URL-encoded subpage name -// */ -// public function getSubpageUrlForm() { -// $text = this.getSubpageText(); -// $text = wfUrlencode(strtr($text, ' ', '_')); -// return $text; -// } -// -// /** -// * Get a URL-encoded title (not an actual URL) including interwiki -// * -// * @return String The URL-encoded form -// */ -// public function getPrefixedURL() { -// $s = this.prefix(this.mDbkeyform); -// $s = wfUrlencode(strtr($s, ' ', '_')); -// return $s; -// } -// -// /** -// * Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args -// * get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional -// * second argument named variant. This was deprecated in favor -// * of passing an array of option with a "variant" key -// * Once $query2 is removed for good, this helper can be dropped -// * and the wfArrayToCgi moved to getLocalURL(); -// * -// * @since 1.19 (r105919) -// * @param array|String $query -// * @param String|String[]|boolean $query2 -// * @return String -// */ -// private static function fixUrlQueryArgs($query, $query2 = false) { -// if ($query2 != false) { -// wfDeprecated("Title::get{Canonical,Full,Link,Local,Internal}URL " . -// "method called with a second parameter is deprecated. Add your " . -// "parameter to an array passed as the first parameter.", "1.19"); -// } -// if (is_array($query)) { -// $query = wfArrayToCgi($query); -// } -// if ($query2) { -// if (is_string($query2)) { -// // $query2 is a String, we will consider this to be -// // a deprecated $variant argument and add it to the query -// $query2 = wfArrayToCgi([ 'variant' => $query2 ]); -// } else { -// $query2 = wfArrayToCgi($query2); -// } -// // If we have $query content add a & to it first -// if ($query) { -// $query .= '&'; -// } -// // Now append the queries together -// $query .= $query2; -// } -// return $query; -// } -// -// /** -// * Get a real URL referring to this title, with interwiki link and -// * fragment -// * -// * @see self::getLocalURL for the arguments. -// * @see wfExpandUrl -// * @param String|String[] $query -// * @param String|String[]|boolean $query2 -// * @param String $proto Protocol type to use in URL -// * @return String The URL -// */ -// public function getFullURL($query = Bry_.Empty, $query2 = false, $proto = PROTO_RELATIVE) { -// $query = self::fixUrlQueryArgs($query, $query2); -// -// // Hand off all the decisions on urls to getLocalURL -// $url = this.getLocalURL($query); -// -// // Expand the url to make it a full url. Note that getLocalURL has the -// // potential to output full urls for a variety of reasons, so we use -// // wfExpandUrl instead of simply prepending $wgServer -// $url = wfExpandUrl($url, $proto); -// -// // Finally, add the fragment. -// $url .= this.getFragmentForURL(); -// // Avoid PHP 7.1 warning from passing this by reference -// $titleRef = this; -// Hooks::run('GetFullURL', [ &$titleRef, &$url, $query ]); -// return $url; -// } - - /** - * Get a URL with no fragment or server name (relative URL) from a Title Object. - * If this page is generated with action=render, however, - * $wgServer is prepended to make an absolute URL. - * - * @see self::getFullURL to always get an absolute URL. - * @see self::getLinkURL to always get a URL that's the simplest URL that will be - * valid to link, locally, to the current Title. - * @see self::newFromText to produce a Title Object. - * - * @param String|String[] $query An optional query String, - * not used for interwiki links. Can be specified as an associative array as well, - * e.g., array('action' => 'edit') (keys and values will be URL-escaped). - * Some query patterns will trigger various shorturl path replacements. - * @param String|String[]|boolean $query2 An optional secondary query array. This one MUST - * be an array. If a String is passed it will be interpreted as a deprecated - * variant argument and urlencoded into a variant= argument. - * This second query argument will be added to the $query - * The second parameter is deprecated since 1.19. Pass it as a key,value - * pair in the first parameter array instead. - * - * @return String String of the URL. - */ - public byte[] getLocalURL(byte[] query) {return getLocalURL(query, null);} - public byte[] getLocalURL(byte[] query, byte[] query2) { - byte[] url = null; -// global wgArticlePath, wgScript, wgServer, wgRequest; -// -// query = self::fixUrlQueryArgs(query, query2); -// -// interwiki = self::getInterwikiLookup().fetch(this.mInterwiki); -// if (interwiki) { -// namespace = this.getNsText(); -// if (namespace != Bry_.Empty) { -// // Can this actually happen? Interwikis shouldn't be parsed. -// // Yes! It can in interwiki transclusion. But... it probably shouldn't. -// namespace .= ':'; -// } -// url = interwiki.getURL(namespace . this.getDBkey()); -// url = wfAppendQuery(url, query); -// } else { -// byte[] dbkey = wfUrlencode(this.getPrefixedDBkey()); - byte[] dbkey = this.getPrefixedDBkey(); -// if (query == Bry_.Empty) { -// url = str_replace('$1', dbkey, wgArticlePath); - url = Bry_.Add(Bry__wgArticlePath__wiki, dbkey); - // XO.MW.HOOK:GetLocalURL::Article -// } else { -// global wgVariantArticlePath, wgActionPaths, wgContLang; -// url = false; -// matches = []; -// -// if (!empty(wgActionPaths) -// && preg_match('/^(.*&|)action=([^&]*)(&(.*)|)/', query, matches) -// ) { -// action = urldecode(matches[2]); -// if (isset(wgActionPaths[action])) { -// query = matches[1]; -// if (isset(matches[4])) { -// query .= matches[4]; -// } -// url = str_replace('1', dbkey, wgActionPaths[action]); -// if (query != Bry_.Empty) { -// url = wfAppendQuery(url, query); -// } -// } -// } -// -// if (url == false -// && wgVariantArticlePath -// && preg_match('/^variant=([^&]*)/', query, matches) -// && this.getPageLanguage().equals(wgContLang) -// && this.getPageLanguage().hasVariants() -// ) { -// variant = urldecode(matches[1]); -// if (this.getPageLanguage().hasVariant(variant)) { -// // Only do the variant replacement if the given variant is a valid -// // variant for the page's language. -// url = str_replace('2', urlencode(variant), wgVariantArticlePath); -// url = str_replace('1', dbkey, url); -// } -// } -// -// if (url == false) { -// if (query == '-') { -// query = Bry_.Empty; -// } -// url = "{wgScript}?title={dbkey}&{query}"; -// } -// } - // XO.MW.HOOK:GetLocalURL::Internal -// -// // @todo FIXME: This causes breakage in various places when we -// // actually expected a local URL and end up with dupe prefixes. -// if (wgRequest.getVal('action') == 'render') { -// url = wgServer . url; -// } -// } - // XO.MW.HOOK:GetLocalURL - return url; - } - - /** - * Get a URL that's the simplest URL that will be valid to link, locally, - * to the current Title. It includes the fragment, but does not include - * the server unless action=render is used (or the link is external). If - * there's a fragment but the prefixed text is empty, we just return a link - * to the fragment. - * - * The result obviously should not be URL-escaped, but does need to be - * HTML-escaped if it's being output in HTML. - * - * @param String|String[] $query - * @param boolean $query2 - * @param String|int|boolean $proto A PROTO_* constant on how the URL should be expanded, - * or false (default) for no expansion - * @see self::getLocalURL for the arguments. - * @return String The URL - */ - public byte[] getLinkURL(Object qry_mgr, boolean query2, boolean proto) { -// if (this.isExternal() || $proto != false) { -// $ret = this.getFullURL($query, $query2, $proto); -// } elseif (this.getPrefixedText() == Bry_.Empty && this.hasFragment()) { -// $ret = this.getFragmentForURL(); -// } else { -// $ret = this.getLocalURL($query, $query2) . this.getFragmentForURL(); -// } -// return $ret; - return Bry_.Add(gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, this.getPrefixedText()); - } - -// /** -// * Get the URL form for an @gplx.Internal protected link. -// * - Used in various CDN-related code, in case we have a different -// * @gplx.Internal protected hostname for the server from the exposed one. -// * -// * This uses $wgInternalServer to qualify the path, or $wgServer -// * if $wgInternalServer is not set. If the server variable used is -// * protocol-relative, the URL will be expanded to http:// -// * -// * @see self::getLocalURL for the arguments. -// * @return String The URL -// */ -// public function getInternalURL($query = Bry_.Empty, $query2 = false) { -// global $wgInternalServer, $wgServer; -// $query = self::fixUrlQueryArgs($query, $query2); -// $server = $wgInternalServer != false ? $wgInternalServer : $wgServer; -// $url = wfExpandUrl($server . this.getLocalURL($query), PROTO_HTTP); -// // Avoid PHP 7.1 warning from passing this by reference -// $titleRef = this; -// Hooks::run('GetInternalURL', [ &$titleRef, &$url, $query ]); -// return $url; -// } -// -// /** -// * Get the URL for a canonical link, for use in things like IRC and -// * e-mail notifications. Uses $wgCanonicalServer and the -// * GetCanonicalURL hook. -// * -// * NOTE: Unlike getInternalURL(), the canonical URL includes the fragment -// * -// * @see self::getLocalURL for the arguments. -// * @return String The URL -// * @since 1.18 -// */ -// public function getCanonicalURL($query = Bry_.Empty, $query2 = false) { -// $query = self::fixUrlQueryArgs($query, $query2); -// $url = wfExpandUrl(this.getLocalURL($query) . this.getFragmentForURL(), PROTO_CANONICAL); -// // Avoid PHP 7.1 warning from passing this by reference -// $titleRef = this; -// Hooks::run('GetCanonicalURL', [ &$titleRef, &$url, $query ]); -// return $url; -// } -// -// /** -// * Get the edit URL for this Title -// * -// * @return String The URL, or a null String if this is an interwiki link -// */ -// public function getEditURL() { -// if (this.isExternal()) { -// return Bry_.Empty; -// } -// $s = this.getLocalURL('action=edit'); -// -// return $s; -// } -// -// /** -// * Can $user perform $action on this page? -// * This skips potentially expensive cascading permission checks -// * as well as avoids expensive error formatting -// * -// * Suitable for use for nonessential UI controls in common cases, but -// * _not_ for functional access control. -// * -// * May provide false positives, but should never provide a false negative. -// * -// * @param String $action Action that permission needs to be checked for -// * @param User $user User to check (since 1.19); $wgUser will be used if not provided. -// * @return boolean -// */ -// public function quickUserCan($action, $user = null) { -// return this.userCan($action, $user, false); -// } -// -// /** -// * Can $user perform $action on this page? -// * -// * @param String $action Action that permission needs to be checked for -// * @param User $user User to check (since 1.19); $wgUser will be used if not -// * provided. -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @return boolean -// */ -// public function userCan($action, $user = null, $rigor = 'secure') { -// if (!$user instanceof User) { -// global $wgUser; -// $user = $wgUser; -// } -// -// return !count(this.getUserPermissionsErrorsInternal($action, $user, $rigor, true)); -// } -// -// /** -// * Can $user perform $action on this page? -// * -// * @todo FIXME: This *does not* check throttles (User::pingLimiter()). -// * -// * @param String $action Action that permission needs to be checked for -// * @param User $user User to check -// * @param String $rigor One of (quick,full,secure) -// * - quick : does cheap permission checks from replica DBs (usable for GUI creation) -// * - full : does cheap and expensive checks possibly from a replica DB -// * - secure : does cheap and expensive checks, using the master as needed -// * @param array $ignoreErrors Array of Strings Set this to a list of message keys -// * whose corresponding errors may be ignored. -// * @return array Array of arrays of the arguments to wfMessage to explain permissions problems. -// */ -// public function getUserPermissionsErrors( -// $action, $user, $rigor = 'secure', $ignoreErrors = [] -// ) { -// $errors = this.getUserPermissionsErrorsInternal($action, $user, $rigor); -// -// // Remove the errors being ignored. -// foreach ($errors as $index => $error) { -// $errKey = is_array($error) ? $error[0] : $error; -// -// if (in_array($errKey, $ignoreErrors)) { -// unset($errors[$index]); -// } -// if ($errKey instanceof MessageSpecifier && in_array($errKey.getKey(), $ignoreErrors)) { -// unset($errors[$index]); -// } -// } -// -// return $errors; -// } -// -// /** -// * Permissions checks that fail most often, and which are easiest to test. -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkQuickPermissions($action, $user, $errors, $rigor, $short) { -// if (!Hooks::run('TitleQuickPermissions', -// [ this, $user, $action, &$errors, ($rigor != 'quick'), $short ]) -// ) { -// return $errors; -// } -// -// if ($action == 'create') { -// if ( -// (this.isTalkPage() && !$user.isAllowed('createtalk')) || -// (!this.isTalkPage() && !$user.isAllowed('createpage')) -// ) { -// $errors[] = $user.isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ]; -// } -// } elseif ($action == 'move') { -// if (!$user.isAllowed('move-rootuserpages') -// && this.mNamespace == NS_USER && !this.isSubpage()) { -// // Show user page-specific message only if the user can move other pages -// $errors[] = [ 'cant-move-user-page' ]; -// } -// -// // Check if user is allowed to move files if it's a file -// if (this.mNamespace == NS_FILE && !$user.isAllowed('movefile')) { -// $errors[] = [ 'movenotallowedfile' ]; -// } -// -// // Check if user is allowed to move category pages if it's a category page -// if (this.mNamespace == NS_CATEGORY && !$user.isAllowed('move-categorypages')) { -// $errors[] = [ 'cant-move-category-page' ]; -// } -// -// if (!$user.isAllowed('move')) { -// // User can't move anything -// $userCanMove = User::groupHasPermission('user', 'move'); -// $autoconfirmedCanMove = User::groupHasPermission('autoconfirmed', 'move'); -// if ($user.isAnon() && ($userCanMove || $autoconfirmedCanMove)) { -// // custom message if logged-in users without any special rights can move -// $errors[] = [ 'movenologintext' ]; -// } else { -// $errors[] = [ 'movenotallowed' ]; -// } -// } -// } elseif ($action == 'move-target') { -// if (!$user.isAllowed('move')) { -// // User can't move anything -// $errors[] = [ 'movenotallowed' ]; -// } elseif (!$user.isAllowed('move-rootuserpages') -// && this.mNamespace == NS_USER && !this.isSubpage()) { -// // Show user page-specific message only if the user can move other pages -// $errors[] = [ 'cant-move-to-user-page' ]; -// } elseif (!$user.isAllowed('move-categorypages') -// && this.mNamespace == NS_CATEGORY) { -// // Show category page-specific message only if the user can move other pages -// $errors[] = [ 'cant-move-to-category-page' ]; -// } -// } elseif (!$user.isAllowed($action)) { -// $errors[] = this.missingPermissionError($action, $short); -// } -// -// return $errors; -// } -// -// /** -// * Add the resulting error code to the errors array -// * -// * @param array $errors List of current errors -// * @param array $result Result of errors -// * -// * @return array List of errors -// */ -// private function resultToError($errors, $result) { -// if (is_array($result) && count($result) && !is_array($result[0])) { -// // A single array representing an error -// $errors[] = $result; -// } elseif (is_array($result) && is_array($result[0])) { -// // A nested array representing multiple errors -// $errors = array_merge($errors, $result); -// } elseif ($result != Bry_.Empty && is_string($result)) { -// // A String representing a message-id -// $errors[] = [ $result ]; -// } elseif ($result instanceof MessageSpecifier) { -// // A message specifier representing an error -// $errors[] = [ $result ]; -// } elseif ($result == false) { -// // a generic "We don't want them to do that" -// $errors[] = [ 'badaccess-group0' ]; -// } -// return $errors; -// } -// -// /** -// * Check various permission hooks -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkPermissionHooks($action, $user, $errors, $rigor, $short) { -// // Use getUserPermissionsErrors instead -// $result = Bry_.Empty; -// // Avoid PHP 7.1 warning from passing this by reference -// $titleRef = this; -// if (!Hooks::run('userCan', [ &$titleRef, &$user, $action, &$result ])) { -// return $result ? [] : [ [ 'badaccess-group0' ] ]; -// } -// // Check getUserPermissionsErrors hook -// // Avoid PHP 7.1 warning from passing this by reference -// $titleRef = this; -// if (!Hooks::run('getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ])) { -// $errors = this.resultToError($errors, $result); -// } -// // Check getUserPermissionsErrorsExpensive hook -// if ( -// $rigor != 'quick' -// && !($short && count($errors) > 0) -// && !Hooks::run('getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ]) -// ) { -// $errors = this.resultToError($errors, $result); -// } -// -// return $errors; -// } -// -// /** -// * Check permissions on special pages & namespaces -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkSpecialsAndNSPermissions($action, $user, $errors, $rigor, $short) { -// // Only 'createaccount' can be performed on special pages, -// // which don't actually exist in the DB. -// if (NS_SPECIAL == this.mNamespace && $action != 'createaccount') { -// $errors[] = [ 'ns-specialprotected' ]; -// } -// -// // Check $wgNamespaceProtection for restricted namespaces -// if (this.isNamespaceProtected($user)) { -// $ns = this.mNamespace == NS_MAIN ? -// wfMessage('nstab-main').text() : this.getNsText(); -// $errors[] = this.mNamespace == NS_MEDIAWIKI ? -// [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ]; -// } -// -// return $errors; -// } -// -// /** -// * Check CSS/JS sub-page permissions -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkCSSandJSPermissions($action, $user, $errors, $rigor, $short) { -// // Protect css/js subpages of user pages -// // XXX: this might be better using restrictions -// // XXX: right 'editusercssjs' is deprecated, for backward compatibility only -// if ($action != 'patrol' && !$user.isAllowed('editusercssjs')) { -// if (preg_match('/^' . preg_quote($user.getName(), '/') . '\//', this.mTextform)) { -// if (this.isCssSubpage() && !$user.isAllowedAny('editmyusercss', 'editusercss')) { -// $errors[] = [ 'mycustomcssprotected', $action ]; -// } elseif (this.isJsSubpage() && !$user.isAllowedAny('editmyuserjs', 'edituserjs')) { -// $errors[] = [ 'mycustomjsprotected', $action ]; -// } -// } else { -// if (this.isCssSubpage() && !$user.isAllowed('editusercss')) { -// $errors[] = [ 'customcssprotected', $action ]; -// } elseif (this.isJsSubpage() && !$user.isAllowed('edituserjs')) { -// $errors[] = [ 'customjsprotected', $action ]; -// } -// } -// } -// -// return $errors; -// } -// -// /** -// * Check against page_restrictions table requirements on this -// * page. The user must possess all required rights for this -// * action. -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkPageRestrictions($action, $user, $errors, $rigor, $short) { -// foreach (this.getRestrictions($action) as $right) { -// // Backwards compatibility, rewrite sysop . editprotected -// if ($right == 'sysop') { -// $right = 'editprotected'; -// } -// // Backwards compatibility, rewrite autoconfirmed . editsemiprotected -// if ($right == 'autoconfirmed') { -// $right = 'editsemiprotected'; -// } -// if ($right == Bry_.Empty) { -// continue; -// } -// if (!$user.isAllowed($right)) { -// $errors[] = [ 'protectedpagetext', $right, $action ]; -// } elseif (this.mCascadeRestriction && !$user.isAllowed('protect')) { -// $errors[] = [ 'protectedpagetext', 'protect', $action ]; -// } -// } -// -// return $errors; -// } -// -// /** -// * Check restrictions on cascading pages. -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short) { -// if ($rigor != 'quick' && !this.isCssJsSubpage()) { -// // We /could/ use the protection level on the source page, but it's -// // fairly ugly as we have to establish a precedence hierarchy for pages -// // included by multiple cascade-protected pages. So just restrict -// // it to people with 'protect' permission, as they could remove the -// // protection anyway. -// list($cascadingSources, $restrictions) = this.getCascadeProtectionSources(); -// // Cascading protection depends on more than this page... -// // Several cascading protected pages may include this page... -// // Check each cascading level -// // This is only for protection restrictions, not for all actions -// if (isset($restrictions[$action])) { -// foreach ($restrictions[$action] as $right) { -// // Backwards compatibility, rewrite sysop . editprotected -// if ($right == 'sysop') { -// $right = 'editprotected'; -// } -// // Backwards compatibility, rewrite autoconfirmed . editsemiprotected -// if ($right == 'autoconfirmed') { -// $right = 'editsemiprotected'; -// } -// if ($right != Bry_.Empty && !$user.isAllowedAll('protect', $right)) { -// $pages = Bry_.Empty; -// foreach ($cascadingSources as $page) { -// $pages .= '* [[:' . $page.getPrefixedText() . "]]\n"; -// } -// $errors[] = [ 'cascadeprotected', count($cascadingSources), $pages, $action ]; -// } -// } -// } -// } -// -// return $errors; -// } -// -// /** -// * Check action permissions not already checked in checkQuickPermissions -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkActionPermissions($action, $user, $errors, $rigor, $short) { -// global $wgDeleteRevisionsLimit, $wgLang; -// -// if ($action == 'protect') { -// if (count(this.getUserPermissionsErrorsInternal('edit', $user, $rigor, true))) { -// // If they can't edit, they shouldn't protect. -// $errors[] = [ 'protect-cantedit' ]; -// } -// } elseif ($action == 'create') { -// $title_protection = this.getTitleProtection(); -// if ($title_protection) { -// if ($title_protection['permission'] == Bry_.Empty -// || !$user.isAllowed($title_protection['permission']) -// ) { -// $errors[] = [ -// 'titleprotected', -// User::whoIs($title_protection['user']), -// $title_protection['reason'] -// ]; -// } -// } -// } elseif ($action == 'move') { -// // Check for immobile pages -// if (!XomwNamespace::isMovable(this.mNamespace)) { -// // Specific message for this case -// $errors[] = [ 'immobile-source-namespace', this.getNsText() ]; -// } elseif (!this.isMovable()) { -// // Less specific message for rarer cases -// $errors[] = [ 'immobile-source-page' ]; -// } -// } elseif ($action == 'move-target') { -// if (!XomwNamespace::isMovable(this.mNamespace)) { -// $errors[] = [ 'immobile-target-namespace', this.getNsText() ]; -// } elseif (!this.isMovable()) { -// $errors[] = [ 'immobile-target-page' ]; -// } -// } elseif ($action == 'delete') { -// $tempErrors = this.checkPageRestrictions('edit', $user, [], $rigor, true); -// if (!$tempErrors) { -// $tempErrors = this.checkCascadingSourcesRestrictions('edit', -// $user, $tempErrors, $rigor, true); -// } -// if ($tempErrors) { -// // If protection keeps them from editing, they shouldn't be able to delete. -// $errors[] = [ 'deleteprotected' ]; -// } -// if ($rigor != 'quick' && $wgDeleteRevisionsLimit -// && !this.userCan('bigdelete', $user) && this.isBigDeletion() -// ) { -// $errors[] = [ 'delete-toobig', $wgLang.formatNum($wgDeleteRevisionsLimit) ]; -// } -// } -// return $errors; -// } -// -// /** -// * Check that the user isn't blocked from editing. -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkUserBlock($action, $user, $errors, $rigor, $short) { -// global $wgEmailConfirmToEdit, $wgBlockDisablesLogin; -// // Account creation blocks handled at userlogin. -// // Unblocking handled in SpecialUnblock -// if ($rigor == 'quick' || in_array($action, [ 'createaccount', 'unblock' ])) { -// return $errors; -// } -// -// // Optimize for a very common case -// if ($action == 'read' && !$wgBlockDisablesLogin) { -// return $errors; -// } -// -// if ($wgEmailConfirmToEdit && !$user.isEmailConfirmed()) { -// $errors[] = [ 'confirmedittext' ]; -// } -// -// $useSlave = ($rigor != 'secure'); -// if (($action == 'edit' || $action == 'create') -// && !$user.isBlockedFrom(this, $useSlave) -// ) { -// // Don't block the user from editing their own talk page unless they've been -// // explicitly blocked from that too. -// } elseif ($user.isBlocked() && $user.getBlock().prevents($action) != false) { -// // @todo FIXME: Pass the relevant context into this function. -// $errors[] = $user.getBlock().getPermissionsError(RequestContext::getMain()); -// } -// -// return $errors; -// } -// -// /** -// * Check that the user is allowed to read this page. -// * -// * @param String $action The action to check -// * @param User $user User to check -// * @param array $errors List of current errors -// * @param String $rigor Same format as Title::getUserPermissionsErrors() -// * @param boolean $short Short circuit on first error -// * -// * @return array List of errors -// */ -// private function checkReadPermissions($action, $user, $errors, $rigor, $short) { -// global $wgWhitelistRead, $wgWhitelistReadRegexp; -// -// $whitelisted = false; -// if (User::isEveryoneAllowed('read')) { -// // Shortcut for public wikis, allows skipping quite a bit of code -// $whitelisted = true; -// } elseif ($user.isAllowed('read')) { -// // If the user is allowed to read pages, he is allowed to read all pages -// $whitelisted = true; -// } elseif (this.isSpecial('Userlogin') -// || this.isSpecial('PasswordReset') -// || this.isSpecial('Userlogout') -// ) { -// // Always grant access to the login page. -// // Even anons need to be able to log in. -// $whitelisted = true; -// } elseif (is_array($wgWhitelistRead) && count($wgWhitelistRead)) { -// // Time to check the whitelist -// // Only do these checks is there's something to check against -// $name = this.getPrefixedText(); -// $dbName = this.getPrefixedDBkey(); -// -// // Check for explicit whitelisting with and without underscores -// if (in_array($name, $wgWhitelistRead, true) || in_array($dbName, $wgWhitelistRead, true)) { -// $whitelisted = true; -// } elseif (this.getNamespace() == NS_MAIN) { -// // Old settings might have the title prefixed with -// // a colon for main-namespace pages -// if (in_array(':' . $name, $wgWhitelistRead)) { -// $whitelisted = true; -// } -// } elseif (this.isSpecialPage()) { -// // If it's a special page, ditch the subpage bit and check again -// $name = this.getDBkey(); -// list($name, /* $subpage */) = SpecialPageFactory::resolveAlias($name); -// if ($name) { -// $pure = SpecialPage::getTitleFor($name).getPrefixedText(); -// if (in_array($pure, $wgWhitelistRead, true)) { -// $whitelisted = true; -// } -// } -// } -// } -// -// if (!$whitelisted && is_array($wgWhitelistReadRegexp) && !empty($wgWhitelistReadRegexp)) { -// $name = this.getPrefixedText(); -// // Check for regex whitelisting -// foreach ($wgWhitelistReadRegexp as $listItem) { -// if (preg_match($listItem, $name)) { -// $whitelisted = true; -// break; -// } -// } -// } -// -// if (!$whitelisted) { -// // If the title is not whitelisted, give extensions a chance to do so... -// Hooks::run('TitleReadWhitelist', [ this, $user, &$whitelisted ]); -// if (!$whitelisted) { -// $errors[] = this.missingPermissionError($action, $short); -// } -// } -// -// return $errors; -// } -// -// /** -// * Get a description array when the user doesn't have the right to perform -// * $action (i.e. when User::isAllowed() returns false) -// * -// * @param String $action The action to check -// * @param boolean $short Short circuit on first error -// * @return array List of errors -// */ -// private function missingPermissionError($action, $short) { -// // We avoid expensive display logic for quickUserCan's and such -// if ($short) { -// return [ 'badaccess-group0' ]; -// } -// -// $groups = array_map([ 'User', 'makeGroupLinkWiki' ], -// User::getGroupsWithPermission($action)); -// -// if (count($groups)) { -// global $wgLang; -// return [ -// 'badaccess-groups', -// $wgLang.commaList($groups), -// count($groups) -// ]; -// } else { -// return [ 'badaccess-group0' ]; -// } -// } -// -// /** -// * Can $user perform $action on this page? This is an @gplx.Internal protected function, -// * with multiple levels of checks depending on performance needs; see $rigor below. -// * It does not check wfReadOnly(). -// * -// * @param String $action Action that permission needs to be checked for -// * @param User $user User to check -// * @param String $rigor One of (quick,full,secure) -// * - quick : does cheap permission checks from replica DBs (usable for GUI creation) -// * - full : does cheap and expensive checks possibly from a replica DB -// * - secure : does cheap and expensive checks, using the master as needed -// * @param boolean $short Set this to true to stop after the first permission error. -// * @return array Array of arrays of the arguments to wfMessage to explain permissions problems. -// */ -// protected function getUserPermissionsErrorsInternal( -// $action, $user, $rigor = 'secure', $short = false -// ) { -// if ($rigor == true) { -// $rigor = 'secure'; // b/c -// } elseif ($rigor == false) { -// $rigor = 'quick'; // b/c -// } elseif (!in_array($rigor, [ 'quick', 'full', 'secure' ])) { -// throw new Exception("Invalid rigor parameter '$rigor'."); -// } -// -// // Read has special handling -// if ($action == 'read') { -// $checks = [ -// 'checkPermissionHooks', -// 'checkReadPermissions', -// 'checkUserBlock', // for wgBlockDisablesLogin -// ]; -// // Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions -// // here as it will lead to duplicate error messages. This is okay to do -// // since anywhere that checks for create will also check for edit, and -// // those checks are called for edit. -// } elseif ($action == 'create') { -// $checks = [ -// 'checkQuickPermissions', -// 'checkPermissionHooks', -// 'checkPageRestrictions', -// 'checkCascadingSourcesRestrictions', -// 'checkActionPermissions', -// 'checkUserBlock' -// ]; -// } else { -// $checks = [ -// 'checkQuickPermissions', -// 'checkPermissionHooks', -// 'checkSpecialsAndNSPermissions', -// 'checkCSSandJSPermissions', -// 'checkPageRestrictions', -// 'checkCascadingSourcesRestrictions', -// 'checkActionPermissions', -// 'checkUserBlock' -// ]; -// } -// -// $errors = []; -// while (count($checks) > 0 && -// !($short && count($errors) > 0)) { -// $method = array_shift($checks); -// $errors = this.$method($action, $user, $errors, $rigor, $short); -// } -// -// return $errors; -// } -// -// /** -// * Get a filtered list of all restriction types supported by this wiki. -// * @param boolean $exists True to get all restriction types that apply to -// * titles that do exist, False for all restriction types that apply to -// * titles that do not exist -// * @return array -// */ -// public static function getFilteredRestrictionTypes($exists = true) { -// global $wgRestrictionTypes; -// $types = $wgRestrictionTypes; -// if ($exists) { -// // Remove the create restriction for existing titles -// $types = array_diff($types, [ 'create' ]); -// } else { -// // Only the create and upload restrictions apply to non-existing titles -// $types = array_intersect($types, [ 'create', 'upload' ]); -// } -// return $types; -// } -// -// /** -// * Returns restriction types for the current Title -// * -// * @return array Applicable restriction types -// */ -// public function getRestrictionTypes() { -// if (this.isSpecialPage()) { -// return []; -// } -// -// $types = self::getFilteredRestrictionTypes(this.exists()); -// -// if (this.getNamespace() != NS_FILE) { -// // Remove the upload restriction for non-file titles -// $types = array_diff($types, [ 'upload' ]); -// } -// -// Hooks::run('TitleGetRestrictionTypes', [ this, &$types ]); -// -// wfDebug(__METHOD__ . ': applicable restrictions to [[' . -// this.getPrefixedText() . ']] are {' . implode(',', $types) . "}\n"); -// -// return $types; -// } -// -// /** -// * Is this title subject to title protection? -// * Title protection is the one applied against creation of such title. -// * -// * @return array|boolean An associative array representing any existent title -// * protection, or false if there's none. -// */ -// public function getTitleProtection() { -// // Can't protect pages in special namespaces -// if (this.getNamespace() < 0) { -// return false; -// } -// -// // Can't protect pages that exist. -// if (this.exists()) { -// return false; -// } -// -// if (this.mTitleProtection == null) { -// $dbr = wfGetDB(DB_REPLICA); -// $res = $dbr.select( -// 'protected_titles', -// [ -// 'user' => 'pt_user', -// 'reason' => 'pt_reason', -// 'expiry' => 'pt_expiry', -// 'permission' => 'pt_create_perm' -// ], -// [ 'pt_namespace' => this.getNamespace(), 'pt_title' => this.getDBkey() ], -// __METHOD__ -// ); -// -// // fetchRow returns false if there are no rows. -// $row = $dbr.fetchRow($res); -// if ($row) { -// if ($row['permission'] == 'sysop') { -// $row['permission'] = 'editprotected'; // B/C -// } -// if ($row['permission'] == 'autoconfirmed') { -// $row['permission'] = 'editsemiprotected'; // B/C -// } -// $row['expiry'] = $dbr.decodeExpiry($row['expiry']); -// } -// this.mTitleProtection = $row; -// } -// return this.mTitleProtection; -// } -// -// /** -// * Remove any title protection due to page existing -// */ -// public function deleteTitleProtection() { -// $dbw = wfGetDB(DB_MASTER); -// -// $dbw.delete( -// 'protected_titles', -// [ 'pt_namespace' => this.getNamespace(), 'pt_title' => this.getDBkey() ], -// __METHOD__ -// ); -// this.mTitleProtection = false; -// } -// -// /** -// * Is this page "semi-protected" - the *only* protection levels are listed -// * in $wgSemiprotectedRestrictionLevels? -// * -// * @param String $action Action to check (default: edit) -// * @return boolean -// */ -// public function isSemiProtected($action = 'edit') { -// global $wgSemiprotectedRestrictionLevels; -// -// $restrictions = this.getRestrictions($action); -// $semi = $wgSemiprotectedRestrictionLevels; -// if (!$restrictions || !$semi) { -// // Not protected, or all protection is full protection -// return false; -// } -// -// // Remap autoconfirmed to editsemiprotected for BC -// foreach (array_keys($semi, 'autoconfirmed') as $key) { -// $semi[$key] = 'editsemiprotected'; -// } -// foreach (array_keys($restrictions, 'autoconfirmed') as $key) { -// $restrictions[$key] = 'editsemiprotected'; -// } -// -// return !array_diff($restrictions, $semi); -// } -// -// /** -// * Does the title correspond to a protected article? -// * -// * @param String $action The action the page is protected from, -// * by default checks all actions. -// * @return boolean -// */ -// public function isProtected($action = Bry_.Empty) { -// global $wgRestrictionLevels; -// -// $restrictionTypes = this.getRestrictionTypes(); -// -// // Special pages have inherent protection -// if (this.isSpecialPage()) { -// return true; -// } -// -// // Check regular protection levels -// foreach ($restrictionTypes as $type) { -// if ($action == $type || $action == Bry_.Empty) { -// $r = this.getRestrictions($type); -// foreach ($wgRestrictionLevels as $level) { -// if (in_array($level, $r) && $level != Bry_.Empty) { -// return true; -// } -// } -// } -// } -// -// return false; -// } -// -// /** -// * Determines if $user is unable to edit this page because it has been protected -// * by $wgNamespaceProtection. -// * -// * @param User $user User Object to check permissions -// * @return boolean -// */ -// public function isNamespaceProtected(User $user) { -// global $wgNamespaceProtection; -// -// if (isset($wgNamespaceProtection[this.mNamespace])) { -// foreach ((array)$wgNamespaceProtection[this.mNamespace] as $right) { -// if ($right != Bry_.Empty && !$user.isAllowed($right)) { -// return true; -// } -// } -// } -// return false; -// } -// -// /** -// * Cascading protection: Return true if cascading restrictions apply to this page, false if not. -// * -// * @return boolean If the page is subject to cascading restrictions. -// */ -// public function isCascadeProtected() { -// list($sources, /* $restrictions */) = this.getCascadeProtectionSources(false); -// return ($sources > 0); -// } -// -// /** -// * Determines whether cascading protection sources have already been loaded from -// * the database. -// * -// * @param boolean $getPages True to check if the pages are loaded, or false to check -// * if the status is loaded. -// * @return boolean Whether or not the specified information has been loaded -// * @since 1.23 -// */ -// public function areCascadeProtectionSourcesLoaded($getPages = true) { -// return $getPages ? this.mCascadeSources != null : this.mHasCascadingRestrictions != null; -// } -// -// /** -// * Cascading protection: Get the source of any cascading restrictions on this page. -// * -// * @param boolean $getPages Whether or not to retrieve the actual pages -// * that the restrictions have come from and the actual restrictions -// * themselves. -// * @return array Two elements: First is an array of Title objects of the -// * pages from which cascading restrictions have come, false for -// * none, or true if such restrictions exist but $getPages was not -// * set. Second is an array like that returned by -// * Title::getAllRestrictions(), or an empty array if $getPages is -// * false. -// */ -// public function getCascadeProtectionSources($getPages = true) { -// $pagerestrictions = []; -// -// if (this.mCascadeSources != null && $getPages) { -// return [ this.mCascadeSources, this.mCascadingRestrictions ]; -// } elseif (this.mHasCascadingRestrictions != null && !$getPages) { -// return [ this.mHasCascadingRestrictions, $pagerestrictions ]; -// } -// -// $dbr = wfGetDB(DB_REPLICA); -// -// if (this.getNamespace() == NS_FILE) { -// $tables = [ 'imagelinks', 'page_restrictions' ]; -// $where_clauses = [ -// 'il_to' => this.getDBkey(), -// 'il_from=pr_page', -// 'pr_cascade' => 1 -// ]; -// } else { -// $tables = [ 'templatelinks', 'page_restrictions' ]; -// $where_clauses = [ -// 'tl_namespace' => this.getNamespace(), -// 'tl_title' => this.getDBkey(), -// 'tl_from=pr_page', -// 'pr_cascade' => 1 -// ]; -// } -// -// if ($getPages) { -// $cols = [ 'pr_page', 'page_namespace', 'page_title', -// 'pr_expiry', 'pr_type', 'pr_level' ]; -// $where_clauses[] = 'page_id=pr_page'; -// $tables[] = 'page'; -// } else { -// $cols = [ 'pr_expiry' ]; -// } -// -// $res = $dbr.select($tables, $cols, $where_clauses, __METHOD__); -// -// $sources = $getPages ? [] : false; -// $now = wfTimestampNow(); -// -// foreach ($res as $row) { -// $expiry = $dbr.decodeExpiry($row.pr_expiry); -// if ($expiry > $now) { -// if ($getPages) { -// $page_id = $row.pr_page; -// $page_ns = $row.page_namespace; -// $page_title = $row.page_title; -// $sources[$page_id] = Title::makeTitle($page_ns, $page_title); -// // Add groups needed for each restriction type if its not already there -// // Make sure this restriction type still exists -// -// if (!isset($pagerestrictions[$row.pr_type])) { -// $pagerestrictions[$row.pr_type] = []; -// } -// -// if ( -// isset($pagerestrictions[$row.pr_type]) -// && !in_array($row.pr_level, $pagerestrictions[$row.pr_type]) -// ) { -// $pagerestrictions[$row.pr_type][] = $row.pr_level; -// } -// } else { -// $sources = true; -// } -// } -// } -// -// if ($getPages) { -// this.mCascadeSources = $sources; -// this.mCascadingRestrictions = $pagerestrictions; -// } else { -// this.mHasCascadingRestrictions = $sources; -// } -// -// return [ $sources, $pagerestrictions ]; -// } -// -// /** -// * Accessor for mRestrictionsLoaded -// * -// * @return boolean Whether or not the page's restrictions have already been -// * loaded from the database -// * @since 1.23 -// */ -// public function areRestrictionsLoaded() { -// return this.mRestrictionsLoaded; -// } -// -// /** -// * Accessor/initialisation for mRestrictions -// * -// * @param String $action Action that permission needs to be checked for -// * @return array Restriction levels needed to take the action. All levels are -// * required. Note that restriction levels are normally user rights, but 'sysop' -// * and 'autoconfirmed' are also allowed for backwards compatibility. These should -// * be mapped to 'editprotected' and 'editsemiprotected' respectively. -// */ -// public function getRestrictions($action) { -// if (!this.mRestrictionsLoaded) { -// this.loadRestrictions(); -// } -// return isset(this.mRestrictions[$action]) -// ? this.mRestrictions[$action] -// : []; -// } -// -// /** -// * Accessor/initialisation for mRestrictions -// * -// * @return array Keys are actions, values are arrays as returned by -// * Title::getRestrictions() -// * @since 1.23 -// */ -// public function getAllRestrictions() { -// if (!this.mRestrictionsLoaded) { -// this.loadRestrictions(); -// } -// return this.mRestrictions; -// } -// -// /** -// * Get the expiry time for the restriction against a given action -// * -// * @param String $action -// * @return String|boolean 14-char timestamp, or 'infinity' if the page is protected forever -// * or not protected at all, or false if the action is not recognised. -// */ -// public function getRestrictionExpiry($action) { -// if (!this.mRestrictionsLoaded) { -// this.loadRestrictions(); -// } -// return isset(this.mRestrictionsExpiry[$action]) ? this.mRestrictionsExpiry[$action] : false; -// } -// -// /** -// * Returns cascading restrictions for the current article -// * -// * @return boolean -// */ -// function areRestrictionsCascading() { -// if (!this.mRestrictionsLoaded) { -// this.loadRestrictions(); -// } -// -// return this.mCascadeRestriction; -// } -// -// /** -// * Compiles list of active page restrictions from both page table (pre 1.10) -// * and page_restrictions table for this existing page. -// * Public for usage by LiquidThreads. -// * -// * @param array $rows Array of db result objects -// * @param String $oldFashionedRestrictions Comma-separated list of page -// * restrictions from page table (pre 1.10) -// */ -// public function loadRestrictionsFromRows($rows, $oldFashionedRestrictions = null) { -// $dbr = wfGetDB(DB_REPLICA); -// -// $restrictionTypes = this.getRestrictionTypes(); -// -// foreach ($restrictionTypes as $type) { -// this.mRestrictions[$type] = []; -// this.mRestrictionsExpiry[$type] = 'infinity'; -// } -// -// this.mCascadeRestriction = false; -// -// // Backwards-compatibility: also load the restrictions from the page record (old format). -// if ($oldFashionedRestrictions != null) { -// this.mOldRestrictions = $oldFashionedRestrictions; -// } -// -// if (this.mOldRestrictions == false) { -// this.mOldRestrictions = $dbr.selectField('page', 'page_restrictions', -// [ 'page_id' => this.getArticleID() ], __METHOD__); -// } -// -// if (this.mOldRestrictions != Bry_.Empty) { -// foreach (explode(':', trim(this.mOldRestrictions)) as $restrict) { -// $temp = explode('=', trim($restrict)); -// if (count($temp) == 1) { -// // old old format should be treated as edit/move restriction -// this.mRestrictions['edit'] = explode(',', trim($temp[0])); -// this.mRestrictions['move'] = explode(',', trim($temp[0])); -// } else { -// $restriction = trim($temp[1]); -// if ($restriction != Bry_.Empty) { // some old entries are empty -// this.mRestrictions[$temp[0]] = explode(',', $restriction); -// } -// } -// } -// } -// -// if (count($rows)) { -// // Current system - load second to make them override. -// $now = wfTimestampNow(); -// -// // Cycle through all the restrictions. -// foreach ($rows as $row) { -// // Don't take care of restrictions types that aren't allowed -// if (!in_array($row.pr_type, $restrictionTypes)) { -// continue; -// } -// -// // This code should be refactored, now that it's being used more generally, -// // But I don't really see any harm in leaving it in Block for now -werdna -// $expiry = $dbr.decodeExpiry($row.pr_expiry); -// -// // Only apply the restrictions if they haven't expired! -// if (!$expiry || $expiry > $now) { -// this.mRestrictionsExpiry[$row.pr_type] = $expiry; -// this.mRestrictions[$row.pr_type] = explode(',', trim($row.pr_level)); -// -// this.mCascadeRestriction |= $row.pr_cascade; -// } -// } -// } -// -// this.mRestrictionsLoaded = true; -// } -// -// /** -// * Load restrictions from the page_restrictions table -// * -// * @param String $oldFashionedRestrictions Comma-separated list of page -// * restrictions from page table (pre 1.10) -// */ -// public function loadRestrictions($oldFashionedRestrictions = null) { -// if (this.mRestrictionsLoaded) { -// return; -// } -// -// $id = this.getArticleID(); -// if ($id) { -// $cache = ObjectCache::getMainWANInstance(); -// $rows = $cache.getWithSetCallback( -// // Page protections always leave a new null revision -// $cache.makeKey('page-restrictions', $id, this.getLatestRevID()), -// $cache::TTL_DAY, -// function ($curValue, &$ttl, array &$setOpts) { -// $dbr = wfGetDB(DB_REPLICA); -// -// $setOpts += Database::getCacheSetOptions($dbr); -// -// return iterator_to_array( -// $dbr.select( -// 'page_restrictions', -// [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ], -// [ 'pr_page' => this.getArticleID() ], -// __METHOD__ -// ) -// ); -// } -// ); -// -// this.loadRestrictionsFromRows($rows, $oldFashionedRestrictions); -// } else { -// $title_protection = this.getTitleProtection(); -// -// if ($title_protection) { -// $now = wfTimestampNow(); -// $expiry = wfGetDB(DB_REPLICA).decodeExpiry($title_protection['expiry']); -// -// if (!$expiry || $expiry > $now) { -// // Apply the restrictions -// this.mRestrictionsExpiry['create'] = $expiry; -// this.mRestrictions['create'] = -// explode(',', trim($title_protection['permission'])); -// } else { // Get rid of the old restrictions -// this.mTitleProtection = false; -// } -// } else { -// this.mRestrictionsExpiry['create'] = 'infinity'; -// } -// this.mRestrictionsLoaded = true; -// } -// } -// -// /** -// * Flush the protection cache in this Object and force reload from the database. -// * This is used when updating protection from WikiPage::doUpdateRestrictions(). -// */ -// public function flushRestrictions() { -// this.mRestrictionsLoaded = false; -// this.mTitleProtection = null; -// } -// -// /** -// * Purge expired restrictions from the page_restrictions table -// * -// * This will purge no more than $wgUpdateRowsPerQuery page_restrictions rows -// */ -// static function purgeExpiredRestrictions() { -// if (wfReadOnly()) { -// return; -// } -// -// DeferredUpdates::addUpdate(new AtomicSectionUpdate( -// wfGetDB(DB_MASTER), -// __METHOD__, -// function (IDatabase $dbw, $fname) { -// $config = MediaWikiServices::getInstance().getMainConfig(); -// $ids = $dbw.selectFieldValues( -// 'page_restrictions', -// 'pr_id', -// [ 'pr_expiry < ' . $dbw.addQuotes($dbw.timestamp()) ], -// $fname, -// [ 'LIMIT' => $config.get('UpdateRowsPerQuery') ] // T135470 -// ); -// if ($ids) { -// $dbw.delete('page_restrictions', [ 'pr_id' => $ids ], $fname); -// } -// } -// )); -// -// DeferredUpdates::addUpdate(new AtomicSectionUpdate( -// wfGetDB(DB_MASTER), -// __METHOD__, -// function (IDatabase $dbw, $fname) { -// $dbw.delete( -// 'protected_titles', -// [ 'pt_expiry < ' . $dbw.addQuotes($dbw.timestamp()) ], -// $fname -// ); -// } -// )); -// } -// -// /** -// * Does this have subpages? (Warning, usually requires an extra DB query.) -// * -// * @return boolean -// */ -// public function hasSubpages() { -// if (!XomwNamespace::hasSubpages(this.mNamespace)) { -// // Duh -// return false; -// } -// -// // We dynamically add a member variable for the purpose of this method -// // alone to cache the result. There's no point in having it hanging -// // around uninitialized in every Title Object; therefore we only add it -// // if needed and don't declare it statically. -// if (this.mHasSubpages == null) { -// this.mHasSubpages = false; -// $subpages = this.getSubpages(1); -// if ($subpages instanceof TitleArray) { -// this.mHasSubpages = (boolean)$subpages.count(); -// } -// } -// -// return this.mHasSubpages; -// } -// -// /** -// * Get all subpages of this page. -// * -// * @param int $limit Maximum number of subpages to fetch; -1 for no limit -// * @return TitleArray|array TitleArray, or empty array if this page's namespace -// * doesn't allow subpages -// */ -// public function getSubpages($limit = -1) { -// if (!XomwNamespace::hasSubpages(this.getNamespace())) { -// return []; -// } -// -// $dbr = wfGetDB(DB_REPLICA); -// $conds['page_namespace'] = this.getNamespace(); -// $conds[] = 'page_title ' . $dbr.buildLike(this.getDBkey() . '/', $dbr.anyString()); -// $options = []; -// if ($limit > -1) { -// $options['LIMIT'] = $limit; -// } -// this.mSubpages = TitleArray::newFromResult( -// $dbr.select('page', -// [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ], -// $conds, -// __METHOD__, -// $options -// ) -// ); -// return this.mSubpages; + * Represents a title within MediaWiki. + * Optionally may contain an interwiki designation or namespace. + * @note This class can fetch various kinds of data from the database; + * however, it does so inefficiently. + * @note Consider using a TitleValue object instead. TitleValue is more lightweight + * and does not rely on global state or the database. + */ +public class XomwTitle { // implements LinkTarget, IDBAccessObject +// /** @var MapCacheLRU|null */ +// private static $titleCache = null; +// +// /** +// * Title::newFromText maintains a cache to avoid expensive re-normalization of +// * commonly used titles. On a batch operation this can become a memory leak +// * if not bounded. After hitting this many titles reset the cache. +// */ +// const CACHE_MAX = 1000; +// +// /** +// * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends +// * to use the master DB +// */ +// const GAID_FOR_UPDATE = 1; +// +// /** +// * Flag for use with factory methods like newFromLinkTarget() that have +// * a $forceClone parameter. If set, the method must return a new instance. +// * Without this flag, some factory methods may return existing instances. +// * +// * @since 1.33 +// */ +// const NEW_CLONE = 'clone'; +// +// /** +// * @name Private member variables +// * Please use the accessor functions instead. +// * @private +// */ +// // @{ +// +// /** @var string Text form (spaces not underscores) of the main part */ +// public $mTextform = ''; +// +// /** @var string URL-encoded form of the main part */ +// public $mUrlform = ''; +// +// /** @var string Main part with underscores */ +// public $mDbkeyform = ''; +// +// /** @var string Database key with the initial letter in the case specified by the user */ +// protected $mUserCaseDBKey; +// +// /** @var int Namespace index, i.e. one of the NS_xxxx constants */ +// public $mNamespace = NS_MAIN; +// +// /** @var string Interwiki prefix */ +// public $mInterwiki = ''; +// +// /** @var bool Was this Title created from a string with a local interwiki prefix? */ +// private $mLocalInterwiki = false; +// +// /** @var string Title fragment (i.e. the bit after the #) */ +// public $mFragment = ''; +// +// /** @var int Article ID, fetched from the link cache on demand */ +// public $mArticleID = -1; +// +// /** @var bool|int ID of most recent revision */ +// protected $mLatestID = false; +// +// /** +// * @var bool|string ID of the page's content model, i.e. one of the +// * CONTENT_MODEL_XXX constants +// */ +// private $mContentModel = false; +// +// /** +// * @var bool If a content model was forced via setContentModel() +// * this will be true to avoid having other code paths reset it +// */ +// private $mForcedContentModel = false; +// +// /** @var int Estimated number of revisions; null of not loaded */ +// private $mEstimateRevisions; +// +// /** @var array Array of groups allowed to edit this article */ +// public $mRestrictions = []; +// +// /** +// * @var string|bool Comma-separated set of permission keys +// * indicating who can move or edit the page from the page table, (pre 1.10) rows. +// * Edit and move sections are separated by a colon +// * Example: "edit=autoconfirmed,sysop:move=sysop" +// */ +// protected $mOldRestrictions = false; +// +// /** @var bool Cascade restrictions on this page to included templates and images? */ +// public $mCascadeRestriction; +// +// /** Caching the results of getCascadeProtectionSources */ +// public $mCascadingRestrictions; +// +// /** @var array When do the restrictions on this page expire? */ +// protected $mRestrictionsExpiry = []; +// +// /** @var bool Are cascading restrictions in effect on this page? */ +// protected $mHasCascadingRestrictions; +// +// /** @var array Where are the cascading restrictions coming from on this page? */ +// public $mCascadeSources; +// +// /** @var bool Boolean for initialisation on demand */ +// public $mRestrictionsLoaded = false; +// +// /** +// * Text form including namespace/interwiki, initialised on demand +// * +// * Only public to share cache with TitleFormatter +// * +// * @private +// * @var string|null +// */ +// public $prefixedText = null; +// +// /** @var mixed Cached value for getTitleProtection (create protection) */ +// public $mTitleProtection; +// +// /** +// * @var int Namespace index when there is no namespace. Don't change the +// * following default, NS_MAIN is hardcoded in several places. See T2696. +// * Zero except in {{transclusion}} tags. +// */ +// public $mDefaultNamespace = NS_MAIN; +// +// /** @var int The page length, 0 for special pages */ +// protected $mLength = -1; +// +// /** @var null Is the article at this title a redirect? */ +// public $mRedirect = null; +// +// /** @var array Associative array of user ID -> timestamp/false */ +// private $mNotificationTimestamp = []; +// +// /** @var bool Whether a page has any subpages */ +// private $mHasSubpages; +// +// /** @var bool The (string) language code of the page's language and content code. */ +// private $mPageLanguage = false; +// +// /** @var string|bool|null The page language code from the database, null if not saved in +// * the database or false if not loaded, yet. */ +// private $mDbPageLanguage = false; +// +// /** @var TitleValue|null A corresponding TitleValue object */ +// private $mTitleValue = null; +// +// /** @var bool|null Would deleting this page be a big deletion? */ +// private $mIsBigDeletion = null; +// // @} +// +// /** +// * B/C kludge: provide a TitleParser for use by Title. +// * Ideally, Title would have no methods that need this. +// * Avoid usage of this singleton by using TitleValue +// * and the associated services when possible. +// * +// * @return TitleFormatter +// */ +// private static function getTitleFormatter() { +// return MediaWikiServices::getInstance()->getTitleFormatter(); +// } +// +// /** +// * B/C kludge: provide an InterwikiLookup for use by Title. +// * Ideally, Title would have no methods that need this. +// * Avoid usage of this singleton by using TitleValue +// * and the associated services when possible. +// * +// * @return InterwikiLookup +// */ +// private static function getInterwikiLookup() { +// return MediaWikiServices::getInstance()->getInterwikiLookup(); +// } +// +// /** +// * @protected +// */ +// function __construct() { +// } +// +// /** +// * Create a new Title from a prefixed DB key +// * +// * @param string $key The database key, which has underscores +// * instead of spaces, possibly including namespace and +// * interwiki prefixes +// * @return Title|null Title, or null on an error +// */ +// public static function newFromDBkey( $key ) { +// $t = new self(); +// $t->mDbkeyform = $key; +// +// try { +// $t->secureAndSplit(); +// return $t; +// } catch ( MalformedTitleException $ex ) { +// return null; // } -// -// /** -// * Is there a version of this page in the deletion archive? -// * -// * @return int The number of archived revisions -// */ -// public function isDeleted() { -// if (this.getNamespace() < 0) { -// $n = 0; +// } +// +// /** +// * Returns a Title given a TitleValue. +// * If the given TitleValue is already a Title instance, that instance is returned, +// * unless $forceClone is "clone". If $forceClone is "clone" and the given TitleValue +// * is already a Title instance, that instance is copied using the clone operator. +// * +// * @param TitleValue $titleValue Assumed to be safe. +// * @param string $forceClone set to NEW_CLONE to ensure a fresh instance is returned. +// * +// * @return Title +// */ +// public static function newFromTitleValue( TitleValue $titleValue, $forceClone = '' ) { +// return self::newFromLinkTarget( $titleValue, $forceClone ); +// } +// +// /** +// * Returns a Title given a LinkTarget. +// * If the given LinkTarget is already a Title instance, that instance is returned, +// * unless $forceClone is "clone". If $forceClone is "clone" and the given LinkTarget +// * is already a Title instance, that instance is copied using the clone operator. +// * +// * @param LinkTarget $linkTarget Assumed to be safe. +// * @param string $forceClone set to NEW_CLONE to ensure a fresh instance is returned. +// * +// * @return Title +// */ +// public static function newFromLinkTarget( LinkTarget $linkTarget, $forceClone = '' ) { +// if ( $linkTarget instanceof Title ) { +// // Special case if it's already a Title object +// if ( $forceClone === self::NEW_CLONE ) { +// return clone $linkTarget; // } else { -// $dbr = wfGetDB(DB_REPLICA); -// -// $n = $dbr.selectField('archive', 'COUNT(*)', -// [ 'ar_namespace' => this.getNamespace(), 'ar_title' => this.getDBkey() ], -// __METHOD__ -// ); -// if (this.getNamespace() == NS_FILE) { -// $n += $dbr.selectField('filearchive', 'COUNT(*)', -// [ 'fa_name' => this.getDBkey() ], -// __METHOD__ -// ); -// } +// return $linkTarget; // } -// return (int)$n; // } -// -// /** -// * Is there a version of this page in the deletion archive? -// * -// * @return boolean -// */ -// public function isDeletedQuick() { -// if (this.getNamespace() < 0) { -// return false; -// } -// $dbr = wfGetDB(DB_REPLICA); -// $deleted = (boolean)$dbr.selectField('archive', '1', -// [ 'ar_namespace' => this.getNamespace(), 'ar_title' => this.getDBkey() ], -// __METHOD__ -// ); -// if (!$deleted && this.getNamespace() == NS_FILE) { -// $deleted = (boolean)$dbr.selectField('filearchive', '1', -// [ 'fa_name' => this.getDBkey() ], -// __METHOD__ -// ); -// } -// return $deleted; +// return self::makeTitle( +// $linkTarget->getNamespace(), +// $linkTarget->getText(), +// $linkTarget->getFragment(), +// $linkTarget->getInterwiki() +// ); +// } +// +// /** +// * Create a new Title from text, such as what one would find in a link. De- +// * codes any HTML entities in the text. +// * +// * Title objects returned by this method are guaranteed to be valid, and +// * thus return true from the isValid() method. +// * +// * @note The Title instance returned by this method is not guaranteed to be a fresh instance. +// * It may instead be a cached instance created previously, with references to it remaining +// * elsewhere. +// * +// * @param string|int|null $text The link text; spaces, prefixes, and an +// * initial ':' indicating the main namespace are accepted. +// * @param int $defaultNamespace The namespace to use if none is specified +// * by a prefix. If you want to force a specific namespace even if +// * $text might begin with a namespace prefix, use makeTitle() or +// * makeTitleSafe(). +// * @throws InvalidArgumentException +// * @return Title|null Title or null on an error. +// */ +// public static function newFromText( $text, $defaultNamespace = NS_MAIN ) { +// // DWIM: Integers can be passed in here when page titles are used as array keys. +// if ( $text !== null && !is_string( $text ) && !is_int( $text ) ) { +// throw new InvalidArgumentException( '$text must be a string.' ); +// } +// if ( $text === null ) { +// return null; // } // -// /** -// * Get the article ID for this Title from the link cache, -// * adding it if necessary -// * -// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select -// * for update -// * @return int The ID -// */ -// public function getArticleID($flags = 0) { -// if (this.getNamespace() < 0) { -// this.mArticleID = 0; -// return this.mArticleID; -// } -// $linkCache = LinkCache::singleton(); -// if ($flags & self::GAID_FOR_UPDATE) { -// $oldUpdate = $linkCache.forUpdate(true); -// $linkCache.clearLink(this); -// this.mArticleID = $linkCache.addLinkObj(this); -// $linkCache.forUpdate($oldUpdate); -// } else { -// if (-1 == this.mArticleID) { -// this.mArticleID = $linkCache.addLinkObj(this); -// } -// } -// return this.mArticleID; +// try { +// return self::newFromTextThrow( (string)$text, $defaultNamespace ); +// } catch ( MalformedTitleException $ex ) { +// return null; // } -// -// /** -// * Is this an article that is a redirect page? -// * Uses link cache, adding it if necessary -// * -// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update -// * @return boolean -// */ -// public function isRedirect($flags = 0) { -// if (!is_null(this.mRedirect)) { -// return this.mRedirect; -// } -// if (!this.getArticleID($flags)) { -// this.mRedirect = false; -// return this.mRedirect; -// } -// -// $linkCache = LinkCache::singleton(); -// $linkCache.addLinkObj(this); # in case we already had an article ID -// $cached = $linkCache.getGoodLinkFieldObj(this, 'redirect'); -// if ($cached == null) { -// // Trust LinkCache's state over our own -// // LinkCache is telling us that the page doesn't exist, despite there being cached -// // data relating to an existing page in this.mArticleID. Updaters should clear -// // LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is -// // set, then LinkCache will definitely be up to date here, since getArticleID() forces -// // LinkCache to refresh its data from the master. -// this.mRedirect = false; -// return this.mRedirect; -// } -// -// this.mRedirect = (boolean)$cached; -// -// return this.mRedirect; +// } +// +// /** +// * Like Title::newFromText(), but throws MalformedTitleException when the title is invalid, +// * rather than returning null. +// * +// * The exception subclasses encode detailed information about why the title is invalid. +// * +// * Title objects returned by this method are guaranteed to be valid, and +// * thus return true from the isValid() method. +// * +// * @note The Title instance returned by this method is not guaranteed to be a fresh instance. +// * It may instead be a cached instance created previously, with references to it remaining +// * elsewhere. +// * +// * @see Title::newFromText +// * +// * @since 1.25 +// * @param string $text Title text to check +// * @param int $defaultNamespace +// * @throws MalformedTitleException If the title is invalid +// * @return Title +// */ +// public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) { +// if ( is_object( $text ) ) { +// throw new MWException( '$text must be a string, given an object' ); +// } elseif ( $text === null ) { +// // Legacy code relies on MalformedTitleException being thrown in this case +// // (happens when URL with no title in it is parsed). TODO fix +// throw new MalformedTitleException( 'title-invalid-empty' ); +// } +// +// $titleCache = self::getTitleCache(); +// +// // Wiki pages often contain multiple links to the same page. +// // Title normalization and parsing can become expensive on pages with many +// // links, so we can save a little time by caching them. +// // In theory these are value objects and won't get changed... +// if ( $defaultNamespace == NS_MAIN ) { +// $t = $titleCache->get( $text ); +// if ( $t ) { +// return $t; +// } +// } +// +// // Convert things like é ā or 〗 into normalized (T16952) text +// $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text ); +// +// $t = new Title(); +// $t->mDbkeyform = strtr( $filteredText, ' ', '_' ); +// $t->mDefaultNamespace = (int)$defaultNamespace; +// +// $t->secureAndSplit(); +// if ( $defaultNamespace == NS_MAIN ) { +// $titleCache->set( $text, $t ); +// } +// return $t; +// } +// +// /** +// * THIS IS NOT THE FUNCTION YOU WANT. Use Title::newFromText(). +// * +// * Example of wrong and broken code: +// * $title = Title::newFromURL( $wgRequest->getVal( 'title' ) ); +// * +// * Example of right code: +// * $title = Title::newFromText( $wgRequest->getVal( 'title' ) ); +// * +// * Create a new Title from URL-encoded text. Ensures that +// * the given title's length does not exceed the maximum. +// * +// * @param string $url The title, as might be taken from a URL +// * @return Title|null The new object, or null on an error +// */ +// public static function newFromURL( $url ) { +// $t = new Title(); +// +// # For compatibility with old buggy URLs. "+" is usually not valid in titles, +// # but some URLs used it as a space replacement and they still come +// # from some external search tools. +// if ( strpos( self::legalChars(), '+' ) === false ) { +// $url = strtr( $url, '+', ' ' ); +// } +// +// $t->mDbkeyform = strtr( $url, ' ', '_' ); +// +// try { +// $t->secureAndSplit(); +// return $t; +// } catch ( MalformedTitleException $ex ) { +// return null; // } -// -// /** -// * What is the length of this page? -// * Uses link cache, adding it if necessary -// * -// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update -// * @return int -// */ -// public function getLength($flags = 0) { -// if (this.mLength != -1) { -// return this.mLength; -// } -// if (!this.getArticleID($flags)) { -// this.mLength = 0; -// return this.mLength; -// } -// $linkCache = LinkCache::singleton(); -// $linkCache.addLinkObj(this); # in case we already had an article ID -// $cached = $linkCache.getGoodLinkFieldObj(this, 'length'); -// if ($cached == null) { -// // Trust LinkCache's state over our own, as for isRedirect() -// this.mLength = 0; -// return this.mLength; -// } -// -// this.mLength = intval($cached); -// -// return this.mLength; +// } +// +// /** +// * @return MapCacheLRU +// */ +// private static function getTitleCache() { +// if ( self::$titleCache === null ) { +// self::$titleCache = new MapCacheLRU( self::CACHE_MAX ); +// } +// return self::$titleCache; +// } +// +// /** +// * Returns a list of fields that are to be selected for initializing Title +// * objects or LinkCache entries. Uses $wgContentHandlerUseDB to determine +// * whether to include page_content_model. +// * +// * @return array +// */ +// protected static function getSelectFields() { +// global $wgContentHandlerUseDB, $wgPageLanguageUseDB; +// +// $fields = [ +// 'page_namespace', 'page_title', 'page_id', +// 'page_len', 'page_is_redirect', 'page_latest', +// ]; +// +// if ( $wgContentHandlerUseDB ) { +// $fields[] = 'page_content_model'; +// } +// +// if ( $wgPageLanguageUseDB ) { +// $fields[] = 'page_lang'; +// } +// +// return $fields; +// } +// +// /** +// * Create a new Title from an article ID +// * +// * @param int $id The page_id corresponding to the Title to create +// * @param int $flags Use Title::GAID_FOR_UPDATE to use master +// * @return Title|null The new object, or null on an error +// */ +// public static function newFromID( $id, $flags = 0 ) { +// $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA ); +// $row = $db->selectRow( +// 'page', +// self::getSelectFields(), +// [ 'page_id' => $id ], +// __METHOD__ +// ); +// if ( $row !== false ) { +// $title = self::newFromRow( $row ); +// } else { +// $title = null; +// } +// +// return $title; +// } +// +// /** +// * Make an array of titles from an array of IDs +// * +// * @param int[] $ids Array of IDs +// * @return Title[] Array of Titles +// */ +// public static function newFromIDs( $ids ) { +// if ( !count( $ids ) ) { +// return []; +// } +// $dbr = wfGetDB( DB_REPLICA ); +// +// $res = $dbr->select( +// 'page', +// self::getSelectFields(), +// [ 'page_id' => $ids ], +// __METHOD__ +// ); +// +// $titles = []; +// foreach ( $res as $row ) { +// $titles[] = self::newFromRow( $row ); +// } +// return $titles; +// } +// +// /** +// * Make a Title object from a DB row +// * +// * @param stdClass $row Object database row (needs at least page_title,page_namespace) +// * @return Title Corresponding Title +// */ +// public static function newFromRow( $row ) { +// $t = self::makeTitle( $row->page_namespace, $row->page_title ); +// $t->loadFromRow( $row ); +// return $t; +// } +// +// /** +// * Load Title object fields from a DB row. +// * If false is given, the title will be treated as non-existing. +// * +// * @param stdClass|bool $row Database row +// */ +// public function loadFromRow( $row ) { +// if ( $row ) { // page found +// if ( isset( $row->page_id ) ) { +// $this->mArticleID = (int)$row->page_id; +// } +// if ( isset( $row->page_len ) ) { +// $this->mLength = (int)$row->page_len; +// } +// if ( isset( $row->page_is_redirect ) ) { +// $this->mRedirect = (bool)$row->page_is_redirect; +// } +// if ( isset( $row->page_latest ) ) { +// $this->mLatestID = (int)$row->page_latest; +// } +// if ( !$this->mForcedContentModel && isset( $row->page_content_model ) ) { +// $this->mContentModel = (string)$row->page_content_model; +// } elseif ( !$this->mForcedContentModel ) { +// $this->mContentModel = false; # initialized lazily in getContentModel() +// } +// if ( isset( $row->page_lang ) ) { +// $this->mDbPageLanguage = (string)$row->page_lang; +// } +// if ( isset( $row->page_restrictions ) ) { +// $this->mOldRestrictions = $row->page_restrictions; +// } +// } else { // page not found +// $this->mArticleID = 0; +// $this->mLength = 0; +// $this->mRedirect = false; +// $this->mLatestID = 0; +// if ( !$this->mForcedContentModel ) { +// $this->mContentModel = false; # initialized lazily in getContentModel() +// } +// } +// } +// +// /** +// * Create a new Title from a namespace index and a DB key. +// * +// * It's assumed that $ns and $title are safe, for instance when +// * they came directly from the database or a special page name, +// * not from user input. +// * +// * No validation is applied. For convenience, spaces are normalized +// * to underscores, so that e.g. user_text fields can be used directly. +// * +// * @note This method may return Title objects that are "invalid" +// * according to the isValid() method. This is usually caused by +// * configuration changes: e.g. a namespace that was once defined is +// * no longer configured, or a character that was once allowed in +// * titles is now forbidden. +// * +// * @param int $ns The namespace of the article +// * @param string $title The unprefixed database key form +// * @param string $fragment The link fragment (after the "#") +// * @param string $interwiki The interwiki prefix +// * @return Title The new object +// */ +// public static function makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) { +// $t = new Title(); +// $t->mInterwiki = $interwiki; +// $t->mFragment = $fragment; +// $t->mNamespace = $ns = (int)$ns; +// $t->mDbkeyform = strtr( $title, ' ', '_' ); +// $t->mArticleID = ( $ns >= 0 ) ? -1 : 0; +// $t->mUrlform = wfUrlencode( $t->mDbkeyform ); +// $t->mTextform = strtr( $title, '_', ' ' ); +// $t->mContentModel = false; # initialized lazily in getContentModel() +// return $t; +// } +// +// /** +// * Create a new Title from a namespace index and a DB key. +// * The parameters will be checked for validity, which is a bit slower +// * than makeTitle() but safer for user-provided data. +// * +// * Title objects returned by makeTitleSafe() are guaranteed to be valid, +// * that is, they return true from the isValid() method. If no valid Title +// * can be constructed from the input, this method returns null. +// * +// * @param int $ns The namespace of the article +// * @param string $title Database key form +// * @param string $fragment The link fragment (after the "#") +// * @param string $interwiki Interwiki prefix +// * @return Title|null The new object, or null on an error +// */ +// public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) { +// // NOTE: ideally, this would just call makeTitle() and then isValid(), +// // but presently, that means more overhead on a potential performance hotspot. +// +// if ( !MWNamespace::exists( $ns ) ) { +// return null; // } // -// /** -// * What is the page_latest field for this page? -// * -// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update -// * @return int Int or 0 if the page doesn't exist -// */ -// public function getLatestRevID($flags = 0) { -// if (!($flags & Title::GAID_FOR_UPDATE) && this.mLatestID != false) { -// return intval(this.mLatestID); -// } -// if (!this.getArticleID($flags)) { -// this.mLatestID = 0; -// return this.mLatestID; -// } -// $linkCache = LinkCache::singleton(); -// $linkCache.addLinkObj(this); # in case we already had an article ID -// $cached = $linkCache.getGoodLinkFieldObj(this, 'revision'); -// if ($cached == null) { -// // Trust LinkCache's state over our own, as for isRedirect() -// this.mLatestID = 0; -// return this.mLatestID; -// } -// -// this.mLatestID = intval($cached); -// -// return this.mLatestID; -// } +// $t = new Title(); +// $t->mDbkeyform = self::makeName( $ns, $title, $fragment, $interwiki, true ); // -// /** -// * This clears some fields in this Object, and clears any associated -// * keys in the "bad links" section of the link cache. -// * -// * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow -// * loading of the new page_id. It's also called from -// * WikiPage::doDeleteArticleReal() -// * -// * @param int $newid The new Article ID -// */ -// public function resetArticleID($newid) { -// $linkCache = LinkCache::singleton(); -// $linkCache.clearLink(this); -// -// if ($newid == false) { -// this.mArticleID = -1; -// } else { -// this.mArticleID = intval($newid); -// } -// this.mRestrictionsLoaded = false; -// this.mRestrictions = []; -// this.mOldRestrictions = false; -// this.mRedirect = null; -// this.mLength = -1; -// this.mLatestID = false; -// this.mContentModel = false; -// this.mEstimateRevisions = null; -// this.mPageLanguage = false; -// this.mDbPageLanguage = false; -// this.mIsBigDeletion = null; +// try { +// $t->secureAndSplit(); +// return $t; +// } catch ( MalformedTitleException $ex ) { +// return null; // } -// -// public static function clearCaches() { -// $linkCache = LinkCache::singleton(); -// $linkCache.clear(); -// -// $titleCache = self::getTitleCache(); -// $titleCache.clear(); +// } +// +// /** +// * Create a new Title for the Main Page +// * +// * @note The Title instance returned by this method is not guaranteed to be a fresh instance. +// * It may instead be a cached instance created previously, with references to it remaining +// * elsewhere. +// * +// * @return Title The new object +// */ +// public static function newMainPage() { +// $title = self::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() ); +// // Don't give fatal errors if the message is broken +// if ( !$title ) { +// $title = self::newFromText( 'Main Page' ); +// } +// return $title; +// } +// +// /** +// * Get the prefixed DB key associated with an ID +// * +// * @param int $id The page_id of the article +// * @return Title|null An object representing the article, or null if no such article was found +// */ +// public static function nameOf( $id ) { +// $dbr = wfGetDB( DB_REPLICA ); +// +// $s = $dbr->selectRow( +// 'page', +// [ 'page_namespace', 'page_title' ], +// [ 'page_id' => $id ], +// __METHOD__ +// ); +// if ( $s === false ) { +// return null; // } // -// /** -// * Capitalize a text String for a title if it belongs to a namespace that capitalizes -// * -// * @param String $text Containing title to capitalize -// * @param int $ns Namespace index, defaults to NS_MAIN -// * @return String Containing capitalized title -// */ -// public static function capitalize($text, $ns = NS_MAIN) { -// global $wgContLang; -// -// if (XomwNamespace::isCapitalized($ns)) { -// return $wgContLang.ucfirst($text); +// $n = self::makeName( $s->page_namespace, $s->page_title ); +// return $n; +// } +// +// /** +// * Get a regex character class describing the legal characters in a link +// * +// * @return string The list of characters, not delimited +// */ +// public static function legalChars() { +// global $wgLegalTitleChars; +// return $wgLegalTitleChars; +// } +// +// /** +// * Utility method for converting a character sequence from bytes to Unicode. +// * +// * Primary usecase being converting $wgLegalTitleChars to a sequence usable in +// * javascript, as PHP uses UTF-8 bytes where javascript uses Unicode code units. +// * +// * @param string $byteClass +// * @return string +// */ +// public static function convertByteClassToUnicodeClass( $byteClass ) { +// $length = strlen( $byteClass ); +// // Input token queue +// $x0 = $x1 = $x2 = ''; +// // Decoded queue +// $d0 = $d1 = $d2 = ''; +// // Decoded integer codepoints +// $ord0 = $ord1 = $ord2 = 0; +// // Re-encoded queue +// $r0 = $r1 = $r2 = ''; +// // Output +// $out = ''; +// // Flags +// $allowUnicode = false; +// for ( $pos = 0; $pos < $length; $pos++ ) { +// // Shift the queues down +// $x2 = $x1; +// $x1 = $x0; +// $d2 = $d1; +// $d1 = $d0; +// $ord2 = $ord1; +// $ord1 = $ord0; +// $r2 = $r1; +// $r1 = $r0; +// // Load the current input token and decoded values +// $inChar = $byteClass[$pos]; +// if ( $inChar == '\\' ) { +// if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) { +// $x0 = $inChar . $m[0]; +// $d0 = chr( hexdec( $m[1] ) ); +// $pos += strlen( $m[0] ); +// } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) { +// $x0 = $inChar . $m[0]; +// $d0 = chr( octdec( $m[0] ) ); +// $pos += strlen( $m[0] ); +// } elseif ( $pos + 1 >= $length ) { +// $x0 = $d0 = '\\'; +// } else { +// $d0 = $byteClass[$pos + 1]; +// $x0 = $inChar . $d0; +// $pos += 1; +// } // } else { -// return $text; -// } -// } - - /** - * Secure and split - main initialisation function for this Object - * - * Assumes that mDbkeyform has been set, and is urldecoded - * and uses underscores, but not otherwise munged. This function - * removes illegal characters, splits off the interwiki and - * namespace prefixes, sets the other forms, and canonicalizes - * everything. - * - * @throws XomwMalformedTitleException On invalid titles - * @return boolean True on success - */ - private boolean secureAndSplit(XomwEnv env) { - // Initialisation - this.mInterwiki = Bry_.Empty; - this.mFragment = Bry_.Empty; - this.mNamespace = this.mDefaultNamespace; // Usually NS_MAIN - - byte[] dbkey = this.mDbkeyform; - - // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share - // the parsing code with Title, while avoiding massive refactoring. - // @todo: get rid of secureAndSplit, refactor parsing code. - // @note: getTitleParser() returns a TitleParser implementation which does not have a - // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does - XomwMediaWikiTitleCodec titleCodec = env.MediaWikiServices().getTitleParser(); - // XomwMalformedTitleException can be thrown here - XomwMediaWikiTitleCodecParts parts = titleCodec.splitTitleString(dbkey, this.getDefaultNamespace()); - - // Fill fields -// this.setFragment('#' . parts.fragment); - this.mInterwiki = parts.interwiki; - this.mLocalInterwiki = parts.local_interwiki; - this.mNamespace = parts.ns; - this.mUserCaseDBKey = parts.user_case_dbkey; - - this.mDbkeyform = parts.dbkey; - this.mUrlform = XomwGlobalFunctions.wfUrlencode(this.mDbkeyform); - this.mTextform = XophpString_.strtr(this.mDbkeyform, Byte_ascii.Underline, Byte_ascii.Space); - - // We already know that some pages won't be in the database! - if (this.isExternal() || this.mNamespace == XomwDefines.NS_SPECIAL) { - this.mArticleID = 0; - } - - return true; - } - -// /** -// * Get an array of Title objects linking to this Title -// * Also stores the IDs in the link cache. -// * -// * WARNING: do not use this function on arbitrary user-supplied titles! -// * On heavily-used templates it will max out the memory. -// * -// * @param array $options May be FOR UPDATE -// * @param String $table Table name -// * @param String $prefix Fields prefix -// * @return Title[] Array of Title objects linking here -// */ -// public function getLinksTo($options = [], $table = 'pagelinks', $prefix = 'pl') { -// if (count($options) > 0) { -// $db = wfGetDB(DB_MASTER); +// $x0 = $d0 = $inChar; +// } +// $ord0 = ord( $d0 ); +// // Load the current re-encoded value +// if ( $ord0 < 32 || $ord0 == 0x7f ) { +// $r0 = sprintf( '\x%02x', $ord0 ); +// } elseif ( $ord0 >= 0x80 ) { +// // Allow unicode if a single high-bit character appears +// $r0 = sprintf( '\x%02x', $ord0 ); +// $allowUnicode = true; +// // @phan-suppress-next-line PhanParamSuspiciousOrder false positive +// } elseif ( strpos( '-\\[]^', $d0 ) !== false ) { +// $r0 = '\\' . $d0; // } else { -// $db = wfGetDB(DB_REPLICA); -// } -// -// $res = $db.select( -// [ 'page', $table ], -// self::getSelectFields(), -// [ -// "{$prefix}_from=page_id", -// "{$prefix}_namespace" => this.getNamespace(), -// "{$prefix}_title" => this.getDBkey() ], -// __METHOD__, -// $options -// ); -// -// $retVal = []; -// if ($res.numRows()) { -// $linkCache = LinkCache::singleton(); -// foreach ($res as $row) { -// $titleObj = Title::makeTitle($row.page_namespace, $row.page_title); -// if ($titleObj) { -// $linkCache.addGoodLinkObjFromRow($titleObj, $row); -// $retVal[] = $titleObj; +// $r0 = $d0; +// } +// // Do the output +// if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) { +// // Range +// if ( $ord2 > $ord0 ) { +// // Empty range +// } elseif ( $ord0 >= 0x80 ) { +// // Unicode range +// $allowUnicode = true; +// if ( $ord2 < 0x80 ) { +// // Keep the non-unicode section of the range +// $out .= "$r2-\\x7F"; // } -// } -// } -// return $retVal; -// } -// -// /** -// * Get an array of Title objects using this Title as a template -// * Also stores the IDs in the link cache. -// * -// * WARNING: do not use this function on arbitrary user-supplied titles! -// * On heavily-used templates it will max out the memory. -// * -// * @param array $options Query option to Database::select() -// * @return Title[] Array of Title the Title objects linking here -// */ -// public function getTemplateLinksTo($options = []) { -// return this.getLinksTo($options, 'templatelinks', 'tl'); -// } -// -// /** -// * Get an array of Title objects linked from this Title -// * Also stores the IDs in the link cache. -// * -// * WARNING: do not use this function on arbitrary user-supplied titles! -// * On heavily-used templates it will max out the memory. -// * -// * @param array $options Query option to Database::select() -// * @param String $table Table name -// * @param String $prefix Fields prefix -// * @return array Array of Title objects linking here -// */ -// public function getLinksFrom($options = [], $table = 'pagelinks', $prefix = 'pl') { -// $id = this.getArticleID(); -// -// // If the page doesn't exist; there can't be any link from this page -// if (!$id) { -// return []; -// } -// -// $db = wfGetDB(DB_REPLICA); -// -// $blNamespace = "{$prefix}_namespace"; -// $blTitle = "{$prefix}_title"; -// -// $res = $db.select( -// [ $table, 'page' ], -// array_merge( -// [ $blNamespace, $blTitle ], -// WikiPage::selectFields() -// ), -// [ "{$prefix}_from" => $id ], -// __METHOD__, -// $options, -// [ 'page' => [ -// 'LEFT JOIN', -// [ "page_namespace=$blNamespace", "page_title=$blTitle" ] -// ] ] -// ); -// -// $retVal = []; -// $linkCache = LinkCache::singleton(); -// foreach ($res as $row) { -// if ($row.page_id) { -// $titleObj = Title::newFromRow($row); // } else { -// $titleObj = Title::makeTitle($row.$blNamespace, $row.$blTitle); -// $linkCache.addBadLinkObj($titleObj); +// // Normal range +// $out .= "$r2-$r0"; // } -// $retVal[] = $titleObj; -// } -// -// return $retVal; -// } -// -// /** -// * Get an array of Title objects used on this Title as a template -// * Also stores the IDs in the link cache. -// * -// * WARNING: do not use this function on arbitrary user-supplied titles! -// * On heavily-used templates it will max out the memory. -// * -// * @param array $options May be FOR UPDATE -// * @return Title[] Array of Title the Title objects used here -// */ -// public function getTemplateLinksFrom($options = []) { -// return this.getLinksFrom($options, 'templatelinks', 'tl'); +// // Reset state to the initial value +// $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = ''; +// } elseif ( $ord2 < 0x80 ) { +// // ASCII character +// $out .= $r2; +// } +// } +// if ( $ord1 < 0x80 ) { +// $out .= $r1; +// } +// if ( $ord0 < 0x80 ) { +// $out .= $r0; +// } +// if ( $allowUnicode ) { +// $out .= '\u0080-\uFFFF'; +// } +// return $out; +// } +// +// /** +// * Make a prefixed DB key from a DB key and a namespace index +// * +// * @param int $ns Numerical representation of the namespace +// * @param string $title The DB key form the title +// * @param string $fragment The link fragment (after the "#") +// * @param string $interwiki The interwiki prefix +// * @param bool $canonicalNamespace If true, use the canonical name for +// * $ns instead of the localized version. +// * @return string The prefixed form of the title +// */ +// public static function makeName( $ns, $title, $fragment = '', $interwiki = '', +// $canonicalNamespace = false +// ) { +// if ( $canonicalNamespace ) { +// $namespace = MWNamespace::getCanonicalName( $ns ); +// } else { +// $namespace = MediaWikiServices::getInstance()->getContentLanguage()->getNsText( $ns ); +// } +// $name = $namespace == '' ? $title : "$namespace:$title"; +// if ( strval( $interwiki ) != '' ) { +// $name = "$interwiki:$name"; +// } +// if ( strval( $fragment ) != '' ) { +// $name .= '#' . $fragment; +// } +// return $name; +// } +// +// /** +// * Callback for usort() to do title sorts by (namespace, title) +// * +// * @param LinkTarget $a +// * @param LinkTarget $b +// * +// * @return int Result of string comparison, or namespace comparison +// */ +// public static function compare( LinkTarget $a, LinkTarget $b ) { +// return $a->getNamespace() <=> $b->getNamespace() +// ?: strcmp( $a->getText(), $b->getText() ); +// } +// +// /** +// * Returns true if the title is valid, false if it is invalid. +// * +// * Valid titles can be round-tripped via makeTitleSafe() and newFromText(). +// * Invalid titles may get returned from makeTitle(), and it may be useful to +// * allow them to exist, e.g. in order to process log entries about pages in +// * namespaces that belong to extensions that are no longer installed. +// * +// * @note This method is relatively expensive. When constructing Title +// * objects that need to be valid, use an instantiator method that is guaranteed +// * to return valid titles, such as makeTitleSafe() or newFromText(). +// * +// * @return bool +// */ +// public function isValid() { +// if ( !MWNamespace::exists( $this->mNamespace ) ) { +// return false; // } // -// /** -// * Get an array of Title objects referring to non-existent articles linked -// * from this page. -// * -// * @todo check if needed (used only in SpecialBrokenRedirects.php, and -// * should use redirect table in this case). -// * @return Title[] Array of Title the Title objects -// */ -// public function getBrokenLinksFrom() { -// if (this.getArticleID() == 0) { -// // All links from article ID 0 are false positives -// return []; -// } -// -// $dbr = wfGetDB(DB_REPLICA); -// $res = $dbr.select( -// [ 'page', 'pagelinks' ], -// [ 'pl_namespace', 'pl_title' ], -// [ -// 'pl_from' => this.getArticleID(), -// 'page_namespace IS NULL' -// ], -// __METHOD__, [], -// [ -// 'page' => [ -// 'LEFT JOIN', -// [ 'pl_namespace=page_namespace', 'pl_title=page_title' ] -// ] -// ] -// ); -// -// $retVal = []; -// foreach ($res as $row) { -// $retVal[] = Title::makeTitle($row.pl_namespace, $row.pl_title); -// } -// return $retVal; +// try { +// $parser = MediaWikiServices::getInstance()->getTitleParser(); +// $parser->parseTitle( $this->mDbkeyform, $this->mNamespace ); +// return true; +// } catch ( MalformedTitleException $ex ) { +// return false; // } -// -// /** -// * Get a list of URLs to purge from the CDN cache when this -// * page changes -// * -// * @return String[] Array of String the URLs -// */ -// public function getCdnUrls() { -// $urls = [ -// this.getInternalURL(), -// this.getInternalURL('action=history') -// ]; -// -// $pageLang = this.getPageLanguage(); -// if ($pageLang.hasVariants()) { -// $variants = $pageLang.getVariants(); -// foreach ($variants as $vCode) { -// $urls[] = this.getInternalURL($vCode); -// } -// } -// -// // If we are looking at a css/js user subpage, purge the action=raw. -// if (this.isJsSubpage()) { -// $urls[] = this.getInternalURL('action=raw&ctype=text/javascript'); -// } elseif (this.isCssSubpage()) { -// $urls[] = this.getInternalURL('action=raw&ctype=text/css'); -// } -// -// Hooks::run('TitleSquidURLs', [ this, &$urls ]); -// return $urls; +// } +// +// /** +// * Determine whether the object refers to a page within +// * this project (either this wiki or a wiki with a local +// * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local ) +// * +// * @return bool True if this is an in-project interwiki link or a wikilink, false otherwise +// */ +// public function isLocal() { +// if ( $this->isExternal() ) { +// $iw = self::getInterwikiLookup()->fetch( $this->mInterwiki ); +// if ( $iw ) { +// return $iw->isLocal(); +// } +// } +// return true; +// } +// +// /** +// * Is this Title interwiki? +// * +// * @return bool +// */ +// public function isExternal() { +// return $this->mInterwiki !== ''; +// } +// +// /** +// * Get the interwiki prefix +// * +// * Use Title::isExternal to check if a interwiki is set +// * +// * @return string Interwiki prefix +// */ +// public function getInterwiki() { +// return $this->mInterwiki; +// } +// +// /** +// * Was this a local interwiki link? +// * +// * @return bool +// */ +// public function wasLocalInterwiki() { +// return $this->mLocalInterwiki; +// } +// +// /** +// * Determine whether the object refers to a page within +// * this project and is transcludable. +// * +// * @return bool True if this is transcludable +// */ +// public function isTrans() { +// if ( !$this->isExternal() ) { +// return false; // } // -// /** -// * @deprecated since 1.27 use getCdnUrls() -// */ -// public function getSquidURLs() { -// return this.getCdnUrls(); -// } +// return self::getInterwikiLookup()->fetch( $this->mInterwiki )->isTranscludable(); +// } // -// /** -// * Purge all applicable CDN URLs -// */ -// public function purgeSquid() { -// DeferredUpdates::addUpdate( -// new CdnCacheUpdate(this.getCdnUrls()), -// DeferredUpdates::PRESEND -// ); +// /** +// * Returns the DB name of the distant wiki which owns the object. +// * +// * @return string|false The DB name +// */ +// public function getTransWikiID() { +// if ( !$this->isExternal() ) { +// return false; // } // -// /** -// * Check whether a given move operation would be valid. -// * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise -// * -// * @deprecated since 1.25, use MovePage's methods instead -// * @param Title $nt The new title -// * @param boolean $auth Whether to check user permissions (uses $wgUser) -// * @param String $reason Is the log summary of the move, used for spam checking -// * @return array|boolean True on success, getUserPermissionsErrors()-like array on failure -// */ -// public function isValidMoveOperation(&$nt, $auth = true, $reason = Bry_.Empty) { -// global $wgUser; -// -// if (!($nt instanceof Title)) { -// // Normally we'd add this to $errors, but we'll get -// // lots of syntax errors if $nt is not an Object -// return [ [ 'badtitletext' ] ]; -// } -// -// $mp = new MovePage(this, $nt); -// $errors = $mp.isValidMove().getErrorsArray(); -// if ($auth) { -// $errors = wfMergeErrorArrays( -// $errors, -// $mp.checkPermissions($wgUser, $reason).getErrorsArray() +// return self::getInterwikiLookup()->fetch( $this->mInterwiki )->getWikiID(); +// } +// +// /** +// * Get a TitleValue object representing this Title. +// * +// * @note Not all valid Titles have a corresponding valid TitleValue +// * (e.g. TitleValues cannot represent page-local links that have a +// * fragment but no title text). +// * +// * @return TitleValue|null +// */ +// public function getTitleValue() { +// if ( $this->mTitleValue === null ) { +// try { +// $this->mTitleValue = new TitleValue( +// $this->mNamespace, +// $this->mDbkeyform, +// $this->mFragment, +// $this->mInterwiki // ); -// } -// -// return $errors ?: true; -// } -// -// /** -// * Check if the requested move target is a valid file move target -// * @todo move this to MovePage -// * @param Title $nt Target title -// * @return array List of errors -// */ -// protected function validateFileMoveOperation($nt) { -// global $wgUser; -// -// $errors = []; -// -// $destFile = wfLocalFile($nt); -// $destFile.load(File::READ_LATEST); -// if (!$wgUser.isAllowed('reupload-shared') -// && !$destFile.exists() && wfFindFile($nt) -// ) { -// $errors[] = [ 'file-exists-sharedrepo' ]; -// } -// -// return $errors; +// } catch ( InvalidArgumentException $ex ) { +// wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' . +// $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" ); +// } +// } +// +// return $this->mTitleValue; +// } +// +// /** +// * Get the text form (spaces not underscores) of the main part +// * +// * @return string Main part of the title +// */ +// public function getText() { +// return $this->mTextform; +// } +// +// /** +// * Get the URL-encoded form of the main part +// * +// * @return string Main part of the title, URL-encoded +// */ +// public function getPartialURL() { +// return $this->mUrlform; +// } +// +// /** +// * Get the main part with underscores +// * +// * @return string Main part of the title, with underscores +// */ +// public function getDBkey() { +// return $this->mDbkeyform; +// } +// +// /** +// * Get the DB key with the initial letter case as specified by the user +// * @deprecated since 1.33; please use Title::getDBKey() instead +// * +// * @return string DB key +// */ +// function getUserCaseDBKey() { +// if ( !is_null( $this->mUserCaseDBKey ) ) { +// return $this->mUserCaseDBKey; +// } else { +// // If created via makeTitle(), $this->mUserCaseDBKey is not set. +// return $this->mDbkeyform; +// } +// } +// +// /** +// * Get the namespace index, i.e. one of the NS_xxxx constants. +// * +// * @return int Namespace index +// */ +// public function getNamespace() { +// return $this->mNamespace; +// } +// +// /** +// * Get the page's content model id, see the CONTENT_MODEL_XXX constants. +// * +// * @todo Deprecate this in favor of SlotRecord::getModel() +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return string Content model id +// */ +// public function getContentModel( $flags = 0 ) { +// if ( !$this->mForcedContentModel +// && ( !$this->mContentModel || $flags === self::GAID_FOR_UPDATE ) +// && $this->getArticleID( $flags ) +// ) { +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->addLinkObj( $this ); # in case we already had an article ID +// $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' ); +// } +// +// if ( !$this->mContentModel ) { +// $this->mContentModel = ContentHandler::getDefaultModelFor( $this ); +// } +// +// return $this->mContentModel; +// } +// +// /** +// * Convenience method for checking a title's content model name +// * +// * @param string $id The content model ID (use the CONTENT_MODEL_XXX constants). +// * @return bool True if $this->getContentModel() == $id +// */ +// public function hasContentModel( $id ) { +// return $this->getContentModel() == $id; +// } +// +// /** +// * Set a proposed content model for the page for permissions +// * checking. This does not actually change the content model +// * of a title! +// * +// * Additionally, you should make sure you've checked +// * ContentHandler::canBeUsedOn() first. +// * +// * @since 1.28 +// * @param string $model CONTENT_MODEL_XXX constant +// */ +// public function setContentModel( $model ) { +// $this->mContentModel = $model; +// $this->mForcedContentModel = true; +// } +// +// /** +// * Get the namespace text +// * +// * @return string|false Namespace text +// */ +// public function getNsText() { +// if ( $this->isExternal() ) { +// // This probably shouldn't even happen, except for interwiki transclusion. +// // If possible, use the canonical name for the foreign namespace. +// $nsText = MWNamespace::getCanonicalName( $this->mNamespace ); +// if ( $nsText !== false ) { +// return $nsText; +// } +// } +// +// try { +// $formatter = self::getTitleFormatter(); +// return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform ); +// } catch ( InvalidArgumentException $ex ) { +// wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" ); +// return false; // } -// -// /** -// * Move a title to a new location -// * -// * @deprecated since 1.25, use the MovePage class instead -// * @param Title $nt The new title -// * @param boolean $auth Indicates whether $wgUser's permissions -// * should be checked -// * @param String $reason The reason for the move -// * @param boolean $createRedirect Whether to create a redirect from the old title to the new title. -// * Ignored if the user doesn't have the suppressredirect right. -// * @param array $changeTags Applied to the entry in the move log and redirect page revision -// * @return array|boolean True on success, getUserPermissionsErrors()-like array on failure -// */ -// public function moveTo(&$nt, $auth = true, $reason = Bry_.Empty, $createRedirect = true, -// array $changeTags = []) { -// -// global $wgUser; -// $err = this.isValidMoveOperation($nt, $auth, $reason); -// if (is_array($err)) { -// // Auto-block user's IP if the account was "hard" blocked -// $wgUser.spreadAnyEditBlock(); -// return $err; -// } -// // Check suppressredirect permission -// if ($auth && !$wgUser.isAllowed('suppressredirect')) { -// $createRedirect = true; -// } -// -// $mp = new MovePage(this, $nt); -// $status = $mp.move($wgUser, $reason, $createRedirect, $changeTags); -// if ($status.isOK()) { +// } +// +// /** +// * Get the namespace text of the subject (rather than talk) page +// * +// * @return string Namespace text +// */ +// public function getSubjectNsText() { +// return MediaWikiServices::getInstance()->getContentLanguage()-> +// getNsText( MWNamespace::getSubject( $this->mNamespace ) ); +// } +// +// /** +// * Get the namespace text of the talk page +// * +// * @return string Namespace text +// */ +// public function getTalkNsText() { +// return MediaWikiServices::getInstance()->getContentLanguage()-> +// getNsText( MWNamespace::getTalk( $this->mNamespace ) ); +// } +// +// /** +// * Can this title have a corresponding talk page? +// * +// * @see MWNamespace::hasTalkNamespace +// * @since 1.30 +// * +// * @return bool True if this title either is a talk page or can have a talk page associated. +// */ +// public function canHaveTalkPage() { +// return MWNamespace::hasTalkNamespace( $this->mNamespace ); +// } +// +// /** +// * Is this in a namespace that allows actual pages? +// * +// * @return bool +// */ +// public function canExist() { +// return $this->mNamespace >= NS_MAIN; +// } +// +// /** +// * Can this title be added to a user's watchlist? +// * +// * @return bool +// */ +// public function isWatchable() { +// return !$this->isExternal() && MWNamespace::isWatchable( $this->mNamespace ); +// } +// +// /** +// * Returns true if this is a special page. +// * +// * @return bool +// */ +// public function isSpecialPage() { +// return $this->mNamespace == NS_SPECIAL; +// } +// +// /** +// * Returns true if this title resolves to the named special page +// * +// * @param string $name The special page name +// * @return bool +// */ +// public function isSpecial( $name ) { +// if ( $this->isSpecialPage() ) { +// list( $thisName, /* $subpage */ ) = +// MediaWikiServices::getInstance()->getSpecialPageFactory()-> +// resolveAlias( $this->mDbkeyform ); +// if ( $name == $thisName ) { // return true; -// } else { -// return $status.getErrorsArray(); // } // } -// -// /** -// * Move this page's subpages to be subpages of $nt -// * -// * @param Title $nt Move target -// * @param boolean $auth Whether $wgUser's permissions should be checked -// * @param String $reason The reason for the move -// * @param boolean $createRedirect Whether to create redirects from the old subpages to -// * the new ones Ignored if the user doesn't have the 'suppressredirect' right -// * @param array $changeTags Applied to the entry in the move log and redirect page revision -// * @return array Array with old page titles as keys, and strings (new page titles) or -// * getUserPermissionsErrors()-like arrays (errors) as values, or a -// * getUserPermissionsErrors()-like error array with numeric indices if -// * no pages were moved -// */ -// public function moveSubpages($nt, $auth = true, $reason = Bry_.Empty, $createRedirect = true, -// array $changeTags = []) { -// -// global $wgMaximumMovedPages; -// // Check permissions -// if (!this.userCan('move-subpages')) { -// return [ -// [ 'cant-move-subpages' ], -// ]; -// } -// // Do the source and target namespaces support subpages? -// if (!XomwNamespace::hasSubpages(this.getNamespace())) { -// return [ -// [ 'namespace-nosubpages', XomwNamespace::getCanonicalName(this.getNamespace()) ], -// ]; -// } -// if (!XomwNamespace::hasSubpages($nt.getNamespace())) { -// return [ -// [ 'namespace-nosubpages', XomwNamespace::getCanonicalName($nt.getNamespace()) ], -// ]; -// } -// -// $subpages = this.getSubpages($wgMaximumMovedPages + 1); -// $retval = []; -// $count = 0; -// foreach ($subpages as $oldSubpage) { -// $count++; -// if ($count > $wgMaximumMovedPages) { -// $retval[$oldSubpage.getPrefixedText()] = [ -// [ 'movepage-max-pages', $wgMaximumMovedPages ], -// ]; -// break; -// } -// -// // We don't know whether this function was called before -// // or after moving the root page, so check both -// // this and $nt -// if ($oldSubpage.getArticleID() == this.getArticleID() -// || $oldSubpage.getArticleID() == $nt.getArticleID() -// ) { -// // When moving a page to a subpage of itself, -// // don't move it twice -// continue; -// } -// $newPageName = preg_replace( -// '#^' . preg_quote(this.getDBkey(), '#') . '#', -// StringUtils::escapeRegexReplacement($nt.getDBkey()), # bug 21234 -// $oldSubpage.getDBkey()); -// if ($oldSubpage.isTalkPage()) { -// $newNs = $nt.getTalkPage().getNamespace(); -// } else { -// $newNs = $nt.getSubjectPage().getNamespace(); -// } -// // Bug 14385: we need makeTitleSafe because the new page names may -// // be longer than 255 characters. -// $newSubpage = Title::makeTitleSafe($newNs, $newPageName); -// -// $success = $oldSubpage.moveTo($newSubpage, $auth, $reason, $createRedirect, $changeTags); -// if ($success == true) { -// $retval[$oldSubpage.getPrefixedText()] = $newSubpage.getPrefixedText(); -// } else { -// $retval[$oldSubpage.getPrefixedText()] = $success; +// return false; +// } +// +// /** +// * If the Title refers to a special page alias which is not the local default, resolve +// * the alias, and localise the name as necessary. Otherwise, return $this +// * +// * @return Title +// */ +// public function fixSpecialName() { +// if ( $this->isSpecialPage() ) { +// $spFactory = MediaWikiServices::getInstance()->getSpecialPageFactory(); +// list( $canonicalName, $par ) = $spFactory->resolveAlias( $this->mDbkeyform ); +// if ( $canonicalName ) { +// $localName = $spFactory->getLocalNameFor( $canonicalName, $par ); +// if ( $localName != $this->mDbkeyform ) { +// return self::makeTitle( NS_SPECIAL, $localName ); // } // } -// return $retval; // } -// -// /** -// * Checks if this page is just a one-rev redirect. -// * Adds synchronized, so don't use just for light purposes. -// * -// * @return boolean -// */ -// public function isSingleRevRedirect() { -// global $wgContentHandlerUseDB; -// -// $dbw = wfGetDB(DB_MASTER); -// -// // Is it a redirect? -// $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ]; -// if ($wgContentHandlerUseDB) { -// $fields[] = 'page_content_model'; -// } -// -// $row = $dbw.selectRow('page', -// $fields, -// this.pageCond(), -// __METHOD__, -// [ 'FOR UPDATE' ] -// ); -// // Cache some fields we may want -// this.mArticleID = $row ? intval($row.page_id) : 0; -// this.mRedirect = $row ? (boolean)$row.page_is_redirect : false; -// this.mLatestID = $row ? intval($row.page_latest) : false; -// this.mContentModel = $row && isset($row.page_content_model) -// ? strval($row.page_content_model) -// : false; -// -// if (!this.mRedirect) { -// return false; +// return $this; +// } +// +// /** +// * Returns true if the title is inside the specified namespace. +// * +// * Please make use of this instead of comparing to getNamespace() +// * This function is much more resistant to changes we may make +// * to namespaces than code that makes direct comparisons. +// * @param int $ns The namespace +// * @return bool +// * @since 1.19 +// */ +// public function inNamespace( $ns ) { +// return MWNamespace::equals( $this->mNamespace, $ns ); +// } +// +// /** +// * Returns true if the title is inside one of the specified namespaces. +// * +// * @param int|int[] $namespaces,... The namespaces to check for +// * @return bool +// * @since 1.19 +// */ +// public function inNamespaces( /* ... */ ) { +// $namespaces = func_get_args(); +// if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) { +// $namespaces = $namespaces[0]; +// } +// +// foreach ( $namespaces as $ns ) { +// if ( $this->inNamespace( $ns ) ) { +// return true; // } -// // Does the article have a history? -// $row = $dbw.selectField([ 'page', 'revision' ], -// 'rev_id', -// [ 'page_namespace' => this.getNamespace(), -// 'page_title' => this.getDBkey(), -// 'page_id=rev_page', -// 'page_latest != rev_id' -// ], -// __METHOD__, -// [ 'FOR UPDATE' ] -// ); -// // Return true if there was no history -// return ($row == false); // } // -// /** -// * Checks if this can be moved to a given Title -// * - Selects for update, so don't call it unless you mean business -// * -// * @deprecated since 1.25, use MovePage's methods instead -// * @param Title $nt The new title to check -// * @return boolean -// */ -// public function isValidMoveTarget($nt) { -// // Is it an existing file? -// if ($nt.getNamespace() == NS_FILE) { -// $file = wfLocalFile($nt); -// $file.load(File::READ_LATEST); -// if ($file.exists()) { -// wfDebug(__METHOD__ . ": file exists\n"); -// return false; -// } -// } -// // Is it a redirect with no history? -// if (!$nt.isSingleRevRedirect()) { -// wfDebug(__METHOD__ . ": not a one-rev redirect\n"); -// return false; -// } -// // Get the article text -// $rev = Revision::newFromTitle($nt, false, Revision::READ_LATEST); -// if (!is_object($rev)) { -// return false; -// } -// $content = $rev.getContent(); -// // Does the redirect point to the source? -// // Or is it a broken self-redirect, usually caused by namespace collisions? -// $redirTitle = $content ? $content.getRedirectTarget() : null; -// -// if ($redirTitle) { -// if ($redirTitle.getPrefixedDBkey() != this.getPrefixedDBkey() && -// $redirTitle.getPrefixedDBkey() != $nt.getPrefixedDBkey()) { -// wfDebug(__METHOD__ . ": redirect points to other page\n"); -// return false; -// } else { -// return true; -// } -// } else { -// // Fail safe (not a redirect after all. strange.) -// wfDebug(__METHOD__ . ": failsafe: database sais " . $nt.getPrefixedDBkey() . -// " is a redirect, but it doesn't contain a valid redirect.\n"); -// return false; -// } +// return false; +// } +// +// /** +// * Returns true if the title has the same subject namespace as the +// * namespace specified. +// * For example this method will take NS_USER and return true if namespace +// * is either NS_USER or NS_USER_TALK since both of them have NS_USER +// * as their subject namespace. +// * +// * This is MUCH simpler than individually testing for equivalence +// * against both NS_USER and NS_USER_TALK, and is also forward compatible. +// * @since 1.19 +// * @param int $ns +// * @return bool +// */ +// public function hasSubjectNamespace( $ns ) { +// return MWNamespace::subjectEquals( $this->mNamespace, $ns ); +// } +// +// /** +// * Is this Title in a namespace which contains content? +// * In other words, is this a content page, for the purposes of calculating +// * statistics, etc? +// * +// * @return bool +// */ +// public function isContentPage() { +// return MWNamespace::isContent( $this->mNamespace ); +// } +// +// /** +// * Would anybody with sufficient privileges be able to move this page? +// * Some pages just aren't movable. +// * +// * @return bool +// */ +// public function isMovable() { +// if ( !MWNamespace::isMovable( $this->mNamespace ) || $this->isExternal() ) { +// // Interwiki title or immovable namespace. Hooks don't get to override here +// return false; // } // -// /** -// * Get categories to which this Title belongs and return an array of -// * categories' names. -// * -// * @return array Array of parents in the form: -// * $parent => $currentarticle -// */ -// public function getParentCategories() { -// global $wgContLang; -// -// $data = []; -// -// $titleKey = this.getArticleID(); +// $result = true; +// Hooks::run( 'TitleIsMovable', [ $this, &$result ] ); +// return $result; +// } +// +// /** +// * Is this the mainpage? +// * @note Title::newFromText seems to be sufficiently optimized by the title +// * cache that we don't need to over-optimize by doing direct comparisons and +// * accidentally creating new bugs where $title->equals( Title::newFromText() ) +// * ends up reporting something differently than $title->isMainPage(); +// * +// * @since 1.18 +// * @return bool +// */ +// public function isMainPage() { +// return $this->equals( self::newMainPage() ); +// } +// +// /** +// * Is this a subpage? +// * +// * @return bool +// */ +// public function isSubpage() { +// return MWNamespace::hasSubpages( $this->mNamespace ) +// ? strpos( $this->getText(), '/' ) !== false +// : false; +// } +// +// /** +// * Is this a conversion table for the LanguageConverter? +// * +// * @return bool +// */ +// public function isConversionTable() { +// // @todo ConversionTable should become a separate content model. +// +// return $this->mNamespace == NS_MEDIAWIKI && +// strpos( $this->getText(), 'Conversiontable/' ) === 0; +// } +// +// /** +// * Does that page contain wikitext, or it is JS, CSS or whatever? +// * +// * @return bool +// */ +// public function isWikitextPage() { +// return $this->hasContentModel( CONTENT_MODEL_WIKITEXT ); +// } +// +// /** +// * Could this MediaWiki namespace page contain custom CSS, JSON, or JavaScript for the +// * global UI. This is generally true for pages in the MediaWiki namespace having +// * CONTENT_MODEL_CSS, CONTENT_MODEL_JSON, or CONTENT_MODEL_JAVASCRIPT. +// * +// * This method does *not* return true for per-user JS/JSON/CSS. Use isUserConfigPage() +// * for that! +// * +// * Note that this method should not return true for pages that contain and show +// * "inactive" CSS, JSON, or JS. +// * +// * @return bool +// * @since 1.31 +// */ +// public function isSiteConfigPage() { +// return ( +// $this->isSiteCssConfigPage() +// || $this->isSiteJsonConfigPage() +// || $this->isSiteJsConfigPage() +// ); +// } +// +// /** +// * Is this a "config" (.css, .json, or .js) sub-page of a user page? +// * +// * @return bool +// * @since 1.31 +// */ +// public function isUserConfigPage() { +// return ( +// $this->isUserCssConfigPage() +// || $this->isUserJsonConfigPage() +// || $this->isUserJsConfigPage() +// ); +// } +// +// /** +// * Trim down a .css, .json, or .js subpage title to get the corresponding skin name +// * +// * @return string Containing skin name from .css, .json, or .js subpage title +// * @since 1.31 +// */ +// public function getSkinFromConfigSubpage() { +// $subpage = explode( '/', $this->mTextform ); +// $subpage = $subpage[count( $subpage ) - 1]; +// $lastdot = strrpos( $subpage, '.' ); +// if ( $lastdot === false ) { +// return $subpage; # Never happens: only called for names ending in '.css'/'.json'/'.js' +// } +// return substr( $subpage, 0, $lastdot ); +// } +// +// /** +// * Is this a CSS "config" sub-page of a user page? +// * +// * @return bool +// * @since 1.31 +// */ +// public function isUserCssConfigPage() { +// return ( +// NS_USER == $this->mNamespace +// && $this->isSubpage() +// && $this->hasContentModel( CONTENT_MODEL_CSS ) +// ); +// } +// +// /** +// * Is this a JSON "config" sub-page of a user page? +// * +// * @return bool +// * @since 1.31 +// */ +// public function isUserJsonConfigPage() { +// return ( +// NS_USER == $this->mNamespace +// && $this->isSubpage() +// && $this->hasContentModel( CONTENT_MODEL_JSON ) +// ); +// } +// +// /** +// * Is this a JS "config" sub-page of a user page? +// * +// * @return bool +// * @since 1.31 +// */ +// public function isUserJsConfigPage() { +// return ( +// NS_USER == $this->mNamespace +// && $this->isSubpage() +// && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) +// ); +// } +// +// /** +// * Is this a sitewide CSS "config" page? +// * +// * @return bool +// * @since 1.32 +// */ +// public function isSiteCssConfigPage() { +// return ( +// NS_MEDIAWIKI == $this->mNamespace +// && ( +// $this->hasContentModel( CONTENT_MODEL_CSS ) +// // paranoia - a MediaWiki: namespace page with mismatching extension and content +// // model is probably by mistake and might get handled incorrectly (see e.g. T112937) +// || substr( $this->mDbkeyform, -4 ) === '.css' +// ) +// ); +// } +// +// /** +// * Is this a sitewide JSON "config" page? +// * +// * @return bool +// * @since 1.32 +// */ +// public function isSiteJsonConfigPage() { +// return ( +// NS_MEDIAWIKI == $this->mNamespace +// && ( +// $this->hasContentModel( CONTENT_MODEL_JSON ) +// // paranoia - a MediaWiki: namespace page with mismatching extension and content +// // model is probably by mistake and might get handled incorrectly (see e.g. T112937) +// || substr( $this->mDbkeyform, -5 ) === '.json' +// ) +// ); +// } +// +// /** +// * Is this a sitewide JS "config" page? +// * +// * @return bool +// * @since 1.31 +// */ +// public function isSiteJsConfigPage() { +// return ( +// NS_MEDIAWIKI == $this->mNamespace +// && ( +// $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) +// // paranoia - a MediaWiki: namespace page with mismatching extension and content +// // model is probably by mistake and might get handled incorrectly (see e.g. T112937) +// || substr( $this->mDbkeyform, -3 ) === '.js' +// ) +// ); +// } +// +// /** +// * Is this a message which can contain raw HTML? +// * +// * @return bool +// * @since 1.32 +// */ +// public function isRawHtmlMessage() { +// global $wgRawHtmlMessages; +// +// if ( !$this->inNamespace( NS_MEDIAWIKI ) ) { +// return false; +// } +// $message = lcfirst( $this->getRootTitle()->getDBkey() ); +// return in_array( $message, $wgRawHtmlMessages, true ); +// } +// +// /** +// * Is this a talk page of some sort? +// * +// * @return bool +// */ +// public function isTalkPage() { +// return MWNamespace::isTalk( $this->mNamespace ); +// } +// +// /** +// * Get a Title object associated with the talk page of this article +// * +// * @return Title The object for the talk page +// */ +// public function getTalkPage() { +// return self::makeTitle( MWNamespace::getTalk( $this->mNamespace ), $this->mDbkeyform ); +// } +// +// /** +// * Get a Title object associated with the talk page of this article, +// * if such a talk page can exist. +// * +// * @since 1.30 +// * +// * @return Title|null The object for the talk page, +// * or null if no associated talk page can exist, according to canHaveTalkPage(). +// */ +// public function getTalkPageIfDefined() { +// if ( !$this->canHaveTalkPage() ) { +// return null; +// } // -// if ($titleKey == 0) { -// return $data; -// } +// return $this->getTalkPage(); +// } +// +// /** +// * Get a title object associated with the subject page of this +// * talk page +// * +// * @return Title The object for the subject page +// */ +// public function getSubjectPage() { +// // Is this the same title? +// $subjectNS = MWNamespace::getSubject( $this->mNamespace ); +// if ( $this->mNamespace == $subjectNS ) { +// return $this; +// } +// return self::makeTitle( $subjectNS, $this->mDbkeyform ); +// } +// +// /** +// * Get the other title for this page, if this is a subject page +// * get the talk page, if it is a subject page get the talk page +// * +// * @since 1.25 +// * @throws MWException If the page doesn't have an other page +// * @return Title +// */ +// public function getOtherPage() { +// if ( $this->isSpecialPage() ) { +// throw new MWException( 'Special pages cannot have other pages' ); +// } +// if ( $this->isTalkPage() ) { +// return $this->getSubjectPage(); +// } else { +// if ( !$this->canHaveTalkPage() ) { +// throw new MWException( "{$this->getPrefixedText()} does not have an other page" ); +// } +// return $this->getTalkPage(); +// } +// } +// +// /** +// * Get the default namespace index, for when there is no namespace +// * +// * @return int Default namespace index +// */ +// public function getDefaultNamespace() { +// return $this->mDefaultNamespace; +// } +// +// /** +// * Get the Title fragment (i.e.\ the bit after the #) in text form +// * +// * Use Title::hasFragment to check for a fragment +// * +// * @return string Title fragment +// */ +// public function getFragment() { +// return $this->mFragment; +// } +// +// /** +// * Check if a Title fragment is set +// * +// * @return bool +// * @since 1.23 +// */ +// public function hasFragment() { +// return $this->mFragment !== ''; +// } +// +// /** +// * Get the fragment in URL form, including the "#" character if there is one +// * +// * @return string Fragment in URL form +// */ +// public function getFragmentForURL() { +// if ( !$this->hasFragment() ) { +// return ''; +// } elseif ( $this->isExternal() ) { +// // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki, +// // so we treat it like a local interwiki. +// $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki ); +// if ( $interwiki && !$interwiki->isLocal() ) { +// return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment ); +// } +// } +// +// return '#' . Sanitizer::escapeIdForLink( $this->mFragment ); +// } +// +// /** +// * Set the fragment for this title. Removes the first character from the +// * specified fragment before setting, so it assumes you're passing it with +// * an initial "#". +// * +// * Deprecated for public use, use Title::makeTitle() with fragment parameter, +// * or Title::createFragmentTarget(). +// * Still in active use privately. +// * +// * @private +// * @param string $fragment Text +// */ +// public function setFragment( $fragment ) { +// $this->mFragment = strtr( substr( $fragment, 1 ), '_', ' ' ); +// } +// +// /** +// * Creates a new Title for a different fragment of the same page. +// * +// * @since 1.27 +// * @param string $fragment +// * @return Title +// */ +// public function createFragmentTarget( $fragment ) { +// return self::makeTitle( +// $this->mNamespace, +// $this->getText(), +// $fragment, +// $this->mInterwiki +// ); +// } +// +// /** +// * Prefix some arbitrary text with the namespace or interwiki prefix +// * of this object +// * +// * @param string $name The text +// * @return string The prefixed text +// */ +// private function prefix( $name ) { +// $p = ''; +// if ( $this->isExternal() ) { +// $p = $this->mInterwiki . ':'; +// } +// +// if ( $this->mNamespace != 0 ) { +// $nsText = $this->getNsText(); +// +// if ( $nsText === false ) { +// // See T165149. Awkward, but better than erroneously linking to the main namespace. +// $nsText = MediaWikiServices::getInstance()->getContentLanguage()-> +// getNsText( NS_SPECIAL ) . ":Badtitle/NS{$this->mNamespace}"; +// } +// +// $p .= $nsText . ':'; +// } +// return $p . $name; +// } +// +// /** +// * Get the prefixed database key form +// * +// * @return string The prefixed title, with underscores and +// * any interwiki and namespace prefixes +// */ +// public function getPrefixedDBkey() { +// $s = $this->prefix( $this->mDbkeyform ); +// $s = strtr( $s, ' ', '_' ); +// return $s; +// } +// +// /** +// * Get the prefixed title with spaces. +// * This is the form usually used for display +// * +// * @return string The prefixed title, with spaces +// */ +// public function getPrefixedText() { +// if ( $this->prefixedText === null ) { +// $s = $this->prefix( $this->mTextform ); +// $s = strtr( $s, '_', ' ' ); +// $this->prefixedText = $s; +// } +// return $this->prefixedText; +// } +// +// /** +// * Return a string representation of this title +// * +// * @return string Representation of this title +// */ +// public function __toString() { +// return $this->getPrefixedText(); +// } +// +// /** +// * Get the prefixed title with spaces, plus any fragment +// * (part beginning with '#') +// * +// * @return string The prefixed title, with spaces and the fragment, including '#' +// */ +// public function getFullText() { +// $text = $this->getPrefixedText(); +// if ( $this->hasFragment() ) { +// $text .= '#' . $this->mFragment; +// } +// return $text; +// } +// +// /** +// * Get the root page name text without a namespace, i.e. the leftmost part before any slashes +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz')->getRootText(); +// * # returns: 'Foo' +// * @endcode +// * +// * @return string Root name +// * @since 1.20 +// */ +// public function getRootText() { +// if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { +// return $this->getText(); +// } +// +// return strtok( $this->getText(), '/' ); +// } +// +// /** +// * Get the root page name title, i.e. the leftmost part before any slashes +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz')->getRootTitle(); +// * # returns: Title{User:Foo} +// * @endcode +// * +// * @return Title Root title +// * @since 1.20 +// */ +// public function getRootTitle() { +// return self::makeTitle( $this->mNamespace, $this->getRootText() ); +// } +// +// /** +// * Get the base page name without a namespace, i.e. the part before the subpage name +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz')->getBaseText(); +// * # returns: 'Foo/Bar' +// * @endcode +// * +// * @return string Base name +// */ +// public function getBaseText() { +// $text = $this->getText(); +// if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { +// return $text; +// } // -// $dbr = wfGetDB(DB_REPLICA); +// $lastSlashPos = strrpos( $text, '/' ); +// // Don't discard the real title if there's no subpage involved +// if ( $lastSlashPos === false ) { +// return $text; +// } // -// $res = $dbr.select( -// 'categorylinks', -// 'cl_to', -// [ 'cl_from' => $titleKey ], -// __METHOD__ +// return substr( $text, 0, $lastSlashPos ); +// } +// +// /** +// * Get the base page name title, i.e. the part before the subpage name +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz')->getBaseTitle(); +// * # returns: Title{User:Foo/Bar} +// * @endcode +// * +// * @return Title Base title +// * @since 1.20 +// */ +// public function getBaseTitle() { +// return self::makeTitle( $this->mNamespace, $this->getBaseText() ); +// } +// +// /** +// * Get the lowest-level subpage name, i.e. the rightmost part after any slashes +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz')->getSubpageText(); +// * # returns: "Baz" +// * @endcode +// * +// * @return string Subpage name +// */ +// public function getSubpageText() { +// if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { +// return $this->mTextform; +// } +// $parts = explode( '/', $this->mTextform ); +// return $parts[count( $parts ) - 1]; +// } +// +// /** +// * Get the title for a subpage of the current page +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz')->getSubpage("Asdf"); +// * # returns: Title{User:Foo/Bar/Baz/Asdf} +// * @endcode +// * +// * @param string $text The subpage name to add to the title +// * @return Title|null Subpage title, or null on an error +// * @since 1.20 +// */ +// public function getSubpage( $text ) { +// return self::makeTitleSafe( $this->mNamespace, $this->getText() . '/' . $text ); +// } +// +// /** +// * Get a URL-encoded form of the subpage text +// * +// * @return string URL-encoded subpage name +// */ +// public function getSubpageUrlForm() { +// $text = $this->getSubpageText(); +// $text = wfUrlencode( strtr( $text, ' ', '_' ) ); +// return $text; +// } +// +// /** +// * Get a URL-encoded title (not an actual URL) including interwiki +// * +// * @return string The URL-encoded form +// */ +// public function getPrefixedURL() { +// $s = $this->prefix( $this->mDbkeyform ); +// $s = wfUrlencode( strtr( $s, ' ', '_' ) ); +// return $s; +// } +// +// /** +// * Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args +// * get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional +// * second argument named variant. This was deprecated in favor +// * of passing an array of option with a "variant" key +// * Once $query2 is removed for good, this helper can be dropped +// * and the wfArrayToCgi moved to getLocalURL(); +// * +// * @since 1.19 (r105919) +// * @param array|string $query +// * @param string|string[]|bool $query2 +// * @return string +// */ +// private static function fixUrlQueryArgs( $query, $query2 = false ) { +// if ( $query2 !== false ) { +// wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " . +// "method called with a second parameter is deprecated. Add your " . +// "parameter to an array passed as the first parameter.", "1.19" ); +// } +// if ( is_array( $query ) ) { +// $query = wfArrayToCgi( $query ); +// } +// if ( $query2 ) { +// if ( is_string( $query2 ) ) { +// // $query2 is a string, we will consider this to be +// // a deprecated $variant argument and add it to the query +// $query2 = wfArrayToCgi( [ 'variant' => $query2 ] ); +// } else { +// $query2 = wfArrayToCgi( $query2 ); +// } +// // If we have $query content add a & to it first +// if ( $query ) { +// $query .= '&'; +// } +// // Now append the queries together +// $query .= $query2; +// } +// return $query; +// } +// +// /** +// * Get a real URL referring to this title, with interwiki link and +// * fragment +// * +// * @see self::getLocalURL for the arguments. +// * @see wfExpandUrl +// * @param string|string[] $query +// * @param string|string[]|bool $query2 +// * @param string|int|null $proto Protocol type to use in URL +// * @return string The URL +// */ +// public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) { +// $query = self::fixUrlQueryArgs( $query, $query2 ); +// +// # Hand off all the decisions on urls to getLocalURL +// $url = $this->getLocalURL( $query ); +// +// # Expand the url to make it a full url. Note that getLocalURL has the +// # potential to output full urls for a variety of reasons, so we use +// # wfExpandUrl instead of simply prepending $wgServer +// $url = wfExpandUrl( $url, $proto ); +// +// # Finally, add the fragment. +// $url .= $this->getFragmentForURL(); +// // Avoid PHP 7.1 warning from passing $this by reference +// $titleRef = $this; +// Hooks::run( 'GetFullURL', [ &$titleRef, &$url, $query ] ); +// return $url; +// } +// +// /** +// * Get a url appropriate for making redirects based on an untrusted url arg +// * +// * This is basically the same as getFullUrl(), but in the case of external +// * interwikis, we send the user to a landing page, to prevent possible +// * phishing attacks and the like. +// * +// * @note Uses current protocol by default, since technically relative urls +// * aren't allowed in redirects per HTTP spec, so this is not suitable for +// * places where the url gets cached, as might pollute between +// * https and non-https users. +// * @see self::getLocalURL for the arguments. +// * @param array|string $query +// * @param string $proto Protocol type to use in URL +// * @return string A url suitable to use in an HTTP location header. +// */ +// public function getFullUrlForRedirect( $query = '', $proto = PROTO_CURRENT ) { +// $target = $this; +// if ( $this->isExternal() ) { +// $target = SpecialPage::getTitleFor( +// 'GoToInterwiki', +// $this->getPrefixedDBkey() // ); -// -// if ($res.numRows() > 0) { -// foreach ($res as $row) { -// // $data[] = Title::newFromText($wgContLang.getNsText (NS_CATEGORY).':'.$row.cl_to); -// $data[$wgContLang.getNsText(NS_CATEGORY) . ':' . $row.cl_to] = this.getFullText(); -// } -// } -// return $data; // } +// return $target->getFullURL( $query, false, $proto ); +// } +// +// /** +// * Get a URL with no fragment or server name (relative URL) from a Title object. +// * If this page is generated with action=render, however, +// * $wgServer is prepended to make an absolute URL. +// * +// * @see self::getFullURL to always get an absolute URL. +// * @see self::getLinkURL to always get a URL that's the simplest URL that will be +// * valid to link, locally, to the current Title. +// * @see self::newFromText to produce a Title object. +// * +// * @param string|string[] $query An optional query string, +// * not used for interwiki links. Can be specified as an associative array as well, +// * e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped). +// * Some query patterns will trigger various shorturl path replacements. +// * @param string|string[]|bool $query2 An optional secondary query array. This one MUST +// * be an array. If a string is passed it will be interpreted as a deprecated +// * variant argument and urlencoded into a variant= argument. +// * This second query argument will be added to the $query +// * The second parameter is deprecated since 1.19. Pass it as a key,value +// * pair in the first parameter array instead. +// * +// * @return string String of the URL. +// */ +// public function getLocalURL( $query = '', $query2 = false ) { +// global $wgArticlePath, $wgScript, $wgServer, $wgRequest; +// +// $query = self::fixUrlQueryArgs( $query, $query2 ); +// +// $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki ); +// if ( $interwiki ) { +// $namespace = $this->getNsText(); +// if ( $namespace != '' ) { +// # Can this actually happen? Interwikis shouldn't be parsed. +// # Yes! It can in interwiki transclusion. But... it probably shouldn't. +// $namespace .= ':'; +// } +// $url = $interwiki->getURL( $namespace . $this->mDbkeyform ); +// $url = wfAppendQuery( $url, $query ); +// } else { +// $dbkey = wfUrlencode( $this->getPrefixedDBkey() ); +// if ( $query == '' ) { +// $url = str_replace( '$1', $dbkey, $wgArticlePath ); +// // Avoid PHP 7.1 warning from passing $this by reference +// $titleRef = $this; +// Hooks::run( 'GetLocalURL::Article', [ &$titleRef, &$url ] ); +// } else { +// global $wgVariantArticlePath, $wgActionPaths; +// $url = false; +// $matches = []; // -// /** -// * Get a tree of parent categories -// * -// * @param array $children Array with the children in the keys, to check for circular refs -// * @return array Tree of parent categories -// */ -// public function getParentCategoryTree($children = []) { -// $stack = []; -// $parents = this.getParentCategories(); -// -// if ($parents) { -// foreach ($parents as $parent => $current) { -// if (array_key_exists($parent, $children)) { -// // Circular reference -// $stack[$parent] = []; -// } else { -// $nt = Title::newFromText($parent); -// if ($nt) { -// $stack[$parent] = $nt.getParentCategoryTree($children + [ $parent => 1 ]); +// if ( !empty( $wgActionPaths ) +// && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) +// ) { +// $action = urldecode( $matches[2] ); +// if ( isset( $wgActionPaths[$action] ) ) { +// $query = $matches[1]; +// if ( isset( $matches[4] ) ) { +// $query .= $matches[4]; +// } +// $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] ); +// if ( $query != '' ) { +// $url = wfAppendQuery( $url, $query ); // } // } // } -// } // -// return $stack; -// } +// if ( $url === false +// && $wgVariantArticlePath +// && preg_match( '/^variant=([^&]*)$/', $query, $matches ) +// && $this->getPageLanguage()->equals( +// MediaWikiServices::getInstance()->getContentLanguage() ) +// && $this->getPageLanguage()->hasVariants() +// ) { +// $variant = urldecode( $matches[1] ); +// if ( $this->getPageLanguage()->hasVariant( $variant ) ) { +// // Only do the variant replacement if the given variant is a valid +// // variant for the page's language. +// $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath ); +// $url = str_replace( '$1', $dbkey, $url ); +// } +// } // -// /** -// * Get an associative array for selecting this title from -// * the "page" table -// * -// * @return array Array suitable for the $where parameter of DB::select() -// */ -// public function pageCond() { -// if (this.mArticleID > 0) { -// // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs -// return [ 'page_id' => this.mArticleID ]; -// } else { -// return [ 'page_namespace' => this.mNamespace, 'page_title' => this.mDbkeyform ]; +// if ( $url === false ) { +// if ( $query == '-' ) { +// $query = ''; +// } +// $url = "{$wgScript}?title={$dbkey}&{$query}"; +// } // } +// // Avoid PHP 7.1 warning from passing $this by reference +// $titleRef = $this; +// Hooks::run( 'GetLocalURL::Internal', [ &$titleRef, &$url, $query ] ); +// +// // @todo FIXME: This causes breakage in various places when we +// // actually expected a local URL and end up with dupe prefixes. +// if ( $wgRequest->getVal( 'action' ) == 'render' ) { +// $url = $wgServer . $url; +// } +// } +// // Avoid PHP 7.1 warning from passing $this by reference +// $titleRef = $this; +// Hooks::run( 'GetLocalURL', [ &$titleRef, &$url, $query ] ); +// return $url; +// } +// +// /** +// * Get a URL that's the simplest URL that will be valid to link, locally, +// * to the current Title. It includes the fragment, but does not include +// * the server unless action=render is used (or the link is external). If +// * there's a fragment but the prefixed text is empty, we just return a link +// * to the fragment. +// * +// * The result obviously should not be URL-escaped, but does need to be +// * HTML-escaped if it's being output in HTML. +// * +// * @param string|string[] $query +// * @param bool $query2 +// * @param string|int|bool $proto A PROTO_* constant on how the URL should be expanded, +// * or false (default) for no expansion +// * @see self::getLocalURL for the arguments. +// * @return string The URL +// */ +// public function getLinkURL( $query = '', $query2 = false, $proto = false ) { +// if ( $this->isExternal() || $proto !== false ) { +// $ret = $this->getFullURL( $query, $query2, $proto ); +// } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) { +// $ret = $this->getFragmentForURL(); +// } else { +// $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL(); +// } +// return $ret; +// } +// +// /** +// * Get the URL form for an internal link. +// * - Used in various CDN-related code, in case we have a different +// * internal hostname for the server from the exposed one. +// * +// * This uses $wgInternalServer to qualify the path, or $wgServer +// * if $wgInternalServer is not set. If the server variable used is +// * protocol-relative, the URL will be expanded to http:// +// * +// * @see self::getLocalURL for the arguments. +// * @param string|string[] $query +// * @param string|bool $query2 Deprecated +// * @return string The URL +// */ +// public function getInternalURL( $query = '', $query2 = false ) { +// global $wgInternalServer, $wgServer; +// $query = self::fixUrlQueryArgs( $query, $query2 ); +// $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer; +// $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP ); +// // Avoid PHP 7.1 warning from passing $this by reference +// $titleRef = $this; +// Hooks::run( 'GetInternalURL', [ &$titleRef, &$url, $query ] ); +// return $url; +// } +// +// /** +// * Get the URL for a canonical link, for use in things like IRC and +// * e-mail notifications. Uses $wgCanonicalServer and the +// * GetCanonicalURL hook. +// * +// * NOTE: Unlike getInternalURL(), the canonical URL includes the fragment +// * +// * @see self::getLocalURL for the arguments. +// * @param string|string[] $query +// * @param string|bool $query2 Deprecated +// * @return string The URL +// * @since 1.18 +// */ +// public function getCanonicalURL( $query = '', $query2 = false ) { +// $query = self::fixUrlQueryArgs( $query, $query2 ); +// $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL ); +// // Avoid PHP 7.1 warning from passing $this by reference +// $titleRef = $this; +// Hooks::run( 'GetCanonicalURL', [ &$titleRef, &$url, $query ] ); +// return $url; +// } +// +// /** +// * Get the edit URL for this Title +// * +// * @return string The URL, or a null string if this is an interwiki link +// */ +// public function getEditURL() { +// if ( $this->isExternal() ) { +// return ''; +// } +// $s = $this->getLocalURL( 'action=edit' ); +// +// return $s; +// } +// +// /** +// * Can $user perform $action on this page? +// * This skips potentially expensive cascading permission checks +// * as well as avoids expensive error formatting +// * +// * Suitable for use for nonessential UI controls in common cases, but +// * _not_ for functional access control. +// * +// * May provide false positives, but should never provide a false negative. +// * +// * @param string $action Action that permission needs to be checked for +// * @param User|null $user User to check (since 1.19); $wgUser will be used if not provided. +// * +// * @return bool +// * @throws Exception +// * +// * @deprecated since 1.33, +// * use MediaWikiServices::getInstance()->getPermissionManager()->quickUserCan(..) instead +// * +// */ +// public function quickUserCan( $action, $user = null ) { +// return $this->userCan( $action, $user, false ); +// } +// +// /** +// * Can $user perform $action on this page? +// * +// * @param string $action Action that permission needs to be checked for +// * @param User|null $user User to check (since 1.19); $wgUser will be used if not +// * provided. +// * @param string $rigor Same format as Title::getUserPermissionsErrors() +// * +// * @return bool +// * @throws Exception +// * +// * @deprecated since 1.33, +// * use MediaWikiServices::getInstance()->getPermissionManager()->userCan(..) instead +// * +// */ +// public function userCan( $action, $user = null, $rigor = PermissionManager::RIGOR_SECURE ) { +// if ( !$user instanceof User ) { +// global $wgUser; +// $user = $wgUser; +// } +// +// // TODO: this is for b/c, eventually will be removed +// if ( $rigor === true ) { +// $rigor = PermissionManager::RIGOR_SECURE; // b/c +// } elseif ( $rigor === false ) { +// $rigor = PermissionManager::RIGOR_QUICK; // b/c +// } +// +// return MediaWikiServices::getInstance()->getPermissionManager() +// ->userCan( $action, $user, $this, $rigor ); +// } +// +// /** +// * Can $user perform $action on this page? +// * +// * @todo FIXME: This *does not* check throttles (User::pingLimiter()). +// * +// * @param string $action Action that permission needs to be checked for +// * @param User $user User to check +// * @param string $rigor One of (quick,full,secure) +// * - quick : does cheap permission checks from replica DBs (usable for GUI creation) +// * - full : does cheap and expensive checks possibly from a replica DB +// * - secure : does cheap and expensive checks, using the master as needed +// * @param array $ignoreErrors Array of Strings Set this to a list of message keys +// * whose corresponding errors may be ignored. +// * +// * @return array Array of arrays of the arguments to wfMessage to explain permissions problems. +// * @throws Exception +// * +// * @deprecated since 1.33, +// * use MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissionsErrors() +// * +// */ +// public function getUserPermissionsErrors( +// $action, $user, $rigor = PermissionManager::RIGOR_SECURE, $ignoreErrors = [] +// ) { +// // TODO: this is for b/c, eventually will be removed +// if ( $rigor === true ) { +// $rigor = PermissionManager::RIGOR_SECURE; // b/c +// } elseif ( $rigor === false ) { +// $rigor = PermissionManager::RIGOR_QUICK; // b/c +// } +// +// return MediaWikiServices::getInstance()->getPermissionManager() +// ->getPermissionErrors( $action, $user, $this, $rigor, $ignoreErrors ); +// } +// +// /** +// * Add the resulting error code to the errors array +// * +// * @param array $errors List of current errors +// * @param array $result Result of errors +// * +// * @return array List of errors +// */ +// private function resultToError( $errors, $result ) { +// if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) { +// // A single array representing an error +// $errors[] = $result; +// } elseif ( is_array( $result ) && is_array( $result[0] ) ) { +// // A nested array representing multiple errors +// $errors = array_merge( $errors, $result ); +// } elseif ( $result !== '' && is_string( $result ) ) { +// // A string representing a message-id +// $errors[] = [ $result ]; +// } elseif ( $result instanceof MessageSpecifier ) { +// // A message specifier representing an error +// $errors[] = [ $result ]; +// } elseif ( $result === false ) { +// // a generic "We don't want them to do that" +// $errors[] = [ 'badaccess-group0' ]; +// } +// return $errors; +// } +// +// /** +// * Get a filtered list of all restriction types supported by this wiki. +// * @param bool $exists True to get all restriction types that apply to +// * titles that do exist, False for all restriction types that apply to +// * titles that do not exist +// * @return array +// */ +// public static function getFilteredRestrictionTypes( $exists = true ) { +// global $wgRestrictionTypes; +// $types = $wgRestrictionTypes; +// if ( $exists ) { +// # Remove the create restriction for existing titles +// $types = array_diff( $types, [ 'create' ] ); +// } else { +// # Only the create and upload restrictions apply to non-existing titles +// $types = array_intersect( $types, [ 'create', 'upload' ] ); +// } +// return $types; +// } +// +// /** +// * Returns restriction types for the current Title +// * +// * @return array Applicable restriction types +// */ +// public function getRestrictionTypes() { +// if ( $this->isSpecialPage() ) { +// return []; +// } +// +// $types = self::getFilteredRestrictionTypes( $this->exists() ); +// +// if ( $this->mNamespace != NS_FILE ) { +// # Remove the upload restriction for non-file titles +// $types = array_diff( $types, [ 'upload' ] ); +// } +// +// Hooks::run( 'TitleGetRestrictionTypes', [ $this, &$types ] ); +// +// wfDebug( __METHOD__ . ': applicable restrictions to [[' . +// $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" ); +// +// return $types; +// } +// +// /** +// * Is this title subject to title protection? +// * Title protection is the one applied against creation of such title. +// * +// * @return array|bool An associative array representing any existent title +// * protection, or false if there's none. +// */ +// public function getTitleProtection() { +// $protection = $this->getTitleProtectionInternal(); +// if ( $protection ) { +// if ( $protection['permission'] == 'sysop' ) { +// $protection['permission'] = 'editprotected'; // B/C +// } +// if ( $protection['permission'] == 'autoconfirmed' ) { +// $protection['permission'] = 'editsemiprotected'; // B/C +// } +// } +// return $protection; +// } +// +// /** +// * Fetch title protection settings +// * +// * To work correctly, $this->loadRestrictions() needs to have access to the +// * actual protections in the database without munging 'sysop' => +// * 'editprotected' and 'autoconfirmed' => 'editsemiprotected'. Other +// * callers probably want $this->getTitleProtection() instead. +// * +// * @return array|bool +// */ +// protected function getTitleProtectionInternal() { +// // Can't protect pages in special namespaces +// if ( $this->mNamespace < 0 ) { +// return false; // } // -// /** -// * Get the revision ID of the previous revision -// * -// * @param int $revId Revision ID. Get the revision that was before this one. -// * @param int $flags Title::GAID_FOR_UPDATE -// * @return int|boolean Old revision ID, or false if none exists -// */ -// public function getPreviousRevisionID($revId, $flags = 0) { -// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); -// $revId = $db.selectField('revision', 'rev_id', -// [ -// 'rev_page' => this.getArticleID($flags), -// 'rev_id < ' . intval($revId) -// ], -// __METHOD__, -// [ 'ORDER BY' => 'rev_id DESC' ] -// ); -// -// if ($revId == false) { -// return false; -// } else { -// return intval($revId); -// } +// // Can't protect pages that exist. +// if ( $this->exists() ) { +// return false; // } // -// /** -// * Get the revision ID of the next revision -// * -// * @param int $revId Revision ID. Get the revision that was after this one. -// * @param int $flags Title::GAID_FOR_UPDATE -// * @return int|boolean Next revision ID, or false if none exists -// */ -// public function getNextRevisionID($revId, $flags = 0) { -// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); -// $revId = $db.selectField('revision', 'rev_id', +// if ( $this->mTitleProtection === null ) { +// $dbr = wfGetDB( DB_REPLICA ); +// $commentStore = CommentStore::getStore(); +// $commentQuery = $commentStore->getJoin( 'pt_reason' ); +// $res = $dbr->select( +// [ 'protected_titles' ] + $commentQuery['tables'], // [ -// 'rev_page' => this.getArticleID($flags), -// 'rev_id > ' . intval($revId) -// ], -// __METHOD__, -// [ 'ORDER BY' => 'rev_id' ] +// 'user' => 'pt_user', +// 'expiry' => 'pt_expiry', +// 'permission' => 'pt_create_perm' +// ] + $commentQuery['fields'], +// [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ], +// __METHOD__, +// [], +// $commentQuery['joins'] // ); // -// if ($revId == false) { -// return false; +// // fetchRow returns false if there are no rows. +// $row = $dbr->fetchRow( $res ); +// if ( $row ) { +// $this->mTitleProtection = [ +// 'user' => $row['user'], +// 'expiry' => $dbr->decodeExpiry( $row['expiry'] ), +// 'permission' => $row['permission'], +// 'reason' => $commentStore->getComment( 'pt_reason', $row )->text, +// ]; // } else { -// return intval($revId); -// } +// $this->mTitleProtection = false; +// } +// } +// return $this->mTitleProtection; +// } +// +// /** +// * Remove any title protection due to page existing +// */ +// public function deleteTitleProtection() { +// $dbw = wfGetDB( DB_MASTER ); +// +// $dbw->delete( +// 'protected_titles', +// [ 'pt_namespace' => $this->mNamespace, 'pt_title' => $this->mDbkeyform ], +// __METHOD__ +// ); +// $this->mTitleProtection = false; +// } +// +// /** +// * Is this page "semi-protected" - the *only* protection levels are listed +// * in $wgSemiprotectedRestrictionLevels? +// * +// * @param string $action Action to check (default: edit) +// * @return bool +// */ +// public function isSemiProtected( $action = 'edit' ) { +// global $wgSemiprotectedRestrictionLevels; +// +// $restrictions = $this->getRestrictions( $action ); +// $semi = $wgSemiprotectedRestrictionLevels; +// if ( !$restrictions || !$semi ) { +// // Not protected, or all protection is full protection +// return false; // } // -// /** -// * Get the first revision of the page -// * -// * @param int $flags Title::GAID_FOR_UPDATE -// * @return Revision|null If page doesn't exist -// */ -// public function getFirstRevision($flags = 0) { -// $pageId = this.getArticleID($flags); -// if ($pageId) { -// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); -// $row = $db.selectRow('revision', Revision::selectFields(), -// [ 'rev_page' => $pageId ], -// __METHOD__, -// [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ] -// ); -// if ($row) { -// return new Revision($row); -// } -// } -// return null; +// // Remap autoconfirmed to editsemiprotected for BC +// foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) { +// $semi[$key] = 'editsemiprotected'; // } -// -// /** -// * Get the oldest revision timestamp of this page -// * -// * @param int $flags Title::GAID_FOR_UPDATE -// * @return String MW timestamp -// */ -// public function getEarliestRevTime($flags = 0) { -// $rev = this.getFirstRevision($flags); -// return $rev ? $rev.getTimestamp() : null; +// foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) { +// $restrictions[$key] = 'editsemiprotected'; // } // -// /** -// * Check if this is a new page -// * -// * @return boolean -// */ -// public function isNewPage() { -// $dbr = wfGetDB(DB_REPLICA); -// return (boolean)$dbr.selectField('page', 'page_is_new', this.pageCond(), __METHOD__); +// return !array_diff( $restrictions, $semi ); +// } +// +// /** +// * Does the title correspond to a protected article? +// * +// * @param string $action The action the page is protected from, +// * by default checks all actions. +// * @return bool +// */ +// public function isProtected( $action = '' ) { +// global $wgRestrictionLevels; +// +// $restrictionTypes = $this->getRestrictionTypes(); +// +// # Special pages have inherent protection +// if ( $this->isSpecialPage() ) { +// return true; // } // -// /** -// * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit -// * -// * @return boolean -// */ -// public function isBigDeletion() { -// global $wgDeleteRevisionsLimit; -// -// if (!$wgDeleteRevisionsLimit) { -// return false; +// # Check regular protection levels +// foreach ( $restrictionTypes as $type ) { +// if ( $action == $type || $action == '' ) { +// $r = $this->getRestrictions( $type ); +// foreach ( $wgRestrictionLevels as $level ) { +// if ( in_array( $level, $r ) && $level != '' ) { +// return true; +// } +// } // } +// } // -// if (this.mIsBigDeletion == null) { -// $dbr = wfGetDB(DB_REPLICA); +// return false; +// } // -// $revCount = $dbr.selectRowCount( -// 'revision', -// '1', -// [ 'rev_page' => this.getArticleID() ], -// __METHOD__, -// [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ] -// ); +// /** +// * Determines if $user is unable to edit this page because it has been protected +// * by $wgNamespaceProtection. +// * +// * @param User $user User object to check permissions +// * @return bool +// */ +// public function isNamespaceProtected( User $user ) { +// global $wgNamespaceProtection; // -// this.mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit; +// if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) { +// foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) { +// if ( $right != '' && !$user->isAllowed( $right ) ) { +// return true; +// } // } +// } +// return false; +// } +// +// /** +// * Cascading protection: Return true if cascading restrictions apply to this page, false if not. +// * +// * @return bool If the page is subject to cascading restrictions. +// */ +// public function isCascadeProtected() { +// list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false ); +// return ( $sources > 0 ); +// } +// +// /** +// * Determines whether cascading protection sources have already been loaded from +// * the database. +// * +// * @param bool $getPages True to check if the pages are loaded, or false to check +// * if the status is loaded. +// * @return bool Whether or not the specified information has been loaded +// * @since 1.23 +// */ +// public function areCascadeProtectionSourcesLoaded( $getPages = true ) { +// return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null; +// } +// +// /** +// * Cascading protection: Get the source of any cascading restrictions on this page. +// * +// * @param bool $getPages Whether or not to retrieve the actual pages +// * that the restrictions have come from and the actual restrictions +// * themselves. +// * @return array Two elements: First is an array of Title objects of the +// * pages from which cascading restrictions have come, false for +// * none, or true if such restrictions exist but $getPages was not +// * set. Second is an array like that returned by +// * Title::getAllRestrictions(), or an empty array if $getPages is +// * false. +// */ +// public function getCascadeProtectionSources( $getPages = true ) { +// $pagerestrictions = []; +// +// if ( $this->mCascadeSources !== null && $getPages ) { +// return [ $this->mCascadeSources, $this->mCascadingRestrictions ]; +// } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) { +// return [ $this->mHasCascadingRestrictions, $pagerestrictions ]; +// } +// +// $dbr = wfGetDB( DB_REPLICA ); +// +// if ( $this->mNamespace == NS_FILE ) { +// $tables = [ 'imagelinks', 'page_restrictions' ]; +// $where_clauses = [ +// 'il_to' => $this->mDbkeyform, +// 'il_from=pr_page', +// 'pr_cascade' => 1 +// ]; +// } else { +// $tables = [ 'templatelinks', 'page_restrictions' ]; +// $where_clauses = [ +// 'tl_namespace' => $this->mNamespace, +// 'tl_title' => $this->mDbkeyform, +// 'tl_from=pr_page', +// 'pr_cascade' => 1 +// ]; +// } // -// return this.mIsBigDeletion; +// if ( $getPages ) { +// $cols = [ 'pr_page', 'page_namespace', 'page_title', +// 'pr_expiry', 'pr_type', 'pr_level' ]; +// $where_clauses[] = 'page_id=pr_page'; +// $tables[] = 'page'; +// } else { +// $cols = [ 'pr_expiry' ]; // } // -// /** -// * Get the approximate revision count of this page. -// * -// * @return int -// */ -// public function estimateRevisionCount() { -// if (!this.exists()) { -// return 0; -// } +// $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ ); // -// if (this.mEstimateRevisions == null) { -// $dbr = wfGetDB(DB_REPLICA); -// this.mEstimateRevisions = $dbr.estimateRowCount('revision', '*', -// [ 'rev_page' => this.getArticleID() ], __METHOD__); -// } +// $sources = $getPages ? [] : false; +// $now = wfTimestampNow(); // -// return this.mEstimateRevisions; -// } +// foreach ( $res as $row ) { +// $expiry = $dbr->decodeExpiry( $row->pr_expiry ); +// if ( $expiry > $now ) { +// if ( $getPages ) { +// $page_id = $row->pr_page; +// $page_ns = $row->page_namespace; +// $page_title = $row->page_title; +// $sources[$page_id] = self::makeTitle( $page_ns, $page_title ); +// # Add groups needed for each restriction type if its not already there +// # Make sure this restriction type still exists // -// /** -// * Get the number of revisions between the given revision. -// * Used for diffs and other things that really need it. -// * -// * @param int|Revision $old Old revision or rev ID (first before range) -// * @param int|Revision $new New revision or rev ID (first after range) -// * @param int|null $max Limit of Revisions to count, will be incremented to detect truncations -// * @return int Number of revisions between these revisions. -// */ -// public function countRevisionsBetween($old, $new, $max = null) { -// if (!($old instanceof Revision)) { -// $old = Revision::newFromTitle(this, (int)$old); -// } -// if (!($new instanceof Revision)) { -// $new = Revision::newFromTitle(this, (int)$new); -// } -// if (!$old || !$new) { -// return 0; // nothing to compare -// } -// $dbr = wfGetDB(DB_REPLICA); -// $conds = [ -// 'rev_page' => this.getArticleID(), -// 'rev_timestamp > ' . $dbr.addQuotes($dbr.timestamp($old.getTimestamp())), -// 'rev_timestamp < ' . $dbr.addQuotes($dbr.timestamp($new.getTimestamp())) -// ]; -// if ($max != null) { -// return $dbr.selectRowCount('revision', '1', -// $conds, -// __METHOD__, -// [ 'LIMIT' => $max + 1 ] // extra to detect truncation -// ); -// } else { -// return (int)$dbr.selectField('revision', 'count(*)', $conds, __METHOD__); +// if ( !isset( $pagerestrictions[$row->pr_type] ) ) { +// $pagerestrictions[$row->pr_type] = []; +// } +// +// if ( +// isset( $pagerestrictions[$row->pr_type] ) +// && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) +// ) { +// $pagerestrictions[$row->pr_type][] = $row->pr_level; +// } +// } else { +// $sources = true; +// } // } // } // -// /** -// * Get the authors between the given revisions or revision IDs. -// * Used for diffs and other things that really need it. -// * -// * @since 1.23 -// * -// * @param int|Revision $old Old revision or rev ID (first before range by default) -// * @param int|Revision $new New revision or rev ID (first after range by default) -// * @param int $limit Maximum number of authors -// * @param String|array $options (Optional): Single option, or an array of options: -// * 'include_old' Include $old in the range; $new is excluded. -// * 'include_new' Include $new in the range; $old is excluded. -// * 'include_both' Include both $old and $new in the range. -// * Unknown option values are ignored. -// * @return array|null Names of revision authors in the range; null if not both revisions exist -// */ -// public function getAuthorsBetween($old, $new, $limit, $options = []) { -// if (!($old instanceof Revision)) { -// $old = Revision::newFromTitle(this, (int)$old); -// } -// if (!($new instanceof Revision)) { -// $new = Revision::newFromTitle(this, (int)$new); -// } -// // XXX: what if Revision objects are passed in, but they don't refer to this title? -// // Add $old.getPage() != $new.getPage() || $old.getPage() != this.getArticleID() -// // in the sanity check below? -// if (!$old || !$new) { -// return null; // nothing to compare -// } -// $authors = []; -// $old_cmp = '>'; -// $new_cmp = '<'; -// $options = (array)$options; -// if (in_array('include_old', $options)) { -// $old_cmp = '>='; -// } -// if (in_array('include_new', $options)) { -// $new_cmp = '<='; -// } -// if (in_array('include_both', $options)) { -// $old_cmp = '>='; -// $new_cmp = '<='; -// } -// // No DB query needed if $old and $new are the same or successive revisions: -// if ($old.getId() == $new.getId()) { -// return ($old_cmp == '>' && $new_cmp == '<') ? -// [] : -// [ $old.getUserText(Revision::RAW) ]; -// } elseif ($old.getId() == $new.getParentId()) { -// if ($old_cmp == '>=' && $new_cmp == '<=') { -// $authors[] = $old.getUserText(Revision::RAW); -// if ($old.getUserText(Revision::RAW) != $new.getUserText(Revision::RAW)) { -// $authors[] = $new.getUserText(Revision::RAW); +// if ( $getPages ) { +// $this->mCascadeSources = $sources; +// $this->mCascadingRestrictions = $pagerestrictions; +// } else { +// $this->mHasCascadingRestrictions = $sources; +// } +// +// return [ $sources, $pagerestrictions ]; +// } +// +// /** +// * Accessor for mRestrictionsLoaded +// * +// * @return bool Whether or not the page's restrictions have already been +// * loaded from the database +// * @since 1.23 +// */ +// public function areRestrictionsLoaded() { +// return $this->mRestrictionsLoaded; +// } +// +// /** +// * Accessor/initialisation for mRestrictions +// * +// * @param string $action Action that permission needs to be checked for +// * @return array Restriction levels needed to take the action. All levels are +// * required. Note that restriction levels are normally user rights, but 'sysop' +// * and 'autoconfirmed' are also allowed for backwards compatibility. These should +// * be mapped to 'editprotected' and 'editsemiprotected' respectively. +// */ +// public function getRestrictions( $action ) { +// if ( !$this->mRestrictionsLoaded ) { +// $this->loadRestrictions(); +// } +// return $this->mRestrictions[$action] ?? []; +// } +// +// /** +// * Accessor/initialisation for mRestrictions +// * +// * @return array Keys are actions, values are arrays as returned by +// * Title::getRestrictions() +// * @since 1.23 +// */ +// public function getAllRestrictions() { +// if ( !$this->mRestrictionsLoaded ) { +// $this->loadRestrictions(); +// } +// return $this->mRestrictions; +// } +// +// /** +// * Get the expiry time for the restriction against a given action +// * +// * @param string $action +// * @return string|bool 14-char timestamp, or 'infinity' if the page is protected forever +// * or not protected at all, or false if the action is not recognised. +// */ +// public function getRestrictionExpiry( $action ) { +// if ( !$this->mRestrictionsLoaded ) { +// $this->loadRestrictions(); +// } +// return $this->mRestrictionsExpiry[$action] ?? false; +// } +// +// /** +// * Returns cascading restrictions for the current article +// * +// * @return bool +// */ +// function areRestrictionsCascading() { +// if ( !$this->mRestrictionsLoaded ) { +// $this->loadRestrictions(); +// } +// +// return $this->mCascadeRestriction; +// } +// +// /** +// * Compiles list of active page restrictions from both page table (pre 1.10) +// * and page_restrictions table for this existing page. +// * Public for usage by LiquidThreads. +// * +// * @param array $rows Array of db result objects +// * @param string|null $oldFashionedRestrictions Comma-separated set of permission keys +// * indicating who can move or edit the page from the page table, (pre 1.10) rows. +// * Edit and move sections are separated by a colon +// * Example: "edit=autoconfirmed,sysop:move=sysop" +// */ +// public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) { +// // This function will only read rows from a table that we migrated away +// // from before adding READ_LATEST support to loadRestrictions, so we +// // don't need to support reading from DB_MASTER here. +// $dbr = wfGetDB( DB_REPLICA ); +// +// $restrictionTypes = $this->getRestrictionTypes(); +// +// foreach ( $restrictionTypes as $type ) { +// $this->mRestrictions[$type] = []; +// $this->mRestrictionsExpiry[$type] = 'infinity'; +// } +// +// $this->mCascadeRestriction = false; +// +// # Backwards-compatibility: also load the restrictions from the page record (old format). +// if ( $oldFashionedRestrictions !== null ) { +// $this->mOldRestrictions = $oldFashionedRestrictions; +// } +// +// if ( $this->mOldRestrictions === false ) { +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->addLinkObj( $this ); # in case we already had an article ID +// $this->mOldRestrictions = $linkCache->getGoodLinkFieldObj( $this, 'restrictions' ); +// } +// +// if ( $this->mOldRestrictions != '' ) { +// foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) { +// $temp = explode( '=', trim( $restrict ) ); +// if ( count( $temp ) == 1 ) { +// // old old format should be treated as edit/move restriction +// $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) ); +// $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) ); +// } else { +// $restriction = trim( $temp[1] ); +// if ( $restriction != '' ) { // some old entries are empty +// $this->mRestrictions[$temp[0]] = explode( ',', $restriction ); // } -// } elseif ($old_cmp == '>=') { -// $authors[] = $old.getUserText(Revision::RAW); -// } elseif ($new_cmp == '<=') { -// $authors[] = $new.getUserText(Revision::RAW); // } -// return $authors; -// } -// $dbr = wfGetDB(DB_REPLICA); -// $res = $dbr.select('revision', 'DISTINCT rev_user_text', -// [ -// 'rev_page' => this.getArticleID(), -// "rev_timestamp $old_cmp " . $dbr.addQuotes($dbr.timestamp($old.getTimestamp())), -// "rev_timestamp $new_cmp " . $dbr.addQuotes($dbr.timestamp($new.getTimestamp())) -// ], __METHOD__, -// [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated -// ); -// foreach ($res as $row) { -// $authors[] = $row.rev_user_text; // } -// return $authors; // } // -// /** -// * Get the number of authors between the given revisions or revision IDs. -// * Used for diffs and other things that really need it. -// * -// * @param int|Revision $old Old revision or rev ID (first before range by default) -// * @param int|Revision $new New revision or rev ID (first after range by default) -// * @param int $limit Maximum number of authors -// * @param String|array $options (Optional): Single option, or an array of options: -// * 'include_old' Include $old in the range; $new is excluded. -// * 'include_new' Include $new in the range; $old is excluded. -// * 'include_both' Include both $old and $new in the range. -// * Unknown option values are ignored. -// * @return int Number of revision authors in the range; zero if not both revisions exist -// */ -// public function countAuthorsBetween($old, $new, $limit, $options = []) { -// $authors = this.getAuthorsBetween($old, $new, $limit, $options); -// return $authors ? count($authors) : 0; -// } - - /** - * Compare with another title. - * - * @param Title $title - * @return boolean - */ - public boolean equals(XomwTitle title) { - // Note: == is necessary for proper matching of number-like titles. - return Bry_.Eq(this.getInterwiki(), title.getInterwiki()) - && this.getNamespace() == title.getNamespace() - && Bry_.Eq(this.getDBkey(), title.getDBkey()); - } - -// /** -// * Check if this title is a subpage of another title -// * -// * @param Title $title -// * @return boolean -// */ -// public function isSubpageOf(Title $title) { -// return this.getInterwiki() == $title.getInterwiki() -// && this.getNamespace() == $title.getNamespace() -// && strpos(this.getDBkey(), $title.getDBkey() . '/') == 0; -// } +// if ( count( $rows ) ) { +// # Current system - load second to make them override. +// $now = wfTimestampNow(); // -// /** -// * Check if page exists. For historical reasons, this function simply -// * checks for the existence of the title in the page table, and will -// * thus return false for interwiki links, special pages and the like. -// * If you want to know if a title can be meaningfully viewed, you should -// * probably call the isKnown() method instead. -// * -// * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check -// * from master/for update -// * @return boolean -// */ -// public function exists($flags = 0) { -// $exists = this.getArticleID($flags) != 0; -// Hooks::run('TitleExists', [ this, &$exists ]); -// return $exists; -// } - - /** - * Should links to this title be shown as potentially viewable (i.e. as - * "bluelinks"), even if there's no record by this title in the page - * table? - * - * This function is semi-deprecated for public use, as well as somewhat - * misleadingly named. You probably just want to call isKnown(), which - * calls this function internally. - * - * (ISSUE: Most of these checks are cheap, but the file existence check - * can potentially be quite expensive. Including it here fixes a lot of - * existing code, but we might want to add an optional parameter to skip - * it and any other expensive checks.) - * - * @return boolean - */ - public boolean isAlwaysKnown() { -// $isKnown = null; -// -// /** -// * Allows overriding default behavior for determining if a page exists. -// * If $isKnown is kept as null, regular checks happen. If it's -// * a boolean, this value is returned by the isKnown method. -// * -// * @since 1.20 -// * -// * @param Title $title -// * @param boolean|null $isKnown -// */ -// Hooks::run('TitleIsAlwaysKnown', [ this, &$isKnown ]); -// -// if (!is_null($isKnown)) { -// return $isKnown; -// } +// # Cycle through all the restrictions. +// foreach ( $rows as $row ) { +// // Don't take care of restrictions types that aren't allowed +// if ( !in_array( $row->pr_type, $restrictionTypes ) ) { +// continue; +// } // -// if (this.isExternal()) { -// return true; // any interwiki link might be viewable, for all we know -// } +// $expiry = $dbr->decodeExpiry( $row->pr_expiry ); // -// switch (this.mNamespace) { -// case NS_MEDIA: -// case NS_FILE: -// // file exists, possibly in a foreign repo -// return (boolean)wfFindFile(this); -// case NS_SPECIAL: -// // valid special page -// return SpecialPageFactory::exists(this.getDBkey()); -// case NS_MAIN: -// // selflink, possibly with fragment -// return this.mDbkeyform == Bry_.Empty; -// case NS_MEDIAWIKI: -// // known system message -// return this.hasSourceText() != false; -// default: -// return false; -// } - return false; - } - - /** - * Does this title refer to a page that can (or might) be meaningfully - * viewed? In particular, this function may be used to determine if - * links to the title should be rendered as "bluelinks" (as opposed to - * "redlinks" to non-existent pages). - * Adding something else to this function will cause inconsistency - * since LinkHolderArray calls isAlwaysKnown() and does its own - * page existence check. - * - * @return boolean - */ - public boolean isKnown() { -// return this.isAlwaysKnown() || this.exists(); - return true; - } - -// /** -// * Does this page have source text? -// * -// * @return boolean -// */ -// public function hasSourceText() { -// if (this.exists()) { -// return true; +// // Only apply the restrictions if they haven't expired! +// if ( !$expiry || $expiry > $now ) { +// $this->mRestrictionsExpiry[$row->pr_type] = $expiry; +// $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) ); +// +// $this->mCascadeRestriction |= $row->pr_cascade; +// } // } +// } // -// if (this.mNamespace == NS_MEDIAWIKI) { -// // If the page doesn't exist but is a known system message, default -// // message content will be displayed, same for language subpages- -// // Use always content language to avoid loading hundreds of languages -// // to get the link color. -// global $wgContLang; -// list($name,) = MessageCache::singleton().figureMessage( -// $wgContLang.lcfirst(this.getText()) +// $this->mRestrictionsLoaded = true; +// } +// +// /** +// * Load restrictions from the page_restrictions table +// * +// * @param string|null $oldFashionedRestrictions Comma-separated set of permission keys +// * indicating who can move or edit the page from the page table, (pre 1.10) rows. +// * Edit and move sections are separated by a colon +// * Example: "edit=autoconfirmed,sysop:move=sysop" +// * @param int $flags A bit field. If self::READ_LATEST is set, skip replicas and read +// * from the master DB. +// */ +// public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) { +// $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ); +// if ( $this->mRestrictionsLoaded && !$readLatest ) { +// return; +// } +// +// // TODO: should probably pass $flags into getArticleID, but it seems hacky +// // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value. +// // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject? +// $id = $this->getArticleID(); +// if ( $id ) { +// $fname = __METHOD__; +// $loadRestrictionsFromDb = function ( IDatabase $dbr ) use ( $fname, $id ) { +// return iterator_to_array( +// $dbr->select( +// 'page_restrictions', +// [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ], +// [ 'pr_page' => $id ], +// $fname +// ) // ); -// $message = wfMessage($name).inLanguage($wgContLang).useDatabase(false); -// return $message.exists(); -// } +// }; // -// return false; -// } +// if ( $readLatest ) { +// $dbr = wfGetDB( DB_MASTER ); +// $rows = $loadRestrictionsFromDb( $dbr ); +// } else { +// $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); +// $rows = $cache->getWithSetCallback( +// // Page protections always leave a new null revision +// $cache->makeKey( 'page-restrictions', 'v1', $id, $this->getLatestRevID() ), +// $cache::TTL_DAY, +// function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) { +// $dbr = wfGetDB( DB_REPLICA ); +// +// $setOpts += Database::getCacheSetOptions( $dbr ); +// $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); +// if ( $lb->hasOrMadeRecentMasterChanges() ) { +// // @TODO: cleanup Title cache and caller assumption mess in general +// $ttl = WANObjectCache::TTL_UNCACHEABLE; +// } // -// /** -// * Get the default message text or false if the message doesn't exist -// * -// * @return String|boolean -// */ -// public function getDefaultMessageText() { -// global $wgContLang; -// -// if (this.getNamespace() != NS_MEDIAWIKI) { // Just in case -// return false; +// return $loadRestrictionsFromDb( $dbr ); +// } +// ); // } // -// list($name, $lang) = MessageCache::singleton().figureMessage( -// $wgContLang.lcfirst(this.getText()) -// ); -// $message = wfMessage($name).inLanguage($lang).useDatabase(false); +// $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions ); +// } else { +// $title_protection = $this->getTitleProtectionInternal(); // -// if ($message.exists()) { -// return $message.plain(); +// if ( $title_protection ) { +// $now = wfTimestampNow(); +// $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] ); +// +// if ( !$expiry || $expiry > $now ) { +// // Apply the restrictions +// $this->mRestrictionsExpiry['create'] = $expiry; +// $this->mRestrictions['create'] = +// explode( ',', trim( $title_protection['permission'] ) ); +// } else { // Get rid of the old restrictions +// $this->mTitleProtection = false; +// } // } else { -// return false; +// $this->mRestrictionsExpiry['create'] = 'infinity'; +// } +// $this->mRestrictionsLoaded = true; +// } +// } +// +// /** +// * Flush the protection cache in this object and force reload from the database. +// * This is used when updating protection from WikiPage::doUpdateRestrictions(). +// */ +// public function flushRestrictions() { +// $this->mRestrictionsLoaded = false; +// $this->mTitleProtection = null; +// } +// +// /** +// * Purge expired restrictions from the page_restrictions table +// * +// * This will purge no more than $wgUpdateRowsPerQuery page_restrictions rows +// */ +// static function purgeExpiredRestrictions() { +// if ( wfReadOnly() ) { +// return; +// } +// +// DeferredUpdates::addUpdate( new AtomicSectionUpdate( +// wfGetDB( DB_MASTER ), +// __METHOD__, +// function ( IDatabase $dbw, $fname ) { +// $config = MediaWikiServices::getInstance()->getMainConfig(); +// $ids = $dbw->selectFieldValues( +// 'page_restrictions', +// 'pr_id', +// [ 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ], +// $fname, +// [ 'LIMIT' => $config->get( 'UpdateRowsPerQuery' ) ] // T135470 +// ); +// if ( $ids ) { +// $dbw->delete( 'page_restrictions', [ 'pr_id' => $ids ], $fname ); // } // } +// ) ); // -// /** -// * Updates page_touched for this page; called from LinksUpdate.php -// * -// * @param String $purgeTime [optional] TS_MW timestamp -// * @return boolean True if the update succeeded -// */ -// public function invalidateCache($purgeTime = null) { -// if (wfReadOnly()) { -// return false; -// } elseif (this.mArticleID == 0) { -// return true; // avoid gap locking if we know it's not there -// } -// -// $dbw = wfGetDB(DB_MASTER); -// $dbw.onTransactionPreCommitOrIdle(function () { -// ResourceLoaderWikiModule::invalidateModuleCache(this, null, null, wfWikiID()); -// }); -// -// $conds = this.pageCond(); -// DeferredUpdates::addUpdate( -// new AutoCommitUpdate( -// $dbw, -// __METHOD__, -// function (IDatabase $dbw, $fname) use ($conds, $purgeTime) { -// $dbTimestamp = $dbw.timestamp($purgeTime ?: time()); -// $dbw.update( -// 'page', -// [ 'page_touched' => $dbTimestamp ], -// $conds + [ 'page_touched < ' . $dbw.addQuotes($dbTimestamp) ], -// $fname -// ); -// MediaWikiServices::getInstance().getLinkCache().invalidateTitle(this); -// } -// ), -// DeferredUpdates::PRESEND -// ); -// -// return true; +// DeferredUpdates::addUpdate( new AtomicSectionUpdate( +// wfGetDB( DB_MASTER ), +// __METHOD__, +// function ( IDatabase $dbw, $fname ) { +// $dbw->delete( +// 'protected_titles', +// [ 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ], +// $fname +// ); +// } +// ) ); +// } +// +// /** +// * Does this have subpages? (Warning, usually requires an extra DB query.) +// * +// * @return bool +// */ +// public function hasSubpages() { +// if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { +// # Duh +// return false; // } // -// /** -// * Update page_touched timestamps and send CDN purge messages for -// * pages linking to this title. May be sent to the job queue depending -// * on the number of links. Typically called on create and delete. -// */ -// public function touchLinks() { -// DeferredUpdates::addUpdate(new HTMLCacheUpdate(this, 'pagelinks')); -// if (this.getNamespace() == NS_CATEGORY) { -// DeferredUpdates::addUpdate(new HTMLCacheUpdate(this, 'categorylinks')); +// # We dynamically add a member variable for the purpose of this method +// # alone to cache the result. There's no point in having it hanging +// # around uninitialized in every Title object; therefore we only add it +// # if needed and don't declare it statically. +// if ( $this->mHasSubpages === null ) { +// $this->mHasSubpages = false; +// $subpages = $this->getSubpages( 1 ); +// if ( $subpages instanceof TitleArray ) { +// $this->mHasSubpages = (bool)$subpages->count(); +// } +// } +// +// return $this->mHasSubpages; +// } +// +// /** +// * Get all subpages of this page. +// * +// * @param int $limit Maximum number of subpages to fetch; -1 for no limit +// * @return TitleArray|array TitleArray, or empty array if this page's namespace +// * doesn't allow subpages +// */ +// public function getSubpages( $limit = -1 ) { +// if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { +// return []; +// } +// +// $dbr = wfGetDB( DB_REPLICA ); +// $conds['page_namespace'] = $this->mNamespace; +// $conds[] = 'page_title ' . $dbr->buildLike( $this->mDbkeyform . '/', $dbr->anyString() ); +// $options = []; +// if ( $limit > -1 ) { +// $options['LIMIT'] = $limit; +// } +// return TitleArray::newFromResult( +// $dbr->select( 'page', +// [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ], +// $conds, +// __METHOD__, +// $options +// ) +// ); +// } +// +// /** +// * Is there a version of this page in the deletion archive? +// * +// * @return int The number of archived revisions +// */ +// public function isDeleted() { +// if ( $this->mNamespace < 0 ) { +// $n = 0; +// } else { +// $dbr = wfGetDB( DB_REPLICA ); +// +// $n = $dbr->selectField( 'archive', 'COUNT(*)', +// [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ], +// __METHOD__ +// ); +// if ( $this->mNamespace == NS_FILE ) { +// $n += $dbr->selectField( 'filearchive', 'COUNT(*)', +// [ 'fa_name' => $this->mDbkeyform ], +// __METHOD__ +// ); // } // } +// return (int)$n; +// } // -// /** -// * Get the last touched timestamp -// * -// * @param IDatabase $db Optional db -// * @return String|false Last-touched timestamp -// */ -// public function getTouched($db = null) { -// if ($db == null) { -// $db = wfGetDB(DB_REPLICA); +// /** +// * Is there a version of this page in the deletion archive? +// * +// * @return bool +// */ +// public function isDeletedQuick() { +// if ( $this->mNamespace < 0 ) { +// return false; +// } +// $dbr = wfGetDB( DB_REPLICA ); +// $deleted = (bool)$dbr->selectField( 'archive', '1', +// [ 'ar_namespace' => $this->mNamespace, 'ar_title' => $this->mDbkeyform ], +// __METHOD__ +// ); +// if ( !$deleted && $this->mNamespace == NS_FILE ) { +// $deleted = (bool)$dbr->selectField( 'filearchive', '1', +// [ 'fa_name' => $this->mDbkeyform ], +// __METHOD__ +// ); +// } +// return $deleted; +// } +// +// /** +// * Get the article ID for this Title from the link cache, +// * adding it if necessary +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select +// * for update +// * @return int The ID +// */ +// public function getArticleID( $flags = 0 ) { +// if ( $this->mNamespace < 0 ) { +// $this->mArticleID = 0; +// return $this->mArticleID; +// } +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// if ( $flags & self::GAID_FOR_UPDATE ) { +// $oldUpdate = $linkCache->forUpdate( true ); +// $linkCache->clearLink( $this ); +// $this->mArticleID = $linkCache->addLinkObj( $this ); +// $linkCache->forUpdate( $oldUpdate ); +// } elseif ( $this->mArticleID == -1 ) { +// $this->mArticleID = $linkCache->addLinkObj( $this ); +// } +// return $this->mArticleID; +// } +// +// /** +// * Is this an article that is a redirect page? +// * Uses link cache, adding it if necessary +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return bool +// */ +// public function isRedirect( $flags = 0 ) { +// if ( !is_null( $this->mRedirect ) ) { +// return $this->mRedirect; +// } +// if ( !$this->getArticleID( $flags ) ) { +// $this->mRedirect = false; +// return $this->mRedirect; +// } +// +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->addLinkObj( $this ); # in case we already had an article ID +// $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' ); +// if ( $cached === null ) { +// # Trust LinkCache's state over our own +// # LinkCache is telling us that the page doesn't exist, despite there being cached +// # data relating to an existing page in $this->mArticleID. Updaters should clear +// # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is +// # set, then LinkCache will definitely be up to date here, since getArticleID() forces +// # LinkCache to refresh its data from the master. +// $this->mRedirect = false; +// return $this->mRedirect; +// } +// +// $this->mRedirect = (bool)$cached; +// +// return $this->mRedirect; +// } +// +// /** +// * What is the length of this page? +// * Uses link cache, adding it if necessary +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return int +// */ +// public function getLength( $flags = 0 ) { +// if ( $this->mLength != -1 ) { +// return $this->mLength; +// } +// if ( !$this->getArticleID( $flags ) ) { +// $this->mLength = 0; +// return $this->mLength; +// } +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->addLinkObj( $this ); # in case we already had an article ID +// $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' ); +// if ( $cached === null ) { +// # Trust LinkCache's state over our own, as for isRedirect() +// $this->mLength = 0; +// return $this->mLength; +// } +// +// $this->mLength = intval( $cached ); +// +// return $this->mLength; +// } +// +// /** +// * What is the page_latest field for this page? +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return int Int or 0 if the page doesn't exist +// */ +// public function getLatestRevID( $flags = 0 ) { +// if ( !( $flags & self::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) { +// return intval( $this->mLatestID ); +// } +// if ( !$this->getArticleID( $flags ) ) { +// $this->mLatestID = 0; +// return $this->mLatestID; +// } +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->addLinkObj( $this ); # in case we already had an article ID +// $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' ); +// if ( $cached === null ) { +// # Trust LinkCache's state over our own, as for isRedirect() +// $this->mLatestID = 0; +// return $this->mLatestID; +// } +// +// $this->mLatestID = intval( $cached ); +// +// return $this->mLatestID; +// } +// +// /** +// * This clears some fields in this object, and clears any associated +// * keys in the "bad links" section of the link cache. +// * +// * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow +// * loading of the new page_id. It's also called from +// * WikiPage::doDeleteArticleReal() +// * +// * @param int $newid The new Article ID +// */ +// public function resetArticleID( $newid ) { +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->clearLink( $this ); +// +// if ( $newid === false ) { +// $this->mArticleID = -1; +// } else { +// $this->mArticleID = intval( $newid ); +// } +// $this->mRestrictionsLoaded = false; +// $this->mRestrictions = []; +// $this->mOldRestrictions = false; +// $this->mRedirect = null; +// $this->mLength = -1; +// $this->mLatestID = false; +// $this->mContentModel = false; +// $this->mEstimateRevisions = null; +// $this->mPageLanguage = false; +// $this->mDbPageLanguage = false; +// $this->mIsBigDeletion = null; +// } +// +// public static function clearCaches() { +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->clear(); +// +// $titleCache = self::getTitleCache(); +// $titleCache->clear(); +// } +// +// /** +// * Capitalize a text string for a title if it belongs to a namespace that capitalizes +// * +// * @param string $text Containing title to capitalize +// * @param int $ns Namespace index, defaults to NS_MAIN +// * @return string Containing capitalized title +// */ +// public static function capitalize( $text, $ns = NS_MAIN ) { +// if ( MWNamespace::isCapitalized( $ns ) ) { +// return MediaWikiServices::getInstance()->getContentLanguage()->ucfirst( $text ); +// } else { +// return $text; +// } +// } +// +// /** +// * Secure and split - main initialisation function for this object +// * +// * Assumes that mDbkeyform has been set, and is urldecoded +// * and uses underscores, but not otherwise munged. This function +// * removes illegal characters, splits off the interwiki and +// * namespace prefixes, sets the other forms, and canonicalizes +// * everything. +// * +// * @throws MalformedTitleException On invalid titles +// * @return bool True on success +// */ +// private function secureAndSplit() { +// // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share +// // the parsing code with Title, while avoiding massive refactoring. +// // @todo: get rid of secureAndSplit, refactor parsing code. +// // @note: getTitleParser() returns a TitleParser implementation which does not have a +// // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does +// /** @var MediaWikiTitleCodec $titleCodec */ +// $titleCodec = MediaWikiServices::getInstance()->getTitleParser(); +// // MalformedTitleException can be thrown here +// $parts = $titleCodec->splitTitleString( $this->mDbkeyform, $this->mDefaultNamespace ); +// +// # Fill fields +// $this->setFragment( '#' . $parts['fragment'] ); +// $this->mInterwiki = $parts['interwiki']; +// $this->mLocalInterwiki = $parts['local_interwiki']; +// $this->mNamespace = $parts['namespace']; +// $this->mUserCaseDBKey = $parts['user_case_dbkey']; +// +// $this->mDbkeyform = $parts['dbkey']; +// $this->mUrlform = wfUrlencode( $this->mDbkeyform ); +// $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' ); +// +// # We already know that some pages won't be in the database! +// if ( $this->isExternal() || $this->isSpecialPage() ) { +// $this->mArticleID = 0; +// } +// +// return true; +// } +// +// /** +// * Get an array of Title objects linking to this Title +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options May be FOR UPDATE +// * @param string $table Table name +// * @param string $prefix Fields prefix +// * @return Title[] Array of Title objects linking here +// */ +// public function getLinksTo( $options = [], $table = 'pagelinks', $prefix = 'pl' ) { +// if ( count( $options ) > 0 ) { +// $db = wfGetDB( DB_MASTER ); +// } else { +// $db = wfGetDB( DB_REPLICA ); +// } +// +// $res = $db->select( +// [ 'page', $table ], +// self::getSelectFields(), +// [ +// "{$prefix}_from=page_id", +// "{$prefix}_namespace" => $this->mNamespace, +// "{$prefix}_title" => $this->mDbkeyform ], +// __METHOD__, +// $options +// ); +// +// $retVal = []; +// if ( $res->numRows() ) { +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// foreach ( $res as $row ) { +// $titleObj = self::makeTitle( $row->page_namespace, $row->page_title ); +// if ( $titleObj ) { +// $linkCache->addGoodLinkObjFromRow( $titleObj, $row ); +// $retVal[] = $titleObj; +// } // } -// $touched = $db.selectField('page', 'page_touched', this.pageCond(), __METHOD__); -// return $touched; +// } +// return $retVal; +// } +// +// /** +// * Get an array of Title objects using this Title as a template +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options Query option to Database::select() +// * @return Title[] Array of Title the Title objects linking here +// */ +// public function getTemplateLinksTo( $options = [] ) { +// return $this->getLinksTo( $options, 'templatelinks', 'tl' ); +// } +// +// /** +// * Get an array of Title objects linked from this Title +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options Query option to Database::select() +// * @param string $table Table name +// * @param string $prefix Fields prefix +// * @return array Array of Title objects linking here +// */ +// public function getLinksFrom( $options = [], $table = 'pagelinks', $prefix = 'pl' ) { +// $id = $this->getArticleID(); +// +// # If the page doesn't exist; there can't be any link from this page +// if ( !$id ) { +// return []; +// } +// +// $db = wfGetDB( DB_REPLICA ); +// +// $blNamespace = "{$prefix}_namespace"; +// $blTitle = "{$prefix}_title"; +// +// $pageQuery = WikiPage::getQueryInfo(); +// $res = $db->select( +// [ $table, 'nestpage' => $pageQuery['tables'] ], +// array_merge( +// [ $blNamespace, $blTitle ], +// $pageQuery['fields'] +// ), +// [ "{$prefix}_from" => $id ], +// __METHOD__, +// $options, +// [ 'nestpage' => [ +// 'LEFT JOIN', +// [ "page_namespace=$blNamespace", "page_title=$blTitle" ] +// ] ] + $pageQuery['joins'] +// ); +// +// $retVal = []; +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// foreach ( $res as $row ) { +// if ( $row->page_id ) { +// $titleObj = self::newFromRow( $row ); +// } else { +// $titleObj = self::makeTitle( $row->$blNamespace, $row->$blTitle ); +// $linkCache->addBadLinkObj( $titleObj ); +// } +// $retVal[] = $titleObj; +// } +// +// return $retVal; +// } +// +// /** +// * Get an array of Title objects used on this Title as a template +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options May be FOR UPDATE +// * @return Title[] Array of Title the Title objects used here +// */ +// public function getTemplateLinksFrom( $options = [] ) { +// return $this->getLinksFrom( $options, 'templatelinks', 'tl' ); +// } +// +// /** +// * Get an array of Title objects referring to non-existent articles linked +// * from this page. +// * +// * @todo check if needed (used only in SpecialBrokenRedirects.php, and +// * should use redirect table in this case). +// * @return Title[] Array of Title the Title objects +// */ +// public function getBrokenLinksFrom() { +// if ( $this->getArticleID() == 0 ) { +// # All links from article ID 0 are false positives +// return []; +// } +// +// $dbr = wfGetDB( DB_REPLICA ); +// $res = $dbr->select( +// [ 'page', 'pagelinks' ], +// [ 'pl_namespace', 'pl_title' ], +// [ +// 'pl_from' => $this->getArticleID(), +// 'page_namespace IS NULL' +// ], +// __METHOD__, [], +// [ +// 'page' => [ +// 'LEFT JOIN', +// [ 'pl_namespace=page_namespace', 'pl_title=page_title' ] +// ] +// ] +// ); +// +// $retVal = []; +// foreach ( $res as $row ) { +// $retVal[] = self::makeTitle( $row->pl_namespace, $row->pl_title ); +// } +// return $retVal; +// } +// +// /** +// * Get a list of URLs to purge from the CDN cache when this +// * page changes +// * +// * @return string[] Array of String the URLs +// */ +// public function getCdnUrls() { +// $urls = [ +// $this->getInternalURL(), +// $this->getInternalURL( 'action=history' ) +// ]; +// +// $pageLang = $this->getPageLanguage(); +// if ( $pageLang->hasVariants() ) { +// $variants = $pageLang->getVariants(); +// foreach ( $variants as $vCode ) { +// $urls[] = $this->getInternalURL( $vCode ); +// } +// } +// +// // If we are looking at a css/js user subpage, purge the action=raw. +// if ( $this->isUserJsConfigPage() ) { +// $urls[] = $this->getInternalURL( 'action=raw&ctype=text/javascript' ); +// } elseif ( $this->isUserJsonConfigPage() ) { +// $urls[] = $this->getInternalURL( 'action=raw&ctype=application/json' ); +// } elseif ( $this->isUserCssConfigPage() ) { +// $urls[] = $this->getInternalURL( 'action=raw&ctype=text/css' ); +// } +// +// Hooks::run( 'TitleSquidURLs', [ $this, &$urls ] ); +// return $urls; +// } +// +// /** +// * Purge all applicable CDN URLs +// */ +// public function purgeSquid() { +// DeferredUpdates::addUpdate( +// new CdnCacheUpdate( $this->getCdnUrls() ), +// DeferredUpdates::PRESEND +// ); +// } +// +// /** +// * Check whether a given move operation would be valid. +// * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise +// * +// * @deprecated since 1.25, use MovePage's methods instead +// * @param Title &$nt The new title +// * @param bool $auth Whether to check user permissions (uses $wgUser) +// * @param string $reason Is the log summary of the move, used for spam checking +// * @return array|bool True on success, getUserPermissionsErrors()-like array on failure +// */ +// public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) { +// global $wgUser; +// +// if ( !( $nt instanceof Title ) ) { +// // Normally we'd add this to $errors, but we'll get +// // lots of syntax errors if $nt is not an object +// return [ [ 'badtitletext' ] ]; +// } +// +// $mp = new MovePage( $this, $nt ); +// $errors = $mp->isValidMove()->getErrorsArray(); +// if ( $auth ) { +// $errors = wfMergeErrorArrays( +// $errors, +// $mp->checkPermissions( $wgUser, $reason )->getErrorsArray() +// ); // } // -// /** -// * Get the timestamp when this page was updated since the user last saw it. -// * -// * @param User $user -// * @return String|null -// */ -// public function getNotificationTimestamp($user = null) { -// global $wgUser; +// return $errors ?: true; +// } +// +// /** +// * Move a title to a new location +// * +// * @deprecated since 1.25, use the MovePage class instead +// * @param Title &$nt The new title +// * @param bool $auth Indicates whether $wgUser's permissions +// * should be checked +// * @param string $reason The reason for the move +// * @param bool $createRedirect Whether to create a redirect from the old title to the new title. +// * Ignored if the user doesn't have the suppressredirect right. +// * @param array $changeTags Applied to the entry in the move log and redirect page revision +// * @return array|bool True on success, getUserPermissionsErrors()-like array on failure +// */ +// public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true, +// array $changeTags = [] +// ) { +// global $wgUser; +// $err = $this->isValidMoveOperation( $nt, $auth, $reason ); +// if ( is_array( $err ) ) { +// // Auto-block user's IP if the account was "hard" blocked +// $wgUser->spreadAnyEditBlock(); +// return $err; +// } +// // Check suppressredirect permission +// if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) { +// $createRedirect = true; +// } +// +// $mp = new MovePage( $this, $nt ); +// $status = $mp->move( $wgUser, $reason, $createRedirect, $changeTags ); +// if ( $status->isOK() ) { +// return true; +// } else { +// return $status->getErrorsArray(); +// } +// } +// +// /** +// * Move this page's subpages to be subpages of $nt +// * +// * @param Title $nt Move target +// * @param bool $auth Whether $wgUser's permissions should be checked +// * @param string $reason The reason for the move +// * @param bool $createRedirect Whether to create redirects from the old subpages to +// * the new ones Ignored if the user doesn't have the 'suppressredirect' right +// * @param array $changeTags Applied to the entry in the move log and redirect page revision +// * @return array Array with old page titles as keys, and strings (new page titles) or +// * getUserPermissionsErrors()-like arrays (errors) as values, or a +// * getUserPermissionsErrors()-like error array with numeric indices if +// * no pages were moved +// */ +// public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true, +// array $changeTags = [] +// ) { +// global $wgMaximumMovedPages; +// // Check permissions +// if ( !$this->userCan( 'move-subpages' ) ) { +// return [ +// [ 'cant-move-subpages' ], +// ]; +// } +// // Do the source and target namespaces support subpages? +// if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) { +// return [ +// [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $this->mNamespace ) ], +// ]; +// } +// if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) { +// return [ +// [ 'namespace-nosubpages', MWNamespace::getCanonicalName( $nt->getNamespace() ) ], +// ]; +// } // -// // Assume current user if none given -// if (!$user) { -// $user = $wgUser; -// } -// // Check cache first -// $uid = $user.getId(); -// if (!$uid) { -// return false; -// } -// // avoid isset here, as it'll return false for null entries -// if (array_key_exists($uid, this.mNotificationTimestamp)) { -// return this.mNotificationTimestamp[$uid]; -// } -// // Don't cache too much! -// if (count(this.mNotificationTimestamp) >= self::CACHE_MAX) { -// this.mNotificationTimestamp = []; +// $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 ); +// $retval = []; +// $count = 0; +// foreach ( $subpages as $oldSubpage ) { +// $count++; +// if ( $count > $wgMaximumMovedPages ) { +// $retval[$oldSubpage->getPrefixedText()] = [ +// [ 'movepage-max-pages', $wgMaximumMovedPages ], +// ]; +// break; // } // -// $store = MediaWikiServices::getInstance().getWatchedItemStore(); -// $watchedItem = $store.getWatchedItem($user, this); -// if ($watchedItem) { -// this.mNotificationTimestamp[$uid] = $watchedItem.getNotificationTimestamp(); +// // We don't know whether this function was called before +// // or after moving the root page, so check both +// // $this and $nt +// if ( $oldSubpage->getArticleID() == $this->getArticleID() +// || $oldSubpage->getArticleID() == $nt->getArticleID() +// ) { +// // When moving a page to a subpage of itself, +// // don't move it twice +// continue; +// } +// $newPageName = preg_replace( +// '#^' . preg_quote( $this->mDbkeyform, '#' ) . '#', +// StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234 +// $oldSubpage->getDBkey() ); +// if ( $oldSubpage->isTalkPage() ) { +// $newNs = $nt->getTalkPage()->getNamespace(); // } else { -// this.mNotificationTimestamp[$uid] = false; +// $newNs = $nt->getSubjectPage()->getNamespace(); // } +// # T16385: we need makeTitleSafe because the new page names may +// # be longer than 255 characters. +// $newSubpage = self::makeTitleSafe( $newNs, $newPageName ); // -// return this.mNotificationTimestamp[$uid]; -// } -// -// /** -// * Generate strings used for xml 'id' names in monobook tabs -// * -// * @param String $prepend Defaults to 'nstab-' -// * @return String XML 'id' name -// */ -// public function getNamespaceKey($prepend = 'nstab-') { -// global $wgContLang; -// // Gets the subject namespace if this title -// $namespace = XomwNamespace::getSubject(this.getNamespace()); -// // Checks if canonical namespace name exists for namespace -// if (XomwNamespace::exists(this.getNamespace())) { -// // Uses canonical namespace name -// $namespaceKey = XomwNamespace::getCanonicalName($namespace); +// $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect, $changeTags ); +// if ( $success === true ) { +// $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText(); // } else { -// // Uses text of namespace -// $namespaceKey = this.getSubjectNsText(); -// } -// // Makes namespace key lowercase -// $namespaceKey = $wgContLang.lc($namespaceKey); -// // Uses main -// if ($namespaceKey == Bry_.Empty) { -// $namespaceKey = 'main'; -// } -// // Changes file to image for backwards compatibility -// if ($namespaceKey == 'file') { -// $namespaceKey = 'image'; +// $retval[$oldSubpage->getPrefixedText()] = $success; +// } +// } +// return $retval; +// } +// +// /** +// * Checks if this page is just a one-rev redirect. +// * Adds lock, so don't use just for light purposes. +// * +// * @return bool +// */ +// public function isSingleRevRedirect() { +// global $wgContentHandlerUseDB; +// +// $dbw = wfGetDB( DB_MASTER ); +// +// # Is it a redirect? +// $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ]; +// if ( $wgContentHandlerUseDB ) { +// $fields[] = 'page_content_model'; +// } +// +// $row = $dbw->selectRow( 'page', +// $fields, +// $this->pageCond(), +// __METHOD__, +// [ 'FOR UPDATE' ] +// ); +// # Cache some fields we may want +// $this->mArticleID = $row ? intval( $row->page_id ) : 0; +// $this->mRedirect = $row ? (bool)$row->page_is_redirect : false; +// $this->mLatestID = $row ? intval( $row->page_latest ) : false; +// $this->mContentModel = $row && isset( $row->page_content_model ) +// ? strval( $row->page_content_model ) +// : false; +// +// if ( !$this->mRedirect ) { +// return false; +// } +// # Does the article have a history? +// $row = $dbw->selectField( [ 'page', 'revision' ], +// 'rev_id', +// [ 'page_namespace' => $this->mNamespace, +// 'page_title' => $this->mDbkeyform, +// 'page_id=rev_page', +// 'page_latest != rev_id' +// ], +// __METHOD__, +// [ 'FOR UPDATE' ] +// ); +// # Return true if there was no history +// return ( $row === false ); +// } +// +// /** +// * Checks if $this can be moved to a given Title +// * - Selects for update, so don't call it unless you mean business +// * +// * @deprecated since 1.25, use MovePage's methods instead +// * @param Title $nt The new title to check +// * @return bool +// */ +// public function isValidMoveTarget( $nt ) { +// # Is it an existing file? +// if ( $nt->getNamespace() == NS_FILE ) { +// $file = wfLocalFile( $nt ); +// $file->load( File::READ_LATEST ); +// if ( $file->exists() ) { +// wfDebug( __METHOD__ . ": file exists\n" ); +// return false; // } -// return $prepend . $namespaceKey; // } +// # Is it a redirect with no history? +// if ( !$nt->isSingleRevRedirect() ) { +// wfDebug( __METHOD__ . ": not a one-rev redirect\n" ); +// return false; +// } +// # Get the article text +// $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST ); +// if ( !is_object( $rev ) ) { +// return false; +// } +// $content = $rev->getContent(); +// # Does the redirect point to the source? +// # Or is it a broken self-redirect, usually caused by namespace collisions? +// $redirTitle = $content ? $content->getRedirectTarget() : null; // -// /** -// * Get all extant redirects to this Title -// * -// * @param int|null $ns Single namespace to consider; null to consider all namespaces -// * @return Title[] Array of Title redirects to this title -// */ -// public function getRedirectsHere($ns = null) { -// $redirs = []; -// -// $dbr = wfGetDB(DB_REPLICA); -// $where = [ -// 'rd_namespace' => this.getNamespace(), -// 'rd_title' => this.getDBkey(), -// 'rd_from = page_id' -// ]; -// if (this.isExternal()) { -// $where['rd_interwiki'] = this.getInterwiki(); +// if ( $redirTitle ) { +// if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() && +// $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) { +// wfDebug( __METHOD__ . ": redirect points to other page\n" ); +// return false; // } else { -// $where[] = 'rd_interwiki = ' . $dbr.addQuotes(Bry_.Empty) . ' OR rd_interwiki IS NULL'; -// } -// if (!is_null($ns)) { -// $where['page_namespace'] = $ns; +// return true; // } +// } else { +// # Fail safe (not a redirect after all. strange.) +// wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() . +// " is a redirect, but it doesn't contain a valid redirect.\n" ); +// return false; +// } +// } // -// $res = $dbr.select( -// [ 'redirect', 'page' ], -// [ 'page_namespace', 'page_title' ], -// $where, -// __METHOD__ -// ); +// /** +// * Get categories to which this Title belongs and return an array of +// * categories' names. +// * +// * @return array Array of parents in the form: +// * $parent => $currentarticle +// */ +// public function getParentCategories() { +// $data = []; // -// foreach ($res as $row) { -// $redirs[] = self::newFromRow($row); -// } -// return $redirs; -// } +// $titleKey = $this->getArticleID(); // -// /** -// * Check if this Title is a valid redirect target -// * -// * @return boolean -// */ -// public function isValidRedirectTarget() { -// global $wgInvalidRedirectTargets; -// -// if (this.isSpecialPage()) { -// // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here -// if (this.isSpecial('Userlogout')) { -// return false; -// } +// if ( $titleKey === 0 ) { +// return $data; +// } // -// foreach ($wgInvalidRedirectTargets as $target) { -// if (this.isSpecial($target)) { -// return false; +// $dbr = wfGetDB( DB_REPLICA ); +// +// $res = $dbr->select( +// 'categorylinks', +// 'cl_to', +// [ 'cl_from' => $titleKey ], +// __METHOD__ +// ); +// +// if ( $res->numRows() > 0 ) { +// $contLang = MediaWikiServices::getInstance()->getContentLanguage(); +// foreach ( $res as $row ) { +// // $data[] = Title::newFromText( $contLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to); +// $data[$contLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = +// $this->getFullText(); +// } +// } +// return $data; +// } +// +// /** +// * Get a tree of parent categories +// * +// * @param array $children Array with the children in the keys, to check for circular refs +// * @return array Tree of parent categories +// */ +// public function getParentCategoryTree( $children = [] ) { +// $stack = []; +// $parents = $this->getParentCategories(); +// +// if ( $parents ) { +// foreach ( $parents as $parent => $current ) { +// if ( array_key_exists( $parent, $children ) ) { +// # Circular reference +// $stack[$parent] = []; +// } else { +// $nt = self::newFromText( $parent ); +// if ( $nt ) { +// $stack[$parent] = $nt->getParentCategoryTree( $children + [ $parent => 1 ] ); // } // } // } -// -// return true; -// } -// -// /** -// * Get a backlink cache Object -// * -// * @return BacklinkCache -// */ -// public function getBacklinkCache() { -// return BacklinkCache::get(this); -// } -// -// /** -// * Whether the magic words __INDEX__ and __NOINDEX__ function for this page. -// * -// * @return boolean -// */ -// public function canUseNoindex() { -// global $wgExemptFromUserRobotsControl; -// -// $bannedNamespaces = is_null($wgExemptFromUserRobotsControl) -// ? XomwNamespace::getContentNamespaces() -// : $wgExemptFromUserRobotsControl; -// -// return !in_array(this.mNamespace, $bannedNamespaces); // } // -// /** -// * Returns the raw sort key to be used for categories, with the specified -// * prefix. This will be fed to Collation::getSortKey() to get a -// * binary sortkey that can be used for actual sorting. -// * -// * @param String $prefix The prefix to be used, specified using -// * {{defaultsort:}} or like [[Category:Foo|prefix]]. Empty for no -// * prefix. -// * @return String -// */ -// public function getCategorySortkey($prefix = Bry_.Empty) { -// $unprefixed = this.getText(); -// -// // Anything that uses this hook should only depend -// // on the Title Object passed in, and should probably -// // tell the users to run updateCollations.php --force -// // in order to re-sort existing category relations. -// Hooks::run('GetDefaultSortkey', [ this, &$unprefixed ]); -// if ($prefix != Bry_.Empty) { -// // Separate with a line feed, so the unprefixed part is only used as -// // a tiebreaker when two pages have the exact same prefix. -// // In UCA, tab is the only character that can sort above LF -// // so we strip both of them from the original prefix. -// $prefix = strtr($prefix, "\n\t", ' '); -// return "$prefix\n$unprefixed"; +// return $stack; +// } +// +// /** +// * Get an associative array for selecting this title from +// * the "page" table +// * +// * @return array Array suitable for the $where parameter of DB::select() +// */ +// public function pageCond() { +// if ( $this->mArticleID > 0 ) { +// // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs +// return [ 'page_id' => $this->mArticleID ]; +// } else { +// return [ 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ]; +// } +// } +// +// /** +// * Get next/previous revision ID relative to another revision ID +// * @param int $revId Revision ID. Get the revision that was before this one. +// * @param int $flags Title::GAID_FOR_UPDATE +// * @param string $dir 'next' or 'prev' +// * @return int|bool New revision ID, or false if none exists +// */ +// private function getRelativeRevisionID( $revId, $flags, $dir ) { +// $revId = (int)$revId; +// if ( $dir === 'next' ) { +// $op = '>'; +// $sort = 'ASC'; +// } elseif ( $dir === 'prev' ) { +// $op = '<'; +// $sort = 'DESC'; +// } else { +// throw new InvalidArgumentException( '$dir must be "next" or "prev"' ); +// } +// +// if ( $flags & self::GAID_FOR_UPDATE ) { +// $db = wfGetDB( DB_MASTER ); +// } else { +// $db = wfGetDB( DB_REPLICA, 'contributions' ); +// } +// +// // Intentionally not caring if the specified revision belongs to this +// // page. We only care about the timestamp. +// $ts = $db->selectField( 'revision', 'rev_timestamp', [ 'rev_id' => $revId ], __METHOD__ ); +// if ( $ts === false ) { +// $ts = $db->selectField( 'archive', 'ar_timestamp', [ 'ar_rev_id' => $revId ], __METHOD__ ); +// if ( $ts === false ) { +// // Or should this throw an InvalidArgumentException or something? +// return false; // } -// return $unprefixed; // } +// $ts = $db->addQuotes( $ts ); // -// /** -// * Returns the page language code saved in the database, if $wgPageLanguageUseDB is set -// * to true in LocalSettings.php, otherwise returns false. If there is no language saved in -// * the db, it will return NULL. -// * -// * @return String|null|boolean -// */ -// private function getDbPageLanguageCode() { -// global $wgPageLanguageUseDB; -// -// // check, if the page language could be saved in the database, and if so and -// // the value is not requested already, lookup the page language using LinkCache -// if ($wgPageLanguageUseDB && this.mDbPageLanguage == false) { -// $linkCache = LinkCache::singleton(); -// $linkCache.addLinkObj(this); -// this.mDbPageLanguage = $linkCache.getGoodLinkFieldObj(this, 'lang'); -// } +// $revId = $db->selectField( 'revision', 'rev_id', +// [ +// 'rev_page' => $this->getArticleID( $flags ), +// "rev_timestamp $op $ts OR (rev_timestamp = $ts AND rev_id $op $revId)" +// ], +// __METHOD__, +// [ +// 'ORDER BY' => "rev_timestamp $sort, rev_id $sort", +// 'IGNORE INDEX' => 'rev_timestamp', // Probably needed for T159319 +// ] +// ); // -// return this.mDbPageLanguage; +// if ( $revId === false ) { +// return false; +// } else { +// return intval( $revId ); +// } +// } +// +// /** +// * Get the revision ID of the previous revision +// * +// * @param int $revId Revision ID. Get the revision that was before this one. +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return int|bool Old revision ID, or false if none exists +// */ +// public function getPreviousRevisionID( $revId, $flags = 0 ) { +// return $this->getRelativeRevisionID( $revId, $flags, 'prev' ); +// } +// +// /** +// * Get the revision ID of the next revision +// * +// * @param int $revId Revision ID. Get the revision that was after this one. +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return int|bool Next revision ID, or false if none exists +// */ +// public function getNextRevisionID( $revId, $flags = 0 ) { +// return $this->getRelativeRevisionID( $revId, $flags, 'next' ); +// } +// +// /** +// * Get the first revision of the page +// * +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return Revision|null If page doesn't exist +// */ +// public function getFirstRevision( $flags = 0 ) { +// $pageId = $this->getArticleID( $flags ); +// if ( $pageId ) { +// $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_REPLICA ); +// $revQuery = Revision::getQueryInfo(); +// $row = $db->selectRow( $revQuery['tables'], $revQuery['fields'], +// [ 'rev_page' => $pageId ], +// __METHOD__, +// [ +// 'ORDER BY' => 'rev_timestamp ASC, rev_id ASC', +// 'IGNORE INDEX' => [ 'revision' => 'rev_timestamp' ], // See T159319 +// ], +// $revQuery['joins'] +// ); +// if ( $row ) { +// return new Revision( $row, 0, $this ); +// } +// } +// return null; +// } +// +// /** +// * Get the oldest revision timestamp of this page +// * +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return string|null MW timestamp +// */ +// public function getEarliestRevTime( $flags = 0 ) { +// $rev = $this->getFirstRevision( $flags ); +// return $rev ? $rev->getTimestamp() : null; +// } +// +// /** +// * Check if this is a new page +// * +// * @return bool +// */ +// public function isNewPage() { +// $dbr = wfGetDB( DB_REPLICA ); +// return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ ); +// } +// +// /** +// * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit +// * +// * @return bool +// */ +// public function isBigDeletion() { +// global $wgDeleteRevisionsLimit; +// +// if ( !$wgDeleteRevisionsLimit ) { +// return false; // } // -// /** -// * Get the language in which the content of this page is written in -// * wikitext. Defaults to $wgContLang, but in certain cases it can be -// * e.g. $wgLang (such as special pages, which are in the user language). -// * -// * @since 1.18 -// * @return Language -// */ -// public function getPageLanguage() { -// global $wgLang, $wgLanguageCode; -// if (this.isSpecialPage()) { -// // special pages are in the user language -// return $wgLang; -// } +// if ( $this->mIsBigDeletion === null ) { +// $dbr = wfGetDB( DB_REPLICA ); // -// // Checking if DB language is set -// $dbPageLanguage = this.getDbPageLanguageCode(); -// if ($dbPageLanguage) { -// return wfGetLangObj($dbPageLanguage); -// } +// $revCount = $dbr->selectRowCount( +// 'revision', +// '1', +// [ 'rev_page' => $this->getArticleID() ], +// __METHOD__, +// [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ] +// ); // -// if (!this.mPageLanguage || this.mPageLanguage[1] != $wgLanguageCode) { -// // Note that this may depend on user settings, so the cache should -// // be only per-request. -// // NOTE: ContentHandler::getPageLanguage() may need to load the -// // content to determine the page language! -// // Checking $wgLanguageCode hasn't changed for the benefit of unit -// // tests. -// $contentHandler = ContentHandler::getForTitle(this); -// $langObj = $contentHandler.getPageLanguage(this); -// this.mPageLanguage = [ $langObj.getCode(), $wgLanguageCode ]; -// } else { -// $langObj = wfGetLangObj(this.mPageLanguage[0]); +// $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit; +// } +// +// return $this->mIsBigDeletion; +// } +// +// /** +// * Get the approximate revision count of this page. +// * +// * @return int +// */ +// public function estimateRevisionCount() { +// if ( !$this->exists() ) { +// return 0; +// } +// +// if ( $this->mEstimateRevisions === null ) { +// $dbr = wfGetDB( DB_REPLICA ); +// $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*', +// [ 'rev_page' => $this->getArticleID() ], __METHOD__ ); +// } +// +// return $this->mEstimateRevisions; +// } +// +// /** +// * Get the number of revisions between the given revision. +// * Used for diffs and other things that really need it. +// * +// * @param int|Revision $old Old revision or rev ID (first before range) +// * @param int|Revision $new New revision or rev ID (first after range) +// * @param int|null $max Limit of Revisions to count, will be incremented to detect truncations +// * @return int Number of revisions between these revisions. +// */ +// public function countRevisionsBetween( $old, $new, $max = null ) { +// if ( !( $old instanceof Revision ) ) { +// $old = Revision::newFromTitle( $this, (int)$old ); +// } +// if ( !( $new instanceof Revision ) ) { +// $new = Revision::newFromTitle( $this, (int)$new ); +// } +// if ( !$old || !$new ) { +// return 0; // nothing to compare +// } +// $dbr = wfGetDB( DB_REPLICA ); +// $conds = [ +// 'rev_page' => $this->getArticleID(), +// 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ), +// 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) ) +// ]; +// if ( $max !== null ) { +// return $dbr->selectRowCount( 'revision', '1', +// $conds, +// __METHOD__, +// [ 'LIMIT' => $max + 1 ] // extra to detect truncation +// ); +// } else { +// return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ ); +// } +// } +// +// /** +// * Get the authors between the given revisions or revision IDs. +// * Used for diffs and other things that really need it. +// * +// * @since 1.23 +// * +// * @param int|Revision $old Old revision or rev ID (first before range by default) +// * @param int|Revision $new New revision or rev ID (first after range by default) +// * @param int $limit Maximum number of authors +// * @param string|array $options (Optional): Single option, or an array of options: +// * 'include_old' Include $old in the range; $new is excluded. +// * 'include_new' Include $new in the range; $old is excluded. +// * 'include_both' Include both $old and $new in the range. +// * Unknown option values are ignored. +// * @return array|null Names of revision authors in the range; null if not both revisions exist +// */ +// public function getAuthorsBetween( $old, $new, $limit, $options = [] ) { +// if ( !( $old instanceof Revision ) ) { +// $old = Revision::newFromTitle( $this, (int)$old ); +// } +// if ( !( $new instanceof Revision ) ) { +// $new = Revision::newFromTitle( $this, (int)$new ); +// } +// // XXX: what if Revision objects are passed in, but they don't refer to this title? +// // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID() +// // in the sanity check below? +// if ( !$old || !$new ) { +// return null; // nothing to compare +// } +// $authors = []; +// $old_cmp = '>'; +// $new_cmp = '<'; +// $options = (array)$options; +// if ( in_array( 'include_old', $options ) ) { +// $old_cmp = '>='; +// } +// if ( in_array( 'include_new', $options ) ) { +// $new_cmp = '<='; +// } +// if ( in_array( 'include_both', $options ) ) { +// $old_cmp = '>='; +// $new_cmp = '<='; +// } +// // No DB query needed if $old and $new are the same or successive revisions: +// if ( $old->getId() === $new->getId() ) { +// return ( $old_cmp === '>' && $new_cmp === '<' ) ? +// [] : +// [ $old->getUserText( Revision::RAW ) ]; +// } elseif ( $old->getId() === $new->getParentId() ) { +// if ( $old_cmp === '>=' && $new_cmp === '<=' ) { +// $authors[] = $old->getUserText( Revision::RAW ); +// if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) { +// $authors[] = $new->getUserText( Revision::RAW ); +// } +// } elseif ( $old_cmp === '>=' ) { +// $authors[] = $old->getUserText( Revision::RAW ); +// } elseif ( $new_cmp === '<=' ) { +// $authors[] = $new->getUserText( Revision::RAW ); // } +// return $authors; +// } +// $dbr = wfGetDB( DB_REPLICA ); +// $revQuery = Revision::getQueryInfo(); +// $authors = $dbr->selectFieldValues( +// $revQuery['tables'], +// $revQuery['fields']['rev_user_text'], +// [ +// 'rev_page' => $this->getArticleID(), +// "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ), +// "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) ) +// ], __METHOD__, +// [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated +// $revQuery['joins'] +// ); +// return $authors; +// } +// +// /** +// * Get the number of authors between the given revisions or revision IDs. +// * Used for diffs and other things that really need it. +// * +// * @param int|Revision $old Old revision or rev ID (first before range by default) +// * @param int|Revision $new New revision or rev ID (first after range by default) +// * @param int $limit Maximum number of authors +// * @param string|array $options (Optional): Single option, or an array of options: +// * 'include_old' Include $old in the range; $new is excluded. +// * 'include_new' Include $new in the range; $old is excluded. +// * 'include_both' Include both $old and $new in the range. +// * Unknown option values are ignored. +// * @return int Number of revision authors in the range; zero if not both revisions exist +// */ +// public function countAuthorsBetween( $old, $new, $limit, $options = [] ) { +// $authors = $this->getAuthorsBetween( $old, $new, $limit, $options ); +// return $authors ? count( $authors ) : 0; +// } +// +// /** +// * Compare with another title. +// * +// * @param Title $title +// * @return bool +// */ +// public function equals( Title $title ) { +// // Note: === is necessary for proper matching of number-like titles. +// return $this->mInterwiki === $title->mInterwiki +// && $this->mNamespace == $title->mNamespace +// && $this->mDbkeyform === $title->mDbkeyform; +// } +// +// /** +// * Check if this title is a subpage of another title +// * +// * @param Title $title +// * @return bool +// */ +// public function isSubpageOf( Title $title ) { +// return $this->mInterwiki === $title->mInterwiki +// && $this->mNamespace == $title->mNamespace +// && strpos( $this->mDbkeyform, $title->mDbkeyform . '/' ) === 0; +// } +// +// /** +// * Check if page exists. For historical reasons, this function simply +// * checks for the existence of the title in the page table, and will +// * thus return false for interwiki links, special pages and the like. +// * If you want to know if a title can be meaningfully viewed, you should +// * probably call the isKnown() method instead. +// * +// * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check +// * from master/for update +// * @return bool +// */ +// public function exists( $flags = 0 ) { +// $exists = $this->getArticleID( $flags ) != 0; +// Hooks::run( 'TitleExists', [ $this, &$exists ] ); +// return $exists; +// } +// +// /** +// * Should links to this title be shown as potentially viewable (i.e. as +// * "bluelinks"), even if there's no record by this title in the page +// * table? +// * +// * This function is semi-deprecated for public use, as well as somewhat +// * misleadingly named. You probably just want to call isKnown(), which +// * calls this function internally. +// * +// * (ISSUE: Most of these checks are cheap, but the file existence check +// * can potentially be quite expensive. Including it here fixes a lot of +// * existing code, but we might want to add an optional parameter to skip +// * it and any other expensive checks.) +// * +// * @return bool +// */ +// public function isAlwaysKnown() { +// $isKnown = null; +// +// /** +// * Allows overriding default behavior for determining if a page exists. +// * If $isKnown is kept as null, regular checks happen. If it's +// * a boolean, this value is returned by the isKnown method. +// * +// * @since 1.20 +// * +// * @param Title $title +// * @param bool|null $isKnown +// */ +// Hooks::run( 'TitleIsAlwaysKnown', [ $this, &$isKnown ] ); +// +// if ( !is_null( $isKnown ) ) { +// return $isKnown; +// } +// +// if ( $this->isExternal() ) { +// return true; // any interwiki link might be viewable, for all we know +// } +// +// switch ( $this->mNamespace ) { +// case NS_MEDIA: +// case NS_FILE: +// // file exists, possibly in a foreign repo +// return (bool)wfFindFile( $this ); +// case NS_SPECIAL: +// // valid special page +// return MediaWikiServices::getInstance()->getSpecialPageFactory()-> +// exists( $this->mDbkeyform ); +// case NS_MAIN: +// // selflink, possibly with fragment +// return $this->mDbkeyform == ''; +// case NS_MEDIAWIKI: +// // known system message +// return $this->hasSourceText() !== false; +// default: +// return false; +// } +// } +// +// /** +// * Does this title refer to a page that can (or might) be meaningfully +// * viewed? In particular, this function may be used to determine if +// * links to the title should be rendered as "bluelinks" (as opposed to +// * "redlinks" to non-existent pages). +// * Adding something else to this function will cause inconsistency +// * since LinkHolderArray calls isAlwaysKnown() and does its own +// * page existence check. +// * +// * @return bool +// */ +// public function isKnown() { +// return $this->isAlwaysKnown() || $this->exists(); +// } +// +// /** +// * Does this page have source text? +// * +// * @return bool +// */ +// public function hasSourceText() { +// if ( $this->exists() ) { +// return true; +// } // -// return $langObj; +// if ( $this->mNamespace == NS_MEDIAWIKI ) { +// // If the page doesn't exist but is a known system message, default +// // message content will be displayed, same for language subpages- +// // Use always content language to avoid loading hundreds of languages +// // to get the link color. +// $contLang = MediaWikiServices::getInstance()->getContentLanguage(); +// list( $name, ) = MessageCache::singleton()->figureMessage( +// $contLang->lcfirst( $this->getText() ) +// ); +// $message = wfMessage( $name )->inLanguage( $contLang )->useDatabase( false ); +// return $message->exists(); +// } +// +// return false; +// } +// +// /** +// * Get the default (plain) message contents for an page that overrides an +// * interface message key. +// * +// * Primary use cases: +// * +// * - Article: +// * - Show default when viewing the page. The Article::getSubstituteContent +// * method displays the default message content, instead of the +// * 'noarticletext' placeholder message normally used. +// * +// * - EditPage: +// * - Title of edit page. When creating an interface message override, +// * the editor is told they are "Editing the page", instead of +// * "Creating the page". (EditPage::setHeaders) +// * - Edit notice. The 'translateinterface' edit notice is shown when creating +// * or editing a an interface message override. (EditPage::showIntro) +// * - Opening the editor. The contents of the localisation message are used +// * as contents of the editor when creating a new page in the MediaWiki +// * namespace. This simplifies the process for editors when "changing" +// * an interface message by creating an override. (EditPage::getContentObject) +// * - Showing a diff. The left-hand side of a diff when an editor is +// * previewing their changes before saving the creation of a page in the +// * MediaWiki namespace. (EditPage::showDiff) +// * - Disallowing a save. When attempting to create a a MediaWiki-namespace +// * page with the proposed content matching the interface message default, +// * the save is rejected, the same way we disallow blank pages from being +// * created. (EditPage::internalAttemptSave) +// * +// * - ApiEditPage: +// * - Default content, when using the 'prepend' or 'append' feature. +// * +// * - SkinTemplate: +// * - Label the create action as "Edit", if the page can be an override. +// * +// * @return string|bool +// */ +// public function getDefaultMessageText() { +// if ( $this->mNamespace != NS_MEDIAWIKI ) { // Just in case +// return false; // } // -// /** -// * Get the language in which the content of this page is written when -// * viewed by user. Defaults to $wgContLang, but in certain cases it can be -// * e.g. $wgLang (such as special pages, which are in the user language). -// * -// * @since 1.20 -// * @return Language -// */ -// public function getPageViewLanguage() { -// global $wgLang; -// -// if (this.isSpecialPage()) { -// // If the user chooses a variant, the content is actually -// // in a language whose code is the variant code. -// $variant = $wgLang.getPreferredVariant(); -// if ($wgLang.getCode() != $variant) { -// return Language::factory($variant); -// } +// list( $name, $lang ) = MessageCache::singleton()->figureMessage( +// MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $this->getText() ) +// ); +// $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false ); +// +// if ( $message->exists() ) { +// return $message->plain(); +// } else { +// return false; +// } +// } // -// return $wgLang; +// /** +// * Updates page_touched for this page; called from LinksUpdate.php +// * +// * @param string|null $purgeTime [optional] TS_MW timestamp +// * @return bool True if the update succeeded +// */ +// public function invalidateCache( $purgeTime = null ) { +// if ( wfReadOnly() ) { +// return false; +// } elseif ( $this->mArticleID === 0 ) { +// return true; // avoid gap locking if we know it's not there +// } +// +// $dbw = wfGetDB( DB_MASTER ); +// $dbw->onTransactionPreCommitOrIdle( +// function () use ( $dbw ) { +// ResourceLoaderWikiModule::invalidateModuleCache( +// $this, null, null, $dbw->getDomainID() ); +// }, +// __METHOD__ +// ); +// +// $conds = $this->pageCond(); +// DeferredUpdates::addUpdate( +// new AutoCommitUpdate( +// $dbw, +// __METHOD__, +// function ( IDatabase $dbw, $fname ) use ( $conds, $purgeTime ) { +// $dbTimestamp = $dbw->timestamp( $purgeTime ?: time() ); +// $dbw->update( +// 'page', +// [ 'page_touched' => $dbTimestamp ], +// $conds + [ 'page_touched < ' . $dbw->addQuotes( $dbTimestamp ) ], +// $fname +// ); +// MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $this ); +// } +// ), +// DeferredUpdates::PRESEND +// ); +// +// return true; +// } +// +// /** +// * Update page_touched timestamps and send CDN purge messages for +// * pages linking to this title. May be sent to the job queue depending +// * on the number of links. Typically called on create and delete. +// */ +// public function touchLinks() { +// DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks', 'page-touch' ) ); +// if ( $this->mNamespace == NS_CATEGORY ) { +// DeferredUpdates::addUpdate( +// new HTMLCacheUpdate( $this, 'categorylinks', 'category-touch' ) +// ); +// } +// } +// +// /** +// * Get the last touched timestamp +// * +// * @param IDatabase|null $db +// * @return string|false Last-touched timestamp +// */ +// public function getTouched( $db = null ) { +// if ( $db === null ) { +// $db = wfGetDB( DB_REPLICA ); +// } +// $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ ); +// return $touched; +// } +// +// /** +// * Get the timestamp when this page was updated since the user last saw it. +// * +// * @param User|null $user +// * @return string|null +// */ +// public function getNotificationTimestamp( $user = null ) { +// global $wgUser; +// +// // Assume current user if none given +// if ( !$user ) { +// $user = $wgUser; +// } +// // Check cache first +// $uid = $user->getId(); +// if ( !$uid ) { +// return false; +// } +// // avoid isset here, as it'll return false for null entries +// if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) { +// return $this->mNotificationTimestamp[$uid]; +// } +// // Don't cache too much! +// if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) { +// $this->mNotificationTimestamp = []; +// } +// +// $store = MediaWikiServices::getInstance()->getWatchedItemStore(); +// $watchedItem = $store->getWatchedItem( $user, $this ); +// if ( $watchedItem ) { +// $this->mNotificationTimestamp[$uid] = $watchedItem->getNotificationTimestamp(); +// } else { +// $this->mNotificationTimestamp[$uid] = false; +// } +// +// return $this->mNotificationTimestamp[$uid]; +// } +// +// /** +// * Generate strings used for xml 'id' names in monobook tabs +// * +// * @param string $prepend Defaults to 'nstab-' +// * @return string XML 'id' name +// */ +// public function getNamespaceKey( $prepend = 'nstab-' ) { +// // Gets the subject namespace of this title +// $subjectNS = MWNamespace::getSubject( $this->mNamespace ); +// // Prefer canonical namespace name for HTML IDs +// $namespaceKey = MWNamespace::getCanonicalName( $subjectNS ); +// if ( $namespaceKey === false ) { +// // Fallback to localised text +// $namespaceKey = $this->getSubjectNsText(); +// } +// // Makes namespace key lowercase +// $namespaceKey = MediaWikiServices::getInstance()->getContentLanguage()->lc( $namespaceKey ); +// // Uses main +// if ( $namespaceKey == '' ) { +// $namespaceKey = 'main'; +// } +// // Changes file to image for backwards compatibility +// if ( $namespaceKey == 'file' ) { +// $namespaceKey = 'image'; +// } +// return $prepend . $namespaceKey; +// } +// +// /** +// * Get all extant redirects to this Title +// * +// * @param int|null $ns Single namespace to consider; null to consider all namespaces +// * @return Title[] Array of Title redirects to this title +// */ +// public function getRedirectsHere( $ns = null ) { +// $redirs = []; +// +// $dbr = wfGetDB( DB_REPLICA ); +// $where = [ +// 'rd_namespace' => $this->mNamespace, +// 'rd_title' => $this->mDbkeyform, +// 'rd_from = page_id' +// ]; +// if ( $this->isExternal() ) { +// $where['rd_interwiki'] = $this->mInterwiki; +// } else { +// $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL'; +// } +// if ( !is_null( $ns ) ) { +// $where['page_namespace'] = $ns; +// } +// +// $res = $dbr->select( +// [ 'redirect', 'page' ], +// [ 'page_namespace', 'page_title' ], +// $where, +// __METHOD__ +// ); +// +// foreach ( $res as $row ) { +// $redirs[] = self::newFromRow( $row ); +// } +// return $redirs; +// } +// +// /** +// * Check if this Title is a valid redirect target +// * +// * @return bool +// */ +// public function isValidRedirectTarget() { +// global $wgInvalidRedirectTargets; +// +// if ( $this->isSpecialPage() ) { +// // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here +// if ( $this->isSpecial( 'Userlogout' ) ) { +// return false; // } // -// // Checking if DB language is set -// $dbPageLanguage = this.getDbPageLanguageCode(); -// if ($dbPageLanguage) { -// $pageLang = wfGetLangObj($dbPageLanguage); -// $variant = $pageLang.getPreferredVariant(); -// if ($pageLang.getCode() != $variant) { -// $pageLang = Language::factory($variant); +// foreach ( $wgInvalidRedirectTargets as $target ) { +// if ( $this->isSpecial( $target ) ) { +// return false; // } +// } +// } // -// return $pageLang; +// return true; +// } +// +// /** +// * Get a backlink cache object +// * +// * @return BacklinkCache +// */ +// public function getBacklinkCache() { +// return BacklinkCache::get( $this ); +// } +// +// /** +// * Whether the magic words __INDEX__ and __NOINDEX__ function for this page. +// * +// * @return bool +// */ +// public function canUseNoindex() { +// global $wgExemptFromUserRobotsControl; +// +// $bannedNamespaces = $wgExemptFromUserRobotsControl ?? MWNamespace::getContentNamespaces(); +// +// return !in_array( $this->mNamespace, $bannedNamespaces ); +// } +// +// /** +// * Returns the raw sort key to be used for categories, with the specified +// * prefix. This will be fed to Collation::getSortKey() to get a +// * binary sortkey that can be used for actual sorting. +// * +// * @param string $prefix The prefix to be used, specified using +// * {{defaultsort:}} or like [[Category:Foo|prefix]]. Empty for no +// * prefix. +// * @return string +// */ +// public function getCategorySortkey( $prefix = '' ) { +// $unprefixed = $this->getText(); +// +// // Anything that uses this hook should only depend +// // on the Title object passed in, and should probably +// // tell the users to run updateCollations.php --force +// // in order to re-sort existing category relations. +// Hooks::run( 'GetDefaultSortkey', [ $this, &$unprefixed ] ); +// if ( $prefix !== '' ) { +// # Separate with a line feed, so the unprefixed part is only used as +// # a tiebreaker when two pages have the exact same prefix. +// # In UCA, tab is the only character that can sort above LF +// # so we strip both of them from the original prefix. +// $prefix = strtr( $prefix, "\n\t", ' ' ); +// return "$prefix\n$unprefixed"; +// } +// return $unprefixed; +// } +// +// /** +// * Returns the page language code saved in the database, if $wgPageLanguageUseDB is set +// * to true in LocalSettings.php, otherwise returns false. If there is no language saved in +// * the db, it will return NULL. +// * +// * @return string|null|bool +// */ +// private function getDbPageLanguageCode() { +// global $wgPageLanguageUseDB; +// +// // check, if the page language could be saved in the database, and if so and +// // the value is not requested already, lookup the page language using LinkCache +// if ( $wgPageLanguageUseDB && $this->mDbPageLanguage === false ) { +// $linkCache = MediaWikiServices::getInstance()->getLinkCache(); +// $linkCache->addLinkObj( $this ); +// $this->mDbPageLanguage = $linkCache->getGoodLinkFieldObj( $this, 'lang' ); +// } +// +// return $this->mDbPageLanguage; +// } +// +// /** +// * Get the language in which the content of this page is written in +// * wikitext. Defaults to content language, but in certain cases it can be +// * e.g. $wgLang (such as special pages, which are in the user language). +// * +// * @since 1.18 +// * @return Language +// */ +// public function getPageLanguage() { +// global $wgLang, $wgLanguageCode; +// if ( $this->isSpecialPage() ) { +// // special pages are in the user language +// return $wgLang; +// } +// +// // Checking if DB language is set +// $dbPageLanguage = $this->getDbPageLanguageCode(); +// if ( $dbPageLanguage ) { +// return wfGetLangObj( $dbPageLanguage ); +// } +// +// if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) { +// // Note that this may depend on user settings, so the cache should +// // be only per-request. +// // NOTE: ContentHandler::getPageLanguage() may need to load the +// // content to determine the page language! +// // Checking $wgLanguageCode hasn't changed for the benefit of unit +// // tests. +// $contentHandler = ContentHandler::getForTitle( $this ); +// $langObj = $contentHandler->getPageLanguage( $this ); +// $this->mPageLanguage = [ $langObj->getCode(), $wgLanguageCode ]; +// } else { +// $langObj = Language::factory( $this->mPageLanguage[0] ); +// } +// +// return $langObj; +// } +// +// /** +// * Get the language in which the content of this page is written when +// * viewed by user. Defaults to content language, but in certain cases it can be +// * e.g. $wgLang (such as special pages, which are in the user language). +// * +// * @since 1.20 +// * @return Language +// */ +// public function getPageViewLanguage() { +// global $wgLang; +// +// if ( $this->isSpecialPage() ) { +// // If the user chooses a variant, the content is actually +// // in a language whose code is the variant code. +// $variant = $wgLang->getPreferredVariant(); +// if ( $wgLang->getCode() !== $variant ) { +// return Language::factory( $variant ); +// } +// +// return $wgLang; +// } +// +// // Checking if DB language is set +// $dbPageLanguage = $this->getDbPageLanguageCode(); +// if ( $dbPageLanguage ) { +// $pageLang = wfGetLangObj( $dbPageLanguage ); +// $variant = $pageLang->getPreferredVariant(); +// if ( $pageLang->getCode() !== $variant ) { +// $pageLang = Language::factory( $variant ); // } // -// // @note Can't be cached persistently, depends on user settings. -// // @note ContentHandler::getPageViewLanguage() may need to load the -// // content to determine the page language! -// $contentHandler = ContentHandler::getForTitle(this); -// $pageLang = $contentHandler.getPageViewLanguage(this); // return $pageLang; // } // -// /** -// * Get a list of rendered edit notices for this page. -// * -// * Array is keyed by the original message key, and values are rendered using parseAsBlock, so -// * they will already be wrapped in paragraphs. -// * -// * @since 1.21 -// * @param int $oldid Revision ID that's being edited -// * @return array -// */ -// public function getEditNotices($oldid = 0) { -// $notices = []; -// -// // Optional notice for the entire namespace -// $editnotice_ns = 'editnotice-' . this.getNamespace(); -// $msg = wfMessage($editnotice_ns); -// if ($msg.exists()) { -// $html = $msg.parseAsBlock(); -// // Edit notices may have complex logic, but output nothing (T91715) -// if (trim($html) != Bry_.Empty) { -// $notices[$editnotice_ns] = Html::rawElement( -// 'div', -// [ 'class' => [ -// 'mw-editnotice', -// 'mw-editnotice-namespace', -// Sanitizer::escapeClass("mw-$editnotice_ns") -// ] ], -// $html -// ); -// } +// // @note Can't be cached persistently, depends on user settings. +// // @note ContentHandler::getPageViewLanguage() may need to load the +// // content to determine the page language! +// $contentHandler = ContentHandler::getForTitle( $this ); +// $pageLang = $contentHandler->getPageViewLanguage( $this ); +// return $pageLang; +// } +// +// /** +// * Get a list of rendered edit notices for this page. +// * +// * Array is keyed by the original message key, and values are rendered using parseAsBlock, so +// * they will already be wrapped in paragraphs. +// * +// * @since 1.21 +// * @param int $oldid Revision ID that's being edited +// * @return array +// */ +// public function getEditNotices( $oldid = 0 ) { +// $notices = []; +// +// // Optional notice for the entire namespace +// $editnotice_ns = 'editnotice-' . $this->mNamespace; +// $msg = wfMessage( $editnotice_ns ); +// if ( $msg->exists() ) { +// $html = $msg->parseAsBlock(); +// // Edit notices may have complex logic, but output nothing (T91715) +// if ( trim( $html ) !== '' ) { +// $notices[$editnotice_ns] = Html::rawElement( +// 'div', +// [ 'class' => [ +// 'mw-editnotice', +// 'mw-editnotice-namespace', +// Sanitizer::escapeClass( "mw-$editnotice_ns" ) +// ] ], +// $html +// ); // } +// } // -// if (XomwNamespace::hasSubpages(this.getNamespace())) { -// // Optional notice for page itself and any parent page -// $parts = explode('/', this.getDBkey()); -// $editnotice_base = $editnotice_ns; -// while (count($parts) > 0) { -// $editnotice_base .= '-' . array_shift($parts); -// $msg = wfMessage($editnotice_base); -// if ($msg.exists()) { -// $html = $msg.parseAsBlock(); -// if (trim($html) != Bry_.Empty) { -// $notices[$editnotice_base] = Html::rawElement( -// 'div', -// [ 'class' => [ -// 'mw-editnotice', -// 'mw-editnotice-super', -// Sanitizer::escapeClass("mw-$editnotice_base") -// ] ], -// $html -// ); -// } -// } -// } -// } else { -// // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys -// $editnoticeText = $editnotice_ns . '-' . strtr(this.getDBkey(), '/', '-'); -// $msg = wfMessage($editnoticeText); -// if ($msg.exists()) { -// $html = $msg.parseAsBlock(); -// if (trim($html) != Bry_.Empty) { -// $notices[$editnoticeText] = Html::rawElement( +// if ( MWNamespace::hasSubpages( $this->mNamespace ) ) { +// // Optional notice for page itself and any parent page +// $editnotice_base = $editnotice_ns; +// foreach ( explode( '/', $this->mDbkeyform ) as $part ) { +// $editnotice_base .= '-' . $part; +// $msg = wfMessage( $editnotice_base ); +// if ( $msg->exists() ) { +// $html = $msg->parseAsBlock(); +// if ( trim( $html ) !== '' ) { +// $notices[$editnotice_base] = Html::rawElement( // 'div', // [ 'class' => [ -// 'mw-editnotice', -// 'mw-editnotice-page', -// Sanitizer::escapeClass("mw-$editnoticeText") +// 'mw-editnotice', +// 'mw-editnotice-base', +// Sanitizer::escapeClass( "mw-$editnotice_base" ) // ] ], -// $html +// $html // ); // } // } // } -// -// Hooks::run('TitleGetEditNotices', [ this, $oldid, &$notices ]); -// return $notices; +// } else { +// // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys +// $editnoticeText = $editnotice_ns . '-' . strtr( $this->mDbkeyform, '/', '-' ); +// $msg = wfMessage( $editnoticeText ); +// if ( $msg->exists() ) { +// $html = $msg->parseAsBlock(); +// if ( trim( $html ) !== '' ) { +// $notices[$editnoticeText] = Html::rawElement( +// 'div', +// [ 'class' => [ +// 'mw-editnotice', +// 'mw-editnotice-page', +// Sanitizer::escapeClass( "mw-$editnoticeText" ) +// ] ], +// $html +// ); +// } +// } // } // -// /** -// * @return array -// */ -// public function __sleep() { -// return [ -// 'mNamespace', -// 'mDbkeyform', -// 'mFragment', -// 'mInterwiki', -// 'mLocalInterwiki', -// 'mUserCaseDBKey', -// 'mDefaultNamespace', -// ]; -// } - - // public function __wakeup() { - // this.mArticleID = (this.mNamespace >= 0) ? -1 : 0; - // this.mUrlform = wfUrlencode(this.mDbkeyform); - // this.mTextform = strtr(this.mDbkeyform, '_', ' '); - // } - private static final byte[] Bry__wgArticlePath__wiki = Bry_.new_a7("/wiki/"); - - // REF.MW: DefaultSettings.php - // Allowed title characters -- regex character class - // Don't change this unless you know what you're doing - // - // Problematic punctuation: - // - []{}|# Are needed for link syntax, never enable these - // - <> Causes problems with HTML escaping, don't use - // - % Enabled by default, minor problems with path to query rewrite rules, see below - // - + Enabled by default, but doesn't work with path to query rewrite rules, - // corrupted by apache - // - ? Enabled by default, but doesn't work with path to PATH_INFO rewrites - // - // All three of these punctuation problems can be avoided by using an alias, - // instead of a rewrite rule of either variety. - // - // The problem with % is that when using a path to query rewrite rule, URLs are - // double-unescaped: once by Apache's path conversion code, and again by PHP. So - // %253F, for example, becomes "?". Our code does not double-escape to compensate - // for this, indeed double escaping would break if the double-escaped title was - // passed in the query String rather than the path. This is a minor security issue - // because articles can be created such that they are hard to view or edit. - // - // In some rare cases you may wish to remove + for compatibility with old links. - // - // Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but - // this breaks interlanguage links - // $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+"; - // - // REGEX: - // without-backslash escaping --> \s%!"$&'()*,-./0-9:;=?@A-Z\^_`a-z~x80-xFF+ - // rearranged - // letters --> 0-9A-Za-z - // unicode-chars --> x80-xFF - // symbols --> \s%!"$&'()*,-./:;=?@\^_`~+" - // deliberately ignores - // control chars: 00-31,127 - // []{}|#<> - public static int Find_fwd_while_title(byte[] src, int src_bgn, int src_end, boolean[] valid) { - int cur = src_bgn; - while (true) { - if (cur == src_end) break; - byte b = src[cur]; - int b_len = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(b); - if (b_len == 1) { // ASCII - if (valid[b & 0xFF]) // valid; EX: "a0A B&$"; PATCH.JAVA:need to convert to unsigned byte - cur++; - else // invalid; EX: "" - break; - } - else { // Multi-byte UTF8; NOTE: all sequences are valid - cur += b_len; - } - } - return cur; - } - private static boolean[] title_chars_valid; - public static boolean[] Title_chars_valid() { - if (title_chars_valid == null) { - title_chars_valid = new boolean[128]; - // add num and alpha - for (int i = Byte_ascii.Num_0; i <= Byte_ascii.Num_9; i++) - title_chars_valid[i] = true; - for (int i = Byte_ascii.Ltr_A; i <= Byte_ascii.Ltr_Z; i++) - title_chars_valid[i] = true; - for (int i = Byte_ascii.Ltr_a; i <= Byte_ascii.Ltr_z; i++) - title_chars_valid[i] = true; +// Hooks::run( 'TitleGetEditNotices', [ $this, $oldid, &$notices ] ); +// return $notices; +// } +// +// /** +// * @return array +// */ +// public function __sleep() { +// return [ +// 'mNamespace', +// 'mDbkeyform', +// 'mFragment', +// 'mInterwiki', +// 'mLocalInterwiki', +// 'mUserCaseDBKey', +// 'mDefaultNamespace', +// ]; +// } +// +// public function __wakeup() { +// $this->mArticleID = ( $this->mNamespace >= 0 ) ? -1 : 0; +// $this->mUrlform = wfUrlencode( $this->mDbkeyform ); +// $this->mTextform = strtr( $this->mDbkeyform, '_', ' ' ); +// } - // add symbols: \s%!"$&'()*,-./:;=?@\^_`~+" - byte[] symbols = new byte[] - { Byte_ascii.Space - , Byte_ascii.Percent - , Byte_ascii.Bang - , Byte_ascii.Quote - , Byte_ascii.Amp - , Byte_ascii.Apos - , Byte_ascii.Paren_bgn - , Byte_ascii.Paren_end - , Byte_ascii.Star - , Byte_ascii.Comma - , Byte_ascii.Dash - , Byte_ascii.Dot - , Byte_ascii.Slash - , Byte_ascii.Colon - , Byte_ascii.Semic - , Byte_ascii.Eq - , Byte_ascii.Question - , Byte_ascii.At - , Byte_ascii.Backslash - , Byte_ascii.Pow - , Byte_ascii.Underline - , Byte_ascii.Tick - , Byte_ascii.Tilde - , Byte_ascii.Plus - }; - int symbols_len = symbols.length; - for (int i = 0; i < symbols_len; i++) - title_chars_valid[symbols[i]] = true; - } - return title_chars_valid; - } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitleOld.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitleOld.java new file mode 100644 index 000000000..fd473f62e --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitleOld.java @@ -0,0 +1,4949 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes; import gplx.*; +import gplx.xowa.mediawiki.*; +import gplx.xowa.mediawiki.includes.linkers.XomwLinkTarget; +import gplx.xowa.mediawiki.includes.title.*; +/** +* Represents a title within MediaWiki. +* Optionally may contain an interwiki designation or namespace. +* @note This class can fetch various kinds of data from the database; +* however, it does so inefficiently. +* @note Consider using a TitleValue Object instead. TitleValue is more lightweight +* and does not rely on global state or the database. +*/ +public class XomwTitleOld implements XomwLinkTarget { +// /** @var HashBagOStuff */ +// static private $titleCache = null; +// +// /** +// * Title::newFromText maintains a cache to avoid expensive re-normalization of +// * commonly used titles. On a batch operation this can become a memory leak +// * if not bounded. After hitting this many titles reset the cache. +// */ +// static final CACHE_MAX = 1000; +// +// /** +// * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends +// * to use the master DB +// */ +// static final GAID_FOR_UPDATE = 1; +// +// /** +// * @name Private member variables +// * Please use the accessor functions instead. +// * @private +// */ +// // @{ + + /** @var String Text form (spaces not underscores) of the main part */ + private byte[] mTextform = Bry_.Empty; + + /** @var String URL-encoded form of the main part */ + private byte[] mUrlform = Bry_.Empty; + + /** @var String Main part with underscores */ + // XO: EX: "Help_talk:A_b" . "A_b" + private byte[] mDbkeyform = Bry_.Empty; + + /** @var String Database key with the initial letter in the case specified by the user */ + protected byte[] mUserCaseDBKey; + + /** @var int Namespace index, i.e. one of the NS_xxxx constants */ + public int mNamespace = 0; + + /** @var String Interwiki prefix */ + public byte[] mInterwiki = Bry_.Empty; + + /** @var boolean Was this Title created from a String with a local interwiki prefix? */ + private boolean mLocalInterwiki = false; + + /** @var String Title fragment (i.e. the bit after the #) */ + private byte[] mFragment = Bry_.Empty; + + /** @var int Article ID, fetched from the link cache on demand */ + public int mArticleID = -1; + +// /** @var boolean|int ID of most recent revision */ +// protected $mLatestID = false; +// +// /** +// * @var boolean|String ID of the page's content model, i.e. one of the +// * CONTENT_MODEL_XXX constants +// */ +// private $mContentModel = false; +// +// /** +// * @var boolean If a content model was forced via setContentModel() +// * this will be true to avoid having other code paths reset it +// */ +// private $mForcedContentModel = false; +// +// /** @var int Estimated number of revisions; null of not loaded */ +// private $mEstimateRevisions; +// +// /** @var array Array of groups allowed to edit this article */ +// public $mRestrictions = []; +// +// /** @var String|boolean */ +// protected $mOldRestrictions = false; +// +// /** @var boolean Cascade restrictions on this page to included templates and images? */ +// public $mCascadeRestriction; +// +// /** Caching the results of getCascadeProtectionSources */ +// public $mCascadingRestrictions; +// +// /** @var array When do the restrictions on this page expire? */ +// protected $mRestrictionsExpiry = []; +// +// /** @var boolean Are cascading restrictions in effect on this page? */ +// protected $mHasCascadingRestrictions; +// +// /** @var array Where are the cascading restrictions coming from on this page? */ +// public $mCascadeSources; +// +// /** @var boolean Boolean for initialisation on demand */ +// public $mRestrictionsLoaded = false; + + /** @var String Text form including namespace/interwiki, initialised on demand */ + private byte[] mPrefixedText = null; + +// /** @var mixed Cached value for getTitleProtection (create protection) */ +// public $mTitleProtection; + + /** + * @var int Namespace index when there is no namespace. Don't change the + * following default, NS_MAIN is hardcoded in several places. See bug 696. + * Zero except in {{transclusion}} tags. + */ + public int mDefaultNamespace = XomwDefines.NS_MAIN; + +// /** @var int The page length, 0 for special pages */ +// protected $mLength = -1; +// +// /** @var null Is the article at this title a redirect? */ +// public $mRedirect = null; +// +// /** @var array Associative array of user ID . timestamp/false */ +// private $mNotificationTimestamp = []; +// +// /** @var boolean Whether a page has any subpages */ +// private $mHasSubpages; +// +// /** @var boolean The (String) language code of the page's language and content code. */ +// private $mPageLanguage = false; +// +// /** @var String|boolean|null The page language code from the database, null if not saved in +// * the database or false if not loaded, yet. */ +// private $mDbPageLanguage = false; +// +// /** @var TitleValue A corresponding TitleValue Object */ +// private $mTitleValue = null; +// +// /** @var boolean Would deleting this page be a big deletion? */ +// private $mIsBigDeletion = null; +// // @} + + private final XomwEnv env; + /** + * B/C kludge: provide a TitleParser for use by Title. + * Ideally, Title would have no methods that need this. + * Avoid usage of this singleton by using TitleValue + * and the associated services when possible. + * + * @return TitleFormatter + */ + // private static XomwMediaWikiTitleCodec getTitleFormatter() { + // return XomwMediaWikiServices.getInstance().getTitleFormatter(); + // } + +// /** +// * B/C kludge: provide an InterwikiLookup for use by Title. +// * Ideally, Title would have no methods that need this. +// * Avoid usage of this singleton by using TitleValue +// * and the associated services when possible. +// * +// * @return InterwikiLookup +// */ +// private static function getInterwikiLookup() { +// return MediaWikiServices::getInstance().getInterwikiLookup(); +// } + + /** + * @access protected + */ + XomwTitleOld(XomwEnv env) { + this.env = env; + } + +// /** +// * Create a new Title from a prefixed DB key +// * +// * @param String $key The database key, which has underscores +// * instead of spaces, possibly including namespace and +// * interwiki prefixes +// * @return Title|null Title, or null on an error +// */ +// public static function newFromDBkey($key) { +// t = new Title(); +// t.mDbkeyform = $key; +// +// try { +// t.secureAndSplit(); +// return t; +// } catch (XomwMalformedTitleException $ex) { +// return null; +// } +// } +// +// /** +// * Create a new Title from a TitleValue +// * +// * @param TitleValue $titleValue Assumed to be safe. +// * +// * @return Title +// */ +// public static function newFromTitleValue(TitleValue $titleValue) { +// return self::newFromLinkTarget($titleValue); +// } +// +// /** +// * Create a new Title from a LinkTarget +// * +// * @param LinkTarget $linkTarget Assumed to be safe. +// * +// * @return Title +// */ +// public static function newFromLinkTarget(LinkTarget $linkTarget) { +// if ($linkTarget instanceof Title) { +// // Special case if it's already a Title Object +// return $linkTarget; +// } +// return self::makeTitle( +// $linkTarget.getNamespace(), +// $linkTarget.getText(), +// $linkTarget.getFragment(), +// $linkTarget.getInterwiki() +// ); +// } + + /** + * Create a new Title from text, such as what one would find in a link. De- + * codes any HTML entities in the text. + * + * @param String|int|null $text The link text; spaces, prefixes, and an + * initial ':' indicating the main namespace are accepted. + * @param int $defaultNamespace The namespace to use if none is specified + * by a prefix. If you want to force a specific namespace even if + * $text might begin with a namespace prefix, use makeTitle() or + * makeTitleSafe(). + * @throws InvalidArgumentException + * @return Title|null Title or null on an error. + */ + public static XomwTitleOld newFromText(XomwEnv env, byte[] text) {return newFromText(env, text, XomwDefines.NS_MAIN);} + public static XomwTitleOld newFromText(XomwEnv env, String text, int defaultNamespace) {return newFromText(env, Bry_.new_u8(text));} + private static XomwTitleOld newFromText(XomwEnv env, byte[] text, int defaultNamespace) { + // DWIM: Integers can be passed in here when page titles are used as array keys. + // XO.MW.SKIP:STRONGCAST + // if ($text != null && !is_string($text) && !is_int($text)) { + // throw new InvalidArgumentException('$text must be a String.'); + // } + if (text == null) { + return null; + } + + try { + return XomwTitleOld.newFromTextThrow(env, text, defaultNamespace); + } catch (XomwMalformedTitleException ex) { + Err_.Noop(ex); + return null; + } + } + + /** + * Like Title::newFromText(), but throws XomwMalformedTitleException when the title is invalid, + * rather than returning null. + * + * The exception subclasses encode detailed information about why the title is invalid. + * + * @see Title::newFromText + * + * @since 1.25 + * @param String $text Title text to check + * @param int $defaultNamespace + * @throws XomwMalformedTitleException If the title is invalid + * @return Title + */ + private static XomwTitleOld newFromTextThrow(XomwEnv env, byte[] text, int defaultNamespace) { + // if (is_object($text)) { + // throw new MWException('$text must be a String, given an Object'); + // } + + // XO.MW.SKIP:CACHE + // $titleCache = self::getTitleCache(); + + // Wiki pages often contain multiple links to the same page. + // Title normalization and parsing can become expensive on pages with many + // links, so we can save a little time by caching them. + // In theory these are value objects and won't get changed... + // if ($defaultNamespace == NS_MAIN) { + // t = $titleCache.get($text); + // if (t) { + // return t; + // } + // } + + // Convert things like é ā or 〗 into normalized (bug 14952) text +// $filteredText = Sanitizer::decodeCharReferencesAndNormalize($text); + byte[] filteredText = text; + + XomwTitleOld t = new XomwTitleOld(env); + t.mDbkeyform = XophpString_.strtr(filteredText, Byte_ascii.Space, Byte_ascii.Underline); + t.mDefaultNamespace = defaultNamespace; + + t.secureAndSplit(env); + // XO.MW.SKIP:CACHE + // if ($defaultNamespace == NS_MAIN) { + // $titleCache.set($text, t); + // } + return t; + } + +// /** +// * THIS IS NOT THE FUNCTION YOU WANT. Use Title::newFromText(). +// * +// * Example of wrong and broken code: +// * $title = Title::newFromURL($wgRequest.getVal('title')); +// * +// * Example of right code: +// * $title = Title::newFromText($wgRequest.getVal('title')); +// * +// * Create a new Title from URL-encoded text. Ensures that +// * the given title's length does not exceed the maximum. +// * +// * @param String $url The title, as might be taken from a URL +// * @return Title|null The new Object, or null on an error +// */ +// public static function newFromURL($url) { +// t = new Title(); +// +// // For compatibility with old buggy URLs. "+" is usually not valid in titles, +// // but some URLs used it as a space replacement and they still come +// // from some external search tools. +// if (strpos(self::legalChars(), '+') == false) { +// $url = strtr($url, '+', ' '); +// } +// +// t.mDbkeyform = strtr($url, ' ', '_'); +// +// try { +// t.secureAndSplit(); +// return t; +// } catch (XomwMalformedTitleException $ex) { +// return null; +// } +// } +// +// /** +// * @return HashBagOStuff +// */ +// private static function getTitleCache() { +// if (self::$titleCache == null) { +// self::$titleCache = new HashBagOStuff([ 'maxKeys' => self::CACHE_MAX ]); +// } +// return self::$titleCache; +// } +// +// /** +// * Returns a list of fields that are to be selected for initializing Title +// * objects or LinkCache entries. Uses $wgContentHandlerUseDB to determine +// * whether to include page_content_model. +// * +// * @return array +// */ +// protected static function getSelectFields() { +// global $wgContentHandlerUseDB, $wgPageLanguageUseDB; +// +// $fields = [ +// 'page_namespace', 'page_title', 'page_id', +// 'page_len', 'page_is_redirect', 'page_latest', +// ]; +// +// if ($wgContentHandlerUseDB) { +// $fields[] = 'page_content_model'; +// } +// +// if ($wgPageLanguageUseDB) { +// $fields[] = 'page_lang'; +// } +// +// return $fields; +// } + + /** + * Create a new Title from an article ID + * + * @param int $id The page_id corresponding to the Title to create + * @param int $flags Use Title::GAID_FOR_UPDATE to use master + * @return Title|null The new Object, or null on an error + */ + public static XomwTitleOld newFromID(int id) {return newFromID(id, 0);} + public static XomwTitleOld newFromID(int id, int flags) { +// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); +// $row = $db.selectRow( +// 'page', +// self::getSelectFields(), +// [ 'page_id' => $id ], +// __METHOD__ +// ); +// if ($row != false) { +// $title = Title::newFromRow($row); +// } else { +// $title = null; +// } +// return $title; + return null; + } +// +// /** +// * Make an array of titles from an array of IDs +// * +// * @param int[] $ids Array of IDs +// * @return Title[] Array of Titles +// */ +// public static function newFromIDs($ids) { +// if (!count($ids)) { +// return []; +// } +// $dbr = wfGetDB(DB_REPLICA); +// +// $res = $dbr.select( +// 'page', +// self::getSelectFields(), +// [ 'page_id' => $ids ], +// __METHOD__ +// ); +// +// $titles = []; +// foreach ($res as $row) { +// $titles[] = Title::newFromRow($row); +// } +// return $titles; +// } +// +// /** +// * Make a Title Object from a DB row +// * +// * @param stdClass $row Object database row (needs at least page_title,page_namespace) +// * @return Title Corresponding Title +// */ +// public static function newFromRow($row) { +// t = self::makeTitle($row.page_namespace, $row.page_title); +// t.loadFromRow($row); +// return t; +// } +// +// /** +// * Load Title Object fields from a DB row. +// * If false is given, the title will be treated as non-existing. +// * +// * @param stdClass|boolean $row Database row +// */ +// public function loadFromRow($row) { +// if ($row) { // page found +// if (isset($row.page_id)) { +// this.mArticleID = (int)$row.page_id; +// } +// if (isset($row.page_len)) { +// this.mLength = (int)$row.page_len; +// } +// if (isset($row.page_is_redirect)) { +// this.mRedirect = (boolean)$row.page_is_redirect; +// } +// if (isset($row.page_latest)) { +// this.mLatestID = (int)$row.page_latest; +// } +// if (!this.mForcedContentModel && isset($row.page_content_model)) { +// this.mContentModel = strval($row.page_content_model); +// } elseif (!this.mForcedContentModel) { +// this.mContentModel = false; # initialized lazily in getContentModel() +// } +// if (isset($row.page_lang)) { +// this.mDbPageLanguage = (String)$row.page_lang; +// } +// if (isset($row.page_restrictions)) { +// this.mOldRestrictions = $row.page_restrictions; +// } +// } else { // page not found +// this.mArticleID = 0; +// this.mLength = 0; +// this.mRedirect = false; +// this.mLatestID = 0; +// if (!this.mForcedContentModel) { +// this.mContentModel = false; # initialized lazily in getContentModel() +// } +// } +// } +// +// /** +// * Create a new Title from a namespace index and a DB key. +// * It's assumed that $ns and $title are *valid*, for instance when +// * they came directly from the database or a special page name. +// * For convenience, spaces are converted to underscores so that +// * eg user_text fields can be used directly. +// * +// * @param int $ns The namespace of the article +// * @param String $title The unprefixed database key form +// * @param String $fragment The link fragment (after the "#") +// * @param String $interwiki The interwiki prefix +// * @return Title The new Object +// */ +// public static function makeTitle($ns, $title, $fragment = Bry_.Empty, $interwiki = Bry_.Empty) { +// t = new Title(); +// t.mInterwiki = $interwiki; +// t.mFragment = $fragment; +// t.mNamespace = $ns = intval($ns); +// t.mDbkeyform = strtr($title, ' ', '_'); +// t.mArticleID = ($ns >= 0) ? -1 : 0; +// t.mUrlform = wfUrlencode(t.mDbkeyform); +// t.mTextform = strtr($title, '_', ' '); +// t.mContentModel = false; # initialized lazily in getContentModel() +// return t; +// } +// +// /** +// * Create a new Title from a namespace index and a DB key. +// * The parameters will be checked for validity, which is a bit slower +// * than makeTitle() but safer for user-provided data. +// * +// * @param int $ns The namespace of the article +// * @param String $title Database key form +// * @param String $fragment The link fragment (after the "#") +// * @param String $interwiki Interwiki prefix +// * @return Title|null The new Object, or null on an error +// */ +// public static function makeTitleSafe($ns, $title, $fragment = Bry_.Empty, $interwiki = Bry_.Empty) { +// if (!XomwNamespace::exists($ns)) { +// return null; +// } +// +// t = new Title(); +// t.mDbkeyform = Title::makeName($ns, $title, $fragment, $interwiki, true); +// +// try { +// t.secureAndSplit(); +// return t; +// } catch (XomwMalformedTitleException $ex) { +// return null; +// } +// } + + /** + * Create a new Title for the Main Page + * + * @return Title The new Object + */ + public static XomwTitleOld newMainPage(XomwEnv env) { + XomwTitleOld title = XomwTitleOld.newFromText(env, XomwGlobalFunctions.wfMessage(env, "mainpage").inContentLanguage().text()); + // Don't give fatal errors if the message is broken + if (title == null) { + title = XomwTitleOld.newFromText(env, Bry_.new_a7("Main Page")); + } + return title; + } + +// /** +// * Get the prefixed DB key associated with an ID +// * +// * @param int $id The page_id of the article +// * @return Title|null An Object representing the article, or null if no such article was found +// */ +// public static function nameOf($id) { +// $dbr = wfGetDB(DB_REPLICA); +// +// $s = $dbr.selectRow( +// 'page', +// [ 'page_namespace', 'page_title' ], +// [ 'page_id' => $id ], +// __METHOD__ +// ); +// if ($s == false) { +// return null; +// } +// +// $n = self::makeName($s.page_namespace, $s.page_title); +// return $n; +// } +// +// /** +// * Get a regex character class describing the legal characters in a link +// * +// * @return String The list of characters, not delimited +// */ +// public static function legalChars() { +// global $wgLegalTitleChars; +// return $wgLegalTitleChars; +// } +// +// /** +// * Returns a simple regex that will match on characters and sequences invalid in titles. +// * Note that this doesn't pick up many things that could be wrong with titles, but that +// * replacing this regex with something valid will make many titles valid. +// * +// * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead +// * +// * @return String Regex String +// */ +// static function getTitleInvalidRegex() { +// wfDeprecated(__METHOD__, '1.25'); +// return MediaWikiTitleCodec::getTitleInvalidRegex(); +// } +// +// /** +// * Utility method for converting a character sequence from bytes to Unicode. +// * +// * Primary usecase being converting $wgLegalTitleChars to a sequence usable in +// * javascript, as PHP uses UTF-8 bytes where javascript uses Unicode code units. +// * +// * @param String $byteClass +// * @return String +// */ +// public static function convertByteClassToUnicodeClass($byteClass) { +// $length = strlen($byteClass); +// // Input token queue +// $x0 = $x1 = $x2 = Bry_.Empty; +// // Decoded queue +// $d0 = $d1 = $d2 = Bry_.Empty; +// // Decoded integer codepoints +// $ord0 = $ord1 = $ord2 = 0; +// // Re-encoded queue +// $r0 = $r1 = $r2 = Bry_.Empty; +// // Output +// $out = Bry_.Empty; +// // Flags +// $allowUnicode = false; +// for ($pos = 0; $pos < $length; $pos++) { +// // Shift the queues down +// $x2 = $x1; +// $x1 = $x0; +// $d2 = $d1; +// $d1 = $d0; +// $ord2 = $ord1; +// $ord1 = $ord0; +// $r2 = $r1; +// $r1 = $r0; +// // Load the current input token and decoded values +// $inChar = $byteClass[$pos]; +// if ($inChar == '\\') { +// if (preg_match('/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1)) { +// $x0 = $inChar . $m[0]; +// $d0 = chr(hexdec($m[1])); +// $pos += strlen($m[0]); +// } elseif (preg_match('/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1)) { +// $x0 = $inChar . $m[0]; +// $d0 = chr(octdec($m[0])); +// $pos += strlen($m[0]); +// } elseif ($pos + 1 >= $length) { +// $x0 = $d0 = '\\'; +// } else { +// $d0 = $byteClass[$pos + 1]; +// $x0 = $inChar . $d0; +// $pos += 1; +// } +// } else { +// $x0 = $d0 = $inChar; +// } +// $ord0 = ord($d0); +// // Load the current re-encoded value +// if ($ord0 < 32 || $ord0 == 0x7f) { +// $r0 = sprintf('\x%02x', $ord0); +// } elseif ($ord0 >= 0x80) { +// // Allow unicode if a single high-bit character appears +// $r0 = sprintf('\x%02x', $ord0); +// $allowUnicode = true; +// } elseif (strpos('-\\[]^', $d0) != false) { +// $r0 = '\\' . $d0; +// } else { +// $r0 = $d0; +// } +// // Do the output +// if ($x0 != Bry_.Empty && $x1 == '-' && $x2 != Bry_.Empty) { +// // Range +// if ($ord2 > $ord0) { +// // Empty range +// } elseif ($ord0 >= 0x80) { +// // Unicode range +// $allowUnicode = true; +// if ($ord2 < 0x80) { +// // Keep the non-unicode section of the range +// $out .= "$r2-\\x7F"; +// } +// } else { +// // Normal range +// $out .= "$r2-$r0"; +// } +// // Reset state to the initial value +// $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = Bry_.Empty; +// } elseif ($ord2 < 0x80) { +// // ASCII character +// $out .= $r2; +// } +// } +// if ($ord1 < 0x80) { +// $out .= $r1; +// } +// if ($ord0 < 0x80) { +// $out .= $r0; +// } +// if ($allowUnicode) { +// $out .= '\u0080-\uFFFF'; +// } +// return $out; +// } +// +// /** +// * Make a prefixed DB key from a DB key and a namespace index +// * +// * @param int $ns Numerical representation of the namespace +// * @param String $title The DB key form the title +// * @param String $fragment The link fragment (after the "#") +// * @param String $interwiki The interwiki prefix +// * @param boolean $canonicalNamespace If true, use the canonical name for +// * $ns instead of the localized version. +// * @return String The prefixed form of the title +// */ +// public static function makeName($ns, $title, $fragment = Bry_.Empty, $interwiki = Bry_.Empty, +// $canonicalNamespace = false +// ) { +// global $wgContLang; +// +// if ($canonicalNamespace) { +// $namespace = XomwNamespace::getCanonicalName($ns); +// } else { +// $namespace = $wgContLang.getNsText($ns); +// } +// $name = $namespace == Bry_.Empty ? $title : "$namespace:$title"; +// if (strval($interwiki) != Bry_.Empty) { +// $name = "$interwiki:$name"; +// } +// if (strval($fragment) != Bry_.Empty) { +// $name .= '#' . $fragment; +// } +// return $name; +// } +// +// /** +// * Escape a text fragment, say from a link, for a URL +// * +// * @param String $fragment Containing a URL or link fragment (after the "#") +// * @return String Escaped String +// */ +// static function escapeFragmentForURL($fragment) { +// // Note that we don't urlencode the fragment. urlencoded Unicode +// // fragments appear not to work in IE (at least up to 7) or in at least +// // one version of Opera 9.x. The W3C validator, for one, doesn't seem +// // to care if they aren't encoded. +// return Sanitizer::escapeId($fragment, 'noninitial'); +// } +// +// /** +// * Callback for usort() to do title sorts by (namespace, title) +// * +// * @param LinkTarget $a +// * @param LinkTarget $b +// * +// * @return int Result of String comparison, or namespace comparison +// */ +// public static function compare(LinkTarget $a, LinkTarget $b) { +// if ($a.getNamespace() == $b.getNamespace()) { +// return strcmp($a.getText(), $b.getText()); +// } else { +// return $a.getNamespace() - $b.getNamespace(); +// } +// } +// +// /** +// * Determine whether the Object refers to a page within +// * this project (either this wiki or a wiki with a local +// * interwiki, see https://www.mediawiki.org/wiki/Manual:Interwiki_table#iw_local) +// * +// * @return boolean True if this is an in-project interwiki link or a wikilink, false otherwise +// */ +// public function isLocal() { +// if (this.isExternal()) { +// $iw = self::getInterwikiLookup().fetch(this.mInterwiki); +// if ($iw) { +// return $iw.isLocal(); +// } +// } +// return true; +// } + + /** + * Is this Title interwiki? + * + * @return boolean + */ + public boolean isExternal() { + return this.mInterwiki != Bry_.Empty; + } + + /** + * Get the interwiki prefix + * + * Use Title::isExternal to check if a interwiki is set + * + * @return String Interwiki prefix + */ + public byte[] getInterwiki() { + return this.mInterwiki; + } + + /** + * Was this a local interwiki link? + * + * @return boolean + */ + public boolean wasLocalInterwiki() { + return this.mLocalInterwiki; + } + +// /** +// * Determine whether the Object refers to a page within +// * this project and is transcludable. +// * +// * @return boolean True if this is transcludable +// */ +// public function isTrans() { +// if (!this.isExternal()) { +// return false; +// } +// +// return self::getInterwikiLookup().fetch(this.mInterwiki).isTranscludable(); +// } +// +// /** +// * Returns the DB name of the distant wiki which owns the Object. +// * +// * @return String|false The DB name +// */ +// public function getTransWikiID() { +// if (!this.isExternal()) { +// return false; +// } +// +// return self::getInterwikiLookup().fetch(this.mInterwiki).getWikiID(); +// } +// +// /** +// * Get a TitleValue Object representing this Title. +// * +// * @note Not all valid Titles have a corresponding valid TitleValue +// * (e.g. TitleValues cannot represent page-local links that have a +// * fragment but no title text). +// * +// * @return TitleValue|null +// */ +// public function getTitleValue() { +// if (this.mTitleValue == null) { +// try { +// this.mTitleValue = new TitleValue( +// this.getNamespace(), +// this.getDBkey(), +// this.getFragment(), +// this.getInterwiki() +// ); +// } catch (InvalidArgumentException $ex) { +// wfDebug(__METHOD__ . ': Can\'t create a TitleValue for [[' . +// this.getPrefixedText() . ']]: ' . $ex.getMessage() . "\n"); +// } +// } +// +// return this.mTitleValue; +// } + + /** + * Get the text form (spaces not underscores) of the main part + * + * @return String Main part of the title + */ + public byte[] getText() { + return this.mTextform; + } + + /** + * Get the URL-encoded form of the main part + * + * @return String Main part of the title, URL-encoded + */ + public byte[] getPartialURL() { + return this.mUrlform; + } + + /** + * Get the main part with underscores + * + * @return String Main part of the title, with underscores + */ + public byte[] getDBkey() { + return this.mDbkeyform; + } + + /** + * Get the DB key with the initial letter case as specified by the user + * + * @return String DB key + */ + public byte[] getUserCaseDBKey() { + if (this.mUserCaseDBKey != null) { + return this.mUserCaseDBKey; + } else { + // If created via makeTitle(), this.mUserCaseDBKey is not set. + return this.mDbkeyform; + } + } + + /** + * Get the namespace index, i.e. one of the NS_xxxx constants. + * + * @return int Namespace index + */ + public int getNamespace() { + return this.mNamespace; + } + +// /** +// * Get the page's content model id, see the CONTENT_MODEL_XXX constants. +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return String Content model id +// */ +// public function getContentModel($flags = 0) { +// if (!this.mForcedContentModel +// && (!this.mContentModel || $flags == Title::GAID_FOR_UPDATE) +// && this.getArticleID($flags) +// ) { +// $linkCache = LinkCache::singleton(); +// $linkCache.addLinkObj(this); # in case we already had an article ID +// this.mContentModel = $linkCache.getGoodLinkFieldObj(this, 'model'); +// } +// +// if (!this.mContentModel) { +// this.mContentModel = ContentHandler::getDefaultModelFor(this); +// } +// +// return this.mContentModel; +// } +// +// /** +// * Convenience method for checking a title's content model name +// * +// * @param String $id The content model ID (use the CONTENT_MODEL_XXX constants). +// * @return boolean True if this.getContentModel() == $id +// */ +// public function hasContentModel($id) { +// return this.getContentModel() == $id; +// } +// +// /** +// * Set a proposed content model for the page for permissions +// * checking. This does not actually change the content model +// * of a title! +// * +// * Additionally, you should make sure you've checked +// * ContentHandler::canBeUsedOn() first. +// * +// * @since 1.28 +// * @param String $model CONTENT_MODEL_XXX constant +// */ +// public function setContentModel($model) { +// this.mContentModel = $model; +// this.mForcedContentModel = true; +// } + + /** + * Get the namespace text + * + * @return String|false Namespace text + */ + public byte[] getNsText() { + if (this.isExternal()) { +// // This probably shouldn't even happen, +// // but for interwiki transclusion it sometimes does. +// // Use the canonical namespaces if possible to try to +// // resolve a foreign namespace. +// if (XomwNamespace::exists(this.mNamespace)) { +// return XomwNamespace::getCanonicalName(this.mNamespace); +// } + } + +// try { + XomwTitleFormatter formatter = env.MediaWikiServices().getTitleFormatter(); + return formatter.getNamespaceName(this.mNamespace, this.mDbkeyform); +// } catch (InvalidArgumentException $ex) { +// wfDebug(__METHOD__ . ': ' . $ex.getMessage() . "\n"); +// return false; +// } + } + +// /** +// * Get the namespace text of the subject (rather than talk) page +// * +// * @return String Namespace text +// */ +// public function getSubjectNsText() { +// global $wgContLang; +// return $wgContLang.getNsText(XomwNamespace::getSubject(this.mNamespace)); +// } +// +// /** +// * Get the namespace text of the talk page +// * +// * @return String Namespace text +// */ +// public function getTalkNsText() { +// global $wgContLang; +// return $wgContLang.getNsText(XomwNamespace::getTalk(this.mNamespace)); +// } +// +// /** +// * Could this title have a corresponding talk page? +// * +// * @return boolean +// */ +// public function canTalk() { +// return XomwNamespace::canTalk(this.mNamespace); +// } +// +// /** +// * Is this in a namespace that allows actual pages? +// * +// * @return boolean +// */ +// public function canExist() { +// return this.mNamespace >= NS_MAIN; +// } +// +// /** +// * Can this title be added to a user's watchlist? +// * +// * @return boolean +// */ +// public function isWatchable() { +// return !this.isExternal() && XomwNamespace::isWatchable(this.getNamespace()); +// } +// +// /** +// * Returns true if this is a special page. +// * +// * @return boolean +// */ +// public function isSpecialPage() { +// return this.getNamespace() == NS_SPECIAL; +// } +// +// /** +// * Returns true if this title resolves to the named special page +// * +// * @param String $name The special page name +// * @return boolean +// */ +// public function isSpecial($name) { +// if (this.isSpecialPage()) { +// list($thisName, /* $subpage */) = SpecialPageFactory::resolveAlias(this.getDBkey()); +// if ($name == $thisName) { +// return true; +// } +// } +// return false; +// } +// +// /** +// * If the Title refers to a special page alias which is not the local default, resolve +// * the alias, and localise the name as necessary. Otherwise, return this +// * +// * @return Title +// */ +// public function fixSpecialName() { +// if (this.isSpecialPage()) { +// list($canonicalName, $par) = SpecialPageFactory::resolveAlias(this.mDbkeyform); +// if ($canonicalName) { +// $localName = SpecialPageFactory::getLocalNameFor($canonicalName, $par); +// if ($localName != this.mDbkeyform) { +// return Title::makeTitle(NS_SPECIAL, $localName); +// } +// } +// } +// return this; +// } +// +// /** +// * Returns true if the title is inside the specified namespace. +// * +// * Please make use of this instead of comparing to getNamespace() +// * This function is much more resistant to changes we may make +// * to namespaces than code that makes direct comparisons. +// * @param int $ns The namespace +// * @return boolean +// * @since 1.19 +// */ +// public function inNamespace($ns) { +// return XomwNamespace::equals(this.getNamespace(), $ns); +// } +// +// /** +// * Returns true if the title is inside one of the specified namespaces. +// * +// * @param int|int[] $namespaces,... The namespaces to check for +// * @return boolean +// * @since 1.19 +// */ +// public function inNamespaces(/* ... */) { +// $namespaces = func_get_args(); +// if (count($namespaces) > 0 && is_array($namespaces[0])) { +// $namespaces = $namespaces[0]; +// } +// +// foreach ($namespaces as $ns) { +// if (this.inNamespace($ns)) { +// return true; +// } +// } +// +// return false; +// } +// +// /** +// * Returns true if the title has the same subject namespace as the +// * namespace specified. +// * For example this method will take NS_USER and return true if namespace +// * is either NS_USER or NS_USER_TALK since both of them have NS_USER +// * as their subject namespace. +// * +// * This is MUCH simpler than individually testing for equivalence +// * against both NS_USER and NS_USER_TALK, and is also forward compatible. +// * @since 1.19 +// * @param int $ns +// * @return boolean +// */ +// public function hasSubjectNamespace($ns) { +// return XomwNamespace::subjectEquals(this.getNamespace(), $ns); +// } +// +// /** +// * Is this Title in a namespace which contains content? +// * In other words, is this a content page, for the purposes of calculating +// * statistics, etc? +// * +// * @return boolean +// */ +// public function isContentPage() { +// return XomwNamespace::isContent(this.getNamespace()); +// } +// +// /** +// * Would anybody with sufficient privileges be able to move this page? +// * Some pages just aren't movable. +// * +// * @return boolean +// */ +// public function isMovable() { +// if (!XomwNamespace::isMovable(this.getNamespace()) || this.isExternal()) { +// // Interwiki title or immovable namespace. Hooks don't get to override here +// return false; +// } +// +// $result = true; +// Hooks::run('TitleIsMovable', [ this, &$result ]); +// return $result; +// } +// +// /** +// * Is this the mainpage? +// * @note Title::newFromText seems to be sufficiently optimized by the title +// * cache that we don't need to over-optimize by doing direct comparisons and +// * accidentally creating new bugs where $title.equals(Title::newFromText()) +// * ends up reporting something differently than $title.isMainPage(); +// * +// * @since 1.18 +// * @return boolean +// */ +// public function isMainPage() { +// return this.equals(Title::newMainPage()); +// } +// +// /** +// * Is this a subpage? +// * +// * @return boolean +// */ +// public function isSubpage() { +// return XomwNamespace::hasSubpages(this.mNamespace) +// ? strpos(this.getText(), '/') != false +// : false; +// } +// +// /** +// * Is this a conversion table for the LanguageConverter? +// * +// * @return boolean +// */ +// public function isConversionTable() { +// // @todo ConversionTable should become a separate content model. +// +// return this.getNamespace() == NS_MEDIAWIKI && +// strpos(this.getText(), 'Conversiontable/') == 0; +// } +// +// /** +// * Does that page contain wikitext, or it is JS, CSS or whatever? +// * +// * @return boolean +// */ +// public function isWikitextPage() { +// return this.hasContentModel(CONTENT_MODEL_WIKITEXT); +// } +// +// /** +// * Could this page contain custom CSS or JavaScript for the global UI. +// * This is generally true for pages in the MediaWiki namespace having CONTENT_MODEL_CSS +// * or CONTENT_MODEL_JAVASCRIPT. +// * +// * This method does *not* return true for per-user JS/CSS. Use isCssJsSubpage() +// * for that! +// * +// * Note that this method should not return true for pages that contain and +// * show "inactive" CSS or JS. +// * +// * @return boolean +// * @todo FIXME: Rename to isSiteConfigPage() and remove deprecated hook +// */ +// public function isCssOrJsPage() { +// $isCssOrJsPage = NS_MEDIAWIKI == this.mNamespace +// && (this.hasContentModel(CONTENT_MODEL_CSS) +// || this.hasContentModel(CONTENT_MODEL_JAVASCRIPT)); +// +// return $isCssOrJsPage; +// } +// +// /** +// * Is this a .css or .js subpage of a user page? +// * @return boolean +// * @todo FIXME: Rename to isUserConfigPage() +// */ +// public function isCssJsSubpage() { +// return (NS_USER == this.mNamespace && this.isSubpage() +// && (this.hasContentModel(CONTENT_MODEL_CSS) +// || this.hasContentModel(CONTENT_MODEL_JAVASCRIPT))); +// } +// +// /** +// * Trim down a .css or .js subpage title to get the corresponding skin name +// * +// * @return String Containing skin name from .css or .js subpage title +// */ +// public function getSkinFromCssJsSubpage() { +// $subpage = explode('/', this.mTextform); +// $subpage = $subpage[count($subpage) - 1]; +// $lastdot = strrpos($subpage, '.'); +// if ($lastdot == false) { +// return $subpage; # Never happens: only called for names ending in '.css' or '.js' +// } +// return substr($subpage, 0, $lastdot); +// } +// +// /** +// * Is this a .css subpage of a user page? +// * +// * @return boolean +// */ +// public function isCssSubpage() { +// return (NS_USER == this.mNamespace && this.isSubpage() +// && this.hasContentModel(CONTENT_MODEL_CSS)); +// } +// +// /** +// * Is this a .js subpage of a user page? +// * +// * @return boolean +// */ +// public function isJsSubpage() { +// return (NS_USER == this.mNamespace && this.isSubpage() +// && this.hasContentModel(CONTENT_MODEL_JAVASCRIPT)); +// } +// +// /** +// * Is this a talk page of some sort? +// * +// * @return boolean +// */ +// public function isTalkPage() { +// return XomwNamespace::isTalk(this.getNamespace()); +// } +// +// /** +// * Get a Title Object associated with the talk page of this article +// * +// * @return Title The Object for the talk page +// */ +// public function getTalkPage() { +// return Title::makeTitle(XomwNamespace::getTalk(this.getNamespace()), this.getDBkey()); +// } +// +// /** +// * Get a title Object associated with the subject page of this +// * talk page +// * +// * @return Title The Object for the subject page +// */ +// public function getSubjectPage() { +// // Is this the same title? +// $subjectNS = XomwNamespace::getSubject(this.getNamespace()); +// if (this.getNamespace() == $subjectNS) { +// return this; +// } +// return Title::makeTitle($subjectNS, this.getDBkey()); +// } +// +// /** +// * Get the other title for this page, if this is a subject page +// * get the talk page, if it is a subject page get the talk page +// * +// * @since 1.25 +// * @throws MWException +// * @return Title +// */ +// public function getOtherPage() { +// if (this.isSpecialPage()) { +// throw new MWException('Special pages cannot have other pages'); +// } +// if (this.isTalkPage()) { +// return this.getSubjectPage(); +// } else { +// return this.getTalkPage(); +// } +// } + + /** + * Get the default namespace index, for when there is no namespace + * + * @return int Default namespace index + */ + public int getDefaultNamespace() { + return this.mDefaultNamespace; + } + + /** + * Get the Title fragment (i.e.\ the bit after the #) in text form + * + * Use Title::hasFragment to check for a fragment + * + * @return String Title fragment + */ + public byte[] getFragment() { + return this.mFragment; + } + + /** + * Check if a Title fragment is set + * + * @return boolean + * @since 1.23 + */ + public boolean hasFragment() { + return this.mFragment != Bry_.Empty; + } + +// /** +// * Get the fragment in URL form, including the "#" character if there is one +// * @return String Fragment in URL form +// */ +// public function getFragmentForURL() { +// if (!this.hasFragment()) { +// return Bry_.Empty; +// } else { +// return '#' . Title::escapeFragmentForURL(this.getFragment()); +// } +// } +// +// /** +// * Set the fragment for this title. Removes the first character from the +// * specified fragment before setting, so it assumes you're passing it with +// * an initial "#". +// * +// * Deprecated for public use, use Title::makeTitle() with fragment parameter, +// * or Title::createFragmentTarget(). +// * Still in active use privately. +// * +// * @private +// * @param String $fragment Text +// */ +// public function setFragment($fragment) { +// this.mFragment = strtr(substr($fragment, 1), '_', ' '); +// } + + /** + * Creates a new Title for a different fragment of the same page. + * + * @since 1.27 + * @param String $fragment + * @return Title + */ + public XomwTitleOld createFragmentTarget(byte[] fragment) { + return null; +// return self::makeTitle( +// this.getNamespace(), +// this.getText(), +// $fragment, +// this.getInterwiki() +// ); + } + + /** + * Prefix some arbitrary text with the namespace or interwiki prefix + * of this Object + * + * @param String $name The text + * @return String The prefixed text + */ + private byte[] prefix(byte[] name) { + byte[] p = Bry_.Empty; +// if (this.isExternal()) { +// p = this.mInterwiki . ':'; +// } + + if (0 != this.mNamespace) { + p = Bry_.Add(p, this.getNsText(), Byte_ascii.Colon_bry); + } + return Bry_.Add(p, name); + } + + /** + * Get the prefixed database key form + * + * @return String The prefixed title, with underscores and + * any interwiki and namespace prefixes + */ + public byte[] getPrefixedDBkey() { + byte[] s = this.prefix(this.mDbkeyform); + s = XophpString_.strtr(s, Byte_ascii.Space, Byte_ascii.Underline); + return s; + } + public String getPrefixedDBkeyStr() {return String_.new_u8(getPrefixedDBkey());} + + /** + * Get the prefixed title with spaces. + * This is the form usually used for display + * + * @return String The prefixed title, with spaces + */ + public byte[] getPrefixedText() { + if (this.mPrefixedText == null) { + byte[] s = this.prefix(this.mTextform); + s = XophpString_.strtr(s, Byte_ascii.Underline, Byte_ascii.Space); + this.mPrefixedText = s; + } + return this.mPrefixedText; + } + public String getPrefixedTextStr() {return String_.new_u8(getPrefixedText());} + +// /** +// * Return a String representation of this title +// * +// * @return String Representation of this title +// */ +// public function __toString() { +// return this.getPrefixedText(); +// } +// +// /** +// * Get the prefixed title with spaces, plus any fragment +// * (part beginning with '#') +// * +// * @return String The prefixed title, with spaces and the fragment, including '#' +// */ +// public function getFullText() { +// $text = this.getPrefixedText(); +// if (this.hasFragment()) { +// $text .= '#' . this.getFragment(); +// } +// return $text; +// } +// +// /** +// * Get the root page name text without a namespace, i.e. the leftmost part before any slashes +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz').getRootText(); +// * # returns: 'Foo' +// * @endcode +// * +// * @return String Root name +// * @since 1.20 +// */ +// public function getRootText() { +// if (!XomwNamespace::hasSubpages(this.mNamespace)) { +// return this.getText(); +// } +// +// return strtok(this.getText(), '/'); +// } +// +// /** +// * Get the root page name title, i.e. the leftmost part before any slashes +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz').getRootTitle(); +// * # returns: Title{User:Foo} +// * @endcode +// * +// * @return Title Root title +// * @since 1.20 +// */ +// public function getRootTitle() { +// return Title::makeTitle(this.getNamespace(), this.getRootText()); +// } +// +// /** +// * Get the super page name without a namespace, i.e. the part before the subpage name +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz').getBaseText(); +// * # returns: 'Foo/Bar' +// * @endcode +// * +// * @return String Base name +// */ +// public function getBaseText() { +// if (!XomwNamespace::hasSubpages(this.mNamespace)) { +// return this.getText(); +// } +// +// $parts = explode('/', this.getText()); +// // Don't discard the real title if there's no subpage involved +// if (count($parts) > 1) { +// unset($parts[count($parts) - 1]); +// } +// return implode('/', $parts); +// } +// +// /** +// * Get the super page name title, i.e. the part before the subpage name +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz').getBaseTitle(); +// * # returns: Title{User:Foo/Bar} +// * @endcode +// * +// * @return Title Base title +// * @since 1.20 +// */ +// public function getBaseTitle() { +// return Title::makeTitle(this.getNamespace(), this.getBaseText()); +// } +// +// /** +// * Get the lowest-level subpage name, i.e. the rightmost part after any slashes +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz').getSubpageText(); +// * # returns: "Baz" +// * @endcode +// * +// * @return String Subpage name +// */ +// public function getSubpageText() { +// if (!XomwNamespace::hasSubpages(this.mNamespace)) { +// return this.mTextform; +// } +// $parts = explode('/', this.mTextform); +// return $parts[count($parts) - 1]; +// } +// +// /** +// * Get the title for a subpage of the current page +// * +// * @par Example: +// * @code +// * Title::newFromText('User:Foo/Bar/Baz').getSubpage("Asdf"); +// * # returns: Title{User:Foo/Bar/Baz/Asdf} +// * @endcode +// * +// * @param String $text The subpage name to add to the title +// * @return Title Subpage title +// * @since 1.20 +// */ +// public function getSubpage($text) { +// return Title::makeTitleSafe(this.getNamespace(), this.getText() . '/' . $text); +// } +// +// /** +// * Get a URL-encoded form of the subpage text +// * +// * @return String URL-encoded subpage name +// */ +// public function getSubpageUrlForm() { +// $text = this.getSubpageText(); +// $text = wfUrlencode(strtr($text, ' ', '_')); +// return $text; +// } +// +// /** +// * Get a URL-encoded title (not an actual URL) including interwiki +// * +// * @return String The URL-encoded form +// */ +// public function getPrefixedURL() { +// $s = this.prefix(this.mDbkeyform); +// $s = wfUrlencode(strtr($s, ' ', '_')); +// return $s; +// } +// +// /** +// * Helper to fix up the get{Canonical,Full,Link,Local,Internal}URL args +// * get{Canonical,Full,Link,Local,Internal}URL methods accepted an optional +// * second argument named variant. This was deprecated in favor +// * of passing an array of option with a "variant" key +// * Once $query2 is removed for good, this helper can be dropped +// * and the wfArrayToCgi moved to getLocalURL(); +// * +// * @since 1.19 (r105919) +// * @param array|String $query +// * @param String|String[]|boolean $query2 +// * @return String +// */ +// private static function fixUrlQueryArgs($query, $query2 = false) { +// if ($query2 != false) { +// wfDeprecated("Title::get{Canonical,Full,Link,Local,Internal}URL " . +// "method called with a second parameter is deprecated. Add your " . +// "parameter to an array passed as the first parameter.", "1.19"); +// } +// if (is_array($query)) { +// $query = wfArrayToCgi($query); +// } +// if ($query2) { +// if (is_string($query2)) { +// // $query2 is a String, we will consider this to be +// // a deprecated $variant argument and add it to the query +// $query2 = wfArrayToCgi([ 'variant' => $query2 ]); +// } else { +// $query2 = wfArrayToCgi($query2); +// } +// // If we have $query content add a & to it first +// if ($query) { +// $query .= '&'; +// } +// // Now append the queries together +// $query .= $query2; +// } +// return $query; +// } +// +// /** +// * Get a real URL referring to this title, with interwiki link and +// * fragment +// * +// * @see self::getLocalURL for the arguments. +// * @see wfExpandUrl +// * @param String|String[] $query +// * @param String|String[]|boolean $query2 +// * @param String $proto Protocol type to use in URL +// * @return String The URL +// */ +// public function getFullURL($query = Bry_.Empty, $query2 = false, $proto = PROTO_RELATIVE) { +// $query = self::fixUrlQueryArgs($query, $query2); +// +// // Hand off all the decisions on urls to getLocalURL +// $url = this.getLocalURL($query); +// +// // Expand the url to make it a full url. Note that getLocalURL has the +// // potential to output full urls for a variety of reasons, so we use +// // wfExpandUrl instead of simply prepending $wgServer +// $url = wfExpandUrl($url, $proto); +// +// // Finally, add the fragment. +// $url .= this.getFragmentForURL(); +// // Avoid PHP 7.1 warning from passing this by reference +// $titleRef = this; +// Hooks::run('GetFullURL', [ &$titleRef, &$url, $query ]); +// return $url; +// } + + /** + * Get a URL with no fragment or server name (relative URL) from a Title Object. + * If this page is generated with action=render, however, + * $wgServer is prepended to make an absolute URL. + * + * @see self::getFullURL to always get an absolute URL. + * @see self::getLinkURL to always get a URL that's the simplest URL that will be + * valid to link, locally, to the current Title. + * @see self::newFromText to produce a Title Object. + * + * @param String|String[] $query An optional query String, + * not used for interwiki links. Can be specified as an associative array as well, + * e.g., array('action' => 'edit') (keys and values will be URL-escaped). + * Some query patterns will trigger various shorturl path replacements. + * @param String|String[]|boolean $query2 An optional secondary query array. This one MUST + * be an array. If a String is passed it will be interpreted as a deprecated + * variant argument and urlencoded into a variant= argument. + * This second query argument will be added to the $query + * The second parameter is deprecated since 1.19. Pass it as a key,value + * pair in the first parameter array instead. + * + * @return String String of the URL. + */ + public byte[] getLocalURL(byte[] query) {return getLocalURL(query, null);} + public byte[] getLocalURL(byte[] query, byte[] query2) { + byte[] url = null; +// global wgArticlePath, wgScript, wgServer, wgRequest; +// +// query = self::fixUrlQueryArgs(query, query2); +// +// interwiki = self::getInterwikiLookup().fetch(this.mInterwiki); +// if (interwiki) { +// namespace = this.getNsText(); +// if (namespace != Bry_.Empty) { +// // Can this actually happen? Interwikis shouldn't be parsed. +// // Yes! It can in interwiki transclusion. But... it probably shouldn't. +// namespace .= ':'; +// } +// url = interwiki.getURL(namespace . this.getDBkey()); +// url = wfAppendQuery(url, query); +// } else { +// byte[] dbkey = wfUrlencode(this.getPrefixedDBkey()); + byte[] dbkey = this.getPrefixedDBkey(); +// if (query == Bry_.Empty) { +// url = str_replace('$1', dbkey, wgArticlePath); + url = Bry_.Add(Bry__wgArticlePath__wiki, dbkey); + // XO.MW.HOOK:GetLocalURL::Article +// } else { +// global wgVariantArticlePath, wgActionPaths, wgContLang; +// url = false; +// matches = []; +// +// if (!empty(wgActionPaths) +// && preg_match('/^(.*&|)action=([^&]*)(&(.*)|)/', query, matches) +// ) { +// action = urldecode(matches[2]); +// if (isset(wgActionPaths[action])) { +// query = matches[1]; +// if (isset(matches[4])) { +// query .= matches[4]; +// } +// url = str_replace('1', dbkey, wgActionPaths[action]); +// if (query != Bry_.Empty) { +// url = wfAppendQuery(url, query); +// } +// } +// } +// +// if (url == false +// && wgVariantArticlePath +// && preg_match('/^variant=([^&]*)/', query, matches) +// && this.getPageLanguage().equals(wgContLang) +// && this.getPageLanguage().hasVariants() +// ) { +// variant = urldecode(matches[1]); +// if (this.getPageLanguage().hasVariant(variant)) { +// // Only do the variant replacement if the given variant is a valid +// // variant for the page's language. +// url = str_replace('2', urlencode(variant), wgVariantArticlePath); +// url = str_replace('1', dbkey, url); +// } +// } +// +// if (url == false) { +// if (query == '-') { +// query = Bry_.Empty; +// } +// url = "{wgScript}?title={dbkey}&{query}"; +// } +// } + // XO.MW.HOOK:GetLocalURL::Internal +// +// // @todo FIXME: This causes breakage in various places when we +// // actually expected a local URL and end up with dupe prefixes. +// if (wgRequest.getVal('action') == 'render') { +// url = wgServer . url; +// } +// } + // XO.MW.HOOK:GetLocalURL + return url; + } + + /** + * Get a URL that's the simplest URL that will be valid to link, locally, + * to the current Title. It includes the fragment, but does not include + * the server unless action=render is used (or the link is external). If + * there's a fragment but the prefixed text is empty, we just return a link + * to the fragment. + * + * The result obviously should not be URL-escaped, but does need to be + * HTML-escaped if it's being output in HTML. + * + * @param String|String[] $query + * @param boolean $query2 + * @param String|int|boolean $proto A PROTO_* constant on how the URL should be expanded, + * or false (default) for no expansion + * @see self::getLocalURL for the arguments. + * @return String The URL + */ + public byte[] getLinkURL(Object qry_mgr, boolean query2, boolean proto) { +// if (this.isExternal() || $proto != false) { +// $ret = this.getFullURL($query, $query2, $proto); +// } elseif (this.getPrefixedText() == Bry_.Empty && this.hasFragment()) { +// $ret = this.getFragmentForURL(); +// } else { +// $ret = this.getLocalURL($query, $query2) . this.getFragmentForURL(); +// } +// return $ret; + return Bry_.Add(gplx.xowa.htmls.hrefs.Xoh_href_.Bry__wiki, this.getPrefixedText()); + } + +// /** +// * Get the URL form for an @gplx.Internal protected link. +// * - Used in various CDN-related code, in case we have a different +// * @gplx.Internal protected hostname for the server from the exposed one. +// * +// * This uses $wgInternalServer to qualify the path, or $wgServer +// * if $wgInternalServer is not set. If the server variable used is +// * protocol-relative, the URL will be expanded to http:// +// * +// * @see self::getLocalURL for the arguments. +// * @return String The URL +// */ +// public function getInternalURL($query = Bry_.Empty, $query2 = false) { +// global $wgInternalServer, $wgServer; +// $query = self::fixUrlQueryArgs($query, $query2); +// $server = $wgInternalServer != false ? $wgInternalServer : $wgServer; +// $url = wfExpandUrl($server . this.getLocalURL($query), PROTO_HTTP); +// // Avoid PHP 7.1 warning from passing this by reference +// $titleRef = this; +// Hooks::run('GetInternalURL', [ &$titleRef, &$url, $query ]); +// return $url; +// } +// +// /** +// * Get the URL for a canonical link, for use in things like IRC and +// * e-mail notifications. Uses $wgCanonicalServer and the +// * GetCanonicalURL hook. +// * +// * NOTE: Unlike getInternalURL(), the canonical URL includes the fragment +// * +// * @see self::getLocalURL for the arguments. +// * @return String The URL +// * @since 1.18 +// */ +// public function getCanonicalURL($query = Bry_.Empty, $query2 = false) { +// $query = self::fixUrlQueryArgs($query, $query2); +// $url = wfExpandUrl(this.getLocalURL($query) . this.getFragmentForURL(), PROTO_CANONICAL); +// // Avoid PHP 7.1 warning from passing this by reference +// $titleRef = this; +// Hooks::run('GetCanonicalURL', [ &$titleRef, &$url, $query ]); +// return $url; +// } +// +// /** +// * Get the edit URL for this Title +// * +// * @return String The URL, or a null String if this is an interwiki link +// */ +// public function getEditURL() { +// if (this.isExternal()) { +// return Bry_.Empty; +// } +// $s = this.getLocalURL('action=edit'); +// +// return $s; +// } +// +// /** +// * Can $user perform $action on this page? +// * This skips potentially expensive cascading permission checks +// * as well as avoids expensive error formatting +// * +// * Suitable for use for nonessential UI controls in common cases, but +// * _not_ for functional access control. +// * +// * May provide false positives, but should never provide a false negative. +// * +// * @param String $action Action that permission needs to be checked for +// * @param User $user User to check (since 1.19); $wgUser will be used if not provided. +// * @return boolean +// */ +// public function quickUserCan($action, $user = null) { +// return this.userCan($action, $user, false); +// } +// +// /** +// * Can $user perform $action on this page? +// * +// * @param String $action Action that permission needs to be checked for +// * @param User $user User to check (since 1.19); $wgUser will be used if not +// * provided. +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @return boolean +// */ +// public function userCan($action, $user = null, $rigor = 'secure') { +// if (!$user instanceof User) { +// global $wgUser; +// $user = $wgUser; +// } +// +// return !count(this.getUserPermissionsErrorsInternal($action, $user, $rigor, true)); +// } +// +// /** +// * Can $user perform $action on this page? +// * +// * @todo FIXME: This *does not* check throttles (User::pingLimiter()). +// * +// * @param String $action Action that permission needs to be checked for +// * @param User $user User to check +// * @param String $rigor One of (quick,full,secure) +// * - quick : does cheap permission checks from replica DBs (usable for GUI creation) +// * - full : does cheap and expensive checks possibly from a replica DB +// * - secure : does cheap and expensive checks, using the master as needed +// * @param array $ignoreErrors Array of Strings Set this to a list of message keys +// * whose corresponding errors may be ignored. +// * @return array Array of arrays of the arguments to wfMessage to explain permissions problems. +// */ +// public function getUserPermissionsErrors( +// $action, $user, $rigor = 'secure', $ignoreErrors = [] +// ) { +// $errors = this.getUserPermissionsErrorsInternal($action, $user, $rigor); +// +// // Remove the errors being ignored. +// foreach ($errors as $index => $error) { +// $errKey = is_array($error) ? $error[0] : $error; +// +// if (in_array($errKey, $ignoreErrors)) { +// unset($errors[$index]); +// } +// if ($errKey instanceof MessageSpecifier && in_array($errKey.getKey(), $ignoreErrors)) { +// unset($errors[$index]); +// } +// } +// +// return $errors; +// } +// +// /** +// * Permissions checks that fail most often, and which are easiest to test. +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkQuickPermissions($action, $user, $errors, $rigor, $short) { +// if (!Hooks::run('TitleQuickPermissions', +// [ this, $user, $action, &$errors, ($rigor != 'quick'), $short ]) +// ) { +// return $errors; +// } +// +// if ($action == 'create') { +// if ( +// (this.isTalkPage() && !$user.isAllowed('createtalk')) || +// (!this.isTalkPage() && !$user.isAllowed('createpage')) +// ) { +// $errors[] = $user.isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ]; +// } +// } elseif ($action == 'move') { +// if (!$user.isAllowed('move-rootuserpages') +// && this.mNamespace == NS_USER && !this.isSubpage()) { +// // Show user page-specific message only if the user can move other pages +// $errors[] = [ 'cant-move-user-page' ]; +// } +// +// // Check if user is allowed to move files if it's a file +// if (this.mNamespace == NS_FILE && !$user.isAllowed('movefile')) { +// $errors[] = [ 'movenotallowedfile' ]; +// } +// +// // Check if user is allowed to move category pages if it's a category page +// if (this.mNamespace == NS_CATEGORY && !$user.isAllowed('move-categorypages')) { +// $errors[] = [ 'cant-move-category-page' ]; +// } +// +// if (!$user.isAllowed('move')) { +// // User can't move anything +// $userCanMove = User::groupHasPermission('user', 'move'); +// $autoconfirmedCanMove = User::groupHasPermission('autoconfirmed', 'move'); +// if ($user.isAnon() && ($userCanMove || $autoconfirmedCanMove)) { +// // custom message if logged-in users without any special rights can move +// $errors[] = [ 'movenologintext' ]; +// } else { +// $errors[] = [ 'movenotallowed' ]; +// } +// } +// } elseif ($action == 'move-target') { +// if (!$user.isAllowed('move')) { +// // User can't move anything +// $errors[] = [ 'movenotallowed' ]; +// } elseif (!$user.isAllowed('move-rootuserpages') +// && this.mNamespace == NS_USER && !this.isSubpage()) { +// // Show user page-specific message only if the user can move other pages +// $errors[] = [ 'cant-move-to-user-page' ]; +// } elseif (!$user.isAllowed('move-categorypages') +// && this.mNamespace == NS_CATEGORY) { +// // Show category page-specific message only if the user can move other pages +// $errors[] = [ 'cant-move-to-category-page' ]; +// } +// } elseif (!$user.isAllowed($action)) { +// $errors[] = this.missingPermissionError($action, $short); +// } +// +// return $errors; +// } +// +// /** +// * Add the resulting error code to the errors array +// * +// * @param array $errors List of current errors +// * @param array $result Result of errors +// * +// * @return array List of errors +// */ +// private function resultToError($errors, $result) { +// if (is_array($result) && count($result) && !is_array($result[0])) { +// // A single array representing an error +// $errors[] = $result; +// } elseif (is_array($result) && is_array($result[0])) { +// // A nested array representing multiple errors +// $errors = array_merge($errors, $result); +// } elseif ($result != Bry_.Empty && is_string($result)) { +// // A String representing a message-id +// $errors[] = [ $result ]; +// } elseif ($result instanceof MessageSpecifier) { +// // A message specifier representing an error +// $errors[] = [ $result ]; +// } elseif ($result == false) { +// // a generic "We don't want them to do that" +// $errors[] = [ 'badaccess-group0' ]; +// } +// return $errors; +// } +// +// /** +// * Check various permission hooks +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkPermissionHooks($action, $user, $errors, $rigor, $short) { +// // Use getUserPermissionsErrors instead +// $result = Bry_.Empty; +// // Avoid PHP 7.1 warning from passing this by reference +// $titleRef = this; +// if (!Hooks::run('userCan', [ &$titleRef, &$user, $action, &$result ])) { +// return $result ? [] : [ [ 'badaccess-group0' ] ]; +// } +// // Check getUserPermissionsErrors hook +// // Avoid PHP 7.1 warning from passing this by reference +// $titleRef = this; +// if (!Hooks::run('getUserPermissionsErrors', [ &$titleRef, &$user, $action, &$result ])) { +// $errors = this.resultToError($errors, $result); +// } +// // Check getUserPermissionsErrorsExpensive hook +// if ( +// $rigor != 'quick' +// && !($short && count($errors) > 0) +// && !Hooks::run('getUserPermissionsErrorsExpensive', [ &$titleRef, &$user, $action, &$result ]) +// ) { +// $errors = this.resultToError($errors, $result); +// } +// +// return $errors; +// } +// +// /** +// * Check permissions on special pages & namespaces +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkSpecialsAndNSPermissions($action, $user, $errors, $rigor, $short) { +// // Only 'createaccount' can be performed on special pages, +// // which don't actually exist in the DB. +// if (NS_SPECIAL == this.mNamespace && $action != 'createaccount') { +// $errors[] = [ 'ns-specialprotected' ]; +// } +// +// // Check $wgNamespaceProtection for restricted namespaces +// if (this.isNamespaceProtected($user)) { +// $ns = this.mNamespace == NS_MAIN ? +// wfMessage('nstab-main').text() : this.getNsText(); +// $errors[] = this.mNamespace == NS_MEDIAWIKI ? +// [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ]; +// } +// +// return $errors; +// } +// +// /** +// * Check CSS/JS sub-page permissions +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkCSSandJSPermissions($action, $user, $errors, $rigor, $short) { +// // Protect css/js subpages of user pages +// // XXX: this might be better using restrictions +// // XXX: right 'editusercssjs' is deprecated, for backward compatibility only +// if ($action != 'patrol' && !$user.isAllowed('editusercssjs')) { +// if (preg_match('/^' . preg_quote($user.getName(), '/') . '\//', this.mTextform)) { +// if (this.isCssSubpage() && !$user.isAllowedAny('editmyusercss', 'editusercss')) { +// $errors[] = [ 'mycustomcssprotected', $action ]; +// } elseif (this.isJsSubpage() && !$user.isAllowedAny('editmyuserjs', 'edituserjs')) { +// $errors[] = [ 'mycustomjsprotected', $action ]; +// } +// } else { +// if (this.isCssSubpage() && !$user.isAllowed('editusercss')) { +// $errors[] = [ 'customcssprotected', $action ]; +// } elseif (this.isJsSubpage() && !$user.isAllowed('edituserjs')) { +// $errors[] = [ 'customjsprotected', $action ]; +// } +// } +// } +// +// return $errors; +// } +// +// /** +// * Check against page_restrictions table requirements on this +// * page. The user must possess all required rights for this +// * action. +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkPageRestrictions($action, $user, $errors, $rigor, $short) { +// foreach (this.getRestrictions($action) as $right) { +// // Backwards compatibility, rewrite sysop . editprotected +// if ($right == 'sysop') { +// $right = 'editprotected'; +// } +// // Backwards compatibility, rewrite autoconfirmed . editsemiprotected +// if ($right == 'autoconfirmed') { +// $right = 'editsemiprotected'; +// } +// if ($right == Bry_.Empty) { +// continue; +// } +// if (!$user.isAllowed($right)) { +// $errors[] = [ 'protectedpagetext', $right, $action ]; +// } elseif (this.mCascadeRestriction && !$user.isAllowed('protect')) { +// $errors[] = [ 'protectedpagetext', 'protect', $action ]; +// } +// } +// +// return $errors; +// } +// +// /** +// * Check restrictions on cascading pages. +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkCascadingSourcesRestrictions($action, $user, $errors, $rigor, $short) { +// if ($rigor != 'quick' && !this.isCssJsSubpage()) { +// // We /could/ use the protection level on the source page, but it's +// // fairly ugly as we have to establish a precedence hierarchy for pages +// // included by multiple cascade-protected pages. So just restrict +// // it to people with 'protect' permission, as they could remove the +// // protection anyway. +// list($cascadingSources, $restrictions) = this.getCascadeProtectionSources(); +// // Cascading protection depends on more than this page... +// // Several cascading protected pages may include this page... +// // Check each cascading level +// // This is only for protection restrictions, not for all actions +// if (isset($restrictions[$action])) { +// foreach ($restrictions[$action] as $right) { +// // Backwards compatibility, rewrite sysop . editprotected +// if ($right == 'sysop') { +// $right = 'editprotected'; +// } +// // Backwards compatibility, rewrite autoconfirmed . editsemiprotected +// if ($right == 'autoconfirmed') { +// $right = 'editsemiprotected'; +// } +// if ($right != Bry_.Empty && !$user.isAllowedAll('protect', $right)) { +// $pages = Bry_.Empty; +// foreach ($cascadingSources as $page) { +// $pages .= '* [[:' . $page.getPrefixedText() . "]]\n"; +// } +// $errors[] = [ 'cascadeprotected', count($cascadingSources), $pages, $action ]; +// } +// } +// } +// } +// +// return $errors; +// } +// +// /** +// * Check action permissions not already checked in checkQuickPermissions +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkActionPermissions($action, $user, $errors, $rigor, $short) { +// global $wgDeleteRevisionsLimit, $wgLang; +// +// if ($action == 'protect') { +// if (count(this.getUserPermissionsErrorsInternal('edit', $user, $rigor, true))) { +// // If they can't edit, they shouldn't protect. +// $errors[] = [ 'protect-cantedit' ]; +// } +// } elseif ($action == 'create') { +// $title_protection = this.getTitleProtection(); +// if ($title_protection) { +// if ($title_protection['permission'] == Bry_.Empty +// || !$user.isAllowed($title_protection['permission']) +// ) { +// $errors[] = [ +// 'titleprotected', +// User::whoIs($title_protection['user']), +// $title_protection['reason'] +// ]; +// } +// } +// } elseif ($action == 'move') { +// // Check for immobile pages +// if (!XomwNamespace::isMovable(this.mNamespace)) { +// // Specific message for this case +// $errors[] = [ 'immobile-source-namespace', this.getNsText() ]; +// } elseif (!this.isMovable()) { +// // Less specific message for rarer cases +// $errors[] = [ 'immobile-source-page' ]; +// } +// } elseif ($action == 'move-target') { +// if (!XomwNamespace::isMovable(this.mNamespace)) { +// $errors[] = [ 'immobile-target-namespace', this.getNsText() ]; +// } elseif (!this.isMovable()) { +// $errors[] = [ 'immobile-target-page' ]; +// } +// } elseif ($action == 'delete') { +// $tempErrors = this.checkPageRestrictions('edit', $user, [], $rigor, true); +// if (!$tempErrors) { +// $tempErrors = this.checkCascadingSourcesRestrictions('edit', +// $user, $tempErrors, $rigor, true); +// } +// if ($tempErrors) { +// // If protection keeps them from editing, they shouldn't be able to delete. +// $errors[] = [ 'deleteprotected' ]; +// } +// if ($rigor != 'quick' && $wgDeleteRevisionsLimit +// && !this.userCan('bigdelete', $user) && this.isBigDeletion() +// ) { +// $errors[] = [ 'delete-toobig', $wgLang.formatNum($wgDeleteRevisionsLimit) ]; +// } +// } +// return $errors; +// } +// +// /** +// * Check that the user isn't blocked from editing. +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkUserBlock($action, $user, $errors, $rigor, $short) { +// global $wgEmailConfirmToEdit, $wgBlockDisablesLogin; +// // Account creation blocks handled at userlogin. +// // Unblocking handled in SpecialUnblock +// if ($rigor == 'quick' || in_array($action, [ 'createaccount', 'unblock' ])) { +// return $errors; +// } +// +// // Optimize for a very common case +// if ($action == 'read' && !$wgBlockDisablesLogin) { +// return $errors; +// } +// +// if ($wgEmailConfirmToEdit && !$user.isEmailConfirmed()) { +// $errors[] = [ 'confirmedittext' ]; +// } +// +// $useSlave = ($rigor != 'secure'); +// if (($action == 'edit' || $action == 'create') +// && !$user.isBlockedFrom(this, $useSlave) +// ) { +// // Don't block the user from editing their own talk page unless they've been +// // explicitly blocked from that too. +// } elseif ($user.isBlocked() && $user.getBlock().prevents($action) != false) { +// // @todo FIXME: Pass the relevant context into this function. +// $errors[] = $user.getBlock().getPermissionsError(RequestContext::getMain()); +// } +// +// return $errors; +// } +// +// /** +// * Check that the user is allowed to read this page. +// * +// * @param String $action The action to check +// * @param User $user User to check +// * @param array $errors List of current errors +// * @param String $rigor Same format as Title::getUserPermissionsErrors() +// * @param boolean $short Short circuit on first error +// * +// * @return array List of errors +// */ +// private function checkReadPermissions($action, $user, $errors, $rigor, $short) { +// global $wgWhitelistRead, $wgWhitelistReadRegexp; +// +// $whitelisted = false; +// if (User::isEveryoneAllowed('read')) { +// // Shortcut for public wikis, allows skipping quite a bit of code +// $whitelisted = true; +// } elseif ($user.isAllowed('read')) { +// // If the user is allowed to read pages, he is allowed to read all pages +// $whitelisted = true; +// } elseif (this.isSpecial('Userlogin') +// || this.isSpecial('PasswordReset') +// || this.isSpecial('Userlogout') +// ) { +// // Always grant access to the login page. +// // Even anons need to be able to log in. +// $whitelisted = true; +// } elseif (is_array($wgWhitelistRead) && count($wgWhitelistRead)) { +// // Time to check the whitelist +// // Only do these checks is there's something to check against +// $name = this.getPrefixedText(); +// $dbName = this.getPrefixedDBkey(); +// +// // Check for explicit whitelisting with and without underscores +// if (in_array($name, $wgWhitelistRead, true) || in_array($dbName, $wgWhitelistRead, true)) { +// $whitelisted = true; +// } elseif (this.getNamespace() == NS_MAIN) { +// // Old settings might have the title prefixed with +// // a colon for main-namespace pages +// if (in_array(':' . $name, $wgWhitelistRead)) { +// $whitelisted = true; +// } +// } elseif (this.isSpecialPage()) { +// // If it's a special page, ditch the subpage bit and check again +// $name = this.getDBkey(); +// list($name, /* $subpage */) = SpecialPageFactory::resolveAlias($name); +// if ($name) { +// $pure = SpecialPage::getTitleFor($name).getPrefixedText(); +// if (in_array($pure, $wgWhitelistRead, true)) { +// $whitelisted = true; +// } +// } +// } +// } +// +// if (!$whitelisted && is_array($wgWhitelistReadRegexp) && !empty($wgWhitelistReadRegexp)) { +// $name = this.getPrefixedText(); +// // Check for regex whitelisting +// foreach ($wgWhitelistReadRegexp as $listItem) { +// if (preg_match($listItem, $name)) { +// $whitelisted = true; +// break; +// } +// } +// } +// +// if (!$whitelisted) { +// // If the title is not whitelisted, give extensions a chance to do so... +// Hooks::run('TitleReadWhitelist', [ this, $user, &$whitelisted ]); +// if (!$whitelisted) { +// $errors[] = this.missingPermissionError($action, $short); +// } +// } +// +// return $errors; +// } +// +// /** +// * Get a description array when the user doesn't have the right to perform +// * $action (i.e. when User::isAllowed() returns false) +// * +// * @param String $action The action to check +// * @param boolean $short Short circuit on first error +// * @return array List of errors +// */ +// private function missingPermissionError($action, $short) { +// // We avoid expensive display logic for quickUserCan's and such +// if ($short) { +// return [ 'badaccess-group0' ]; +// } +// +// $groups = array_map([ 'User', 'makeGroupLinkWiki' ], +// User::getGroupsWithPermission($action)); +// +// if (count($groups)) { +// global $wgLang; +// return [ +// 'badaccess-groups', +// $wgLang.commaList($groups), +// count($groups) +// ]; +// } else { +// return [ 'badaccess-group0' ]; +// } +// } +// +// /** +// * Can $user perform $action on this page? This is an @gplx.Internal protected function, +// * with multiple levels of checks depending on performance needs; see $rigor below. +// * It does not check wfReadOnly(). +// * +// * @param String $action Action that permission needs to be checked for +// * @param User $user User to check +// * @param String $rigor One of (quick,full,secure) +// * - quick : does cheap permission checks from replica DBs (usable for GUI creation) +// * - full : does cheap and expensive checks possibly from a replica DB +// * - secure : does cheap and expensive checks, using the master as needed +// * @param boolean $short Set this to true to stop after the first permission error. +// * @return array Array of arrays of the arguments to wfMessage to explain permissions problems. +// */ +// protected function getUserPermissionsErrorsInternal( +// $action, $user, $rigor = 'secure', $short = false +// ) { +// if ($rigor == true) { +// $rigor = 'secure'; // b/c +// } elseif ($rigor == false) { +// $rigor = 'quick'; // b/c +// } elseif (!in_array($rigor, [ 'quick', 'full', 'secure' ])) { +// throw new Exception("Invalid rigor parameter '$rigor'."); +// } +// +// // Read has special handling +// if ($action == 'read') { +// $checks = [ +// 'checkPermissionHooks', +// 'checkReadPermissions', +// 'checkUserBlock', // for wgBlockDisablesLogin +// ]; +// // Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions +// // here as it will lead to duplicate error messages. This is okay to do +// // since anywhere that checks for create will also check for edit, and +// // those checks are called for edit. +// } elseif ($action == 'create') { +// $checks = [ +// 'checkQuickPermissions', +// 'checkPermissionHooks', +// 'checkPageRestrictions', +// 'checkCascadingSourcesRestrictions', +// 'checkActionPermissions', +// 'checkUserBlock' +// ]; +// } else { +// $checks = [ +// 'checkQuickPermissions', +// 'checkPermissionHooks', +// 'checkSpecialsAndNSPermissions', +// 'checkCSSandJSPermissions', +// 'checkPageRestrictions', +// 'checkCascadingSourcesRestrictions', +// 'checkActionPermissions', +// 'checkUserBlock' +// ]; +// } +// +// $errors = []; +// while (count($checks) > 0 && +// !($short && count($errors) > 0)) { +// $method = array_shift($checks); +// $errors = this.$method($action, $user, $errors, $rigor, $short); +// } +// +// return $errors; +// } +// +// /** +// * Get a filtered list of all restriction types supported by this wiki. +// * @param boolean $exists True to get all restriction types that apply to +// * titles that do exist, False for all restriction types that apply to +// * titles that do not exist +// * @return array +// */ +// public static function getFilteredRestrictionTypes($exists = true) { +// global $wgRestrictionTypes; +// $types = $wgRestrictionTypes; +// if ($exists) { +// // Remove the create restriction for existing titles +// $types = array_diff($types, [ 'create' ]); +// } else { +// // Only the create and upload restrictions apply to non-existing titles +// $types = array_intersect($types, [ 'create', 'upload' ]); +// } +// return $types; +// } +// +// /** +// * Returns restriction types for the current Title +// * +// * @return array Applicable restriction types +// */ +// public function getRestrictionTypes() { +// if (this.isSpecialPage()) { +// return []; +// } +// +// $types = self::getFilteredRestrictionTypes(this.exists()); +// +// if (this.getNamespace() != NS_FILE) { +// // Remove the upload restriction for non-file titles +// $types = array_diff($types, [ 'upload' ]); +// } +// +// Hooks::run('TitleGetRestrictionTypes', [ this, &$types ]); +// +// wfDebug(__METHOD__ . ': applicable restrictions to [[' . +// this.getPrefixedText() . ']] are {' . implode(',', $types) . "}\n"); +// +// return $types; +// } +// +// /** +// * Is this title subject to title protection? +// * Title protection is the one applied against creation of such title. +// * +// * @return array|boolean An associative array representing any existent title +// * protection, or false if there's none. +// */ +// public function getTitleProtection() { +// // Can't protect pages in special namespaces +// if (this.getNamespace() < 0) { +// return false; +// } +// +// // Can't protect pages that exist. +// if (this.exists()) { +// return false; +// } +// +// if (this.mTitleProtection == null) { +// $dbr = wfGetDB(DB_REPLICA); +// $res = $dbr.select( +// 'protected_titles', +// [ +// 'user' => 'pt_user', +// 'reason' => 'pt_reason', +// 'expiry' => 'pt_expiry', +// 'permission' => 'pt_create_perm' +// ], +// [ 'pt_namespace' => this.getNamespace(), 'pt_title' => this.getDBkey() ], +// __METHOD__ +// ); +// +// // fetchRow returns false if there are no rows. +// $row = $dbr.fetchRow($res); +// if ($row) { +// if ($row['permission'] == 'sysop') { +// $row['permission'] = 'editprotected'; // B/C +// } +// if ($row['permission'] == 'autoconfirmed') { +// $row['permission'] = 'editsemiprotected'; // B/C +// } +// $row['expiry'] = $dbr.decodeExpiry($row['expiry']); +// } +// this.mTitleProtection = $row; +// } +// return this.mTitleProtection; +// } +// +// /** +// * Remove any title protection due to page existing +// */ +// public function deleteTitleProtection() { +// $dbw = wfGetDB(DB_MASTER); +// +// $dbw.delete( +// 'protected_titles', +// [ 'pt_namespace' => this.getNamespace(), 'pt_title' => this.getDBkey() ], +// __METHOD__ +// ); +// this.mTitleProtection = false; +// } +// +// /** +// * Is this page "semi-protected" - the *only* protection levels are listed +// * in $wgSemiprotectedRestrictionLevels? +// * +// * @param String $action Action to check (default: edit) +// * @return boolean +// */ +// public function isSemiProtected($action = 'edit') { +// global $wgSemiprotectedRestrictionLevels; +// +// $restrictions = this.getRestrictions($action); +// $semi = $wgSemiprotectedRestrictionLevels; +// if (!$restrictions || !$semi) { +// // Not protected, or all protection is full protection +// return false; +// } +// +// // Remap autoconfirmed to editsemiprotected for BC +// foreach (array_keys($semi, 'autoconfirmed') as $key) { +// $semi[$key] = 'editsemiprotected'; +// } +// foreach (array_keys($restrictions, 'autoconfirmed') as $key) { +// $restrictions[$key] = 'editsemiprotected'; +// } +// +// return !array_diff($restrictions, $semi); +// } +// +// /** +// * Does the title correspond to a protected article? +// * +// * @param String $action The action the page is protected from, +// * by default checks all actions. +// * @return boolean +// */ +// public function isProtected($action = Bry_.Empty) { +// global $wgRestrictionLevels; +// +// $restrictionTypes = this.getRestrictionTypes(); +// +// // Special pages have inherent protection +// if (this.isSpecialPage()) { +// return true; +// } +// +// // Check regular protection levels +// foreach ($restrictionTypes as $type) { +// if ($action == $type || $action == Bry_.Empty) { +// $r = this.getRestrictions($type); +// foreach ($wgRestrictionLevels as $level) { +// if (in_array($level, $r) && $level != Bry_.Empty) { +// return true; +// } +// } +// } +// } +// +// return false; +// } +// +// /** +// * Determines if $user is unable to edit this page because it has been protected +// * by $wgNamespaceProtection. +// * +// * @param User $user User Object to check permissions +// * @return boolean +// */ +// public function isNamespaceProtected(User $user) { +// global $wgNamespaceProtection; +// +// if (isset($wgNamespaceProtection[this.mNamespace])) { +// foreach ((array)$wgNamespaceProtection[this.mNamespace] as $right) { +// if ($right != Bry_.Empty && !$user.isAllowed($right)) { +// return true; +// } +// } +// } +// return false; +// } +// +// /** +// * Cascading protection: Return true if cascading restrictions apply to this page, false if not. +// * +// * @return boolean If the page is subject to cascading restrictions. +// */ +// public function isCascadeProtected() { +// list($sources, /* $restrictions */) = this.getCascadeProtectionSources(false); +// return ($sources > 0); +// } +// +// /** +// * Determines whether cascading protection sources have already been loaded from +// * the database. +// * +// * @param boolean $getPages True to check if the pages are loaded, or false to check +// * if the status is loaded. +// * @return boolean Whether or not the specified information has been loaded +// * @since 1.23 +// */ +// public function areCascadeProtectionSourcesLoaded($getPages = true) { +// return $getPages ? this.mCascadeSources != null : this.mHasCascadingRestrictions != null; +// } +// +// /** +// * Cascading protection: Get the source of any cascading restrictions on this page. +// * +// * @param boolean $getPages Whether or not to retrieve the actual pages +// * that the restrictions have come from and the actual restrictions +// * themselves. +// * @return array Two elements: First is an array of Title objects of the +// * pages from which cascading restrictions have come, false for +// * none, or true if such restrictions exist but $getPages was not +// * set. Second is an array like that returned by +// * Title::getAllRestrictions(), or an empty array if $getPages is +// * false. +// */ +// public function getCascadeProtectionSources($getPages = true) { +// $pagerestrictions = []; +// +// if (this.mCascadeSources != null && $getPages) { +// return [ this.mCascadeSources, this.mCascadingRestrictions ]; +// } elseif (this.mHasCascadingRestrictions != null && !$getPages) { +// return [ this.mHasCascadingRestrictions, $pagerestrictions ]; +// } +// +// $dbr = wfGetDB(DB_REPLICA); +// +// if (this.getNamespace() == NS_FILE) { +// $tables = [ 'imagelinks', 'page_restrictions' ]; +// $where_clauses = [ +// 'il_to' => this.getDBkey(), +// 'il_from=pr_page', +// 'pr_cascade' => 1 +// ]; +// } else { +// $tables = [ 'templatelinks', 'page_restrictions' ]; +// $where_clauses = [ +// 'tl_namespace' => this.getNamespace(), +// 'tl_title' => this.getDBkey(), +// 'tl_from=pr_page', +// 'pr_cascade' => 1 +// ]; +// } +// +// if ($getPages) { +// $cols = [ 'pr_page', 'page_namespace', 'page_title', +// 'pr_expiry', 'pr_type', 'pr_level' ]; +// $where_clauses[] = 'page_id=pr_page'; +// $tables[] = 'page'; +// } else { +// $cols = [ 'pr_expiry' ]; +// } +// +// $res = $dbr.select($tables, $cols, $where_clauses, __METHOD__); +// +// $sources = $getPages ? [] : false; +// $now = wfTimestampNow(); +// +// foreach ($res as $row) { +// $expiry = $dbr.decodeExpiry($row.pr_expiry); +// if ($expiry > $now) { +// if ($getPages) { +// $page_id = $row.pr_page; +// $page_ns = $row.page_namespace; +// $page_title = $row.page_title; +// $sources[$page_id] = Title::makeTitle($page_ns, $page_title); +// // Add groups needed for each restriction type if its not already there +// // Make sure this restriction type still exists +// +// if (!isset($pagerestrictions[$row.pr_type])) { +// $pagerestrictions[$row.pr_type] = []; +// } +// +// if ( +// isset($pagerestrictions[$row.pr_type]) +// && !in_array($row.pr_level, $pagerestrictions[$row.pr_type]) +// ) { +// $pagerestrictions[$row.pr_type][] = $row.pr_level; +// } +// } else { +// $sources = true; +// } +// } +// } +// +// if ($getPages) { +// this.mCascadeSources = $sources; +// this.mCascadingRestrictions = $pagerestrictions; +// } else { +// this.mHasCascadingRestrictions = $sources; +// } +// +// return [ $sources, $pagerestrictions ]; +// } +// +// /** +// * Accessor for mRestrictionsLoaded +// * +// * @return boolean Whether or not the page's restrictions have already been +// * loaded from the database +// * @since 1.23 +// */ +// public function areRestrictionsLoaded() { +// return this.mRestrictionsLoaded; +// } +// +// /** +// * Accessor/initialisation for mRestrictions +// * +// * @param String $action Action that permission needs to be checked for +// * @return array Restriction levels needed to take the action. All levels are +// * required. Note that restriction levels are normally user rights, but 'sysop' +// * and 'autoconfirmed' are also allowed for backwards compatibility. These should +// * be mapped to 'editprotected' and 'editsemiprotected' respectively. +// */ +// public function getRestrictions($action) { +// if (!this.mRestrictionsLoaded) { +// this.loadRestrictions(); +// } +// return isset(this.mRestrictions[$action]) +// ? this.mRestrictions[$action] +// : []; +// } +// +// /** +// * Accessor/initialisation for mRestrictions +// * +// * @return array Keys are actions, values are arrays as returned by +// * Title::getRestrictions() +// * @since 1.23 +// */ +// public function getAllRestrictions() { +// if (!this.mRestrictionsLoaded) { +// this.loadRestrictions(); +// } +// return this.mRestrictions; +// } +// +// /** +// * Get the expiry time for the restriction against a given action +// * +// * @param String $action +// * @return String|boolean 14-char timestamp, or 'infinity' if the page is protected forever +// * or not protected at all, or false if the action is not recognised. +// */ +// public function getRestrictionExpiry($action) { +// if (!this.mRestrictionsLoaded) { +// this.loadRestrictions(); +// } +// return isset(this.mRestrictionsExpiry[$action]) ? this.mRestrictionsExpiry[$action] : false; +// } +// +// /** +// * Returns cascading restrictions for the current article +// * +// * @return boolean +// */ +// function areRestrictionsCascading() { +// if (!this.mRestrictionsLoaded) { +// this.loadRestrictions(); +// } +// +// return this.mCascadeRestriction; +// } +// +// /** +// * Compiles list of active page restrictions from both page table (pre 1.10) +// * and page_restrictions table for this existing page. +// * Public for usage by LiquidThreads. +// * +// * @param array $rows Array of db result objects +// * @param String $oldFashionedRestrictions Comma-separated list of page +// * restrictions from page table (pre 1.10) +// */ +// public function loadRestrictionsFromRows($rows, $oldFashionedRestrictions = null) { +// $dbr = wfGetDB(DB_REPLICA); +// +// $restrictionTypes = this.getRestrictionTypes(); +// +// foreach ($restrictionTypes as $type) { +// this.mRestrictions[$type] = []; +// this.mRestrictionsExpiry[$type] = 'infinity'; +// } +// +// this.mCascadeRestriction = false; +// +// // Backwards-compatibility: also load the restrictions from the page record (old format). +// if ($oldFashionedRestrictions != null) { +// this.mOldRestrictions = $oldFashionedRestrictions; +// } +// +// if (this.mOldRestrictions == false) { +// this.mOldRestrictions = $dbr.selectField('page', 'page_restrictions', +// [ 'page_id' => this.getArticleID() ], __METHOD__); +// } +// +// if (this.mOldRestrictions != Bry_.Empty) { +// foreach (explode(':', trim(this.mOldRestrictions)) as $restrict) { +// $temp = explode('=', trim($restrict)); +// if (count($temp) == 1) { +// // old old format should be treated as edit/move restriction +// this.mRestrictions['edit'] = explode(',', trim($temp[0])); +// this.mRestrictions['move'] = explode(',', trim($temp[0])); +// } else { +// $restriction = trim($temp[1]); +// if ($restriction != Bry_.Empty) { // some old entries are empty +// this.mRestrictions[$temp[0]] = explode(',', $restriction); +// } +// } +// } +// } +// +// if (count($rows)) { +// // Current system - load second to make them override. +// $now = wfTimestampNow(); +// +// // Cycle through all the restrictions. +// foreach ($rows as $row) { +// // Don't take care of restrictions types that aren't allowed +// if (!in_array($row.pr_type, $restrictionTypes)) { +// continue; +// } +// +// // This code should be refactored, now that it's being used more generally, +// // But I don't really see any harm in leaving it in Block for now -werdna +// $expiry = $dbr.decodeExpiry($row.pr_expiry); +// +// // Only apply the restrictions if they haven't expired! +// if (!$expiry || $expiry > $now) { +// this.mRestrictionsExpiry[$row.pr_type] = $expiry; +// this.mRestrictions[$row.pr_type] = explode(',', trim($row.pr_level)); +// +// this.mCascadeRestriction |= $row.pr_cascade; +// } +// } +// } +// +// this.mRestrictionsLoaded = true; +// } +// +// /** +// * Load restrictions from the page_restrictions table +// * +// * @param String $oldFashionedRestrictions Comma-separated list of page +// * restrictions from page table (pre 1.10) +// */ +// public function loadRestrictions($oldFashionedRestrictions = null) { +// if (this.mRestrictionsLoaded) { +// return; +// } +// +// $id = this.getArticleID(); +// if ($id) { +// $cache = ObjectCache::getMainWANInstance(); +// $rows = $cache.getWithSetCallback( +// // Page protections always leave a new null revision +// $cache.makeKey('page-restrictions', $id, this.getLatestRevID()), +// $cache::TTL_DAY, +// function ($curValue, &$ttl, array &$setOpts) { +// $dbr = wfGetDB(DB_REPLICA); +// +// $setOpts += Database::getCacheSetOptions($dbr); +// +// return iterator_to_array( +// $dbr.select( +// 'page_restrictions', +// [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ], +// [ 'pr_page' => this.getArticleID() ], +// __METHOD__ +// ) +// ); +// } +// ); +// +// this.loadRestrictionsFromRows($rows, $oldFashionedRestrictions); +// } else { +// $title_protection = this.getTitleProtection(); +// +// if ($title_protection) { +// $now = wfTimestampNow(); +// $expiry = wfGetDB(DB_REPLICA).decodeExpiry($title_protection['expiry']); +// +// if (!$expiry || $expiry > $now) { +// // Apply the restrictions +// this.mRestrictionsExpiry['create'] = $expiry; +// this.mRestrictions['create'] = +// explode(',', trim($title_protection['permission'])); +// } else { // Get rid of the old restrictions +// this.mTitleProtection = false; +// } +// } else { +// this.mRestrictionsExpiry['create'] = 'infinity'; +// } +// this.mRestrictionsLoaded = true; +// } +// } +// +// /** +// * Flush the protection cache in this Object and force reload from the database. +// * This is used when updating protection from WikiPage::doUpdateRestrictions(). +// */ +// public function flushRestrictions() { +// this.mRestrictionsLoaded = false; +// this.mTitleProtection = null; +// } +// +// /** +// * Purge expired restrictions from the page_restrictions table +// * +// * This will purge no more than $wgUpdateRowsPerQuery page_restrictions rows +// */ +// static function purgeExpiredRestrictions() { +// if (wfReadOnly()) { +// return; +// } +// +// DeferredUpdates::addUpdate(new AtomicSectionUpdate( +// wfGetDB(DB_MASTER), +// __METHOD__, +// function (IDatabase $dbw, $fname) { +// $config = MediaWikiServices::getInstance().getMainConfig(); +// $ids = $dbw.selectFieldValues( +// 'page_restrictions', +// 'pr_id', +// [ 'pr_expiry < ' . $dbw.addQuotes($dbw.timestamp()) ], +// $fname, +// [ 'LIMIT' => $config.get('UpdateRowsPerQuery') ] // T135470 +// ); +// if ($ids) { +// $dbw.delete('page_restrictions', [ 'pr_id' => $ids ], $fname); +// } +// } +// )); +// +// DeferredUpdates::addUpdate(new AtomicSectionUpdate( +// wfGetDB(DB_MASTER), +// __METHOD__, +// function (IDatabase $dbw, $fname) { +// $dbw.delete( +// 'protected_titles', +// [ 'pt_expiry < ' . $dbw.addQuotes($dbw.timestamp()) ], +// $fname +// ); +// } +// )); +// } +// +// /** +// * Does this have subpages? (Warning, usually requires an extra DB query.) +// * +// * @return boolean +// */ +// public function hasSubpages() { +// if (!XomwNamespace::hasSubpages(this.mNamespace)) { +// // Duh +// return false; +// } +// +// // We dynamically add a member variable for the purpose of this method +// // alone to cache the result. There's no point in having it hanging +// // around uninitialized in every Title Object; therefore we only add it +// // if needed and don't declare it statically. +// if (this.mHasSubpages == null) { +// this.mHasSubpages = false; +// $subpages = this.getSubpages(1); +// if ($subpages instanceof TitleArray) { +// this.mHasSubpages = (boolean)$subpages.count(); +// } +// } +// +// return this.mHasSubpages; +// } +// +// /** +// * Get all subpages of this page. +// * +// * @param int $limit Maximum number of subpages to fetch; -1 for no limit +// * @return TitleArray|array TitleArray, or empty array if this page's namespace +// * doesn't allow subpages +// */ +// public function getSubpages($limit = -1) { +// if (!XomwNamespace::hasSubpages(this.getNamespace())) { +// return []; +// } +// +// $dbr = wfGetDB(DB_REPLICA); +// $conds['page_namespace'] = this.getNamespace(); +// $conds[] = 'page_title ' . $dbr.buildLike(this.getDBkey() . '/', $dbr.anyString()); +// $options = []; +// if ($limit > -1) { +// $options['LIMIT'] = $limit; +// } +// this.mSubpages = TitleArray::newFromResult( +// $dbr.select('page', +// [ 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ], +// $conds, +// __METHOD__, +// $options +// ) +// ); +// return this.mSubpages; +// } +// +// /** +// * Is there a version of this page in the deletion archive? +// * +// * @return int The number of archived revisions +// */ +// public function isDeleted() { +// if (this.getNamespace() < 0) { +// $n = 0; +// } else { +// $dbr = wfGetDB(DB_REPLICA); +// +// $n = $dbr.selectField('archive', 'COUNT(*)', +// [ 'ar_namespace' => this.getNamespace(), 'ar_title' => this.getDBkey() ], +// __METHOD__ +// ); +// if (this.getNamespace() == NS_FILE) { +// $n += $dbr.selectField('filearchive', 'COUNT(*)', +// [ 'fa_name' => this.getDBkey() ], +// __METHOD__ +// ); +// } +// } +// return (int)$n; +// } +// +// /** +// * Is there a version of this page in the deletion archive? +// * +// * @return boolean +// */ +// public function isDeletedQuick() { +// if (this.getNamespace() < 0) { +// return false; +// } +// $dbr = wfGetDB(DB_REPLICA); +// $deleted = (boolean)$dbr.selectField('archive', '1', +// [ 'ar_namespace' => this.getNamespace(), 'ar_title' => this.getDBkey() ], +// __METHOD__ +// ); +// if (!$deleted && this.getNamespace() == NS_FILE) { +// $deleted = (boolean)$dbr.selectField('filearchive', '1', +// [ 'fa_name' => this.getDBkey() ], +// __METHOD__ +// ); +// } +// return $deleted; +// } + + /** + * Get the article ID for this Title from the link cache, + * adding it if necessary + * + * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select + * for update + * @return int The ID + */ + public int getArticleID() {return getArticleID(0);} + public int getArticleID(int flags) { + if (this.getNamespace() < 0) { + this.mArticleID = 0; + return this.mArticleID; + } +// $linkCache = LinkCache::singleton(); +// if ($flags & self::GAID_FOR_UPDATE) { +// $oldUpdate = $linkCache.forUpdate(true); +// $linkCache.clearLink(this); +// this.mArticleID = $linkCache.addLinkObj(this); +// $linkCache.forUpdate($oldUpdate); +// } else { +// if (-1 == this.mArticleID) { +// this.mArticleID = $linkCache.addLinkObj(this); +// } +// } + return this.mArticleID; + } +// +// /** +// * Is this an article that is a redirect page? +// * Uses link cache, adding it if necessary +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return boolean +// */ +// public function isRedirect($flags = 0) { +// if (!is_null(this.mRedirect)) { +// return this.mRedirect; +// } +// if (!this.getArticleID($flags)) { +// this.mRedirect = false; +// return this.mRedirect; +// } +// +// $linkCache = LinkCache::singleton(); +// $linkCache.addLinkObj(this); # in case we already had an article ID +// $cached = $linkCache.getGoodLinkFieldObj(this, 'redirect'); +// if ($cached == null) { +// // Trust LinkCache's state over our own +// // LinkCache is telling us that the page doesn't exist, despite there being cached +// // data relating to an existing page in this.mArticleID. Updaters should clear +// // LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is +// // set, then LinkCache will definitely be up to date here, since getArticleID() forces +// // LinkCache to refresh its data from the master. +// this.mRedirect = false; +// return this.mRedirect; +// } +// +// this.mRedirect = (boolean)$cached; +// +// return this.mRedirect; +// } +// +// /** +// * What is the length of this page? +// * Uses link cache, adding it if necessary +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return int +// */ +// public function getLength($flags = 0) { +// if (this.mLength != -1) { +// return this.mLength; +// } +// if (!this.getArticleID($flags)) { +// this.mLength = 0; +// return this.mLength; +// } +// $linkCache = LinkCache::singleton(); +// $linkCache.addLinkObj(this); # in case we already had an article ID +// $cached = $linkCache.getGoodLinkFieldObj(this, 'length'); +// if ($cached == null) { +// // Trust LinkCache's state over our own, as for isRedirect() +// this.mLength = 0; +// return this.mLength; +// } +// +// this.mLength = intval($cached); +// +// return this.mLength; +// } +// +// /** +// * What is the page_latest field for this page? +// * +// * @param int $flags A bit field; may be Title::GAID_FOR_UPDATE to select for update +// * @return int Int or 0 if the page doesn't exist +// */ +// public function getLatestRevID($flags = 0) { +// if (!($flags & Title::GAID_FOR_UPDATE) && this.mLatestID != false) { +// return intval(this.mLatestID); +// } +// if (!this.getArticleID($flags)) { +// this.mLatestID = 0; +// return this.mLatestID; +// } +// $linkCache = LinkCache::singleton(); +// $linkCache.addLinkObj(this); # in case we already had an article ID +// $cached = $linkCache.getGoodLinkFieldObj(this, 'revision'); +// if ($cached == null) { +// // Trust LinkCache's state over our own, as for isRedirect() +// this.mLatestID = 0; +// return this.mLatestID; +// } +// +// this.mLatestID = intval($cached); +// +// return this.mLatestID; +// } +// +// /** +// * This clears some fields in this Object, and clears any associated +// * keys in the "bad links" section of the link cache. +// * +// * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow +// * loading of the new page_id. It's also called from +// * WikiPage::doDeleteArticleReal() +// * +// * @param int $newid The new Article ID +// */ +// public function resetArticleID($newid) { +// $linkCache = LinkCache::singleton(); +// $linkCache.clearLink(this); +// +// if ($newid == false) { +// this.mArticleID = -1; +// } else { +// this.mArticleID = intval($newid); +// } +// this.mRestrictionsLoaded = false; +// this.mRestrictions = []; +// this.mOldRestrictions = false; +// this.mRedirect = null; +// this.mLength = -1; +// this.mLatestID = false; +// this.mContentModel = false; +// this.mEstimateRevisions = null; +// this.mPageLanguage = false; +// this.mDbPageLanguage = false; +// this.mIsBigDeletion = null; +// } +// +// public static function clearCaches() { +// $linkCache = LinkCache::singleton(); +// $linkCache.clear(); +// +// $titleCache = self::getTitleCache(); +// $titleCache.clear(); +// } +// +// /** +// * Capitalize a text String for a title if it belongs to a namespace that capitalizes +// * +// * @param String $text Containing title to capitalize +// * @param int $ns Namespace index, defaults to NS_MAIN +// * @return String Containing capitalized title +// */ +// public static function capitalize($text, $ns = NS_MAIN) { +// global $wgContLang; +// +// if (XomwNamespace::isCapitalized($ns)) { +// return $wgContLang.ucfirst($text); +// } else { +// return $text; +// } +// } + + /** + * Secure and split - main initialisation function for this Object + * + * Assumes that mDbkeyform has been set, and is urldecoded + * and uses underscores, but not otherwise munged. This function + * removes illegal characters, splits off the interwiki and + * namespace prefixes, sets the other forms, and canonicalizes + * everything. + * + * @throws XomwMalformedTitleException On invalid titles + * @return boolean True on success + */ + private boolean secureAndSplit(XomwEnv env) { + // Initialisation + this.mInterwiki = Bry_.Empty; + this.mFragment = Bry_.Empty; + this.mNamespace = this.mDefaultNamespace; // Usually NS_MAIN + + byte[] dbkey = this.mDbkeyform; + + // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share + // the parsing code with Title, while avoiding massive refactoring. + // @todo: get rid of secureAndSplit, refactor parsing code. + // @note: getTitleParser() returns a TitleParser implementation which does not have a + // splitTitleString method, but the only implementation (MediaWikiTitleCodec) does + XomwMediaWikiTitleCodec titleCodec = env.MediaWikiServices().getTitleParser(); + // XomwMalformedTitleException can be thrown here + XomwMediaWikiTitleCodecParts parts = titleCodec.splitTitleString(dbkey, this.getDefaultNamespace()); + + // Fill fields +// this.setFragment('#' . parts.fragment); + this.mInterwiki = parts.interwiki; + this.mLocalInterwiki = parts.local_interwiki; + this.mNamespace = parts.ns; + this.mUserCaseDBKey = parts.user_case_dbkey; + + this.mDbkeyform = parts.dbkey; + this.mUrlform = XomwGlobalFunctions.wfUrlencode(this.mDbkeyform); + this.mTextform = XophpString_.strtr(this.mDbkeyform, Byte_ascii.Underline, Byte_ascii.Space); + + // We already know that some pages won't be in the database! + if (this.isExternal() || this.mNamespace == XomwDefines.NS_SPECIAL) { + this.mArticleID = 0; + } + + return true; + } + +// /** +// * Get an array of Title objects linking to this Title +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options May be FOR UPDATE +// * @param String $table Table name +// * @param String $prefix Fields prefix +// * @return Title[] Array of Title objects linking here +// */ +// public function getLinksTo($options = [], $table = 'pagelinks', $prefix = 'pl') { +// if (count($options) > 0) { +// $db = wfGetDB(DB_MASTER); +// } else { +// $db = wfGetDB(DB_REPLICA); +// } +// +// $res = $db.select( +// [ 'page', $table ], +// self::getSelectFields(), +// [ +// "{$prefix}_from=page_id", +// "{$prefix}_namespace" => this.getNamespace(), +// "{$prefix}_title" => this.getDBkey() ], +// __METHOD__, +// $options +// ); +// +// $retVal = []; +// if ($res.numRows()) { +// $linkCache = LinkCache::singleton(); +// foreach ($res as $row) { +// $titleObj = Title::makeTitle($row.page_namespace, $row.page_title); +// if ($titleObj) { +// $linkCache.addGoodLinkObjFromRow($titleObj, $row); +// $retVal[] = $titleObj; +// } +// } +// } +// return $retVal; +// } +// +// /** +// * Get an array of Title objects using this Title as a template +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options Query option to Database::select() +// * @return Title[] Array of Title the Title objects linking here +// */ +// public function getTemplateLinksTo($options = []) { +// return this.getLinksTo($options, 'templatelinks', 'tl'); +// } +// +// /** +// * Get an array of Title objects linked from this Title +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options Query option to Database::select() +// * @param String $table Table name +// * @param String $prefix Fields prefix +// * @return array Array of Title objects linking here +// */ +// public function getLinksFrom($options = [], $table = 'pagelinks', $prefix = 'pl') { +// $id = this.getArticleID(); +// +// // If the page doesn't exist; there can't be any link from this page +// if (!$id) { +// return []; +// } +// +// $db = wfGetDB(DB_REPLICA); +// +// $blNamespace = "{$prefix}_namespace"; +// $blTitle = "{$prefix}_title"; +// +// $res = $db.select( +// [ $table, 'page' ], +// array_merge( +// [ $blNamespace, $blTitle ], +// WikiPage::selectFields() +// ), +// [ "{$prefix}_from" => $id ], +// __METHOD__, +// $options, +// [ 'page' => [ +// 'LEFT JOIN', +// [ "page_namespace=$blNamespace", "page_title=$blTitle" ] +// ] ] +// ); +// +// $retVal = []; +// $linkCache = LinkCache::singleton(); +// foreach ($res as $row) { +// if ($row.page_id) { +// $titleObj = Title::newFromRow($row); +// } else { +// $titleObj = Title::makeTitle($row.$blNamespace, $row.$blTitle); +// $linkCache.addBadLinkObj($titleObj); +// } +// $retVal[] = $titleObj; +// } +// +// return $retVal; +// } +// +// /** +// * Get an array of Title objects used on this Title as a template +// * Also stores the IDs in the link cache. +// * +// * WARNING: do not use this function on arbitrary user-supplied titles! +// * On heavily-used templates it will max out the memory. +// * +// * @param array $options May be FOR UPDATE +// * @return Title[] Array of Title the Title objects used here +// */ +// public function getTemplateLinksFrom($options = []) { +// return this.getLinksFrom($options, 'templatelinks', 'tl'); +// } +// +// /** +// * Get an array of Title objects referring to non-existent articles linked +// * from this page. +// * +// * @todo check if needed (used only in SpecialBrokenRedirects.php, and +// * should use redirect table in this case). +// * @return Title[] Array of Title the Title objects +// */ +// public function getBrokenLinksFrom() { +// if (this.getArticleID() == 0) { +// // All links from article ID 0 are false positives +// return []; +// } +// +// $dbr = wfGetDB(DB_REPLICA); +// $res = $dbr.select( +// [ 'page', 'pagelinks' ], +// [ 'pl_namespace', 'pl_title' ], +// [ +// 'pl_from' => this.getArticleID(), +// 'page_namespace IS NULL' +// ], +// __METHOD__, [], +// [ +// 'page' => [ +// 'LEFT JOIN', +// [ 'pl_namespace=page_namespace', 'pl_title=page_title' ] +// ] +// ] +// ); +// +// $retVal = []; +// foreach ($res as $row) { +// $retVal[] = Title::makeTitle($row.pl_namespace, $row.pl_title); +// } +// return $retVal; +// } +// +// /** +// * Get a list of URLs to purge from the CDN cache when this +// * page changes +// * +// * @return String[] Array of String the URLs +// */ +// public function getCdnUrls() { +// $urls = [ +// this.getInternalURL(), +// this.getInternalURL('action=history') +// ]; +// +// $pageLang = this.getPageLanguage(); +// if ($pageLang.hasVariants()) { +// $variants = $pageLang.getVariants(); +// foreach ($variants as $vCode) { +// $urls[] = this.getInternalURL($vCode); +// } +// } +// +// // If we are looking at a css/js user subpage, purge the action=raw. +// if (this.isJsSubpage()) { +// $urls[] = this.getInternalURL('action=raw&ctype=text/javascript'); +// } elseif (this.isCssSubpage()) { +// $urls[] = this.getInternalURL('action=raw&ctype=text/css'); +// } +// +// Hooks::run('TitleSquidURLs', [ this, &$urls ]); +// return $urls; +// } +// +// /** +// * @deprecated since 1.27 use getCdnUrls() +// */ +// public function getSquidURLs() { +// return this.getCdnUrls(); +// } +// +// /** +// * Purge all applicable CDN URLs +// */ +// public function purgeSquid() { +// DeferredUpdates::addUpdate( +// new CdnCacheUpdate(this.getCdnUrls()), +// DeferredUpdates::PRESEND +// ); +// } +// +// /** +// * Check whether a given move operation would be valid. +// * Returns true if ok, or a getUserPermissionsErrors()-like array otherwise +// * +// * @deprecated since 1.25, use MovePage's methods instead +// * @param Title $nt The new title +// * @param boolean $auth Whether to check user permissions (uses $wgUser) +// * @param String $reason Is the log summary of the move, used for spam checking +// * @return array|boolean True on success, getUserPermissionsErrors()-like array on failure +// */ +// public function isValidMoveOperation(&$nt, $auth = true, $reason = Bry_.Empty) { +// global $wgUser; +// +// if (!($nt instanceof Title)) { +// // Normally we'd add this to $errors, but we'll get +// // lots of syntax errors if $nt is not an Object +// return [ [ 'badtitletext' ] ]; +// } +// +// $mp = new MovePage(this, $nt); +// $errors = $mp.isValidMove().getErrorsArray(); +// if ($auth) { +// $errors = wfMergeErrorArrays( +// $errors, +// $mp.checkPermissions($wgUser, $reason).getErrorsArray() +// ); +// } +// +// return $errors ?: true; +// } +// +// /** +// * Check if the requested move target is a valid file move target +// * @todo move this to MovePage +// * @param Title $nt Target title +// * @return array List of errors +// */ +// protected function validateFileMoveOperation($nt) { +// global $wgUser; +// +// $errors = []; +// +// $destFile = wfLocalFile($nt); +// $destFile.load(File::READ_LATEST); +// if (!$wgUser.isAllowed('reupload-shared') +// && !$destFile.exists() && wfFindFile($nt) +// ) { +// $errors[] = [ 'file-exists-sharedrepo' ]; +// } +// +// return $errors; +// } +// +// /** +// * Move a title to a new location +// * +// * @deprecated since 1.25, use the MovePage class instead +// * @param Title $nt The new title +// * @param boolean $auth Indicates whether $wgUser's permissions +// * should be checked +// * @param String $reason The reason for the move +// * @param boolean $createRedirect Whether to create a redirect from the old title to the new title. +// * Ignored if the user doesn't have the suppressredirect right. +// * @param array $changeTags Applied to the entry in the move log and redirect page revision +// * @return array|boolean True on success, getUserPermissionsErrors()-like array on failure +// */ +// public function moveTo(&$nt, $auth = true, $reason = Bry_.Empty, $createRedirect = true, +// array $changeTags = []) { +// +// global $wgUser; +// $err = this.isValidMoveOperation($nt, $auth, $reason); +// if (is_array($err)) { +// // Auto-block user's IP if the account was "hard" blocked +// $wgUser.spreadAnyEditBlock(); +// return $err; +// } +// // Check suppressredirect permission +// if ($auth && !$wgUser.isAllowed('suppressredirect')) { +// $createRedirect = true; +// } +// +// $mp = new MovePage(this, $nt); +// $status = $mp.move($wgUser, $reason, $createRedirect, $changeTags); +// if ($status.isOK()) { +// return true; +// } else { +// return $status.getErrorsArray(); +// } +// } +// +// /** +// * Move this page's subpages to be subpages of $nt +// * +// * @param Title $nt Move target +// * @param boolean $auth Whether $wgUser's permissions should be checked +// * @param String $reason The reason for the move +// * @param boolean $createRedirect Whether to create redirects from the old subpages to +// * the new ones Ignored if the user doesn't have the 'suppressredirect' right +// * @param array $changeTags Applied to the entry in the move log and redirect page revision +// * @return array Array with old page titles as keys, and strings (new page titles) or +// * getUserPermissionsErrors()-like arrays (errors) as values, or a +// * getUserPermissionsErrors()-like error array with numeric indices if +// * no pages were moved +// */ +// public function moveSubpages($nt, $auth = true, $reason = Bry_.Empty, $createRedirect = true, +// array $changeTags = []) { +// +// global $wgMaximumMovedPages; +// // Check permissions +// if (!this.userCan('move-subpages')) { +// return [ +// [ 'cant-move-subpages' ], +// ]; +// } +// // Do the source and target namespaces support subpages? +// if (!XomwNamespace::hasSubpages(this.getNamespace())) { +// return [ +// [ 'namespace-nosubpages', XomwNamespace::getCanonicalName(this.getNamespace()) ], +// ]; +// } +// if (!XomwNamespace::hasSubpages($nt.getNamespace())) { +// return [ +// [ 'namespace-nosubpages', XomwNamespace::getCanonicalName($nt.getNamespace()) ], +// ]; +// } +// +// $subpages = this.getSubpages($wgMaximumMovedPages + 1); +// $retval = []; +// $count = 0; +// foreach ($subpages as $oldSubpage) { +// $count++; +// if ($count > $wgMaximumMovedPages) { +// $retval[$oldSubpage.getPrefixedText()] = [ +// [ 'movepage-max-pages', $wgMaximumMovedPages ], +// ]; +// break; +// } +// +// // We don't know whether this function was called before +// // or after moving the root page, so check both +// // this and $nt +// if ($oldSubpage.getArticleID() == this.getArticleID() +// || $oldSubpage.getArticleID() == $nt.getArticleID() +// ) { +// // When moving a page to a subpage of itself, +// // don't move it twice +// continue; +// } +// $newPageName = preg_replace( +// '#^' . preg_quote(this.getDBkey(), '#') . '#', +// StringUtils::escapeRegexReplacement($nt.getDBkey()), # bug 21234 +// $oldSubpage.getDBkey()); +// if ($oldSubpage.isTalkPage()) { +// $newNs = $nt.getTalkPage().getNamespace(); +// } else { +// $newNs = $nt.getSubjectPage().getNamespace(); +// } +// // Bug 14385: we need makeTitleSafe because the new page names may +// // be longer than 255 characters. +// $newSubpage = Title::makeTitleSafe($newNs, $newPageName); +// +// $success = $oldSubpage.moveTo($newSubpage, $auth, $reason, $createRedirect, $changeTags); +// if ($success == true) { +// $retval[$oldSubpage.getPrefixedText()] = $newSubpage.getPrefixedText(); +// } else { +// $retval[$oldSubpage.getPrefixedText()] = $success; +// } +// } +// return $retval; +// } +// +// /** +// * Checks if this page is just a one-rev redirect. +// * Adds synchronized, so don't use just for light purposes. +// * +// * @return boolean +// */ +// public function isSingleRevRedirect() { +// global $wgContentHandlerUseDB; +// +// $dbw = wfGetDB(DB_MASTER); +// +// // Is it a redirect? +// $fields = [ 'page_is_redirect', 'page_latest', 'page_id' ]; +// if ($wgContentHandlerUseDB) { +// $fields[] = 'page_content_model'; +// } +// +// $row = $dbw.selectRow('page', +// $fields, +// this.pageCond(), +// __METHOD__, +// [ 'FOR UPDATE' ] +// ); +// // Cache some fields we may want +// this.mArticleID = $row ? intval($row.page_id) : 0; +// this.mRedirect = $row ? (boolean)$row.page_is_redirect : false; +// this.mLatestID = $row ? intval($row.page_latest) : false; +// this.mContentModel = $row && isset($row.page_content_model) +// ? strval($row.page_content_model) +// : false; +// +// if (!this.mRedirect) { +// return false; +// } +// // Does the article have a history? +// $row = $dbw.selectField([ 'page', 'revision' ], +// 'rev_id', +// [ 'page_namespace' => this.getNamespace(), +// 'page_title' => this.getDBkey(), +// 'page_id=rev_page', +// 'page_latest != rev_id' +// ], +// __METHOD__, +// [ 'FOR UPDATE' ] +// ); +// // Return true if there was no history +// return ($row == false); +// } +// +// /** +// * Checks if this can be moved to a given Title +// * - Selects for update, so don't call it unless you mean business +// * +// * @deprecated since 1.25, use MovePage's methods instead +// * @param Title $nt The new title to check +// * @return boolean +// */ +// public function isValidMoveTarget($nt) { +// // Is it an existing file? +// if ($nt.getNamespace() == NS_FILE) { +// $file = wfLocalFile($nt); +// $file.load(File::READ_LATEST); +// if ($file.exists()) { +// wfDebug(__METHOD__ . ": file exists\n"); +// return false; +// } +// } +// // Is it a redirect with no history? +// if (!$nt.isSingleRevRedirect()) { +// wfDebug(__METHOD__ . ": not a one-rev redirect\n"); +// return false; +// } +// // Get the article text +// $rev = Revision::newFromTitle($nt, false, Revision::READ_LATEST); +// if (!is_object($rev)) { +// return false; +// } +// $content = $rev.getContent(); +// // Does the redirect point to the source? +// // Or is it a broken self-redirect, usually caused by namespace collisions? +// $redirTitle = $content ? $content.getRedirectTarget() : null; +// +// if ($redirTitle) { +// if ($redirTitle.getPrefixedDBkey() != this.getPrefixedDBkey() && +// $redirTitle.getPrefixedDBkey() != $nt.getPrefixedDBkey()) { +// wfDebug(__METHOD__ . ": redirect points to other page\n"); +// return false; +// } else { +// return true; +// } +// } else { +// // Fail safe (not a redirect after all. strange.) +// wfDebug(__METHOD__ . ": failsafe: database sais " . $nt.getPrefixedDBkey() . +// " is a redirect, but it doesn't contain a valid redirect.\n"); +// return false; +// } +// } +// +// /** +// * Get categories to which this Title belongs and return an array of +// * categories' names. +// * +// * @return array Array of parents in the form: +// * $parent => $currentarticle +// */ +// public function getParentCategories() { +// global $wgContLang; +// +// $data = []; +// +// $titleKey = this.getArticleID(); +// +// if ($titleKey == 0) { +// return $data; +// } +// +// $dbr = wfGetDB(DB_REPLICA); +// +// $res = $dbr.select( +// 'categorylinks', +// 'cl_to', +// [ 'cl_from' => $titleKey ], +// __METHOD__ +// ); +// +// if ($res.numRows() > 0) { +// foreach ($res as $row) { +// // $data[] = Title::newFromText($wgContLang.getNsText (NS_CATEGORY).':'.$row.cl_to); +// $data[$wgContLang.getNsText(NS_CATEGORY) . ':' . $row.cl_to] = this.getFullText(); +// } +// } +// return $data; +// } +// +// /** +// * Get a tree of parent categories +// * +// * @param array $children Array with the children in the keys, to check for circular refs +// * @return array Tree of parent categories +// */ +// public function getParentCategoryTree($children = []) { +// $stack = []; +// $parents = this.getParentCategories(); +// +// if ($parents) { +// foreach ($parents as $parent => $current) { +// if (array_key_exists($parent, $children)) { +// // Circular reference +// $stack[$parent] = []; +// } else { +// $nt = Title::newFromText($parent); +// if ($nt) { +// $stack[$parent] = $nt.getParentCategoryTree($children + [ $parent => 1 ]); +// } +// } +// } +// } +// +// return $stack; +// } +// +// /** +// * Get an associative array for selecting this title from +// * the "page" table +// * +// * @return array Array suitable for the $where parameter of DB::select() +// */ +// public function pageCond() { +// if (this.mArticleID > 0) { +// // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs +// return [ 'page_id' => this.mArticleID ]; +// } else { +// return [ 'page_namespace' => this.mNamespace, 'page_title' => this.mDbkeyform ]; +// } +// } +// +// /** +// * Get the revision ID of the previous revision +// * +// * @param int $revId Revision ID. Get the revision that was before this one. +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return int|boolean Old revision ID, or false if none exists +// */ +// public function getPreviousRevisionID($revId, $flags = 0) { +// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); +// $revId = $db.selectField('revision', 'rev_id', +// [ +// 'rev_page' => this.getArticleID($flags), +// 'rev_id < ' . intval($revId) +// ], +// __METHOD__, +// [ 'ORDER BY' => 'rev_id DESC' ] +// ); +// +// if ($revId == false) { +// return false; +// } else { +// return intval($revId); +// } +// } +// +// /** +// * Get the revision ID of the next revision +// * +// * @param int $revId Revision ID. Get the revision that was after this one. +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return int|boolean Next revision ID, or false if none exists +// */ +// public function getNextRevisionID($revId, $flags = 0) { +// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); +// $revId = $db.selectField('revision', 'rev_id', +// [ +// 'rev_page' => this.getArticleID($flags), +// 'rev_id > ' . intval($revId) +// ], +// __METHOD__, +// [ 'ORDER BY' => 'rev_id' ] +// ); +// +// if ($revId == false) { +// return false; +// } else { +// return intval($revId); +// } +// } +// +// /** +// * Get the first revision of the page +// * +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return Revision|null If page doesn't exist +// */ +// public function getFirstRevision($flags = 0) { +// $pageId = this.getArticleID($flags); +// if ($pageId) { +// $db = ($flags & self::GAID_FOR_UPDATE) ? wfGetDB(DB_MASTER) : wfGetDB(DB_REPLICA); +// $row = $db.selectRow('revision', Revision::selectFields(), +// [ 'rev_page' => $pageId ], +// __METHOD__, +// [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ] +// ); +// if ($row) { +// return new Revision($row); +// } +// } +// return null; +// } +// +// /** +// * Get the oldest revision timestamp of this page +// * +// * @param int $flags Title::GAID_FOR_UPDATE +// * @return String MW timestamp +// */ +// public function getEarliestRevTime($flags = 0) { +// $rev = this.getFirstRevision($flags); +// return $rev ? $rev.getTimestamp() : null; +// } +// +// /** +// * Check if this is a new page +// * +// * @return boolean +// */ +// public function isNewPage() { +// $dbr = wfGetDB(DB_REPLICA); +// return (boolean)$dbr.selectField('page', 'page_is_new', this.pageCond(), __METHOD__); +// } +// +// /** +// * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit +// * +// * @return boolean +// */ +// public function isBigDeletion() { +// global $wgDeleteRevisionsLimit; +// +// if (!$wgDeleteRevisionsLimit) { +// return false; +// } +// +// if (this.mIsBigDeletion == null) { +// $dbr = wfGetDB(DB_REPLICA); +// +// $revCount = $dbr.selectRowCount( +// 'revision', +// '1', +// [ 'rev_page' => this.getArticleID() ], +// __METHOD__, +// [ 'LIMIT' => $wgDeleteRevisionsLimit + 1 ] +// ); +// +// this.mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit; +// } +// +// return this.mIsBigDeletion; +// } +// +// /** +// * Get the approximate revision count of this page. +// * +// * @return int +// */ +// public function estimateRevisionCount() { +// if (!this.exists()) { +// return 0; +// } +// +// if (this.mEstimateRevisions == null) { +// $dbr = wfGetDB(DB_REPLICA); +// this.mEstimateRevisions = $dbr.estimateRowCount('revision', '*', +// [ 'rev_page' => this.getArticleID() ], __METHOD__); +// } +// +// return this.mEstimateRevisions; +// } +// +// /** +// * Get the number of revisions between the given revision. +// * Used for diffs and other things that really need it. +// * +// * @param int|Revision $old Old revision or rev ID (first before range) +// * @param int|Revision $new New revision or rev ID (first after range) +// * @param int|null $max Limit of Revisions to count, will be incremented to detect truncations +// * @return int Number of revisions between these revisions. +// */ +// public function countRevisionsBetween($old, $new, $max = null) { +// if (!($old instanceof Revision)) { +// $old = Revision::newFromTitle(this, (int)$old); +// } +// if (!($new instanceof Revision)) { +// $new = Revision::newFromTitle(this, (int)$new); +// } +// if (!$old || !$new) { +// return 0; // nothing to compare +// } +// $dbr = wfGetDB(DB_REPLICA); +// $conds = [ +// 'rev_page' => this.getArticleID(), +// 'rev_timestamp > ' . $dbr.addQuotes($dbr.timestamp($old.getTimestamp())), +// 'rev_timestamp < ' . $dbr.addQuotes($dbr.timestamp($new.getTimestamp())) +// ]; +// if ($max != null) { +// return $dbr.selectRowCount('revision', '1', +// $conds, +// __METHOD__, +// [ 'LIMIT' => $max + 1 ] // extra to detect truncation +// ); +// } else { +// return (int)$dbr.selectField('revision', 'count(*)', $conds, __METHOD__); +// } +// } +// +// /** +// * Get the authors between the given revisions or revision IDs. +// * Used for diffs and other things that really need it. +// * +// * @since 1.23 +// * +// * @param int|Revision $old Old revision or rev ID (first before range by default) +// * @param int|Revision $new New revision or rev ID (first after range by default) +// * @param int $limit Maximum number of authors +// * @param String|array $options (Optional): Single option, or an array of options: +// * 'include_old' Include $old in the range; $new is excluded. +// * 'include_new' Include $new in the range; $old is excluded. +// * 'include_both' Include both $old and $new in the range. +// * Unknown option values are ignored. +// * @return array|null Names of revision authors in the range; null if not both revisions exist +// */ +// public function getAuthorsBetween($old, $new, $limit, $options = []) { +// if (!($old instanceof Revision)) { +// $old = Revision::newFromTitle(this, (int)$old); +// } +// if (!($new instanceof Revision)) { +// $new = Revision::newFromTitle(this, (int)$new); +// } +// // XXX: what if Revision objects are passed in, but they don't refer to this title? +// // Add $old.getPage() != $new.getPage() || $old.getPage() != this.getArticleID() +// // in the sanity check below? +// if (!$old || !$new) { +// return null; // nothing to compare +// } +// $authors = []; +// $old_cmp = '>'; +// $new_cmp = '<'; +// $options = (array)$options; +// if (in_array('include_old', $options)) { +// $old_cmp = '>='; +// } +// if (in_array('include_new', $options)) { +// $new_cmp = '<='; +// } +// if (in_array('include_both', $options)) { +// $old_cmp = '>='; +// $new_cmp = '<='; +// } +// // No DB query needed if $old and $new are the same or successive revisions: +// if ($old.getId() == $new.getId()) { +// return ($old_cmp == '>' && $new_cmp == '<') ? +// [] : +// [ $old.getUserText(Revision::RAW) ]; +// } elseif ($old.getId() == $new.getParentId()) { +// if ($old_cmp == '>=' && $new_cmp == '<=') { +// $authors[] = $old.getUserText(Revision::RAW); +// if ($old.getUserText(Revision::RAW) != $new.getUserText(Revision::RAW)) { +// $authors[] = $new.getUserText(Revision::RAW); +// } +// } elseif ($old_cmp == '>=') { +// $authors[] = $old.getUserText(Revision::RAW); +// } elseif ($new_cmp == '<=') { +// $authors[] = $new.getUserText(Revision::RAW); +// } +// return $authors; +// } +// $dbr = wfGetDB(DB_REPLICA); +// $res = $dbr.select('revision', 'DISTINCT rev_user_text', +// [ +// 'rev_page' => this.getArticleID(), +// "rev_timestamp $old_cmp " . $dbr.addQuotes($dbr.timestamp($old.getTimestamp())), +// "rev_timestamp $new_cmp " . $dbr.addQuotes($dbr.timestamp($new.getTimestamp())) +// ], __METHOD__, +// [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated +// ); +// foreach ($res as $row) { +// $authors[] = $row.rev_user_text; +// } +// return $authors; +// } +// +// /** +// * Get the number of authors between the given revisions or revision IDs. +// * Used for diffs and other things that really need it. +// * +// * @param int|Revision $old Old revision or rev ID (first before range by default) +// * @param int|Revision $new New revision or rev ID (first after range by default) +// * @param int $limit Maximum number of authors +// * @param String|array $options (Optional): Single option, or an array of options: +// * 'include_old' Include $old in the range; $new is excluded. +// * 'include_new' Include $new in the range; $old is excluded. +// * 'include_both' Include both $old and $new in the range. +// * Unknown option values are ignored. +// * @return int Number of revision authors in the range; zero if not both revisions exist +// */ +// public function countAuthorsBetween($old, $new, $limit, $options = []) { +// $authors = this.getAuthorsBetween($old, $new, $limit, $options); +// return $authors ? count($authors) : 0; +// } + + /** + * Compare with another title. + * + * @param Title $title + * @return boolean + */ + public boolean equals(XomwTitleOld title) { + // Note: == is necessary for proper matching of number-like titles. + return Bry_.Eq(this.getInterwiki(), title.getInterwiki()) + && this.getNamespace() == title.getNamespace() + && Bry_.Eq(this.getDBkey(), title.getDBkey()); + } + +// /** +// * Check if this title is a subpage of another title +// * +// * @param Title $title +// * @return boolean +// */ +// public function isSubpageOf(Title $title) { +// return this.getInterwiki() == $title.getInterwiki() +// && this.getNamespace() == $title.getNamespace() +// && strpos(this.getDBkey(), $title.getDBkey() . '/') == 0; +// } +// +// /** +// * Check if page exists. For historical reasons, this function simply +// * checks for the existence of the title in the page table, and will +// * thus return false for interwiki links, special pages and the like. +// * If you want to know if a title can be meaningfully viewed, you should +// * probably call the isKnown() method instead. +// * +// * @param int $flags An optional bit field; may be Title::GAID_FOR_UPDATE to check +// * from master/for update +// * @return boolean +// */ +// public function exists($flags = 0) { +// $exists = this.getArticleID($flags) != 0; +// Hooks::run('TitleExists', [ this, &$exists ]); +// return $exists; +// } + + /** + * Should links to this title be shown as potentially viewable (i.e. as + * "bluelinks"), even if there's no record by this title in the page + * table? + * + * This function is semi-deprecated for public use, as well as somewhat + * misleadingly named. You probably just want to call isKnown(), which + * calls this function internally. + * + * (ISSUE: Most of these checks are cheap, but the file existence check + * can potentially be quite expensive. Including it here fixes a lot of + * existing code, but we might want to add an optional parameter to skip + * it and any other expensive checks.) + * + * @return boolean + */ + public boolean isAlwaysKnown() { +// $isKnown = null; +// +// /** +// * Allows overriding default behavior for determining if a page exists. +// * If $isKnown is kept as null, regular checks happen. If it's +// * a boolean, this value is returned by the isKnown method. +// * +// * @since 1.20 +// * +// * @param Title $title +// * @param boolean|null $isKnown +// */ +// Hooks::run('TitleIsAlwaysKnown', [ this, &$isKnown ]); +// +// if (!is_null($isKnown)) { +// return $isKnown; +// } +// +// if (this.isExternal()) { +// return true; // any interwiki link might be viewable, for all we know +// } +// +// switch (this.mNamespace) { +// case NS_MEDIA: +// case NS_FILE: +// // file exists, possibly in a foreign repo +// return (boolean)wfFindFile(this); +// case NS_SPECIAL: +// // valid special page +// return SpecialPageFactory::exists(this.getDBkey()); +// case NS_MAIN: +// // selflink, possibly with fragment +// return this.mDbkeyform == Bry_.Empty; +// case NS_MEDIAWIKI: +// // known system message +// return this.hasSourceText() != false; +// default: +// return false; +// } + return false; + } + + /** + * Does this title refer to a page that can (or might) be meaningfully + * viewed? In particular, this function may be used to determine if + * links to the title should be rendered as "bluelinks" (as opposed to + * "redlinks" to non-existent pages). + * Adding something else to this function will cause inconsistency + * since LinkHolderArray calls isAlwaysKnown() and does its own + * page existence check. + * + * @return boolean + */ + public boolean isKnown() { +// return this.isAlwaysKnown() || this.exists(); + return true; + } + +// /** +// * Does this page have source text? +// * +// * @return boolean +// */ +// public function hasSourceText() { +// if (this.exists()) { +// return true; +// } +// +// if (this.mNamespace == NS_MEDIAWIKI) { +// // If the page doesn't exist but is a known system message, default +// // message content will be displayed, same for language subpages- +// // Use always content language to avoid loading hundreds of languages +// // to get the link color. +// global $wgContLang; +// list($name,) = MessageCache::singleton().figureMessage( +// $wgContLang.lcfirst(this.getText()) +// ); +// $message = wfMessage($name).inLanguage($wgContLang).useDatabase(false); +// return $message.exists(); +// } +// +// return false; +// } +// +// /** +// * Get the default message text or false if the message doesn't exist +// * +// * @return String|boolean +// */ +// public function getDefaultMessageText() { +// global $wgContLang; +// +// if (this.getNamespace() != NS_MEDIAWIKI) { // Just in case +// return false; +// } +// +// list($name, $lang) = MessageCache::singleton().figureMessage( +// $wgContLang.lcfirst(this.getText()) +// ); +// $message = wfMessage($name).inLanguage($lang).useDatabase(false); +// +// if ($message.exists()) { +// return $message.plain(); +// } else { +// return false; +// } +// } +// +// /** +// * Updates page_touched for this page; called from LinksUpdate.php +// * +// * @param String $purgeTime [optional] TS_MW timestamp +// * @return boolean True if the update succeeded +// */ +// public function invalidateCache($purgeTime = null) { +// if (wfReadOnly()) { +// return false; +// } elseif (this.mArticleID == 0) { +// return true; // avoid gap locking if we know it's not there +// } +// +// $dbw = wfGetDB(DB_MASTER); +// $dbw.onTransactionPreCommitOrIdle(function () { +// ResourceLoaderWikiModule::invalidateModuleCache(this, null, null, wfWikiID()); +// }); +// +// $conds = this.pageCond(); +// DeferredUpdates::addUpdate( +// new AutoCommitUpdate( +// $dbw, +// __METHOD__, +// function (IDatabase $dbw, $fname) use ($conds, $purgeTime) { +// $dbTimestamp = $dbw.timestamp($purgeTime ?: time()); +// $dbw.update( +// 'page', +// [ 'page_touched' => $dbTimestamp ], +// $conds + [ 'page_touched < ' . $dbw.addQuotes($dbTimestamp) ], +// $fname +// ); +// MediaWikiServices::getInstance().getLinkCache().invalidateTitle(this); +// } +// ), +// DeferredUpdates::PRESEND +// ); +// +// return true; +// } +// +// /** +// * Update page_touched timestamps and send CDN purge messages for +// * pages linking to this title. May be sent to the job queue depending +// * on the number of links. Typically called on create and delete. +// */ +// public function touchLinks() { +// DeferredUpdates::addUpdate(new HTMLCacheUpdate(this, 'pagelinks')); +// if (this.getNamespace() == NS_CATEGORY) { +// DeferredUpdates::addUpdate(new HTMLCacheUpdate(this, 'categorylinks')); +// } +// } +// +// /** +// * Get the last touched timestamp +// * +// * @param IDatabase $db Optional db +// * @return String|false Last-touched timestamp +// */ +// public function getTouched($db = null) { +// if ($db == null) { +// $db = wfGetDB(DB_REPLICA); +// } +// $touched = $db.selectField('page', 'page_touched', this.pageCond(), __METHOD__); +// return $touched; +// } +// +// /** +// * Get the timestamp when this page was updated since the user last saw it. +// * +// * @param User $user +// * @return String|null +// */ +// public function getNotificationTimestamp($user = null) { +// global $wgUser; +// +// // Assume current user if none given +// if (!$user) { +// $user = $wgUser; +// } +// // Check cache first +// $uid = $user.getId(); +// if (!$uid) { +// return false; +// } +// // avoid isset here, as it'll return false for null entries +// if (array_key_exists($uid, this.mNotificationTimestamp)) { +// return this.mNotificationTimestamp[$uid]; +// } +// // Don't cache too much! +// if (count(this.mNotificationTimestamp) >= self::CACHE_MAX) { +// this.mNotificationTimestamp = []; +// } +// +// $store = MediaWikiServices::getInstance().getWatchedItemStore(); +// $watchedItem = $store.getWatchedItem($user, this); +// if ($watchedItem) { +// this.mNotificationTimestamp[$uid] = $watchedItem.getNotificationTimestamp(); +// } else { +// this.mNotificationTimestamp[$uid] = false; +// } +// +// return this.mNotificationTimestamp[$uid]; +// } +// +// /** +// * Generate strings used for xml 'id' names in monobook tabs +// * +// * @param String $prepend Defaults to 'nstab-' +// * @return String XML 'id' name +// */ +// public function getNamespaceKey($prepend = 'nstab-') { +// global $wgContLang; +// // Gets the subject namespace if this title +// $namespace = XomwNamespace::getSubject(this.getNamespace()); +// // Checks if canonical namespace name exists for namespace +// if (XomwNamespace::exists(this.getNamespace())) { +// // Uses canonical namespace name +// $namespaceKey = XomwNamespace::getCanonicalName($namespace); +// } else { +// // Uses text of namespace +// $namespaceKey = this.getSubjectNsText(); +// } +// // Makes namespace key lowercase +// $namespaceKey = $wgContLang.lc($namespaceKey); +// // Uses main +// if ($namespaceKey == Bry_.Empty) { +// $namespaceKey = 'main'; +// } +// // Changes file to image for backwards compatibility +// if ($namespaceKey == 'file') { +// $namespaceKey = 'image'; +// } +// return $prepend . $namespaceKey; +// } +// +// /** +// * Get all extant redirects to this Title +// * +// * @param int|null $ns Single namespace to consider; null to consider all namespaces +// * @return Title[] Array of Title redirects to this title +// */ +// public function getRedirectsHere($ns = null) { +// $redirs = []; +// +// $dbr = wfGetDB(DB_REPLICA); +// $where = [ +// 'rd_namespace' => this.getNamespace(), +// 'rd_title' => this.getDBkey(), +// 'rd_from = page_id' +// ]; +// if (this.isExternal()) { +// $where['rd_interwiki'] = this.getInterwiki(); +// } else { +// $where[] = 'rd_interwiki = ' . $dbr.addQuotes(Bry_.Empty) . ' OR rd_interwiki IS NULL'; +// } +// if (!is_null($ns)) { +// $where['page_namespace'] = $ns; +// } +// +// $res = $dbr.select( +// [ 'redirect', 'page' ], +// [ 'page_namespace', 'page_title' ], +// $where, +// __METHOD__ +// ); +// +// foreach ($res as $row) { +// $redirs[] = self::newFromRow($row); +// } +// return $redirs; +// } +// +// /** +// * Check if this Title is a valid redirect target +// * +// * @return boolean +// */ +// public function isValidRedirectTarget() { +// global $wgInvalidRedirectTargets; +// +// if (this.isSpecialPage()) { +// // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here +// if (this.isSpecial('Userlogout')) { +// return false; +// } +// +// foreach ($wgInvalidRedirectTargets as $target) { +// if (this.isSpecial($target)) { +// return false; +// } +// } +// } +// +// return true; +// } +// +// /** +// * Get a backlink cache Object +// * +// * @return BacklinkCache +// */ +// public function getBacklinkCache() { +// return BacklinkCache::get(this); +// } +// +// /** +// * Whether the magic words __INDEX__ and __NOINDEX__ function for this page. +// * +// * @return boolean +// */ +// public function canUseNoindex() { +// global $wgExemptFromUserRobotsControl; +// +// $bannedNamespaces = is_null($wgExemptFromUserRobotsControl) +// ? XomwNamespace::getContentNamespaces() +// : $wgExemptFromUserRobotsControl; +// +// return !in_array(this.mNamespace, $bannedNamespaces); +// } +// +// /** +// * Returns the raw sort key to be used for categories, with the specified +// * prefix. This will be fed to Collation::getSortKey() to get a +// * binary sortkey that can be used for actual sorting. +// * +// * @param String $prefix The prefix to be used, specified using +// * {{defaultsort:}} or like [[Category:Foo|prefix]]. Empty for no +// * prefix. +// * @return String +// */ +// public function getCategorySortkey($prefix = Bry_.Empty) { +// $unprefixed = this.getText(); +// +// // Anything that uses this hook should only depend +// // on the Title Object passed in, and should probably +// // tell the users to run updateCollations.php --force +// // in order to re-sort existing category relations. +// Hooks::run('GetDefaultSortkey', [ this, &$unprefixed ]); +// if ($prefix != Bry_.Empty) { +// // Separate with a line feed, so the unprefixed part is only used as +// // a tiebreaker when two pages have the exact same prefix. +// // In UCA, tab is the only character that can sort above LF +// // so we strip both of them from the original prefix. +// $prefix = strtr($prefix, "\n\t", ' '); +// return "$prefix\n$unprefixed"; +// } +// return $unprefixed; +// } +// +// /** +// * Returns the page language code saved in the database, if $wgPageLanguageUseDB is set +// * to true in LocalSettings.php, otherwise returns false. If there is no language saved in +// * the db, it will return NULL. +// * +// * @return String|null|boolean +// */ +// private function getDbPageLanguageCode() { +// global $wgPageLanguageUseDB; +// +// // check, if the page language could be saved in the database, and if so and +// // the value is not requested already, lookup the page language using LinkCache +// if ($wgPageLanguageUseDB && this.mDbPageLanguage == false) { +// $linkCache = LinkCache::singleton(); +// $linkCache.addLinkObj(this); +// this.mDbPageLanguage = $linkCache.getGoodLinkFieldObj(this, 'lang'); +// } +// +// return this.mDbPageLanguage; +// } +// +// /** +// * Get the language in which the content of this page is written in +// * wikitext. Defaults to $wgContLang, but in certain cases it can be +// * e.g. $wgLang (such as special pages, which are in the user language). +// * +// * @since 1.18 +// * @return Language +// */ +// public function getPageLanguage() { +// global $wgLang, $wgLanguageCode; +// if (this.isSpecialPage()) { +// // special pages are in the user language +// return $wgLang; +// } +// +// // Checking if DB language is set +// $dbPageLanguage = this.getDbPageLanguageCode(); +// if ($dbPageLanguage) { +// return wfGetLangObj($dbPageLanguage); +// } +// +// if (!this.mPageLanguage || this.mPageLanguage[1] != $wgLanguageCode) { +// // Note that this may depend on user settings, so the cache should +// // be only per-request. +// // NOTE: ContentHandler::getPageLanguage() may need to load the +// // content to determine the page language! +// // Checking $wgLanguageCode hasn't changed for the benefit of unit +// // tests. +// $contentHandler = ContentHandler::getForTitle(this); +// $langObj = $contentHandler.getPageLanguage(this); +// this.mPageLanguage = [ $langObj.getCode(), $wgLanguageCode ]; +// } else { +// $langObj = wfGetLangObj(this.mPageLanguage[0]); +// } +// +// return $langObj; +// } +// +// /** +// * Get the language in which the content of this page is written when +// * viewed by user. Defaults to $wgContLang, but in certain cases it can be +// * e.g. $wgLang (such as special pages, which are in the user language). +// * +// * @since 1.20 +// * @return Language +// */ +// public function getPageViewLanguage() { +// global $wgLang; +// +// if (this.isSpecialPage()) { +// // If the user chooses a variant, the content is actually +// // in a language whose code is the variant code. +// $variant = $wgLang.getPreferredVariant(); +// if ($wgLang.getCode() != $variant) { +// return Language::factory($variant); +// } +// +// return $wgLang; +// } +// +// // Checking if DB language is set +// $dbPageLanguage = this.getDbPageLanguageCode(); +// if ($dbPageLanguage) { +// $pageLang = wfGetLangObj($dbPageLanguage); +// $variant = $pageLang.getPreferredVariant(); +// if ($pageLang.getCode() != $variant) { +// $pageLang = Language::factory($variant); +// } +// +// return $pageLang; +// } +// +// // @note Can't be cached persistently, depends on user settings. +// // @note ContentHandler::getPageViewLanguage() may need to load the +// // content to determine the page language! +// $contentHandler = ContentHandler::getForTitle(this); +// $pageLang = $contentHandler.getPageViewLanguage(this); +// return $pageLang; +// } +// +// /** +// * Get a list of rendered edit notices for this page. +// * +// * Array is keyed by the original message key, and values are rendered using parseAsBlock, so +// * they will already be wrapped in paragraphs. +// * +// * @since 1.21 +// * @param int $oldid Revision ID that's being edited +// * @return array +// */ +// public function getEditNotices($oldid = 0) { +// $notices = []; +// +// // Optional notice for the entire namespace +// $editnotice_ns = 'editnotice-' . this.getNamespace(); +// $msg = wfMessage($editnotice_ns); +// if ($msg.exists()) { +// $html = $msg.parseAsBlock(); +// // Edit notices may have complex logic, but output nothing (T91715) +// if (trim($html) != Bry_.Empty) { +// $notices[$editnotice_ns] = Html::rawElement( +// 'div', +// [ 'class' => [ +// 'mw-editnotice', +// 'mw-editnotice-namespace', +// Sanitizer::escapeClass("mw-$editnotice_ns") +// ] ], +// $html +// ); +// } +// } +// +// if (XomwNamespace::hasSubpages(this.getNamespace())) { +// // Optional notice for page itself and any parent page +// $parts = explode('/', this.getDBkey()); +// $editnotice_base = $editnotice_ns; +// while (count($parts) > 0) { +// $editnotice_base .= '-' . array_shift($parts); +// $msg = wfMessage($editnotice_base); +// if ($msg.exists()) { +// $html = $msg.parseAsBlock(); +// if (trim($html) != Bry_.Empty) { +// $notices[$editnotice_base] = Html::rawElement( +// 'div', +// [ 'class' => [ +// 'mw-editnotice', +// 'mw-editnotice-super', +// Sanitizer::escapeClass("mw-$editnotice_base") +// ] ], +// $html +// ); +// } +// } +// } +// } else { +// // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys +// $editnoticeText = $editnotice_ns . '-' . strtr(this.getDBkey(), '/', '-'); +// $msg = wfMessage($editnoticeText); +// if ($msg.exists()) { +// $html = $msg.parseAsBlock(); +// if (trim($html) != Bry_.Empty) { +// $notices[$editnoticeText] = Html::rawElement( +// 'div', +// [ 'class' => [ +// 'mw-editnotice', +// 'mw-editnotice-page', +// Sanitizer::escapeClass("mw-$editnoticeText") +// ] ], +// $html +// ); +// } +// } +// } +// +// Hooks::run('TitleGetEditNotices', [ this, $oldid, &$notices ]); +// return $notices; +// } +// +// /** +// * @return array +// */ +// public function __sleep() { +// return [ +// 'mNamespace', +// 'mDbkeyform', +// 'mFragment', +// 'mInterwiki', +// 'mLocalInterwiki', +// 'mUserCaseDBKey', +// 'mDefaultNamespace', +// ]; +// } + + // public function __wakeup() { + // this.mArticleID = (this.mNamespace >= 0) ? -1 : 0; + // this.mUrlform = wfUrlencode(this.mDbkeyform); + // this.mTextform = strtr(this.mDbkeyform, '_', ' '); + // } + private static final byte[] Bry__wgArticlePath__wiki = Bry_.new_a7("/wiki/"); + + // REF.MW: DefaultSettings.php + // Allowed title characters -- regex character class + // Don't change this unless you know what you're doing + // + // Problematic punctuation: + // - []{}|# Are needed for link syntax, never enable these + // - <> Causes problems with HTML escaping, don't use + // - % Enabled by default, minor problems with path to query rewrite rules, see below + // - + Enabled by default, but doesn't work with path to query rewrite rules, + // corrupted by apache + // - ? Enabled by default, but doesn't work with path to PATH_INFO rewrites + // + // All three of these punctuation problems can be avoided by using an alias, + // instead of a rewrite rule of either variety. + // + // The problem with % is that when using a path to query rewrite rule, URLs are + // double-unescaped: once by Apache's path conversion code, and again by PHP. So + // %253F, for example, becomes "?". Our code does not double-escape to compensate + // for this, indeed double escaping would break if the double-escaped title was + // passed in the query String rather than the path. This is a minor security issue + // because articles can be created such that they are hard to view or edit. + // + // In some rare cases you may wish to remove + for compatibility with old links. + // + // Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but + // this breaks interlanguage links + // $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+"; + // + // REGEX: + // without-backslash escaping --> \s%!"$&'()*,-./0-9:;=?@A-Z\^_`a-z~x80-xFF+ + // rearranged + // letters --> 0-9A-Za-z + // unicode-chars --> x80-xFF + // symbols --> \s%!"$&'()*,-./:;=?@\^_`~+" + // deliberately ignores + // control chars: 00-31,127 + // []{}|#<> + public static int Find_fwd_while_title(byte[] src, int src_bgn, int src_end, boolean[] valid) { + int cur = src_bgn; + while (true) { + if (cur == src_end) break; + byte b = src[cur]; + int b_len = gplx.core.intls.Utf8_.Len_of_char_by_1st_byte(b); + if (b_len == 1) { // ASCII + if (valid[b & 0xFF]) // valid; EX: "a0A B&$"; PATCH.JAVA:need to convert to unsigned byte + cur++; + else // invalid; EX: "<title>" + break; + } + else { // Multi-byte UTF8; NOTE: all sequences are valid + cur += b_len; + } + } + return cur; + } + private static boolean[] title_chars_valid; + public static boolean[] Title_chars_valid() { + if (title_chars_valid == null) { + title_chars_valid = new boolean[128]; + // add num and alpha + for (int i = Byte_ascii.Num_0; i <= Byte_ascii.Num_9; i++) + title_chars_valid[i] = true; + for (int i = Byte_ascii.Ltr_A; i <= Byte_ascii.Ltr_Z; i++) + title_chars_valid[i] = true; + for (int i = Byte_ascii.Ltr_a; i <= Byte_ascii.Ltr_z; i++) + title_chars_valid[i] = true; + + // add symbols: \s%!"$&'()*,-./:;=?@\^_`~+" + byte[] symbols = new byte[] + { Byte_ascii.Space + , Byte_ascii.Percent + , Byte_ascii.Bang + , Byte_ascii.Quote + , Byte_ascii.Amp + , Byte_ascii.Apos + , Byte_ascii.Paren_bgn + , Byte_ascii.Paren_end + , Byte_ascii.Star + , Byte_ascii.Comma + , Byte_ascii.Dash + , Byte_ascii.Dot + , Byte_ascii.Slash + , Byte_ascii.Colon + , Byte_ascii.Semic + , Byte_ascii.Eq + , Byte_ascii.Question + , Byte_ascii.At + , Byte_ascii.Backslash + , Byte_ascii.Pow + , Byte_ascii.Underline + , Byte_ascii.Tick + , Byte_ascii.Tilde + , Byte_ascii.Plus + }; + int symbols_len = symbols.length; + for (int i = 0; i < symbols_len; i++) + title_chars_valid[symbols[i]] = true; + } + return title_chars_valid; + } +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle_tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle_tst.java index aaee3fbc2..1646ce3b9 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle_tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/XomwTitle_tst.java @@ -13,16 +13,16 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; -import org.junit.*; import gplx.core.tests.*; -public class XomwTitle_tst { - private final XomwTitle_fxt fxt = new XomwTitle_fxt(); - @Test public void Alphanum() {fxt.Test__find_fwd_while_title("0aB" , 3);} - @Test public void Angle() {fxt.Test__find_fwd_while_title("0a<" , 2);} -} -class XomwTitle_fxt { - public void Test__find_fwd_while_title(String src_str, int expd) { - byte[] src_bry = Bry_.new_u8(src_str); - Gftest.Eq__int(expd, XomwTitle.Find_fwd_while_title(src_bry, 0, src_bry.length, XomwTitle.Title_chars_valid())); - } -} +package gplx.xowa.mediawiki.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; +import org.junit.*; import gplx.core.tests.*; +public class XomwTitle_tst { + private final XomwTitleOld_fxt fxt = new XomwTitleOld_fxt(); + @Test public void Alphanum() {fxt.Test__find_fwd_while_title("0aB" , 3);} + @Test public void Angle() {fxt.Test__find_fwd_while_title("0a<" , 2);} +} +class XomwTitleOld_fxt { + public void Test__find_fwd_while_title(String src_str, int expd) { + byte[] src_bry = Bry_.new_u8(src_str); + Gftest.Eq__int(expd, XomwTitleOld.Find_fwd_while_title(src_bry, 0, src_bry.length, XomwTitleOld.Title_chars_valid())); + } +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java b/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java index ce1863340..2eeeb6cc7 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwAbstractContent.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.exception.*; import gplx.xowa.mediawiki.includes.parsers.*; @@ -243,8 +243,8 @@ public abstract class XomwAbstractContent implements XomwContent { * * @see Content::getRedirectChain */ - public XomwTitle[] getRedirectChain() { -// XomwTitle title = this.getRedirectTarget(); + public XomwTitleOld[] getRedirectChain() { +// XomwTitleOld title = this.getRedirectTarget(); // if (title == null) { // return null; // } @@ -253,7 +253,7 @@ public abstract class XomwAbstractContent implements XomwContent { // // List_adp titles = List_adp_.New_by_many(title); // while (--recurse > 0) { -// XomwTitle newtitle = null; +// XomwTitleOld newtitle = null; // if (title.isRedirect()) { // $page = WikiPage::factory(title); // $newtitle = $page.getRedirectTarget(); @@ -261,7 +261,7 @@ public abstract class XomwAbstractContent implements XomwContent { // break; // } // // Redirects to some special pages are not permitted -// if (Type_.Eq_by_obj(newtitle, typeof(XomwTitle)) && newtitle.isValidRedirectTarget()) { +// if (Type_.Eq_by_obj(newtitle, typeof(XomwTitleOld)) && newtitle.isValidRedirectTarget()) { // // The new title passes the checks, so make that our current // // title so that further recursion can be checked // title = newtitle; @@ -271,7 +271,7 @@ public abstract class XomwAbstractContent implements XomwContent { // } // } // -// return (XomwTitle[])titles.To_ary_and_clear(typeof(XomwTitle)); +// return (XomwTitleOld[])titles.To_ary_and_clear(typeof(XomwTitleOld)); throw Err_.new_unimplemented(); } @@ -417,7 +417,7 @@ public abstract class XomwAbstractContent implements XomwContent { // } /** - * This default implementation always returns false. Subclasses @Override may + * This default implementation always returns false. Subclasses @Override may * this to supply matching logic. * * @since 1.21 @@ -542,29 +542,29 @@ public abstract class XomwAbstractContent implements XomwContent { public abstract boolean isCountable(boolean hasLinks); - public abstract XomwParserOutput getParserOutput(XomwTitle title, int revId, + public abstract XomwParserOutput getParserOutput(XomwTitleOld title, int revId, XomwParserOptions options, boolean generateHtml); - public abstract Object getSecondaryDataUpdates(XomwTitle title, XomwContent old, + public abstract Object getSecondaryDataUpdates(XomwTitleOld title, XomwContent old, boolean recursive, XomwParserOutput parserOutput); - public abstract XomwTitle getRedirectTarget(); + public abstract XomwTitleOld getRedirectTarget(); - public abstract XomwTitle getUltimateRedirectTarget(); + public abstract XomwTitleOld getUltimateRedirectTarget(); public abstract boolean isRedirect(); - public abstract XomwContent updateRedirect(XomwTitle target); + public abstract XomwContent updateRedirect(XomwTitleOld target); public abstract XomwContent getSection(String sectionId); public abstract byte[] replaceSection(String sectionId, XomwContent with, String sectionTitle); - public abstract XomwContent preSaveTransform(XomwTitle title, Object user, XomwParserOptions parserOptions); + public abstract XomwContent preSaveTransform(XomwTitleOld title, Object user, XomwParserOptions parserOptions); public abstract XomwContent addSectionHeader(byte[] header); - public abstract XomwContent preloadTransform(XomwTitle title, XomwParserOptions parserOptions, Object[] ary); + public abstract XomwContent preloadTransform(XomwTitleOld title, XomwParserOptions parserOptions, Object[] ary); public abstract Object prepareSave(Object page, int flags, int parentRevId, Object user); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContent.java b/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContent.java index 86b5e257b..57c73c0a1 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContent.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContent.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; // MW.SRC:1.33.1 @@ -269,7 +269,7 @@ public interface XomwContent { * @return ParserOutput */ // generateHtml = true - XomwParserOutput getParserOutput(XomwTitle title, int revId, + XomwParserOutput getParserOutput(XomwTitleOld title, int revId, XomwParserOptions options, boolean generateHtml); // TODO: make RenderOutput and RenderOptions super classes @@ -307,7 +307,7 @@ public interface XomwContent { * @since 1.21 */ // DFLT: recursive = true - Object getSecondaryDataUpdates(XomwTitle title, XomwContent old, + Object getSecondaryDataUpdates(XomwTitleOld title, XomwContent old, boolean recursive, XomwParserOutput parserOutput); /** @@ -320,7 +320,7 @@ public interface XomwContent { * * @return Title[]|null List of Titles, with the destination last. */ - XomwTitle[] getRedirectChain(); + XomwTitleOld[] getRedirectChain(); /** * Construct the redirect destination from this content and return a Title, @@ -332,7 +332,7 @@ public interface XomwContent { * * @return Title|null The corresponding Title. */ - XomwTitle getRedirectTarget(); + XomwTitleOld getRedirectTarget(); /** * Construct the redirect destination from this content and return the @@ -349,7 +349,7 @@ public interface XomwContent { * * @return Title|null */ - XomwTitle getUltimateRedirectTarget(); + XomwTitleOld getUltimateRedirectTarget(); /** * Returns whether this Content represents a redirect. @@ -372,7 +372,7 @@ public interface XomwContent { * @return Content A new Content Object with the updated redirect (or $this * if this Content Object isn't a redirect) */ - XomwContent updateRedirect(XomwTitle target); + XomwContent updateRedirect(XomwTitleOld target); /** * Returns the section with the given ID. @@ -416,7 +416,7 @@ public interface XomwContent { * * @return Content */ - XomwContent preSaveTransform(XomwTitle title, Object user, XomwParserOptions parserOptions ); + XomwContent preSaveTransform(XomwTitleOld title, Object user, XomwParserOptions parserOptions ); /** * Returns a new WikitextContent Object with the given section heading @@ -443,7 +443,7 @@ public interface XomwContent { * * @return Content */ - XomwContent preloadTransform(XomwTitle title, XomwParserOptions parserOptions, Object[] ary); + XomwContent preloadTransform(XomwTitleOld title, XomwParserOptions parserOptions, Object[] ary); /** * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContentHandler.java b/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContentHandler.java index 9376ff831..703b45a31 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContentHandler.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/content/XomwContentHandler.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; /** * A content handler knows how do deal with a specific type of content on a wiki @@ -224,7 +224,7 @@ public abstract class XomwContentHandler { * * @return ContentHandler */ -// public static XomwContentHandler getForTitle(XomwTitle title) { +// public static XomwContentHandler getForTitle(XomwTitleOld title) { // int modelId = title.getContentModel(); // // return ContentHandler.getForModelID(modelId); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/XomwFileRepo.java b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/XomwFileRepo.java index 2218a7e4c..40d2b965f 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/XomwFileRepo.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/XomwFileRepo.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.filerepo; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.file.*; /* TODO.XO: @@ -617,7 +617,7 @@ public class XomwFileRepo { * @param Title title * @return String */ - public byte[] getNameFromTitle(XomwTitle title) { + public byte[] getNameFromTitle(XomwTitleOld title) { // global wgContLang; // if (this.initialCapital != XomwNamespace::isCapitalized(NS_FILE)) { // name = title.getUserCaseDBKey(); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFile.java b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFile.java index 714055446..7de6c9865 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFile.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFile.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; import gplx.xowa.mediawiki.includes.media.*; import gplx.xowa.mediawiki.includes.parsers.*; import gplx.xowa.mediawiki.includes.parsers.lnkis.*; @@ -69,7 +69,7 @@ public class XomwFile { public XomwFileRepo repo; /** @var Title|String|boolean */ - private XomwTitle title; + private XomwTitleOld title; // /** @var String Text of last error */ // protected lastError; @@ -265,7 +265,7 @@ public class XomwFile { // return strcmp(a.getName(), b.getName()); // } - public XomwFile(XomwEnv env, XomwTitle title, XomwFileRepo repo) { + public XomwFile(XomwEnv env, XomwTitleOld title, XomwFileRepo repo) { this.env = env; this.title = title; // change title.getDBKey to normalizeTitle @@ -306,7 +306,7 @@ public class XomwFile { * * @return Title */ - public XomwTitle getTitle() { + public XomwTitleOld getTitle() { return this.title; } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinder.java b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinder.java index b9a5ea8ca..2350348fe 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinder.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinder.java @@ -13,7 +13,7 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; -public interface XomwFileFinder { - XomwFile Find_file(XomwTitle ttl); -} +package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; +public interface XomwFileFinder { + XomwFile Find_file(XomwTitleOld ttl); +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderMock.java b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderMock.java index f4533dd37..6d22d6674 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderMock.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderMock.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; import gplx.xowa.mediawiki.includes.parsers.*; public class XomwFileFinderMock implements XomwFileFinder { @@ -20,12 +20,12 @@ public class XomwFileFinderMock implements XomwFileFinder { public XomwFileFinderMock(XomwEnv env) {this.env = env;} private final Hash_adp_bry hash = Hash_adp_bry.cs(); public void Clear() {hash.Clear();} - public XomwFile Find_file(XomwTitle ttl) { + public XomwFile Find_file(XomwTitleOld ttl) { return (XomwFile)hash.Get_by(ttl.getPrefixedDBkey()); } public void Add(String title, XomwFileRepo repo, int w, int h, byte[] mime) { byte[] title_bry = Bry_.new_u8(title); - XomwLocalFile file = new XomwLocalFile(env, XomwTitle.newFromText(env, title_bry), repo, w, h, mime); + XomwLocalFile file = new XomwLocalFile(env, XomwTitleOld.newFromText(env, title_bry), repo, w, h, mime); hash.Add_if_dupe_use_nth(title_bry, file); } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderNoop.java b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderNoop.java index 9aaac85bd..7e62292e2 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderNoop.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwFileFinderNoop.java @@ -13,7 +13,7 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; -public class XomwFileFinderNoop implements XomwFileFinder { - public XomwFile Find_file(XomwTitle ttl) {return null;} -} +package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; +public class XomwFileFinderNoop implements XomwFileFinder { + public XomwFile Find_file(XomwTitleOld ttl) {return null;} +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwLocalFile.java b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwLocalFile.java index 979acc105..5269befae 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwLocalFile.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/filerepo/file/XomwLocalFile.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.filerepo.file; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.filerepo.*; import gplx.xowa.mediawiki.includes.parsers.*; public class XomwLocalFile extends XomwFile {// static final VERSION = 10; // cache version @@ -186,7 +186,7 @@ public class XomwLocalFile extends XomwFile {// static final VERSION = 10; // c // ]; // } - public XomwLocalFile(XomwEnv env, XomwTitle title, XomwFileRepo repo, int w, int h, byte[] mime) {super(env, title, repo); + public XomwLocalFile(XomwEnv env, XomwTitleOld title, XomwFileRepo repo, int w, int h, byte[] mime) {super(env, title, repo); this.width = w; this.height = h; this.mime = mime; diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/libs/rdbms/database/XomwIDatabase.java b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/rdbms/database/XomwIDatabase.java new file mode 100644 index 000000000..77d3ece6b --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/libs/rdbms/database/XomwIDatabase.java @@ -0,0 +1,2160 @@ +package gplx.xowa.mediawiki.includes.libs.rdbms.database; + +// MW.SRC:1.33.1 +/** + * @defgroup Database Database + * This group deals with database interface functions + * and query specifics/optimisations. + */ +/** + * Basic database interface for live and lazy-loaded relation database handles + * + * @note IDatabase and DBConnRef should be updated to reflect any changes + * @ingroup Database + */ +public interface XomwIDatabase { + /** @var int Callback triggered immediately due to no active transaction */ + public static final int TRIGGER_IDLE = 1; + /** @var int Callback triggered by COMMIT */ + public static final int TRIGGER_COMMIT = 2; + /** @var int Callback triggered by ROLLBACK */ + public static final int TRIGGER_ROLLBACK = 3; + + /** @var string Transaction is requested by regular caller outside of the DB layer */ + public static final String TRANSACTION_EXPLICIT = ""; + /** @var string Transaction is requested internally via DBO_TRX/startAtomic() */ + public static final String TRANSACTION_INTERNAL = "implicit"; + + /** @var string Atomic section is not cancelable */ + public static final String ATOMIC_NOT_CANCELABLE = ""; + /** @var string Atomic section is cancelable */ + public static final String ATOMIC_CANCELABLE = "cancelable"; + + /** @var string Commit/rollback is from outside the IDatabase handle and connection manager */ + public static final String FLUSHING_ONE = ""; + /** @var string Commit/rollback is from the connection manager for the IDatabase handle */ + public static final String FLUSHING_ALL_PEERS = "flush"; + /** @var string Commit/rollback is from the IDatabase handle internally */ + public static final String FLUSHING_INTERNAL = "flush-internal"; + + /** @var string Do not remember the prior flags */ + public static final String REMEMBER_NOTHING = ""; + /** @var string Remember the prior flags */ + public static final String REMEMBER_PRIOR = "remember"; + /** @var string Restore to the prior flag state */ + public static final String RESTORE_PRIOR = "prior"; + /** @var string Restore to the initial flag state */ + public static final String RESTORE_INITIAL = "initial"; + + /** @var string Estimate total time (RTT, scanning, waiting on locks, applying) */ + public static final String ESTIMATE_TOTAL = "total"; + /** @var string Estimate time to apply (scanning, applying) */ + public static final String ESTIMATE_DB_APPLY = "apply"; + + /** @var int Combine list with comma delimeters */ + public static final int LIST_COMMA = 0; + /** @var int Combine list with AND clauses */ + public static final int LIST_AND = 1; + /** @var int Convert map into a SET clause */ + public static final int LIST_SET = 2; + /** @var int Treat as field name and do not apply value escaping */ + public static final int LIST_NAMES = 3; + /** @var int Combine list with OR clauses */ + public static final int LIST_OR = 4; + + /** @var int Enable debug logging */ + public static final int DBO_DEBUG = 1; + /** @var int Disable query buffering (only one result set can be iterated at a time) */ + public static final int DBO_NOBUFFER = 2; + /** @var int Ignore query errors (internal use only!) */ + public static final int DBO_IGNORE = 4; + /** @var int Automatically start a transaction before running a query if none is active */ + public static final int DBO_TRX = 8; + /** @var int Use DBO_TRX in non-CLI mode */ + public static final int DBO_DEFAULT = 16; + /** @var int Use DB persistent connections if possible */ + public static final int DBO_PERSISTENT = 32; + /** @var int DBA session mode; mostly for Oracle */ + public static final int DBO_SYSDBA = 64; + /** @var int Schema file mode; mostly for Oracle */ + public static final int DBO_DDLMODE = 128; + /** @var int Enable SSL/TLS in connection protocol */ + public static final int DBO_SSL = 256; + /** @var int Enable compression in connection protocol */ + public static final int DBO_COMPRESS = 512; + + /** @var int Ignore query errors and return false when they happen */ + public static final int QUERY_SILENCE_ERRORS = 1; // b/c for 1.32 query() argument; note that (int)true = 1 + /** + * @var int Treat the TEMPORARY table from the given CREATE query as if it is + * permanent as far as write tracking is concerned. This is useful for testing. + */ + public static final int QUERY_PSEUDO_PERMANENT = 2; + /** @var int Enforce that a query does not make effective writes */ + public static final int QUERY_REPLICA_ROLE = 4; + + /** @var bool Parameter to unionQueries() for UNION ALL */ + public static final boolean UNION_ALL = true; + /** @var bool Parameter to unionQueries() for UNION DISTINCT */ + public static final boolean UNION_DISTINCT = false; + + /** + * A string describing the current software version, and possibly + * other details in a user-friendly way. Will be listed on Special:Version, etc. + * Use getServerVersion() to get machine-friendly information. + * + * @return string Version information from the database server + */ + public String getServerInfo(); + + /** + * Turns buffering of SQL result sets on (true) or off (false). Default is "on". + * + * Unbuffered queries are very troublesome in MySQL: + * + * - If another query is executed while the first query is being read + * out, the first query is killed. This means you can't call normal + * Database functions while you are reading an unbuffered query result + * from a normal Database connection. + * + * - Unbuffered queries cause the MySQL server to use large amounts of + * memory and to hold broad locks which block other queries. + * + * If you want to limit client-side memory, it's almost always better to + * split up queries into batches using a LIMIT clause than to switch off + * buffering. + * + * @param null|bool $buffer + * @return null|bool The previous value of the flag + */ + public boolean bufferResults(boolean buffer); // buffer = null +// +// /** +// * Gets the current transaction level. +// * +// * Historically, transactions were allowed to be "nested". This is no +// * longer supported, so this function really only returns a boolean. +// * +// * @return int The previous value +// */ +// public function trxLevel(); +// +// /** +// * Get the UNIX timestamp of the time that the transaction was established +// * +// * This can be used to reason about the staleness of SELECT data +// * in REPEATABLE-READ transaction isolation level. +// * +// * @return float|null Returns null if there is not active transaction +// * @since 1.25 +// */ +// public function trxTimestamp(); +// +// /** +// * @return bool Whether an explicit transaction or atomic sections are still open +// * @since 1.28 +// */ +// public function explicitTrxActive(); +// +// /** +// * Assert that all explicit transactions or atomic sections have been closed. +// * @throws DBTransactionError +// * @since 1.32 +// */ +// public function assertNoOpenTransactions(); +// +// /** +// * Get/set the table prefix. +// * @param string|null $prefix The table prefix to set, or omitted to leave it unchanged. +// * @return string The previous table prefix +// * @throws DBUnexpectedError +// */ +// public function tablePrefix( $prefix = null ); +// +// /** +// * Get/set the db schema. +// * @param string|null $schema The database schema to set, or omitted to leave it unchanged. +// * @return string The previous db schema +// */ +// public function dbSchema( $schema = null ); +// +// /** +// * Get properties passed down from the server info array of the load +// * balancer. +// * +// * @param string|null $name The entry of the info array to get, or null to get the +// * whole array +// * +// * @return array|mixed|null +// */ +// public function getLBInfo( $name = null ); +// +// /** +// * Set the LB info array, or a member of it. If called with one parameter, +// * the LB info array is set to that parameter. If it is called with two +// * parameters, the member with the given name is set to the given value. +// * +// * @param string $name +// * @param array|null $value +// */ +// public function setLBInfo( $name, $value = null ); +// +// /** +// * Set a lazy-connecting DB handle to the master DB (for replication status purposes) +// * +// * @param IDatabase $conn +// * @since 1.27 +// */ +// public function setLazyMasterHandle( IDatabase $conn ); +// +// /** +// * Returns true if this database does an implicit sort when doing GROUP BY +// * +// * @return bool +// * @deprecated Since 1.30; only use grouped or aggregated fields in the SELECT +// */ +// public function implicitGroupby(); +// +// /** +// * Returns true if this database does an implicit order by when the column has an index +// * For example: SELECT page_title FROM page LIMIT 1 +// * +// * @return bool +// */ +// public function implicitOrderby(); +// +// /** +// * Return the last query that went through IDatabase::query() +// * @return string +// */ +// public function lastQuery(); +// +// /** +// * Returns true if the connection may have been used for write queries. +// * Should return true if unsure. +// * +// * @return bool +// * @deprecated Since 1.31; use lastDoneWrites() +// */ +// public function doneWrites(); +// +// /** +// * Returns the last time the connection may have been used for write queries. +// * Should return a timestamp if unsure. +// * +// * @return int|float UNIX timestamp or false +// * @since 1.24 +// */ +// public function lastDoneWrites(); +// +// /** +// * @return bool Whether there is a transaction open with possible write queries +// * @since 1.27 +// */ +// public function writesPending(); +// +// /** +// * @return bool Whether there is a transaction open with pre-commit callbacks pending +// * @since 1.32 +// */ +// public function preCommitCallbacksPending(); +// +// /** +// * Whether there is a transaction open with either possible write queries +// * or unresolved pre-commit/commit/resolution callbacks pending +// * +// * This does *not* count recurring callbacks, e.g. from setTransactionListener(). +// * +// * @return bool +// */ +// public function writesOrCallbacksPending(); +// +// /** +// * Get the time spend running write queries for this transaction +// * +// * High times could be due to scanning, updates, locking, and such +// * +// * @param string $type IDatabase::ESTIMATE_* constant [default: ESTIMATE_ALL] +// * @return float|bool Returns false if not transaction is active +// * @since 1.26 +// */ +// public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ); +// +// /** +// * Get the list of method names that did write queries for this transaction +// * +// * @return array +// * @since 1.27 +// */ +// public function pendingWriteCallers(); +// +// /** +// * Get the number of affected rows from pending write queries +// * +// * @return int +// * @since 1.30 +// */ +// public function pendingWriteRowsAffected(); +// +// /** +// * Is a connection to the database open? +// * @return bool +// */ +// public function isOpen(); +// +// /** +// * Set a flag for this connection +// * +// * @param int $flag DBO_* constants from Defines.php: +// * - DBO_DEBUG: output some debug info (same as debug()) +// * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) +// * - DBO_TRX: automatically start transactions +// * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode +// * and removes it in command line mode +// * - DBO_PERSISTENT: use persistant database connection +// * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING] +// */ +// public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ); +// +// /** +// * Clear a flag for this connection +// * +// * @param int $flag DBO_* constants from Defines.php: +// * - DBO_DEBUG: output some debug info (same as debug()) +// * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) +// * - DBO_TRX: automatically start transactions +// * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode +// * and removes it in command line mode +// * - DBO_PERSISTENT: use persistant database connection +// * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING] +// */ +// public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ); +// +// /** +// * Restore the flags to their prior state before the last setFlag/clearFlag call +// * +// * @param string $state IDatabase::RESTORE_* constant. [default: RESTORE_PRIOR] +// * @since 1.28 +// */ +// public function restoreFlags( $state = self::RESTORE_PRIOR ); +// +// /** +// * Returns a boolean whether the flag $flag is set for this connection +// * +// * @param int $flag DBO_* constants from Defines.php: +// * - DBO_DEBUG: output some debug info (same as debug()) +// * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) +// * - DBO_TRX: automatically start transactions +// * - DBO_PERSISTENT: use persistant database connection +// * @return bool +// */ +// public function getFlag( $flag ); +// +// /** +// * Return the currently selected domain ID +// * +// * Null components (database/schema) might change once a connection is established +// * +// * @return string +// */ +// public function getDomainID(); +// +// /** +// * Alias for getDomainID() +// * +// * @return string +// * @deprecated 1.30 +// */ +// public function getWikiID(); +// +// /** +// * Get the type of the DBMS, as it appears in $wgDBtype. +// * +// * @return string +// */ +// public function getType(); +// +// /** +// * Fetch the next row from the given result object, in object form. +// * Fields can be retrieved with $row->fieldname, with fields acting like +// * member variables. +// * If no more rows are available, false is returned. +// * +// * @param IResultWrapper|stdClass $res Object as returned from IDatabase::query(), etc. +// * @return stdClass|bool +// * @throws DBUnexpectedError Thrown if the database returns an error +// */ +// public function fetchObject( $res ); +// +// /** +// * Fetch the next row from the given result object, in associative array +// * form. Fields are retrieved with $row['fieldname']. +// * If no more rows are available, false is returned. +// * +// * @param IResultWrapper $res Result object as returned from IDatabase::query(), etc. +// * @return array|bool +// * @throws DBUnexpectedError Thrown if the database returns an error +// */ +// public function fetchRow( $res ); +// +// /** +// * Get the number of rows in a query result. If the query did not return +// * any rows (for example, if it was a write query), this returns zero. +// * +// * @param mixed $res A SQL result +// * @return int +// */ +// public function numRows( $res ); +// +// /** +// * Get the number of fields in a result object +// * @see https://secure.php.net/mysql_num_fields +// * +// * @param mixed $res A SQL result +// * @return int +// */ +// public function numFields( $res ); +// +// /** +// * Get a field name in a result object +// * @see https://secure.php.net/mysql_field_name +// * +// * @param mixed $res A SQL result +// * @param int $n +// * @return string +// */ +// public function fieldName( $res, $n ); +// +// /** +// * Get the inserted value of an auto-increment row +// * +// * This should only be called after an insert that used an auto-incremented +// * value. If no such insert was previously done in the current database +// * session, the return value is undefined. +// * +// * @return int +// */ +// public function insertId(); +// +// /** +// * Change the position of the cursor in a result object +// * @see https://secure.php.net/mysql_data_seek +// * +// * @param mixed $res A SQL result +// * @param int $row +// */ +// public function dataSeek( $res, $row ); +// +// /** +// * Get the last error number +// * @see https://secure.php.net/mysql_errno +// * +// * @return int +// */ +// public function lastErrno(); +// +// /** +// * Get a description of the last error +// * @see https://secure.php.net/mysql_error +// * +// * @return string +// */ +// public function lastError(); +// +// /** +// * Get the number of rows affected by the last write query +// * @see https://secure.php.net/mysql_affected_rows +// * +// * @return int +// */ +// public function affectedRows(); +// +// /** +// * Returns a wikitext link to the DB's website, e.g., +// * return "[https://www.mysql.com/ MySQL]"; +// * Should at least contain plain text, if for some reason +// * your database has no website. +// * +// * @return string Wikitext of a link to the server software's web site +// */ +// public function getSoftwareLink(); +// +// /** +// * A string describing the current software version, like from +// * mysql_get_server_info(). +// * +// * @return string Version information from the database server. +// */ +// public function getServerVersion(); +// +// /** +// * Close the database connection +// * +// * This should only be called after any transactions have been resolved, +// * aside from read-only automatic transactions (assuming no callbacks are registered). +// * If a transaction is still open anyway, it will be rolled back. +// * +// * @throws DBError +// * @return bool Operation success. true if already closed. +// */ +// public function close(); +// +// /** +// * Run an SQL query and return the result. Normally throws a DBQueryError +// * on failure. If errors are ignored, returns false instead. +// * +// * If a connection loss is detected, then an attempt to reconnect will be made. +// * For queries that involve no larger transactions or locks, they will be re-issued +// * for convenience, provided the connection was re-established. +// * +// * In new code, the query wrappers select(), insert(), update(), delete(), +// * etc. should be used where possible, since they give much better DBMS +// * independence and automatically quote or validate user input in a variety +// * of contexts. This function is generally only useful for queries which are +// * explicitly DBMS-dependent and are unsupported by the query wrappers, such +// * as CREATE TABLE. +// * +// * However, the query wrappers themselves should call this function. +// * +// * @param string $sql SQL query +// * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST +// * comment (you can use __METHOD__ or add some extra info) +// * @param int $flags Bitfield of IDatabase::QUERY_* constants. Note that suppression +// * of errors is best handled by try/catch rather than using one of these flags. +// * @return bool|IResultWrapper True for a successful write query, IResultWrapper object +// * for a successful read query, or false on failure if QUERY_SILENCE_ERRORS is set. +// * @throws DBError +// */ +// public function query( $sql, $fname = __METHOD__, $flags = 0 ); +// +// /** +// * Free a result object returned by query() or select(). It's usually not +// * necessary to call this, just use unset() or let the variable holding +// * the result object go out of scope. +// * +// * @param mixed $res A SQL result +// */ +// public function freeResult( $res ); +// +// /** +// * A SELECT wrapper which returns a single field from a single result row. +// * +// * Usually throws a DBQueryError on failure. If errors are explicitly +// * ignored, returns false on failure. +// * +// * If no result rows are returned from the query, false is returned. +// * +// * @param string|array $table Table name. See IDatabase::select() for details. +// * @param string $var The field name to select. This must be a valid SQL +// * fragment: do not use unvalidated user input. +// * @param string|array $cond The condition array. See IDatabase::select() for details. +// * @param string $fname The function name of the caller. +// * @param string|array $options The query options. See IDatabase::select() for details. +// * @param string|array $join_conds The query join conditions. See IDatabase::select() for details. +// * +// * @return mixed The value from the field +// * @throws DBError +// */ +// public function selectField( +// $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = [] +// ); +// +// /** +// * A SELECT wrapper which returns a list of single field values from result rows. +// * +// * Usually throws a DBQueryError on failure. If errors are explicitly +// * ignored, returns false on failure. +// * +// * If no result rows are returned from the query, false is returned. +// * +// * @param string|array $table Table name. See IDatabase::select() for details. +// * @param string $var The field name to select. This must be a valid SQL +// * fragment: do not use unvalidated user input. +// * @param string|array $cond The condition array. See IDatabase::select() for details. +// * @param string $fname The function name of the caller. +// * @param string|array $options The query options. See IDatabase::select() for details. +// * @param string|array $join_conds The query join conditions. See IDatabase::select() for details. +// * +// * @return array The values from the field in the order they were returned from the DB +// * @throws DBError +// * @since 1.25 +// */ +// public function selectFieldValues( +// $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = [] +// ); +// +// /** +// * Execute a SELECT query constructed using the various parameters provided. +// * See below for full details of the parameters. +// * +// * @param string|array $table Table name(s) +// * +// * May be either an array of table names, or a single string holding a table +// * name. If an array is given, table aliases can be specified, for example: +// * +// * [ 'a' => 'user' ] +// * +// * This includes the user table in the query, with the alias "a" available +// * for use in field names (e.g. a.user_name). +// * +// * A derived table, defined by the result of selectSQLText(), requires an alias +// * key and a Subquery instance value which wraps the SQL query, for example: +// * +// * [ 'c' => new Subquery( 'SELECT ...' ) ] +// * +// * Joins using parentheses for grouping (since MediaWiki 1.31) may be +// * constructed using nested arrays. For example, +// * +// * [ 'tableA', 'nestedB' => [ 'tableB', 'b2' => 'tableB2' ] ] +// * +// * along with `$join_conds` like +// * +// * [ 'b2' => [ 'JOIN', 'b_id = b2_id' ], 'nestedB' => [ 'LEFT JOIN', 'b_a = a_id' ] ] +// * +// * will produce SQL something like +// * +// * FROM tableA LEFT JOIN (tableB JOIN tableB2 AS b2 ON (b_id = b2_id)) ON (b_a = a_id) +// * +// * All of the table names given here are automatically run through +// * Database::tableName(), which causes the table prefix (if any) to be +// * added, and various other table name mappings to be performed. +// * +// * Do not use untrusted user input as a table name. Alias names should +// * not have characters outside of the Basic multilingual plane. +// * +// * @param string|array $vars Field name(s) +// * +// * May be either a field name or an array of field names. The field names +// * can be complete fragments of SQL, for direct inclusion into the SELECT +// * query. If an array is given, field aliases can be specified, for example: +// * +// * [ 'maxrev' => 'MAX(rev_id)' ] +// * +// * This includes an expression with the alias "maxrev" in the query. +// * +// * If an expression is given, care must be taken to ensure that it is +// * DBMS-independent. +// * +// * Untrusted user input must not be passed to this parameter. +// * +// * @param string|array $conds +// * +// * May be either a string containing a single condition, or an array of +// * conditions. If an array is given, the conditions constructed from each +// * element are combined with AND. +// * +// * Array elements may take one of two forms: +// * +// * - Elements with a numeric key are interpreted as raw SQL fragments. +// * - Elements with a string key are interpreted as equality conditions, +// * where the key is the field name. +// * - If the value of such an array element is a scalar (such as a +// * string), it will be treated as data and thus quoted appropriately. +// * If it is null, an IS NULL clause will be added. +// * - If the value is an array, an IN (...) clause will be constructed +// * from its non-null elements, and an IS NULL clause will be added +// * if null is present, such that the field may match any of the +// * elements in the array. The non-null elements will be quoted. +// * +// * Note that expressions are often DBMS-dependent in their syntax. +// * DBMS-independent wrappers are provided for constructing several types of +// * expression commonly used in condition queries. See: +// * - IDatabase::buildLike() +// * - IDatabase::conditional() +// * +// * Untrusted user input is safe in the values of string keys, however untrusted +// * input must not be used in the array key names or in the values of numeric keys. +// * Escaping of untrusted input used in values of numeric keys should be done via +// * IDatabase::addQuotes() +// * +// * Use an empty array, string, or '*' to update all rows. +// * +// * @param string $fname Caller function name +// * +// * @param string|array $options Query options +// * +// * Optional: Array of query options. Boolean options are specified by +// * including them in the array as a string value with a numeric key, for +// * example: +// * +// * [ 'FOR UPDATE' ] +// * +// * The supported options are: +// * +// * - OFFSET: Skip this many rows at the start of the result set. OFFSET +// * with LIMIT can theoretically be used for paging through a result set, +// * but this is discouraged for performance reasons. +// * +// * - LIMIT: Integer: return at most this many rows. The rows are sorted +// * and then the first rows are taken until the limit is reached. LIMIT +// * is applied to a result set after OFFSET. +// * +// * - FOR UPDATE: Boolean: lock the returned rows so that they can't be +// * changed until the next COMMIT. +// * +// * - DISTINCT: Boolean: return only unique result rows. +// * +// * - GROUP BY: May be either an SQL fragment string naming a field or +// * expression to group by, or an array of such SQL fragments. +// * +// * - HAVING: May be either an string containing a HAVING clause or an array of +// * conditions building the HAVING clause. If an array is given, the conditions +// * constructed from each element are combined with AND. +// * +// * - ORDER BY: May be either an SQL fragment giving a field name or +// * expression to order by, or an array of such SQL fragments. +// * +// * - USE INDEX: This may be either a string giving the index name to use +// * for the query, or an array. If it is an associative array, each key +// * gives the table name (or alias), each value gives the index name to +// * use for that table. All strings are SQL fragments and so should be +// * validated by the caller. +// * +// * - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run, +// * instead of SELECT. +// * +// * And also the following boolean MySQL extensions, see the MySQL manual +// * for documentation: +// * +// * - LOCK IN SHARE MODE +// * - STRAIGHT_JOIN +// * - HIGH_PRIORITY +// * - SQL_BIG_RESULT +// * - SQL_BUFFER_RESULT +// * - SQL_SMALL_RESULT +// * - SQL_CALC_FOUND_ROWS +// * - SQL_CACHE +// * - SQL_NO_CACHE +// * +// * +// * @param string|array $join_conds Join conditions +// * +// * Optional associative array of table-specific join conditions. In the +// * most common case, this is unnecessary, since the join condition can be +// * in $conds. However, it is useful for doing a LEFT JOIN. +// * +// * The key of the array contains the table name or alias. The value is an +// * array with two elements, numbered 0 and 1. The first gives the type of +// * join, the second is the same as the $conds parameter. Thus it can be +// * an SQL fragment, or an array where the string keys are equality and the +// * numeric keys are SQL fragments all AND'd together. For example: +// * +// * [ 'page' => [ 'LEFT JOIN', 'page_latest=rev_id' ] ] +// * +// * @return IResultWrapper Resulting rows +// * @throws DBError +// */ +// public function select( +// $table, $vars, $conds = '', $fname = __METHOD__, +// $options = [], $join_conds = [] +// ); +// +// /** +// * The equivalent of IDatabase::select() except that the constructed SQL +// * is returned, instead of being immediately executed. This can be useful for +// * doing UNION queries, where the SQL text of each query is needed. In general, +// * however, callers outside of Database classes should just use select(). +// * +// * @see IDatabase::select() +// * +// * @param string|array $table Table name +// * @param string|array $vars Field names +// * @param string|array $conds Conditions +// * @param string $fname Caller function name +// * @param string|array $options Query options +// * @param string|array $join_conds Join conditions +// * @return string SQL query string +// */ +// public function selectSQLText( +// $table, $vars, $conds = '', $fname = __METHOD__, +// $options = [], $join_conds = [] +// ); +// +// /** +// * Single row SELECT wrapper. Equivalent to IDatabase::select(), except +// * that a single row object is returned. If the query returns no rows, +// * false is returned. +// * +// * @param string|array $table Table name +// * @param string|array $vars Field names +// * @param array $conds Conditions +// * @param string $fname Caller function name +// * @param string|array $options Query options +// * @param array|string $join_conds Join conditions +// * +// * @return stdClass|bool +// * @throws DBError +// */ +// public function selectRow( $table, $vars, $conds, $fname = __METHOD__, +// $options = [], $join_conds = [] +// ); +// +// /** +// * Estimate the number of rows in dataset +// * +// * MySQL allows you to estimate the number of rows that would be returned +// * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using +// * index cardinality statistics, and is notoriously inaccurate, especially +// * when large numbers of rows have recently been added or deleted. +// * +// * For DBMSs that don't support fast result size estimation, this function +// * will actually perform the SELECT COUNT(*). +// * +// * Takes the same arguments as IDatabase::select(). +// * +// * @param string $table Table name +// * @param string $var Column for which NULL values are not counted [default "*"] +// * @param array|string $conds Filters on the table +// * @param string $fname Function name for profiling +// * @param array $options Options for select +// * @param array|string $join_conds Join conditions +// * @return int Row count +// * @throws DBError +// */ +// public function estimateRowCount( +// $table, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = [] +// ); +// +// /** +// * Get the number of rows in dataset +// * +// * This is useful when trying to do COUNT(*) but with a LIMIT for performance. +// * +// * Takes the same arguments as IDatabase::select(). +// * +// * @since 1.27 Added $join_conds parameter +// * +// * @param array|string $tables Table names +// * @param string $var Column for which NULL values are not counted [default "*"] +// * @param array|string $conds Filters on the table +// * @param string $fname Function name for profiling +// * @param array $options Options for select +// * @param array $join_conds Join conditions (since 1.27) +// * @return int Row count +// * @throws DBError +// */ +// public function selectRowCount( +// $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = [] +// ); +// +// /** +// * Lock all rows meeting the given conditions/options FOR UPDATE +// * +// * @param array|string $table Table names +// * @param array|string $conds Filters on the table +// * @param string $fname Function name for profiling +// * @param array $options Options for select ("FOR UPDATE" is added automatically) +// * @param array $join_conds Join conditions +// * @return int Number of matching rows found (and locked) +// * @since 1.32 +// */ +// public function lockForUpdate( +// $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = [] +// ); +// +// /** +// * Determines whether a field exists in a table +// * +// * @param string $table Table name +// * @param string $field Filed to check on that table +// * @param string $fname Calling function name (optional) +// * @return bool Whether $table has filed $field +// * @throws DBError +// */ +// public function fieldExists( $table, $field, $fname = __METHOD__ ); +// +// /** +// * Determines whether an index exists +// * Usually throws a DBQueryError on failure +// * If errors are explicitly ignored, returns NULL on failure +// * +// * @param string $table +// * @param string $index +// * @param string $fname +// * @return bool|null +// * @throws DBError +// */ +// public function indexExists( $table, $index, $fname = __METHOD__ ); +// +// /** +// * Query whether a given table exists +// * +// * @param string $table +// * @param string $fname +// * @return bool +// * @throws DBError +// */ +// public function tableExists( $table, $fname = __METHOD__ ); +// +// /** +// * INSERT wrapper, inserts an array into a table. +// * +// * $a may be either: +// * +// * - A single associative array. The array keys are the field names, and +// * the values are the values to insert. The values are treated as data +// * and will be quoted appropriately. If NULL is inserted, this will be +// * converted to a database NULL. +// * - An array with numeric keys, holding a list of associative arrays. +// * This causes a multi-row INSERT on DBMSs that support it. The keys in +// * each subarray must be identical to each other, and in the same order. +// * +// * Usually throws a DBQueryError on failure. If errors are explicitly ignored, +// * returns success. +// * +// * $options is an array of options, with boolean options encoded as values +// * with numeric keys, in the same style as $options in +// * IDatabase::select(). Supported options are: +// * +// * - IGNORE: Boolean: if present, duplicate key errors are ignored, and +// * any rows which cause duplicate key errors are not inserted. It's +// * possible to determine how many rows were successfully inserted using +// * IDatabase::affectedRows(). +// * +// * @param string $table Table name. This will be passed through +// * Database::tableName(). +// * @param array $a Array of rows to insert +// * @param string $fname Calling function name (use __METHOD__) for logs/profiling +// * @param array $options Array of options +// * @return bool Return true if no exception was thrown (deprecated since 1.33) +// * @throws DBError +// */ +// public function insert( $table, $a, $fname = __METHOD__, $options = [] ); +// +// /** +// * UPDATE wrapper. Takes a condition array and a SET array. +// * +// * @param string $table Name of the table to UPDATE. This will be passed through +// * Database::tableName(). +// * @param array $values An array of values to SET. For each array element, +// * the key gives the field name, and the value gives the data to set +// * that field to. The data will be quoted by IDatabase::addQuotes(). +// * Values with integer keys form unquoted SET statements, which can be used for +// * things like "field = field + 1" or similar computed values. +// * @param array $conds An array of conditions (WHERE). See +// * IDatabase::select() for the details of the format of condition +// * arrays. Use '*' to update all rows. +// * @param string $fname The function name of the caller (from __METHOD__), +// * for logging and profiling. +// * @param array $options An array of UPDATE options, can be: +// * - IGNORE: Ignore unique key conflicts +// * - LOW_PRIORITY: MySQL-specific, see MySQL manual. +// * @return bool Return true if no exception was thrown (deprecated since 1.33) +// * @throws DBError +// */ +// public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ); +// +// /** +// * Makes an encoded list of strings from an array +// * +// * These can be used to make conjunctions or disjunctions on SQL condition strings +// * derived from an array (see IDatabase::select() $conds documentation). +// * +// * Example usage: +// * @code +// * $sql = $db->makeList( [ +// * 'rev_page' => $id, +// * $db->makeList( [ 'rev_minor' => 1, 'rev_len' < 500 ], $db::LIST_OR ] ) +// * ], $db::LIST_AND ); +// * @endcode +// * This would set $sql to "rev_page = '$id' AND (rev_minor = '1' OR rev_len < '500')" +// * +// * @param array $a Containing the data +// * @param int $mode IDatabase class constant: +// * - IDatabase::LIST_COMMA: Comma separated, no field names +// * - IDatabase::LIST_AND: ANDed WHERE clause (without the WHERE). +// * - IDatabase::LIST_OR: ORed WHERE clause (without the WHERE) +// * - IDatabase::LIST_SET: Comma separated with field names, like a SET clause +// * - IDatabase::LIST_NAMES: Comma separated field names +// * @throws DBError +// * @return string +// */ +// public function makeList( $a, $mode = self::LIST_COMMA ); +// +// /** +// * Build a partial where clause from a 2-d array such as used for LinkBatch. +// * The keys on each level may be either integers or strings. +// * +// * @param array $data Organized as 2-d +// * [ baseKeyVal => [ subKeyVal => [ignored], ... ], ... ] +// * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace') +// * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title') +// * @return string|bool SQL fragment, or false if no items in array +// */ +// public function makeWhereFrom2d( $data, $baseKey, $subKey ); +// +// /** +// * Return aggregated value alias +// * +// * @param array $valuedata +// * @param string $valuename +// * +// * @return string +// * @deprecated Since 1.33 +// */ +// public function aggregateValue( $valuedata, $valuename = 'value' ); +// +// /** +// * @param string $field +// * @return string +// */ +// public function bitNot( $field ); +// +// /** +// * @param string $fieldLeft +// * @param string $fieldRight +// * @return string +// */ +// public function bitAnd( $fieldLeft, $fieldRight ); +// +// /** +// * @param string $fieldLeft +// * @param string $fieldRight +// * @return string +// */ +// public function bitOr( $fieldLeft, $fieldRight ); +// +// /** +// * Build a concatenation list to feed into a SQL query +// * @param array $stringList List of raw SQL expressions; caller is +// * responsible for any quoting +// * @return string +// */ +// public function buildConcat( $stringList ); +// +// /** +// * Build a GROUP_CONCAT or equivalent statement for a query. +// * +// * This is useful for combining a field for several rows into a single string. +// * NULL values will not appear in the output, duplicated values will appear, +// * and the resulting delimiter-separated values have no defined sort order. +// * Code using the results may need to use the PHP unique() or sort() methods. +// * +// * @param string $delim Glue to bind the results together +// * @param string|array $table Table name +// * @param string $field Field name +// * @param string|array $conds Conditions +// * @param string|array $join_conds Join conditions +// * @return string SQL text +// * @since 1.23 +// */ +// public function buildGroupConcatField( +// $delim, $table, $field, $conds = '', $join_conds = [] +// ); +// +// /** +// * Build a SUBSTRING function. +// * +// * Behavior for non-ASCII values is undefined. +// * +// * @param string $input Field name +// * @param int $startPosition Positive integer +// * @param int|null $length Non-negative integer length or null for no limit +// * @throws InvalidArgumentException +// * @return string SQL text +// * @since 1.31 +// */ +// public function buildSubString( $input, $startPosition, $length = null ); +// +// /** +// * @param string $field Field or column to cast +// * @return string +// * @since 1.28 +// */ +// public function buildStringCast( $field ); +// +// /** +// * @param string $field Field or column to cast +// * @return string +// * @since 1.31 +// */ +// public function buildIntegerCast( $field ); +// +// /** +// * Equivalent to IDatabase::selectSQLText() except wraps the result in Subqyery +// * +// * @see IDatabase::selectSQLText() +// * +// * @param string|array $table Table name +// * @param string|array $vars Field names +// * @param string|array $conds Conditions +// * @param string $fname Caller function name +// * @param string|array $options Query options +// * @param string|array $join_conds Join conditions +// * @return Subquery +// * @since 1.31 +// */ +// public function buildSelectSubquery( +// $table, $vars, $conds = '', $fname = __METHOD__, +// $options = [], $join_conds = [] +// ); +// +// /** +// * Returns true if DBs are assumed to be on potentially different servers +// * +// * In systems like mysql/mariadb, different databases can easily be referenced on a single +// * connection merely by name, even in a single query via JOIN. On the other hand, Postgres +// * treats databases as fully separate, only allowing mechanisms like postgres_fdw to +// * effectively "mount" foreign DBs. This is true even among DBs on the same server. +// * +// * @return bool +// * @since 1.29 +// */ +// public function databasesAreIndependent(); +// +// /** +// * Change the current database +// * +// * This should not be called outside LoadBalancer for connections managed by a LoadBalancer +// * +// * @param string $db +// * @return bool True unless an exception was thrown +// * @throws DBConnectionError If databasesAreIndependent() is true and an error occurs +// * @throws DBError +// * @deprecated Since 1.32 Use selectDomain() instead +// */ +// public function selectDB( $db ); +// +// /** +// * Set the current domain (database, schema, and table prefix) +// * +// * This will throw an error for some database types if the database unspecified +// * +// * This should not be called outside LoadBalancer for connections managed by a LoadBalancer +// * +// * @param string|DatabaseDomain $domain +// * @since 1.32 +// * @throws DBConnectionError +// */ +// public function selectDomain( $domain ); +// +// /** +// * Get the current DB name +// * @return string|null +// */ +// public function getDBname(); +// +// /** +// * Get the server hostname or IP address +// * @return string +// */ +// public function getServer(); +// +// /** +// * Adds quotes and backslashes. +// * +// * @param string|int|null|bool|Blob $s +// * @return string|int +// */ +// public function addQuotes( $s ); +// +// /** +// * Quotes an identifier, in order to make user controlled input safe +// * +// * Depending on the database this will either be `backticks` or "double quotes" +// * +// * @param string $s +// * @return string +// * @since 1.33 +// */ +// public function addIdentifierQuotes( $s ); +// +// /** +// * LIKE statement wrapper, receives a variable-length argument list with +// * parts of pattern to match containing either string literals that will be +// * escaped or tokens returned by anyChar() or anyString(). Alternatively, +// * the function could be provided with an array of aforementioned +// * parameters. +// * +// * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns +// * a LIKE clause that searches for subpages of 'My page title'. +// * Alternatively: +// * $pattern = [ 'My_page_title/', $dbr->anyString() ]; +// * $query .= $dbr->buildLike( $pattern ); +// * +// * @since 1.16 +// * @return string Fully built LIKE statement +// */ +// public function buildLike(); +// +// /** +// * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query +// * +// * @return LikeMatch +// */ +// public function anyChar(); +// +// /** +// * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query +// * +// * @return LikeMatch +// */ +// public function anyString(); +// +// /** +// * Deprecated method, calls should be removed. +// * +// * This was formerly used for PostgreSQL and Oracle to handle +// * self::insertId() auto-incrementing fields. It is no longer necessary +// * since DatabasePostgres::insertId() has been reimplemented using +// * `lastval()` and Oracle has been reimplemented using triggers. +// * +// * Implementations should return null if inserting `NULL` into an +// * auto-incrementing field works, otherwise it should return an instance of +// * NextSequenceValue and filter it on calls to relevant methods. +// * +// * @deprecated since 1.30, no longer needed +// * @param string $seqName +// * @return null|NextSequenceValue +// */ +// public function nextSequenceValue( $seqName ); +// +// /** +// * REPLACE query wrapper. +// * +// * REPLACE is a very handy MySQL extension, which functions like an INSERT +// * except that when there is a duplicate key error, the old row is deleted +// * and the new row is inserted in its place. +// * +// * We simulate this with standard SQL with a DELETE followed by INSERT. To +// * perform the delete, we need to know what the unique indexes are so that +// * we know how to find the conflicting rows. +// * +// * It may be more efficient to leave off unique indexes which are unlikely +// * to collide. However if you do this, you run the risk of encountering +// * errors which wouldn't have occurred in MySQL. +// * +// * @param string $table The table to replace the row(s) in. +// * @param array[]|string[]|string $uniqueIndexes All unique indexes. One of the following: +// * a) the one unique field in the table (when no composite unique key exist) +// * b) a list of all unique fields in the table (when no composite unique key exist) +// * c) a list of all unique indexes in the table (each as a list of the indexed fields) +// * @param array $rows Can be either a single row to insert, or multiple rows, +// * in the same format as for IDatabase::insert() +// * @param string $fname Calling function name (use __METHOD__) for logs/profiling +// * @throws DBError +// */ +// public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ); +// +// /** +// * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table. +// * +// * This updates any conflicting rows (according to the unique indexes) using +// * the provided SET clause and inserts any remaining (non-conflicted) rows. +// * +// * $rows may be either: +// * - A single associative array. The array keys are the field names, and +// * the values are the values to insert. The values are treated as data +// * and will be quoted appropriately. If NULL is inserted, this will be +// * converted to a database NULL. +// * - An array with numeric keys, holding a list of associative arrays. +// * This causes a multi-row INSERT on DBMSs that support it. The keys in +// * each subarray must be identical to each other, and in the same order. +// * +// * It may be more efficient to leave off unique indexes which are unlikely +// * to collide. However if you do this, you run the risk of encountering +// * errors which wouldn't have occurred in MySQL. +// * +// * Usually throws a DBQueryError on failure. If errors are explicitly ignored, +// * returns success. +// * +// * @since 1.22 +// * +// * @param string $table Table name. This will be passed through Database::tableName(). +// * @param array $rows A single row or list of rows to insert +// * @param array[]|string[]|string $uniqueIndexes All unique indexes. One of the following: +// * a) the one unique field in the table (when no composite unique key exist) +// * b) a list of all unique fields in the table (when no composite unique key exist) +// * c) a list of all unique indexes in the table (each as a list of the indexed fields) +// * @param array $set An array of values to SET. For each array element, the +// * key gives the field name, and the value gives the data to set that +// * field to. The data will be quoted by IDatabase::addQuotes(). +// * Values with integer keys form unquoted SET statements, which can be used for +// * things like "field = field + 1" or similar computed values. +// * @param string $fname Calling function name (use __METHOD__) for logs/profiling +// * @throws DBError +// * @return bool Return true if no exception was thrown (deprecated since 1.33) +// */ +// public function upsert( +// $table, array $rows, $uniqueIndexes, array $set, $fname = __METHOD__ +// ); +// +// /** +// * DELETE where the condition is a join. +// * +// * MySQL overrides this to use a multi-table DELETE syntax, in other databases +// * we use sub-selects +// * +// * For safety, an empty $conds will not delete everything. If you want to +// * delete all rows where the join condition matches, set $conds='*'. +// * +// * DO NOT put the join condition in $conds. +// * +// * @param string $delTable The table to delete from. +// * @param string $joinTable The other table. +// * @param string $delVar The variable to join on, in the first table. +// * @param string $joinVar The variable to join on, in the second table. +// * @param array $conds Condition array of field names mapped to variables, +// * ANDed together in the WHERE clause +// * @param string $fname Calling function name (use __METHOD__) for logs/profiling +// * @throws DBError +// */ +// public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, +// $fname = __METHOD__ +// ); +// +// /** +// * DELETE query wrapper. +// * +// * @param string $table Table name +// * @param string|array $conds Array of conditions. See $conds in IDatabase::select() +// * for the format. Use $conds == "*" to delete all rows +// * @param string $fname Name of the calling function +// * @throws DBUnexpectedError +// * @return bool Return true if no exception was thrown (deprecated since 1.33) +// * @throws DBError +// */ +// public function delete( $table, $conds, $fname = __METHOD__ ); +// +// /** +// * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it +// * into another table. +// * +// * @warning If the insert will use an auto-increment or sequence to +// * determine the value of a column, this may break replication on +// * databases using statement-based replication if the SELECT is not +// * deterministically ordered. +// * +// * @param string $destTable The table name to insert into +// * @param string|array $srcTable May be either a table name, or an array of table names +// * to include in a join. +// * +// * @param array $varMap Must be an associative array of the form +// * [ 'dest1' => 'source1', ... ]. Source items may be literals +// * rather than field names, but strings should be quoted with +// * IDatabase::addQuotes() +// * +// * @param array $conds Condition array. See $conds in IDatabase::select() for +// * the details of the format of condition arrays. May be "*" to copy the +// * whole table. +// * +// * @param string $fname The function name of the caller, from __METHOD__ +// * +// * @param array $insertOptions Options for the INSERT part of the query, see +// * IDatabase::insert() for details. Also, one additional option is +// * available: pass 'NO_AUTO_COLUMNS' to hint that the query does not use +// * an auto-increment or sequence to determine any column values. +// * @param array $selectOptions Options for the SELECT part of the query, see +// * IDatabase::select() for details. +// * @param array $selectJoinConds Join conditions for the SELECT part of the query, see +// * IDatabase::select() for details. +// * +// * @return bool Return true if no exception was thrown (deprecated since 1.33) +// * @throws DBError +// */ +// public function insertSelect( $destTable, $srcTable, $varMap, $conds, +// $fname = __METHOD__, +// $insertOptions = [], $selectOptions = [], $selectJoinConds = [] +// ); +// +// /** +// * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries +// * within the UNION construct. +// * @return bool +// */ +// public function unionSupportsOrderAndLimit(); +// +// /** +// * Construct a UNION query +// * This is used for providing overload point for other DB abstractions +// * not compatible with the MySQL syntax. +// * @param array $sqls SQL statements to combine +// * @param bool $all Either IDatabase::UNION_ALL or IDatabase::UNION_DISTINCT +// * @return string SQL fragment +// */ +// public function unionQueries( $sqls, $all ); +// +// /** +// * Construct a UNION query for permutations of conditions +// * +// * Databases sometimes have trouble with queries that have multiple values +// * for multiple condition parameters combined with limits and ordering. +// * This method constructs queries for the Cartesian product of the +// * conditions and unions them all together. +// * +// * @see IDatabase::select() +// * @since 1.30 +// * @param string|array $table Table name +// * @param string|array $vars Field names +// * @param array $permute_conds Conditions for the Cartesian product. Keys +// * are field names, values are arrays of the possible values for that +// * field. +// * @param string|array $extra_conds Additional conditions to include in the +// * query. +// * @param string $fname Caller function name +// * @param string|array $options Query options. In addition to the options +// * recognized by IDatabase::select(), the following may be used: +// * - NOTALL: Set to use UNION instead of UNION ALL. +// * - INNER ORDER BY: If specified and supported, subqueries will use this +// * instead of ORDER BY. +// * @param string|array $join_conds Join conditions +// * @return string SQL query string. +// */ +// public function unionConditionPermutations( +// $table, $vars, array $permute_conds, $extra_conds = '', $fname = __METHOD__, +// $options = [], $join_conds = [] +// ); +// +// /** +// * Returns an SQL expression for a simple conditional. This doesn't need +// * to be overridden unless CASE isn't supported in your DBMS. +// * +// * @param string|array $cond SQL expression which will result in a boolean value +// * @param string $trueVal SQL expression to return if true +// * @param string $falseVal SQL expression to return if false +// * @return string SQL fragment +// */ +// public function conditional( $cond, $trueVal, $falseVal ); +// +// /** +// * Returns a command for str_replace function in SQL query. +// * Uses REPLACE() in MySQL +// * +// * @param string $orig Column to modify +// * @param string $old Column to seek +// * @param string $new Column to replace with +// * +// * @return string +// */ +// public function strreplace( $orig, $old, $new ); +// +// /** +// * Determines how long the server has been up +// * +// * @return int +// * @throws DBError +// */ +// public function getServerUptime(); +// +// /** +// * Determines if the last failure was due to a deadlock +// * +// * Note that during a deadlock, the prior transaction will have been lost +// * +// * @return bool +// */ +// public function wasDeadlock(); +// +// /** +// * Determines if the last failure was due to a lock timeout +// * +// * Note that during a lock wait timeout, the prior transaction will have been lost +// * +// * @return bool +// */ +// public function wasLockTimeout(); +// +// /** +// * Determines if the last query error was due to a dropped connection +// * +// * Note that during a connection loss, the prior transaction will have been lost +// * +// * @return bool +// * @since 1.31 +// */ +// public function wasConnectionLoss(); +// +// /** +// * Determines if the last failure was due to the database being read-only. +// * +// * @return bool +// */ +// public function wasReadOnlyError(); +// +// /** +// * Determines if the last query error was due to something outside of the query itself +// * +// * Note that the transaction may have been lost, discarding prior writes and results +// * +// * @return bool +// */ +// public function wasErrorReissuable(); +// +// /** +// * Wait for the replica DB to catch up to a given master position +// * +// * Note that this does not start any new transactions. If any existing transaction +// * is flushed, and this is called, then queries will reflect the point the DB was synced +// * up to (on success) without interference from REPEATABLE-READ snapshots. +// * +// * @param DBMasterPos $pos +// * @param int $timeout The maximum number of seconds to wait for synchronisation +// * @return int|null Zero if the replica DB was past that position already, +// * greater than zero if we waited for some period of time, less than +// * zero if it timed out, and null on error +// * @throws DBError +// */ +// public function masterPosWait( DBMasterPos $pos, $timeout ); +// +// /** +// * Get the replication position of this replica DB +// * +// * @return DBMasterPos|bool False if this is not a replica DB +// * @throws DBError +// */ +// public function getReplicaPos(); +// +// /** +// * Get the position of this master +// * +// * @return DBMasterPos|bool False if this is not a master +// * @throws DBError +// */ +// public function getMasterPos(); +// +// /** +// * @return bool Whether the DB is marked as read-only server-side +// * @since 1.28 +// */ +// public function serverIsReadOnly(); +// +// /** +// * Run a callback as soon as the current transaction commits or rolls back. +// * An error is thrown if no transaction is pending. Queries in the function will run in +// * AUTOCOMMIT mode unless there are begin() calls. Callbacks must commit any transactions +// * that they begin. +// * +// * This is useful for combining cooperative locks and DB transactions. +// * +// * @note do not assume that *other* IDatabase instances will be AUTOCOMMIT mode +// * +// * The callback takes the following arguments: +// * - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK) +// * - This IDatabase instance (since 1.32) +// * +// * @param callable $callback +// * @param string $fname Caller name +// * @since 1.28 +// */ +// public function onTransactionResolution( callable $callback, $fname = __METHOD__ ); +// +// /** +// * Run a callback as soon as there is no transaction pending. +// * If there is a transaction and it is rolled back, then the callback is cancelled. +// * +// * When transaction round mode (DBO_TRX) is set, the callback will run at the end +// * of the round, just after all peer transactions COMMIT. If the transaction round +// * is rolled back, then the callback is cancelled. +// * +// * Queries in the function will run in AUTOCOMMIT mode unless there are begin() calls. +// * Callbacks must commit any transactions that they begin. +// * +// * This is useful for updates to different systems or when separate transactions are needed. +// * For example, one might want to enqueue jobs into a system outside the database, but only +// * after the database is updated so that the jobs will see the data when they actually run. +// * It can also be used for updates that easily suffer from lock timeouts and deadlocks, +// * but where atomicity is not essential. +// * +// * Avoid using IDatabase instances aside from this one in the callback, unless such instances +// * never have IDatabase::DBO_TRX set. This keeps callbacks from interfering with one another. +// * +// * Updates will execute in the order they were enqueued. +// * +// * @note do not assume that *other* IDatabase instances will be AUTOCOMMIT mode +// * +// * The callback takes the following arguments: +// * - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE) +// * - This IDatabase instance (since 1.32) +// * +// * @param callable $callback +// * @param string $fname Caller name +// * @since 1.32 +// */ +// public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ); +// +// /** +// * Alias for onTransactionCommitOrIdle() for backwards-compatibility +// * +// * @param callable $callback +// * @param string $fname +// * @since 1.20 +// * @deprecated Since 1.32 +// */ +// public function onTransactionIdle( callable $callback, $fname = __METHOD__ ); +// +// /** +// * Run a callback before the current transaction commits or now if there is none. +// * If there is a transaction and it is rolled back, then the callback is cancelled. +// * +// * When transaction round mode (DBO_TRX) is set, the callback will run at the end +// * of the round, just before all peer transactions COMMIT. If the transaction round +// * is rolled back, then the callback is cancelled. +// * +// * Callbacks must not start nor commit any transactions. If no transaction is active, +// * then a transaction will wrap the callback. +// * +// * This is useful for updates that easily suffer from lock timeouts and deadlocks, +// * but where atomicity is strongly desired for these updates and some related updates. +// * +// * Updates will execute in the order they were enqueued. +// * +// * The callback takes the one argument: +// * - This IDatabase instance (since 1.32) +// * +// * @param callable $callback +// * @param string $fname Caller name +// * @since 1.22 +// */ +// public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ); +// +// /** +// * Run a callback after each time any transaction commits or rolls back +// * +// * The callback takes two arguments: +// * - IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK +// * - This IDatabase object +// * Callbacks must commit any transactions that they begin. +// * +// * Registering a callback here will not affect writesOrCallbacks() pending. +// * +// * Since callbacks from this or onTransactionCommitOrIdle() can start and end transactions, +// * a single call to IDatabase::commit might trigger multiple runs of the listener callbacks. +// * +// * @param string $name Callback name +// * @param callable|null $callback Use null to unset a listener +// * @since 1.28 +// */ +// public function setTransactionListener( $name, callable $callback = null ); +// +// /** +// * Begin an atomic section of SQL statements +// * +// * Start an implicit transaction if no transaction is already active, set a savepoint +// * (if $cancelable is ATOMIC_CANCELABLE), and track the given section name to enforce +// * that the transaction is not committed prematurely. The end of the section must be +// * signified exactly once, either by endAtomic() or cancelAtomic(). Sections can have +// * have layers of inner sections (sub-sections), but all sections must be ended in order +// * of innermost to outermost. Transactions cannot be started or committed until all +// * atomic sections are closed. +// * +// * ATOMIC_CANCELABLE is useful when the caller needs to handle specific failure cases +// * by discarding the section's writes. This should not be used for failures when: +// * - upsert() could easily be used instead +// * - insert() with IGNORE could easily be used instead +// * - select() with FOR UPDATE could be checked before issuing writes instead +// * - The failure is from code that runs after the first write but doesn't need to +// * - The failures are from contention solvable via onTransactionPreCommitOrIdle() +// * - The failures are deadlocks; the RDBMs usually discard the whole transaction +// * +// * @note callers must use additional measures for situations involving two or more +// * (peer) transactions (e.g. updating two database servers at once). The transaction +// * and savepoint logic of this method only applies to this specific IDatabase instance. +// * +// * Example usage: +// * @code +// * // Start a transaction if there isn't one already +// * $dbw->startAtomic( __METHOD__ ); +// * // Serialize these thread table updates +// * $dbw->select( 'thread', '1', [ 'td_id' => $tid ], __METHOD__, 'FOR UPDATE' ); +// * // Add a new comment for the thread +// * $dbw->insert( 'comment', $row, __METHOD__ ); +// * $cid = $db->insertId(); +// * // Update thread reference to last comment +// * $dbw->update( 'thread', [ 'td_latest' => $cid ], [ 'td_id' => $tid ], __METHOD__ ); +// * // Demark the end of this conceptual unit of updates +// * $dbw->endAtomic( __METHOD__ ); +// * @endcode +// * +// * Example usage (atomic changes that might have to be discarded): +// * @code +// * // Start a transaction if there isn't one already +// * $sectionId = $dbw->startAtomic( __METHOD__, $dbw::ATOMIC_CANCELABLE ); +// * // Create new record metadata row +// * $dbw->insert( 'records', $row, __METHOD__ ); +// * // Figure out where to store the data based on the new row's ID +// * $path = $recordDirectory . '/' . $dbw->insertId(); +// * // Write the record data to the storage system +// * $status = $fileBackend->create( [ 'dst' => $path, 'content' => $data ] ); +// * if ( $status->isOK() ) { +// * // Try to cleanup files orphaned by transaction rollback +// * $dbw->onTransactionResolution( +// * function ( $type ) use ( $fileBackend, $path ) { +// * if ( $type === IDatabase::TRIGGER_ROLLBACK ) { +// * $fileBackend->delete( [ 'src' => $path ] ); +// * } +// * }, +// * __METHOD__ +// * ); +// * // Demark the end of this conceptual unit of updates +// * $dbw->endAtomic( __METHOD__ ); +// * } else { +// * // Discard these writes from the transaction (preserving prior writes) +// * $dbw->cancelAtomic( __METHOD__, $sectionId ); +// * } +// * @endcode +// * +// * @since 1.23 +// * @param string $fname +// * @param string $cancelable Pass self::ATOMIC_CANCELABLE to use a +// * savepoint and enable self::cancelAtomic() for this section. +// * @return AtomicSectionIdentifier section ID token +// * @throws DBError +// */ +// public function startAtomic( $fname = __METHOD__, $cancelable = self::ATOMIC_NOT_CANCELABLE ); +// +// /** +// * Ends an atomic section of SQL statements +// * +// * Ends the next section of atomic SQL statements and commits the transaction +// * if necessary. +// * +// * @since 1.23 +// * @see IDatabase::startAtomic +// * @param string $fname +// * @throws DBError +// */ +// public function endAtomic( $fname = __METHOD__ ); +// +// /** +// * Cancel an atomic section of SQL statements +// * +// * This will roll back only the statements executed since the start of the +// * most recent atomic section, and close that section. If a transaction was +// * open before the corresponding startAtomic() call, any statements before +// * that call are *not* rolled back and the transaction remains open. If the +// * corresponding startAtomic() implicitly started a transaction, that +// * transaction is rolled back. +// * +// * @note callers must use additional measures for situations involving two or more +// * (peer) transactions (e.g. updating two database servers at once). The transaction +// * and savepoint logic of startAtomic() are bound to specific IDatabase instances. +// * +// * Note that a call to IDatabase::rollback() will also roll back any open atomic sections. +// * +// * @note As a micro-optimization to save a few DB calls, this method may only +// * be called when startAtomic() was called with the ATOMIC_CANCELABLE flag. +// * @since 1.31 +// * @see IDatabase::startAtomic +// * @param string $fname +// * @param AtomicSectionIdentifier|null $sectionId Section ID from startAtomic(); +// * passing this enables cancellation of unclosed nested sections [optional] +// * @throws DBError +// */ +// public function cancelAtomic( $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null ); +// +// /** +// * Perform an atomic section of reversable SQL statements from a callback +// * +// * The $callback takes the following arguments: +// * - This database object +// * - The value of $fname +// * +// * This will execute the callback inside a pair of startAtomic()/endAtomic() calls. +// * If any exception occurs during execution of the callback, it will be handled as follows: +// * - If $cancelable is ATOMIC_CANCELABLE, cancelAtomic() will be called to back out any +// * (and only) statements executed during the atomic section. If that succeeds, then the +// * exception will be re-thrown; if it fails, then a different exception will be thrown +// * and any further query attempts will fail until rollback() is called. +// * - If $cancelable is ATOMIC_NOT_CANCELABLE, cancelAtomic() will be called to mark the +// * end of the section and the error will be re-thrown. Any further query attempts will +// * fail until rollback() is called. +// * +// * This method is convenient for letting calls to the caller of this method be wrapped +// * in a try/catch blocks for exception types that imply that the caller failed but was +// * able to properly discard the changes it made in the transaction. This method can be +// * an alternative to explicit calls to startAtomic()/endAtomic()/cancelAtomic(). +// * +// * Example usage, "RecordStore::save" method: +// * @code +// * $dbw->doAtomicSection( __METHOD__, function ( $dbw ) use ( $record ) { +// * // Create new record metadata row +// * $dbw->insert( 'records', $record->toArray(), __METHOD__ ); +// * // Figure out where to store the data based on the new row's ID +// * $path = $this->recordDirectory . '/' . $dbw->insertId(); +// * // Write the record data to the storage system; +// * // blob store throughs StoreFailureException on failure +// * $this->blobStore->create( $path, $record->getJSON() ); +// * // Try to cleanup files orphaned by transaction rollback +// * $dbw->onTransactionResolution( +// * function ( $type ) use ( $path ) { +// * if ( $type === IDatabase::TRIGGER_ROLLBACK ) { +// * $this->blobStore->delete( $path ); +// * } +// * }, +// * __METHOD__ +// * ); +// * }, $dbw::ATOMIC_CANCELABLE ); +// * @endcode +// * +// * Example usage, caller of the "RecordStore::save" method: +// * @code +// * $dbw->startAtomic( __METHOD__ ); +// * // ...various SQL writes happen... +// * try { +// * $recordStore->save( $record ); +// * } catch ( StoreFailureException $e ) { +// * // ...various SQL writes happen... +// * } +// * // ...various SQL writes happen... +// * $dbw->endAtomic( __METHOD__ ); +// * @endcode +// * +// * @see Database::startAtomic +// * @see Database::endAtomic +// * @see Database::cancelAtomic +// * +// * @param string $fname Caller name (usually __METHOD__) +// * @param callable $callback Callback that issues DB updates +// * @param string $cancelable Pass self::ATOMIC_CANCELABLE to use a +// * savepoint and enable self::cancelAtomic() for this section. +// * @return mixed $res Result of the callback (since 1.28) +// * @throws DBError +// * @throws RuntimeException +// * @since 1.27; prior to 1.31 this did a rollback() instead of +// * cancelAtomic(), and assumed no callers up the stack would ever try to +// * catch the exception. +// */ +// public function doAtomicSection( +// $fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE +// ); +// +// /** +// * Begin a transaction. If a transaction is already in progress, +// * that transaction will be committed before the new transaction is started. +// * +// * Only call this from code with outer transcation scope. +// * See https://www.mediawiki.org/wiki/Database_transactions for details. +// * Nesting of transactions is not supported. +// * +// * Note that when the DBO_TRX flag is set (which is usually the case for web +// * requests, but not for maintenance scripts), any previous database query +// * will have started a transaction automatically. +// * +// * Nesting of transactions is not supported. Attempts to nest transactions +// * will cause a warning, unless the current transaction was started +// * automatically because of the DBO_TRX flag. +// * +// * @param string $fname Calling function name +// * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional] +// * @throws DBError +// */ +// public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ); +// +// /** +// * Commits a transaction previously started using begin(). +// * If no transaction is in progress, a warning is issued. +// * +// * Only call this from code with outer transcation scope. +// * See https://www.mediawiki.org/wiki/Database_transactions for details. +// * Nesting of transactions is not supported. +// * +// * @param string $fname +// * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_* +// * constant to disable warnings about explicitly committing implicit transactions, +// * or calling commit when no transaction is in progress. +// * +// * This will trigger an exception if there is an ongoing explicit transaction. +// * +// * Only set the flush flag if you are sure that these warnings are not applicable, +// * and no explicit transactions are open. +// * +// * @throws DBError +// */ +// public function commit( $fname = __METHOD__, $flush = '' ); +// +// /** +// * Rollback a transaction previously started using begin(). +// * If no transaction is in progress, a warning is issued. +// * +// * Only call this from code with outer transcation scope. +// * See https://www.mediawiki.org/wiki/Database_transactions for details. +// * Nesting of transactions is not supported. If a serious unexpected error occurs, +// * throwing an Exception is preferrable, using a pre-installed error handler to trigger +// * rollback (in any case, failure to issue COMMIT will cause rollback server-side). +// * +// * Query, connection, and onTransaction* callback errors will be suppressed and logged. +// * +// * @param string $fname Calling function name +// * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_* +// * constant to disable warnings about calling rollback when no transaction is in +// * progress. This will silently break any ongoing explicit transaction. Only set the +// * flush flag if you are sure that it is safe to ignore these warnings in your context. +// * @throws DBError +// * @since 1.23 Added $flush parameter +// */ +// public function rollback( $fname = __METHOD__, $flush = '' ); +// +// /** +// * Commit any transaction but error out if writes or callbacks are pending +// * +// * This is intended for clearing out REPEATABLE-READ snapshots so that callers can +// * see a new point-in-time of the database. This is useful when one of many transaction +// * rounds finished and significant time will pass in the script's lifetime. It is also +// * useful to call on a replica DB after waiting on replication to catch up to the master. +// * +// * @param string $fname Calling function name +// * @throws DBError +// * @since 1.28 +// */ +// public function flushSnapshot( $fname = __METHOD__ ); +// +// /** +// * Convert a timestamp in one of the formats accepted by wfTimestamp() +// * to the format used for inserting into timestamp fields in this DBMS. +// * +// * The result is unquoted, and needs to be passed through addQuotes() +// * before it can be included in raw SQL. +// * +// * @param string|int $ts +// * +// * @return string +// */ +// public function timestamp( $ts = 0 ); +// +// /** +// * Convert a timestamp in one of the formats accepted by wfTimestamp() +// * to the format used for inserting into timestamp fields in this DBMS. If +// * NULL is input, it is passed through, allowing NULL values to be inserted +// * into timestamp fields. +// * +// * The result is unquoted, and needs to be passed through addQuotes() +// * before it can be included in raw SQL. +// * +// * @param string|int|null $ts +// * +// * @return string +// */ +// public function timestampOrNull( $ts = null ); +// +// /** +// * Ping the server and try to reconnect if it there is no connection +// * +// * @param float|null &$rtt Value to store the estimated RTT [optional] +// * @return bool Success or failure +// */ +// public function ping( &$rtt = null ); +// +// /** +// * Get the amount of replication lag for this database server +// * +// * Callers should avoid using this method while a transaction is active +// * +// * @return int|bool Database replication lag in seconds or false on error +// * @throws DBError +// */ +// public function getLag(); +// +// /** +// * Get the replica DB lag when the current transaction started +// * or a general lag estimate if not transaction is active +// * +// * This is useful when transactions might use snapshot isolation +// * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data +// * is this lag plus transaction duration. If they don't, it is still +// * safe to be pessimistic. In AUTOCOMMIT mode, this still gives an +// * indication of the staleness of subsequent reads. +// * +// * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN) +// * @throws DBError +// * @since 1.27 +// */ +// public function getSessionLagStatus(); +// +// /** +// * Return the maximum number of items allowed in a list, or 0 for unlimited. +// * +// * @return int +// */ +// public function maxListLen(); +// +// /** +// * Some DBMSs have a special format for inserting into blob fields, they +// * don't allow simple quoted strings to be inserted. To insert into such +// * a field, pass the data through this function before passing it to +// * IDatabase::insert(). +// * +// * @param string $b +// * @return string|Blob +// */ +// public function encodeBlob( $b ); +// +// /** +// * Some DBMSs return a special placeholder object representing blob fields +// * in result objects. Pass the object through this function to return the +// * original string. +// * +// * @param string|Blob $b +// * @return string +// */ +// public function decodeBlob( $b ); +// +// /** +// * Override database's default behavior. $options include: +// * 'connTimeout' : Set the connection timeout value in seconds. +// * May be useful for very long batch queries such as +// * full-wiki dumps, where a single query reads out over +// * hours or days. +// * +// * @param array $options +// * @return void +// * @throws DBError +// */ +// public function setSessionOptions( array $options ); +// +// /** +// * Set variables to be used in sourceFile/sourceStream, in preference to the +// * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at +// * all. If it's set to false, $GLOBALS will be used. +// * +// * @param bool|array $vars Mapping variable name to value. +// */ +// public function setSchemaVars( $vars ); +// +// /** +// * Check to see if a named lock is not locked by any thread (non-blocking) +// * +// * @param string $lockName Name of lock to poll +// * @param string $method Name of method calling us +// * @return bool +// * @throws DBError +// * @since 1.20 +// */ +// public function lockIsFree( $lockName, $method ); +// +// /** +// * Acquire a named lock +// * +// * Named locks are not related to transactions +// * +// * @param string $lockName Name of lock to aquire +// * @param string $method Name of the calling method +// * @param int $timeout Acquisition timeout in seconds +// * @return bool +// * @throws DBError +// */ +// public function lock( $lockName, $method, $timeout = 5 ); +// +// /** +// * Release a lock +// * +// * Named locks are not related to transactions +// * +// * @param string $lockName Name of lock to release +// * @param string $method Name of the calling method +// * +// * @return int Returns 1 if the lock was released, 0 if the lock was not established +// * by this thread (in which case the lock is not released), and NULL if the named lock +// * did not exist +// * +// * @throws DBError +// */ +// public function unlock( $lockName, $method ); +// +// /** +// * Acquire a named lock, flush any transaction, and return an RAII style unlocker object +// * +// * Only call this from outer transcation scope and when only one DB will be affected. +// * See https://www.mediawiki.org/wiki/Database_transactions for details. +// * +// * This is suitiable for transactions that need to be serialized using cooperative locks, +// * where each transaction can see each others' changes. Any transaction is flushed to clear +// * out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope, +// * the lock will be released unless a transaction is active. If one is active, then the lock +// * will be released when it either commits or rolls back. +// * +// * If the lock acquisition failed, then no transaction flush happens, and null is returned. +// * +// * @param string $lockKey Name of lock to release +// * @param string $fname Name of the calling method +// * @param int $timeout Acquisition timeout in seconds +// * @return ScopedCallback|null +// * @throws DBError +// * @since 1.27 +// */ +// public function getScopedLockAndFlush( $lockKey, $fname, $timeout ); +// +// /** +// * Check to see if a named lock used by lock() use blocking queues +// * +// * @return bool +// * @since 1.26 +// */ +// public function namedLocksEnqueue(); +// +// /** +// * Find out when 'infinity' is. Most DBMSes support this. This is a special +// * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well +// * because "i" sorts after all numbers. +// * +// * @return string +// */ +// public function getInfinity(); +// +// /** +// * Encode an expiry time into the DBMS dependent format +// * +// * @param string $expiry Timestamp for expiry, or the 'infinity' string +// * @return string +// */ +// public function encodeExpiry( $expiry ); +// +// /** +// * Decode an expiry time into a DBMS independent format +// * +// * @param string $expiry DB timestamp field value for expiry +// * @param int $format TS_* constant, defaults to TS_MW +// * @return string +// */ +// public function decodeExpiry( $expiry, $format = TS_MW ); +// +// /** +// * Allow or deny "big selects" for this session only. This is done by setting +// * the sql_big_selects session variable. +// * +// * This is a MySQL-specific feature. +// * +// * @param bool|string $value True for allow, false for deny, or "default" to +// * restore the initial value +// */ +// public function setBigSelects( $value = true ); +// +// /** +// * @return bool Whether this DB is read-only +// * @since 1.27 +// */ +// public function isReadOnly(); +// +// /** +// * Make certain table names use their own database, schema, and table prefix +// * when passed into SQL queries pre-escaped and without a qualified database name +// * +// * For example, "user" can be converted to "myschema.mydbname.user" for convenience. +// * Appearances like `user`, somedb.user, somedb.someschema.user will used literally. +// * +// * Calling this twice will completely clear any old table aliases. Also, note that +// * callers are responsible for making sure the schemas and databases actually exist. +// * +// * @param array[] $aliases Map of (table => (dbname, schema, prefix) map) +// * @since 1.28 +// */ +// public function setTableAliases( array $aliases ); +// +// /** +// * Convert certain index names to alternative names before querying the DB +// * +// * Note that this applies to indexes regardless of the table they belong to. +// * +// * This can be employed when an index was renamed X => Y in code, but the new Y-named +// * indexes were not yet built on all DBs. After all the Y-named ones are added by the DBA, +// * the aliases can be removed, and then the old X-named indexes dropped. +// * +// * @param string[] $aliases +// * @since 1.31 +// */ +// public function setIndexAliases( array $aliases ); +} + +///** +// * @deprecated since 1.29 +// */ +//class_alias( IDatabase::class, 'IDatabase' ); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkRenderer.java b/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkRenderer.java index f86ca1006..34e098ccf 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkRenderer.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkRenderer.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.linkers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.langs.htmls.*; import gplx.xowa.mediawiki.includes.xohtml.*; @@ -136,7 +136,7 @@ public class XomwLinkRenderer { * @return String */ public void makeLink(Bry_bfr bfr, - XomwTitle target, byte[] text, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { + XomwTitleOld target, byte[] text, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { // $title = Title::newFromLinkTarget($target); // does db lookup? if (target.isKnown()) { this.makeKnownLink(bfr, target, text, extraAttribs, query); @@ -238,7 +238,7 @@ public class XomwLinkRenderer { * @return String */ public void makePreloadedLink(Bry_bfr bfr, - XomwTitle target, byte[] text, byte[] classes, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { + XomwTitleOld target, byte[] text, byte[] classes, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { // XO.MW.HOOK: this.runBeginHook --> 'HtmlPageLinkRendererBegin', 'LinkBegin' target = this.normalizeTarget(target); @@ -269,7 +269,7 @@ public class XomwLinkRenderer { * @return String */ public void makeKnownLink(Bry_bfr bfr, - XomwTitle target, byte[] text, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { + XomwTitleOld target, byte[] text, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { byte[] classes = Bry_.Empty; if (target.isExternal()) { classes = Bry__classes__extiw; @@ -295,7 +295,7 @@ public class XomwLinkRenderer { * @return String */ public void makeBrokenLink(Bry_bfr bfr, - XomwTitle target, byte[] text, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { + XomwTitleOld target, byte[] text, Xomw_atr_mgr extraAttribs, Xomw_qry_mgr query) { // XO.MW.HOOK: Run legacy hook // We don't want to include fragments for broken links, because they @@ -341,7 +341,7 @@ public class XomwLinkRenderer { * @param boolean $isKnown * @return null|String */ - private void buildAElement(Bry_bfr bfr, XomwTitle target, byte[] text, Xomw_atr_mgr attribs, boolean isKnown) { + private void buildAElement(Bry_bfr bfr, XomwTitleOld target, byte[] text, Xomw_atr_mgr attribs, boolean isKnown) { // XO.MW.HOOK:HtmlPageLinkRendererEnd byte[] htmlBry = text; @@ -357,7 +357,7 @@ public class XomwLinkRenderer { * @return String non-escaped text */ // XO.MW:SYNC:1.29; DATE:2017-01-31 - private byte[] getLinkText(XomwTitle target) { + private byte[] getLinkText(XomwTitleOld target) { byte[] prefixed_text = target.getPrefixedText(); // If the target is just a fragment, with no title, we return the fragment // text. Otherwise, we return the title text itself. @@ -367,7 +367,7 @@ public class XomwLinkRenderer { return prefixed_text; } - private byte[] getLinkUrl(XomwTitle target, Xomw_qry_mgr query) { + private byte[] getLinkUrl(XomwTitleOld target, Xomw_qry_mgr query) { // TODO: Use a LinkTargetResolver service instead of Title // $title = Title::newFromLinkTarget($target); // if (this.forceArticlePath) { @@ -392,7 +392,7 @@ public class XomwLinkRenderer { * @param LinkTarget $target * @return LinkTarget */ - private XomwTitle normalizeTarget(XomwTitle target) { + private XomwTitleOld normalizeTarget(XomwTitleOld target) { return XomwLinker.normaliseSpecialPage(target); } @@ -448,7 +448,7 @@ public class XomwLinkRenderer { * @param LinkTarget $target * @return String CSS class */ - public byte[] getLinkClasses(XomwTitle target) { + public byte[] getLinkClasses(XomwTitleOld target) { // Make sure the target is in the cache // $id = this.linkCache->addLinkObj($target); // if ($id == 0) { diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkTarget.java b/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkTarget.java new file mode 100644 index 000000000..591d16dc1 --- /dev/null +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/linkers/XomwLinkTarget.java @@ -0,0 +1,112 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.xowa.mediawiki.includes.linkers; + +// MW.SRC:v1.33.1 +/** + * @since 1.27 + */ +public interface XomwLinkTarget { +// +// /** +// * Get the namespace index. +// * @since 1.27 +// * +// * @return int Namespace index +// */ +// public int getNamespace(); +// +// /** +// * Convenience function to test if it is in the namespace +// * @since 1.27 +// * +// * @param int $ns +// * @return bool +// */ +// public boolean inNamespace(int ns); +// +// /** +// * Get the link fragment (i.e. the bit after the #) in text form. +// * @since 1.27 +// * +// * @return string link fragment +// */ +// public String getFragment(); +// +// /** +// * Whether the link target has a fragment +// * @since 1.27 +// * +// * @return bool +// */ +// public boolean hasFragment(); +// +// /** +// * Get the main part with underscores. +// * @since 1.27 +// * +// * @return string Main part of the link, with underscores (for use in href attributes) +// */ +// public String getDBkey(); +// +// /** +// * Returns the link in text form, without namespace prefix or fragment. +// * This is computed from the DB key by replacing any underscores with spaces. +// * @since 1.27 +// * +// * @return string +// */ +// public String getText(); +// +// /** +// * Creates a new LinkTarget for a different fragment of the same page. +// * It is expected that the same type of object will be returned, but the +// * only requirement is that it is a LinkTarget. +// * @since 1.27 +// * +// * @param string $fragment The fragment name, or "" for the entire page. +// * +// * @return LinkTarget +// */ +// public XomwLinkTarget createFragmentTarget(String fragment); +// +// /** +// * Whether this LinkTarget has an interwiki component +// * @since 1.27 +// * +// * @return bool +// */ +// public boolean isExternal(); +// +// /** +// * The interwiki component of this LinkTarget +// * @since 1.27 +// * +// * @return string +// */ +// public String getInterwiki(); +// +// /** +// * Returns an informative human readable representation of the link target, +// * for use in logging and debugging. There is no requirement for the return +// * value to have any relationship with the input of TitleParser. +// * @since 1.31 +// * +// * @return string +// */ +// public String __toString(); + +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/media/XomwImageHandler_tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/media/XomwImageHandler_tst.java index 6108b6445..8ec0344cb 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/media/XomwImageHandler_tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/media/XomwImageHandler_tst.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.media; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import org.junit.*; import gplx.core.tests.*; import gplx.xowa.mediawiki.includes.parsers.*; import gplx.xowa.mediawiki.includes.parsers.lnkis.*; @@ -46,7 +46,7 @@ class XomwImageHandler_fxt { return rv; } public void Init__file(String title, int w, int h) { - this.file = new XomwLocalFile(env, XomwTitle.newFromText(env, Bry_.new_u8(title)), repo, w, h, XomwMediaHandlerFactory.Mime__image__png); + this.file = new XomwLocalFile(env, XomwTitleOld.newFromText(env, Bry_.new_u8(title)), repo, w, h, XomwMediaHandlerFactory.Mime__image__png); } public void Test__normaliseParams(Xomw_params_handler prms, Xomw_params_handler expd) { // exec diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiCategoryPage.java b/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiCategoryPage.java index c737cc77c..bc364dc29 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiCategoryPage.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiCategoryPage.java @@ -1,24 +1,24 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.page; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; // MW.SRC:1.33.1 /** * Special handling for category pages */ -public class XomwWikiCategoryPage extends XomwWikiPage { public XomwWikiCategoryPage(XomwTitle title) {super(title); +public class XomwWikiCategoryPage extends XomwWikiPage { public XomwWikiCategoryPage(XomwTitleOld title) {super(title); } // // /** diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiFilePage.java b/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiFilePage.java index c662117c9..ae4a8d9e7 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiFilePage.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiFilePage.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.page; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; // MW.SRC:1.33.1 /** @@ -29,7 +29,7 @@ public class XomwWikiFilePage extends XomwWikiPage { // /** @var File */ // /** @var array */ // protected $mDupes = null; - public XomwWikiFilePage(XomwTitle title) {super(title); + public XomwWikiFilePage(XomwTitleOld title) {super(title); // $this->mDupes = null; // $this->mRepo = null; } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiPage.java b/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiPage.java index 426b877d6..26730a545 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiPage.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/page/XomwWikiPage.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.page; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; // MW.SRC:1.33.1 import gplx.xowa.mediawiki.includes.content.*; @@ -31,7 +31,7 @@ public class XomwWikiPage implements XomwPage, XomwIDBAccessObject { /** * @var Title */ - public XomwTitle mTitle = null; + public XomwTitleOld mTitle = null; /** * @var boolean @@ -98,7 +98,7 @@ public class XomwWikiPage implements XomwPage, XomwIDBAccessObject { * Constructor and clear the article * @param Title title Reference to a Title Object. */ - public XomwWikiPage(XomwTitle title) { + public XomwWikiPage(XomwTitleOld title) { this.mTitle = title; } @@ -118,7 +118,7 @@ public class XomwWikiPage implements XomwPage, XomwIDBAccessObject { * @throws MWException * @return WikiPage|WikiCategoryPage|WikiFilePage */ - public static XomwWikiPage factory(XomwTitle title) { + public static XomwWikiPage factory(XomwTitleOld title) { int ns = title.getNamespace(); if (ns == XomwDefines.NS_MEDIA) { diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray.java index f0f14429d..54ce8bd9a 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.langs.htmls.*; import gplx.xowa.mediawiki.includes.xohtml.*; @@ -227,7 +227,7 @@ public class XomwLinkHolderArray { * @param String $prefix [optional] * @return String */ - public void makeHolder(Bry_bfr bfr, XomwTitle nt, byte[] text, byte[][] query, byte[] trail, byte[] prefix) { + public void makeHolder(Bry_bfr bfr, XomwTitleOld nt, byte[] text, byte[][] query, byte[] trail, byte[] prefix) { if (nt == null) { // Fail gracefully bfr.Add_str_a7("<!-- ERROR -->").Add(prefix).Add(text).Add(trail); @@ -739,7 +739,7 @@ public class XomwLinkHolderArray { // $this->doVariants( $colours ); // } // } - public void Test__add(XomwTitle ttl, byte[] capt) { + public void Test__add(XomwTitleOld ttl, byte[] capt) { int key = parent.nextLinkID(); XomwLinkHolderItem item = new XomwLinkHolderItem(ttl, capt, Bry_.Ary_empty); internals.Add(key, item); @@ -767,12 +767,12 @@ class XomwLinkHolderList { public XomwLinkHolderItem Get_by(int key) {return ary[key];} } class XomwLinkHolderItem { - public XomwLinkHolderItem(XomwTitle title, byte[] text, byte[][] query) { + public XomwLinkHolderItem(XomwTitleOld title, byte[] text, byte[][] query) { this.title = title; this.text = text; this.query = query; } - public XomwTitle Title() {return title;} private final XomwTitle title; + public XomwTitleOld Title() {return title;} private final XomwTitleOld title; public byte[] Text() {return text;} private final byte[] text; public byte[] Pdbk() {return title.getPrefixedDBkey();} public byte[][] Query() {return query;} private final byte[][] query; diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray_tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray_tst.java index 5d9dfb5f9..5e9193969 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray_tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwLinkHolderArray_tst.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import org.junit.*; import gplx.core.tests.*; import gplx.xowa.mediawiki.includes.linkers.*; @@ -34,7 +34,7 @@ class XomwLinkHolderArray_fxt { this.holders = new XomwLinkHolderArray(parser); } public void Init__add(String ttl, String capt) { - holders.Test__add(XomwTitle.newFromText(env, Bry_.new_u8(ttl)), Bry_.new_u8(capt)); + holders.Test__add(XomwTitleOld.newFromText(env, Bry_.new_u8(ttl)), Bry_.new_u8(capt)); } public void Test__replace(String src, String expd) { if (apos) expd = gplx.langs.htmls.Gfh_utl.Replace_apos(expd); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser.java index bbc502b7b..430a82698 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.core.btries.*; import gplx.core.net.*; @@ -28,6 +28,9 @@ import gplx.xowa.mediawiki.includes.parsers.lnkes.*; import gplx.xowa.mediawiki.includes.parsers.magiclinks.*; import gplx.xowa.mediawiki.includes.parsers.nbsps.*; import gplx.xowa.mediawiki.includes.parsers.preprocessors.*; + +import static gplx.xowa.mediawiki.includes.XomwDefines.NS_MEDIAWIKI; + /** * PHP Parser - Processes wiki markup (which uses a more user-friendly * syntax, such as "[[link]]" for making links), and provides a one-way @@ -212,7 +215,7 @@ public class XomwParser implements XomwParserIface { /** * @var Title */ - public XomwTitle mTitle; // Title context, used for self-link rendering and similar things + public XomwTitleOld mTitle; // Title context, used for self-link rendering and similar things public int mOutputType; // Output type, one of the OT_xxx constants public XophpArray ot; // Shortcut alias, see setOutputType() // public mRevisionObject; // The revision Object of the specified revision ID @@ -240,7 +243,7 @@ public class XomwParser implements XomwParserIface { // * @var MapCacheLRU|null // * @since 1.24 // * -// * A cache of the current revisions of titles. Keys are $title->getPrefixedDbKey() +// * A cache of the current revisions of titles. Keys are title.getPrefixedDbKey() // */ // public $currentRevisionCache; // @@ -284,6 +287,271 @@ public class XomwParser implements XomwParserIface { public XomwSanitizer Sanitizer() {return sanitizer;} public XomwStripState Strip_state() {return mStripState;} +// MW.SRC:1.33.1 +// /** +// * Update this version number when the ParserOutput format +// * changes in an incompatible way, so the parser cache +// * can automatically discard old data. +// */ +// const VERSION = '1.6.4'; +// +// /** +// * Update this version number when the output of serialiseHalfParsedText() +// * changes in an incompatible way +// */ +// const HALF_PARSED_VERSION = 2; +// +// // Flags for Parser::setFunctionHook +// const SFH_NO_HASH = 1; +// const SFH_OBJECT_ARGS = 2; +// +// // Constants needed for external link processing +// // Everything except bracket, space, or control characters +// // \p{Zs} is unicode 'separator, space' category. It covers the space 0x20 +// // as well as U+3000 is IDEOGRAPHIC SPACE for T21052 +// // \x{FFFD} is the Unicode replacement character, which Preprocessor_DOM +// // uses to replace invalid HTML characters. +// const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]'; +// // Simplified expression to match an IPv4 or IPv6 address, or +// // at least one character of a host name (embeds EXT_LINK_URL_CLASS) +// const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}])'; +// // RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR) +// // phpcs:ignore Generic.Files.LineLength +// const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}\x{FFFD}]+) +// \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu'; +// +// // Regular expression for a non-newline space +// const SPACE_NOT_NL = '(?:\t| |&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})'; +// +// // Flags for preprocessToDom +// const PTD_FOR_INCLUSION = 1; +// +// // Allowed values for $this.mOutputType +// // Parameter to startExternalParse(). +// const OT_HTML = 1; # like parse() +// const OT_WIKI = 2; # like preSaveTransform() +// const OT_PREPROCESS = 3; # like preprocess() +// const OT_MSG = 3; +// const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged. +// +// /** +// * @var string Prefix and suffix for temporary replacement strings +// * for the multipass parser. +// * +// * \x7f should never appear in input as it's disallowed in XML. +// * Using it at the front also gives us a little extra robustness +// * since it shouldn't match when butted up against identifier-like +// * string constructs. +// * +// * Must not consist of all title characters, or else it will change +// * the behavior of <nowiki> in a link. +// * +// * Must have a character that needs escaping in attributes, otherwise +// * someone could put a strip marker in an attribute, to get around +// * escaping quote marks, and break out of the attribute. Thus we add +// * `'". +// */ +// const MARKER_SUFFIX = "-QINU`\"'\x7f"; +// const MARKER_PREFIX = "\x7f'\"`UNIQ-"; +// +// // Markers used for wrapping the table of contents +// const TOC_START = '<mw:toc>'; +// const TOC_END = '</mw:toc>'; +// +// /** @var int Assume that no output will later be saved this many seconds after parsing */ +// const MAX_TTS = 900; +// +// // Persistent: +// public $mTagHooks = []; +// public $mTransparentTagHooks = []; +// public $mFunctionHooks = []; +// public $mFunctionSynonyms = [ 0 => [], 1 => [] ]; +// public $mFunctionTagHooks = []; +// public $mStripList = []; +// public $mDefaultStripList = []; +// public $mVarCache = []; +// public $mImageParams = []; +// public $mImageParamsMagicArray = []; +// public $mMarkerIndex = 0; +// /** +// * @var bool Whether firstCallInit still needs to be called +// */ +// public $mFirstCall = true; +// +// // Initialised by initialiseVariables() +// +// /** +// * @var MagicWordArray +// */ +// public $mVariables; +// +// /** +// * @var MagicWordArray +// */ +// public $mSubstWords; +// // Initialised in constructor +// public $mConf, $mExtLinkBracketedRegex, $mUrlProtocols; +// +// // Initialized in getPreprocessor() +// /** @var Preprocessor */ +// public $mPreprocessor; +// +// // Cleared with clearState(): +// /** +// * @var ParserOutput +// */ +// public $mOutput; +// public $mAutonumber; +// +// /** +// * @var StripState +// */ +// public $mStripState; +// +// public $mIncludeCount; +// /** +// * @var LinkHolderArray +// */ +// public $mLinkHolders; +// +// public $mLinkID; +// public $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth; +// public $mDefaultSort; +// public $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores; +// public $mExpensiveFunctionCount; # number of expensive parser function calls +// public $mShowToc, $mForceTocPosition; +// +// /** +// * @var User +// */ +// public $mUser; # User object; only used when doing pre-save transform +// +// // Temporary +// // These are variables reset at least once per parse regardless of $clearState +// +// /** +// * @var ParserOptions +// */ +// public $mOptions; +// +// /** +// * @var Title +// */ +// public $mTitle; # Title context, used for self-link rendering and similar things +// public $mOutputType; # Output type, one of the OT_xxx constants +// public $ot; # Shortcut alias, see setOutputType() +// public $mRevisionObject; # The revision object of the specified revision ID +// public $mRevisionId; # ID to display in {{REVISIONID}} tags +// public $mRevisionTimestamp; # The timestamp of the specified revision ID +// public $mRevisionUser; # User to display in {{REVISIONUSER}} tag +// public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable +// public $mRevIdForTs; # The revision ID which was used to fetch the timestamp +// public $mInputSize = false; # For {{PAGESIZE}} on current page. +// +// /** +// * @var string Deprecated accessor for the strip marker prefix. +// * @deprecated since 1.26; use Parser::MARKER_PREFIX instead. +// */ +// public $mUniqPrefix = self::MARKER_PREFIX; +// +// /** +// * @var array Array with the language name of each language link (i.e. the +// * interwiki prefix) in the key, value arbitrary. Used to avoid sending +// * duplicate language links to the ParserOutput. +// */ +// public $mLangLinkLanguages; +// +// /** +// * @var MapCacheLRU|null +// * @since 1.24 +// * +// * A cache of the current revisions of titles. Keys are title.getPrefixedDbKey() +// */ +// public $currentRevisionCache; +// +// /** +// * @var bool|string Recursive call protection. +// * This variable should be treated as if it were private. +// */ +// public $mInParse = false; +// +// /** @var SectionProfiler */ +// protected $mProfiler; +// +// /** +// * @var LinkRenderer +// */ +// protected $mLinkRenderer; +// +// /** @var MagicWordFactory */ +// private $magicWordFactory; +// +// /** @var Language */ +// private $contLang; +// +// /** @var ParserFactory */ +// private $factory; +// +// /** @var SpecialPageFactory */ +// private $specialPageFactory; +// +// /** @var Config */ +// private $siteConfig; +// +// /** @var LinkRendererFactory */ +// private $linkRendererFactory; +// +// /** +// * @param array $parserConf See $wgParserConf documentation +// * @param MagicWordFactory|null $magicWordFactory +// * @param Language|null $contLang Content language +// * @param ParserFactory|null $factory +// * @param string|null $urlProtocols As returned from wfUrlProtocols() +// * @param SpecialPageFactory|null $spFactory +// * @param Config|null $siteConfig +// * @param LinkRendererFactory|null $linkRendererFactory +// */ +// public function __construct( +// array $parserConf = [], MagicWordFactory $magicWordFactory = null, +// Language $contLang = null, ParserFactory $factory = null, $urlProtocols = null, +// SpecialPageFactory $spFactory = null, Config $siteConfig = null, +// LinkRendererFactory $linkRendererFactory = null +// ) { +// $this.mConf = $parserConf; +// $this.mUrlProtocols = $urlProtocols ?? wfUrlProtocols(); +// $this.mExtLinkBracketedRegex = '/\[(((?i)' . $this.mUrlProtocols . ')' . +// self::EXT_LINK_ADDR . +// self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su'; +// if (isset($parserConf['preprocessorClass'])) { +// $this.mPreprocessorClass = $parserConf['preprocessorClass']; +// } elseif (wfIsHHVM()) { +// // Under HHVM Preprocessor_Hash is much faster than Preprocessor_DOM +// $this.mPreprocessorClass = Preprocessor_Hash::class; +// } elseif (extension_loaded('domxml')) { +// // PECL extension that conflicts with the core DOM extension (T15770) +// wfDebug("Warning: you have the obsolete domxml extension for PHP. Please remove it!\n"); +// $this.mPreprocessorClass = Preprocessor_Hash::class; +// } elseif (extension_loaded('dom')) { +// $this.mPreprocessorClass = Preprocessor_DOM::class; +// } else { +// $this.mPreprocessorClass = Preprocessor_Hash::class; +// } +// wfDebug(__CLASS__ . ": using preprocessor: {$this.mPreprocessorClass}\n"); +// +// $services = MediaWikiServices::getInstance(); +// $this.magicWordFactory = $magicWordFactory ?? +// $services.getMagicWordFactory(); +// +// $this.contLang = $contLang ?? $services.getContentLanguage(); +// +// $this.factory = $factory ?? $services.getParserFactory(); +// $this.specialPageFactory = $spFactory ?? $services.getSpecialPageFactory(); +// $this.siteConfig = $siteConfig ?? MediaWikiServices::getInstance().getMainConfig(); +// +// $this.linkRendererFactory = +// $linkRendererFactory ?? MediaWikiServices::getInstance().getLinkRendererFactory(); +// } +// /** * @param array $conf @@ -349,60 +617,113 @@ public class XomwParser implements XomwParserIface { doubleunderWkr.Init_by_wiki(doubleunder_data, wiki.Lang()); magiclinksWkr.Init_by_wiki(); } - public void Init_by_page(XomwTitle ttl) { + public void Init_by_page(XomwTitleOld ttl) { // pctx.Init_by_page(ttl); } - -// /** -// * Reduce memory usage to reduce the impact of circular references -// */ -// public function __destruct() { -// if (isset(this.mLinkHolders)) { -// unset(this.mLinkHolders); -// } -// foreach ($this as $name => $value) { -// unset(this.$name); -// } +// MW.SRC:1.33.1 +// /** +// * Reduce memory usage to reduce the impact of circular references +// */ +// public function __destruct() { +// if (isset($this.mLinkHolders)) { +// unset($this.mLinkHolders); +// } +// foreach ($this as $name => $value) { +// unset($this.$name); +// } +// } +// +// /** +// * Allow extensions to clean up when the parser is cloned +// */ +// public function __clone() { +// $this.mInParse = false; +// +// // T58226: When you create a reference "to" an object field, that +// // makes the object field itself be a reference too (until the other +// // reference goes out of scope). When cloning, any field that's a +// // reference is copied as a reference in the new object. Both of these +// // are defined PHP5 behaviors, as inconvenient as it is for us when old +// // hooks from PHP4 days are passing fields by reference. +// foreach ([ 'mStripState', 'mVarCache' ] as $k) { +// // Make a non-reference copy of the field, then rebind the field to +// // reference the new copy. +// $tmp = $this.$k; +// $this.$k =& $tmp; +// unset($tmp); // } // -// /** -// * Allow extensions to clean up when the parser is cloned -// */ -// public function __clone() { -// this.mInParse = false; -// -// // T58226: When you create a reference "to" an Object field, that -// // makes the Object field itself be a reference too (until the other -// // reference goes out of scope). When cloning, any field that's a -// // reference is copied as a reference in the new Object. Both of these -// // are defined PHP5 behaviors, as inconvenient as it is for us when old -// // hooks from PHP4 days are passing fields by reference. -// foreach ([ 'mStripState', 'mVarCache' ] as $k) { -// // Make a non-reference copy of the field, then rebind the field to -// // reference the new copy. -// $tmp = this.$k; -// this.$k =& $tmp; -// unset($tmp); -// } +// Hooks::run('ParserCloned', [ $this ]); +// } // -// Hooks::run('ParserCloned', [ $this ]); +// /** +// * Do various kinds of initialisation on the first call of the parser +// */ +// public function firstCallInit() { +// if (!$this.mFirstCall) { +// return; +// } +// $this.mFirstCall = false; +// +// CoreParserFunctions::register($this); +// CoreTagHooks::register($this); +// $this.initialiseVariables(); +// +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// Hooks::run('ParserFirstCallInit', [ &$parser ]); +// } +// +// /** +// * Clear Parser state +// * +// * @private +// */ +// public function clearState() { +// $this.firstCallInit(); +// $this.mOutput = new ParserOutput; +// $this.mOptions.registerWatcher([ $this.mOutput, 'recordOption' ]); +// $this.mAutonumber = 0; +// $this.mIncludeCount = []; +// $this.mLinkHolders = new LinkHolderArray($this); +// $this.mLinkID = 0; +// $this.mRevisionObject = $this.mRevisionTimestamp = +// $this.mRevisionId = $this.mRevisionUser = $this.mRevisionSize = null; +// $this.mVarCache = []; +// $this.mUser = null; +// $this.mLangLinkLanguages = []; +// $this.currentRevisionCache = null; +// +// $this.mStripState = new StripState($this); +// +// // Clear these on every parse, T6549 +// $this.mTplRedirCache = $this.mTplDomCache = []; +// +// $this.mShowToc = true; +// $this.mForceTocPosition = false; +// $this.mIncludeSizes = [ +// 'post-expand' => 0, +// 'arg' => 0, +// ]; +// $this.mPPNodeCount = 0; +// $this.mGeneratedPPNodeCount = 0; +// $this.mHighestExpansionDepth = 0; +// $this.mDefaultSort = false; +// $this.mHeadings = []; +// $this.mDoubleUnderscores = []; +// $this.mExpensiveFunctionCount = 0; +// +// // Fix cloning +// if (isset($this.mPreprocessor) && $this.mPreprocessor.parser !== $this) { +// $this.mPreprocessor = null; // } // -// /** -// * Do various kinds of initialisation on the first call of the parser -// */ -// public function firstCallInit() { -// if (!this.mFirstCall) { -// return; -// } -// this.mFirstCall = false; -// -// CoreParserFunctions::register($this); -// CoreTagHooks::register($this); -// this.initialiseVariables(); +// $this.mProfiler = new SectionProfiler(); // -// Hooks::run('ParserFirstCallInit', [ &$this ]); -// } +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// Hooks::run('ParserClearState', [ &$parser ]); +// } /** * Clear Parser state @@ -414,7 +735,7 @@ public class XomwParser implements XomwParserIface { // this.firstCallInit(); // } // this.mOutput = new ParserOutput; -// this.mOptions->registerWatcher([ this.mOutput, 'recordOption' ]); +// this.mOptions.registerWatcher([ this.mOutput, 'recordOption' ]); // this.mAutonumber = 0; // this.mIncludeCount = []; this.mLinkHolders = new XomwLinkHolderArray(this); @@ -446,7 +767,7 @@ public class XomwParser implements XomwParserIface { // this.mExpensiveFunctionCount = 0; // // // Fix cloning -// if (isset(this.mPreprocessor) && this.mPreprocessor->parser !== $this) { +// if (isset(this.mPreprocessor) && this.mPreprocessor.parser !== $this) { // this.mPreprocessor = null; // } // @@ -460,7 +781,7 @@ public class XomwParser implements XomwParserIface { * Do not call this function recursively. * * @param String $text Text we want to parse - * @param Title $title + * @param Title title * @param ParserOptions $options * @param boolean $linestart * @param boolean $clearState @@ -468,12 +789,12 @@ public class XomwParser implements XomwParserIface { * @return ParserOutput A ParserOutput */ // public function parse( -// $text, Title $title, ParserOptions $options, +// $text, Title title, ParserOptions $options, // $linestart = true, $clearState = true, $revid = null // ) { - public void parse(XomwParserBfr pbfr, XomwParserCtx pctx,byte[] text, XomwTitle title, XomwParserOptions options) {this.parse(pbfr, pctx, text, title, options, true, true, -1);} + public void parse(XomwParserBfr pbfr, XomwParserCtx pctx,byte[] text, XomwTitleOld title, XomwParserOptions options) {this.parse(pbfr, pctx, text, title, options, true, true, -1);} public void parse(XomwParserBfr pbfr, XomwParserCtx pctx, - byte[] text, XomwTitle title, XomwParserOptions options, + byte[] text, XomwTitleOld title, XomwParserOptions options, boolean linestart, boolean clearState, int revid ) { /** @@ -490,12 +811,12 @@ public class XomwParser implements XomwParserIface { // magicScopeVariable = this.synchronized(); // } // -// this.startParse($title, $options, XomwParser.OT_HTML, $clearState); +// this.startParse(title, $options, XomwParser.OT_HTML, $clearState); // // this.currentRevisionCache = null; // this.mInputSize = strlen($text); -// if (this.mOptions->getEnableLimitReport()) { -// this.mOutput->resetParseStartTime(); +// if (this.mOptions.getEnableLimitReport()) { +// this.mOutput.resetParseStartTime(); // } // // $oldRevisionId = this.mRevisionId; @@ -526,64 +847,64 @@ public class XomwParser implements XomwParserIface { // * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over // * automatic link conversion. // */ -// if (!($options->getDisableTitleConversion() +// if (!($options.getDisableTitleConversion() // || isset(this.mDoubleUnderscores['nocontentconvert']) // || isset(this.mDoubleUnderscores['notitleconvert']) -// || this.mOutput->getDisplayTitle() !== false) +// || this.mOutput.getDisplayTitle() !== false) // ) { -// $convruletitle = this.getConverterLanguage()->getConvRuleTitle(); +// $convruletitle = this.getConverterLanguage().getConvRuleTitle(); // if ($convruletitle) { -// this.mOutput->setTitleText($convruletitle); +// this.mOutput.setTitleText($convruletitle); // } else { -// $titleText = this.getConverterLanguage()->convertTitle($title); -// this.mOutput->setTitleText($titleText); +// titleText = this.getConverterLanguage().convertTitle(title); +// this.mOutput.setTitleText(titleText); // } // } // // // Done parsing! Compute runtime adaptive expiry if set -// this.mOutput->finalizeAdaptiveCacheExpiry(); +// this.mOutput.finalizeAdaptiveCacheExpiry(); // // // Warn if too many heavyweight parser functions were used -// if (this.mExpensiveFunctionCount > this.mOptions->getExpensiveParserFunctionLimit()) { +// if (this.mExpensiveFunctionCount > this.mOptions.getExpensiveParserFunctionLimit()) { // this.limitationWarn('expensive-parserfunction', // this.mExpensiveFunctionCount, -// this.mOptions->getExpensiveParserFunctionLimit() +// this.mOptions.getExpensiveParserFunctionLimit() // ); // } // // // Information on include size limits, for the benefit of users who try to skirt them -// if (this.mOptions->getEnableLimitReport()) { -// max = this.mOptions->getMaxIncludeSize(); +// if (this.mOptions.getEnableLimitReport()) { +// max = this.mOptions.getMaxIncludeSize(); // -// $cpuTime = this.mOutput->getTimeSinceStart('cpu'); +// $cpuTime = this.mOutput.getTimeSinceStart('cpu'); // if ($cpuTime !== null) { -// this.mOutput->setLimitReportData('limitreport-cputime', +// this.mOutput.setLimitReportData('limitreport-cputime', // sprintf("%.3f", $cpuTime) // ); // } // -// $wallTime = this.mOutput->getTimeSinceStart('wall'); -// this.mOutput->setLimitReportData('limitreport-walltime', +// $wallTime = this.mOutput.getTimeSinceStart('wall'); +// this.mOutput.setLimitReportData('limitreport-walltime', // sprintf("%.3f", $wallTime) // ); // -// this.mOutput->setLimitReportData('limitreport-ppvisitednodes', -// [ this.mPPNodeCount, this.mOptions->getMaxPPNodeCount() ] +// this.mOutput.setLimitReportData('limitreport-ppvisitednodes', +// [ this.mPPNodeCount, this.mOptions.getMaxPPNodeCount() ] // ); -// this.mOutput->setLimitReportData('limitreport-ppgeneratednodes', -// [ this.mGeneratedPPNodeCount, this.mOptions->getMaxGeneratedPPNodeCount() ] +// this.mOutput.setLimitReportData('limitreport-ppgeneratednodes', +// [ this.mGeneratedPPNodeCount, this.mOptions.getMaxGeneratedPPNodeCount() ] // ); -// this.mOutput->setLimitReportData('limitreport-postexpandincludesize', +// this.mOutput.setLimitReportData('limitreport-postexpandincludesize', // [ this.mIncludeSizes['post-expand'], max ] // ); -// this.mOutput->setLimitReportData('limitreport-templateargumentsize', +// this.mOutput.setLimitReportData('limitreport-templateargumentsize', // [ this.mIncludeSizes['arg'], max ] // ); -// this.mOutput->setLimitReportData('limitreport-expansiondepth', -// [ this.mHighestExpansionDepth, this.mOptions->getMaxPPExpandDepth() ] +// this.mOutput.setLimitReportData('limitreport-expansiondepth', +// [ this.mHighestExpansionDepth, this.mOptions.getMaxPPExpandDepth() ] // ); -// this.mOutput->setLimitReportData('limitreport-expensivefunctioncount', -// [ this.mExpensiveFunctionCount, this.mOptions->getExpensiveParserFunctionLimit() ] +// this.mOutput.setLimitReportData('limitreport-expensivefunctioncount', +// [ this.mExpensiveFunctionCount, this.mOptions.getExpensiveParserFunctionLimit() ] // ); // Hooks::run('ParserLimitReportPrepare', [ $this, this.mOutput ]); // @@ -591,25 +912,25 @@ public class XomwParser implements XomwParserIface { // if ($wgShowHostnames) { // $limitReport .= 'Parsed by ' . wfHostname() . "\n"; // } -// $limitReport .= 'Cached time: ' . this.mOutput->getCacheTime() . "\n"; -// $limitReport .= 'Cache expiry: ' . this.mOutput->getCacheExpiry() . "\n"; +// $limitReport .= 'Cached time: ' . this.mOutput.getCacheTime() . "\n"; +// $limitReport .= 'Cache expiry: ' . this.mOutput.getCacheExpiry() . "\n"; // $limitReport .= 'Dynamic content: ' . -// (this.mOutput->hasDynamicContent() ? 'true' : 'false') . +// (this.mOutput.hasDynamicContent() ? 'true' : 'false') . // "\n"; // -// foreach (this.mOutput->getLimitReportData() as $key => $value) { +// foreach (this.mOutput.getLimitReportData() as $key => $value) { // if (Hooks::run('ParserLimitReportFormat', // [ $key, &$value, &$limitReport, false, false ] // )) { -// $keyMsg = wfMessage($key)->inLanguage('en')->useDatabase(false); +// $keyMsg = wfMessage($key).inLanguage('en').useDatabase(false); // $valueMsg = wfMessage([ "$key-value-text", "$key-value" ]) -// ->inLanguage('en')->useDatabase(false); -// if (!$valueMsg->exists()) { +// .inLanguage('en').useDatabase(false); +// if (!$valueMsg.exists()) { // $valueMsg = new RawMessage('$1'); // } -// if (!$keyMsg->isDisabled() && !$valueMsg->isDisabled()) { -// $valueMsg->params($value); -// $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n"; +// if (!$keyMsg.isDisabled() && !$valueMsg.isDisabled()) { +// $valueMsg.params($value); +// $limitReport .= "{$keyMsg.text()}: {$valueMsg.text()}\n"; // } // } // } @@ -621,10 +942,10 @@ public class XomwParser implements XomwParserIface { // // Sanitize for comment. Note '-' in the replacement is U+2010, // // which looks much like the problematic '-'. // $limitReport = str_replace([ '-', '&' ], [ '-', '&' ], $limitReport); -// $text .= "\n<!-- \n$limitReport-->\n"; +// $text .= "\n<!-- \n$limitReport-.\n"; // // // Add on template profiling data in human/machine readable way -// $dataByFunc = this.mProfiler->getFunctionStats(); +// $dataByFunc = this.mProfiler.getFunctionStats(); // uasort($dataByFunc, function ($a, $b) { // return $a['real'] < $b['real']; // descending order // }); @@ -635,27 +956,27 @@ public class XomwParser implements XomwParserIface { // htmlspecialchars($item['name'])); // } // $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n"; -// $text .= implode("\n", $profileReport) . "\n-->\n"; +// $text .= implode("\n", $profileReport) . "\n-.\n"; // -// this.mOutput->setLimitReportData('limitreport-timingprofile', $profileReport); +// this.mOutput.setLimitReportData('limitreport-timingprofile', $profileReport); // // // Add other cache related metadata // if ($wgShowHostnames) { -// this.mOutput->setLimitReportData('cachereport-origin', wfHostname()); +// this.mOutput.setLimitReportData('cachereport-origin', wfHostname()); // } -// this.mOutput->setLimitReportData('cachereport-timestamp', -// this.mOutput->getCacheTime()); -// this.mOutput->setLimitReportData('cachereport-ttl', -// this.mOutput->getCacheExpiry()); -// this.mOutput->setLimitReportData('cachereport-transientcontent', -// this.mOutput->hasDynamicContent()); -// -// if (this.mGeneratedPPNodeCount > this.mOptions->getMaxGeneratedPPNodeCount() / 10) { +// this.mOutput.setLimitReportData('cachereport-timestamp', +// this.mOutput.getCacheTime()); +// this.mOutput.setLimitReportData('cachereport-ttl', +// this.mOutput.getCacheExpiry()); +// this.mOutput.setLimitReportData('cachereport-transientcontent', +// this.mOutput.hasDynamicContent()); +// +// if (this.mGeneratedPPNodeCount > this.mOptions.getMaxGeneratedPPNodeCount() / 10) { // wfDebugLog('generated-pp-node-count', this.mGeneratedPPNodeCount . ' ' . -// this.mTitle->getPrefixedDBkey()); +// this.mTitle.getPrefixedDBkey()); // } // } -// this.mOutput->setText($text); +// this.mOutput.setText($text); // // this.mRevisionId = $oldRevisionId; // this.mRevisionObject = $oldRevisionObject; @@ -668,195 +989,412 @@ public class XomwParser implements XomwParserIface { // return this.mOutput; } -// /** -// * Half-parse wikitext to half-parsed HTML. This recursive parser entry point -// * can be called from an extension tag hook. -// * -// * The output of this function IS NOT SAFE PARSED HTML; it is "half-parsed" -// * instead, which means that lists and links have not been fully parsed yet, -// * and strip markers are still present. -// * -// * Use recursiveTagParseFully() to fully parse wikitext to output-safe HTML. -// * -// * Use this function if you're a parser tag hook and you want to parse -// * wikitext before or after applying additional transformations, and you -// * intend to *return the result as hook output*, which will cause it to go -// * through the rest of parsing process automatically. -// * -// * If $frame is not provided, then template variables (e.g., {{{1}}}) within -// * $text are not expanded -// * -// * @param String $text Text extension wants to have parsed -// * @param boolean|PPFrame $frame The frame to use for expanding any template variables -// * @return String UNSAFE half-parsed HTML -// */ -// public function recursiveTagParse($text, $frame = false) { -// Hooks::run('ParserBeforeStrip', [ &$this, &$text, &this.mStripState ]); -// Hooks::run('ParserAfterStrip', [ &$this, &$text, &this.mStripState ]); -// $text = this.internalParse($text, false, $frame); -// return $text; +// MW.SRC:1.33.1 +// /** +// * Convert wikitext to HTML +// * Do not call this function recursively. +// * +// * @param string $text Text we want to parse +// * @param-taint $text escapes_htmlnoent +// * @param Title title +// * @param ParserOptions $options +// * @param bool $linestart +// * @param bool $clearState +// * @param int|null $revid Number to pass in {{REVISIONID}} +// * @return ParserOutput A ParserOutput +// * @return-taint escaped +// */ +// public function parse( +// $text, Title title, ParserOptions $options, +// $linestart = true, $clearState = true, $revid = null +// ) { +// if ($clearState) { +// // We use U+007F DELETE to construct strip markers, so we have to make +// // sure that this character does not occur in the input text. +// $text = strtr($text, "\x7f", "?"); +// $magicScopeVariable = $this.lock(); // } +// // Strip U+0000 NULL (T159174) +// $text = str_replace("\000", '', $text); // -// /** -// * Fully parse wikitext to fully parsed HTML. This recursive parser entry -// * point can be called from an extension tag hook. -// * -// * The output of this function is fully-parsed HTML that is safe for output. -// * If you're a parser tag hook, you might want to use recursiveTagParse() -// * instead. -// * -// * If $frame is not provided, then template variables (e.g., {{{1}}}) within -// * $text are not expanded -// * -// * @since 1.25 -// * -// * @param String $text Text extension wants to have parsed -// * @param boolean|PPFrame $frame The frame to use for expanding any template variables -// * @return String Fully parsed HTML -// */ -// public function recursiveTagParseFully($text, $frame = false) { -// $text = this.recursiveTagParse($text, $frame); -// $text = this.internalParseHalfParsed($text, false); -// return $text; +// $this.startParse(title, $options, self::OT_HTML, $clearState); +// +// $this.currentRevisionCache = null; +// $this.mInputSize = strlen($text); +// if ($this.mOptions.getEnableLimitReport()) { +// $this.mOutput.resetParseStartTime(); +// } +// +// $oldRevisionId = $this.mRevisionId; +// $oldRevisionObject = $this.mRevisionObject; +// $oldRevisionTimestamp = $this.mRevisionTimestamp; +// $oldRevisionUser = $this.mRevisionUser; +// $oldRevisionSize = $this.mRevisionSize; +// if ($revid !== null) { +// $this.mRevisionId = $revid; +// $this.mRevisionObject = null; +// $this.mRevisionTimestamp = null; +// $this.mRevisionUser = null; +// $this.mRevisionSize = null; // } // +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// Hooks::run('ParserBeforeStrip', [ &$parser, &$text, &$this.mStripState ]); +// // No more strip! +// Hooks::run('ParserAfterStrip', [ &$parser, &$text, &$this.mStripState ]); +// $text = $this.internalParse($text); +// Hooks::run('ParserAfterParse', [ &$parser, &$text, &$this.mStripState ]); +// +// $text = $this.internalParseHalfParsed($text, true, $linestart); +// // /** -// * Expand templates and variables in the text, producing valid, static wikitext. -// * Also removes comments. -// * Do not call this function recursively. -// * @param String $text -// * @param Title $title -// * @param ParserOptions $options -// * @param int|null $revid -// * @param boolean|PPFrame $frame -// * @return mixed|String -// */ -// public function preprocess($text, Title $title = null, -// ParserOptions $options, $revid = null, $frame = false +// * A converted title will be provided in the output object if title and +// * content conversion are enabled, the article text does not contain +// * a conversion-suppressing double-underscore tag, and no +// * {{DISPLAYTITLE:...}} is present. DISPLAYTITLE takes precedence over +// * automatic link conversion. +// */ +// if (!($options.getDisableTitleConversion() +// || isset($this.mDoubleUnderscores['nocontentconvert']) +// || isset($this.mDoubleUnderscores['notitleconvert']) +// || $this.mOutput.getDisplayTitle() !== false) // ) { -// magicScopeVariable = this.synchronized(); -// this.startParse($title, $options, XomwParser.OT_PREPROCESS, true); -// if ($revid !== null) { -// this.mRevisionId = $revid; +// $convruletitle = $this.getTargetLanguage().getConvRuleTitle(); +// if ($convruletitle) { +// $this.mOutput.setTitleText($convruletitle); +// } else { +// titleText = $this.getTargetLanguage().convertTitle(title); +// $this.mOutput.setTitleText(titleText); // } -// Hooks::run('ParserBeforeStrip', [ &$this, &$text, &this.mStripState ]); -// Hooks::run('ParserAfterStrip', [ &$this, &$text, &this.mStripState ]); -// $text = this.replaceVariables($text, $frame); -// $text = this.mStripState->unstripBoth($text); -// return $text; // } // -// /** -// * Recursive parser entry point that can be called from an extension tag -// * hook. -// * -// * @param String $text Text to be expanded -// * @param boolean|PPFrame $frame The frame to use for expanding any template variables -// * @return String -// * @since 1.19 -// */ -// public function recursivePreprocess($text, $frame = false) { -// $text = this.replaceVariables($text, $frame); -// $text = this.mStripState->unstripBoth($text); -// return $text; +// // Compute runtime adaptive expiry if set +// $this.mOutput.finalizeAdaptiveCacheExpiry(); +// +// // Warn if too many heavyweight parser functions were used +// if ($this.mExpensiveFunctionCount > $this.mOptions.getExpensiveParserFunctionLimit()) { +// $this.limitationWarn('expensive-parserfunction', +// $this.mExpensiveFunctionCount, +// $this.mOptions.getExpensiveParserFunctionLimit() +// ); // } // -// /** -// * Process the wikitext for the "?preload=" feature. (T7210) -// * -// * "<noinclude>", "<includeonly>" etc. are parsed as for template -// * transclusion, comments, templates, arguments, tags hooks and parser -// * functions are untouched. -// * -// * @param String $text -// * @param Title $title -// * @param ParserOptions $options -// * @param array $params -// * @return String -// */ -// public function getPreloadText($text, Title $title, ParserOptions $options, $params = []) { -// msg = new RawMessage($text); -// $text = msg->params($params)->plain(); -// -// // Parser (re)initialisation -// magicScopeVariable = this.synchronized(); -// this.startParse($title, $options, XomwParser.OT_PLAIN, true); -// -// $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES; -// $dom = this.preprocessToDom($text, XomwParser.PTD_FOR_INCLUSION); -// $text = this.getPreprocessor()->newFrame()->expand($dom, $flags); -// $text = this.mStripState->unstripBoth($text); -// return $text; +// // Information on limits, for the benefit of users who try to skirt them +// if ($this.mOptions.getEnableLimitReport()) { +// $text .= $this.makeLimitReport(); // } // -// /** -// * Get a random String -// * -// * @return String -// * @deprecated since 1.26; use wfRandomString() instead. -// */ -// public static function getRandomString() { -// wfDeprecated(__METHOD__, '1.26'); -// return wfRandomString(16); +// // Wrap non-interface parser output in a <div> so it can be targeted +// // with CSS (T37247) +// $class = $this.mOptions.getWrapOutputClass(); +// if ($class !== false && !$this.mOptions.getInterfaceMessage()) { +// $this.mOutput.addWrapperDivClass($class); // } // -// /** -// * Set the current user. -// * Should only be used when doing pre-save transform. -// * -// * @param User|null $user User Object or null (to reset) -// */ -// public function setUser($user) { -// this.mUser = $user; +// $this.mOutput.setText($text); +// +// $this.mRevisionId = $oldRevisionId; +// $this.mRevisionObject = $oldRevisionObject; +// $this.mRevisionTimestamp = $oldRevisionTimestamp; +// $this.mRevisionUser = $oldRevisionUser; +// $this.mRevisionSize = $oldRevisionSize; +// $this.mInputSize = false; +// $this.currentRevisionCache = null; +// +// return $this.mOutput; +// } +// +// /** +// * Set the limit report data in the current ParserOutput, and return the +// * limit report HTML comment. +// * +// * @return string +// */ +// protected function makeLimitReport() { +// $maxIncludeSize = $this.mOptions.getMaxIncludeSize(); +// +// $cpuTime = $this.mOutput.getTimeSinceStart('cpu'); +// if ($cpuTime !== null) { +// $this.mOutput.setLimitReportData('limitreport-cputime', +// sprintf("%.3f", $cpuTime) +// ); // } // -// /** -// * Accessor for mUniqPrefix. -// * -// * @return String -// * @deprecated since 1.26; use Parser::MARKER_PREFIX instead. -// */ -// public function uniqPrefix() { -// wfDeprecated(__METHOD__, '1.26'); -// return XomwParser.MARKER_PREFIX; +// $wallTime = $this.mOutput.getTimeSinceStart('wall'); +// $this.mOutput.setLimitReportData('limitreport-walltime', +// sprintf("%.3f", $wallTime) +// ); +// +// $this.mOutput.setLimitReportData('limitreport-ppvisitednodes', +// [ $this.mPPNodeCount, $this.mOptions.getMaxPPNodeCount() ] +// ); +// $this.mOutput.setLimitReportData('limitreport-ppgeneratednodes', +// [ $this.mGeneratedPPNodeCount, $this.mOptions.getMaxGeneratedPPNodeCount() ] +// ); +// $this.mOutput.setLimitReportData('limitreport-postexpandincludesize', +// [ $this.mIncludeSizes['post-expand'], $maxIncludeSize ] +// ); +// $this.mOutput.setLimitReportData('limitreport-templateargumentsize', +// [ $this.mIncludeSizes['arg'], $maxIncludeSize ] +// ); +// $this.mOutput.setLimitReportData('limitreport-expansiondepth', +// [ $this.mHighestExpansionDepth, $this.mOptions.getMaxPPExpandDepth() ] +// ); +// $this.mOutput.setLimitReportData('limitreport-expensivefunctioncount', +// [ $this.mExpensiveFunctionCount, $this.mOptions.getExpensiveParserFunctionLimit() ] +// ); +// +// foreach ($this.mStripState.getLimitReport() as list($key, $value)) { +// $this.mOutput.setLimitReportData($key, $value); // } // -// /** -// * Set the context title -// * -// * @param Title $t -// */ -// public function setTitle($t) { -// if (!$t) { -// $t = Title::newFromText('NO TITLE'); -// } +// Hooks::run('ParserLimitReportPrepare', [ $this, $this.mOutput ]); // -// if ($t->hasFragment()) { -// // Strip the fragment to avoid various odd effects -// this.mTitle = $t->createFragmentTarget(''); -// } else { -// this.mTitle = $t; +// $limitReport = "NewPP limit report\n"; +// if ($this.siteConfig.get('ShowHostnames')) { +// $limitReport .= 'Parsed by ' . wfHostname() . "\n"; +// } +// $limitReport .= 'Cached time: ' . $this.mOutput.getCacheTime() . "\n"; +// $limitReport .= 'Cache expiry: ' . $this.mOutput.getCacheExpiry() . "\n"; +// $limitReport .= 'Dynamic content: ' . +// ($this.mOutput.hasDynamicContent() ? 'true' : 'false') . +// "\n"; +// +// foreach ($this.mOutput.getLimitReportData() as $key => $value) { +// if (Hooks::run('ParserLimitReportFormat', +// [ $key, &$value, &$limitReport, false, false ] +// )) { +// $keyMsg = wfMessage($key).inLanguage('en').useDatabase(false); +// $valueMsg = wfMessage([ "$key-value-text", "$key-value" ]) +// .inLanguage('en').useDatabase(false); +// if (!$valueMsg.exists()) { +// $valueMsg = new RawMessage('$1'); +// } +// if (!$keyMsg.isDisabled() && !$valueMsg.isDisabled()) { +// $valueMsg.params($value); +// $limitReport .= "{$keyMsg.text()}: {$valueMsg.text()}\n"; +// } // } // } +// // Since we're not really outputting HTML, decode the entities and +// // then re-encode the things that need hiding inside HTML comments. +// $limitReport = htmlspecialchars_decode($limitReport); +// +// // Sanitize for comment. Note '‐' in the replacement is U+2010, +// // which looks much like the problematic '-'. +// $limitReport = str_replace([ '-', '&' ], [ '‐', '&' ], $limitReport); +// $text = "\n<!-- \n$limitReport-.\n"; +// +// // Add on template profiling data in human/machine readable way +// $dataByFunc = $this.mProfiler.getFunctionStats(); +// uasort($dataByFunc, function ($a, $b) { +// return $b['real'] <=> $a['real']; // descending order +// }); +// $profileReport = []; +// foreach (array_slice($dataByFunc, 0, 10) as $item) { +// $profileReport[] = sprintf("%6.2f%% %8.3f %6d %s", +// $item['%real'], $item['real'], $item['calls'], +// htmlspecialchars($item['name'])); +// } +// $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n"; +// $text .= implode("\n", $profileReport) . "\n-.\n"; // -// /** -// * Accessor for the Title Object -// * -// * @return Title -// */ -// public function getTitle() { -// return this.mTitle; +// $this.mOutput.setLimitReportData('limitreport-timingprofile', $profileReport); +// +// // Add other cache related metadata +// if ($this.siteConfig.get('ShowHostnames')) { +// $this.mOutput.setLimitReportData('cachereport-origin', wfHostname()); +// } +// $this.mOutput.setLimitReportData('cachereport-timestamp', +// $this.mOutput.getCacheTime()); +// $this.mOutput.setLimitReportData('cachereport-ttl', +// $this.mOutput.getCacheExpiry()); +// $this.mOutput.setLimitReportData('cachereport-transientcontent', +// $this.mOutput.hasDynamicContent()); +// +// if ($this.mGeneratedPPNodeCount > $this.mOptions.getMaxGeneratedPPNodeCount() / 10) { +// wfDebugLog('generated-pp-node-count', $this.mGeneratedPPNodeCount . ' ' . +// $this.mTitle.getPrefixedDBkey()); +// } +// return $text; +// } +// +// /** +// * Half-parse wikitext to half-parsed HTML. This recursive parser entry point +// * can be called from an extension tag hook. +// * +// * The output of this function IS NOT SAFE PARSED HTML; it is "half-parsed" +// * instead, which means that lists and links have not been fully parsed yet, +// * and strip markers are still present. +// * +// * Use recursiveTagParseFully() to fully parse wikitext to output-safe HTML. +// * +// * Use this function if you're a parser tag hook and you want to parse +// * wikitext before or after applying additional transformations, and you +// * intend to *return the result as hook output*, which will cause it to go +// * through the rest of parsing process automatically. +// * +// * If $frame is not provided, then template variables (e.g., {{{1}}}) within +// * $text are not expanded +// * +// * @param string $text Text extension wants to have parsed +// * @param-taint $text escapes_htmlnoent +// * @param bool|PPFrame $frame The frame to use for expanding any template variables +// * @return string UNSAFE half-parsed HTML +// * @return-taint escaped +// */ +// public function recursiveTagParse($text, $frame = false) { +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// Hooks::run('ParserBeforeStrip', [ &$parser, &$text, &$this.mStripState ]); +// Hooks::run('ParserAfterStrip', [ &$parser, &$text, &$this.mStripState ]); +// $text = $this.internalParse($text, false, $frame); +// return $text; +// } +// +// /** +// * Fully parse wikitext to fully parsed HTML. This recursive parser entry +// * point can be called from an extension tag hook. +// * +// * The output of this function is fully-parsed HTML that is safe for output. +// * If you're a parser tag hook, you might want to use recursiveTagParse() +// * instead. +// * +// * If $frame is not provided, then template variables (e.g., {{{1}}}) within +// * $text are not expanded +// * +// * @since 1.25 +// * +// * @param string $text Text extension wants to have parsed +// * @param-taint $text escapes_htmlnoent +// * @param bool|PPFrame $frame The frame to use for expanding any template variables +// * @return string Fully parsed HTML +// * @return-taint escaped +// */ +// public function recursiveTagParseFully($text, $frame = false) { +// $text = $this.recursiveTagParse($text, $frame); +// $text = $this.internalParseHalfParsed($text, false); +// return $text; +// } +// +// /** +// * Expand templates and variables in the text, producing valid, static wikitext. +// * Also removes comments. +// * Do not call this function recursively. +// * @param string $text +// * @param Title|null title +// * @param ParserOptions $options +// * @param int|null $revid +// * @param bool|PPFrame $frame +// * @return mixed|string +// */ +// public function preprocess($text, Title title = null, +// ParserOptions $options, $revid = null, $frame = false +// ) { +// $magicScopeVariable = $this.lock(); +// $this.startParse(title, $options, self::OT_PREPROCESS, true); +// if ($revid !== null) { +// $this.mRevisionId = $revid; +// } +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// Hooks::run('ParserBeforeStrip', [ &$parser, &$text, &$this.mStripState ]); +// Hooks::run('ParserAfterStrip', [ &$parser, &$text, &$this.mStripState ]); +// $text = $this.replaceVariables($text, $frame); +// $text = $this.mStripState.unstripBoth($text); +// return $text; +// } +// +// /** +// * Recursive parser entry point that can be called from an extension tag +// * hook. +// * +// * @param string $text Text to be expanded +// * @param bool|PPFrame $frame The frame to use for expanding any template variables +// * @return string +// * @since 1.19 +// */ +// public function recursivePreprocess($text, $frame = false) { +// $text = $this.replaceVariables($text, $frame); +// $text = $this.mStripState.unstripBoth($text); +// return $text; +// } +// +// /** +// * Process the wikitext for the "?preload=" feature. (T7210) +// * +// * "<noinclude>", "<includeonly>" etc. are parsed as for template +// * transclusion, comments, templates, arguments, tags hooks and parser +// * functions are untouched. +// * +// * @param string $text +// * @param Title title +// * @param ParserOptions $options +// * @param array $params +// * @return string +// */ +// public function getPreloadText($text, Title title, ParserOptions $options, $params = []) { +// $msg = new RawMessage($text); +// $text = $msg.params($params).plain(); +// +// // Parser (re)initialisation +// $magicScopeVariable = $this.lock(); +// $this.startParse(title, $options, self::OT_PLAIN, true); +// +// $flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES; +// $dom = $this.preprocessToDom($text, self::PTD_FOR_INCLUSION); +// $text = $this.getPreprocessor().newFrame().expand($dom, $flags); +// $text = $this.mStripState.unstripBoth($text); +// return $text; +// } +// +// /** +// * Set the current user. +// * Should only be used when doing pre-save transform. +// * +// * @param User|null $user User object or null (to reset) +// */ +// public function setUser($user) { +// $this.mUser = $user; +// } +// +// /** +// * Set the context title +// * +// * @param Title $t +// */ +// public function setTitle($t) { +// if (!$t) { +// $t = Title::newFromText('NO TITLE'); // } // -// /** -// * Accessor/mutator for the Title Object -// * -// * @param Title $x Title Object or null to just get the current one -// * @return Title -// */ -// public function Title($x = null) { -// return wfSetVar(this.mTitle, $x); +// if ($t.hasFragment()) { +// // Strip the fragment to avoid various odd effects +// $this.mTitle = $t.createFragmentTarget(''); +// } else { +// $this.mTitle = $t; // } +// } +// +// /** +// * Accessor for the Title object +// * +// * @return Title +// */ +// public function getTitle() { +// return $this.mTitle; +// } +// +// /** +// * Accessor/mutator for the Title object +// * +// * @param Title|null $x Title object or null to just get the current one +// * @return Title +// */ +// public function Title($x = null) { +// return wfSetVar($this.mTitle, $x); +// } /** * Set the output type @@ -902,16 +1440,23 @@ public class XomwParser implements XomwParserIface { return this.mOptions; } - -// /** -// * Accessor/mutator for the ParserOptions Object -// * -// * @param ParserOptions $x New value or null to just get the current one -// * @return ParserOptions Current ParserOptions Object -// */ -// public function Options($x = null) { -// return wfSetVar(this.mOptions, $x); -// } +// MW.SRC:1.33.1 +// /** +// * Accessor/mutator for the ParserOptions object +// * +// * @param ParserOptions|null $x New value or null to just get the current one +// * @return ParserOptions Current ParserOptions object +// */ +// public function Options($x = null) { +// return wfSetVar($this.mOptions, $x); +// } +// +// /** +// * @return int +// */ +// public function nextLinkID() { +// return $this.mLinkID++; +// } /** * @return int @@ -919,65 +1464,59 @@ public class XomwParser implements XomwParserIface { public int nextLinkID() { return this.mLinkID++; } - -// /** -// * @param int $id -// */ -// public function setLinkID($id) { -// this.mLinkID = $id; -// } -// -// /** -// * Get a language Object for use in parser functions such as {{FORMATNUM:}} -// * @return Language -// */ -// public function getFunctionLang() { -// return this.getTargetLanguage(); -// } -// -// /** -// * Get the target language for the content being parsed. This is usually the -// * language that the content is in. -// * -// * @since 1.19 -// * -// * @throws MWException -// * @return Language -// */ -// public function getTargetLanguage() { -// $target = this.mOptions->getTargetLanguage(); -// -// if ($target !== null) { -// return $target; -// } elseif (this.mOptions->getInterfaceMessage()) { -// return this.mOptions->getUserLangObj(); -// } elseif (is_null(this.mTitle)) { -// throw new MWException(__METHOD__ . ': this.mTitle is null'); -// } -// -// return this.mTitle->getPageLanguage(); -// } -// -// /** -// * Get the language Object for language conversion -// * @return Language|null -// */ -// public function getConverterLanguage() { -// return this.getTargetLanguage(); +// MW.SRC:1.33.1 +// /** +// * Get a language object for use in parser functions such as {{FORMATNUM:}} +// * @return Language +// */ +// public function getFunctionLang() { +// return $this.getTargetLanguage(); +// } +// +// /** +// * Get the target language for the content being parsed. This is usually the +// * language that the content is in. +// * +// * @since 1.19 +// * +// * @throws MWException +// * @return Language +// */ +// public function getTargetLanguage() { +// $target = $this.mOptions.getTargetLanguage(); +// +// if ($target !== null) { +// return $target; +// } elseif ($this.mOptions.getInterfaceMessage()) { +// return $this.mOptions.getUserLangObj(); +// } elseif (is_null($this.mTitle)) { +// throw new MWException(__METHOD__ . ': $this.mTitle is null'); // } // -// /** -// * Get a User Object either from this.mUser, if set, or from the -// * ParserOptions Object otherwise -// * -// * @return User -// */ -// public function getUser() { -// if (!is_null(this.mUser)) { -// return this.mUser; -// } -// return this.mOptions->getUser(); +// return $this.mTitle.getPageLanguage(); +// } +// +// /** +// * Get the language object for language conversion +// * @deprecated since 1.32, just use getTargetLanguage() +// * @return Language|null +// */ +// public function getConverterLanguage() { +// return $this.getTargetLanguage(); +// } +// +// /** +// * Get a User object either from $this.mUser, if set, or from the +// * ParserOptions object otherwise +// * +// * @return User +// */ +// public function getUser() { +// if (!is_null($this.mUser)) { +// return $this.mUser; // } +// return $this.mOptions.getUser(); +// } /** * Get a preprocessor Object @@ -1001,9 +1540,9 @@ public class XomwParser implements XomwParserIface { public XomwLinkRenderer getLinkRenderer() { if (this.mLinkRenderer == null) { // this.mLinkRenderer = XomwMediaWikiServices.getInstance() -// .getLinkRendererFactory()->create(); -// this.mLinkRenderer->setStubThreshold( -// this.getOptions()->getStubThreshold() +// .getLinkRendererFactory().create(); +// this.mLinkRenderer.setStubThreshold( +// this.getOptions().getStubThreshold() // ); this.mLinkRenderer = new XomwLinkRenderer(sanitizer); } @@ -1073,7 +1612,7 @@ public class XomwParser implements XomwParserIface { // $tail = null; // } else { // if ($element === '!--') { -// $end = '/(-->)/'; +// $end = '/(-.)/'; // } else { // $end = "/(<\\/$element\\s*>)/i"; // } @@ -1097,6 +1636,32 @@ public class XomwParser implements XomwParserIface { // return $stripped; // } +// MW.SRC:1.33.1 +// /** +// * Get a list of strippable XML-like elements +// * +// * @return array +// */ +// public function getStripList() { +// return $this.mStripList; +// } +// +// /** +// * Add an item to the strip state +// * Returns the unique tag which must be inserted into the stripped text +// * The tag will be replaced with the original text in unstrip() +// * +// * @param string $text +// * +// * @return string +// */ +// public function insertStripItem($text) { +// $marker = self::MARKER_PREFIX . "-item-{$this.mMarkerIndex}-" . self::MARKER_SUFFIX; +// $this.mMarkerIndex++; +// $this.mStripState.addGeneral($marker, $text); +// return $marker; +// } + /** * Get a list of strippable XML-like elements * @@ -1134,6 +1699,287 @@ public class XomwParser implements XomwParserIface { // XO.MOVED to Xomw_table_Wkr // public function doTableStuff($text) {} +// MW.SRC:1.33.1 +// /** +// * parse the wiki syntax used to render tables +// * +// * @private +// * @param string $text +// * @return string +// */ +// public function doTableStuff($text) { +// $lines = StringUtils::explode("\n", $text); +// $out = ''; +// $td_history = []; # Is currently a td tag open? +// $last_tag_history = []; # Save history of last lag activated (td, th or caption) +// $tr_history = []; # Is currently a tr tag open? +// $tr_attributes = []; # history of tr attributes +// $has_opened_tr = []; # Did this table open a <tr> element? +// $indent_level = 0; # indent level of the table +// +// foreach ($lines as $outLine) { +// $line = trim($outLine); +// +// if ($line === '') { # empty line, go to next line +// $out .= $outLine . "\n"; +// continue; +// } +// +// $first_character = $line[0]; +// $first_two = substr($line, 0, 2); +// $matches = []; +// +// if (preg_match('/^(:*)\s*\{\|(.*)$/', $line, $matches)) { +// // First check if we are starting a new table +// $indent_level = strlen($matches[1]); +// +// $attributes = $this.mStripState.unstripBoth($matches[2]); +// $attributes = Sanitizer::fixTagAttributes($attributes, 'table'); +// +// $outLine = str_repeat('<dl><dd>', $indent_level) . "<table{$attributes}>"; +// array_push($td_history, false); +// array_push($last_tag_history, ''); +// array_push($tr_history, false); +// array_push($tr_attributes, ''); +// array_push($has_opened_tr, false); +// } elseif (count($td_history) == 0) { +// // Don't do any of the following +// $out .= $outLine . "\n"; +// continue; +// } elseif ($first_two === '|}') { +// // We are ending a table +// $line = '</table>' . substr($line, 2); +// $last_tag = array_pop($last_tag_history); +// +// if (!array_pop($has_opened_tr)) { +// $line = "<tr><td></td></tr>{$line}"; +// } +// +// if (array_pop($tr_history)) { +// $line = "</tr>{$line}"; +// } +// +// if (array_pop($td_history)) { +// $line = "</{$last_tag}>{$line}"; +// } +// array_pop($tr_attributes); +// if ($indent_level > 0) { +// $outLine = rtrim($line) . str_repeat('</dd></dl>', $indent_level); +// } else { +// $outLine = $line; +// } +// } elseif ($first_two === '|-') { +// // Now we have a table row +// $line = preg_replace('#^\|-+#', '', $line); +// +// // Whats after the tag is now only attributes +// $attributes = $this.mStripState.unstripBoth($line); +// $attributes = Sanitizer::fixTagAttributes($attributes, 'tr'); +// array_pop($tr_attributes); +// array_push($tr_attributes, $attributes); +// +// $line = ''; +// $last_tag = array_pop($last_tag_history); +// array_pop($has_opened_tr); +// array_push($has_opened_tr, true); +// +// if (array_pop($tr_history)) { +// $line = '</tr>'; +// } +// +// if (array_pop($td_history)) { +// $line = "</{$last_tag}>{$line}"; +// } +// +// $outLine = $line; +// array_push($tr_history, false); +// array_push($td_history, false); +// array_push($last_tag_history, ''); +// } elseif ($first_character === '|' +// || $first_character === '!' +// || $first_two === '|+' +// ) { +// // This might be cell elements, td, th or captions +// if ($first_two === '|+') { +// $first_character = '+'; +// $line = substr($line, 2); +// } else { +// $line = substr($line, 1); +// } +// +// // Implies both are valid for table headings. +// if ($first_character === '!') { +// $line = StringUtils::replaceMarkup('!!', '||', $line); +// } +// +// // Split up multiple cells on the same line. +// // FIXME : This can result in improper nesting of tags processed +// // by earlier parser steps. +// $cells = explode('||', $line); +// +// $outLine = ''; +// +// // Loop through each table cell +// foreach ($cells as $cell) { +// $previous = ''; +// if ($first_character !== '+') { +// $tr_after = array_pop($tr_attributes); +// if (!array_pop($tr_history)) { +// $previous = "<tr{$tr_after}>\n"; +// } +// array_push($tr_history, true); +// array_push($tr_attributes, ''); +// array_pop($has_opened_tr); +// array_push($has_opened_tr, true); +// } +// +// $last_tag = array_pop($last_tag_history); +// +// if (array_pop($td_history)) { +// $previous = "</{$last_tag}>\n{$previous}"; +// } +// +// if ($first_character === '|') { +// $last_tag = 'td'; +// } elseif ($first_character === '!') { +// $last_tag = 'th'; +// } elseif ($first_character === '+') { +// $last_tag = 'caption'; +// } else { +// $last_tag = ''; +// } +// +// array_push($last_tag_history, $last_tag); +// +// // A cell could contain both parameters and data +// $cell_data = explode('|', $cell, 2); +// +// // T2553: Note that a '|' inside an invalid link should not +// // be mistaken as delimiting cell parameters +// // Bug T153140: Neither should language converter markup. +// if (preg_match('/\[\[|-\{/', $cell_data[0]) === 1) { +// $cell = "{$previous}<{$last_tag}>" . trim($cell); +// } elseif (count($cell_data) == 1) { +// // Whitespace in cells is trimmed +// $cell = "{$previous}<{$last_tag}>" . trim($cell_data[0]); +// } else { +// $attributes = $this.mStripState.unstripBoth($cell_data[0]); +// $attributes = Sanitizer::fixTagAttributes($attributes, $last_tag); +// // Whitespace in cells is trimmed +// $cell = "{$previous}<{$last_tag}{$attributes}>" . trim($cell_data[1]); +// } +// +// $outLine .= $cell; +// array_push($td_history, true); +// } +// } +// $out .= $outLine . "\n"; +// } +// +// // Closing open td, tr && table +// while (count($td_history) > 0) { +// if (array_pop($td_history)) { +// $out .= "</td>\n"; +// } +// if (array_pop($tr_history)) { +// $out .= "</tr>\n"; +// } +// if (!array_pop($has_opened_tr)) { +// $out .= "<tr><td></td></tr>\n"; +// } +// +// $out .= "</table>\n"; +// } +// +// // Remove trailing line-ending (b/c) +// if (substr($out, -1) === "\n") { +// $out = substr($out, 0, -1); +// } +// +// // special case: don't return empty table +// if ($out === "<table>\n<tr><td></td></tr>\n</table>") { +// $out = ''; +// } +// +// return $out; +// } +// +// /** +// * Helper function for parse() that transforms wiki markup into half-parsed +// * HTML. Only called for $mOutputType == self::OT_HTML. +// * +// * @private +// * +// * @param string $text The text to parse +// * @param-taint $text escapes_html +// * @param bool $isMain Whether this is being called from the main parse() function +// * @param PPFrame|bool $frame A pre-processor frame +// * +// * @return string +// */ +// public function internalParse($text, $isMain = true, $frame = false) { +// $origText = $text; +// +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// +// // Hook to suspend the parser in this state +// if (!Hooks::run('ParserBeforeInternalParse', [ &$parser, &$text, &$this.mStripState ])) { +// return $text; +// } +// +// // if $frame is provided, then use $frame for replacing any variables +// if ($frame) { +// // use frame depth to infer how include/noinclude tags should be handled +// // depth=0 means this is the top-level document; otherwise it's an included document +// if (!$frame.depth) { +// $flag = 0; +// } else { +// $flag = self::PTD_FOR_INCLUSION; +// } +// $dom = $this.preprocessToDom($text, $flag); +// $text = $frame.expand($dom); +// } else { +// // if $frame is not provided, then use old-style replaceVariables +// $text = $this.replaceVariables($text); +// } +// +// Hooks::run('InternalParseBeforeSanitize', [ &$parser, &$text, &$this.mStripState ]); +// $text = Sanitizer::removeHTMLtags( +// $text, +// [ $this, 'attributeStripCallback' ], +// false, +// array_keys($this.mTransparentTagHooks), +// [], +// [ $this, 'addTrackingCategory' ] +// ); +// Hooks::run('InternalParseBeforeLinks', [ &$parser, &$text, &$this.mStripState ]); +// +// // Tables need to come after variable replacement for things to work +// // properly; putting them before other transformations should keep +// // exciting things like link expansions from showing up in surprising +// // places. +// $text = $this.doTableStuff($text); +// +// $text = preg_replace('/(^|\n)-----*/', '\\1<hr />', $text); +// +// $text = $this.doDoubleUnderscore($text); +// +// $text = $this.doHeadings($text); +// $text = $this.replaceInternalLinks($text); +// $text = $this.doAllQuotes($text); +// $text = $this.replaceExternalLinks($text); +// +// // replaceInternalLinks may sometimes leave behind +// // absolute URLs, which have to be masked to hide them from replaceExternalLinks +// $text = str_replace(self::MARKER_PREFIX . 'NOPARSE', '', $text); +// +// $text = $this.doMagicLinks($text); +// $text = $this.formatHeadings($text, $origText, $isMain); +// +// return $text; +// } + /** * Helper function for parse() that transforms wiki markup into half-parsed * HTML. Only called for mOutputType == XomwParser.OT_HTML. @@ -1159,16 +2005,16 @@ public class XomwParser implements XomwParserIface { // use frame depth to infer how include/noinclude tags should be handled // depth=0 means this is the top-level document; otherwise it's an included document // boolean for_inclusion = false; -// if (!$frame->depth) { +// if (!$frame.depth) { // $flag = 0; // } else { // $flag = Parser::PTD_FOR_INCLUSION; // } // text = prepro_wkr.Preprocess_to_xml(text, for_inclusion); - // text = $frame->expand($dom); + // text = $frame.expand($dom); // } else { // // if $frame is not provided, then use old-style replaceVariables -// text = $this->replaceVariables(text); +// text = $this.replaceVariables(text); // } // MW.HOOK:InternalParseBeforeSanitize @@ -1176,7 +2022,7 @@ public class XomwParser implements XomwParserIface { // text, // [ &$this, 'attributeStripCallback' ], // false, -// array_keys($this->mTransparentTagHooks), +// array_keys($this.mTransparentTagHooks), // [], // [ &$this, 'addTrackingCategory' ] // ); @@ -1203,7 +2049,7 @@ public class XomwParser implements XomwParserIface { XomwParserBfr_.Replace(pbfr, Bry__marker__noparse, Bry_.Empty); // $text = str_replace(XomwParser.MARKER_PREFIX . 'NOPARSE', '', $text); magiclinksWkr.doMagicLinks(pctx, pbfr); -// $text = $this->formatHeadings($text, $origText, $isMain); +// $text = $this.formatHeadings($text, $origText, $isMain); } public String internalParse(String text, boolean isMain, XomwPPFrame frame) { // isMain=true; frame=false; @@ -1292,7 +2138,7 @@ public class XomwParser implements XomwParserIface { // '/(\\302\\253) /' => '\\1 ', // '/ (!\s*important)/' => ' \\1', // Beware of CSS magic word !important, T13874. // ]; - // $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text ); + // $text = preg_replace(array_keys($fixtags), array_values($fixtags), $text); nbspWkr.doNbsp(pctx, pbfr); blockWkr.doBlockLevels(pctx, pbfr, lineStart); @@ -1304,14 +2150,14 @@ public class XomwParser implements XomwParserIface { // b) Content isn't converted // c) It's a conversion table // d) it is an interface message (which is in the user language) -// if ( !( $this->mOptions->getDisableContentConversion() -// || isset( $this->mDoubleUnderscores['nocontentconvert'] ) ) +// if (!($this.mOptions.getDisableContentConversion() +// || isset($this.mDoubleUnderscores['nocontentconvert'])) // ) { -// if ( !$this->mOptions->getInterfaceMessage() ) { +// if (!$this.mOptions.getInterfaceMessage()) { // // The position of the convert() call should not be changed. it // // assumes that the links are all replaced and the only thing left // // is the <nowiki> mark. -// $text = $this->getConverterLanguage()->convert( $text ); +// $text = $this.getConverterLanguage().convert($text); // } // } @@ -1319,21 +2165,21 @@ public class XomwParser implements XomwParserIface { // MW.HOOK:ParserBeforeTidy -// $text = $this->replaceTransparentTags( $text ); +// $text = $this.replaceTransparentTags($text); mStripState.unstripGeneral(pbfr); sanitizer.normalizeCharReferences(pbfr); -// if ( MWTidy::isEnabled() ) { -// if ( $this->mOptions->getTidy() ) { -// $text = MWTidy::tidy( $text ); +// if (MWTidy::isEnabled()) { +// if ($this.mOptions.getTidy()) { +// $text = MWTidy::tidy($text); // } // } // else { // // attempt to sanitize at least some nesting problems // // (T4702 and quite a few others) // $tidyregs = [ -// // ''Something [http://www.cool.com cool''] --> +// // ''Something [http://www.cool.com cool''] -. // // <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a> // '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' => // '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9', @@ -1352,9 +2198,9 @@ public class XomwParser implements XomwParserIface { // ]; // $text = preg_replace( -// array_keys( $tidyregs ), -// array_values( $tidyregs ), -// $text ); +// array_keys($tidyregs), +// array_values($tidyregs), +// $text); // } // // MW.HOOK:ParserAfterTidy @@ -1366,194 +2212,506 @@ public class XomwParser implements XomwParserIface { // XO.MW:MOVED // public function magicLinkCallback(m) {} +// MW.SRC:1.33.1 +// /** +// * Replace special strings like "ISBN xxx" and "RFC xxx" with +// * magic external links. +// * +// * DML +// * @private +// * +// * @param string $text +// * +// * @return string +// */ +// public function doMagicLinks($text) { +// $prots = wfUrlProtocolsWithoutProtRel(); +// $urlChar = self::EXT_LINK_URL_CLASS; +// $addr = self::EXT_LINK_ADDR; +// $space = self::SPACE_NOT_NL; # non-newline space +// $spdash = "(?:-|$space)"; # a dash or a non-newline space +// $spaces = "$space++"; # possessive match of 1 or more spaces +// $text = preg_replace_callback( +// '!(?: # Start cases +// (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text +// (<.*?>) | # m[2]: Skip stuff inside HTML elements' . " +// (\b # m[3]: Free external links +// (?i:$prots) +// ($addr$urlChar*) # m[4]: Post-protocol path +// ) | +// \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number +// ([0-9]+)\b | +// \bISBN $spaces ( # m[6]: ISBN, capture number +// (?: 97[89] $spdash?)? # optional 13-digit ISBN prefix +// (?: [0-9] $spdash?){9} # 9 digits with opt. delimiters +// [0-9Xx] # check digit +// )\b +// )!xu", [ $this, 'magicLinkCallback' ], $text); +// return $text; +// } +// +// /** +// * @throws MWException +// * @param array $m +// * @return string HTML +// */ +// public function magicLinkCallback($m) { +// if (isset($m[1]) && $m[1] !== '') { +// // Skip anchor +// return $m[0]; +// } elseif (isset($m[2]) && $m[2] !== '') { +// // Skip HTML element +// return $m[0]; +// } elseif (isset($m[3]) && $m[3] !== '') { +// // Free external link +// return $this.makeFreeExternalLink($m[0], strlen($m[4])); +// } elseif (isset($m[5]) && $m[5] !== '') { +// // RFC or PMID +// if (substr($m[0], 0, 3) === 'RFC') { +// if (!$this.mOptions.getMagicRFCLinks()) { +// return $m[0]; +// } +// $keyword = 'RFC'; +// $urlmsg = 'rfcurl'; +// $cssClass = 'mw-magiclink-rfc'; +// $trackingCat = 'magiclink-tracking-rfc'; +// $id = $m[5]; +// } elseif (substr($m[0], 0, 4) === 'PMID') { +// if (!$this.mOptions.getMagicPMIDLinks()) { +// return $m[0]; +// } +// $keyword = 'PMID'; +// $urlmsg = 'pubmedurl'; +// $cssClass = 'mw-magiclink-pmid'; +// $trackingCat = 'magiclink-tracking-pmid'; +// $id = $m[5]; +// } else { +// throw new MWException(__METHOD__ . ': unrecognised match type "' . +// substr($m[0], 0, 20) . '"'); +// } +// $url = wfMessage($urlmsg, $id).inContentLanguage().text(); +// $this.addTrackingCategory($trackingCat); +// return Linker::makeExternalLink($url, "{$keyword} {$id}", true, $cssClass, [], $this.mTitle); +// } elseif (isset($m[6]) && $m[6] !== '' +// && $this.mOptions.getMagicISBNLinks() +// ) { +// // ISBN +// $isbn = $m[6]; +// $space = self::SPACE_NOT_NL; # non-newline space +// $isbn = preg_replace("/$space/", ' ', $isbn); +// $num = strtr($isbn, [ +// '-' => '', +// ' ' => '', +// 'x' => 'X', +// ]); +// $this.addTrackingCategory('magiclink-tracking-isbn'); +// return $this.getLinkRenderer().makeKnownLink( +// SpecialPage::getTitleFor('Booksources', $num), +// "ISBN $isbn", +// [ +// 'class' => 'internal mw-magiclink-isbn', +// 'title' => false // suppress title attribute +// ] +// ); +// } else { +// return $m[0]; +// } +// } -// /** -// * Make a free external link, given a user-supplied URL -// * -// * @param String $url -// * @param int $numPostProto -// * The number of characters after the protocol. -// * @return String HTML -// * @private -// */ -// public function makeFreeExternalLink($url, $numPostProto) { -// $trail = ''; -// -// // The characters '<' and '>' (which were escaped by -// // removeHTMLtags()) should not be included in -// // URLs, per RFC 2396. -// // Make   terminate a URL as well (bug T84937) -// m2 = []; -// if (preg_match( -// '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/', -// $url, -// m2, -// PREG_OFFSET_CAPTURE -// )) { -// $trail = substr($url, m2[0][1]) . $trail; -// $url = substr($url, 0, m2[0][1]); -// } +// MW.SRC:1.33.1 +// /** +// * Make a free external link, given a user-supplied URL +// * +// * @param string $url +// * @param int $numPostProto +// * The number of characters after the protocol. +// * @return string HTML +// * @private +// */ +// public function makeFreeExternalLink($url, $numPostProto) { +// $trail = ''; +// +// // The characters '<' and '>' (which were escaped by +// // removeHTMLtags()) should not be included in +// // URLs, per RFC 2396. +// // Make   terminate a URL as well (bug T84937) +// $m2 = []; +// if (preg_match( +// '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/', +// $url, +// $m2, +// PREG_OFFSET_CAPTURE +// )) { +// $trail = substr($url, $m2[0][1]) . $trail; +// $url = substr($url, 0, $m2[0][1]); +// } // -// // Move trailing punctuation to $trail +// // Move trailing punctuation to $trail // $sep = ',;\.:!?'; -// // If there is no left bracket, then consider right brackets fair game too -// if (strpos($url, '(') === false) { -// $sep .= ')'; -// } -// -// $urlRev = strrev($url); -// $numSepChars = strspn($urlRev, $sep); -// // Don't break a trailing HTML entity by moving the ; into $trail -// // This is in hot code, so use substr_compare to avoid having to -// // create a new String Object for the comparison -// if ($numSepChars && substr_compare($url, ";", -$numSepChars, 1) === 0) { -// // more optimization: instead of running preg_match with a $ -// // anchor, which can be slow, do the match on the reversed -// // String starting at the desired offset. -// // un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i -// if (preg_match('/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, m2, 0, $numSepChars)) { -// $numSepChars--; -// } -// } -// if ($numSepChars) { -// $trail = substr($url, -$numSepChars) . $trail; -// $url = substr($url, 0, -$numSepChars); -// } +// // If there is no left bracket, then consider right brackets fair game too +// if (strpos($url, '(') === false) { +// $sep .= ')'; +// } // -// // Verify that we still have a real URL after trail removal, and -// // not just lone protocol -// if (strlen($trail) >= $numPostProto) { -// return $url . $trail; +// $urlRev = strrev($url); +// $numSepChars = strspn($urlRev, $sep); +// // Don't break a trailing HTML entity by moving the ; into $trail +// // This is in hot code, so use substr_compare to avoid having to +// // create a new string object for the comparison +// if ($numSepChars && substr_compare($url, ";", -$numSepChars, 1) === 0) { +// // more optimization: instead of running preg_match with a $ +// // anchor, which can be slow, do the match on the reversed +// // string starting at the desired offset. +// // un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i +// if (preg_match('/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars)) { +// $numSepChars--; // } +// } +// if ($numSepChars) { +// $trail = substr($url, -$numSepChars) . $trail; +// $url = substr($url, 0, -$numSepChars); +// } // -// $url = Sanitizer::cleanUrl($url); +// // Verify that we still have a real URL after trail removal, and +// // not just lone protocol +// if (strlen($trail) >= $numPostProto) { +// return $url . $trail; +// } // -// // Is this an external image? -// $text = this.maybeMakeExternalImage($url); -// if ($text === false) { -// // Not an image, make a link -// $text = Linker::makeExternalLink($url, -// this.getConverterLanguage()->markNoConversion($url, true), -// true, 'free', -// this.getExternalLinkAttribs($url), this.mTitle); -// // Register it in the output Object... -// // Replace unnecessary URL escape codes with their equivalent characters -// $pasteurized = XomwParser.normalizeLinkUrl($url); -// this.mOutput->addExternalLink($pasteurized); -// } -// return $text . $trail; +// $url = Sanitizer::cleanUrl($url); +// +// // Is this an external image? +// $text = $this.maybeMakeExternalImage($url); +// if ($text === false) { +// // Not an image, make a link +// $text = Linker::makeExternalLink($url, +// $this.getTargetLanguage().getConverter().markNoConversion($url), +// true, 'free', +// $this.getExternalLinkAttribs($url), $this.mTitle); +// // Register it in the output object... +// $this.mOutput.addExternalLink($url); +// } +// return $text . $trail; +// } +// +// /** +// * Parse headers and return html +// * +// * @private +// * +// * @param string $text +// * +// * @return string +// */ +// public function doHeadings($text) { +// for ($i = 6; $i >= 1; --$i) { +// $h = str_repeat('=', $i); +// // Trim non-newline whitespace from headings +// // Using \s* will break for: "==\n===\n" and parse as <h2>=</h2> +// $text = preg_replace("/^(?:$h)[ \\t]*(.+?)[ \\t]*(?:$h)\\s*$/m", "<h$i>\\1</h$i>", $text); +// } +// return $text; +// } +// +// /** +// * Replace single quotes with HTML markup +// * @private +// * +// * @param string $text +// * +// * @return string The altered text +// */ +// public function doAllQuotes($text) { +// $outtext = ''; +// $lines = StringUtils::explode("\n", $text); +// foreach ($lines as $line) { +// $outtext .= $this.doQuotes($line) . "\n"; +// } +// $outtext = substr($outtext, 0, -1); +// return $outtext; +// } +// MW.SRC:1.33.1 +// /** +// * Helper function for doAllQuotes() +// * +// * @param string $text +// * +// * @return string +// */ +// public function doQuotes($text) { +// $arr = preg_split("/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE); +// $countarr = count($arr); +// if ($countarr == 1) { +// return $text; // } - - // XO.MW:MOVED - // public function doHeadings($text) {} - - // XO.MW:MOVED - // public function doAllQuotes($text) {} - - // XO.MW:MOVED - // public function doQuotes($text) {} -// /** -// * Replace external links (REL) -// * -// * Note: this is all very hackish and the order of execution matters a lot. -// * Make sure to run tests/parser/parserTests.php if you change this code. -// * -// * @private -// * -// * @param String $text -// * -// * @throws MWException -// * @return String -// */ -// public function replaceExternalLinks($text) { // -// $bits = preg_split(this.mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE); -// if ($bits === false) { -// throw new MWException("PCRE needs to be compiled with " -// . "--enable-unicode-properties in order for MediaWiki to function"); +// // First, do some preliminary work. This may shift some apostrophes from +// // being mark-up to being text. It also counts the number of occurrences +// // of bold and italics mark-ups. +// $numbold = 0; +// $numitalics = 0; +// for ($i = 1; $i < $countarr; $i += 2) { +// $thislen = strlen($arr[$i]); +// // If there are ever four apostrophes, assume the first is supposed to +// // be text, and the remaining three constitute mark-up for bold text. +// // (T15227: ''''foo'''' turns into ' ''' foo ' ''') +// if ($thislen == 4) { +// $arr[$i - 1] .= "'"; +// $arr[$i] = "'''"; +// $thislen = 3; +// } elseif ($thislen > 5) { +// // If there are more than 5 apostrophes in a row, assume they're all +// // text except for the last 5. +// // (T15227: ''''''foo'''''' turns into ' ''''' foo ' ''''') +// $arr[$i - 1] .= str_repeat("'", $thislen - 5); +// $arr[$i] = "'''''"; +// $thislen = 5; +// } +// // Count the number of occurrences of bold and italics mark-ups. +// if ($thislen == 2) { +// $numitalics++; +// } elseif ($thislen == 3) { +// $numbold++; +// } elseif ($thislen == 5) { +// $numitalics++; +// $numbold++; // } -// $s = array_shift($bits); -// -// $i = 0; -// while ($i < count($bits)) { -// $url = $bits[$i++]; -// $i++; // protocol -// $text = $bits[$i++]; -// $trail = $bits[$i++]; -// -// // The characters '<' and '>' (which were escaped by -// // removeHTMLtags()) should not be included in -// // URLs, per RFC 2396. -// m2 = []; -// if (preg_match('/&(lt|gt);/', $url, m2, PREG_OFFSET_CAPTURE)) { -// $text = substr($url, m2[0][1]) . ' ' . $text; -// $url = substr($url, 0, m2[0][1]); -// } +// } // -// // If the link text is an image URL, replace it with an <img> tag -// // This happened by accident in the original parser, but some people used it extensively -// $img = this.maybeMakeExternalImage($text); -// if ($img !== false) { -// $text = $img; +// // If there is an odd number of both bold and italics, it is likely +// // that one of the bold ones was meant to be an apostrophe followed +// // by italics. Which one we cannot know for certain, but it is more +// // likely to be one that has a single-letter word before it. +// if (($numbold % 2 == 1) && ($numitalics % 2 == 1)) { +// $firstsingleletterword = -1; +// $firstmultiletterword = -1; +// $firstspace = -1; +// for ($i = 1; $i < $countarr; $i += 2) { +// if (strlen($arr[$i]) == 3) { +// $x1 = substr($arr[$i - 1], -1); +// $x2 = substr($arr[$i - 1], -2, 1); +// if ($x1 === ' ') { +// if ($firstspace == -1) { +// $firstspace = $i; +// } +// } elseif ($x2 === ' ') { +// $firstsingleletterword = $i; +// // if $firstsingleletterword is set, we don't +// // look at the other options, so we can bail early. +// break; +// } elseif ($firstmultiletterword == -1) { +// $firstmultiletterword = $i; +// } // } +// } // -// $dtrail = ''; -// -// // Set linktype for CSS - if URL==text, link is essentially free -// $linktype = ($text === $url) ? 'free' : 'text'; +// // If there is a single-letter word, use it! +// if ($firstsingleletterword > -1) { +// $arr[$firstsingleletterword] = "''"; +// $arr[$firstsingleletterword - 1] .= "'"; +// } elseif ($firstmultiletterword > -1) { +// // If not, but there's a multi-letter word, use that one. +// $arr[$firstmultiletterword] = "''"; +// $arr[$firstmultiletterword - 1] .= "'"; +// } elseif ($firstspace > -1) { +// // ... otherwise use the first one that has neither. +// // (notice that it is possible for all three to be -1 if, for example, +// // there is only one pentuple-apostrophe in the line) +// $arr[$firstspace] = "''"; +// $arr[$firstspace - 1] .= "'"; +// } +// } // -// // No link text, e.g. [http://domain.tld/some.link] -// if ($text == '') { -// // Autonumber -// $langObj = this.getTargetLanguage(); -// $text = '[' . $langObj->formatNum(++this.mAutonumber) . ']'; -// $linktype = 'autonumber'; +// // Now let's actually convert our apostrophic mush to HTML! +// $output = ''; +// $buffer = ''; +// $state = ''; +// $i = 0; +// foreach ($arr as $r) { +// if (($i % 2) == 0) { +// if ($state === 'both') { +// $buffer .= $r; // } else { -// // Have link text, e.g. [http://domain.tld/some.link text]s -// // Check for trail -// list($dtrail, $trail) = Linker::splitTrail($trail); +// $output .= $r; +// } +// } else { +// $thislen = strlen($r); +// if ($thislen == 2) { +// if ($state === 'i') { +// $output .= '</i>'; +// $state = ''; +// } elseif ($state === 'bi') { +// $output .= '</i>'; +// $state = 'b'; +// } elseif ($state === 'ib') { +// $output .= '</b></i><b>'; +// $state = 'b'; +// } elseif ($state === 'both') { +// $output .= '<b><i>' . $buffer . '</i>'; +// $state = 'b'; +// } else { // $state can be 'b' or '' +// $output .= '<i>'; +// $state .= 'i'; +// } +// } elseif ($thislen == 3) { +// if ($state === 'b') { +// $output .= '</b>'; +// $state = ''; +// } elseif ($state === 'bi') { +// $output .= '</i></b><i>'; +// $state = 'i'; +// } elseif ($state === 'ib') { +// $output .= '</b>'; +// $state = 'i'; +// } elseif ($state === 'both') { +// $output .= '<i><b>' . $buffer . '</b>'; +// $state = 'i'; +// } else { // $state can be 'i' or '' +// $output .= '<b>'; +// $state .= 'b'; +// } +// } elseif ($thislen == 5) { +// if ($state === 'b') { +// $output .= '</b><i>'; +// $state = 'i'; +// } elseif ($state === 'i') { +// $output .= '</i><b>'; +// $state = 'b'; +// } elseif ($state === 'bi') { +// $output .= '</i></b>'; +// $state = ''; +// } elseif ($state === 'ib') { +// $output .= '</b></i>'; +// $state = ''; +// } elseif ($state === 'both') { +// $output .= '<i><b>' . $buffer . '</b></i>'; +// $state = ''; +// } else { // ($state == '') +// $buffer = ''; +// $state = 'both'; +// } // } -// -// $text = this.getConverterLanguage()->markNoConversion($text); -// -// $url = Sanitizer::cleanUrl($url); -// -// // Use the encoded URL -// // This means that users can paste URLs directly into the text -// // Funny characters like � aren't valid in URLs anyway -// // This was changed in August 2004 -// $s .= Linker::makeExternalLink($url, $text, false, $linktype, -// this.getExternalLinkAttribs($url), this.mTitle) . $dtrail . $trail; -// -// // Register link in the output Object. -// // Replace unnecessary URL escape codes with the referenced character -// // This prevents spammers from hiding links from the filters -// $pasteurized = XomwParser.normalizeLinkUrl($url); -// this.mOutput->addExternalLink($pasteurized); // } -// -// return $s; +// $i++; +// } +// // Now close all remaining tags. Notice that the order is important. +// if ($state === 'b' || $state === 'ib') { +// $output .= '</b>'; +// } +// if ($state === 'i' || $state === 'bi' || $state === 'ib') { +// $output .= '</i>'; +// } +// if ($state === 'bi') { +// $output .= '</b>'; +// } +// // There might be lonely ''''', so make sure we have a buffer +// if ($state === 'both' && $buffer) { +// $output .= '<b><i>' . $buffer . '</i></b>'; +// } +// return $output; +// } +// +// /** +// * Replace external links (REL) +// * +// * Note: this is all very hackish and the order of execution matters a lot. +// * Make sure to run tests/parser/parserTests.php if you change this code. +// * +// * @private +// * +// * @param string $text +// * +// * @throws MWException +// * @return string +// */ +// public function replaceExternalLinks($text) { +// $bits = preg_split($this.mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE); +// if ($bits === false) { +// throw new MWException("PCRE needs to be compiled with " +// . "--enable-unicode-properties in order for MediaWiki to function"); // } +// $s = array_shift($bits); // -// /** -// * Get the rel attribute for a particular external link. -// * -// * @since 1.21 -// * @param String|boolean $url Optional URL, to extract the domain from for rel => -// * nofollow if appropriate -// * @param Title $title Optional Title, for wgNoFollowNsExceptions lookups -// * @return String|null Rel attribute for $url -// */ -// public static function getExternalLinkRel($url = false, $title = null) { -// global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions; -// $ns = $title ? $title->getNamespace() : false; -// if ($wgNoFollowLinks && !in_array($ns, $wgNoFollowNsExceptions) -// && !wfMatchesDomainList($url, $wgNoFollowDomainExceptions) -// ) { -// return 'nofollow'; +// $i = 0; +// while ($i < count($bits)) { +// $url = $bits[$i++]; +// $i++; // protocol +// $text = $bits[$i++]; +// $trail = $bits[$i++]; +// +// // The characters '<' and '>' (which were escaped by +// // removeHTMLtags()) should not be included in +// // URLs, per RFC 2396. +// $m2 = []; +// if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE)) { +// $text = substr($url, $m2[0][1]) . ' ' . $text; +// $url = substr($url, 0, $m2[0][1]); // } -// return null; +// +// // If the link text is an image URL, replace it with an <img> tag +// // This happened by accident in the original parser, but some people used it extensively +// $img = $this.maybeMakeExternalImage($text); +// if ($img !== false) { +// $text = $img; +// } +// +// $dtrail = ''; +// +// // Set linktype for CSS +// $linktype = 'text'; +// +// // No link text, e.g. [http://domain.tld/some.link] +// if ($text == '') { +// // Autonumber +// $langObj = $this.getTargetLanguage(); +// $text = '[' . $langObj.formatNum(++$this.mAutonumber) . ']'; +// $linktype = 'autonumber'; +// } else { +// // Have link text, e.g. [http://domain.tld/some.link text]s +// // Check for trail +// list($dtrail, $trail) = Linker::splitTrail($trail); +// } +// +// // Excluding protocol-relative URLs may avoid many false positives. +// if (preg_match('/^(?:' . wfUrlProtocolsWithoutProtRel() . ')/', $text)) { +// $text = $this.getTargetLanguage().getConverter().markNoConversion($text); +// } +// +// $url = Sanitizer::cleanUrl($url); +// +// // Use the encoded URL +// // This means that users can paste URLs directly into the text +// // Funny characters like ö aren't valid in URLs anyway +// // This was changed in August 2004 +// $s .= Linker::makeExternalLink($url, $text, false, $linktype, +// $this.getExternalLinkAttribs($url), $this.mTitle) . $dtrail . $trail; +// +// // Register link in the output object. +// $this.mOutput.addExternalLink($url); +// } +// +// return $s; +// } +// +// /** +// * Get the rel attribute for a particular external link. +// * +// * @since 1.21 +// * @param string|bool $url Optional URL, to extract the domain from for rel => +// * nofollow if appropriate +// * @param Title|null title Optional Title, for wgNoFollowNsExceptions lookups +// * @return string|null Rel attribute for $url +// */ +// public static function getExternalLinkRel($url = false, title = null) { +// global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions; +// $ns = title ? title.getNamespace() : false; +// if ($wgNoFollowLinks && !in_array($ns, $wgNoFollowNsExceptions) +// && !wfMatchesDomainList($url, $wgNoFollowDomainExceptions) +// ) { +// return 'nofollow'; // } +// return null; +// } /** * Get an associative array of additional HTML attributes appropriate for a @@ -1570,162 +2728,545 @@ public class XomwParser implements XomwParserIface { byte[] rel = Get_external_link_rel; // XO.MW.UNSUPPORTED: XO will assume target is blank; MW will set target of "_blank", "_self", etc. depending on global opt - // $target = $this->mOptions->getExternalLinkTarget(); + // $target = $this.mOptions.getExternalLinkTarget(); atrs.Add(Atr__rel, rel); return atrs; } -// /** -// * Replace unusual escape codes in a URL with their equivalent characters -// * -// * This generally follows the syntax defined in RFC 3986, with special -// * consideration for HTTP query strings. -// * -// * @param String $url -// * @return String -// */ -// public static function normalizeLinkUrl($url) { -// // First, make sure unsafe characters are encoded -// $url = preg_replace_callback('/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/', -// function (m) { -// return rawurlencode(m[0]); -// }, -// $url +// /** +// * Get an associative array of additional HTML attributes appropriate for a +// * particular external link. This currently may include rel => nofollow +// * (depending on configuration, namespace, and the URL's domain) and/or a +// * target attribute (depending on configuration). +// * +// * @param string $url URL to extract the domain from for rel => +// * nofollow if appropriate +// * @return array Associative array of HTML attributes +// */ +// public function getExternalLinkAttribs($url) { +// $attribs = []; +// $rel = self::getExternalLinkRel($url, $this.mTitle); +// +// $target = $this.mOptions.getExternalLinkTarget(); +// if ($target) { +// $attribs['target'] = $target; +// if (!in_array($target, [ '_self', '_parent', '_top' ])) { +// // T133507. New windows can navigate parent cross-origin. +// // Including noreferrer due to lacking browser +// // support of noopener. Eventually noreferrer should be removed. +// if ($rel !== '') { +// $rel .= ' '; +// } +// $rel .= 'noreferrer noopener'; +// } +// } +// $attribs['rel'] = $rel; +// return $attribs; +// } +// +// /** +// * Replace unusual escape codes in a URL with their equivalent characters +// * +// * This generally follows the syntax defined in RFC 3986, with special +// * consideration for HTTP query strings. +// * +// * @param string $url +// * @return string +// */ +// public static function normalizeLinkUrl($url) { +// // Test for RFC 3986 IPv6 syntax +// $scheme = '[a-z][a-z0-9+.-]*:'; +// $userinfo = '(?:[a-z0-9\-._~!$&\'()*+,;=:]|%[0-9a-f]{2})*'; +// $ipv6Host = '\\[((?:[0-9a-f:]|%3[0-A]|%[46][1-6])+)\\]'; +// if (preg_match("<^(?:{$scheme})?//(?:{$userinfo}@)?{$ipv6Host}(?:[:/?#].*|)$>i", $url, $m) && +// IP::isValid(rawurldecode($m[1])) +// ) { +// $isIPv6 = rawurldecode($m[1]); +// } else { +// $isIPv6 = false; +// } +// +// // Make sure unsafe characters are encoded +// $url = preg_replace_callback('/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/', +// function ($m) { +// return rawurlencode($m[0]); +// }, +// $url +// ); +// +// $ret = ''; +// $end = strlen($url); +// +// // Fragment part - 'fragment' +// $start = strpos($url, '#'); +// if ($start !== false && $start < $end) { +// $ret = self::normalizeUrlComponent( +// substr($url, $start, $end - $start), '"#%<>[\]^`{|}') . $ret; +// $end = $start; +// } +// +// // Query part - 'query' minus &=+; +// $start = strpos($url, '?'); +// if ($start !== false && $start < $end) { +// $ret = self::normalizeUrlComponent( +// substr($url, $start, $end - $start), '"#%<>[\]^`{|}&=+;') . $ret; +// $end = $start; +// } +// +// // Scheme and path part - 'pchar' +// // (we assume no userinfo or encoded colons in the host) +// $ret = self::normalizeUrlComponent( +// substr($url, 0, $end), '"#%<>[\]^`{|}/?') . $ret; +// +// // Fix IPv6 syntax +// if ($isIPv6 !== false) { +// $ipv6Host = "%5B({$isIPv6})%5D"; +// $ret = preg_replace( +// "<^((?:{$scheme})?//(?:{$userinfo}@)?){$ipv6Host}(?=[:/?#]|$)>i", +// "$1[$2]", +// $ret // ); +// } +// +// return $ret; +// } // -// $ret = ''; -// $end = strlen($url); +// private static function normalizeUrlComponent($component, $unsafe) { +// $callback = function ($matches) use ($unsafe) { +// $char = urldecode($matches[0]); +// $ord = ord($char); +// if ($ord > 32 && $ord < 127 && strpos($unsafe, $char) === false) { +// // Unescape it +// return $char; +// } else { +// // Leave it escaped, but use uppercase for a-f +// return strtoupper($matches[0]); +// } +// }; +// return preg_replace_callback('/%[0-9A-Fa-f]{2}/', $callback, $component); +// } +// +// /** +// * make an image if it's allowed, either through the global +// * option, through the exception, or through the on-wiki whitelist +// * +// * @param string $url +// * +// * @return string +// */ +// private function maybeMakeExternalImage($url) { +// $imagesfrom = $this.mOptions.getAllowExternalImagesFrom(); +// $imagesexception = !empty($imagesfrom); +// $text = false; +// // $imagesfrom could be either a single string or an array of strings, parse out the latter +// if ($imagesexception && is_array($imagesfrom)) { +// $imagematch = false; +// foreach ($imagesfrom as $match) { +// if (strpos($url, $match) === 0) { +// $imagematch = true; +// break; +// } +// } +// } elseif ($imagesexception) { +// $imagematch = (strpos($url, $imagesfrom) === 0); +// } else { +// $imagematch = false; +// } // -// // Fragment part - 'fragment' -// $start = strpos($url, '#'); -// if ($start !== false && $start < $end) { -// $ret = XomwParser.normalizeUrlComponent( -// substr($url, $start, $end - $start), '"#%<>[\]^`{|}') . $ret; -// $end = $start; +// if ($this.mOptions.getAllowExternalImages() +// || ($imagesexception && $imagematch) +// ) { +// if (preg_match(self::EXT_IMAGE_REGEX, $url)) { +// // Image found +// $text = Linker::makeExternalImage($url); // } +// } +// if (!$text && $this.mOptions.getEnableImageWhitelist() +// && preg_match(self::EXT_IMAGE_REGEX, $url) +// ) { +// $whitelist = explode( +// "\n", +// wfMessage('external_image_whitelist').inContentLanguage().text() +// ); // -// // Query part - 'query' minus &=+; -// $start = strpos($url, '?'); -// if ($start !== false && $start < $end) { -// $ret = XomwParser.normalizeUrlComponent( -// substr($url, $start, $end - $start), '"#%<>[\]^`{|}&=+;') . $ret; -// $end = $start; +// foreach ($whitelist as $entry) { +// // Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments +// if (strpos($entry, '#') === 0 || $entry === '') { +// continue; +// } +// if (preg_match('/' . str_replace('/', '\\/', $entry) . '/i', $url)) { +// // Image matches a whitelist entry +// $text = Linker::makeExternalImage($url); +// break; +// } // } +// } +// return $text; +// } +// +// /** +// * Process [[ ]] wikilinks +// * +// * @param string $s +// * +// * @return string Processed text +// * +// * @private +// */ +// public function replaceInternalLinks($s) { +// $this.mLinkHolders.merge($this.replaceInternalLinks2($s)); +// return $s; +// } +// +// /** +// * Process [[ ]] wikilinks (RIL) +// * @param string &$s +// * @throws MWException +// * @return LinkHolderArray +// * +// * @private +// */ +// public function replaceInternalLinks2(&$s) { +// static $tc = false, $e1, $e1_img; +// // the % is needed to support urlencoded titles as well +// if (!$tc) { +// $tc = Title::legalChars() . '#%'; +// // Match a link having the form [[namespace:link|alternate]]trail +// $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD"; +// // Match cases where there is no "]]", which might still be images +// $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; +// } // -// // Scheme and path part - 'pchar' -// // (we assume no userinfo or encoded colons in the host) -// $ret = XomwParser.normalizeUrlComponent( -// substr($url, 0, $end), '"#%<>[\]^`{|}/?') . $ret; +// $holders = new LinkHolderArray($this); +// +// // split the entire text string on occurrences of [[ +// $a = StringUtils::explode('[[', ' ' . $s); +// // get the first element (all text up to first [[), and remove the space we added +// $s = $a.current(); +// $a.next(); +// $line = $a.current(); # Workaround for broken ArrayIterator::next() that returns "void" +// $s = substr($s, 1); +// +// $useLinkPrefixExtension = $this.getTargetLanguage().linkPrefixExtension(); +// $e2 = null; +// if ($useLinkPrefixExtension) { +// // Match the end of a line for a word that's not followed by whitespace, +// // e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched +// $charset = $this.contLang.linkPrefixCharset(); +// $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu"; +// } +// +// if (is_null($this.mTitle)) { +// throw new MWException(__METHOD__ . ": \$this.mTitle is null\n"); +// } +// $nottalk = !$this.mTitle.isTalkPage(); // -// return $ret; +// if ($useLinkPrefixExtension) { +// $m = []; +// if (preg_match($e2, $s, $m)) { +// $first_prefix = $m[2]; +// } else { +// $first_prefix = false; +// } +// } else { +// $prefix = ''; // } // -// private static function normalizeUrlComponent($component, $unsafe) { -// $callback = function (matches) use ($unsafe) { -// $char = urldecode(matches[0]); -// $ord = ord($char); -// if ($ord > 32 && $ord < 127 && strpos($unsafe, $char) === false) { -// // Unescape it -// return $char; +// $useSubpages = $this.areSubpagesAllowed(); +// +// // Loop for each link +// for (; $line !== false && $line !== null; $a.next(), $line = $a.current()) { +// // Check for excessive memory usage +// if ($holders.isBig()) { +// // Too big +// // Do the existence check, replace the link holders and clear the array +// $holders.replace($s); +// $holders.clear(); +// } +// +// if ($useLinkPrefixExtension) { +// if (preg_match($e2, $s, $m)) { +// list(, $s, $prefix) = $m; // } else { -// // Leave it escaped, but use uppercase for a-f -// return strtoupper(matches[0]); +// $prefix = ''; // } -// }; -// return preg_replace_callback('/%[0-9A-Fa-f]{2}/', $callback, $component); -// } +// // first link +// if ($first_prefix) { +// $prefix = $first_prefix; +// $first_prefix = false; +// } +// } // -// /** -// * make an image if it's allowed, either through the global -// * option, through the exception, or through the on-wiki whitelist -// * -// * @param String $url -// * -// * @return String -// */ -// private function maybeMakeExternalImage($url) { -// $imagesfrom = this.mOptions->getAllowExternalImagesFrom(); -// $imagesexception = !empty($imagesfrom); -// $text = false; -// // $imagesfrom could be either a single String or an array of strings, parse out the latter -// if ($imagesexception && is_array($imagesfrom)) { -// $imagematch = false; -// foreach ($imagesfrom as match) { -// if (strpos($url, match) === 0) { -// $imagematch = true; -// break; -// } +// $might_be_img = false; +// +// if (preg_match($e1, $line, $m)) { # page with normal text or alt +// $text = $m[2]; +// // If we get a ] at the beginning of $m[3] that means we have a link that's something like: +// // [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up, +// // the real problem is with the $e1 regex +// // See T1500. +// // Still some problems for cases where the ] is meant to be outside punctuation, +// // and no image is in sight. See T4095. +// if ($text !== '' +// && substr($m[3], 0, 1) === ']' +// && strpos($text, '[') !== false +// ) { +// $text .= ']'; # so that replaceExternalLinks($text) works later +// $m[3] = substr($m[3], 1); +// } +// // fix up urlencoded title texts +// if (strpos($m[1], '%') !== false) { +// // Should anchors '#' also be rejected? +// $m[1] = str_replace([ '<', '>' ], [ '<', '>' ], rawurldecode($m[1])); // } -// } elseif ($imagesexception) { -// $imagematch = (strpos($url, $imagesfrom) === 0); +// $trail = $m[3]; +// } elseif (preg_match($e1_img, $line, $m)) { +// // Invalid, but might be an image with a link in its caption +// $might_be_img = true; +// $text = $m[2]; +// if (strpos($m[1], '%') !== false) { +// $m[1] = str_replace([ '<', '>' ], [ '<', '>' ], rawurldecode($m[1])); +// } +// $trail = ""; +// } else { # Invalid form; output directly +// $s .= $prefix . '[[' . $line; +// continue; +// } +// +// $origLink = ltrim($m[1], ' '); +// +// // Don't allow internal links to pages containing +// // PROTO: where PROTO is a valid URL protocol; these +// // should be external links. +// if (preg_match('/^(?i:' . $this.mUrlProtocols . ')/', $origLink)) { +// $s .= $prefix . '[[' . $line; +// continue; +// } +// +// // Make subpage if necessary +// if ($useSubpages) { +// $link = $this.maybeDoSubpageLink($origLink, $text); // } else { -// $imagematch = false; +// $link = $origLink; // } // -// if (this.mOptions->getAllowExternalImages() -// || ($imagesexception && $imagematch) -// ) { -// if (preg_match(XomwParser.EXT_IMAGE_REGEX, $url)) { -// // Image found -// $text = Linker::makeExternalImage($url); +// // \x7f isn't a default legal title char, so most likely strip +// // markers will force us into the "invalid form" path above. But, +// // just in case, let's assert that xmlish tags aren't valid in +// // the title position. +// $unstrip = $this.mStripState.killMarkers($link); +// $noMarkers = ($unstrip === $link); +// +// $nt = $noMarkers ? Title::newFromText($link) : null; +// if ($nt === null) { +// $s .= $prefix . '[[' . $line; +// continue; +// } +// +// $ns = $nt.getNamespace(); +// $iw = $nt.getInterwiki(); +// +// $noforce = (substr($origLink, 0, 1) !== ':'); +// +// if ($might_be_img) { # if this is actually an invalid link +// if ($ns == NS_FILE && $noforce) { # but might be an image +// $found = false; +// while (true) { +// // look at the next 'line' to see if we can close it there +// $a.next(); +// $next_line = $a.current(); +// if ($next_line === false || $next_line === null) { +// break; +// } +// $m = explode(']]', $next_line, 3); +// if (count($m) == 3) { +// // the first ]] closes the inner link, the second the image +// $found = true; +// $text .= "[[{$m[0]}]]{$m[1]}"; +// $trail = $m[2]; +// break; +// } elseif (count($m) == 2) { +// // if there's exactly one ]] that's fine, we'll keep looking +// $text .= "[[{$m[0]}]]{$m[1]}"; +// } else { +// // if $next_line is invalid too, we need look no further +// $text .= '[[' . $next_line; +// break; +// } +// } +// if (!$found) { +// // we couldn't find the end of this imageLink, so output it raw +// // but don't ignore what might be perfectly normal links in the text we've examined +// $holders.merge($this.replaceInternalLinks2($text)); +// $s .= "{$prefix}[[$link|$text"; +// // note: no $trail, because without an end, there *is* no trail +// continue; +// } +// } else { # it's not an image, so output it raw +// $s .= "{$prefix}[[$link|$text"; +// // note: no $trail, because without an end, there *is* no trail +// continue; // } // } -// if (!$text && this.mOptions->getEnableImageWhitelist() -// && preg_match(XomwParser.EXT_IMAGE_REGEX, $url) -// ) { -// $whitelist = explode( -// "\n", -// wfMessage('external_image_whitelist')->inContentLanguage()->text() -// ); // -// foreach ($whitelist as $entry) { -// // Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments -// if (strpos($entry, '#') === 0 || $entry === '') { +// $wasblank = ($text == ''); +// if ($wasblank) { +// $text = $link; +// if (!$noforce) { +// // Strip off leading ':' +// $text = substr($text, 1); +// } +// } else { +// // T6598 madness. Handle the quotes only if they come from the alternate part +// // [[Lista d''e paise d''o munno]] . <a href="...">Lista d''e paise d''o munno</a> +// // [[Criticism of Harry Potter|Criticism of ''Harry Potter'']] +// // . <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a> +// $text = $this.doQuotes($text); +// } +// +// // Link not escaped by : , create the various objects +// if ($noforce && !$nt.wasLocalInterwiki()) { +// // Interwikis +// if ( +// $iw && $this.mOptions.getInterwikiMagic() && $nottalk && ( +// Language::fetchLanguageName($iw, null, 'mw') || +// in_array($iw, $this.siteConfig.get('ExtraInterlanguageLinkPrefixes')) +// ) +// ) { +// // T26502: filter duplicates +// if (!isset($this.mLangLinkLanguages[$iw])) { +// $this.mLangLinkLanguages[$iw] = true; +// $this.mOutput.addLanguageLink($nt.getFullText()); +// } +// +// /** +// * Strip the whitespace interwiki links produce, see T10897 +// */ +// $s = rtrim($s . $prefix) . $trail; # T175416 +// continue; +// } +// +// if ($ns == NS_FILE) { +// if (!wfIsBadImage($nt.getDBkey(), $this.mTitle)) { +// if ($wasblank) { +// // if no parameters were passed, $text +// // becomes something like "File:Foo.png", +// // which we don't want to pass on to the +// // image generator +// $text = ''; +// } else { +// // recursively parse links inside the image caption +// // actually, this will parse them in any other parameters, too, +// // but it might be hard to fix that, and it doesn't matter ATM +// $text = $this.replaceExternalLinks($text); +// $holders.merge($this.replaceInternalLinks2($text)); +// } +// // cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them +// $s .= $prefix . $this.armorLinks( +// $this.makeImage($nt, $text, $holders)) . $trail; // continue; // } -// if (preg_match('/' . str_replace('/', '\\/', $entry) . '/i', $url)) { -// // Image matches a whitelist entry -// $text = Linker::makeExternalImage($url); -// break; +// } elseif ($ns == NS_CATEGORY) { +// /** +// * Strip the whitespace Category links produce, see T2087 +// */ +// $s = rtrim($s . $prefix) . $trail; # T2087, T87753 +// +// if ($wasblank) { +// $sortkey = $this.getDefaultSort(); +// } else { +// $sortkey = $text; // } +// $sortkey = Sanitizer::decodeCharReferences($sortkey); +// $sortkey = str_replace("\n", '', $sortkey); +// $sortkey = $this.getTargetLanguage().convertCategoryKey($sortkey); +// $this.mOutput.addCategory($nt.getDBkey(), $sortkey); +// +// continue; // } // } -// return $text; -// } - - // XO.MW:MOVED - // public function replaceInternalLinks($s) {} - - // XO.MW:MOVED -// public function replaceInternalLinks2(&$s) {} - -// /** -// * Render a forced-blue link inline; protect against double expansion of -// * URLs if we're in a mode that prepends full URL prefixes to @gplx.Internal protected links. -// * Since this little disaster has to split off the trail text to avoid -// * breaking URLs in the following text without breaking trails on the -// * wiki links, it's been made into a horrible function. -// * -// * @param Title $nt -// * @param String $text -// * @param String $trail -// * @param String $prefix -// * @return String HTML-wikitext mix oh yuck -// */ -// protected function makeKnownLinkHolder($nt, $text = '', $trail = '', $prefix = '') { -// list($inside, $trail) = Linker::splitTrail($trail); // -// if ($text == '') { -// $text = htmlspecialchars($nt->getPrefixedText()); +// // Self-link checking. For some languages, variants of the title are checked in +// // LinkHolderArray::doVariants() to allow batching the existence checks necessary +// // for linking to a different variant. +// if ($ns != NS_SPECIAL && $nt.equals($this.mTitle) && !$nt.hasFragment()) { +// $s .= $prefix . Linker::makeSelfLinkObj($nt, $text, '', $trail); +// continue; // } // -// $link = this.getLinkRenderer()->makeKnownLink( -// $nt, new HtmlArmor("$prefix$text$inside") -// ); -// -// return this.armorLinks($link) . $trail; +// // NS_MEDIA is a pseudo-namespace for linking directly to a file +// // @todo FIXME: Should do batch file existence checks, see comment below +// if ($ns == NS_MEDIA) { +// // Give extensions a chance to select the file revision for us +// $options = []; +// $descQuery = false; +// Hooks::run('BeforeParserFetchFileAndTitle', +// [ $this, $nt, &$options, &$descQuery ]); +// // Fetch and register the file (file title may be different via hooks) +// list($file, $nt) = $this.fetchFileAndTitle($nt, $options); +// // Cloak with NOPARSE to avoid replacement in replaceExternalLinks +// $s .= $prefix . $this.armorLinks( +// Linker::makeMediaLinkFile($nt, $file, $text)) . $trail; +// continue; +// } +// +// // Some titles, such as valid special pages or files in foreign repos, should +// // be shown as bluelinks even though they're not included in the page table +// // @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do +// // batch file existence checks for NS_FILE and NS_MEDIA +// if ($iw == '' && $nt.isAlwaysKnown()) { +// $this.mOutput.addLink($nt); +// $s .= $this.makeKnownLinkHolder($nt, $text, $trail, $prefix); +// } else { +// // Links will be added to the output link list after checking +// $s .= $holders.makeHolder($nt, $text, [], $trail, $prefix); +// } +// } +// return $holders; +// } +// +// /** +// * Render a forced-blue link inline; protect against double expansion of +// * URLs if we're in a mode that prepends full URL prefixes to internal links. +// * Since this little disaster has to split off the trail text to avoid +// * breaking URLs in the following text without breaking trails on the +// * wiki links, it's been made into a horrible function. +// * +// * @param Title $nt +// * @param string $text +// * @param string $trail +// * @param string $prefix +// * @return string HTML-wikitext mix oh yuck +// */ +// protected function makeKnownLinkHolder($nt, $text = '', $trail = '', $prefix = '') { +// list($inside, $trail) = Linker::splitTrail($trail); +// +// if ($text == '') { +// $text = htmlspecialchars($nt.getPrefixedText()); // } // +// $link = $this.getLinkRenderer().makeKnownLink( +// $nt, new HtmlArmor("$prefix$text$inside") +// ); +// +// return $this.armorLinks($link) . $trail; +// } +// MW.SRC:1.33.1 +// /** +// * Insert a NOPARSE hacky thing into any inline links in a chunk that's +// * going to go through further parsing steps before inline URL expansion. +// * +// * Not needed quite as much as it used to be since free links are a bit +// * more sensible these days. But bracketed links are still an issue. +// * +// * @param string $text More-or-less HTML +// * @return string Less-or-more HTML with NOPARSE bits +// */ +// public function armorLinks($text) { +// return preg_replace('/\b((?i)' . $this.mUrlProtocols . ')/', +// self::MARKER_PREFIX . "NOPARSE$1", $text); +// } + /** * Insert a NOPARSE hacky thing into any inline links in a chunk that's * going to go through further parsing steps before inline URL expansion. @@ -1788,413 +3329,445 @@ public class XomwParser implements XomwParserIface { } } } - -// /** -// * Return true if subpage links should be expanded on this page. -// * @return boolean -// */ -// public function areSubpagesAllowed() { -// // Some namespaces don't allow subpages -// return MWNamespace::hasSubpages(this.mTitle->getNamespace()); +// MW.SRC:1.33.1 +// /** +// * Return true if subpage links should be expanded on this page. +// * @return bool +// */ +// public function areSubpagesAllowed() { +// // Some namespaces don't allow subpages +// return MWNamespace::hasSubpages($this.mTitle.getNamespace()); +// } +// +// /** +// * Handle link to subpage if necessary +// * +// * @param string $target The source of the link +// * @param string &$text The link text, modified as necessary +// * @return string The full name of the link +// * @private +// */ +// public function maybeDoSubpageLink($target, &$text) { +// return Linker::normalizeSubpageLink($this.mTitle, $target, $text); +// } +// +// /** +// * Make lists from lines starting with ':', '*', '#', etc. (DBL) +// * +// * @param string $text +// * @param bool $linestart Whether or not this is at the start of a line. +// * @private +// * @return string The lists rendered as HTML +// */ +// public function doBlockLevels($text, $linestart) { +// return BlockLevelPass::doBlockLevels($text, $linestart); +// } +// +// /** +// * Return value of a magic variable (like PAGENAME) +// * +// * @private +// * +// * @param string $index Magic variable identifier as mapped in MagicWordFactory::$mVariableIDs +// * @param bool|PPFrame $frame +// * +// * @throws MWException +// * @return string +// */ +// public function getVariableValue($index, $frame = false) { +// if (is_null($this.mTitle)) { +// // If no title set, bad things are going to happen +// // later. Title should always be set since this +// // should only be called in the middle of a parse +// // operation (but the unit-tests do funky stuff) +// throw new MWException(__METHOD__ . ' Should only be ' +// . ' called while parsing (no title set)'); // } // -// /** -// * Handle link to subpage if necessary -// * -// * @param String $target The source of the link -// * @param String &$text The link text, modified as necessary -// * @return String The full name of the link -// * @private -// */ -// public function maybeDoSubpageLink($target, &$text) { -// return Linker::normalizeSubpageLink(this.mTitle, $target, $text); -// } +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; // // /** -// * Make lists from lines starting with ':', '*', '#', etc. (DBL) -// * -// * @param String $text -// * @param boolean $linestart Whether or not this is at the start of a line. -// * @private -// * @return String The lists rendered as HTML -// */ -// public function doBlockLevels($text, $linestart) { -// return BlockLevelPass::doBlockLevels($text, $linestart); +// * Some of these require message or data lookups and can be +// * expensive to check many times. +// */ +// if (Hooks::run('ParserGetVariableValueVarCache', [ &$parser, &$this.mVarCache ]) +// && isset($this.mVarCache[$index]) +// ) { +// return $this.mVarCache[$index]; // } // -// /** -// * Return value of a magic variable (like PAGENAME) -// * -// * @private -// * -// * @param int $index -// * @param boolean|PPFrame $frame -// * -// * @throws MWException -// * @return String -// */ -// public function getVariableValue($index, $frame = false) { -// global $wgContLang, $wgSitename, $wgServer, $wgServerName; -// global $wgArticlePath, $wgScriptPath, $wgStylePath; -// -// if (is_null(this.mTitle)) { -// // If no title set, bad things are going to happen -// // later. Title should always be set since this -// // should only be called in the middle of a parse -// // operation (but the unit-tests do funky stuff) -// throw new MWException(__METHOD__ . ' Should only be ' -// . ' called while parsing (no title set)'); -// } -// -// /** -// * Some of these require message or data lookups and can be -// * expensive to check many times. -// */ -// if (Hooks::run('ParserGetVariableValueVarCache', [ &$this, &this.mVarCache ])) { -// if (isset(this.mVarCache[$index])) { -// return this.mVarCache[$index]; +// $ts = wfTimestamp(TS_UNIX, $this.mOptions.getTimestamp()); +// Hooks::run('ParserGetVariableValueTs', [ &$parser, &$ts ]); +// +// // In miser mode, disable words that always cause double-parses on page save (T137900) +// static $slowRevWords = [ 'revisionid' => true ]; // @TODO: 'revisiontimestamp' +// if ( +// isset($slowRevWords[$index]) && +// $this.siteConfig.get('MiserMode') && +// !$this.mOptions.getInterfaceMessage() && +// // @TODO: disallow this word on all namespaces +// MWNamespace::isContent($this.mTitle.getNamespace()) +// ) { +// return $this.mRevisionId ? '-' : ''; +// }; +// +// $pageLang = $this.getFunctionLang(); +// +// switch ($index) { +// case '!': +// $value = '|'; +// break; +// case 'currentmonth': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('m'), true); +// break; +// case 'currentmonth1': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('n'), true); +// break; +// case 'currentmonthname': +// $value = $pageLang.getMonthName(MWTimestamp::getInstance($ts).format('n')); +// break; +// case 'currentmonthnamegen': +// $value = $pageLang.getMonthNameGen(MWTimestamp::getInstance($ts).format('n')); +// break; +// case 'currentmonthabbrev': +// $value = $pageLang.getMonthAbbreviation(MWTimestamp::getInstance($ts).format('n')); +// break; +// case 'currentday': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('j'), true); +// break; +// case 'currentday2': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('d'), true); +// break; +// case 'localmonth': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('m'), true); +// break; +// case 'localmonth1': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('n'), true); +// break; +// case 'localmonthname': +// $value = $pageLang.getMonthName(MWTimestamp::getLocalInstance($ts).format('n')); +// break; +// case 'localmonthnamegen': +// $value = $pageLang.getMonthNameGen(MWTimestamp::getLocalInstance($ts).format('n')); +// break; +// case 'localmonthabbrev': +// $value = $pageLang.getMonthAbbreviation(MWTimestamp::getLocalInstance($ts).format('n')); +// break; +// case 'localday': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('j'), true); +// break; +// case 'localday2': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('d'), true); +// break; +// case 'pagename': +// $value = wfEscapeWikiText($this.mTitle.getText()); +// break; +// case 'pagenamee': +// $value = wfEscapeWikiText($this.mTitle.getPartialURL()); +// break; +// case 'fullpagename': +// $value = wfEscapeWikiText($this.mTitle.getPrefixedText()); +// break; +// case 'fullpagenamee': +// $value = wfEscapeWikiText($this.mTitle.getPrefixedURL()); +// break; +// case 'subpagename': +// $value = wfEscapeWikiText($this.mTitle.getSubpageText()); +// break; +// case 'subpagenamee': +// $value = wfEscapeWikiText($this.mTitle.getSubpageUrlForm()); +// break; +// case 'rootpagename': +// $value = wfEscapeWikiText($this.mTitle.getRootText()); +// break; +// case 'rootpagenamee': +// $value = wfEscapeWikiText(wfUrlencode(str_replace( +// ' ', +// '_', +// $this.mTitle.getRootText() +// ))); +// break; +// case 'basepagename': +// $value = wfEscapeWikiText($this.mTitle.getBaseText()); +// break; +// case 'basepagenamee': +// $value = wfEscapeWikiText(wfUrlencode(str_replace( +// ' ', +// '_', +// $this.mTitle.getBaseText() +// ))); +// break; +// case 'talkpagename': +// if ($this.mTitle.canHaveTalkPage()) { +// $talkPage = $this.mTitle.getTalkPage(); +// $value = wfEscapeWikiText($talkPage.getPrefixedText()); +// } else { +// $value = ''; +// } +// break; +// case 'talkpagenamee': +// if ($this.mTitle.canHaveTalkPage()) { +// $talkPage = $this.mTitle.getTalkPage(); +// $value = wfEscapeWikiText($talkPage.getPrefixedURL()); +// } else { +// $value = ''; +// } +// break; +// case 'subjectpagename': +// $subjPage = $this.mTitle.getSubjectPage(); +// $value = wfEscapeWikiText($subjPage.getPrefixedText()); +// break; +// case 'subjectpagenamee': +// $subjPage = $this.mTitle.getSubjectPage(); +// $value = wfEscapeWikiText($subjPage.getPrefixedURL()); +// break; +// case 'pageid': // requested in T25427 +// $pageid = $this.getTitle().getArticleID(); +// if ($pageid == 0) { +// // 0 means the page doesn't exist in the database, +// // which means the user is previewing a new page. +// // The vary-revision flag must be set, because the magic word +// // will have a different value once the page is saved. +// $this.mOutput.setFlag('vary-revision'); +// wfDebug(__METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n"); +// } +// $value = $pageid ?: null; +// break; +// case 'revisionid': +// // Let the edit saving system know we should parse the page +// // *after* a revision ID has been assigned. +// $this.mOutput.setFlag('vary-revision-id'); +// wfDebug(__METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n"); +// $value = $this.mRevisionId; +// +// if (!$value) { +// $rev = $this.getRevisionObject(); +// if ($rev) { +// $value = $rev.getId(); +// } // } -// } // -// $ts = wfTimestamp(TS_UNIX, this.mOptions->getTimestamp()); -// Hooks::run('ParserGetVariableValueTs', [ &$this, &$ts ]); +// if (!$value) { +// $value = $this.mOptions.getSpeculativeRevId(); +// if ($value) { +// $this.mOutput.setSpeculativeRevIdUsed($value); +// } +// } +// break; +// case 'revisionday': +// $value = (int)$this.getRevisionTimestampSubstring(6, 2, self::MAX_TTS, $index); +// break; +// case 'revisionday2': +// $value = $this.getRevisionTimestampSubstring(6, 2, self::MAX_TTS, $index); +// break; +// case 'revisionmonth': +// $value = $this.getRevisionTimestampSubstring(4, 2, self::MAX_TTS, $index); +// break; +// case 'revisionmonth1': +// $value = (int)$this.getRevisionTimestampSubstring(4, 2, self::MAX_TTS, $index); +// break; +// case 'revisionyear': +// $value = $this.getRevisionTimestampSubstring(0, 4, self::MAX_TTS, $index); +// break; +// case 'revisiontimestamp': +// // Let the edit saving system know we should parse the page +// // *after* a revision ID has been assigned. This is for null edits. +// $this.mOutput.setFlag('vary-revision'); +// wfDebug(__METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n"); +// $value = $this.getRevisionTimestamp(); +// break; +// case 'revisionuser': +// // Let the edit saving system know we should parse the page +// // *after* a revision ID has been assigned for null edits. +// $this.mOutput.setFlag('vary-user'); +// wfDebug(__METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n"); +// $value = $this.getRevisionUser(); +// break; +// case 'revisionsize': +// $value = $this.getRevisionSize(); +// break; +// case 'namespace': +// $value = str_replace('_', ' ', +// $this.contLang.getNsText($this.mTitle.getNamespace())); +// break; +// case 'namespacee': +// $value = wfUrlencode($this.contLang.getNsText($this.mTitle.getNamespace())); +// break; +// case 'namespacenumber': +// $value = $this.mTitle.getNamespace(); +// break; +// case 'talkspace': +// $value = $this.mTitle.canHaveTalkPage() +// ? str_replace('_', ' ', $this.mTitle.getTalkNsText()) +// : ''; +// break; +// case 'talkspacee': +// $value = $this.mTitle.canHaveTalkPage() ? wfUrlencode($this.mTitle.getTalkNsText()) : ''; +// break; +// case 'subjectspace': +// $value = str_replace('_', ' ', $this.mTitle.getSubjectNsText()); +// break; +// case 'subjectspacee': +// $value = (wfUrlencode($this.mTitle.getSubjectNsText())); +// break; +// case 'currentdayname': +// $value = $pageLang.getWeekdayName((int)MWTimestamp::getInstance($ts).format('w') + 1); +// break; +// case 'currentyear': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('Y'), true); +// break; +// case 'currenttime': +// $value = $pageLang.time(wfTimestamp(TS_MW, $ts), false, false); +// break; +// case 'currenthour': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('H'), true); +// break; +// case 'currentweek': +// // @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to +// // int to remove the padding +// $value = $pageLang.formatNum((int)MWTimestamp::getInstance($ts).format('W')); +// break; +// case 'currentdow': +// $value = $pageLang.formatNum(MWTimestamp::getInstance($ts).format('w')); +// break; +// case 'localdayname': +// $value = $pageLang.getWeekdayName( +// (int)MWTimestamp::getLocalInstance($ts).format('w') + 1 +// ); +// break; +// case 'localyear': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('Y'), true); +// break; +// case 'localtime': +// $value = $pageLang.time( +// MWTimestamp::getLocalInstance($ts).format('YmdHis'), +// false, +// false +// ); +// break; +// case 'localhour': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('H'), true); +// break; +// case 'localweek': +// // @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to +// // int to remove the padding +// $value = $pageLang.formatNum((int)MWTimestamp::getLocalInstance($ts).format('W')); +// break; +// case 'localdow': +// $value = $pageLang.formatNum(MWTimestamp::getLocalInstance($ts).format('w')); +// break; +// case 'numberofarticles': +// $value = $pageLang.formatNum(SiteStats::articles()); +// break; +// case 'numberoffiles': +// $value = $pageLang.formatNum(SiteStats::images()); +// break; +// case 'numberofusers': +// $value = $pageLang.formatNum(SiteStats::users()); +// break; +// case 'numberofactiveusers': +// $value = $pageLang.formatNum(SiteStats::activeUsers()); +// break; +// case 'numberofpages': +// $value = $pageLang.formatNum(SiteStats::pages()); +// break; +// case 'numberofadmins': +// $value = $pageLang.formatNum(SiteStats::numberingroup('sysop')); +// break; +// case 'numberofedits': +// $value = $pageLang.formatNum(SiteStats::edits()); +// break; +// case 'currenttimestamp': +// $value = wfTimestamp(TS_MW, $ts); +// break; +// case 'localtimestamp': +// $value = MWTimestamp::getLocalInstance($ts).format('YmdHis'); +// break; +// case 'currentversion': +// $value = SpecialVersion::getVersion(); +// break; +// case 'articlepath': +// return $this.siteConfig.get('ArticlePath'); +// case 'sitename': +// return $this.siteConfig.get('Sitename'); +// case 'server': +// return $this.siteConfig.get('Server'); +// case 'servername': +// return $this.siteConfig.get('ServerName'); +// case 'scriptpath': +// return $this.siteConfig.get('ScriptPath'); +// case 'stylepath': +// return $this.siteConfig.get('StylePath'); +// case 'directionmark': +// return $pageLang.getDirMark(); +// case 'contentlanguage': +// return $this.siteConfig.get('LanguageCode'); +// case 'pagelanguage': +// $value = $pageLang.getCode(); +// break; +// case 'cascadingsources': +// $value = CoreParserFunctions::cascadingsources($this); +// break; +// default: +// $ret = null; +// Hooks::run( +// 'ParserGetVariableValueSwitch', +// [ &$parser, &$this.mVarCache, &$index, &$ret, &$frame ] +// ); // -// $pageLang = this.getFunctionLang(); +// return $ret; +// } // -// switch ($index) { -// case '!': -// $value = '|'; -// break; -// case 'currentmonth': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('m')); -// break; -// case 'currentmonth1': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('n')); -// break; -// case 'currentmonthname': -// $value = $pageLang->getMonthName(MWTimestamp::getInstance($ts)->format('n')); -// break; -// case 'currentmonthnamegen': -// $value = $pageLang->getMonthNameGen(MWTimestamp::getInstance($ts)->format('n')); -// break; -// case 'currentmonthabbrev': -// $value = $pageLang->getMonthAbbreviation(MWTimestamp::getInstance($ts)->format('n')); -// break; -// case 'currentday': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('j')); -// break; -// case 'currentday2': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('d')); -// break; -// case 'localmonth': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('m')); -// break; -// case 'localmonth1': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('n')); -// break; -// case 'localmonthname': -// $value = $pageLang->getMonthName(MWTimestamp::getLocalInstance($ts)->format('n')); -// break; -// case 'localmonthnamegen': -// $value = $pageLang->getMonthNameGen(MWTimestamp::getLocalInstance($ts)->format('n')); -// break; -// case 'localmonthabbrev': -// $value = $pageLang->getMonthAbbreviation(MWTimestamp::getLocalInstance($ts)->format('n')); -// break; -// case 'localday': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('j')); -// break; -// case 'localday2': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('d')); -// break; -// case 'pagename': -// $value = wfEscapeWikiText(this.mTitle->getText()); -// break; -// case 'pagenamee': -// $value = wfEscapeWikiText(this.mTitle->getPartialURL()); -// break; -// case 'fullpagename': -// $value = wfEscapeWikiText(this.mTitle->getPrefixedText()); -// break; -// case 'fullpagenamee': -// $value = wfEscapeWikiText(this.mTitle->getPrefixedURL()); -// break; -// case 'subpagename': -// $value = wfEscapeWikiText(this.mTitle->getSubpageText()); -// break; -// case 'subpagenamee': -// $value = wfEscapeWikiText(this.mTitle->getSubpageUrlForm()); -// break; -// case 'rootpagename': -// $value = wfEscapeWikiText(this.mTitle->getRootText()); -// break; -// case 'rootpagenamee': -// $value = wfEscapeWikiText(wfUrlencode(str_replace( -// ' ', -// '_', -// this.mTitle->getRootText() -// ))); -// break; -// case 'basepagename': -// $value = wfEscapeWikiText(this.mTitle->getBaseText()); -// break; -// case 'basepagenamee': -// $value = wfEscapeWikiText(wfUrlencode(str_replace( -// ' ', -// '_', -// this.mTitle->getBaseText() -// ))); -// break; -// case 'talkpagename': -// if (this.mTitle->canTalk()) { -// $talkPage = this.mTitle->getTalkPage(); -// $value = wfEscapeWikiText($talkPage->getPrefixedText()); -// } else { -// $value = ''; -// } -// break; -// case 'talkpagenamee': -// if (this.mTitle->canTalk()) { -// $talkPage = this.mTitle->getTalkPage(); -// $value = wfEscapeWikiText($talkPage->getPrefixedURL()); -// } else { -// $value = ''; -// } -// break; -// case 'subjectpagename': -// $subjPage = this.mTitle->getSubjectPage(); -// $value = wfEscapeWikiText($subjPage->getPrefixedText()); -// break; -// case 'subjectpagenamee': -// $subjPage = this.mTitle->getSubjectPage(); -// $value = wfEscapeWikiText($subjPage->getPrefixedURL()); -// break; -// case 'pageid': // requested in T25427 -// $pageid = this.getTitle()->getArticleID(); -// if ($pageid == 0) { -// // 0 means the page doesn't exist in the database, -// // which means the user is previewing a new page. -// // The vary-revision flag must be set, because the magic word -// // will have a different value once the page is saved. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n"); -// } -// $value = $pageid ? $pageid : null; -// break; -// case 'revisionid': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. -// this.mOutput->setFlag('vary-revision-id'); -// wfDebug(__METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n"); -// $value = this.mRevisionId; -// if (!$value && this.mOptions->getSpeculativeRevIdCallback()) { -// $value = call_user_func(this.mOptions->getSpeculativeRevIdCallback()); -// this.mOutput->setSpeculativeRevIdUsed($value); -// } -// break; -// case 'revisionday': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. This is for null edits. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n"); -// $value = intval(substr(this.getRevisionTimestamp(), 6, 2)); -// break; -// case 'revisionday2': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. This is for null edits. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n"); -// $value = substr(this.getRevisionTimestamp(), 6, 2); -// break; -// case 'revisionmonth': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. This is for null edits. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n"); -// $value = substr(this.getRevisionTimestamp(), 4, 2); -// break; -// case 'revisionmonth1': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. This is for null edits. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n"); -// $value = intval(substr(this.getRevisionTimestamp(), 4, 2)); -// break; -// case 'revisionyear': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. This is for null edits. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n"); -// $value = substr(this.getRevisionTimestamp(), 0, 4); -// break; -// case 'revisiontimestamp': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned. This is for null edits. -// this.mOutput->setFlag('vary-revision'); -// wfDebug(__METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n"); -// $value = this.getRevisionTimestamp(); -// break; -// case 'revisionuser': -// // Let the edit saving system know we should parse the page -// // *after* a revision ID has been assigned for null edits. -// this.mOutput->setFlag('vary-user'); -// wfDebug(__METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n"); -// $value = this.getRevisionUser(); -// break; -// case 'revisionsize': -// $value = this.getRevisionSize(); -// break; -// case 'namespace': -// $value = str_replace('_', ' ', $wgContLang->getNsText(this.mTitle->getNamespace())); -// break; -// case 'namespacee': -// $value = wfUrlencode($wgContLang->getNsText(this.mTitle->getNamespace())); -// break; -// case 'namespacenumber': -// $value = this.mTitle->getNamespace(); -// break; -// case 'talkspace': -// $value = this.mTitle->canTalk() -// ? str_replace('_', ' ', this.mTitle->getTalkNsText()) -// : ''; -// break; -// case 'talkspacee': -// $value = this.mTitle->canTalk() ? wfUrlencode(this.mTitle->getTalkNsText()) : ''; -// break; -// case 'subjectspace': -// $value = str_replace('_', ' ', this.mTitle->getSubjectNsText()); -// break; -// case 'subjectspacee': -// $value = (wfUrlencode(this.mTitle->getSubjectNsText())); -// break; -// case 'currentdayname': -// $value = $pageLang->getWeekdayName((int)MWTimestamp::getInstance($ts)->format('w') + 1); -// break; -// case 'currentyear': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('Y'), true); -// break; -// case 'currenttime': -// $value = $pageLang->time(wfTimestamp(TS_MW, $ts), false, false); -// break; -// case 'currenthour': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('H'), true); -// break; -// case 'currentweek': -// // @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to -// // int to remove the padding -// $value = $pageLang->formatNum((int)MWTimestamp::getInstance($ts)->format('W')); -// break; -// case 'currentdow': -// $value = $pageLang->formatNum(MWTimestamp::getInstance($ts)->format('w')); -// break; -// case 'localdayname': -// $value = $pageLang->getWeekdayName( -// (int)MWTimestamp::getLocalInstance($ts)->format('w') + 1 -// ); -// break; -// case 'localyear': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('Y'), true); -// break; -// case 'localtime': -// $value = $pageLang->time( -// MWTimestamp::getLocalInstance($ts)->format('YmdHis'), -// false, -// false -// ); -// break; -// case 'localhour': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('H'), true); -// break; -// case 'localweek': -// // @bug T6594 PHP5 has it zero padded, PHP4 does not, cast to -// // int to remove the padding -// $value = $pageLang->formatNum((int)MWTimestamp::getLocalInstance($ts)->format('W')); -// break; -// case 'localdow': -// $value = $pageLang->formatNum(MWTimestamp::getLocalInstance($ts)->format('w')); -// break; -// case 'numberofarticles': -// $value = $pageLang->formatNum(SiteStats::articles()); -// break; -// case 'numberoffiles': -// $value = $pageLang->formatNum(SiteStats::images()); -// break; -// case 'numberofusers': -// $value = $pageLang->formatNum(SiteStats::users()); -// break; -// case 'numberofactiveusers': -// $value = $pageLang->formatNum(SiteStats::activeUsers()); -// break; -// case 'numberofpages': -// $value = $pageLang->formatNum(SiteStats::pages()); -// break; -// case 'numberofadmins': -// $value = $pageLang->formatNum(SiteStats::numberingroup('sysop')); -// break; -// case 'numberofedits': -// $value = $pageLang->formatNum(SiteStats::edits()); -// break; -// case 'currenttimestamp': -// $value = wfTimestamp(TS_MW, $ts); -// break; -// case 'localtimestamp': -// $value = MWTimestamp::getLocalInstance($ts)->format('YmdHis'); -// break; -// case 'currentversion': -// $value = SpecialVersion::getVersion(); -// break; -// case 'articlepath': -// return $wgArticlePath; -// case 'sitename': -// return $wgSitename; -// case 'server': -// return $wgServer; -// case 'servername': -// return $wgServerName; -// case 'scriptpath': -// return $wgScriptPath; -// case 'stylepath': -// return $wgStylePath; -// case 'directionmark': -// return $pageLang->getDirMark(); -// case 'contentlanguage': -// global $wgLanguageCode; -// return $wgLanguageCode; -// case 'pagelanguage': -// $value = $pageLang->getCode(); -// break; -// case 'cascadingsources': -// $value = CoreParserFunctions::cascadingsources($this); -// break; -// default: -// $ret = null; -// Hooks::run( -// 'ParserGetVariableValueSwitch', -// [ &$this, &this.mVarCache, &$index, &$ret, &$frame ] -// ); +// if ($index) { +// $this.mVarCache[$index] = $value; +// } // -// return $ret; -// } +// return $value; +// } +// MW.SRC:1.33.1 +// /** +// * @param int $start +// * @param int $len +// * @param int $mtts Max time-till-save; sets vary-revision if result might change by then +// * @param string $variable Parser variable name +// * @return string +// */ +// private function getRevisionTimestampSubstring($start, $len, $mtts, $variable) { +// // Get the timezone-adjusted timestamp to be used for this revision +// $resNow = substr($this.getRevisionTimestamp(), $start, $len); +// // Possibly set vary-revision if there is not yet an associated revision +// if (!$this.getRevisionObject()) { +// // Get the timezone-adjusted timestamp $mtts seconds in the future +// $resThen = substr( +// $this.contLang.userAdjust(wfTimestamp(TS_MW, time() + $mtts), ''), +// $start, +// $len +// ); // -// if ($index) { -// this.mVarCache[$index] = $value; +// if ($resNow !== $resThen) { +// // Let the edit saving system know we should parse the page +// // *after* a revision ID has been assigned. This is for null edits. +// $this.mOutput.setFlag('vary-revision'); +// wfDebug(__METHOD__ . ": $variable used, setting vary-revision...\n"); // } -// -// return $value; // } // -// /** -// * initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers -// * -// * @private -// */ -// public function initialiseVariables() { -// $variableIDs = MagicWord::getVariableIDs(); -// $substIDs = MagicWord::getSubstIDs(); -// -// this.mVariables = new MagicWordArray($variableIDs); -// this.mSubstWords = new MagicWordArray($substIDs); -// } +// return $resNow; +// } +// +// /** +// * initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers +// * +// * @private +// */ +// public function initialiseVariables() { +// $variableIDs = $this.magicWordFactory.getVariableIDs(); +// $substIDs = $this.magicWordFactory.getSubstIDs(); +// +// $this.mVariables = $this.magicWordFactory.newArray($variableIDs); +// $this.mSubstWords = $this.magicWordFactory.newArray($substIDs); +// } /** * Preprocess some wikitext and return the document tree. @@ -2223,25 +3796,26 @@ public class XomwParser implements XomwParserIface { return dom; } -// /** -// * Return a three-element array: leading whitespace, String contents, trailing whitespace -// * -// * @param String $s -// * -// * @return array -// */ -// public static function splitWhitespace($s) { -// $ltrimmed = ltrim($s); -// $w1 = substr($s, 0, strlen($s) - strlen($ltrimmed)); -// $trimmed = rtrim($ltrimmed); -// $diff = strlen($ltrimmed) - strlen($trimmed); -// if ($diff > 0) { -// $w2 = substr($ltrimmed, -$diff); -// } else { -// $w2 = ''; -// } -// return [ $w1, $trimmed, $w2 ]; +// MW.SRC:1.33.1 +// /** +// * Return a three-element array: leading whitespace, string contents, trailing whitespace +// * +// * @param string $s +// * +// * @return array +// */ +// public static function splitWhitespace($s) { +// $ltrimmed = ltrim($s); +// $w1 = substr($s, 0, strlen($s) - strlen($ltrimmed)); +// $trimmed = rtrim($ltrimmed); +// $diff = strlen($ltrimmed) - strlen($trimmed); +// if ($diff > 0) { +// $w2 = substr($ltrimmed, -$diff); +// } else { +// $w2 = ''; // } +// return [ $w1, $trimmed, $w2 ]; +// } /** * Replace magic variables, templates, and template arguments @@ -2284,71 +3858,72 @@ public class XomwParser implements XomwParserIface { return text; } -// /** -// * Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. -// * -// * @param array $args -// * -// * @return array -// */ -// public static function createAssocArgs($args) { -// $assocArgs = []; -// $index = 1; -// foreach ($args as $arg) { -// $eqpos = strpos($arg, '='); -// if ($eqpos === false) { -// $assocArgs[$index++] = $arg; -// } else { -// $name = trim(substr($arg, 0, $eqpos)); -// $value = trim(substr($arg, $eqpos + 1)); -// if ($value === false) { -// $value = ''; -// } -// if ($name !== false) { -// $assocArgs[$name] = $value; -// } +// MW.SRC:1.33.1 +// /** +// * Clean up argument array - refactored in 1.9 so parserfunctions can use it, too. +// * +// * @param array $args +// * +// * @return array +// */ +// public static function createAssocArgs($args) { +// $assocArgs = []; +// $index = 1; +// foreach ($args as $arg) { +// $eqpos = strpos($arg, '='); +// if ($eqpos === false) { +// $assocArgs[$index++] = $arg; +// } else { +// $name = trim(substr($arg, 0, $eqpos)); +// $value = trim(substr($arg, $eqpos + 1)); +// if ($value === false) { +// $value = ''; +// } +// if ($name !== false) { +// $assocArgs[$name] = $value; // } // } -// -// return $assocArgs; -// } -// -// /** -// * Warn the user when a parser limitation is reached -// * Will warn at most once the user per limitation type -// * -// * The results are shown during preview and run through the Parser (See EditPage.php) -// * -// * @param String $limitationType Should be one of: -// * 'expensive-parserfunction' (corresponding messages: -// * 'expensive-parserfunction-warning', -// * 'expensive-parserfunction-category') -// * 'post-expand-template-argument' (corresponding messages: -// * 'post-expand-template-argument-warning', -// * 'post-expand-template-argument-category') -// * 'post-expand-template-inclusion' (corresponding messages: -// * 'post-expand-template-inclusion-warning', -// * 'post-expand-template-inclusion-category') -// * 'node-count-exceeded' (corresponding messages: -// * 'node-count-exceeded-warning', -// * 'node-count-exceeded-category') -// * 'expansion-depth-exceeded' (corresponding messages: -// * 'expansion-depth-exceeded-warning', -// * 'expansion-depth-exceeded-category') -// * @param String|int|null $current Current value -// * @param String|int|null max Maximum allowed, when an explicit limit has been -// * exceeded, provide the values (optional) -// */ -// public function limitationWarn($limitationType, $current = '', max = '') { -// // does no harm if $current and max are present but are unnecessary for the message -// // Not doing ->inLanguage(this.mOptions->getUserLangObj()), since this is shown -// // only during preview, and that would split the parser cache unnecessarily. -// $warning = wfMessage("$limitationType-warning")->numParams($current, max) -// ->text(); -// this.mOutput->addWarning($warning); -// this.addTrackingCategory("$limitationType-category"); // } // +// return $assocArgs; +// } +// +// /** +// * Warn the user when a parser limitation is reached +// * Will warn at most once the user per limitation type +// * +// * The results are shown during preview and run through the Parser (See EditPage.php) +// * +// * @param string $limitationType Should be one of: +// * 'expensive-parserfunction' (corresponding messages: +// * 'expensive-parserfunction-warning', +// * 'expensive-parserfunction-category') +// * 'post-expand-template-argument' (corresponding messages: +// * 'post-expand-template-argument-warning', +// * 'post-expand-template-argument-category') +// * 'post-expand-template-inclusion' (corresponding messages: +// * 'post-expand-template-inclusion-warning', +// * 'post-expand-template-inclusion-category') +// * 'node-count-exceeded' (corresponding messages: +// * 'node-count-exceeded-warning', +// * 'node-count-exceeded-category') +// * 'expansion-depth-exceeded' (corresponding messages: +// * 'expansion-depth-exceeded-warning', +// * 'expansion-depth-exceeded-category') +// * @param string|int|null $current Current value +// * @param string|int|null $max Maximum allowed, when an explicit limit has been +// * exceeded, provide the values (optional) +// */ +// public function limitationWarn($limitationType, $current = '', $max = '') { +// // does no harm if $current and $max are present but are unnecessary for the message +// // Not doing .inLanguage($this.mOptions.getUserLangObj()), since this is shown +// // only during preview, and that would split the parser cache unnecessarily. +// $warning = wfMessage("$limitationType-warning").numParams($current, $max) +// .text(); +// $this.mOutput.addWarning($warning); +// $this.addTrackingCategory("$limitationType-category"); +// } + // MW.SRC:1.33.1 /** * Return the text of a template, after recursively @@ -2379,7 +3954,7 @@ public class XomwParser implements XomwParserIface { boolean isLocalObj = false; // Title Object, where text came from - XomwTitle title = null; + XomwTitleOld title = null; // part1 is the bit before the first |, and must contain only title characters. // Various prefixes will be stripped from it later. @@ -2472,7 +4047,7 @@ public class XomwParser implements XomwParserIface { // Extract any forwarded flags if (XophpArray_.isset(result, "title")) { - title = (XomwTitle)result.Get_by("title"); + title = (XomwTitleOld)result.Get_by("title"); } if (XophpArray_.isset(result, "found")) { found = result.Get_by_bool("found"); @@ -2512,7 +4087,7 @@ public class XomwParser implements XomwParserIface { part1 = relative; ns = this.mTitle.getNamespace(); } - title = XomwTitle.newFromText(env, part1, ns); + title = XomwTitleOld.newFromText(env, part1, ns); if (XophpObject_.is_true(title)) { titleText = title.getPrefixedTextStr(); // Check for language variants if the template is not found @@ -2699,963 +4274,1004 @@ Tfds.Write(nowiki, isHTML, forceRawInterwiki, isChildObj, isLocalObj, titleText, return ret; } -// /** -// * Call a parser function and return an array with text and flags. -// * -// * The returned array will always contain a boolean 'found', indicating -// * whether the parser function was found or not. It may also contain the -// * following: -// * text: String|Object, resulting wikitext or PP DOM Object -// * isHTML: boolean, $text is HTML, armour it against wikitext transformation -// * isChildObj: boolean, $text is a DOM node needing expansion in a child frame -// * isLocalObj: boolean, $text is a DOM node needing expansion in the current frame -// * nowiki: boolean, wiki markup in $text should be escaped -// * -// * @since 1.21 -// * @param PPFrame $frame The current frame, contains template arguments -// * @param String $function Function name -// * @param array $args Arguments to the function -// * @throws MWException -// * @return array -// */ -// public function callParserFunction($frame, $function, array $args = []) { -// global $wgContLang; -// -// // Case sensitive functions -// if (isset(this.mFunctionSynonyms[1][$function])) { -// $function = this.mFunctionSynonyms[1][$function]; +// MW.SRC:1.33.1 +// /** +// * Call a parser function and return an array with text and flags. +// * +// * The returned array will always contain a boolean 'found', indicating +// * whether the parser function was found or not. It may also contain the +// * following: +// * text: string|object, resulting wikitext or PP DOM object +// * isHTML: bool, $text is HTML, armour it against wikitext transformation +// * isChildObj: bool, $text is a DOM node needing expansion in a child frame +// * isLocalObj: bool, $text is a DOM node needing expansion in the current frame +// * nowiki: bool, wiki markup in $text should be escaped +// * +// * @since 1.21 +// * @param PPFrame $frame The current frame, contains template arguments +// * @param string $function Function name +// * @param array $args Arguments to the function +// * @throws MWException +// * @return array +// */ +// public function callParserFunction($frame, $function, array $args = []) { +// // Case sensitive functions +// if (isset($this.mFunctionSynonyms[1][$function])) { +// $function = $this.mFunctionSynonyms[1][$function]; +// } else { +// // Case insensitive functions +// $function = $this.contLang.lc($function); +// if (isset($this.mFunctionSynonyms[0][$function])) { +// $function = $this.mFunctionSynonyms[0][$function]; // } else { -// // Case insensitive functions -// $function = $wgContLang->lc($function); -// if (isset(this.mFunctionSynonyms[0][$function])) { -// $function = this.mFunctionSynonyms[0][$function]; -// } else { -// return [ 'found' => false ]; -// } +// return [ 'found' => false ]; // } +// } // -// list($callback, $flags) = this.mFunctionHooks[$function]; -// -// // Workaround for PHP bug 35229 and similar -// if (!is_callable($callback)) { -// throw new MWException("Tag hook for $function is not callable\n"); -// } +// list($callback, $flags) = $this.mFunctionHooks[$function]; // -// $allArgs = [ &$this ]; -// if ($flags & XomwParser.SFH_OBJECT_ARGS) { -// // Convert arguments to PPNodes and collect for appending to $allArgs -// $funcArgs = []; -// foreach ($args as $k => $v) { -// if ($v instanceof PPNode || $k === 0) { -// $funcArgs[] = $v; -// } else { -// $funcArgs[] = this.mPreprocessor->newPartNodeArray([ $k => $v ])->item(0); -// } -// } +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; // -// // Add a frame parameter, and pass the arguments as an array -// $allArgs[] = $frame; -// $allArgs[] = $funcArgs; -// } else { -// // Convert arguments to plain text and append to $allArgs -// foreach ($args as $k => $v) { -// if ($v instanceof PPNode) { -// $allArgs[] = trim($frame->expand($v)); -// } elseif (is_int($k) && $k >= 0) { -// $allArgs[] = trim($v); -// } else { -// $allArgs[] = trim("$k=$v"); -// } +// $allArgs = [ &$parser ]; +// if ($flags & self::SFH_OBJECT_ARGS) { +// // Convert arguments to PPNodes and collect for appending to $allArgs +// $funcArgs = []; +// foreach ($args as $k => $v) { +// if ($v instanceof PPNode || $k === 0) { +// $funcArgs[] = $v; +// } else { +// $funcArgs[] = $this.mPreprocessor.newPartNodeArray([ $k => $v ]).item(0); // } // } // -// $result = call_user_func_array($callback, $allArgs); -// -// // The interface for function hooks allows them to return a wikitext -// // String or an array containing the String and any flags. This mungs -// // things around to match what this method should return. -// if (!is_array($result)) { -// $result =[ -// 'found' => true, -// 'text' => $result, -// ]; -// } else { -// if (isset($result[0]) && !isset($result['text'])) { -// $result['text'] = $result[0]; +// // Add a frame parameter, and pass the arguments as an array +// $allArgs[] = $frame; +// $allArgs[] = $funcArgs; +// } else { +// // Convert arguments to plain text and append to $allArgs +// foreach ($args as $k => $v) { +// if ($v instanceof PPNode) { +// $allArgs[] = trim($frame.expand($v)); +// } elseif (is_int($k) && $k >= 0) { +// $allArgs[] = trim($v); +// } else { +// $allArgs[] = trim("$k=$v"); // } -// unset($result[0]); -// $result += [ -// 'found' => true, -// ]; // } +// } // -// $noparse = true; -// $preprocessFlags = 0; -// if (isset($result['noparse'])) { -// $noparse = $result['noparse']; -// } -// if (isset($result['preprocessFlags'])) { -// $preprocessFlags = $result['preprocessFlags']; -// } +// $result = $callback(...$allArgs); // -// if (!$noparse) { -// $result['text'] = this.preprocessToDom($result['text'], $preprocessFlags); -// $result['isChildObj'] = true; +// // The interface for function hooks allows them to return a wikitext +// // string or an array containing the string and any flags. This mungs +// // things around to match what this method should return. +// if (!is_array($result)) { +// $result = [ +// 'found' => true, +// 'text' => $result, +// ]; +// } else { +// if (isset($result[0]) && !isset($result['text'])) { +// $result['text'] = $result[0]; // } +// unset($result[0]); +// $result += [ +// 'found' => true, +// ]; +// } // -// return $result; +// $noparse = true; +// $preprocessFlags = 0; +// if (isset($result['noparse'])) { +// $noparse = $result['noparse']; +// } +// if (isset($result['preprocessFlags'])) { +// $preprocessFlags = $result['preprocessFlags']; // } // -// /** -// * Get the semi-parsed DOM representation of a template with a given title, -// * and its redirect destination title. Cached. -// * -// * @param Title $title -// * -// * @return array -// */ -// public function getTemplateDom($title) { -// $cacheTitle = $title; -// $titleText = $title->getPrefixedDBkey(); -// -// if (isset(this.mTplRedirCache[$titleText])) { -// list($ns, $dbk) = this.mTplRedirCache[$titleText]; -// $title = Title::makeTitle($ns, $dbk); -// $titleText = $title->getPrefixedDBkey(); -// } -// if (isset(this.mTplDomCache[$titleText])) { -// return [ this.mTplDomCache[$titleText], $title ]; -// } +// if (!$noparse) { +// $result['text'] = $this.preprocessToDom($result['text'], $preprocessFlags); +// $result['isChildObj'] = true; +// } // -// // Cache miss, go to the database -// list($text, $title) = this.fetchTemplateAndTitle($title); +// return $result; +// } +// +// /** +// * Get the semi-parsed DOM representation of a template with a given title, +// * and its redirect destination title. Cached. +// * +// * @param Title title +// * +// * @return array +// */ +// public function getTemplateDom(title) { +// $cacheTitle = title; +// titleText = title.getPrefixedDBkey(); +// +// if (isset($this.mTplRedirCache[titleText])) { +// list($ns, $dbk) = $this.mTplRedirCache[titleText]; +// title = Title::makeTitle($ns, $dbk); +// titleText = title.getPrefixedDBkey(); +// } +// if (isset($this.mTplDomCache[titleText])) { +// return [ $this.mTplDomCache[titleText], title ]; +// } // -// if ($text === false) { -// this.mTplDomCache[$titleText] = false; -// return [ false, $title ]; -// } +// // Cache miss, go to the database +// list($text, title) = $this.fetchTemplateAndTitle(title); // -// $dom = this.preprocessToDom($text, XomwParser.PTD_FOR_INCLUSION); -// this.mTplDomCache[$titleText] = $dom; +// if ($text === false) { +// $this.mTplDomCache[titleText] = false; +// return [ false, title ]; +// } // -// if (!$title->equals($cacheTitle)) { -// this.mTplRedirCache[$cacheTitle->getPrefixedDBkey()] = -// [ $title->getNamespace(), $cdb = $title->getDBkey() ]; -// } +// $dom = $this.preprocessToDom($text, self::PTD_FOR_INCLUSION); +// $this.mTplDomCache[titleText] = $dom; // -// return [ $dom, $title ]; +// if (!title.equals($cacheTitle)) { +// $this.mTplRedirCache[$cacheTitle.getPrefixedDBkey()] = +// [ title.getNamespace(), title.getDBkey() ]; // } // -// /** -// * Fetch the current revision of a given title. Note that the revision -// * (and even the title) may not exist in the database, so everything -// * contributing to the output of the parser should use this method -// * where possible, rather than getting the revisions themselves. This -// * method also caches its results, so using it benefits performance. -// * -// * @since 1.24 -// * @param Title $title -// * @return Revision -// */ -// public function fetchCurrentRevisionOfTitle($title) { -// $cacheKey = $title->getPrefixedDBkey(); -// if (!this.currentRevisionCache) { -// this.currentRevisionCache = new MapCacheLRU(100); -// } -// if (!this.currentRevisionCache->has($cacheKey)) { -// this.currentRevisionCache->set($cacheKey, -// // Defaults to Parser::statelessFetchRevision() -// call_user_func(this.mOptions->getCurrentRevisionCallback(), $title, $this) -// ); -// } -// return this.currentRevisionCache->get($cacheKey); +// return [ $dom, title ]; +// } +// +// /** +// * Fetch the current revision of a given title. Note that the revision +// * (and even the title) may not exist in the database, so everything +// * contributing to the output of the parser should use this method +// * where possible, rather than getting the revisions themselves. This +// * method also caches its results, so using it benefits performance. +// * +// * @since 1.24 +// * @param Title title +// * @return Revision +// */ +// public function fetchCurrentRevisionOfTitle(title) { +// $cacheKey = title.getPrefixedDBkey(); +// if (!$this.currentRevisionCache) { +// $this.currentRevisionCache = new MapCacheLRU(100); // } -// -// /** -// * Wrapper around Revision::newFromTitle to allow passing additional parameters -// * without passing them on to it. -// * -// * @since 1.24 -// * @param Title $title -// * @param Parser|boolean $parser -// * @return Revision|boolean False if missing -// */ -// public static function statelessFetchRevision(Title $title, $parser = false) { -// $pageId = $title->getArticleID(); -// $revId = $title->getLatestRevID(); -// -// $rev = Revision::newKnownCurrent(wfGetDB(DB_REPLICA), $pageId, $revId); -// if ($rev) { -// $rev->setTitle($title); -// } -// -// return $rev; +// if (!$this.currentRevisionCache.has($cacheKey)) { +// $this.currentRevisionCache.set($cacheKey, +// // Defaults to Parser::statelessFetchRevision() +// call_user_func($this.mOptions.getCurrentRevisionCallback(), title, $this) +// ); // } -// -// /** -// * Fetch the unparsed text of a template and register a reference to it. -// * @param Title $title -// * @return array (String or false, Title) -// */ -// public function fetchTemplateAndTitle($title) { -// // Defaults to Parser::statelessFetchTemplate() -// $templateCb = this.mOptions->getTemplateCallback(); -// $stuff = call_user_func($templateCb, $title, $this); -// // We use U+007F DELETE to distinguish strip markers from regular text. -// $text = $stuff['text']; -// if (is_string($stuff['text'])) { -// $text = strtr($text, "\x7f", "?"); -// } -// $finalTitle = isset($stuff['finalTitle']) ? $stuff['finalTitle'] : $title; -// if (isset($stuff['deps'])) { -// foreach ($stuff['deps'] as $dep) { -// this.mOutput->addTemplate($dep['title'], $dep['page_id'], $dep['rev_id']); -// if ($dep['title']->equals(this.getTitle())) { -// // If we transclude ourselves, the final result -// // will change based on the new version of the page -// this.mOutput->setFlag('vary-revision'); -// } +// return $this.currentRevisionCache.get($cacheKey); +// } + + /** + * Wrapper around Revision::newFromTitle to allow passing additional parameters + * without passing them on to it. + * + * @since 1.24 + * @param Title title + * @param Parser|bool $parser + * @return Revision|bool False if missing + */ + public static XomwRevision statelessFetchRevision(XomwTitleOld title, XomwParser parser) { // parser = false +// XomwRevision rev = XomwRevision.newKnownCurrent(wfGetDB(DB_REPLICA), title); +// +// return rev; + return null; + } + +// /** +// * Fetch the unparsed text of a template and register a reference to it. +// * @param Title title +// * @return array (string or false, Title) +// */ +// public function fetchTemplateAndTitle(title) { +// // Defaults to Parser::statelessFetchTemplate() +// $templateCb = $this.mOptions.getTemplateCallback(); +// $stuff = call_user_func($templateCb, title, $this); +// // We use U+007F DELETE to distinguish strip markers from regular text. +// $text = $stuff['text']; +// if (is_string($stuff['text'])) { +// $text = strtr($text, "\x7f", "?"); +// } +// $finalTitle = $stuff['finalTitle'] ?? title; +// if (isset($stuff['deps'])) { +// foreach ($stuff['deps'] as $dep) { +// $this.mOutput.addTemplate($dep['title'], $dep['page_id'], $dep['rev_id']); +// if ($dep['title'].equals($this.getTitle())) { +// // If we transclude ourselves, the final result +// // will change based on the new version of the page +// $this.mOutput.setFlag('vary-revision'); // } // } -// return [ $text, $finalTitle ]; // } +// return [ $text, $finalTitle ]; +// } +// +// /** +// * Fetch the unparsed text of a template and register a reference to it. +// * @param Title title +// * @return string|bool +// */ +// public function fetchTemplate(title) { +// return $this.fetchTemplateAndTitle(title)[0]; +// } // -// /** -// * Fetch the unparsed text of a template and register a reference to it. -// * @param Title $title -// * @return String|boolean -// */ -// public function fetchTemplate($title) { -// return this.fetchTemplateAndTitle($title)[0]; -// } -// -// /** -// * Static function to get a template -// * Can be overridden via ParserOptions::setTemplateCallback(). -// * -// * @param Title $title -// * @param boolean|Parser $parser -// * -// * @return array -// */ -// public static function statelessFetchTemplate($title, $parser = false) { -// $text = $skip = false; -// $finalTitle = $title; -// $deps = []; -// -// // Loop to fetch the article, with up to 1 redirect -// // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed -// for ($i = 0; $i < 2 && is_object($title); $i++) { -// // @codingStandardsIgnoreEnd -// // Give extensions a chance to select the revision instead -// $id = false; // Assume current -// Hooks::run('BeforeParserFetchTemplateAndtitle', -// [ $parser, $title, &$skip, &$id ]); -// -// if ($skip) { -// $text = false; -// $deps[] = [ -// 'title' => $title, -// 'page_id' => $title->getArticleID(), -// 'rev_id' => null -// ]; -// break; -// } -// // Get the revision -// if ($id) { -// $rev = Revision::newFromId($id); -// } elseif ($parser) { -// $rev = $parser->fetchCurrentRevisionOfTitle($title); -// } else { -// $rev = Revision::newFromTitle($title); -// } -// $rev_id = $rev ? $rev->getId() : 0; -// // If there is no current revision, there is no page -// if ($id === false && !$rev) { -// $linkCache = LinkCache::singleton(); -// $linkCache->addBadLinkObj($title); -// } -// -// $deps[] = [ -// 'title' => $title, -// 'page_id' => $title->getArticleID(), -// 'rev_id' => $rev_id ]; -// if ($rev && !$title->equals($rev->getTitle())) { -// // We fetched a rev from a different title; register it too... -// $deps[] = [ -// 'title' => $rev->getTitle(), -// 'page_id' => $rev->getPage(), -// 'rev_id' => $rev_id ]; -// } -// -// if ($rev) { -// $content = $rev->getContent(); -// $text = $content ? $content->getWikitextForTransclusion() : null; + /** + * Static function to get a template + * Can be overridden via ParserOptions::setTemplateCallback(). + * + * @param Title title + * @param bool|Parser $parser + * + * @return array + */ + public static XophpArray statelessFetchTemplate(XomwTitleOld title, XomwParser parser) { // $parser = false + String text = XophpString_.False; + boolean skip = false; + XomwTitleOld finalTitle = title; + XophpArray deps = XophpArray.New(); + + // Loop to fetch the article, with up to 1 redirect + for (int i = 0; i < 2 && XophpObject_.is_object(title); i++) { + // Give extensions a chance to select the revision instead + int id = XophpInt_.False; // Assume current +// Hooks::run('BeforeParserFetchTemplateAndtitle', +// [ parser, title, &skip, &id ]); + + if (skip) { + text = XophpString_.False; + deps.Add(XophpArray.New() + .Add("title", title) + .Add("page_id", title.getArticleID()) + .Add("rev_id", null) + ); + break; + } + // Get the revision + XomwRevision rev = null; + if (XophpInt_.is_true(id)) { + rev = XomwRevision.newFromId(id); + } else if (XophpObject_.is_true(parser)) { +// rev = parser.fetchCurrentRevisionOfTitle(title); + } else { + rev = XomwRevision.newFromTitle(title); + } + int rev_id = XophpObject_.is_true(rev) ? rev.getId() : 0; + // If there is no current revision, there is no page + if (XophpInt_.is_false(id) && !XophpObject_.is_true(rev)) { +// linkCache = MediaWikiServices::getInstance().getLinkCache(); +// linkCache.addBadLinkObj(title); + } + + deps.Add(XophpArray.New() + .Add("title", title) + .Add("page_id", title.getArticleID()) + .Add("rev_id", rev_id)); +// if (rev && !title.equals(rev.getTitle())) { + // We fetched a rev from a different title; register it too... +// deps.Add(XophpArray.New() +// .Add("title", rev.getTitle()) +// .Add("page_id", rev.getPage()) +// .Add("rev_id", rev_id)); +// } + + if (XophpObject_.is_true(rev)) { +// content = rev.getContent(); +// text = content ? content.getWikitextForTransclusion() : null; // -// Hooks::run('ParserFetchTemplate', -// [ $parser, $title, $rev, &$text, &$deps ]); +// Hooks::run('ParserFetchTemplate', +// [ parser, title, rev, &text, &deps ]); // -// if ($text === false || $text === null) { -// $text = false; -// break; -// } -// } elseif ($title->getNamespace() == NS_MEDIAWIKI) { -// global $wgContLang; -// message = wfMessage($wgContLang->lcfirst($title->getText()))->inContentLanguage(); -// if (!message->exists()) { -// $text = false; -// break; -// } -// $content = message->content(); -// $text = message->plain(); -// } else { +// if (text === false || text === null) { +// text = false; // break; // } -// if (!$content) { + } else if (title.getNamespace() == NS_MEDIAWIKI) { +// message = wfMessage(MediaWikiServices::getInstance().getContentLanguage(). +// lcfirst(title.getText())).inContentLanguage(); +// if (!message.exists()) { +// text = false; // break; // } -// // Redirect? -// $finalTitle = $title; -// $title = $content->getRedirectTarget(); +// content = message.content(); +// text = message.plain(); + } else { + break; + } +// if (!content) { +// break; // } -// return [ -// 'text' => $text, -// 'finalTitle' => $finalTitle, -// 'deps' => $deps ]; + // Redirect? + finalTitle = title; +// title = content.getRedirectTarget(); + } + return XophpArray.New() + .Add("text", text) + .Add("finalTitle", finalTitle) + .Add("deps", deps); + } + +// /** +// * Fetch a file and its title and register a reference to it. +// * If 'broken' is a key in $options then the file will appear as a broken thumbnail. +// * @param Title title +// * @param array $options Array of options to RepoGroup::findFile +// * @return File|bool +// * @deprecated since 1.32, use fetchFileAndTitle instead +// */ +// public function fetchFile(title, $options = []) { +// wfDeprecated(__METHOD__, '1.32'); +// return $this.fetchFileAndTitle(title, $options)[0]; +// } +// +// /** +// * Fetch a file and its title and register a reference to it. +// * If 'broken' is a key in $options then the file will appear as a broken thumbnail. +// * @param Title title +// * @param array $options Array of options to RepoGroup::findFile +// * @return array (File or false, Title of file) +// */ +// public function fetchFileAndTitle(title, $options = []) { +// $file = $this.fetchFileNoRegister(title, $options); +// +// $time = $file ? $file.getTimestamp() : false; +// $sha1 = $file ? $file.getSha1() : false; +// // Register the file as a dependency... +// $this.mOutput.addImage(title.getDBkey(), $time, $sha1); +// if ($file && !title.equals($file.getTitle())) { +// // Update fetched file title +// title = $file.getTitle(); +// $this.mOutput.addImage(title.getDBkey(), $time, $sha1); // } -// -// /** -// * Fetch a file and its title and register a reference to it. -// * If 'broken' is a key in $options then the file will appear as a broken thumbnail. -// * @param Title $title -// * @param array $options Array of options to RepoGroup::findFile -// * @return File|boolean -// */ -// public function fetchFile($title, $options = []) { -// return this.fetchFileAndTitle($title, $options)[0]; +// return [ $file, title ]; +// } +// +// /** +// * Helper function for fetchFileAndTitle. +// * +// * Also useful if you need to fetch a file but not use it yet, +// * for example to get the file's handler. +// * +// * @param Title title +// * @param array $options Array of options to RepoGroup::findFile +// * @return File|bool +// */ +// protected function fetchFileNoRegister(title, $options = []) { +// if (isset($options['broken'])) { +// $file = false; // broken thumbnail forced by hook +// } elseif (isset($options['sha1'])) { // get by (sha1,timestamp) +// $file = RepoGroup::singleton().findFileFromKey($options['sha1'], $options); +// } else { // get by (name,timestamp) +// $file = wfFindFile(title, $options); // } -// -// /** -// * Fetch a file and its title and register a reference to it. -// * If 'broken' is a key in $options then the file will appear as a broken thumbnail. -// * @param Title $title -// * @param array $options Array of options to RepoGroup::findFile -// * @return array (File or false, Title of file) -// */ -// public function fetchFileAndTitle($title, $options = []) { -// $file = this.fetchFileNoRegister($title, $options); -// -// $time = $file ? $file->getTimestamp() : false; -// $sha1 = $file ? $file->getSha1() : false; -// // Register the file as a dependency... -// this.mOutput->addImage($title->getDBkey(), $time, $sha1); -// if ($file && !$title->equals($file->getTitle())) { -// // Update fetched file title -// $title = $file->getTitle(); -// this.mOutput->addImage($title->getDBkey(), $time, $sha1); -// } -// return [ $file, $title ]; +// return $file; +// } +// +// /** +// * Transclude an interwiki link. +// * +// * @param Title title +// * @param string $action Usually one of (raw, render) +// * +// * @return string +// */ +// public function interwikiTransclude(title, $action) { +// if (!$this.siteConfig.get('EnableScaryTranscluding')) { +// return wfMessage('scarytranscludedisabled').inContentLanguage().text(); // } // -// /** -// * Helper function for fetchFileAndTitle. -// * -// * Also useful if you need to fetch a file but not use it yet, -// * for example to get the file's handler. -// * -// * @param Title $title -// * @param array $options Array of options to RepoGroup::findFile -// * @return File|boolean -// */ -// protected function fetchFileNoRegister($title, $options = []) { -// if (isset($options['broken'])) { -// $file = false; // broken thumbnail forced by hook -// } elseif (isset($options['sha1'])) { // get by (sha1,timestamp) -// $file = RepoGroup::singleton()->findFileFromKey($options['sha1'], $options); -// } else { // get by (name,timestamp) -// $file = wfFindFile($title, $options); -// } -// return $file; +// $url = title.getFullURL([ 'action' => $action ]); +// if (strlen($url) > 1024) { +// return wfMessage('scarytranscludetoolong').inContentLanguage().text(); // } // -// /** -// * Transclude an interwiki link. -// * -// * @param Title $title -// * @param String $action -// * -// * @return String -// */ -// public function interwikiTransclude($title, $action) { -// global $wgEnableScaryTranscluding; -// -// if (!$wgEnableScaryTranscluding) { -// return wfMessage('scarytranscludedisabled')->inContentLanguage()->text(); -// } -// -// $url = $title->getFullURL([ 'action' => $action ]); +// $wikiId = title.getTransWikiID(); // remote wiki ID or false // -// if (strlen($url) > 255) { -// return wfMessage('scarytranscludetoolong')->inContentLanguage()->text(); -// } -// return this.fetchScaryTemplateMaybeFromCache($url); -// } +// $fname = __METHOD__; +// $cache = MediaWikiServices::getInstance().getMainWANObjectCache(); // -// /** -// * @param String $url -// * @return mixed|String -// */ -// public function fetchScaryTemplateMaybeFromCache($url) { -// global $wgTranscludeCacheExpiry; -// $dbr = wfGetDB(DB_REPLICA); -// $tsCond = $dbr->timestamp(time() - $wgTranscludeCacheExpiry); -// $obj = $dbr->selectRow('transcache', [ 'tc_time', 'tc_contents' ], -// [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes($tsCond) ]); -// if ($obj) { -// return $obj->tc_contents; -// } +// $data = $cache.getWithSetCallback( +// $cache.makeGlobalKey( +// 'interwiki-transclude', +// ($wikiId !== false) ? $wikiId : 'external', +// sha1($url) +// ), +// $this.siteConfig.get('TranscludeCacheExpiry'), +// function ($oldValue, &$ttl) use ($url, $fname, $cache) { +// $req = MWHttpRequest::factory($url, [], $fname); // -// $req = MWHttpRequest::factory($url, [], __METHOD__); -// $status = $req->execute(); // Status Object -// if ($status->isOK()) { -// $text = $req->getContent(); -// } elseif ($req->getStatus() != 200) { -// // Though we failed to fetch the content, this status is useless. -// return wfMessage('scarytranscludefailed-httpstatus') -// ->params($url, $req->getStatus() /* HTTP status */)->inContentLanguage()->text(); -// } else { -// return wfMessage('scarytranscludefailed', $url)->inContentLanguage()->text(); +// $status = $req.execute(); // Status object +// if (!$status.isOK()) { +// $ttl = $cache::TTL_UNCACHEABLE; +// } elseif ($req.getResponseHeader('X-Database-Lagged') !== null) { +// $ttl = min($cache::TTL_LAGGED, $ttl); // } // -// $dbw = wfGetDB(DB_MASTER); -// $dbw->replace('transcache', [ 'tc_url' ], [ -// 'tc_url' => $url, -// 'tc_time' => $dbw->timestamp(time()), -// 'tc_contents' => $text -// ]); -// return $text; +// return [ +// 'text' => $status.isOK() ? $req.getContent() : null, +// 'code' => $req.getStatus() +// ]; +// }, +// [ +// 'checkKeys' => ($wikiId !== false) +// ? [ $cache.makeGlobalKey('interwiki-page', $wikiId, title.getDBkey()) ] +// : [], +// 'pcGroup' => 'interwiki-transclude:5', +// 'pcTTL' => $cache::TTL_PROC_LONG +// ] +// ); +// +// if (is_string($data['text'])) { +// $text = $data['text']; +// } elseif ($data['code'] != 200) { +// // Though we failed to fetch the content, this status is useless. +// $text = wfMessage('scarytranscludefailed-httpstatus') +// .params($url, $data['code']).inContentLanguage().text(); +// } else { +// $text = wfMessage('scarytranscludefailed', $url).inContentLanguage().text(); // } +// +// return $text; +// } /** - * Triple brace replacement -- used for template arguments - * @private - * - * @param array $piece - * @param PPFrame $frame - * - * @return array - */ + * Triple brace replacement -- used for template arguments + * @private + * + * @param array $piece + * @param PPFrame $frame + * + * @return array + */ public XophpArray argSubstitution(XophpArray piece, XomwPPFrame frame) { + return null; +// $error = false; +// $parts = $piece['parts']; +// $nameWithSpaces = $frame.expand($piece['title']); +// $argName = trim($nameWithSpaces); +// $object = false; +// $text = $frame.getArgument($argName); +// if ($text === false && $parts.getLength() > 0 +// && ($this.ot['html'] +// || $this.ot['pre'] +// || ($this.ot['wiki'] && $frame.isTemplate()) +// ) +// ) { +// // No match in frame, use the supplied default +// $object = $parts.item(0).getChildren(); +// } +// if (!$this.incrementIncludeSize('arg', strlen($text))) { +// $error = '<!-- WARNING: argument omitted, expansion size too large -.'; +// $this.limitationWarn('post-expand-template-argument'); +// } // -// $error = false; -// $parts = $piece['parts']; -// $nameWithSpaces = $frame->expand($piece['title']); -// $argName = trim($nameWithSpaces); -// $Object = false; -// $text = $frame->getArgument($argName); -// if ($text === false && $parts->getLength() > 0 -// && (this.ot['html'] -// || this.ot['pre'] -// || (this.ot['wiki'] && $frame->isTemplate()) -// ) -// ) { -// // No match in frame, use the supplied default -// $Object = $parts->item(0)->getChildren(); -// } -// if (!this.incrementIncludeSize('arg', strlen($text))) { -// $error = '<!-- WARNING: argument omitted, expansion size too large -->'; -// this.limitationWarn('post-expand-template-argument'); -// } -// -// if ($text === false && $Object === false) { -// // No match anywhere -// $Object = $frame->virtualBracketedImplode('{{{', '|', '}}}', $nameWithSpaces, $parts); -// } -// if ($error !== false) { -// $text .= $error; -// } -// if ($Object !== false) { -// $ret = [ 'Object' => $Object ]; -// } else { -// $ret = [ 'text' => $text ]; -// } +// if ($text === false && $object === false) { +// // No match anywhere +// $object = $frame.virtualBracketedImplode('{{{', '|', '}}}', $nameWithSpaces, $parts); +// } +// if ($error !== false) { +// $text .= $error; +// } +// if ($object !== false) { +// $ret = [ 'object' => $object ]; +// } else { +// $ret = [ 'text' => $text ]; +// } // -// return $ret; - return null; +// return $ret; } /** - * Return the text to be used for a given extension tag. - * This is the ghost of strip(). - * - * @param array $Associative array of parameters: - * name PPNode for the tag name - * attr PPNode for unparsed text where tag attributes are thought to be - * attributes Optional associative array of parsed attributes - * inner Contents of extension element - * noClose Original text did not have a close tag - * @param PPFrame $frame - * - * @throws MWException - * @return String - */ - public String extensionSubstitution(XophpArray paramsAry, XomwPPFrame frame) { -// static $errorStr = '<span class="error">'; -// static $errorLen = 20; -// -// $name = $frame->expand($params['name']); -// if (substr($name, 0, $errorLen) === $errorStr) { -// // Probably expansion depth or node count exceeded. Just punt the -// // error up. -// return $name; -// } + * Return the text to be used for a given extension tag. + * This is the ghost of strip(). + * + * @param array $params Associative array of parameters: + * name PPNode for the tag name + * attr PPNode for unparsed text where tag attributes are thought to be + * attributes Optional associative array of parsed attributes + * inner Contents of extension element + * noClose Original text did not have a close tag + * @param PPFrame $frame + * + * @throws MWException + * @return string + */ + public String extensionSubstitution(XophpArray params, XomwPPFrame frame) { + return null; +// static $errorStr = '<span class="error">'; +// static $errorLen = 20; +// +// $name = $frame.expand($params['name']); +// if (substr($name, 0, $errorLen) === $errorStr) { +// // Probably expansion depth or node count exceeded. Just punt the +// // error up. +// return $name; +// } // -// $attrText = !isset($params['attr']) ? null : $frame->expand($params['attr']); -// if (substr($attrText, 0, $errorLen) === $errorStr) { -// // See above -// return $attrText; -// } +// $attrText = !isset($params['attr']) ? null : $frame.expand($params['attr']); +// if (substr($attrText, 0, $errorLen) === $errorStr) { +// // See above +// return $attrText; +// } // -// // We can't safely check if the expansion for $content resulted in an -// // error, because the content could happen to be the error String -// // (T149622). -// $content = !isset($params['inner']) ? null : $frame->expand($params['inner']); +// // We can't safely check if the expansion for $content resulted in an +// // error, because the content could happen to be the error string +// // (T149622). +// $content = !isset($params['inner']) ? null : $frame.expand($params['inner']); // -// marker = XomwParser.MARKER_PREFIX . "-$name-" -// . sprintf('%08X', this.mMarkerIndex++) . XomwParser.MARKER_SUFFIX; +// $marker = self::MARKER_PREFIX . "-$name-" +// . sprintf('%08X', $this.mMarkerIndex++) . self::MARKER_SUFFIX; // -// $isFunctionTag = isset(this.mFunctionTagHooks[strtolower($name)]) && -// (this.ot['html'] || this.ot['pre']); -// if ($isFunctionTag) { -// markerType = 'none'; +// $isFunctionTag = isset($this.mFunctionTagHooks[strtolower($name)]) && +// ($this.ot['html'] || $this.ot['pre']); +// if ($isFunctionTag) { +// $markerType = 'none'; +// } else { +// $markerType = 'general'; +// } +// if ($this.ot['html'] || $isFunctionTag) { +// $name = strtolower($name); +// $attributes = Sanitizer::decodeTagAttributes($attrText); +// if (isset($params['attributes'])) { +// $attributes += $params['attributes']; +// } +// +// if (isset($this.mTagHooks[$name])) { +// $output = call_user_func_array($this.mTagHooks[$name], +// [ $content, $attributes, $this, $frame ]); +// } elseif (isset($this.mFunctionTagHooks[$name])) { +// list($callback,) = $this.mFunctionTagHooks[$name]; +// +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// $output = call_user_func_array($callback, [ &$parser, $frame, $content, $attributes ]); // } else { -// markerType = 'general'; +// $output = '<span class="error">Invalid tag extension name: ' . +// htmlspecialchars($name) . '</span>'; // } -// if (this.ot['html'] || $isFunctionTag) { -// $name = strtolower($name); -// $attributes = Sanitizer::decodeTagAttributes($attrText); -// if (isset($params['attributes'])) { -// $attributes = $attributes + $params['attributes']; -// } -// -// if (isset(this.mTagHooks[$name])) { -// // Workaround for PHP bug 35229 and similar -// if (!is_callable(this.mTagHooks[$name])) { -// throw new MWException("Tag hook for $name is not callable\n"); -// } -// $output = call_user_func_array(this.mTagHooks[$name], -// [ $content, $attributes, $this, $frame ]); -// } elseif (isset(this.mFunctionTagHooks[$name])) { -// list($callback,) = this.mFunctionTagHooks[$name]; -// if (!is_callable($callback)) { -// throw new MWException("Tag hook for $name is not callable\n"); -// } // -// $output = call_user_func_array($callback, [ &$this, $frame, $content, $attributes ]); -// } else { -// $output = '<span class="error">Invalid tag extension name: ' . -// htmlspecialchars($name) . '</span>'; -// } -// -// if (is_array($output)) { -// // Extract flags to local scope (to override markerType) -// $flags = $output; -// $output = $flags[0]; -// unset($flags[0]); -// extract($flags); -// } -// } else { -// if (is_null($attrText)) { -// $attrText = ''; -// } -// if (isset($params['attributes'])) { -// foreach ($params['attributes'] as $attrName => $attrValue) { -// $attrText .= ' ' . htmlspecialchars($attrName) . '="' . -// htmlspecialchars($attrValue) . '"'; -// } -// } -// if ($content === null) { -// $output = "<$name$attrText/>"; -// } else { -// $close = is_null($params['close']) ? '' : $frame->expand($params['close']); -// if (substr($close, 0, $errorLen) === $errorStr) { -// // See above -// return $close; -// } -// $output = "<$name$attrText>$content$close"; +// if (is_array($output)) { +// // Extract flags +// $flags = $output; +// $output = $flags[0]; +// if (isset($flags['markerType'])) { +// $markerType = $flags['markerType']; // } // } -// -// if (markerType === 'none') { -// return $output; -// } elseif (markerType === 'nowiki') { -// this.mStripState->addNoWiki(marker, $output); -// } elseif (markerType === 'general') { -// this.mStripState->addGeneral(marker, $output); -// } else { -// throw new MWException(__METHOD__ . ': invalid marker type'); +// } else { +// if (is_null($attrText)) { +// $attrText = ''; // } -// return marker; - return null; - } -// -// /** -// * Increment an include size counter -// * -// * @param String $type The type of expansion -// * @param int $size The size of the text -// * @return boolean False if this inclusion would take it over the maximum, true otherwise -// */ -// public function incrementIncludeSize($type, $size) { -// if (this.mIncludeSizes[$type] + $size > this.mOptions->getMaxIncludeSize()) { -// return false; +// if (isset($params['attributes'])) { +// foreach ($params['attributes'] as $attrName => $attrValue) { +// $attrText .= ' ' . htmlspecialchars($attrName) . '="' . +// htmlspecialchars($attrValue) . '"'; +// } +// } +// if ($content === null) { +// $output = "<$name$attrText/>"; // } else { -// this.mIncludeSizes[$type] += $size; -// return true; +// $close = is_null($params['close']) ? '' : $frame.expand($params['close']); +// if (substr($close, 0, $errorLen) === $errorStr) { +// // See above +// return $close; +// } +// $output = "<$name$attrText>$content$close"; // } // } // -// /** -// * Increment the expensive function count -// * -// * @return boolean False if the limit has been exceeded -// */ -// public function incrementExpensiveFunctionCount() { -// this.mExpensiveFunctionCount++; -// return this.mExpensiveFunctionCount <= this.mOptions->getExpensiveParserFunctionLimit(); +// if ($markerType === 'none') { +// return $output; +// } elseif ($markerType === 'nowiki') { +// $this.mStripState.addNoWiki($marker, $output); +// } elseif ($markerType === 'general') { +// $this.mStripState.addGeneral($marker, $output); +// } else { +// throw new MWException(__METHOD__ . ': invalid marker type'); // } +// return $marker; + } - // XO.MW:MOVED - // public void doDoubleUnderscore($text) {} - -// /** -// * @see ParserOutput::addTrackingCategory() -// * @param String msg Message key -// * @return boolean Whether the addition was successful -// */ -// public function addTrackingCategory(msg) { -// return this.mOutput->addTrackingCategory(msg, this.mTitle); +// /** +// * Increment an include size counter +// * +// * @param string $type The type of expansion +// * @param int $size The size of the text +// * @return bool False if this inclusion would take it over the maximum, true otherwise +// */ +// public function incrementIncludeSize($type, $size) { +// if ($this.mIncludeSizes[$type] + $size > $this.mOptions.getMaxIncludeSize()) { +// return false; +// } else { +// $this.mIncludeSizes[$type] += $size; +// return true; +// } +// } +// +// /** +// * Increment the expensive function count +// * +// * @return bool False if the limit has been exceeded +// */ +// public function incrementExpensiveFunctionCount() { +// $this.mExpensiveFunctionCount++; +// return $this.mExpensiveFunctionCount <= $this.mOptions.getExpensiveParserFunctionLimit(); +// } +// +// /** +// * Strip double-underscore items like __NOGALLERY__ and __NOTOC__ +// * Fills $this.mDoubleUnderscores, returns the modified text +// * +// * @param string $text +// * +// * @return string +// */ +// public function doDoubleUnderscore($text) { +// // The position of __TOC__ needs to be recorded +// $mw = $this.magicWordFactory.get('toc'); +// if ($mw.match($text)) { +// $this.mShowToc = true; +// $this.mForceTocPosition = true; +// +// // Set a placeholder. At the end we'll fill it in with the TOC. +// $text = $mw.replace('<!--MWTOC\'"-.', $text, 1); +// +// // Only keep the first one. +// $text = $mw.replace('', $text); // } // -// /** -// * This function accomplishes several tasks: -// * 1) Auto-number headings if that option is enabled -// * 2) Add an [edit] link to sections for users who have enabled the option and can edit the page -// * 3) Add a Table of contents on the top for users who have enabled the option -// * 4) Auto-anchor headings -// * -// * It loops through all headlines, collects the necessary data, then splits up the -// * String and re-inserts the newly formatted headlines. -// * -// * @param String $text -// * @param String $origText Original, untouched wikitext -// * @param boolean $isMain -// * @return mixed|String -// * @private -// */ -// public function formatHeadings($text, $origText, $isMain = true) { -// global $wgMaxTocLevel, $wgExperimentalHtmlIds; +// // Now match and remove the rest of them +// $mwa = $this.magicWordFactory.getDoubleUnderscoreArray(); +// $this.mDoubleUnderscores = $mwa.matchAndRemove($text); // -// // Inhibit editsection links if requested in the page -// if (isset(this.mDoubleUnderscores['noeditsection'])) { -// maybeShowEditLink = $showEditLink = false; -// } else { -// maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */ -// $showEditLink = this.mOptions->getEditSection(); -// } -// if ($showEditLink) { -// this.mOutput->setEditSectionTokens(true); -// } +// if (isset($this.mDoubleUnderscores['nogallery'])) { +// $this.mOutput.mNoGallery = true; +// } +// if (isset($this.mDoubleUnderscores['notoc']) && !$this.mForceTocPosition) { +// $this.mShowToc = false; +// } +// if (isset($this.mDoubleUnderscores['hiddencat']) +// && $this.mTitle.getNamespace() == NS_CATEGORY +// ) { +// $this.addTrackingCategory('hidden-category-category'); +// } +// // (T10068) Allow control over whether robots index a page. +// // __INDEX__ always overrides __NOINDEX__, see T16899 +// if (isset($this.mDoubleUnderscores['noindex']) && $this.mTitle.canUseNoindex()) { +// $this.mOutput.setIndexPolicy('noindex'); +// $this.addTrackingCategory('noindex-category'); +// } +// if (isset($this.mDoubleUnderscores['index']) && $this.mTitle.canUseNoindex()) { +// $this.mOutput.setIndexPolicy('index'); +// $this.addTrackingCategory('index-category'); +// } // -// // Get all headlines for numbering them and adding funky stuff like [edit] -// // links - this is for later, but we need the number of headlines right now -// matches = ...; -// $numMatches = preg_match_all( -// '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i', -// $text, -// matches -// ); +// // Cache all double underscores in the database +// foreach ($this.mDoubleUnderscores as $key => $val) { +// $this.mOutput.setProperty($key, ''); +// } // -// // if there are fewer than 4 headlines in the article, do not show TOC -// // unless it's been explicitly enabled. -// $enoughToc = this.mShowToc && -// (($numMatches >= 4) || this.mForceTocPosition); +// return $text; +// } +// +// /** +// * @see ParserOutput::addTrackingCategory() +// * @param string $msg Message key +// * @return bool Whether the addition was successful +// */ +// public function addTrackingCategory($msg) { +// return $this.mOutput.addTrackingCategory($msg, $this.mTitle); +// } +// +// /** +// * This function accomplishes several tasks: +// * 1) Auto-number headings if that option is enabled +// * 2) Add an [edit] link to sections for users who have enabled the option and can edit the page +// * 3) Add a Table of contents on the top for users who have enabled the option +// * 4) Auto-anchor headings +// * +// * It loops through all headlines, collects the necessary data, then splits up the +// * string and re-inserts the newly formatted headlines. +// * +// * @param string $text +// * @param string $origText Original, untouched wikitext +// * @param bool $isMain +// * @return mixed|string +// * @private +// */ +// public function formatHeadings($text, $origText, $isMain = true) { +// // Inhibit editsection links if requested in the page +// if (isset($this.mDoubleUnderscores['noeditsection'])) { +// $maybeShowEditLink = false; +// } else { +// $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */ +// } // -// // Allow user to stipulate that a page should have a "new section" -// // link added via __NEWSECTIONLINK__ -// if (isset(this.mDoubleUnderscores['newsectionlink'])) { -// this.mOutput->setNewSection(true); -// } +// // Get all headlines for numbering them and adding funky stuff like [edit] +// // links - this is for later, but we need the number of headlines right now +// // NOTE: white space in headings have been trimmed in doHeadings. They shouldn't +// // be trimmed here since whitespace in HTML headings is significant. +// $matches = []; +// $numMatches = preg_match_all( +// '/<H(?P<level>[1-6])(?P<attrib>.*?>)(?P<header>[\s\S]*?)<\/H[1-6] *>/i', +// $text, +// $matches +// ); +// +// // if there are fewer than 4 headlines in the article, do not show TOC +// // unless it's been explicitly enabled. +// $enoughToc = $this.mShowToc && +// (($numMatches >= 4) || $this.mForceTocPosition); +// +// // Allow user to stipulate that a page should have a "new section" +// // link added via __NEWSECTIONLINK__ +// if (isset($this.mDoubleUnderscores['newsectionlink'])) { +// $this.mOutput.setNewSection(true); +// } // -// // Allow user to remove the "new section" -// // link via __NONEWSECTIONLINK__ -// if (isset(this.mDoubleUnderscores['nonewsectionlink'])) { -// this.mOutput->hideNewSection(true); -// } +// // Allow user to remove the "new section" +// // link via __NONEWSECTIONLINK__ +// if (isset($this.mDoubleUnderscores['nonewsectionlink'])) { +// $this.mOutput.hideNewSection(true); +// } // -// // if the String __FORCETOC__ (not case-sensitive) occurs in the HTML, -// // override above conditions and always show TOC above first header -// if (isset(this.mDoubleUnderscores['forcetoc'])) { -// this.mShowToc = true; -// $enoughToc = true; -// } +// // if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, +// // override above conditions and always show TOC above first header +// if (isset($this.mDoubleUnderscores['forcetoc'])) { +// $this.mShowToc = true; +// $enoughToc = true; +// } // -// // headline counter -// $headlineCount = 0; -// $numVisible = 0; -// -// // Ugh .. the TOC should have neat indentation levels which can be -// // passed to the skin functions. These are determined here -// $toc = ''; -// $full = ''; -// $head = []; -// $sublevelCount = []; -// $levelCount = []; -// $level = 0; -// $prevlevel = 0; -// $toclevel = 0; -// $prevtoclevel = 0; -// markerRegex = XomwParser.MARKER_PREFIX . "-h-(\d+)-" . XomwParser.MARKER_SUFFIX; -// $baseTitleText = this.mTitle->getPrefixedDBkey(); -// $oldType = this.mOutputType; -// this.setOutputType(XomwParser.OT_WIKI); -// $frame = this.getPreprocessor()->newFrame(); -// $root = this.preprocessToDom($origText); -// $node = $root->getFirstChild(); -// $byteOffset = 0; -// $tocraw = []; -// $refers = []; -// -// $headlines = $numMatches !== false ? matches[3] : []; -// -// foreach ($headlines as $headline) { -// $isTemplate = false; -// $titleText = false; -// $sectionIndex = false; -// $numbering = ''; -// markerMatches = []; -// if (preg_match("/^markerRegex/", $headline, markerMatches)) { -// $serial = markerMatches[1]; -// list($titleText, $sectionIndex) = this.mHeadings[$serial]; -// $isTemplate = ($titleText != $baseTitleText); -// $headline = preg_replace("/^markerRegex\\s*/", "", $headline); +// // headline counter +// $headlineCount = 0; +// $numVisible = 0; +// +// // Ugh .. the TOC should have neat indentation levels which can be +// // passed to the skin functions. These are determined here +// $toc = ''; +// $full = ''; +// $head = []; +// $sublevelCount = []; +// $levelCount = []; +// $level = 0; +// $prevlevel = 0; +// $toclevel = 0; +// $prevtoclevel = 0; +// $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX; +// $baseTitleText = $this.mTitle.getPrefixedDBkey(); +// $oldType = $this.mOutputType; +// $this.setOutputType(self::OT_WIKI); +// $frame = $this.getPreprocessor().newFrame(); +// $root = $this.preprocessToDom($origText); +// $node = $root.getFirstChild(); +// $byteOffset = 0; +// $tocraw = []; +// $refers = []; +// +// $headlines = $numMatches !== false ? $matches[3] : []; +// +// $maxTocLevel = $this.siteConfig.get('MaxTocLevel'); +// foreach ($headlines as $headline) { +// $isTemplate = false; +// titleText = false; +// $sectionIndex = false; +// $numbering = ''; +// $markerMatches = []; +// if (preg_match("/^$markerRegex/", $headline, $markerMatches)) { +// $serial = $markerMatches[1]; +// list(titleText, $sectionIndex) = $this.mHeadings[$serial]; +// $isTemplate = (titleText != $baseTitleText); +// $headline = preg_replace("/^$markerRegex\\s*/", "", $headline); +// } +// +// if ($toclevel) { +// $prevlevel = $level; +// } +// $level = $matches[1][$headlineCount]; +// +// if ($level > $prevlevel) { +// // Increase TOC level +// $toclevel++; +// $sublevelCount[$toclevel] = 0; +// if ($toclevel < $maxTocLevel) { +// $prevtoclevel = $toclevel; +// $toc .= Linker::tocIndent(); +// $numVisible++; // } +// } elseif ($level < $prevlevel && $toclevel > 1) { +// // Decrease TOC level, find level to jump to // -// if ($toclevel) { -// $prevlevel = $level; +// for ($i = $toclevel; $i > 0; $i--) { +// if ($levelCount[$i] == $level) { +// // Found last matching level +// $toclevel = $i; +// break; +// } elseif ($levelCount[$i] < $level) { +// // Found first matching level below current level +// $toclevel = $i + 1; +// break; +// } // } -// $level = matches[1][$headlineCount]; -// -// if ($level > $prevlevel) { -// // Increase TOC level -// $toclevel++; -// $sublevelCount[$toclevel] = 0; -// if ($toclevel < $wgMaxTocLevel) { +// if ($i == 0) { +// $toclevel = 1; +// } +// if ($toclevel < $maxTocLevel) { +// if ($prevtoclevel < $maxTocLevel) { +// // Unindent only if the previous toc level was shown :p +// $toc .= Linker::tocUnindent($prevtoclevel - $toclevel); // $prevtoclevel = $toclevel; -// $toc .= Linker::tocIndent(); -// $numVisible++; -// } -// } elseif ($level < $prevlevel && $toclevel > 1) { -// // Decrease TOC level, find level to jump to -// -// for ($i = $toclevel; $i > 0; $i--) { -// if ($levelCount[$i] == $level) { -// // Found last matching level -// $toclevel = $i; -// break; -// } elseif ($levelCount[$i] < $level) { -// // Found first matching level below current level -// $toclevel = $i + 1; -// break; -// } -// } -// if ($i == 0) { -// $toclevel = 1; -// } -// if ($toclevel < $wgMaxTocLevel) { -// if ($prevtoclevel < $wgMaxTocLevel) { -// // Unindent only if the previous toc level was shown :p -// $toc .= Linker::tocUnindent($prevtoclevel - $toclevel); -// $prevtoclevel = $toclevel; -// } else { -// $toc .= Linker::tocLineEnd(); -// } -// } -// } else { -// // No change in level, end TOC line -// if ($toclevel < $wgMaxTocLevel) { +// } else { // $toc .= Linker::tocLineEnd(); // } // } +// } else { +// // No change in level, end TOC line +// if ($toclevel < $maxTocLevel) { +// $toc .= Linker::tocLineEnd(); +// } +// } // -// $levelCount[$toclevel] = $level; +// $levelCount[$toclevel] = $level; // -// // count number of headlines for each level -// $sublevelCount[$toclevel]++; -// $dot = 0; -// for ($i = 1; $i <= $toclevel; $i++) { -// if (!empty($sublevelCount[$i])) { -// if ($dot) { -// $numbering .= '.'; -// } -// $numbering .= this.getTargetLanguage()->formatNum($sublevelCount[$i]); -// $dot = 1; +// // count number of headlines for each level +// $sublevelCount[$toclevel]++; +// $dot = 0; +// for ($i = 1; $i <= $toclevel; $i++) { +// if (!empty($sublevelCount[$i])) { +// if ($dot) { +// $numbering .= '.'; // } +// $numbering .= $this.getTargetLanguage().formatNum($sublevelCount[$i]); +// $dot = 1; // } +// } +// +// // The safe header is a version of the header text safe to use for links +// +// // Remove link placeholders by the link text. +// // <!--LINK number-. +// // turns into +// // link text with suffix +// // Do this before unstrip since link text can contain strip markers +// $safeHeadline = $this.replaceLinkHoldersText($headline); +// +// // Avoid insertion of weird stuff like <math> by expanding the relevant sections +// $safeHeadline = $this.mStripState.unstripBoth($safeHeadline); +// +// // Remove any <style> or <script> tags (T198618) +// $safeHeadline = preg_replace( +// '#<(style|script)(?: [^>]*[^>/])?>.*?</\1>#is', +// '', +// $safeHeadline +// ); // -// // The safe header is a version of the header text safe to use for links -// -// // Remove link placeholders by the link text. -// // <!--LINK number--> -// // turns into -// // link text with suffix -// // Do this before unstrip since link text can contain strip markers -// $safeHeadline = this.replaceLinkHoldersText($headline); -// -// // Avoid insertion of weird stuff like <math> by expanding the relevant sections -// $safeHeadline = this.mStripState->unstripBoth($safeHeadline); -// -// // Strip out HTML (first regex removes any tag not allowed) -// // Allowed tags are: -// // * <sup> and <sub> (T10393) -// // * <i> (T28375) -// // * <b> (r105284) -// // * <bdi> (T74884) -// // * <span dir="rtl"> and <span dir="ltr"> (T37167) -// // * <s> and <strike> (T35715) -// // We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>, -// // to allow setting directionality in toc items. +// // Strip out HTML (first regex removes any tag not allowed) +// // Allowed tags are: +// // * <sup> and <sub> (T10393) +// // * <i> (T28375) +// // * <b> (r105284) +// // * <bdi> (T74884) +// // * <span dir="rtl"> and <span dir="ltr"> (T37167) +// // * <s> and <strike> (T35715) +// // We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>, +// // to allow setting directionality in toc items. // $tocline = preg_replace( -// [ -// '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#', -// '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#' -// ], -// [ '', '<$1>' ], -// $safeHeadline -// ); +// [ +// '#<(?!/?(span|sup|sub|bdi|i|b|s|strike)(?: [^>]*)?>).*?>#', +// '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b|s|strike))(?: .*?)?>#' +// ], +// [ '', '<$1>' ], +// $safeHeadline +// ); // -// // Strip '<span></span>', which is the result from the above if -// // <span id="foo"></span> is used to produce an additional anchor -// // for a section. -// $tocline = str_replace('<span></span>', '', $tocline); -// -// $tocline = trim($tocline); -// -// // For the anchor, strip out HTML-y stuff period -// $safeHeadline = preg_replace('/<.*?>/', '', $safeHeadline); -// $safeHeadline = Sanitizer::normalizeSectionNameWhitespace($safeHeadline); -// -// // Save headline for section edit hint before it's escaped -// $headlineHint = $safeHeadline; -// -// if ($wgExperimentalHtmlIds) { -// // For reverse compatibility, provide an id that's -// // HTML4-compatible, like we used to. -// // It may be worth noting, academically, that it's possible for -// // the legacy anchor to conflict with a non-legacy headline -// // anchor on the page. In this case likely the "correct" thing -// // would be to either drop the legacy anchors or make sure -// // they're numbered first. However, this would require people -// // to type in section names like "abc_.D7.93.D7.90.D7.A4" -// // manually, so let's not bother worrying about it. -// $legacyHeadline = Sanitizer::escapeId($safeHeadline, -// [ 'noninitial', 'legacy' ]); -// $safeHeadline = Sanitizer::escapeId($safeHeadline); -// -// if ($legacyHeadline == $safeHeadline) { -// // No reason to have both (in fact, we can't) -// $legacyHeadline = false; -// } -// } else { -// $legacyHeadline = false; -// $safeHeadline = Sanitizer::escapeId($safeHeadline, -// 'noninitial'); -// } +// // Strip '<span></span>', which is the result from the above if +// // <span id="foo"></span> is used to produce an additional anchor +// // for a section. +// $tocline = str_replace('<span></span>', '', $tocline); +// +// $tocline = trim($tocline); +// +// // For the anchor, strip out HTML-y stuff period +// $safeHeadline = preg_replace('/<.*?>/', '', $safeHeadline); +// $safeHeadline = Sanitizer::normalizeSectionNameWhitespace($safeHeadline); // -// // HTML names must be case-insensitively unique (T12721). -// // This does not apply to Unicode characters per -// // https://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-String-comparison -// // @todo FIXME: We may be changing them depending on the current locale. +// // Save headline for section edit hint before it's escaped +// $headlineHint = $safeHeadline; +// +// // Decode HTML entities +// $safeHeadline = Sanitizer::decodeCharReferences($safeHeadline); +// +// $safeHeadline = self::normalizeSectionName($safeHeadline); +// +// $fallbackHeadline = Sanitizer::escapeIdForAttribute($safeHeadline, Sanitizer::ID_FALLBACK); +// $linkAnchor = Sanitizer::escapeIdForLink($safeHeadline); +// $safeHeadline = Sanitizer::escapeIdForAttribute($safeHeadline, Sanitizer::ID_PRIMARY); +// if ($fallbackHeadline === $safeHeadline) { +// // No reason to have both (in fact, we can't) +// $fallbackHeadline = false; +// } +// +// // HTML IDs must be case-insensitively unique for IE compatibility (T12721). +// // @todo FIXME: We may be changing them depending on the current locale. // $arrayKey = strtolower($safeHeadline); -// if ($legacyHeadline === false) { -// $legacyArrayKey = false; +// if ($fallbackHeadline === false) { +// $fallbackArrayKey = false; // } else { -// $legacyArrayKey = strtolower($legacyHeadline); +// $fallbackArrayKey = strtolower($fallbackHeadline); // } // -// // Create the anchor for linking from the TOC to the section -// $anchor = $safeHeadline; -// $legacyAnchor = $legacyHeadline; +// // Create the anchor for linking from the TOC to the section +// $anchor = $safeHeadline; +// $fallbackAnchor = $fallbackHeadline; // if (isset($refers[$arrayKey])) { -// // @codingStandardsIgnoreStart +// // phpcs:ignore Generic.Formatting.DisallowMultipleStatements // for ($i = 2; isset($refers["${arrayKey}_$i"]); ++$i); -// // @codingStandardsIgnoreEnd // $anchor .= "_$i"; +// $linkAnchor .= "_$i"; // $refers["${arrayKey}_$i"] = true; // } else { // $refers[$arrayKey] = true; // } -// if ($legacyHeadline !== false && isset($refers[$legacyArrayKey])) { -// // @codingStandardsIgnoreStart -// for ($i = 2; isset($refers["${legacyArrayKey}_$i"]); ++$i); -// // @codingStandardsIgnoreEnd -// $legacyAnchor .= "_$i"; -// $refers["${legacyArrayKey}_$i"] = true; +// if ($fallbackHeadline !== false && isset($refers[$fallbackArrayKey])) { +// // phpcs:ignore Generic.Formatting.DisallowMultipleStatements +// for ($i = 2; isset($refers["${fallbackArrayKey}_$i"]); ++$i); +// $fallbackAnchor .= "_$i"; +// $refers["${fallbackArrayKey}_$i"] = true; // } else { -// $refers[$legacyArrayKey] = true; +// $refers[$fallbackArrayKey] = true; // } // -// // Don't number the heading if it is the only one (looks silly) -// if (count(matches[3]) > 1 && this.mOptions->getNumberHeadings()) { -// // the two are different if the line contains a link -// $headline = Html::element( +// // Don't number the heading if it is the only one (looks silly) +// if (count($matches[3]) > 1 && $this.mOptions.getNumberHeadings()) { +// // the two are different if the line contains a link +// $headline = Html::element( // 'span', -// [ 'class' => 'mw-headline-number' ], -// $numbering -// ) . ' ' . $headline; +// [ 'class' => 'mw-headline-number' ], +// $numbering +// ) . ' ' . $headline; // } // -// if ($enoughToc && (!isset($wgMaxTocLevel) || $toclevel < $wgMaxTocLevel)) { -// $toc .= Linker::tocLine($anchor, $tocline, +// if ($enoughToc && (!isset($maxTocLevel) || $toclevel < $maxTocLevel)) { +// $toc .= Linker::tocLine($linkAnchor, $tocline, // $numbering, $toclevel, ($isTemplate ? false : $sectionIndex)); // } // -// // Add the section to the section tree -// // Find the DOM node for this header -// $noOffset = ($isTemplate || $sectionIndex === false); +// // Add the section to the section tree +// // Find the DOM node for this header +// $noOffset = ($isTemplate || $sectionIndex === false); // while ($node && !$noOffset) { -// if ($node->getName() === 'h') { -// $bits = $node->splitHeading(); +// if ($node.getName() === 'h') { +// $bits = $node.splitHeading(); // if ($bits['i'] == $sectionIndex) { // break; // } // } -// $byteOffset += mb_strlen(this.mStripState->unstripBoth( -// $frame->expand($node, PPFrame::RECOVER_ORIG))); -// $node = $node->getNextSibling(); +// $byteOffset += mb_strlen($this.mStripState.unstripBoth( +// $frame.expand($node, PPFrame::RECOVER_ORIG))); +// $node = $node.getNextSibling(); // } // $tocraw[] = [ -// 'toclevel' => $toclevel, +// 'toclevel' => $toclevel, // 'level' => $level, // 'line' => $tocline, // 'number' => $numbering, // 'index' => ($isTemplate ? 'T-' : '') . $sectionIndex, -// 'fromtitle' => $titleText, +// 'fromtitle' => titleText, // 'byteoffset' => ($noOffset ? null : $byteOffset), // 'anchor' => $anchor, -// ]; +// ]; // -// // give headline the correct <h#> tag -// if (maybeShowEditLink && $sectionIndex !== false) { +// // give headline the correct <h#> tag +// if ($maybeShowEditLink && $sectionIndex !== false) { // // Output edit section links as markers with styles that can be customized by skins // if ($isTemplate) { -// // Put a T flag in the section identifier, to indicate to extractSections() -// // that sections inside <includeonly> should be counted. -// $editsectionPage = $titleText; +// // Put a T flag in the section identifier, to indicate to extractSections() +// // that sections inside <includeonly> should be counted. +// $editsectionPage = titleText; // $editsectionSection = "T-$sectionIndex"; // $editsectionContent = null; // } else { -// $editsectionPage = this.mTitle->getPrefixedText(); +// $editsectionPage = $this.mTitle.getPrefixedText(); // $editsectionSection = $sectionIndex; // $editsectionContent = $headlineHint; // } @@ -3682,549 +5298,547 @@ Tfds.Write(nowiki, isHTML, forceRawInterwiki, isChildObj, isLocalObj, titleText, // $editlink = ''; // } // $head[$headlineCount] = Linker::makeHeadline($level, -// matches['attrib'][$headlineCount], $anchor, $headline, -// $editlink, $legacyAnchor); +// $matches['attrib'][$headlineCount], $anchor, $headline, +// $editlink, $fallbackAnchor); // // $headlineCount++; -// } -// -// this.setOutputType($oldType); +// } // -// // Never ever show TOC if no headers -// if ($numVisible < 1) { -// $enoughToc = false; -// } +// $this.setOutputType($oldType); // -// if ($enoughToc) { -// if ($prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel) { -// $toc .= Linker::tocUnindent($prevtoclevel - 1); -// } -// $toc = Linker::tocList($toc, this.mOptions->getUserLangObj()); -// this.mOutput->setTOCHTML($toc); -// $toc = XomwParser.TOC_START . $toc . XomwParser.TOC_END; -// this.mOutput->addModules('mediawiki.toc'); -// } +// // Never ever show TOC if no headers +// if ($numVisible < 1) { +// $enoughToc = false; +// } // -// if ($isMain) { -// this.mOutput->setSections($tocraw); +// if ($enoughToc) { +// if ($prevtoclevel > 0 && $prevtoclevel < $maxTocLevel) { +// $toc .= Linker::tocUnindent($prevtoclevel - 1); // } +// $toc = Linker::tocList($toc, $this.mOptions.getUserLangObj()); +// $this.mOutput.setTOCHTML($toc); +// $toc = self::TOC_START . $toc . self::TOC_END; +// } // -// // split up and insert constructed headlines -// $blocks = preg_split('/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text); -// $i = 0; -// -// // build an array of document sections -// $sections = []; -// foreach ($blocks as $block) { -// // $head is zero-based, sections aren't. -// if (empty($head[$i - 1])) { -// $sections[$i] = $block; -// } else { -// $sections[$i] = $head[$i - 1] . $block; -// } +// if ($isMain) { +// $this.mOutput.setSections($tocraw); +// } // -// /** -// * Send a hook, one per section. -// * The idea here is to be able to make section-level DIVs, but to do so in a -// * lower-impact, more correct way than r50769 -// * -// * $this : caller -// * $section : the section number -// * &$sectionContent : ref to the content of the section -// * $showEditLinks : boolean describing whether this section has an edit link -// */ -// Hooks::run('ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ]); -// -// $i++; -// } +// // split up and insert constructed headlines +// $blocks = preg_split('/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text); +// $i = 0; // -// if ($enoughToc && $isMain && !this.mForceTocPosition) { -// // append the TOC at the beginning -// // Top anchor now in skin -// $sections[0] = $sections[0] . $toc . "\n"; +// // build an array of document sections +// $sections = []; +// foreach ($blocks as $block) { +// // $head is zero-based, sections aren't. +// if (empty($head[$i - 1])) { +// $sections[$i] = $block; +// } else { +// $sections[$i] = $head[$i - 1] . $block; // } // -// $full .= implode('', $sections); +// /** +// * Send a hook, one per section. +// * The idea here is to be able to make section-level DIVs, but to do so in a +// * lower-impact, more correct way than r50769 +// * +// * $this : caller +// * $section : the section number +// * &$sectionContent : ref to the content of the section +// * $maybeShowEditLinks : boolean describing whether this section has an edit link +// */ +// Hooks::run('ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ]); +// +// $i++; +// } // -// if (this.mForceTocPosition) { -// return str_replace('<!--MWTOC-->', $toc, $full); -// } else { -// return $full; -// } +// if ($enoughToc && $isMain && !$this.mForceTocPosition) { +// // append the TOC at the beginning +// // Top anchor now in skin +// $sections[0] .= $toc . "\n"; // } // -// /** -// * Transform wiki markup when saving a page by doing "\r\n" -> "\n" -// * conversion, substituting signatures, {{subst:}} templates, etc. -// * -// * @param String $text The text to transform -// * @param Title $title The Title Object for the current article -// * @param User $user The User Object describing the current user -// * @param ParserOptions $options Parsing options -// * @param boolean $clearState Whether to clear the parser state first -// * @return String The altered wiki markup -// */ -// public function preSaveTransform($text, Title $title, User $user, -// ParserOptions $options, $clearState = true -// ) { -// if ($clearState) { -// magicScopeVariable = this.synchronized(); -// } -// this.startParse($title, $options, XomwParser.OT_WIKI, $clearState); -// this.setUser($user); +// $full .= implode('', $sections); // -// // We still normalize line endings for backwards-compatibility -// // with other code that just calls PST, but this should already -// // be handled in TextContent subclasses -// $text = TextContent::normalizeLineEndings($text); +// if ($this.mForceTocPosition) { +// return str_replace('<!--MWTOC\'"-.', $toc, $full); +// } else { +// return $full; +// } +// } +// +// /** +// * Transform wiki markup when saving a page by doing "\r\n" . "\n" +// * conversion, substituting signatures, {{subst:}} templates, etc. +// * +// * @param string $text The text to transform +// * @param Title title The Title object for the current article +// * @param User $user The User object describing the current user +// * @param ParserOptions $options Parsing options +// * @param bool $clearState Whether to clear the parser state first +// * @return string The altered wiki markup +// */ +// public function preSaveTransform($text, Title title, User $user, +// ParserOptions $options, $clearState = true +// ) { +// if ($clearState) { +// $magicScopeVariable = $this.lock(); +// } +// $this.startParse(title, $options, self::OT_WIKI, $clearState); +// $this.setUser($user); // -// if ($options->getPreSaveTransform()) { -// $text = this.pstPass2($text, $user); -// } -// $text = this.mStripState->unstripBoth($text); +// // Strip U+0000 NULL (T159174) +// $text = str_replace("\000", '', $text); // -// this.setUser(null); // Reset +// // We still normalize line endings for backwards-compatibility +// // with other code that just calls PST, but this should already +// // be handled in TextContent subclasses +// $text = TextContent::normalizeLineEndings($text); // -// return $text; +// if ($options.getPreSaveTransform()) { +// $text = $this.pstPass2($text, $user); // } -// -// /** -// * Pre-save transform helper function -// * -// * @param String $text -// * @param User $user -// * -// * @return String -// */ -// private function pstPass2($text, $user) { -// global $wgContLang; -// -// // Note: This is the timestamp saved as hardcoded wikitext to -// // the database, we use $wgContLang here in order to give -// // everyone the same signature and use the default one rather -// // than the one selected in each user's preferences. -// // (see also T14815) -// $ts = this.mOptions->getTimestamp(); -// $timestamp = MWTimestamp::getLocalInstance($ts); -// $ts = $timestamp->format('YmdHis'); -// $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text(); -// -// $d = $wgContLang->timeanddate($ts, false, false) . " ($tzMsg)"; -// -// // Variable replacement -// // Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags -// $text = this.replaceVariables($text); -// -// // This works almost by chance, as the replaceVariables are done before the getUserSig(), -// // which may corrupt this parser instance via its wfMessage()->text() call- -// -// // Signatures -// $sigText = this.getUserSig($user); +// $text = $this.mStripState.unstripBoth($text); +// +// $this.setUser(null); # Reset +// +// return $text; +// } +// +// /** +// * Pre-save transform helper function +// * +// * @param string $text +// * @param User $user +// * +// * @return string +// */ +// private function pstPass2($text, $user) { +// // Note: This is the timestamp saved as hardcoded wikitext to the database, we use +// // $this.contLang here in order to give everyone the same signature and use the default one +// // rather than the one selected in each user's preferences. (see also T14815) +// $ts = $this.mOptions.getTimestamp(); +// $timestamp = MWTimestamp::getLocalInstance($ts); +// $ts = $timestamp.format('YmdHis'); +// $tzMsg = $timestamp.getTimezoneMessage().inContentLanguage().text(); +// +// $d = $this.contLang.timeanddate($ts, false, false) . " ($tzMsg)"; +// +// // Variable replacement +// // Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags +// $text = $this.replaceVariables($text); +// +// // This works almost by chance, as the replaceVariables are done before the getUserSig(), +// // which may corrupt this parser instance via its wfMessage().text() call- +// +// // Signatures +// if (strpos($text, '~~~') !== false) { +// $sigText = $this.getUserSig($user); // $text = strtr($text, [ // '~~~~~' => $d, // '~~~~' => "$sigText $d", // '~~~' => $sigText // ]); +// // The main two signature forms used above are time-sensitive +// $this.mOutput.setFlag('user-signature'); +// } // -// // Context links ("pipe tricks"): [[|name]] and [[name (context)|]] -// $tc = '[' . Title::legalChars() . ']'; -// $nc = '[ _0-9A-Za-z\x80-\xff-]'; // Namespaces can use non-ascii! +// // Context links ("pipe tricks"): [[|name]] and [[name (context)|]] +// $tc = '[' . Title::legalChars() . ']'; +// $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii! // // // [[ns:page (context)|]] // $p1 = "/\[\[(:?$nc+:|:|)($tc+?)(?\\($tc+\\))\\|]]/"; -// // [[ns:page(context)|]] (double-width brackets, added in r40257) -// $p4 = "/\[\[(:?$nc+:|:|)($tc+?)(?($tc+))\\|]]/"; -// // [[ns:page (context), context|]] (using either single or double-width comma) -// $p3 = "/\[\[(:?$nc+:|:|)($tc+?)(?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; -// // [[|page]] (reverse pipe trick: add context from page title) -// $p2 = "/\[\[\\|($tc+)]]/"; -// -// // try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" -// $text = preg_replace($p1, '[[\\1\\2\\3|\\2]]', $text); -// $text = preg_replace($p4, '[[\\1\\2\\3|\\2]]', $text); -// $text = preg_replace($p3, '[[\\1\\2\\3\\4|\\2]]', $text); -// -// $t = this.mTitle->getText(); -// m = []; -// if (preg_match("/^($nc+:|)$tc+?(\\($tc+\\))$/", $t, m)) { -// $text = preg_replace($p2, "[[m[1]\\1m[2]|\\1]]", $text); -// } elseif (preg_match("/^($nc+:|)$tc+?(, $tc+|)$/", $t, m) && "m[1]m[2]" != '') { -// $text = preg_replace($p2, "[[m[1]\\1m[2]|\\1]]", $text); -// } else { -// // if there's no context, don't bother duplicating the title +// // [[ns:page(context)|]] (double-width brackets, added in r40257) +// $p4 = "/\[\[(:?$nc+:|:|)($tc+?)(?($tc+))\\|]]/"; +// // [[ns:page (context), context|]] (using either single or double-width comma) +// $p3 = "/\[\[(:?$nc+:|:|)($tc+?)(?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; +// // [[|page]] (reverse pipe trick: add context from page title) +// $p2 = "/\[\[\\|($tc+)]]/"; +// +// // try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]" +// $text = preg_replace($p1, '[[\\1\\2\\3|\\2]]', $text); +// $text = preg_replace($p4, '[[\\1\\2\\3|\\2]]', $text); +// $text = preg_replace($p3, '[[\\1\\2\\3\\4|\\2]]', $text); +// +// $t = $this.mTitle.getText(); +// $m = []; +// if (preg_match("/^($nc+:|)$tc+?(\\($tc+\\))$/", $t, $m)) { +// $text = preg_replace($p2, "[[$m[1]\\1$m[2]|\\1]]", $text); +// } elseif (preg_match("/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m) && "$m[1]$m[2]" != '') { +// $text = preg_replace($p2, "[[$m[1]\\1$m[2]|\\1]]", $text); +// } else { +// // if there's no context, don't bother duplicating the title // $text = preg_replace($p2, '[[\\1]]', $text); -// } -// -// return $text; // } // -// /** -// * Fetch the user's signature text, if any, and normalize to -// * validated, ready-to-insert wikitext. -// * If you have pre-fetched the nickname or the fancySig option, you can -// * specify them here to save a database query. -// * Do not reuse this parser instance after calling getUserSig(), -// * as it may have changed if it's the $wgParser. -// * -// * @param User $user -// * @param String|boolean $nickname Nickname to use or false to use user's default nickname -// * @param boolean|null $fancySig whether the nicknname is the complete signature -// * or null to use default value -// * @return String -// */ -// public function getUserSig(&$user, $nickname = false, $fancySig = null) { -// global $wgMaxSigChars; -// -// $username = $user->getName(); -// -// // If not given, retrieve from the user Object. -// if ($nickname === false) { -// $nickname = $user->getOption('nickname'); -// } +// return $text; +// } +// +// /** +// * Fetch the user's signature text, if any, and normalize to +// * validated, ready-to-insert wikitext. +// * If you have pre-fetched the nickname or the fancySig option, you can +// * specify them here to save a database query. +// * Do not reuse this parser instance after calling getUserSig(), +// * as it may have changed if it's the $wgParser. +// * +// * @param User &$user +// * @param string|bool $nickname Nickname to use or false to use user's default nickname +// * @param bool|null $fancySig whether the nicknname is the complete signature +// * or null to use default value +// * @return string +// */ +// public function getUserSig(&$user, $nickname = false, $fancySig = null) { +// $username = $user.getName(); +// +// // If not given, retrieve from the user object. +// if ($nickname === false) { +// $nickname = $user.getOption('nickname'); +// } // -// if (is_null($fancySig)) { -// $fancySig = $user->getBoolOption('fancysig'); -// } +// if (is_null($fancySig)) { +// $fancySig = $user.getBoolOption('fancysig'); +// } // -// $nickname = $nickname == null ? $username : $nickname; +// $nickname = $nickname == null ? $username : $nickname; // -// if (mb_strlen($nickname) > $wgMaxSigChars) { -// $nickname = $username; -// wfDebug(__METHOD__ . ": $username has overlong signature.\n"); -// } elseif ($fancySig !== false) { -// // Sig. might contain markup; validate this -// if (this.validateSig($nickname) !== false) { -// // Validated; clean up (if needed) and return it -// return this.cleanSig($nickname, true); -// } else { -// // Failed to validate; fall back to the default +// if (mb_strlen($nickname) > $this.siteConfig.get('MaxSigChars')) { +// $nickname = $username; +// wfDebug(__METHOD__ . ": $username has overlong signature.\n"); +// } elseif ($fancySig !== false) { +// // Sig. might contain markup; validate this +// if ($this.validateSig($nickname) !== false) { +// // Validated; clean up (if needed) and return it +// return $this.cleanSig($nickname, true); +// } else { +// // Failed to validate; fall back to the default // $nickname = $username; // wfDebug(__METHOD__ . ": $username has bad XML tags in signature.\n"); -// } // } -// -// // Make sure nickname doesnt get a sig in a sig -// $nickname = XomwParser.cleanSigInSig($nickname); -// -// // If we're still here, make it a link to the user page -// $userText = wfEscapeWikiText($username); -// $nickText = wfEscapeWikiText($nickname); -// msgName = $user->isAnon() ? 'signature-anon' : 'signature'; -// -// return wfMessage(msgName, $userText, $nickText)->inContentLanguage() -// ->title(this.getTitle())->text(); // } // -// /** -// * Check that the user's signature contains no bad XML -// * -// * @param String $text -// * @return String|boolean An expanded String, or false if invalid. -// */ -// public function validateSig($text) { -// return Xml::isWellFormedXmlFragment($text) ? $text : false; +// // Make sure nickname doesnt get a sig in a sig +// $nickname = self::cleanSigInSig($nickname); +// +// // If we're still here, make it a link to the user page +// $userText = wfEscapeWikiText($username); +// $nickText = wfEscapeWikiText($nickname); +// $msgName = $user.isAnon() ? 'signature-anon' : 'signature'; +// +// return wfMessage($msgName, $userText, $nickText).inContentLanguage() +// .title($this.getTitle()).text(); +// } +// +// /** +// * Check that the user's signature contains no bad XML +// * +// * @param string $text +// * @return string|bool An expanded string, or false if invalid. +// */ +// public function validateSig($text) { +// return Xml::isWellFormedXmlFragment($text) ? $text : false; +// } +// +// /** +// * Clean up signature text +// * +// * 1) Strip 3, 4 or 5 tildes out of signatures @see cleanSigInSig +// * 2) Substitute all transclusions +// * +// * @param string $text +// * @param bool $parsing Whether we're cleaning (preferences save) or parsing +// * @return string Signature text +// */ +// public function cleanSig($text, $parsing = false) { +// if (!$parsing) { +// global $wgTitle; +// $magicScopeVariable = $this.lock(); +// $this.startParse($wgTitle, new ParserOptions, self::OT_PREPROCESS, true); // } // -// /** -// * Clean up signature text -// * -// * 1) Strip 3, 4 or 5 tildes out of signatures @see cleanSigInSig -// * 2) Substitute all transclusions -// * -// * @param String $text -// * @param boolean $parsing Whether we're cleaning (preferences save) or parsing -// * @return String Signature text -// */ -// public function cleanSig($text, $parsing = false) { -// if (!$parsing) { -// global $wgTitle; -// magicScopeVariable = this.synchronized(); -// this.startParse($wgTitle, new ParserOptions, XomwParser.OT_PREPROCESS, true); -// } -// -// // Option to disable this feature -// if (!this.mOptions->getCleanSignatures()) { -// return $text; -// } -// -// // @todo FIXME: Regex doesn't respect extension tags or nowiki -// // => Move this logic to braceSubstitution() -// $substWord = MagicWord::get('subst'); -// $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase(); -// $substText = '{{' . $substWord->getSynonym(0); -// -// $text = preg_replace($substRegex, $substText, $text); -// $text = XomwParser.cleanSigInSig($text); -// $dom = this.preprocessToDom($text); -// $frame = this.getPreprocessor()->newFrame(); -// $text = $frame->expand($dom); -// -// if (!$parsing) { -// $text = this.mStripState->unstripBoth($text); -// } -// +// // Option to disable this feature +// if (!$this.mOptions.getCleanSignatures()) { // return $text; // } // -// /** -// * Strip 3, 4 or 5 tildes out of signatures. -// * -// * @param String $text -// * @return String Signature text with /~{3,5}/ removed -// */ -// public static function cleanSigInSig($text) { -// $text = preg_replace('/~{3,5}/', '', $text); -// return $text; -// } +// // @todo FIXME: Regex doesn't respect extension tags or nowiki +// // => Move this logic to braceSubstitution() +// $substWord = $this.magicWordFactory.get('subst'); +// $substRegex = '/\{\{(?!(?:' . $substWord.getBaseRegex() . '))/x' . $substWord.getRegexCase(); +// $substText = '{{' . $substWord.getSynonym(0); // -// /** -// * Set up some variables which are usually set up in parse() -// * so that an external function can call some class members with confidence -// * -// * @param Title|null $title -// * @param ParserOptions $options -// * @param int $outputType -// * @param boolean $clearState -// */ -// public function startExternalParse(Title $title = null, ParserOptions $options, -// $outputType, $clearState = true -// ) { -// this.startParse($title, $options, $outputType, $clearState); -// } +// $text = preg_replace($substRegex, $substText, $text); +// $text = self::cleanSigInSig($text); +// $dom = $this.preprocessToDom($text); +// $frame = $this.getPreprocessor().newFrame(); +// $text = $frame.expand($dom); // -// /** -// * @param Title|null $title -// * @param ParserOptions $options -// * @param int $outputType -// * @param boolean $clearState -// */ -// private function startParse(Title $title = null, ParserOptions $options, -// $outputType, $clearState = true -// ) { -// this.setTitle($title); -// this.mOptions = $options; -// this.setOutputType($outputType); -// if ($clearState) { -// this.clearState(); -// } +// if (!$parsing) { +// $text = $this.mStripState.unstripBoth($text); // } // -// /** -// * Wrapper for preprocess() -// * -// * @param String $text The text to preprocess -// * @param ParserOptions $options Options -// * @param Title|null $title Title Object or null to use $wgTitle -// * @return String -// */ -// public function transformMsg($text, $options, $title = null) { -// static $executing = false; -// -// // Guard against infinite recursion -// if ($executing) { -// return $text; -// } -// $executing = true; -// -// if (!$title) { -// global $wgTitle; -// $title = $wgTitle; -// } -// -// $text = this.preprocess($text, $title, $options); -// -// $executing = false; +// return $text; +// } +// +// /** +// * Strip 3, 4 or 5 tildes out of signatures. +// * +// * @param string $text +// * @return string Signature text with /~{3,5}/ removed +// */ +// public static function cleanSigInSig($text) { +// $text = preg_replace('/~{3,5}/', '', $text); +// return $text; +// } +// +// /** +// * Set up some variables which are usually set up in parse() +// * so that an external function can call some class members with confidence +// * +// * @param Title|null title +// * @param ParserOptions $options +// * @param int $outputType +// * @param bool $clearState +// */ +// public function startExternalParse(Title title = null, ParserOptions $options, +// $outputType, $clearState = true +// ) { +// $this.startParse(title, $options, $outputType, $clearState); +// } +// +// /** +// * @param Title|null title +// * @param ParserOptions $options +// * @param int $outputType +// * @param bool $clearState +// */ +// private function startParse(Title title = null, ParserOptions $options, +// $outputType, $clearState = true +// ) { +// $this.setTitle(title); +// $this.mOptions = $options; +// $this.setOutputType($outputType); +// if ($clearState) { +// $this.clearState(); +// } +// } +// +// /** +// * Wrapper for preprocess() +// * +// * @param string $text The text to preprocess +// * @param ParserOptions $options +// * @param Title|null title Title object or null to use $wgTitle +// * @return string +// */ +// public function transformMsg($text, $options, title = null) { +// static $executing = false; +// +// // Guard against infinite recursion +// if ($executing) { // return $text; // } +// $executing = true; // -// /** -// * Create an HTML-style tag, e.g. "<yourtag>special text</yourtag>" -// * The callback should have the following form: -// * function myParserHook($text, $params, $parser, $frame) { ... } -// * -// * Transform and return $text. Use $parser for any required context, e.g. use -// * $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions -// * -// * Hooks may return extended information by returning an array, of which the -// * first numbered element (index 0) must be the return String, and all other -// * entries are extracted into local variables within an @gplx.Internal protected function -// * in the Parser class. -// * -// * This interface (introduced r61913) appears to be undocumented, but -// * 'markerType' is used by some core tag hooks to override which strip -// * array their results are placed in. **Use great caution if attempting -// * this interface, as it is not documented and injudicious use could smash -// * private variables.** -// * -// * @param String $tag The tag to use, e.g. 'hook' for "<hook>" -// * @param callable $callback The callback function (and Object) to use for the tag -// * @throws MWException -// * @return callable|null The old value of the mTagHooks array associated with the hook -// */ -// public function setHook($tag, $callback) { -// $tag = strtolower($tag); -// if (preg_match('/[<>\r\n]/', $tag, m)) { -// throw new MWException("Invalid character {m[0]} in setHook('$tag', ...) call"); -// } -// $oldVal = isset(this.mTagHooks[$tag]) ? this.mTagHooks[$tag] : null; -// this.mTagHooks[$tag] = $callback; -// if (!in_array($tag, this.mStripList)) { -// this.mStripList[] = $tag; -// } -// -// return $oldVal; +// if (!title) { +// global $wgTitle; +// title = $wgTitle; // } // -// /** -// * As setHook(), but letting the contents be parsed. -// * -// * Transparent tag hooks are like regular XML-style tag hooks, except they -// * operate late in the transformation sequence, on HTML instead of wikitext. -// * -// * This is probably obsoleted by things dealing with parser frames? -// * The only extension currently using it is geoserver. -// * -// * @since 1.10 -// * @todo better document or deprecate this -// * -// * @param String $tag The tag to use, e.g. 'hook' for "<hook>" -// * @param callable $callback The callback function (and Object) to use for the tag -// * @throws MWException -// * @return callable|null The old value of the mTagHooks array associated with the hook -// */ -// public function setTransparentTagHook($tag, $callback) { -// $tag = strtolower($tag); -// if (preg_match('/[<>\r\n]/', $tag, m)) { -// throw new MWException("Invalid character {m[0]} in setTransparentHook('$tag', ...) call"); -// } -// $oldVal = isset(this.mTransparentTagHooks[$tag]) ? this.mTransparentTagHooks[$tag] : null; -// this.mTransparentTagHooks[$tag] = $callback; -// -// return $oldVal; +// $text = $this.preprocess($text, title, $options); +// +// $executing = false; +// return $text; +// } +// +// /** +// * Create an HTML-style tag, e.g. "<yourtag>special text</yourtag>" +// * The callback should have the following form: +// * function myParserHook($text, $params, $parser, $frame) { ... } +// * +// * Transform and return $text. Use $parser for any required context, e.g. use +// * $parser.getTitle() and $parser.getOptions() not $wgTitle or $wgOut.mParserOptions +// * +// * Hooks may return extended information by returning an array, of which the +// * first numbered element (index 0) must be the return string, and all other +// * entries are extracted into local variables within an internal function +// * in the Parser class. +// * +// * This interface (introduced r61913) appears to be undocumented, but +// * 'markerType' is used by some core tag hooks to override which strip +// * array their results are placed in. **Use great caution if attempting +// * this interface, as it is not documented and injudicious use could smash +// * private variables.** +// * +// * @param string $tag The tag to use, e.g. 'hook' for "<hook>" +// * @param callable $callback The callback function (and object) to use for the tag +// * @throws MWException +// * @return callable|null The old value of the mTagHooks array associated with the hook +// */ +// public function setHook($tag, callable $callback) { +// $tag = strtolower($tag); +// if (preg_match('/[<>\r\n]/', $tag, $m)) { +// throw new MWException("Invalid character {$m[0]} in setHook('$tag', ...) call"); // } -// -// /** -// * Remove all tag hooks -// */ -// public function clearTagHooks() { -// this.mTagHooks = []; -// this.mFunctionTagHooks = []; -// this.mStripList = this.mDefaultStripList; +// $oldVal = $this.mTagHooks[$tag] ?? null; +// $this.mTagHooks[$tag] = $callback; +// if (!in_array($tag, $this.mStripList)) { +// $this.mStripList[] = $tag; // } // -// /** -// * Create a function, e.g. {{sum:1|2|3}} -// * The callback function should have the form: -// * function myParserFunction(&$parser, $arg1, $arg2, $arg3) { ... } -// * -// * Or with Parser::SFH_OBJECT_ARGS: -// * function myParserFunction($parser, $frame, $args) { ... } -// * -// * The callback may either return the text result of the function, or an array with the text -// * in element 0, and a number of flags in the other elements. The names of the flags are -// * specified in the keys. Valid flags are: -// * found The text returned is valid, stop processing the template. This -// * is on by default. -// * nowiki Wiki markup in the return value should be escaped -// * isHTML The returned text is HTML, armour it against wikitext transformation -// * -// * @param String $id The magic word ID -// * @param callable $callback The callback function (and Object) to use -// * @param int $flags A combination of the following flags: -// * Parser::SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}} -// * -// * Parser::SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text. -// * This allows for conditional expansion of the parse tree, allowing you to eliminate dead -// * branches and thus speed up parsing. It is also possible to analyse the parse tree of -// * the arguments, and to control the way they are expanded. -// * -// * The $frame parameter is a PPFrame. This can be used to produce expanded text from the -// * arguments, for instance: -// * $text = isset($args[0]) ? $frame->expand($args[0]) : ''; -// * -// * For technical reasons, $args[0] is pre-expanded and will be a String. This may change in -// * future versions. Please call $frame->expand() on it anyway so that your code keeps -// * working if/when this is changed. -// * -// * If you want whitespace to be trimmed from $args, you need to do it yourself, post- -// * expansion. -// * -// * Please read the documentation in includes/parser/Preprocessor.php for more information -// * about the methods available in PPFrame and PPNode. -// * -// * @throws MWException -// * @return String|callable The old callback function for this name, if any -// */ -// public function setFunctionHook($id, $callback, $flags = 0) { -// global $wgContLang; +// return $oldVal; +// } +// +// /** +// * As setHook(), but letting the contents be parsed. +// * +// * Transparent tag hooks are like regular XML-style tag hooks, except they +// * operate late in the transformation sequence, on HTML instead of wikitext. +// * +// * This is probably obsoleted by things dealing with parser frames? +// * The only extension currently using it is geoserver. +// * +// * @since 1.10 +// * @todo better document or deprecate this +// * +// * @param string $tag The tag to use, e.g. 'hook' for "<hook>" +// * @param callable $callback The callback function (and object) to use for the tag +// * @throws MWException +// * @return callable|null The old value of the mTagHooks array associated with the hook +// */ +// public function setTransparentTagHook($tag, callable $callback) { +// $tag = strtolower($tag); +// if (preg_match('/[<>\r\n]/', $tag, $m)) { +// throw new MWException("Invalid character {$m[0]} in setTransparentHook('$tag', ...) call"); +// } +// $oldVal = $this.mTransparentTagHooks[$tag] ?? null; +// $this.mTransparentTagHooks[$tag] = $callback; +// +// return $oldVal; +// } +// +// /** +// * Remove all tag hooks +// */ +// public function clearTagHooks() { +// $this.mTagHooks = []; +// $this.mFunctionTagHooks = []; +// $this.mStripList = $this.mDefaultStripList; +// } +// +// /** +// * Create a function, e.g. {{sum:1|2|3}} +// * The callback function should have the form: +// * function myParserFunction(&$parser, $arg1, $arg2, $arg3) { ... } +// * +// * Or with Parser::SFH_OBJECT_ARGS: +// * function myParserFunction($parser, $frame, $args) { ... } +// * +// * The callback may either return the text result of the function, or an array with the text +// * in element 0, and a number of flags in the other elements. The names of the flags are +// * specified in the keys. Valid flags are: +// * found The text returned is valid, stop processing the template. This +// * is on by default. +// * nowiki Wiki markup in the return value should be escaped +// * isHTML The returned text is HTML, armour it against wikitext transformation +// * +// * @param string $id The magic word ID +// * @param callable $callback The callback function (and object) to use +// * @param int $flags A combination of the following flags: +// * Parser::SFH_NO_HASH No leading hash, i.e. {{plural:...}} instead of {{#if:...}} +// * +// * Parser::SFH_OBJECT_ARGS Pass the template arguments as PPNode objects instead of text. +// * This allows for conditional expansion of the parse tree, allowing you to eliminate dead +// * branches and thus speed up parsing. It is also possible to analyse the parse tree of +// * the arguments, and to control the way they are expanded. +// * +// * The $frame parameter is a PPFrame. This can be used to produce expanded text from the +// * arguments, for instance: +// * $text = isset($args[0]) ? $frame.expand($args[0]) : ''; +// * +// * For technical reasons, $args[0] is pre-expanded and will be a string. This may change in +// * future versions. Please call $frame.expand() on it anyway so that your code keeps +// * working if/when this is changed. +// * +// * If you want whitespace to be trimmed from $args, you need to do it yourself, post- +// * expansion. +// * +// * Please read the documentation in includes/parser/Preprocessor.php for more information +// * about the methods available in PPFrame and PPNode. +// * +// * @throws MWException +// * @return string|callable The old callback function for this name, if any +// */ +// public function setFunctionHook($id, callable $callback, $flags = 0) { +// $oldVal = isset($this.mFunctionHooks[$id]) ? $this.mFunctionHooks[$id][0] : null; +// $this.mFunctionHooks[$id] = [ $callback, $flags ]; +// +// // Add to function cache +// $mw = $this.magicWordFactory.get($id); +// if (!$mw) { +// throw new MWException(__METHOD__ . '() expecting a magic word identifier.'); +// } // -// $oldVal = isset(this.mFunctionHooks[$id]) ? this.mFunctionHooks[$id][0] : null; -// this.mFunctionHooks[$id] = [ $callback, $flags ]; +// $synonyms = $mw.getSynonyms(); +// $sensitive = intval($mw.isCaseSensitive()); // -// // Add to function cache -// mw = MagicWord::get($id); -// if (!mw) { -// throw new MWException(__METHOD__ . '() expecting a magic word identifier.'); +// foreach ($synonyms as $syn) { +// // Case +// if (!$sensitive) { +// $syn = $this.contLang.lc($syn); // } -// -// $synonyms = mw->getSynonyms(); -// $sensitive = intval(mw->isCaseSensitive()); -// -// foreach ($synonyms as $syn) { -// // Case -// if (!$sensitive) { -// $syn = $wgContLang->lc($syn); -// } -// // Add leading hash -// if (!($flags & XomwParser.SFH_NO_HASH)) { -// $syn = '#' . $syn; -// } -// // Remove trailing colon -// if (substr($syn, -1, 1) === ':') { -// $syn = substr($syn, 0, -1); -// } -// this.mFunctionSynonyms[$sensitive][$syn] = $id; +// // Add leading hash +// if (!($flags & self::SFH_NO_HASH)) { +// $syn = '#' . $syn; // } -// return $oldVal; +// // Remove trailing colon +// if (substr($syn, -1, 1) === ':') { +// $syn = substr($syn, 0, -1); +// } +// $this.mFunctionSynonyms[$sensitive][$syn] = $id; // } -// -// /** -// * Get all registered function hook identifiers -// * -// * @return array -// */ -// public function getFunctionHooks() { -// return array_keys(this.mFunctionHooks); +// return $oldVal; +// } +// +// /** +// * Get all registered function hook identifiers +// * +// * @return array +// */ +// public function getFunctionHooks() { +// $this.firstCallInit(); +// return array_keys($this.mFunctionHooks); +// } +// +// /** +// * Create a tag function, e.g. "<test>some stuff</test>". +// * Unlike tag hooks, tag functions are parsed at preprocessor level. +// * Unlike parser functions, their content is not preprocessed. +// * @param string $tag +// * @param callable $callback +// * @param int $flags +// * @throws MWException +// * @return null +// */ +// public function setFunctionTagHook($tag, callable $callback, $flags) { +// $tag = strtolower($tag); +// if (preg_match('/[<>\r\n]/', $tag, $m)) { +// throw new MWException("Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call"); // } +// $old = $this.mFunctionTagHooks[$tag] ?? null; +// $this.mFunctionTagHooks[$tag] = [ $callback, $flags ]; // -// /** -// * Create a tag function, e.g. "<test>some stuff</test>". -// * Unlike tag hooks, tag functions are parsed at preprocessor level. -// * Unlike parser functions, their content is not preprocessed. -// * @param String $tag -// * @param callable $callback -// * @param int $flags -// * @throws MWException -// * @return null -// */ -// public function setFunctionTagHook($tag, $callback, $flags) { -// $tag = strtolower($tag); -// if (preg_match('/[<>\r\n]/', $tag, m)) { -// throw new MWException("Invalid character {m[0]} in setFunctionTagHook('$tag', ...) call"); -// } -// $old = isset(this.mFunctionTagHooks[$tag]) ? -// this.mFunctionTagHooks[$tag] : null; -// this.mFunctionTagHooks[$tag] = [ $callback, $flags ]; -// -// if (!in_array($tag, this.mStripList)) { -// this.mStripList[] = $tag; -// } -// -// return $old; +// if (!in_array($tag, $this.mStripList)) { +// $this.mStripList[] = $tag; // } +// +// return $old; +// } /** - * Replace "<!--LINK-->" link placeholders with actual links, in the buffer + * Replace "<!--LINK-." link placeholders with actual links, in the buffer * Placeholders created in Linker::link() * * @param String $text @@ -4241,376 +5855,379 @@ Tfds.Write(nowiki, isHTML, forceRawInterwiki, isChildObj, isLocalObj, titleText, return tmp_pbfr.Trg().To_bry_and_clear(); } -// /** -// * Replace "<!--LINK-->" link placeholders with plain text of links -// * (not HTML-formatted). -// * -// * @param String $text -// * @return String -// */ -// public function replaceLinkHoldersText($text) { -// return this.mLinkHolders->replaceText($text); +// MW.SRC:v1.33.1 +// /** +// * Replace "<!--LINK-." link placeholders with actual links, in the buffer +// * Placeholders created in Linker::link() +// * +// * @param string &$text +// * @param int $options +// */ +// public function replaceLinkHolders(&$text, $options = 0) { +// $this.mLinkHolders.replace($text); +// } +// +// /** +// * Replace "<!--LINK-." link placeholders with plain text of links +// * (not HTML-formatted). +// * +// * @param string $text +// * @return string +// */ +// public function replaceLinkHoldersText($text) { +// return $this.mLinkHolders.replaceText($text); +// } +// +// /** +// * Renders an image gallery from a text with one line per image. +// * text labels may be given by using |-style alternative text. E.g. +// * Image:one.jpg|The number "1" +// * Image:tree.jpg|A tree +// * given as text will return the HTML of a gallery with two images, +// * labeled 'The number "1"' and +// * 'A tree'. +// * +// * @param string $text +// * @param array $params +// * @return string HTML +// */ +// public function renderImageGallery($text, $params) { +// $mode = false; +// if (isset($params['mode'])) { +// $mode = $params['mode']; // } // -// /** -// * Renders an image gallery from a text with one line per image. -// * text labels may be given by using |-style alternative text. E.g. -// * Image:one.jpg|The number "1" -// * Image:tree.jpg|A tree -// * given as text will return the HTML of a gallery with two images, -// * labeled 'The number "1"' and -// * 'A tree'. -// * -// * @param String $text -// * @param array $params -// * @return String HTML -// */ -// public function renderImageGallery($text, $params) { -// -// mode = false; -// if (isset($params['mode'])) { -// mode = $params['mode']; -// } +// try { +// $ig = ImageGalleryBase::factory($mode); +// } catch (Exception $e) { +// // If invalid type set, fallback to default. +// $ig = ImageGalleryBase::factory(false); +// } // -// try { -// $ig = ImageGalleryBase::factory(mode); -// } catch (Exception $e) { -// // If invalid type set, fallback to default. -// $ig = ImageGalleryBase::factory(false); -// } +// $ig.setContextTitle($this.mTitle); +// $ig.setShowBytes(false); +// $ig.setShowDimensions(false); +// $ig.setShowFilename(false); +// $ig.setParser($this); +// $ig.setHideBadImages(); +// $ig.setAttributes(Sanitizer::validateTagAttributes($params, 'ul')); +// +// if (isset($params['showfilename'])) { +// $ig.setShowFilename(true); +// } else { +// $ig.setShowFilename(false); +// } +// if (isset($params['caption'])) { +// // NOTE: We aren't passing a frame here or below. Frame info +// // is currently opaque to Parsoid, which acts on OT_PREPROCESS. +// // See T107332#4030581 +// $caption = $this.recursiveTagParse($params['caption']); +// $ig.setCaptionHtml($caption); +// } +// if (isset($params['perrow'])) { +// $ig.setPerRow($params['perrow']); +// } +// if (isset($params['widths'])) { +// $ig.setWidths($params['widths']); +// } +// if (isset($params['heights'])) { +// $ig.setHeights($params['heights']); +// } +// $ig.setAdditionalOptions($params); // -// $ig->setContextTitle(this.mTitle); -// $ig->setShowBytes(false); -// $ig->setShowFilename(false); -// $ig->setParser($this); -// $ig->setHideBadImages(); -// $ig->setAttributes(Sanitizer::validateTagAttributes($params, 'table')); +// // Avoid PHP 7.1 warning from passing $this by reference +// $parser = $this; +// Hooks::run('BeforeParserrenderImageGallery', [ &$parser, &$ig ]); // -// if (isset($params['showfilename'])) { -// $ig->setShowFilename(true); -// } else { -// $ig->setShowFilename(false); -// } -// if (isset($params['caption'])) { -// $caption = $params['caption']; -// $caption = htmlspecialchars($caption); -// $caption = this.replaceInternalLinks($caption); -// $ig->setCaptionHtml($caption); -// } -// if (isset($params['perrow'])) { -// $ig->setPerRow($params['perrow']); +// $lines = StringUtils::explode("\n", $text); +// foreach ($lines as $line) { +// // match lines like these: +// // Image:someimage.jpg|This is some image +// $matches = []; +// preg_match("/^([^|]+)(\\|(.*))?$/", $line, $matches); +// // Skip empty lines +// if (count($matches) == 0) { +// continue; // } -// if (isset($params['widths'])) { -// $ig->setWidths($params['widths']); +// +// if (strpos($matches[0], '%') !== false) { +// $matches[1] = rawurldecode($matches[1]); // } -// if (isset($params['heights'])) { -// $ig->setHeights($params['heights']); +// title = Title::newFromText($matches[1], NS_FILE); +// if (is_null(title)) { +// // Bogus title. Ignore these so we don't bomb out later. +// continue; // } -// $ig->setAdditionalOptions($params); -// -// Hooks::run('BeforeParserrenderImageGallery', [ &$this, &$ig ]); -// -// $lines = StringUtils::explode("\n", $text); -// foreach ($lines as $line) { -// // match lines like these: -// // Image:someimage.jpg|This is some image -// matches = []; -// preg_match("/^([^|]+)(\\|(.*))?$/", $line, matches); -// // Skip empty lines -// if (count(matches) == 0) { -// continue; -// } -// -// if (strpos(matches[0], '%') !== false) { -// matches[1] = rawurldecode(matches[1]); -// } -// $title = Title::newFromText(matches[1], NS_FILE); -// if (is_null($title)) { -// // Bogus title. Ignore these so we don't bomb out later. -// continue; -// } // -// // We need to get what handler the file uses, to figure out parameters. -// // Note, a hook can overide the file name, and chose an entirely different -// // file (which potentially could be of a different type and have different handler). +// // We need to get what handler the file uses, to figure out parameters. +// // Note, a hook can overide the file name, and chose an entirely different +// // file (which potentially could be of a different type and have different handler). // $options = []; -// $descQuery = false; -// Hooks::run('BeforeParserFetchFileAndTitle', -// [ $this, $title, &$options, &$descQuery ]); -// // Don't register it now, as TraditionalImageGallery does that later. -// $file = this.fetchFileNoRegister($title, $options); -// $handler = $file ? $file->getHandler() : false; -// -// $paramMap = [ -// 'img_alt' => 'gallery-@gplx.Internal protected-alt', -// 'img_link' => 'gallery-@gplx.Internal protected-link', -// ]; -// if ($handler) { -// $paramMap = $paramMap + $handler->getParamMap(); -// // We don't want people to specify per-image widths. -// // Additionally the width parameter would need special casing anyhow. -// unset($paramMap['img_width']); -// } -// -// mwArray = new MagicWordArray(array_keys($paramMap)); -// -// $label = ''; -// $alt = ''; -// $link = ''; -// $handlerOptions = []; -// if (isset(matches[3])) { -// // look for an |alt= definition while trying not to break existing -// // captions with multiple pipes (|) in it, until a more sensible grammar -// // is defined for images in galleries -// -// // FIXME: Doing recursiveTagParse at this stage, and the trim before -// // splitting on '|' is a bit odd, and different from makeImage. -// matches[3] = this.recursiveTagParse(trim(matches[3])); -// // Protect LanguageConverter markup -// $parameterMatches = StringUtils::delimiterExplode( -// '-{', '}-', '|', matches[3], true /* nested */ -// ); +// $descQuery = false; +// Hooks::run('BeforeParserFetchFileAndTitle', +// [ $this, title, &$options, &$descQuery ]); +// // Don't register it now, as TraditionalImageGallery does that later. +// $file = $this.fetchFileNoRegister(title, $options); +// $handler = $file ? $file.getHandler() : false; +// +// $paramMap = [ +// 'img_alt' => 'gallery-internal-alt', +// 'img_link' => 'gallery-internal-link', +// ]; +// if ($handler) { +// $paramMap += $handler.getParamMap(); +// // We don't want people to specify per-image widths. +// // Additionally the width parameter would need special casing anyhow. +// unset($paramMap['img_width']); +// } +// +// $mwArray = $this.magicWordFactory.newArray(array_keys($paramMap)); +// +// $label = ''; +// $alt = ''; +// $link = ''; +// $handlerOptions = []; +// if (isset($matches[3])) { +// // look for an |alt= definition while trying not to break existing +// // captions with multiple pipes (|) in it, until a more sensible grammar +// // is defined for images in galleries +// +// // FIXME: Doing recursiveTagParse at this stage, and the trim before +// // splitting on '|' is a bit odd, and different from makeImage. +// $matches[3] = $this.recursiveTagParse(trim($matches[3])); +// // Protect LanguageConverter markup +// $parameterMatches = StringUtils::delimiterExplode( +// '-{', '}-', '|', $matches[3], true /* nested */ +// ); // -// foreach ($parameterMatches as $parameterMatch) { -// list(magicName, match) = mwArray->matchVariableStartToEnd($parameterMatch); -// if (magicName) { -// $paramName = $paramMap[magicName]; +// foreach ($parameterMatches as $parameterMatch) { +// list($magicName, $match) = $mwArray.matchVariableStartToEnd($parameterMatch); +// if ($magicName) { +// $paramName = $paramMap[$magicName]; // -// switch ($paramName) { -// case 'gallery-@gplx.Internal protected-alt': -// $alt = this.stripAltText(match, false); +// switch ($paramName) { +// case 'gallery-internal-alt': +// $alt = $this.stripAltText($match, false); // break; -// case 'gallery-@gplx.Internal protected-link': -// $linkValue = strip_tags(this.replaceLinkHoldersText(match)); -// $chars = XomwParser.EXT_LINK_URL_CLASS; -// $addr = XomwParser.EXT_LINK_ADDR; -// $prots = this.mUrlProtocols; -// // check to see if link matches an absolute url, if not then it must be a wiki link. +// case 'gallery-internal-link': +// $linkValue = $this.stripAltText($match, false); // if (preg_match('/^-{R|(.*)}-$/', $linkValue)) { // // Result of LanguageConverter::markNoConversion // // invoked on an external link. // $linkValue = substr($linkValue, 4, -2); // } -// if (preg_match("/^($prots)$addr$chars*$/u", $linkValue)) { -// $link = $linkValue; -// } else { -// $localLinkTitle = Title::newFromText($linkValue); -// if ($localLinkTitle !== null) { -// $link = $localLinkTitle->getLinkURL(); -// } -// } -// break; +// list($type, $target) = $this.parseLinkParameter($linkValue); +// if ($type === 'link-url') { +// $link = $target; +// $this.mOutput.addExternalLink($target); +// } elseif ($type === 'link-title') { +// $link = $target.getLinkURL(); +// $this.mOutput.addLink($target); +// } +// break; // default: // // Must be a handler specific parameter. -// if ($handler->validateParam($paramName, match)) { -// $handlerOptions[$paramName] = match; +// if ($handler.validateParam($paramName, $match)) { +// $handlerOptions[$paramName] = $match; // } else { // // Guess not, consider it as caption. // wfDebug("$parameterMatch failed parameter validation\n"); -// $label = '|' . $parameterMatch; +// $label = $parameterMatch; // } -// } -// -// } else { -// // Last pipe wins. -// $label = '|' . $parameterMatch; // } +// +// } else { +// // Last pipe wins. +// $label = $parameterMatch; // } -// // Remove the pipe. -// $label = substr($label, 1); // } -// -// $ig->add($title, $label, $alt, $link, $handlerOptions); // } -// $html = $ig->toHTML(); -// Hooks::run('AfterParserFetchFileAndTitle', [ $this, $ig, &$html ]); -// return $html; -// } // -// /** -// * @param MediaHandler $handler -// * @return array -// */ -// public function getImageParams($handler) { -// if ($handler) { -// $handlerClass = get_class($handler); -// } else { -// $handlerClass = ''; -// } -// if (!isset(this.mImageParams[$handlerClass])) { -// // Initialise static lists -// static $internalParamNames = [ -// 'horizAlign' => [ 'left', 'right', 'center', 'none' ], -// 'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', -// 'bottom', 'text-bottom' ], -// 'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless', -// 'upright', 'border', 'link', 'alt', 'class' ], -// ]; -// static $internalParamMap; -// if (!$internalParamMap) { -// $internalParamMap = []; -// foreach ($internalParamNames as $type => $names) { -// foreach ($names as $name) { -// magicName = str_replace('-', '_', "img_$name"); -// $internalParamMap[magicName] = [ $type, $name ]; -// } +// $ig.add(title, $label, $alt, $link, $handlerOptions); +// } +// $html = $ig.toHTML(); +// Hooks::run('AfterParserFetchFileAndTitle', [ $this, $ig, &$html ]); +// return $html; +// } +// +// /** +// * @param MediaHandler $handler +// * @return array +// */ +// public function getImageParams($handler) { +// if ($handler) { +// $handlerClass = get_class($handler); +// } else { +// $handlerClass = ''; +// } +// if (!isset($this.mImageParams[$handlerClass])) { +// // Initialise static lists +// static $internalParamNames = [ +// 'horizAlign' => [ 'left', 'right', 'center', 'none' ], +// 'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', +// 'bottom', 'text-bottom' ], +// 'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless', +// 'upright', 'border', 'link', 'alt', 'class' ], +// ]; +// static $internalParamMap; +// if (!$internalParamMap) { +// $internalParamMap = []; +// foreach ($internalParamNames as $type => $names) { +// foreach ($names as $name) { +// // For grep: img_left, img_right, img_center, img_none, +// // img_baseline, img_sub, img_super, img_top, img_text_top, img_middle, +// // img_bottom, img_text_bottom, +// // img_thumbnail, img_manualthumb, img_framed, img_frameless, img_upright, +// // img_border, img_link, img_alt, img_class +// $magicName = str_replace('-', '_', "img_$name"); +// $internalParamMap[$magicName] = [ $type, $name ]; // } // } +// } // -// // Add handler params +// // Add handler params // $paramMap = $internalParamMap; -// if ($handler) { -// $handlerParamMap = $handler->getParamMap(); -// foreach ($handlerParamMap as magic => $paramName) { -// $paramMap[magic] = [ 'handler', $paramName ]; -// } +// if ($handler) { +// $handlerParamMap = $handler.getParamMap(); +// foreach ($handlerParamMap as $magic => $paramName) { +// $paramMap[$magic] = [ 'handler', $paramName ]; // } -// this.mImageParams[$handlerClass] = $paramMap; -// this.mImageParamsMagicArray[$handlerClass] = new MagicWordArray(array_keys($paramMap)); // } -// return [ this.mImageParams[$handlerClass], this.mImageParamsMagicArray[$handlerClass] ]; +// $this.mImageParams[$handlerClass] = $paramMap; +// $this.mImageParamsMagicArray[$handlerClass] = +// $this.magicWordFactory.newArray(array_keys($paramMap)); // } -// -// /** -// * Parse image options text and use it to make an image -// * -// * @param Title $title -// * @param String $options -// * @param LinkHolderArray|boolean $holders -// * @return String HTML -// */ -// public function makeImage($title, $options, $holders = false) { -// // Check if the options text is of the form "options|alt text" -// // Options are: -// // * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang -// // * left no resizing, just left align. label is used for alt= only -// // * right same, but right aligned -// // * none same, but not aligned -// // * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox -// // * center center the image -// // * frame Keep original image size, no magnify-button. -// // * framed Same as "frame" -// // * frameless like 'thumb' but without a frame. Keeps user preferences for width -// // * upright reduce width for upright images, rounded to full __0 px -// // * border draw a 1px border around the image -// // * alt Text for HTML alt attribute (defaults to empty) -// // * class Set a class for img node -// // * link Set the target of the image link. Can be external, interwiki, or local -// // vertical-align values (no % or length right now): -// // * baseline -// // * sub -// // * super -// // * top -// // * text-top -// // * middle -// // * bottom -// // * text-bottom -// -// // Protect LanguageConverter markup when splitting into parts +// return [ $this.mImageParams[$handlerClass], $this.mImageParamsMagicArray[$handlerClass] ]; +// } +// +// /** +// * Parse image options text and use it to make an image +// * +// * @param Title title +// * @param string $options +// * @param LinkHolderArray|bool $holders +// * @return string HTML +// */ +// public function makeImage(title, $options, $holders = false) { +// // Check if the options text is of the form "options|alt text" +// // Options are: +// // * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang +// // * left no resizing, just left align. label is used for alt= only +// // * right same, but right aligned +// // * none same, but not aligned +// // * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox +// // * center center the image +// // * frame Keep original image size, no magnify-button. +// // * framed Same as "frame" +// // * frameless like 'thumb' but without a frame. Keeps user preferences for width +// // * upright reduce width for upright images, rounded to full __0 px +// // * border draw a 1px border around the image +// // * alt Text for HTML alt attribute (defaults to empty) +// // * class Set a class for img node +// // * link Set the target of the image link. Can be external, interwiki, or local +// // vertical-align values (no % or length right now): +// // * baseline +// // * sub +// // * super +// // * top +// // * text-top +// // * middle +// // * bottom +// // * text-bottom +// +// // Protect LanguageConverter markup when splitting into parts // $parts = StringUtils::delimiterExplode( -// '-{', '}-', '|', $options, true /* allow nesting */ -// ); +// '-{', '}-', '|', $options, true /* allow nesting */ +// ); // -// // Give extensions a chance to select the file revision for us -// $options = []; -// $descQuery = false; -// Hooks::run('BeforeParserFetchFileAndTitle', -// [ $this, $title, &$options, &$descQuery ]); -// // Fetch and register the file (file title may be different via hooks) -// list($file, $title) = this.fetchFileAndTitle($title, $options); +// // Give extensions a chance to select the file revision for us +// $options = []; +// $descQuery = false; +// Hooks::run('BeforeParserFetchFileAndTitle', +// [ $this, title, &$options, &$descQuery ]); +// // Fetch and register the file (file title may be different via hooks) +// list($file, title) = $this.fetchFileAndTitle(title, $options); // -// // Get parameter map -// $handler = $file ? $file->getHandler() : false; +// // Get parameter map +// $handler = $file ? $file.getHandler() : false; // -// list($paramMap, mwArray) = this.getImageParams($handler); +// list($paramMap, $mwArray) = $this.getImageParams($handler); // -// if (!$file) { -// this.addTrackingCategory('broken-file-category'); -// } +// if (!$file) { +// $this.addTrackingCategory('broken-file-category'); +// } // -// // Process the input parameters -// $caption = ''; -// $params = [ 'frame' => [], 'handler' => [], -// 'horizAlign' => [], 'vertAlign' => [] ]; -// $seenformat = false; -// foreach ($parts as $part) { -// $part = trim($part); -// list(magicName, $value) = mwArray->matchVariableStartToEnd($part); -// $validated = false; -// if (isset($paramMap[magicName])) { -// list($type, $paramName) = $paramMap[magicName]; -// -// // Special case; width and height come in one variable together +// // Process the input parameters +// $caption = ''; +// $params = [ 'frame' => [], 'handler' => [], +// 'horizAlign' => [], 'vertAlign' => [] ]; +// $seenformat = false; +// foreach ($parts as $part) { +// $part = trim($part); +// list($magicName, $value) = $mwArray.matchVariableStartToEnd($part); +// $validated = false; +// if (isset($paramMap[$magicName])) { +// list($type, $paramName) = $paramMap[$magicName]; +// +// // Special case; width and height come in one variable together // if ($type === 'handler' && $paramName === 'width') { -// $parsedWidthParam = this.parseWidthParam($value); +// $parsedWidthParam = self::parseWidthParam($value); // if (isset($parsedWidthParam['width'])) { // $width = $parsedWidthParam['width']; -// if ($handler->validateParam('width', $width)) { +// if ($handler.validateParam('width', $width)) { // $params[$type]['width'] = $width; // $validated = true; // } // } // if (isset($parsedWidthParam['height'])) { // $height = $parsedWidthParam['height']; -// if ($handler->validateParam('height', $height)) { +// if ($handler.validateParam('height', $height)) { // $params[$type]['height'] = $height; // $validated = true; // } // } -// // else no validation -- T15436 +// // else no validation -- T15436 // } else { // if ($type === 'handler') { -// // Validate handler parameter -// $validated = $handler->validateParam($paramName, $value); +// // Validate handler parameter +// $validated = $handler.validateParam($paramName, $value); // } else { -// // Validate @gplx.Internal protected parameters +// // Validate internal parameters // switch ($paramName) { -// case 'manualthumb': -// case 'alt': -// case 'class': +// case 'manualthumb': +// case 'alt': +// case 'class': // // @todo FIXME: Possibly check validity here for // // manualthumb? downstream behavior seems odd with // // missing manual thumbs. -// $validated = true; -// $value = this.stripAltText($value, $holders); -// break; -// case 'link': -// $chars = XomwParser.EXT_LINK_URL_CLASS; -// $addr = XomwParser.EXT_LINK_ADDR; -// $prots = this.mUrlProtocols; -// if ($value === '') { -// $paramName = 'no-link'; -// $value = true; // $validated = true; -// } elseif (preg_match("/^((?i)$prots)/", $value)) { -// if (preg_match("/^((?i)$prots)$addr$chars*$/u", $value, m)) { -// $paramName = 'link-url'; -// this.mOutput->addExternalLink($value); -// if (this.mOptions->getExternalLinkTarget()) { -// $params[$type]['link-target'] = this.mOptions->getExternalLinkTarget(); -// } -// $validated = true; -// } -// } else { -// $linkTitle = Title::newFromText($value); -// if ($linkTitle) { -// $paramName = 'link-title'; -// $value = $linkTitle; -// this.mOutput->addLink($linkTitle); +// $value = $this.stripAltText($value, $holders); +// break; +// case 'link': +// list($paramName, $value) = +// $this.parseLinkParameter( +// $this.stripAltText($value, $holders) +// ); +// if ($paramName) { // $validated = true; +// if ($paramName === 'no-link') { +// $value = true; +// } +// if (($paramName === 'link-url') && $this.mOptions.getExternalLinkTarget()) { +// $params[$type]['link-target'] = $this.mOptions.getExternalLinkTarget(); +// } // } -// } -// break; -// case 'frameless': -// case 'framed': -// case 'thumbnail': -// // use first appearing option, discard others. -// $validated = !$seenformat; -// $seenformat = true; -// break; -// default: +// break; +// case 'frameless': +// case 'framed': +// case 'thumbnail': +// // use first appearing option, discard others. +// $validated = !$seenformat; +// $seenformat = true; +// break; +// default: // // Most other things appear to be empty or numeric... -// $validated = ($value === false || is_numeric(trim($value))); +// $validated = ($value === false || is_numeric(trim($value))); // } // } // @@ -4618,79 +6235,123 @@ Tfds.Write(nowiki, isHTML, forceRawInterwiki, isChildObj, isLocalObj, titleText, // $params[$type][$paramName] = $value; // } // } -// } -// if (!$validated) { -// $caption = $part; -// } // } -// -// // Process alignment parameters -// if ($params['horizAlign']) { -// $params['frame']['align'] = key($params['horizAlign']); -// } -// if ($params['vertAlign']) { -// $params['frame']['valign'] = key($params['vertAlign']); +// if (!$validated) { +// $caption = $part; // } +// } +// +// // Process alignment parameters +// if ($params['horizAlign']) { +// $params['frame']['align'] = key($params['horizAlign']); +// } +// if ($params['vertAlign']) { +// $params['frame']['valign'] = key($params['vertAlign']); +// } // -// $params['frame']['caption'] = $caption; +// $params['frame']['caption'] = $caption; // -// // Will the image be presented in a frame, with the caption below? +// // Will the image be presented in a frame, with the caption below? // $imageIsFramed = isset($params['frame']['frame']) // || isset($params['frame']['framed']) // || isset($params['frame']['thumbnail']) // || isset($params['frame']['manualthumb']); // -// // In the old days, [[Image:Foo|text...]] would set alt text. Later it -// // came to also set the caption, ordinary text after the image -- which -// // makes no sense, because that just repeats the text multiple times in -// // screen readers. It *also* came to set the title attribute. -// // Now that we have an alt attribute, we should not set the alt text to -// // equal the caption: that's worse than useless, it just repeats the -// // text. This is the framed/thumbnail case. If there's no caption, we -// // use the unnamed parameter for alt text as well, just for the time be- -// // ing, if the unnamed param is set and the alt param is not. -// // For the future, we need to figure out if we want to tweak this more, -// // e.g., introducing a title= parameter for the title; ignoring the un- -// // named parameter entirely for images without a caption; adding an ex- -// // plicit caption= parameter and preserving the old magic unnamed para- -// // meter for BC; ... -// if ($imageIsFramed) { // Framed image -// if ($caption === '' && !isset($params['frame']['alt'])) { -// // No caption or alt text, add the filename as the alt text so -// // that screen readers at least get some description of the image -// $params['frame']['alt'] = $title->getText(); -// } -// // Do not set $params['frame']['title'] because tooltips don't make sense -// // for framed images -// } else { // Inline image -// if (!isset($params['frame']['alt'])) { -// // No alt text, use the "caption" for the alt text -// if ($caption !== '') { -// $params['frame']['alt'] = this.stripAltText($caption, $holders); -// } else { -// // No caption, fall back to using the filename for the -// // alt text -// $params['frame']['alt'] = $title->getText(); -// } +// // In the old days, [[Image:Foo|text...]] would set alt text. Later it +// // came to also set the caption, ordinary text after the image -- which +// // makes no sense, because that just repeats the text multiple times in +// // screen readers. It *also* came to set the title attribute. +// // Now that we have an alt attribute, we should not set the alt text to +// // equal the caption: that's worse than useless, it just repeats the +// // text. This is the framed/thumbnail case. If there's no caption, we +// // use the unnamed parameter for alt text as well, just for the time be- +// // ing, if the unnamed param is set and the alt param is not. +// // For the future, we need to figure out if we want to tweak this more, +// // e.g., introducing a title= parameter for the title; ignoring the un- +// // named parameter entirely for images without a caption; adding an ex- +// // plicit caption= parameter and preserving the old magic unnamed para- +// // meter for BC; ... +// if ($imageIsFramed) { # Framed image +// if ($caption === '' && !isset($params['frame']['alt'])) { +// // No caption or alt text, add the filename as the alt text so +// // that screen readers at least get some description of the image +// $params['frame']['alt'] = title.getText(); +// } +// // Do not set $params['frame']['title'] because tooltips don't make sense +// // for framed images +// } else { # Inline image +// if (!isset($params['frame']['alt'])) { +// // No alt text, use the "caption" for the alt text +// if ($caption !== '') { +// $params['frame']['alt'] = $this.stripAltText($caption, $holders); +// } else { +// // No caption, fall back to using the filename for the +// // alt text +// $params['frame']['alt'] = title.getText(); // } -// // Use the "caption" for the tooltip text -// $params['frame']['title'] = this.stripAltText($caption, $holders); // } +// // Use the "caption" for the tooltip text +// $params['frame']['title'] = $this.stripAltText($caption, $holders); +// } +// $params['handler']['targetlang'] = $this.getTargetLanguage().getCode(); // -// Hooks::run('ParserMakeImageParams', [ $title, $file, &$params, $this ]); +// Hooks::run('ParserMakeImageParams', [ title, $file, &$params, $this ]); // -// // Linker does the rest -// $time = isset($options['time']) ? $options['time'] : false; -// $ret = Linker::makeImageLink($this, $title, $file, $params['frame'], $params['handler'], -// $time, $descQuery, this.mOptions->getThumbSize()); +// // Linker does the rest +// $time = $options['time'] ?? false; +// $ret = Linker::makeImageLink($this, title, $file, $params['frame'], $params['handler'], +// $time, $descQuery, $this.mOptions.getThumbSize()); // -// // Give the handler a chance to modify the parser Object -// if ($handler) { -// $handler->parserTransformHook($this, $file); -// } +// // Give the handler a chance to modify the parser object +// if ($handler) { +// $handler.parserTransformHook($this, $file); +// } // -// return $ret; +// return $ret; +// } +// +// /** +// * Parse the value of 'link' parameter in image syntax (`[[File:Foo.jpg|link=<value>]]`). +// * +// * Adds an entry to appropriate link tables. +// * +// * @since 1.32 +// * @param string $value +// * @return array of `[ type, target ]`, where: +// * - `type` is one of: +// * - `null`: Given value is not a valid link target, use default +// * - `'no-link'`: Given value is empty, do not generate a link +// * - `'link-url'`: Given value is a valid external link +// * - `'link-title'`: Given value is a valid internal link +// * - `target` is: +// * - When `type` is `null` or `'no-link'`: `false` +// * - When `type` is `'link-url'`: URL string corresponding to given value +// * - When `type` is `'link-title'`: Title object corresponding to given value +// */ +// public function parseLinkParameter($value) { +// $chars = self::EXT_LINK_URL_CLASS; +// $addr = self::EXT_LINK_ADDR; +// $prots = $this.mUrlProtocols; +// $type = null; +// $target = false; +// if ($value === '') { +// $type = 'no-link'; +// } elseif (preg_match("/^((?i)$prots)/", $value)) { +// if (preg_match("/^((?i)$prots)$addr$chars*$/u", $value, $m)) { +// $this.mOutput.addExternalLink($value); +// $type = 'link-url'; +// $target = $value; +// } +// } else { +// $linkTitle = Title::newFromText($value); +// if ($linkTitle) { +// $this.mOutput.addLink($linkTitle); +// $type = 'link-title'; +// $target = $linkTitle; +// } // } +// return [ $type, $target ]; +// } /** * @param String $caption @@ -4717,721 +6378,865 @@ Tfds.Write(nowiki, isHTML, forceRawInterwiki, isChildObj, isLocalObj, titleText, return tooltip; } -// /** -// * Set a flag in the output Object indicating that the content is dynamic and -// * shouldn't be cached. -// * @deprecated since 1.28; use getOutput()->updateCacheExpiry() -// */ -// public function disableCache() { -// wfDebug("Parser output marked as uncacheable.\n"); -// if (!this.mOutput) { -// throw new MWException(__METHOD__ . -// " can only be called when actually parsing something"); -// } -// this.mOutput->updateCacheExpiry(0); // new style, for consistency +// /** +// * @param string $caption +// * @param LinkHolderArray|bool $holders +// * @return mixed|string +// */ +// protected function stripAltText($caption, $holders) { +// // Strip bad stuff out of the title (tooltip). We can't just use +// // replaceLinkHoldersText() here, because if this function is called +// // from replaceInternalLinks2(), mLinkHolders won't be up-to-date. +// if ($holders) { +// $tooltip = $holders.replaceText($caption); +// } else { +// $tooltip = $this.replaceLinkHoldersText($caption); // } // -// /** -// * Callback from the Sanitizer for expanding items found in HTML attribute -// * values, so they can be safely tested and escaped. -// * -// * @param String $text -// * @param boolean|PPFrame $frame -// * @return String -// */ -// public function attributeStripCallback(&$text, $frame = false) { -// $text = this.replaceVariables($text, $frame); -// $text = this.mStripState->unstripBoth($text); -// return $text; +// // make sure there are no placeholders in thumbnail attributes +// // that are later expanded to html- so expand them now and +// // remove the tags +// $tooltip = $this.mStripState.unstripBoth($tooltip); +// // Compatibility hack! In HTML certain entity references not terminated +// // by a semicolon are decoded (but not if we're in an attribute; that's +// // how link URLs get away without properly escaping & in queries). +// // But wikitext has always required semicolon-termination of entities, +// // so encode & where needed to avoid decode of semicolon-less entities. +// // See T209236 and +// // https://www.w3.org/TR/html5/syntax.html#named-character-references +// // T210437 discusses moving this workaround to Sanitizer::stripAllTags. +// $tooltip = preg_replace("/ +// & // 1. entity prefix +// (?= // 2. followed by: +// (?: // a. one of the legacy semicolon-less named entities +// A(?:Elig|MP|acute|circ|grave|ring|tilde|uml)| +// C(?:OPY|cedil)|E(?:TH|acute|circ|grave|uml)| +// GT|I(?:acute|circ|grave|uml)|LT|Ntilde| +// O(?:acute|circ|grave|slash|tilde|uml)|QUOT|REG|THORN| +// U(?:acute|circ|grave|uml)|Yacute| +// a(?:acute|c(?:irc|ute)|elig|grave|mp|ring|tilde|uml)|brvbar| +// c(?:cedil|edil|urren)|cent(?!erdot;)|copy(?!sr;)|deg| +// divide(?!ontimes;)|e(?:acute|circ|grave|th|uml)| +// frac(?:1(?:2|4)|34)| +// gt(?!c(?:c|ir)|dot|lPar|quest|r(?:a(?:pprox|rr)|dot|eq(?:less|qless)|less|sim);)| +// i(?:acute|circ|excl|grave|quest|uml)|laquo| +// lt(?!c(?:c|ir)|dot|hree|imes|larr|quest|r(?:Par|i(?:e|f|));)| +// m(?:acr|i(?:cro|ddot))|n(?:bsp|tilde)| +// not(?!in(?:E|dot|v(?:a|b|c)|)|ni(?:v(?:a|b|c)|);)| +// o(?:acute|circ|grave|rd(?:f|m)|slash|tilde|uml)| +// p(?:lusmn|ound)|para(?!llel;)|quot|r(?:aquo|eg)| +// s(?:ect|hy|up(?:1|2|3)|zlig)|thorn|times(?!b(?:ar|)|d;)| +// u(?:acute|circ|grave|ml|uml)|y(?:acute|en|uml) +// ) +// (?:[^;]|$)) // b. and not followed by a semicolon +// // S = study, for efficiency +// /Sx", '&', $tooltip); +// $tooltip = Sanitizer::stripAllTags($tooltip); +// +// return $tooltip; +// } +// /** +// * Set a flag in the output object indicating that the content is dynamic and +// * shouldn't be cached. +// * @deprecated since 1.28; use getOutput().updateCacheExpiry() +// */ +// public function disableCache() { +// wfDebug("Parser output marked as uncacheable.\n"); +// if (!$this.mOutput) { +// throw new MWException(__METHOD__ . +// " can only be called when actually parsing something"); // } -// -// /** -// * Accessor -// * -// * @return array -// */ -// public function getTags() { -// return array_merge( -// array_keys(this.mTransparentTagHooks), -// array_keys(this.mTagHooks), -// array_keys(this.mFunctionTagHooks) -// ); -// } -// -// /** -// * Replace transparent tags in $text with the values given by the callbacks. -// * -// * Transparent tag hooks are like regular XML-style tag hooks, except they -// * operate late in the transformation sequence, on HTML instead of wikitext. -// * -// * @param String $text -// * -// * @return String -// */ -// public function replaceTransparentTags($text) { -// matches = []; -// $elements = array_keys(this.mTransparentTagHooks); -// $text = XomwParser.extractTagsAndParams($elements, $text, matches); -// $replacements = []; -// -// foreach (matches as marker => $data) { -// list($element, $content, $params, $tag) = $data; -// $tagName = strtolower($element); -// if (isset(this.mTransparentTagHooks[$tagName])) { -// $output = call_user_func_array( -// this.mTransparentTagHooks[$tagName], -// [ $content, $params, $this ] -// ); -// } else { -// $output = $tag; -// } -// $replacements[marker] = $output; +// $this.mOutput.updateCacheExpiry(0); // new style, for consistency +// } +// +// /** +// * Callback from the Sanitizer for expanding items found in HTML attribute +// * values, so they can be safely tested and escaped. +// * +// * @param string &$text +// * @param bool|PPFrame $frame +// * @return string +// */ +// public function attributeStripCallback(&$text, $frame = false) { +// $text = $this.replaceVariables($text, $frame); +// $text = $this.mStripState.unstripBoth($text); +// return $text; +// } +// +// /** +// * Accessor +// * +// * @return array +// */ +// public function getTags() { +// $this.firstCallInit(); +// return array_merge( +// array_keys($this.mTransparentTagHooks), +// array_keys($this.mTagHooks), +// array_keys($this.mFunctionTagHooks) +// ); +// } +// +// /** +// * @since 1.32 +// * @return array +// */ +// public function getFunctionSynonyms() { +// $this.firstCallInit(); +// return $this.mFunctionSynonyms; +// } +// +// /** +// * @since 1.32 +// * @return string +// */ +// public function getUrlProtocols() { +// return $this.mUrlProtocols; +// } +// +// /** +// * Replace transparent tags in $text with the values given by the callbacks. +// * +// * Transparent tag hooks are like regular XML-style tag hooks, except they +// * operate late in the transformation sequence, on HTML instead of wikitext. +// * +// * @param string $text +// * +// * @return string +// */ +// public function replaceTransparentTags($text) { +// $matches = []; +// $elements = array_keys($this.mTransparentTagHooks); +// $text = self::extractTagsAndParams($elements, $text, $matches); +// $replacements = []; +// +// foreach ($matches as $marker => $data) { +// list($element, $content, $params, $tag) = $data; +// $tagName = strtolower($element); +// if (isset($this.mTransparentTagHooks[$tagName])) { +// $output = call_user_func_array( +// $this.mTransparentTagHooks[$tagName], +// [ $content, $params, $this ] +// ); +// } else { +// $output = $tag; // } -// return strtr($text, $replacements); +// $replacements[$marker] = $output; // } -// -// /** -// * Break wikitext input into sections, and either pull or replace -// * some particular section's text. -// * -// * External callers should use the getSection and replaceSection methods. -// * -// * @param String $text Page wikitext -// * @param String|int $sectionId A section identifier String of the form: -// * "<flag1> - <flag2> - ... - <section number>" -// * -// * Currently the only recognised flag is "T", which means the target section number -// * was derived during a template inclusion parse, in other words this is a template -// * section edit link. If no flags are given, it was an ordinary section edit link. -// * This flag is required to avoid a section numbering mismatch when a section is -// * enclosed by "<includeonly>" (T8563). -// * -// * The section number 0 pulls the text before the first heading; other numbers will -// * pull the given section along with its lower-level subsections. If the section is -// * not found, mode=get will return $newtext, and mode=replace will return $text. -// * -// * Section 0 is always considered to exist, even if it only contains the empty -// * String. If $text is the empty String and section 0 is replaced, $newText is -// * returned. -// * -// * @param String mode One of "get" or "replace" -// * @param String $newText Replacement text for section data. -// * @return String For "get", the extracted section text. -// * for "replace", the whole page with the section replaced. -// */ -// private function extractSections($text, $sectionId, mode, $newText = '') { -// global $wgTitle; // not generally used but removes an ugly failure mode -// -// magicScopeVariable = this.synchronized(); -// this.startParse($wgTitle, new ParserOptions, XomwParser.OT_PLAIN, true); -// $outText = ''; -// $frame = this.getPreprocessor()->newFrame(); -// -// // Process section extraction flags -// $flags = 0; -// $sectionParts = explode('-', $sectionId); -// $sectionIndex = array_pop($sectionParts); -// foreach ($sectionParts as $part) { -// if ($part === 'T') { -// $flags |= XomwParser.PTD_FOR_INCLUSION; -// } -// } -// -// // Check for empty input -// if (strval($text) === '') { -// // Only sections 0 and T-0 exist in an empty document -// if ($sectionIndex == 0) { -// if (mode === 'get') { -// return ''; -// } else { -// return $newText; -// } -// } else { -// if (mode === 'get') { -// return $newText; -// } else { -// return $text; -// } -// } +// return strtr($text, $replacements); +// } +// +// /** +// * Break wikitext input into sections, and either pull or replace +// * some particular section's text. +// * +// * External callers should use the getSection and replaceSection methods. +// * +// * @param string $text Page wikitext +// * @param string|int $sectionId A section identifier string of the form: +// * "<flag1> - <flag2> - ... - <section number>" +// * +// * Currently the only recognised flag is "T", which means the target section number +// * was derived during a template inclusion parse, in other words this is a template +// * section edit link. If no flags are given, it was an ordinary section edit link. +// * This flag is required to avoid a section numbering mismatch when a section is +// * enclosed by "<includeonly>" (T8563). +// * +// * The section number 0 pulls the text before the first heading; other numbers will +// * pull the given section along with its lower-level subsections. If the section is +// * not found, $mode=get will return $newtext, and $mode=replace will return $text. +// * +// * Section 0 is always considered to exist, even if it only contains the empty +// * string. If $text is the empty string and section 0 is replaced, $newText is +// * returned. +// * +// * @param string $mode One of "get" or "replace" +// * @param string $newText Replacement text for section data. +// * @return string For "get", the extracted section text. +// * for "replace", the whole page with the section replaced. +// */ +// private function extractSections($text, $sectionId, $mode, $newText = '') { +// global $wgTitle; # not generally used but removes an ugly failure mode +// +// $magicScopeVariable = $this.lock(); +// $this.startParse($wgTitle, new ParserOptions, self::OT_PLAIN, true); +// $outText = ''; +// $frame = $this.getPreprocessor().newFrame(); +// +// // Process section extraction flags +// $flags = 0; +// $sectionParts = explode('-', $sectionId); +// $sectionIndex = array_pop($sectionParts); +// foreach ($sectionParts as $part) { +// if ($part === 'T') { +// $flags |= self::PTD_FOR_INCLUSION; // } +// } // -// // Preprocess the text -// $root = this.preprocessToDom($text, $flags); -// -// // <h> nodes indicate section breaks -// // They can only occur at the top level, so we can find them by iterating the root's children -// $node = $root->getFirstChild(); -// -// // Find the target section +// // Check for empty input +// if (strval($text) === '') { +// // Only sections 0 and T-0 exist in an empty document // if ($sectionIndex == 0) { -// // Section zero doesn't nest, level=big -// $targetLevel = 1000; -// } else { -// while ($node) { -// if ($node->getName() === 'h') { -// $bits = $node->splitHeading(); -// if ($bits['i'] == $sectionIndex) { -// $targetLevel = $bits['level']; -// break; -// } -// } -// if (mode === 'replace') { -// $outText .= $frame->expand($node, PPFrame::RECOVER_ORIG); -// } -// $node = $node->getNextSibling(); +// if ($mode === 'get') { +// return ''; // } -// } // -// if (!$node) { -// // Not found -// if (mode === 'get') { +// return $newText; +// } else { +// if ($mode === 'get') { // return $newText; -// } else { -// return $text; // } +// +// return $text; // } +// } // -// // Find the end of the section, including nested sections -// do { -// if ($node->getName() === 'h') { -// $bits = $node->splitHeading(); -// $curLevel = $bits['level']; -// if ($bits['i'] != $sectionIndex && $curLevel <= $targetLevel) { +// // Preprocess the text +// $root = $this.preprocessToDom($text, $flags); +// +// // <h> nodes indicate section breaks +// // They can only occur at the top level, so we can find them by iterating the root's children +// $node = $root.getFirstChild(); +// +// // Find the target section +// if ($sectionIndex == 0) { +// // Section zero doesn't nest, level=big +// $targetLevel = 1000; +// } else { +// while ($node) { +// if ($node.getName() === 'h') { +// $bits = $node.splitHeading(); +// if ($bits['i'] == $sectionIndex) { +// $targetLevel = $bits['level']; // break; // } // } -// if (mode === 'get') { -// $outText .= $frame->expand($node, PPFrame::RECOVER_ORIG); -// } -// $node = $node->getNextSibling(); -// } while ($node); -// -// // Write out the remainder (in replace mode only) -// if (mode === 'replace') { -// // Output the replacement text -// // Add two newlines on -- trailing whitespace in $newText is conventionally -// // stripped by the editor, so we need both newlines to restore the paragraph gap -// // Only add trailing whitespace if there is newText -// if ($newText != "") { -// $outText .= $newText . "\n\n"; +// if ($mode === 'replace') { +// $outText .= $frame.expand($node, PPFrame::RECOVER_ORIG); // } -// -// while ($node) { -// $outText .= $frame->expand($node, PPFrame::RECOVER_ORIG); -// $node = $node->getNextSibling(); -// } -// } -// -// if (is_string($outText)) { -// // Re-insert stripped tags -// $outText = rtrim(this.mStripState->unstripBoth($outText)); +// $node = $node.getNextSibling(); // } -// -// return $outText; -// } -// -// /** -// * This function returns the text of a section, specified by a number ($section). -// * A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or -// * the first section before any such heading (section 0). -// * -// * If a section contains subsections, these are also returned. -// * -// * @param String $text Text to look in -// * @param String|int $sectionId Section identifier as a number or String -// * (e.g. 0, 1 or 'T-1'). -// * @param String $defaultText Default to return if section is not found -// * -// * @return String Text of the requested section -// */ -// public function getSection($text, $sectionId, $defaultText = '') { -// return this.extractSections($text, $sectionId, 'get', $defaultText); // } // -// /** -// * This function returns $oldtext after the content of the section -// * specified by $section has been replaced with $text. If the target -// * section does not exist, $oldtext is returned unchanged. -// * -// * @param String $oldText Former text of the article -// * @param String|int $sectionId Section identifier as a number or String -// * (e.g. 0, 1 or 'T-1'). -// * @param String $newText Replacing text -// * -// * @return String Modified text -// */ -// public function replaceSection($oldText, $sectionId, $newText) { -// return this.extractSections($oldText, $sectionId, 'replace', $newText); -// } -// -// /** -// * Get the ID of the revision we are parsing -// * -// * @return int|null -// */ -// public function getRevisionId() { -// return this.mRevisionId; +// if (!$node) { +// // Not found +// if ($mode === 'get') { +// return $newText; +// } else { +// return $text; +// } // } // -// /** -// * Get the revision Object for this.mRevisionId -// * -// * @return Revision|null Either a Revision Object or null -// * @since 1.23 (public since 1.23) -// */ -// public function getRevisionObject() { -// if (!is_null(this.mRevisionObject)) { -// return this.mRevisionObject; -// } -// if (is_null(this.mRevisionId)) { -// return null; +// // Find the end of the section, including nested sections +// do { +// if ($node.getName() === 'h') { +// $bits = $node.splitHeading(); +// $curLevel = $bits['level']; +// if ($bits['i'] != $sectionIndex && $curLevel <= $targetLevel) { +// break; +// } // } -// -// $rev = call_user_func( -// this.mOptions->getCurrentRevisionCallback(), this.getTitle(), $this -// ); -// -// // If the parse is for a new revision, then the callback should have -// // already been set to force the Object and should match mRevisionId. -// // If not, try to fetch by mRevisionId for sanity. -// if ($rev && $rev->getId() != this.mRevisionId) { -// $rev = Revision::newFromId(this.mRevisionId); +// if ($mode === 'get') { +// $outText .= $frame.expand($node, PPFrame::RECOVER_ORIG); // } +// $node = $node.getNextSibling(); +// } while ($node); // -// this.mRevisionObject = $rev; -// -// return this.mRevisionObject; -// } -// -// /** -// * Get the timestamp associated with the current revision, adjusted for -// * the default server-local timestamp -// * @return String -// */ -// public function getRevisionTimestamp() { -// if (is_null(this.mRevisionTimestamp)) { -// global $wgContLang; -// -// $revObject = this.getRevisionObject(); -// $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow(); -// -// // The cryptic '' timezone parameter tells to use the site-default -// // timezone offset instead of the user settings. -// // Since this value will be saved into the parser cache, served -// // to other users, and potentially even used inside links and such, -// // it needs to be consistent for all visitors. -// this.mRevisionTimestamp = $wgContLang->userAdjust($timestamp, ''); -// +// // Write out the remainder (in replace mode only) +// if ($mode === 'replace') { +// // Output the replacement text +// // Add two newlines on -- trailing whitespace in $newText is conventionally +// // stripped by the editor, so we need both newlines to restore the paragraph gap +// // Only add trailing whitespace if there is newText +// if ($newText != "") { +// $outText .= $newText . "\n\n"; // } -// return this.mRevisionTimestamp; -// } // -// /** -// * Get the name of the user that edited the last revision -// * -// * @return String User name -// */ -// public function getRevisionUser() { -// if (is_null(this.mRevisionUser)) { -// $revObject = this.getRevisionObject(); -// -// // if this template is subst: the revision id will be blank, -// // so just use the current user's name -// if ($revObject) { -// this.mRevisionUser = $revObject->getUserText(); -// } elseif (this.ot['wiki'] || this.mOptions->getIsPreview()) { -// this.mRevisionUser = this.getUser()->getName(); -// } +// while ($node) { +// $outText .= $frame.expand($node, PPFrame::RECOVER_ORIG); +// $node = $node.getNextSibling(); // } -// return this.mRevisionUser; // } // -// /** -// * Get the size of the revision -// * -// * @return int|null Revision size -// */ -// public function getRevisionSize() { -// if (is_null(this.mRevisionSize)) { -// $revObject = this.getRevisionObject(); -// -// // if this variable is subst: the revision id will be blank, -// // so just use the parser input size, because the own substituation -// // will change the size. -// if ($revObject) { -// this.mRevisionSize = $revObject->getSize(); -// } else { -// this.mRevisionSize = this.mInputSize; -// } -// } -// return this.mRevisionSize; +// if (is_string($outText)) { +// // Re-insert stripped tags +// $outText = rtrim($this.mStripState.unstripBoth($outText)); // } // -// /** -// * Mutator for mDefaultSort -// * -// * @param String $sort New value -// */ -// public function setDefaultSort($sort) { -// this.mDefaultSort = $sort; -// this.mOutput->setProperty('defaultsort', $sort); +// return $outText; +// } +// +// /** +// * This function returns the text of a section, specified by a number ($section). +// * A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or +// * the first section before any such heading (section 0). +// * +// * If a section contains subsections, these are also returned. +// * +// * @param string $text Text to look in +// * @param string|int $sectionId Section identifier as a number or string +// * (e.g. 0, 1 or 'T-1'). +// * @param string $defaultText Default to return if section is not found +// * +// * @return string Text of the requested section +// */ +// public function getSection($text, $sectionId, $defaultText = '') { +// return $this.extractSections($text, $sectionId, 'get', $defaultText); +// } +// +// /** +// * This function returns $oldtext after the content of the section +// * specified by $section has been replaced with $text. If the target +// * section does not exist, $oldtext is returned unchanged. +// * +// * @param string $oldText Former text of the article +// * @param string|int $sectionId Section identifier as a number or string +// * (e.g. 0, 1 or 'T-1'). +// * @param string $newText Replacing text +// * +// * @return string Modified text +// */ +// public function replaceSection($oldText, $sectionId, $newText) { +// return $this.extractSections($oldText, $sectionId, 'replace', $newText); +// } +// +// /** +// * Get the ID of the revision we are parsing +// * +// * @return int|null +// */ +// public function getRevisionId() { +// return $this.mRevisionId; +// } +// +// /** +// * Get the revision object for $this.mRevisionId +// * +// * @return Revision|null Either a Revision object or null +// * @since 1.23 (public since 1.23) +// */ +// public function getRevisionObject() { +// if (!is_null($this.mRevisionObject)) { +// return $this.mRevisionObject; // } // -// /** -// * Accessor for mDefaultSort -// * Will use the empty String if none is set. -// * -// * This value is treated as a prefix, so the -// * empty String is equivalent to sorting by -// * page name. -// * -// * @return String -// */ -// public function getDefaultSort() { -// if (this.mDefaultSort !== false) { -// return this.mDefaultSort; -// } else { -// return ''; -// } +// // NOTE: try to get the RevisionObject even if mRevisionId is null. +// // This is useful when parsing revision that has not yet been saved. +// // However, if we get back a saved revision even though we are in +// // preview mode, we'll have to ignore it, see below. +// // NOTE: This callback may be used to inject an OLD revision that was +// // already loaded, so "current" is a bit of a misnomer. We can't just +// // skip it if mRevisionId is set. +// $rev = call_user_func( +// $this.mOptions.getCurrentRevisionCallback(), $this.getTitle(), $this +// ); +// +// if ($this.mRevisionId === null && $rev && $rev.getId()) { +// // We are in preview mode (mRevisionId is null), and the current revision callback +// // returned an existing revision. Ignore it and return null, it's probably the page's +// // current revision, which is not what we want here. Note that we do want to call the +// // callback to allow the unsaved revision to be injected here, e.g. for +// // self-transclusion previews. +// return null; // } // -// /** -// * Accessor for mDefaultSort -// * Unlike getDefaultSort(), will return false if none is set -// * -// * @return String|boolean -// */ -// public function getCustomDefaultSort() { -// return this.mDefaultSort; +// // If the parse is for a new revision, then the callback should have +// // already been set to force the object and should match mRevisionId. +// // If not, try to fetch by mRevisionId for sanity. +// if ($this.mRevisionId && $rev && $rev.getId() != $this.mRevisionId) { +// $rev = Revision::newFromId($this.mRevisionId); // } // -// /** -// * Try to guess the section anchor name based on a wikitext fragment -// * presumably extracted from a heading, for example "Header" from -// * "== Header ==". -// * -// * @param String $text -// * -// * @return String -// */ -// public function guessSectionNameFromWikiText($text) { -// // Strip out wikitext links(they break the anchor) -// $text = this.stripSectionName($text); -// $text = Sanitizer::normalizeSectionNameWhitespace($text); -// return '#' . Sanitizer::escapeId($text, 'noninitial'); +// $this.mRevisionObject = $rev; +// +// return $this.mRevisionObject; +// } +// +// /** +// * Get the timestamp associated with the current revision, adjusted for +// * the default server-local timestamp +// * @return string +// */ +// public function getRevisionTimestamp() { +// if (is_null($this.mRevisionTimestamp)) { +// $revObject = $this.getRevisionObject(); +// $timestamp = $revObject ? $revObject.getTimestamp() : wfTimestampNow(); +// +// // The cryptic '' timezone parameter tells to use the site-default +// // timezone offset instead of the user settings. +// // Since this value will be saved into the parser cache, served +// // to other users, and potentially even used inside links and such, +// // it needs to be consistent for all visitors. +// $this.mRevisionTimestamp = $this.contLang.userAdjust($timestamp, ''); // } -// -// /** -// * Same as guessSectionNameFromWikiText(), but produces legacy anchors -// * instead. For use in redirects, since IE6 interprets Redirect: headers -// * as something other than UTF-8 (apparently?), resulting in breakage. -// * -// * @param String $text The section name -// * @return String An anchor -// */ -// public function guessLegacySectionNameFromWikiText($text) { -// // Strip out wikitext links(they break the anchor) -// $text = this.stripSectionName($text); -// $text = Sanitizer::normalizeSectionNameWhitespace($text); -// return '#' . Sanitizer::escapeId($text, [ 'noninitial', 'legacy' ]); +// return $this.mRevisionTimestamp; +// } +// +// /** +// * Get the name of the user that edited the last revision +// * +// * @return string User name +// */ +// public function getRevisionUser() { +// if (is_null($this.mRevisionUser)) { +// $revObject = $this.getRevisionObject(); +// +// // if this template is subst: the revision id will be blank, +// // so just use the current user's name +// if ($revObject) { +// $this.mRevisionUser = $revObject.getUserText(); +// } elseif ($this.ot['wiki'] || $this.mOptions.getIsPreview()) { +// $this.mRevisionUser = $this.getUser().getName(); +// } // } -// -// /** -// * Strips a text String of wikitext for use in a section anchor -// * -// * Accepts a text String and then removes all wikitext from the -// * String and leaves only the resultant text (i.e. the result of -// * [[User:WikiSysop|Sysop]] would be "Sysop" and the result of -// * [[User:WikiSysop]] would be "User:WikiSysop") - this is intended -// * to create valid section anchors by mimicing the output of the -// * parser when headings are parsed. -// * -// * @param String $text Text String to be stripped of wikitext -// * for use in a Section anchor -// * @return String Filtered text String -// */ -// public function stripSectionName($text) { -// // Strip @gplx.Internal protected link markup -// $text = preg_replace('/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text); -// $text = preg_replace('/\[\[:?([^[]+)\|?\]\]/', '$1', $text); -// -// // Strip external link markup -// // @todo FIXME: Not tolerant to blank link text -// // I.E. [https://www.mediawiki.org] will render as [1] or something depending -// // on how many empty links there are on the page - need to figure that out. -// $text = preg_replace('/\[(?i:' . this.mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text); -// -// // Parse wikitext quotes (italics & bold) -// $text = this.doQuotes($text); -// -// // Strip HTML tags -// $text = StringUtils::delimiterReplace('<', '>', '', $text); -// return $text; +// return $this.mRevisionUser; +// } +// +// /** +// * Get the size of the revision +// * +// * @return int|null Revision size +// */ +// public function getRevisionSize() { +// if (is_null($this.mRevisionSize)) { +// $revObject = $this.getRevisionObject(); +// +// // if this variable is subst: the revision id will be blank, +// // so just use the parser input size, because the own substituation +// // will change the size. +// if ($revObject) { +// $this.mRevisionSize = $revObject.getSize(); +// } else { +// $this.mRevisionSize = $this.mInputSize; +// } // } -// -// /** -// * strip/replaceVariables/unstrip for preprocessor regression testing -// * -// * @param String $text -// * @param Title $title -// * @param ParserOptions $options -// * @param int $outputType -// * -// * @return String -// */ -// public function testSrvus($text, Title $title, ParserOptions $options, -// $outputType = XomwParser.OT_HTML -// ) { -// magicScopeVariable = this.synchronized(); -// this.startParse($title, $options, $outputType, true); -// -// $text = this.replaceVariables($text); -// $text = this.mStripState->unstripBoth($text); -// $text = Sanitizer::removeHTMLtags($text); -// return $text; +// return $this.mRevisionSize; +// } +// +// /** +// * Mutator for $mDefaultSort +// * +// * @param string $sort New value +// */ +// public function setDefaultSort($sort) { +// $this.mDefaultSort = $sort; +// $this.mOutput.setProperty('defaultsort', $sort); +// } +// +// /** +// * Accessor for $mDefaultSort +// * Will use the empty string if none is set. +// * +// * This value is treated as a prefix, so the +// * empty string is equivalent to sorting by +// * page name. +// * +// * @return string +// */ +// public function getDefaultSort() { +// if ($this.mDefaultSort !== false) { +// return $this.mDefaultSort; +// } else { +// return ''; // } -// -// /** -// * @param String $text -// * @param Title $title -// * @param ParserOptions $options -// * @return String -// */ -// public function testPst($text, Title $title, ParserOptions $options) { -// return this.preSaveTransform($text, $title, $options->getUser(), $options); +// } +// +// /** +// * Accessor for $mDefaultSort +// * Unlike getDefaultSort(), will return false if none is set +// * +// * @return string|bool +// */ +// public function getCustomDefaultSort() { +// return $this.mDefaultSort; +// } +// +// private static function getSectionNameFromStrippedText($text) { +// $text = Sanitizer::normalizeSectionNameWhitespace($text); +// $text = Sanitizer::decodeCharReferences($text); +// $text = self::normalizeSectionName($text); +// return $text; +// } +// +// private static function makeAnchor($sectionName) { +// return '#' . Sanitizer::escapeIdForLink($sectionName); +// } +// +// private function makeLegacyAnchor($sectionName) { +// $fragmentMode = $this.siteConfig.get('FragmentMode'); +// if (isset($fragmentMode[1]) && $fragmentMode[1] === 'legacy') { +// // ForAttribute() and ForLink() are the same for legacy encoding +// $id = Sanitizer::escapeIdForAttribute($sectionName, Sanitizer::ID_FALLBACK); +// } else { +// $id = Sanitizer::escapeIdForLink($sectionName); // } // -// /** -// * @param String $text -// * @param Title $title -// * @param ParserOptions $options -// * @return String -// */ -// public function testPreprocess($text, Title $title, ParserOptions $options) { -// return this.testSrvus($text, $title, $options, XomwParser.OT_PREPROCESS); +// return "#$id"; +// } +// +// /** +// * Try to guess the section anchor name based on a wikitext fragment +// * presumably extracted from a heading, for example "Header" from +// * "== Header ==". +// * +// * @param string $text +// * @return string Anchor (starting with '#') +// */ +// public function guessSectionNameFromWikiText($text) { +// // Strip out wikitext links(they break the anchor) +// $text = $this.stripSectionName($text); +// $sectionName = self::getSectionNameFromStrippedText($text); +// return self::makeAnchor($sectionName); +// } +// +// /** +// * Same as guessSectionNameFromWikiText(), but produces legacy anchors +// * instead, if possible. For use in redirects, since various versions +// * of Microsoft browsers interpret Location: headers as something other +// * than UTF-8, resulting in breakage. +// * +// * @param string $text The section name +// * @return string Anchor (starting with '#') +// */ +// public function guessLegacySectionNameFromWikiText($text) { +// // Strip out wikitext links(they break the anchor) +// $text = $this.stripSectionName($text); +// $sectionName = self::getSectionNameFromStrippedText($text); +// return $this.makeLegacyAnchor($sectionName); +// } +// +// /** +// * Like guessSectionNameFromWikiText(), but takes already-stripped text as input. +// * @param string $text Section name (plain text) +// * @return string Anchor (starting with '#') +// */ +// public static function guessSectionNameFromStrippedText($text) { +// $sectionName = self::getSectionNameFromStrippedText($text); +// return self::makeAnchor($sectionName); +// } +// +// /** +// * Apply the same normalization as code making links to this section would +// * +// * @param string $text +// * @return string +// */ +// private static function normalizeSectionName($text) { +// // T90902: ensure the same normalization is applied for IDs as to links +// titleParser = MediaWikiServices::getInstance().getTitleParser(); +// try { +// +// $parts = titleParser.splitTitleString("#$text"); +// } catch (MalformedTitleException $ex) { +// return $text; // } -// -// /** -// * Call a callback function on all regions of the given text that are not -// * inside strip markers, and replace those regions with the return value -// * of the callback. For example, with input: -// * -// * aaa<MARKER>bbb -// * -// * This will call the callback function twice, with 'aaa' and 'bbb'. Those -// * two strings will be replaced with the value returned by the callback in -// * each case. -// * -// * @param String $s -// * @param callable $callback -// * -// * @return String -// */ -// public function markerSkipCallback($s, $callback) { -// $i = 0; -// $out = ''; -// while ($i < strlen($s)) { -// markerStart = strpos($s, XomwParser.MARKER_PREFIX, $i); -// if (markerStart === false) { -// $out .= call_user_func($callback, substr($s, $i)); +// return $parts['fragment']; +// } +// +// /** +// * Strips a text string of wikitext for use in a section anchor +// * +// * Accepts a text string and then removes all wikitext from the +// * string and leaves only the resultant text (i.e. the result of +// * [[User:WikiSysop|Sysop]] would be "Sysop" and the result of +// * [[User:WikiSysop]] would be "User:WikiSysop") - this is intended +// * to create valid section anchors by mimicing the output of the +// * parser when headings are parsed. +// * +// * @param string $text Text string to be stripped of wikitext +// * for use in a Section anchor +// * @return string Filtered text string +// */ +// public function stripSectionName($text) { +// // Strip internal link markup +// $text = preg_replace('/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text); +// $text = preg_replace('/\[\[:?([^[]+)\|?\]\]/', '$1', $text); +// +// // Strip external link markup +// // @todo FIXME: Not tolerant to blank link text +// // I.E. [https://www.mediawiki.org] will render as [1] or something depending +// // on how many empty links there are on the page - need to figure that out. +// $text = preg_replace('/\[(?i:' . $this.mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text); +// +// // Parse wikitext quotes (italics & bold) +// $text = $this.doQuotes($text); +// +// // Strip HTML tags +// $text = StringUtils::delimiterReplace('<', '>', '', $text); +// return $text; +// } +// +// /** +// * strip/replaceVariables/unstrip for preprocessor regression testing +// * +// * @param string $text +// * @param Title title +// * @param ParserOptions $options +// * @param int $outputType +// * +// * @return string +// */ +// public function testSrvus($text, Title title, ParserOptions $options, +// $outputType = self::OT_HTML +// ) { +// $magicScopeVariable = $this.lock(); +// $this.startParse(title, $options, $outputType, true); +// +// $text = $this.replaceVariables($text); +// $text = $this.mStripState.unstripBoth($text); +// $text = Sanitizer::removeHTMLtags($text); +// return $text; +// } +// +// /** +// * @param string $text +// * @param Title title +// * @param ParserOptions $options +// * @return string +// */ +// public function testPst($text, Title title, ParserOptions $options) { +// return $this.preSaveTransform($text, title, $options.getUser(), $options); +// } +// +// /** +// * @param string $text +// * @param Title title +// * @param ParserOptions $options +// * @return string +// */ +// public function testPreprocess($text, Title title, ParserOptions $options) { +// return $this.testSrvus($text, title, $options, self::OT_PREPROCESS); +// } +// +// /** +// * Call a callback function on all regions of the given text that are not +// * inside strip markers, and replace those regions with the return value +// * of the callback. For example, with input: +// * +// * aaa<MARKER>bbb +// * +// * This will call the callback function twice, with 'aaa' and 'bbb'. Those +// * two strings will be replaced with the value returned by the callback in +// * each case. +// * +// * @param string $s +// * @param callable $callback +// * +// * @return string +// */ +// public function markerSkipCallback($s, $callback) { +// $i = 0; +// $out = ''; +// while ($i < strlen($s)) { +// $markerStart = strpos($s, self::MARKER_PREFIX, $i); +// if ($markerStart === false) { +// $out .= call_user_func($callback, substr($s, $i)); +// break; +// } else { +// $out .= call_user_func($callback, substr($s, $i, $markerStart - $i)); +// $markerEnd = strpos($s, self::MARKER_SUFFIX, $markerStart); +// if ($markerEnd === false) { +// $out .= substr($s, $markerStart); // break; // } else { -// $out .= call_user_func($callback, substr($s, $i, markerStart - $i)); -// markerEnd = strpos($s, XomwParser.MARKER_SUFFIX, markerStart); -// if (markerEnd === false) { -// $out .= substr($s, markerStart); -// break; -// } else { -// markerEnd += strlen(XomwParser.MARKER_SUFFIX); -// $out .= substr($s, markerStart, markerEnd - markerStart); -// $i = markerEnd; -// } +// $markerEnd += strlen(self::MARKER_SUFFIX); +// $out .= substr($s, $markerStart, $markerEnd - $markerStart); +// $i = $markerEnd; // } // } -// return $out; // } -// -// /** -// * Remove any strip markers found in the given text. -// * -// * @param String $text Input String -// * @return String -// */ -// public function killMarkers($text) { -// return this.mStripState->killMarkers($text); +// return $out; +// } +// +// /** +// * Remove any strip markers found in the given text. +// * +// * @param string $text +// * @return string +// */ +// public function killMarkers($text) { +// return $this.mStripState.killMarkers($text); +// } +// +// /** +// * Save the parser state required to convert the given half-parsed text to +// * HTML. "Half-parsed" in this context means the output of +// * recursiveTagParse() or internalParse(). This output has strip markers +// * from replaceVariables (extensionSubstitution() etc.), and link +// * placeholders from replaceLinkHolders(). +// * +// * Returns an array which can be serialized and stored persistently. This +// * array can later be loaded into another parser instance with +// * unserializeHalfParsedText(). The text can then be safely incorporated into +// * the return value of a parser hook. +// * +// * @deprecated since 1.31 +// * @param string $text +// * +// * @return array +// */ +// public function serializeHalfParsedText($text) { +// wfDeprecated(__METHOD__, '1.31'); +// $data = [ +// 'text' => $text, +// 'version' => self::HALF_PARSED_VERSION, +// 'stripState' => $this.mStripState.getSubState($text), +// 'linkHolders' => $this.mLinkHolders.getSubArray($text) +// ]; +// return $data; +// } +// +// /** +// * Load the parser state given in the $data array, which is assumed to +// * have been generated by serializeHalfParsedText(). The text contents is +// * extracted from the array, and its markers are transformed into markers +// * appropriate for the current Parser instance. This transformed text is +// * returned, and can be safely included in the return value of a parser +// * hook. +// * +// * If the $data array has been stored persistently, the caller should first +// * check whether it is still valid, by calling isValidHalfParsedText(). +// * +// * @deprecated since 1.31 +// * @param array $data Serialized data +// * @throws MWException +// * @return string +// */ +// public function unserializeHalfParsedText($data) { +// wfDeprecated(__METHOD__, '1.31'); +// if (!isset($data['version']) || $data['version'] != self::HALF_PARSED_VERSION) { +// throw new MWException(__METHOD__ . ': invalid version'); // } // -// /** -// * Save the parser state required to convert the given half-parsed text to -// * HTML. "Half-parsed" in this context means the output of -// * recursiveTagParse() or internalParse(). This output has strip markers -// * from replaceVariables (extensionSubstitution() etc.), and link -// * placeholders from replaceLinkHolders(). -// * -// * Returns an array which can be serialized and stored persistently. This -// * array can later be loaded into another parser instance with -// * unserializeHalfParsedText(). The text can then be safely incorporated into -// * the return value of a parser hook. -// * -// * @param String $text -// * -// * @return array -// */ -// public function serializeHalfParsedText($text) { -// $data = [ -// 'text' => $text, -// 'version' => XomwParser.HALF_PARSED_VERSION, -// 'stripState' => this.mStripState->getSubState($text), -// 'linkHolders' => this.mLinkHolders->getSubArray($text) -// ]; -// return $data; -// } -// -// /** -// * Load the parser state given in the $data array, which is assumed to -// * have been generated by serializeHalfParsedText(). The text contents is -// * extracted from the array, and its markers are transformed into markers -// * appropriate for the current Parser instance. This transformed text is -// * returned, and can be safely included in the return value of a parser -// * hook. -// * -// * If the $data array has been stored persistently, the caller should first -// * check whether it is still valid, by calling isValidHalfParsedText(). -// * -// * @param array $data Serialized data -// * @throws MWException -// * @return String -// */ -// public function unserializeHalfParsedText($data) { -// if (!isset($data['version']) || $data['version'] != XomwParser.HALF_PARSED_VERSION) { -// throw new MWException(__METHOD__ . ': invalid version'); -// } -// -// // First, extract the strip state. +// // First, extract the strip state. // $texts = [ $data['text'] ]; -// $texts = this.mStripState->merge($data['stripState'], $texts); -// -// // Now renumber links -// $texts = this.mLinkHolders->mergeForeign($data['linkHolders'], $texts); -// -// // Should be good to go. -// return $texts[0]; -// } -// -// /** -// * Returns true if the given array, presumed to be generated by -// * serializeHalfParsedText(), is compatible with the current version of the -// * parser. -// * -// * @param array $data -// * -// * @return boolean -// */ -// public function isValidHalfParsedText($data) { -// return isset($data['version']) && $data['version'] == XomwParser.HALF_PARSED_VERSION; -// } -// -// /** -// * Parsed a width param of imagelink like 300px or 200x300px -// * -// * @param String $value -// * -// * @return array -// * @since 1.20 -// */ -// public function parseWidthParam($value) { -// $parsedWidthParam = []; -// if ($value === '') { -// return $parsedWidthParam; -// } -// m = []; -// // (T15500) In both cases (width/height and width only), -// // permit trailing "px" for backward compatibility. -// if (preg_match('/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, m)) { -// $width = intval(m[1]); -// $height = intval(m[2]); -// $parsedWidthParam['width'] = $width; -// $parsedWidthParam['height'] = $height; -// } elseif (preg_match('/^[0-9]*\s*(?:px)?\s*$/', $value)) { -// $width = intval($value); -// $parsedWidthParam['width'] = $width; -// } +// $texts = $this.mStripState.merge($data['stripState'], $texts); +// +// // Now renumber links +// $texts = $this.mLinkHolders.mergeForeign($data['linkHolders'], $texts); +// +// // Should be good to go. +// return $texts[0]; +// } +// +// /** +// * Returns true if the given array, presumed to be generated by +// * serializeHalfParsedText(), is compatible with the current version of the +// * parser. +// * +// * @deprecated since 1.31 +// * @param array $data +// * +// * @return bool +// */ +// public function isValidHalfParsedText($data) { +// wfDeprecated(__METHOD__, '1.31'); +// return isset($data['version']) && $data['version'] == self::HALF_PARSED_VERSION; +// } +// +// /** +// * Parsed a width param of imagelink like 300px or 200x300px +// * +// * @param string $value +// * @param bool $parseHeight +// * +// * @return array +// * @since 1.20 +// */ +// public static function parseWidthParam($value, $parseHeight = true) { +// $parsedWidthParam = []; +// if ($value === '') { // return $parsedWidthParam; // } -// -// /** -// * Lock the current instance of the parser. -// * -// * This is meant to stop someone from calling the parser -// * recursively and messing up all the strip state. -// * -// * @throws MWException If parser is in a parse -// * @return ScopedCallback The synchronized will be released once the return value goes out of scope. -// */ -// protected function synchronized() { -// if (this.mInParse) { -// throw new MWException("Parser state cleared while parsing. " -// . "Did you call Parser::parse recursively?"); -// } -// this.mInParse = true; -// -// $recursiveCheck = new ScopedCallback(function() { -// this.mInParse = false; -// }); -// -// return $recursiveCheck; +// $m = []; +// // (T15500) In both cases (width/height and width only), +// // permit trailing "px" for backward compatibility. +// if ($parseHeight && preg_match('/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m)) { +// $width = intval($m[1]); +// $height = intval($m[2]); +// $parsedWidthParam['width'] = $width; +// $parsedWidthParam['height'] = $height; +// } elseif (preg_match('/^[0-9]*\s*(?:px)?\s*$/', $value)) { +// $width = intval($value); +// $parsedWidthParam['width'] = $width; // } -// -// /** -// * Strip outer <p></p> tag from the HTML source of a single paragraph. -// * -// * Returns original HTML if the <p/> tag has any attributes, if there's no wrapping <p/> tag, -// * or if there is more than one <p/> tag in the input HTML. -// * -// * @param String $html -// * @return String -// * @since 1.24 -// */ -// public static function stripOuterParagraph($html) { -// m = []; -// if (preg_match('/^<p>(.*)\n?<\/p>\n?$/sU', $html, m)) { -// if (strpos(m[1], '</p>') === false) { -// $html = m[1]; -// } -// } -// -// return $html; +// return $parsedWidthParam; +// } +// +// /** +// * Lock the current instance of the parser. +// * +// * This is meant to stop someone from calling the parser +// * recursively and messing up all the strip state. +// * +// * @throws MWException If parser is in a parse +// * @return ScopedCallback The lock will be released once the return value goes out of scope. +// */ +// protected function lock() { +// if ($this.mInParse) { +// throw new MWException("Parser state cleared while parsing. " +// . "Did you call Parser::parse recursively? Lock is held by: " . $this.mInParse); // } // -// /** -// * Return this parser if it is not doing anything, otherwise -// * get a fresh parser. You can use this method by doing -// * myParser = $wgParser->getFreshParser(), or more simply -// * $wgParser->getFreshParser()->parse(...); -// * if you're unsure if $wgParser is safe to use. -// * -// * @since 1.24 -// * @return Parser A parser Object that is not parsing anything -// */ -// public function getFreshParser() { -// global $wgParserConf; -// if (this.mInParse) { -// return new $wgParserConf['class']($wgParserConf); -// } else { -// return $this; -// } +// // Save the backtrace when locking, so that if some code tries locking again, +// // we can print the lock owner's backtrace for easier debugging +// $e = new Exception; +// $this.mInParse = $e.getTraceAsString(); +// +// $recursiveCheck = new ScopedCallback(function () { +// $this.mInParse = false; +// }); +// +// return $recursiveCheck; +// } +// +// /** +// * Strip outer <p></p> tag from the HTML source of a single paragraph. +// * +// * Returns original HTML if the <p/> tag has any attributes, if there's no wrapping <p/> tag, +// * or if there is more than one <p/> tag in the input HTML. +// * +// * @param string $html +// * @return string +// * @since 1.24 +// */ +// public static function stripOuterParagraph($html) { +// $m = []; +// if (preg_match('/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m) && strpos($m[1], '</p>') === false) { +// $html = $m[1]; // } // -// /** -// * Set's up the PHP implementation of OOUI for use in this request -// * and instructs OutputPage to enable OOUI for itself. -// * -// * @since 1.26 -// */ -// public function enableOOUI() { -// OutputPage::setupOOUI(); -// this.mOutput->setEnableOOUI(true); +// return $html; +// } +// +// /** +// * Return this parser if it is not doing anything, otherwise +// * get a fresh parser. You can use this method by doing +// * $myParser = $wgParser.getFreshParser(), or more simply +// * $wgParser.getFreshParser().parse(...); +// * if you're unsure if $wgParser is safe to use. +// * +// * @since 1.24 +// * @return Parser A parser object that is not parsing anything +// */ +// public function getFreshParser() { +// if ($this.mInParse) { +// return $this.factory.create(); +// } else { +// return $this; // } +// } +// +// /** +// * Set's up the PHP implementation of OOUI for use in this request +// * and instructs OutputPage to enable OOUI for itself. +// * +// * @since 1.26 +// */ +// public function enableOOUI() { +// OutputPage::setupOOUI(); +// $this.mOutput.setEnableOOUI(true); +// } public static final String MARKER_PREFIX_STR = "\u007f'\"`UNIQ-"; public static final byte[] MARKER_PREFIX = Bry_.new_a7(MARKER_PREFIX_STR) @@ -5450,7 +7255,7 @@ Tfds.Write(nowiki, isHTML, forceRawInterwiki, isChildObj, isLocalObj, titleText, rv.Add_obj(key, key); } byte[] bry__relative = Bry_.new_a7("//"); - rv.Add_obj(bry__relative, bry__relative); // REF.MW: "$this->mUrlProtocols = wfUrlProtocols();"; "wfUrlProtocols( $includeProtocolRelative = true )" + rv.Add_obj(bry__relative, bry__relative); // REF.MW: "$this.mUrlProtocols = wfUrlProtocols();"; "wfUrlProtocols($includeProtocolRelative = true)" return rv; } } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserCtx.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserCtx.java index 4c43bd7f9..8e916c212 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserCtx.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParserCtx.java @@ -13,18 +13,18 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; -import gplx.xowa.mediawiki.includes.parsers.lnkis.*; -public class XomwParserCtx { - public XomwTitle Page_title() {return page_title;} private XomwTitle page_title; - public Xomw_image_params Lnki_wkr__make_image__img_params = new Xomw_image_params(); - public byte[][] Lnki_wkr__make_image__match_magic_word = new byte[2][]; - public int[] Lnki_wkr__make_image__img_size = new int[2]; - public Xomw_params_mto Linker__makeImageLink__prms = new Xomw_params_mto(); - - public void Init_by_page(XomwTitle page_title) { - this.page_title = page_title; - } - - public static final int Pos__bos = -1; -} +package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; +import gplx.xowa.mediawiki.includes.parsers.lnkis.*; +public class XomwParserCtx { + public XomwTitleOld Page_title() {return page_title;} private XomwTitleOld page_title; + public Xomw_image_params Lnki_wkr__make_image__img_params = new Xomw_image_params(); + public byte[][] Lnki_wkr__make_image__match_magic_word = new byte[2][]; + public int[] Lnki_wkr__make_image__img_size = new int[2]; + public Xomw_params_mto Linker__makeImageLink__prms = new Xomw_params_mto(); + + public void Init_by_page(XomwTitleOld page_title) { + this.page_title = page_title; + } + + public static final int Pos__bos = -1; +} diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser_tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser_tst.java index 2321480e2..c00e8dff0 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser_tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/XomwParser_tst.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import org.junit.*; import gplx.xowa.mediawiki.includes.parsers.preprocessors.*; @@ -67,8 +67,8 @@ class XomwParser_fxt { Xowe_wiki wiki = Xoa_app_fxt.Make__wiki__edit(app); this.parser = new XomwParser(XomwEnv.NewTestByApp(app)); parser.Init_by_wiki(wiki); - parser.Init_by_page(XomwTitle.newFromText(parser.Env(), Bry_.new_a7("Page_1"))); - pctx.Init_by_page(XomwTitle.newFromText(parser.Env(), Bry_.new_a7("Page_1"))); + parser.Init_by_page(XomwTitleOld.newFromText(parser.Env(), Bry_.new_a7("Page_1"))); + pctx.Init_by_page(XomwTitleOld.newFromText(parser.Env(), Bry_.new_a7("Page_1"))); } public void Test__parse(String src_str, String expd) { byte[] src_bry = Bry_.new_u8(src_str); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr.java index 037a0761d..75d592457 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; import gplx.core.btries.*; import gplx.core.primitives.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.wikis.xwikis.*; @@ -42,7 +42,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls private final XomwStripState strip_state; private XomwEnv env; private Xow_wiki wiki; - private XomwTitle mPageTitle; + private XomwTitleOld mPageTitle; // private final XomwLinker_NormalizeSubpageLink normalize_subpage_link = new XomwLinker_NormalizeSubpageLink(); private final Bry_bfr tmp; private final XomwParserIface parser; @@ -69,7 +69,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls this.env = env; this.wiki = wiki; if (title_chars_for_lnki == null) { - title_chars_for_lnki = (boolean[])Array_.Clone(XomwTitle.Title_chars_valid()); + title_chars_for_lnki = (boolean[])Array_.Clone(XomwTitleOld.Title_chars_valid()); // the % is needed to support urlencoded titles as well title_chars_for_lnki[Byte_ascii.Hash] = true; title_chars_for_lnki[Byte_ascii.Percent] = true; @@ -155,7 +155,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls // PORTED.BGN: if (preg_match($e1, $line, $m)) && else if (preg_match($e1_img, $line, $m)) // NOTE: both e1 and e1_img are effectively the same; e1_img allows nested "[["; EX: "[[A|b[[c]]d]]" will stop at "[[A|b" int ttl_bgn = cur; - int ttl_end = XomwTitle.Find_fwd_while_title(src, cur, src_end, title_chars_for_lnki); + int ttl_end = XomwTitleOld.Find_fwd_while_title(src, cur, src_end, title_chars_for_lnki); cur = ttl_end; int capt_bgn = -1, capt_end = -1; int nxt_lnki = -1; @@ -253,7 +253,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls link = Bry_.Mid(link, 1); } // $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null; - XomwTitle nt = XomwTitle.newFromText(env, link); + XomwTitleOld nt = XomwTitleOld.newFromText(env, link); // Make subpage if necessary // boolean useSubpages = nt.Ns().Subpages_enabled(); @@ -261,7 +261,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls // Maybe_do_subpage_link(normalize_subpage_link, orig_link, text); // link = normalize_subpage_link.link; // text = normalize_subpage_link.text; -// nt = XomwTitle.newFromText(link); +// nt = XomwTitleOld.newFromText(link); // } // IGNORE: handled in rewrite above // else { @@ -270,7 +270,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls byte[] unstrip = strip_state.unstripNoWiki(link); if (!Bry_.Eq(unstrip, link)) - nt = XomwTitle.newFromText(env, unstrip); + nt = XomwTitleOld.newFromText(env, unstrip); if (nt == null) { bfr.Add_mid(src, prv, lnki_bgn + 2); // $s .= $prefix . '[[' . $line; prv = cur = lnki_bgn + 2; @@ -443,7 +443,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls } } } - public void makeImage(XomwEnv env, XomwParserCtx pctx, Bry_bfr bfr, XomwTitle title, byte[] options_at_link, XomwLinkHolderArray holders) { + public void makeImage(XomwEnv env, XomwParserCtx pctx, Bry_bfr bfr, XomwTitleOld title, byte[] options_at_link, XomwLinkHolderArray holders) { // Check if the options text is of the form "options|alt text" // Options are: // * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang @@ -758,7 +758,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls * @param array $options Array of options to RepoGroup::findFile * @return array ( File or false, Title of file ) */ - public XomwFile fetchFileAndTitle(XomwTitle title, Hash_adp options) { + public XomwFile fetchFileAndTitle(XomwTitleOld title, Hash_adp options) { XomwFile file = fetchFileNoRegister(title, options); //$time = $file ? $file->getTimestamp() : false; @@ -782,7 +782,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls * @param array $options Array of options to RepoGroup::findFile * @return File|boolean */ - private XomwFile fetchFileNoRegister(XomwTitle title, Hash_adp options) { + private XomwFile fetchFileNoRegister(XomwTitleOld title, Hash_adp options) { XomwFile file = null; // if ( isset( $options['broken'] ) ) { // file = false; // broken thumbnail forced by hook @@ -799,7 +799,7 @@ public class Xomw_lnki_wkr {// THREAD.UNSAFE: caching for repeated calls public void replaceLinkHolders(XomwParserBfr pbfr) { holders.replace(pbfr); } - public void Make_known_link_holder(Bry_bfr bfr, XomwTitle nt, byte[] text, byte[] trail, byte[] prefix) { + public void Make_known_link_holder(Bry_bfr bfr, XomwTitleOld nt, byte[] text, byte[] trail, byte[] prefix) { byte[][] split_trail = linker.splitTrail(trail); byte[] inside = split_trail[0]; trail = split_trail[1]; diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr__file__tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr__file__tst.java index 0b4b9e4cc..b289da4c0 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr__file__tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/lnkis/Xomw_lnki_wkr__file__tst.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.lnkis; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; import org.junit.*; import gplx.core.tests.*; import gplx.xowa.mediawiki.includes.filerepo.*; import gplx.xowa.mediawiki.includes.filerepo.file.*; @@ -96,7 +96,7 @@ class Xomw_lnki_wkr__fxt { // ctx pctx = new XomwParserCtx(); - pctx.Init_by_page(XomwTitle.newFromText(env, Bry_.new_a7("Page_1"))); + pctx.Init_by_page(XomwTitleOld.newFromText(env, Bry_.new_a7("Page_1"))); } public void Clear() { wkr.Clear_state(); diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/magiclinks/Xomw_magiclinks_wkr__tst.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/magiclinks/Xomw_magiclinks_wkr__tst.java index e6a857647..d693c06e1 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/magiclinks/Xomw_magiclinks_wkr__tst.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/magiclinks/Xomw_magiclinks_wkr__tst.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.magiclinks; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; import org.junit.*; public class Xomw_magiclinks_wkr__tst { @@ -71,7 +71,7 @@ class Xomw_magiclinks_wkr__fxt { public Xomw_magiclinks_wkr__fxt() { Xomw_regex_space regex_space = new Xomw_regex_space(); XomwParser parser = new XomwParser(XomwEnv_fxt.NewTest()); - pctx.Init_by_page(XomwTitle.newFromText(parser.Env(), Bry_.new_a7("Page_1"))); + pctx.Init_by_page(XomwTitleOld.newFromText(parser.Env(), Bry_.new_a7("Page_1"))); this.wkr = new Xomw_magiclinks_wkr(parser, parser.Sanitizer(), parser.Linker(), new Xomw_regex_boundary(regex_space), new Xomw_regex_url(regex_space)); wkr.Init_by_wiki(); } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame.java index bd890aa54..9d9bd871b 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.preprocessors; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; // MW.FILE:Preprocessor /** @@ -45,8 +45,8 @@ public abstract class XomwPPFrame { * * @return PPFrame */ - public XomwPPFrame newChild(Object args, XomwTitle title) {return newChild(args, title, 0);} - @gplx.Virtual public XomwPPFrame newChild(Object args, XomwTitle title, int indexOffset) {return null;} + public XomwPPFrame newChild(Object args, XomwTitleOld title) {return newChild(args, title, 0);} + @gplx.Virtual public XomwPPFrame newChild(Object args, XomwTitleOld title, int indexOffset) {return null;} /** * Expand a document tree node, caching the result on its parent with the given key @@ -141,7 +141,7 @@ public abstract class XomwPPFrame { * @param Title $title * @return boolean */ - @gplx.Virtual public boolean loopCheck(XomwTitle title) {return false;} + @gplx.Virtual public boolean loopCheck(XomwTitleOld title) {return false;} /** * Return true if the frame is a template frame @@ -203,5 +203,5 @@ public abstract class XomwPPFrame { * * @return Title */ - @gplx.Virtual public XomwTitle getTitle() {return null;} + @gplx.Virtual public XomwTitleOld getTitle() {return null;} } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame_Hash.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame_Hash.java index 8b667a4f0..ba63d256b 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame_Hash.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPFrame_Hash.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.preprocessors; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; import gplx.xowa.mediawiki.includes.exception.*; import gplx.core.bits.*; @@ -33,7 +33,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { /** /** * @var Title */ - public XomwTitle title; + public XomwTitleOld title; public XophpArray titleCache; /** @@ -80,7 +80,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { /** * @throws MWException * @return PPTemplateFrame_Hash */ - @Override public XomwPPFrame newChild(Object argsObj, XomwTitle title, int indexOffset) { + @Override public XomwPPFrame newChild(Object argsObj, XomwTitleOld title, int indexOffset) { XophpArray namedArgs = XophpArray.New(); XophpArray numberedArgs = XophpArray.New(); if (!XophpObject_.is_true(title)) { @@ -576,7 +576,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { /** * * @return boolean */ - @Override public boolean loopCheck(XomwTitle title) { + @Override public boolean loopCheck(XomwTitleOld title) { return !this.loopCheckHash.isset(title.getPrefixedDBkeyStr()); } @@ -594,7 +594,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { /** * * @return Title */ - @Override public XomwTitle getTitle() { + @Override public XomwTitleOld getTitle() { return this.title; } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPTemplateFrame_Hash.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPTemplateFrame_Hash.java index 8ad2f9ed3..130e99956 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPTemplateFrame_Hash.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors/XomwPPTemplateFrame_Hash.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.preprocessors; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; ///** // * Expansion frame with template arguments @@ -31,7 +31,7 @@ class XomwPPTemplateFrame_Hash extends XomwPPFrame_Hash { public XophpArray num */ // parent = false, numberedArgs = [], namedArgs = []; titl = false public XomwPPTemplateFrame_Hash(XomwPreprocessor preprocessor, XomwPPFrame_Hash parent, XophpArray numberedArgs, - XophpArray namedArgs, XomwTitle title + XophpArray namedArgs, XomwTitleOld title ) {super(preprocessor); this.parent = parent; this.numberedArgs = numberedArgs; diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame.java index 00dfe10af..20dbcc90f 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.preprocessors_new; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; // MW.FILE:Preprocessor /** @@ -45,7 +45,7 @@ public abstract class XomwPPFrame { * * @return PPFrame */ - @gplx.Virtual public XomwPPFrame newChild(Object args, XomwTitle title, int indexOffset) {return null;} + @gplx.Virtual public XomwPPFrame newChild(Object args, XomwTitleOld title, int indexOffset) {return null;} /** * Expand a document tree node, caching the result on its parent with the given key @@ -139,7 +139,7 @@ public abstract class XomwPPFrame { * @param Title $title * @return boolean */ - @gplx.Virtual public boolean loopCheck(XomwTitle title) {return false;} + @gplx.Virtual public boolean loopCheck(XomwTitleOld title) {return false;} /** * Return true if the frame is a template frame @@ -201,5 +201,5 @@ public abstract class XomwPPFrame { * * @return Title */ - @gplx.Virtual public XomwTitle getTitle() {return null;} + @gplx.Virtual public XomwTitleOld getTitle() {return null;} } diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame_Hash.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame_Hash.java index a1b6f4cec..6a4af68c0 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame_Hash.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPFrame_Hash.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.parsers.preprocessors_new; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.parsers.*; import gplx.xowa.mediawiki.includes.exception.*; import gplx.core.bits.*; @@ -33,7 +33,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { // /** // /** // * @var Title // */ -// public XomwTitle title; +// public XomwTitleOld title; // public XophpArray titleCache; // // /** @@ -80,7 +80,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { // /** // * @throws MWException // * @return PPTemplateFrame_Hash // */ -// public override XomwPPFrame newChild(Object argsObj, XomwTitle title, int indexOffset) { +// public override XomwPPFrame newChild(Object argsObj, XomwTitleOld title, int indexOffset) { // XophpArray namedArgs = XophpArray.New(); // XophpArray numberedArgs = XophpArray.New(); // if (!XophpObject_.is_true(title)) { @@ -576,7 +576,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { // /** // * // * @return boolean // */ -// public override boolean loopCheck(XomwTitle title) { +// public override boolean loopCheck(XomwTitleOld title) { // return !this.loopCheckHash.isset(title.getPrefixedDBkeyStr()); // } // @@ -594,7 +594,7 @@ public class XomwPPFrame_Hash extends XomwPPFrame { // /** // * // * @return Title // */ -// public override XomwTitle getTitle() { +// public override XomwTitleOld getTitle() { // return this.title; // } // diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPTemplateFrame_Hash.java b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPTemplateFrame_Hash.java index f004b3476..9f1ced0ce 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPTemplateFrame_Hash.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/parsers/preprocessors_new/XomwPPTemplateFrame_Hash.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ //namespace gplx.xowa.mediawiki.includes.parsers.preprocessors_new { // ///** // // * Expansion frame with template arguments @@ -31,7 +31,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt // */ // // parent = false, numberedArgs = [], namedArgs = []; titl = false // public XomwPPTemplateFrame_Hash(XomwPreprocessor preprocessor, XomwPPFrame_Hash parent, XophpArray numberedArgs, -// XophpArray namedArgs, XomwTitle title +// XophpArray namedArgs, XomwTitleOld title // ) : super(preprocessor) { // this.parent = parent; // this.numberedArgs = numberedArgs; diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java b/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java index df05860d1..7a0205729 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwMediaWikiTitleCodec.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.title; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.languages.*; import gplx.xowa.mediawiki.includes.interwiki.*; @@ -314,7 +314,7 @@ public class XomwMediaWikiTitleCodec implements XomwTitleFormatter { if (Bry_.Len_eq_0(dbkey)) { // Empty self-links should point to the Main Page, to ensure // compatibility with cross-wiki transclusions and the like. - XomwTitle mainPage = XomwTitle.newMainPage(mws.env); + XomwTitleOld mainPage = XomwTitleOld.newMainPage(mws.env); XomwMediaWikiTitleCodecParts rv = new XomwMediaWikiTitleCodecParts(mainPage.getDBkey(), mainPage.getNamespace()); rv.interwiki = mainPage.getInterwiki(); rv.local_interwiki = true; diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleFormatter.java b/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleFormatter.java index 187284674..6f50b3685 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleFormatter.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleFormatter.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.title; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; /** * A title formatter service for MediaWiki. diff --git a/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleParser.java b/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleParser.java index 86f031c8e..5fb19efa1 100644 --- a/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleParser.java +++ b/400_xowa/src/gplx/xowa/mediawiki/includes/title/XomwTitleParser.java @@ -1,18 +1,18 @@ -/* -XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com - -XOWA is licensed under the terms of the General Public License (GPL) Version 3, -or alternatively under the terms of the Apache License Version 2.0. - -You may use XOWA according to either of these licenses as is most appropriate -for your project on a case-by-case basis. - -The terms of each license can be found in the source code repository: - -GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt -Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt -*/ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2017 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ package gplx.xowa.mediawiki.includes.title; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; /** * A title parser service for %MediaWiki. @@ -36,5 +36,5 @@ public interface XomwTitleParser { * @throws MalformedTitleException If the text is not a valid representation of a page title. * @return TitleValue */ - XomwTitle parseTitle(byte[] text, int defaultNamespace); + XomwTitleOld parseTitle(byte[] text, int defaultNamespace); }