* …
=== Action API changes in 1.33 ===
+* (T198913) Added 'ApiOptions' hook.
* …
=== Action API internal changes in 1.33 ===
'SpecialRecentChanges' => __DIR__ . '/includes/specials/SpecialRecentchanges.php',
'SpecialRecentChangesLinked' => __DIR__ . '/includes/specials/SpecialRecentchangeslinked.php',
'SpecialRedirect' => __DIR__ . '/includes/specials/SpecialRedirect.php',
+ 'SpecialRedirectExternal' => __DIR__ . '/includes/specials/SpecialRedirectExternal.php',
'SpecialRedirectToSpecial' => __DIR__ . '/includes/specialpage/RedirectSpecialPage.php',
'SpecialRemoveCredentials' => __DIR__ . '/includes/specials/SpecialRemoveCredentials.php',
'SpecialResetTokens' => __DIR__ . '/includes/specials/SpecialResetTokens.php',
(url), 'width', 'height', 'alt', 'align'.
- url: Url for the given title.
+'ApiOptions': Called by action=options before applying changes to user
+preferences.
+$apiModule: Calling ApiOptions object
+$user: User object whose preferences are being changed
+$changes: Associative array of preference name => value
+$resetKinds: Array of strings specifying which options kinds to reset.
+ See User::resetOptions() and User::getOptionKinds() for possible
+ values.
+
'ApiParseMakeOutputPage': Called when preparing the OutputPage object for
ApiParse. This is mainly intended for calling OutputPage::addContentOverride()
or OutputPage::addContentOverrideCallback().
$this->dieWithError( [ 'apierror-missingparam', 'optionname' ] );
}
- if ( $params['reset'] ) {
- $this->resetPreferences( $params['resetkinds'] );
- $changed = true;
+ $resetKinds = $params['resetkinds'];
+ if ( !$params['reset'] ) {
+ $resetKinds = [];
}
$changes = [];
$newValue = $params['optionvalue'] ?? null;
$changes[$params['optionname']] = $newValue;
}
+
+ Hooks::run( 'ApiOptions', [ $this, $user, $changes, $resetKinds ] );
+
+ if ( $resetKinds ) {
+ $this->resetPreferences( $resetKinds );
+ $changed = true;
+ }
+
if ( !$changed && !count( $changes ) ) {
$this->dieWithError( 'apierror-nochanges' );
}
"apihelp-query+info-paramvalue-prop-notificationtimestamp": "الطابع الزمني لإشعار قائمة المراقبة لكل صفحة.",
"apihelp-query+info-paramvalue-prop-subjectid": "معرف الصفحة للصفحة الرئيسية لكل صفحة نقاش.",
"apihelp-query+info-paramvalue-prop-url": "يعطي مسارا كاملا، ومسارا للتعديل، ومسار الأساسي لكل صفحة.",
- "apihelp-query+info-paramvalue-prop-readable": "ما إذا كان يمكن للمستخدم قراءة هذه الصفحة.",
+ "apihelp-query+info-paramvalue-prop-readable": "ما إذا كان يمكن للمستخدم قراءة هذه الصفحة، استخدم <kbd>intestactions=read</kbd> بدلا من ذلك.",
"apihelp-query+info-paramvalue-prop-preload": "يعطي النص الذي تم إرجاعه بواسطة EditFormPreloadText.",
"apihelp-query+info-paramvalue-prop-displaytitle": "يعطي الطريقة التي يتم بها عرض عنوان الصفحة بالفعل.",
"apihelp-query+info-paramvalue-prop-varianttitles": "يعطي عنوان العرض بجميع الصيغ الخاصة بلغة محتوى الموقع.",
"apihelp-query+info-param-testactions": "اختبر ما إذا كان المستخدم الحالي يمكنه تنفيذ إجراءات معينة على الصفحة.",
+ "apihelp-query+info-param-testactionsdetail": "مستوى التفاصيل لـ<var>$1testactions</var>، استخدم وسائط [[Special:ApiHelp/main|الوحدة الرئيسية]] <var>errorformat</var> و<var>errorlang</var> للتحكم في تنسيق الرسائل التي تم إرجاعها.",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "إرجاع قيمة منطقية لكل إجراء.",
+ "apihelp-query+info-paramvalue-testactionsdetail-full": "إرجاع الرسائل التي تصف سبب عدم السماح بالإجراء ، أو مصفوفة فارغة إذا كان مسموحا بها.",
+ "apihelp-query+info-paramvalue-testactionsdetail-quick": "مثل <kbd>full</kbd> ولكن تخطي المراجعات باهظة الثمن.",
"apihelp-query+info-param-token": "استخدم [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] بدلا من ذلك.",
"apihelp-query+info-example-simple": "الحصول على معلومات حول الصفحة <kbd>Main Page</kbd>.",
"apihelp-query+info-example-protection": "احصل على معلومات عامة وحماية حول الصفحة <kbd>Main Page</kbd>.",
"apihelp-query+info-paramvalue-prop-watchers": "Die Anzahl der Beobachter, falls erlaubt.",
"apihelp-query+info-paramvalue-prop-notificationtimestamp": "Der Beobachtungslisten-Benachrichtigungs-Zeitstempel jeder Seite.",
"apihelp-query+info-paramvalue-prop-subjectid": "Die Seitenkennung der Elternseite jeder Diskussionsseite.",
- "apihelp-query+info-paramvalue-prop-readable": "Ob der Benutzer diese Seite betrachten darf.",
+ "apihelp-query+info-paramvalue-prop-readable": "Ob der Benutzer diese Seite lesen kann. Stattdessen <kbd>intestactions=read</kbd> verwenden.",
"apihelp-query+info-paramvalue-prop-preload": "Gibt den Text aus, der von EditFormPreloadText zurückgegeben wurde.",
"apihelp-query+info-paramvalue-prop-displaytitle": "Gibt die Art und Weise an, in der der Seitentitel tatsächlich angezeigt wird.",
"apihelp-query+info-paramvalue-prop-varianttitles": "Gibt den Anzeigetitel in allen Varianten der Sprache des Websiteinhalts aus.",
"apihelp-query+info-param-testactions": "Überprüft, ob der aktuelle Benutzer gewisse Aktionen auf der Seite ausführen kann.",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "Gibt einen booleschen Wert für jede Aktion zurück.",
+ "apihelp-query+info-paramvalue-testactionsdetail-quick": "Wie <kbd>full</kbd>, aber mit Überspringen von Aufwandsüberprüfungen.",
"apihelp-query+info-example-simple": "Ruft Informationen über die Seite <kbd>Hauptseite</kbd> ab.",
"apihelp-query+iwbacklinks-summary": "Findet alle Seiten, die auf einen angegebenen Interwikilink verlinken.",
"apihelp-query+iwbacklinks-param-prefix": "Präfix für das Interwiki.",
"apihelp-query+redirects-paramvalue-prop-title": "Titel einer jeden Weiterleitung.",
"apihelp-query+redirects-param-namespace": "Schließt nur Seiten in diesen Namensräumen ein.",
"apihelp-query+redirects-param-limit": "Wie viele Weiterleitungen zurückgegeben werden sollen.",
+ "apihelp-query+revisions-summary": "Ruft Informationen zur Version ab.",
+ "apihelp-query+revisions-param-excludeuser": "Schließt Versionen nach Benutzer aus.",
"apihelp-query+revisions-param-tag": "Listet nur Versionen auf, die mit dieser Markierung markiert sind.",
"apihelp-query+revisions+base-param-prop": "Zurückzugebende Eigenschaften jeder Version:",
"apihelp-query+revisions+base-paramvalue-prop-ids": "Die Kennung der Version.",
"apihelp-query+info-paramvalue-prop-notificationtimestamp": "L’horodatage de notification de la liste de suivi de chaque page.",
"apihelp-query+info-paramvalue-prop-subjectid": "L’ID de page de la page parent de chaque page de discussion.",
"apihelp-query+info-paramvalue-prop-url": "Fournit une URL complète, une URL de modification, et l’URL canonique de chaque page.",
- "apihelp-query+info-paramvalue-prop-readable": "Si l’utilisateur peut lire cette page.",
+ "apihelp-query+info-paramvalue-prop-readable": "Si l’utilisateur peut lire cette page. Utiliser plutôt <kbd>intestactions=read</kbd>.",
"apihelp-query+info-paramvalue-prop-preload": "Fournit le texte renvoyé par EditFormPreloadText.",
"apihelp-query+info-paramvalue-prop-displaytitle": "Fournit la manière dont le titre de la page est réellement affiché.",
"apihelp-query+info-paramvalue-prop-varianttitles": "Donne le titre affiché dans toutes les variantes de la langue de contenu du site.",
"apihelp-query+info-param-testactions": "Tester si l’utilisateur actuel peut effectuer certaines actions sur la page.",
+ "apihelp-query+info-param-testactionsdetail": "Niveau de détail pour <var>$1testactions</var>. Utiliser les paramètres <var>errorformat</var> et <var>errorlang</var> du [[Special:ApiHelp/main|module principal]] pour contrôler la mise en forme des messages renvoyés.",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "Renvoyer une valeur booléenne pour chaque action.",
+ "apihelp-query+info-paramvalue-testactionsdetail-full": "Renvoyer des messages décrivant pourquoi l’action est interdite, ou un tableau vide si elle est autorisée.",
+ "apihelp-query+info-paramvalue-testactionsdetail-quick": "Comme <kbd>full</kbd> mais en sautant les contrôles coûteux.",
"apihelp-query+info-param-token": "Utiliser plutôt [[Special:ApiHelp/query+tokens|action=query&meta=tokens]].",
"apihelp-query+info-example-simple": "Obtenir des informations sur la page <kbd>Main Page</kbd>.",
"apihelp-query+info-example-protection": "Obtenir des informations générales et de protection sur la page <kbd>Main Page</kbd>.",
"apihelp-query+info-summary": "Ottieni informazioni base sulla pagina.",
"apihelp-query+info-param-prop": "Quali proprietà aggiuntive ottenere:",
"apihelp-query+info-paramvalue-prop-visitingwatchers": "Il numero di osservatori di ogni pagina che hanno visitato le ultime modifiche alla pagina, se consentito.",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "Restituisce un valore booleano per ogni azione.",
"apihelp-query+iwbacklinks-param-prefix": "Prefisso per l'interwiki.",
"apihelp-query+iwbacklinks-param-limit": "Quante pagine totali restituire.",
"apihelp-query+iwbacklinks-param-prop": "Quali proprietà ottenere:",
"apihelp-query+info-paramvalue-prop-protection": "각 문서의 보호 수준을 나열합니다.",
"apihelp-query+info-paramvalue-prop-readable": "사용자가 이 문서를 읽을 수 있는지의 여부.",
"apihelp-query+info-paramvalue-prop-varianttitles": "모든 종류의 사이트 내용 언어의 표시 제목을 지정합니다.",
+ "apihelp-query+info-paramvalue-testactionsdetail-boolean": "각 동작의 불리언 값을 반환합니다.",
"apihelp-query+iwbacklinks-summary": "제시된 인터위키 링크에 연결된 모든 문서를 찾습니다.",
"apihelp-query+iwbacklinks-param-prefix": "인터위키의 접두사.",
"apihelp-query+iwbacklinks-param-title": "검색할 인터위키 링크. <var>$1blprefix</var>와 함께 사용해야 합니다.",
"apihelp-query+allpages-param-minsize": "限制頁面至少要有這樣多的位元組。",
"apihelp-query+allpages-param-maxsize": "限制頁面最多只能這樣多的位元組。",
"apihelp-query+allpages-param-prtype": "僅限受保護的頁面。",
+ "apihelp-query+allpages-param-prlevel": "篩選基於保護級別的保護(必須與 $1prtype= 參數一起使用)。",
"apihelp-query+allpages-param-limit": "要回傳的頁面總數。",
"apihelp-query+allpages-param-dir": "列出時所採用的方向。",
"apihelp-query+allpages-example-B": "顯示以字母 <kbd>B</kbd> 為開頭的所有頁面清單。",
"apihelp-query+allpages-example-generator": "顯示 4 個以 <kbd>T</kbd> 為開頭的頁面之資訊。",
+ "apihelp-query+allpages-example-generator-revisions": "顯示前 2 個以 <kbd>Re</kbd> 為開頭的非重新導向頁面內容。",
"apihelp-query+allredirects-summary": "列出至命名空間的所有重新導向。",
"apihelp-query+allredirects-param-from": "要起始列舉的重新導向標題。",
"apihelp-query+allredirects-param-to": "要終止列舉的重新導向標題。",
"apihelp-query+mystashedfiles-param-limit": "要取得的檔案數量。",
"apihelp-query+alltransclusions-param-from": "要起始列舉的嵌入標題。",
"apihelp-query+alltransclusions-param-to": "要終止列舉的嵌入標題。",
+ "apihelp-query+alltransclusions-param-prefix": "搜尋以此值為開頭的所有嵌入標題。",
"apihelp-query+alltransclusions-param-prop": "要包含到的資訊部份:",
"apihelp-query+alltransclusions-paramvalue-prop-ids": "添加嵌入頁面的頁面 ID(不能與 $1unique 一起使用)。",
"apihelp-query+alltransclusions-paramvalue-prop-title": "添加嵌入的標題。",
"apihelp-query+info-paramvalue-prop-visitingwatchers": "有訪問頁面近期編輯數的各頁面監視者數目,如有允許的話。",
"apihelp-query+info-paramvalue-prop-notificationtimestamp": "各頁面的監視清單通知時間戳記。",
"apihelp-query+info-paramvalue-prop-subjectid": "各對話頁的父頁面頁面 ID。",
- "apihelp-query+info-paramvalue-prop-readable": "使用者是否可閱讀此頁面。",
+ "apihelp-query+info-paramvalue-prop-url": "替各頁面給予一個完整 URL、一個編輯 URL,以及一個規範 URL。",
+ "apihelp-query+info-paramvalue-prop-readable": "使用者是否可閱讀此頁面。請改用 <kbd>intestactions=read</kbd>。",
"apihelp-query+info-paramvalue-prop-preload": "取得由 EditFormPreloadText 回傳的文字。",
"apihelp-query+info-param-testactions": "測試目前使用者是否可執行頁面上的某項操作。",
"apihelp-query+info-param-token": "請改用 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]]。",
"apihelp-query+iwlinks-paramvalue-prop-url": "添加完整的 URL。",
"apihelp-query+iwlinks-param-limit": "要回傳的跨 Wiki 連結數量。",
"apihelp-query+iwlinks-param-prefix": "僅回傳帶有此前綴的跨 wiki 連結。",
+ "apihelp-query+iwlinks-param-title": "要搜尋的跨 wiki 連結。必須與 <var>$1prefix</var> 一起使用。",
"apihelp-query+iwlinks-param-dir": "列出時所採用的方向。",
"apihelp-query+iwlinks-example-simple": "從頁面 <kbd>Main Page</kbd> 取得跨 wiki 連結。",
"apihelp-query+langbacklinks-summary": "找出連結至指定語言連結的所有頁面。",
"apihelp-query+pagepropnames-summary": "列出所有在 wiki 使用的頁面屬性名稱。",
"apihelp-query+pagepropnames-param-limit": "回傳的名稱數量上限。",
"apihelp-query+pagepropnames-example-simple": "取得前 10 個屬性名稱。",
+ "apihelp-query+pageprops-summary": "取得定義在頁面內容的各樣頁面屬性。",
"apihelp-query+pageprops-example-simple": "取得頁面 <kbd>Main Page</kbd> 與 <kbd>MediaWiki</kbd> 的屬性。",
"apihelp-query+pageswithprop-summary": "列出使用到指定頁面屬性的所有頁面。",
"apihelp-query+pageswithprop-param-prop": "要包含到的資訊部份:",
"apihelp-query+siteinfo-paramvalue-prop-namespacealiases": "已註冊命名空間別名清單。",
"apihelp-query+siteinfo-paramvalue-prop-specialpagealiases": "特殊頁面別名清單。",
"apihelp-query+siteinfo-paramvalue-prop-magicwords": "魔術字及其別名清單。",
+ "apihelp-query+siteinfo-paramvalue-prop-usergroups": "回傳使用者群組以及所分配權限。",
"apihelp-query+siteinfo-paramvalue-prop-libraries": "回傳安裝在 wiki 上的函式庫。",
"apihelp-query+siteinfo-paramvalue-prop-extensions": "回傳安裝在 wiki 上的擴充功能。",
+ "apihelp-query+siteinfo-paramvalue-prop-extensiontags": "回傳解析擴充標籤清單。",
+ "apihelp-query+siteinfo-paramvalue-prop-defaultoptions": "回傳用於使用者偏好設定的預設值。",
+ "apihelp-query+siteinfo-paramvalue-prop-uploaddialog": "回傳上傳對話框的設置。",
+ "apihelp-query+siteinfo-param-filteriw": "僅回傳跨 wiki 地圖的本地端或非本地端項目。",
"apihelp-query+siteinfo-param-showalldb": "列出所有資料庫伺服器,不是只有最延遲的那台。",
"apihelp-query+siteinfo-param-numberingroup": "列出在使用者群組裡的使用者數目。",
"apihelp-query+siteinfo-param-inlanguagecode": "用於本地化語言的語言代碼(盡可能)與外觀名稱。",
"apihelp-query+users-example-simple": "返回使用者 <kbd>Example</kbd> 的資訊。",
"apihelp-query+watchlist-param-start": "起始列舉的時間戳記。",
"apihelp-query+watchlist-param-end": "結束列舉的時間戳記。",
+ "apihelp-query+watchlist-param-namespace": "篩選僅為指定命名空間的更改。",
"apihelp-query+watchlist-param-user": "此列出由該使用者作出的更改。",
"apihelp-query+watchlist-param-excludeuser": "不要列出由該使用者作出的更改。",
"apihelp-query+watchlist-param-limit": "每個請求要回傳的結果總數。",
"apihelp-query+watchlist-paramvalue-prop-patrol": "標記編輯為已巡查。",
"apihelp-query+watchlist-paramvalue-prop-autopatrol": "標記編輯為自動巡查。",
"apihelp-query+watchlist-paramvalue-prop-sizes": "添加頁面舊有與新的長度。",
+ "apihelp-query+watchlist-paramvalue-prop-loginfo": "在適當處添加日誌資訊。",
"apihelp-query+watchlist-paramvalue-prop-tags": "列出項目的標籤。",
"apihelp-query+watchlist-param-type": "要顯示的更改類型:",
"apihelp-query+watchlist-paramvalue-type-edit": "一般頁面編輯。",
"apihelp-resetpassword-param-email": "正被重新設定使用者的電子郵件地址。",
"apihelp-resetpassword-example-user": "向使用者 <kbd>Example</kbd> 寄送重新設定密碼用的電子郵件。",
"apihelp-revisiondelete-summary": "刪除和取消刪除修訂。",
+ "apihelp-revisiondelete-param-type": "正執行的修訂刪除類型。",
"apihelp-revisiondelete-param-hide": "各修訂所要隱藏的內容。",
"apihelp-revisiondelete-param-show": "各修訂所要取消隱藏的內容。",
"apihelp-revisiondelete-param-suppress": "是否對管理者及其他使用者禁止資料。",
"apihelp-stashedit-param-contentformat": "用於輸入文字的內容序列化格式。",
"apihelp-stashedit-param-baserevid": "基本修訂的修訂 ID。",
"apihelp-stashedit-param-summary": "更改摘要。",
+ "apihelp-tag-param-revid": "要添加或移除標籤的一個或多個修訂 ID。",
+ "apihelp-tag-param-logid": "要添加或移除標籤的一個或多個日誌項目 ID。",
"apihelp-tag-param-reason": "變更的原因。",
"apihelp-tokens-summary": "取得資料修改動作的密鑰。",
"apihelp-tokens-extended-description": "此模組已因支援 [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] 而停用。",
"apierror-assertnameduserfailed": "斷言使用者「$1」出錯。",
"apierror-assertuserfailed": "斷言使用者已登入失敗。",
"apierror-autoblocked": "您的 IP 位址已經被自動封鎖,因為它曾經被一名已封鎖的使用者使用過。",
+ "apierror-bad-badfilecontexttitle": "在 <var>$1badfilecontexttitle</var> 參數的無效標題。",
+ "apierror-baddiffto": "<var>$1diffto</var> 必須設定成非負值的數字、<kbd>prev</kbd>、<kbd>next</kbd>、或 <kbd>cur</kbd>。",
+ "apierror-badformat-generic": "內容模組 $2 不支援使用請求格式 $1。",
"apierror-badgenerator-notgenerator": "模組 <kbd>$1</kbd> 不能作為產生器。",
"apierror-badgenerator-unknown": "未知的 <kbd>generator=$1</kbd>。",
"apierror-badip": "IP 參數無效。",
"apierror-mustbeloggedin-linkaccounts": "您必須登入到連結帳號。",
"apierror-mustbeloggedin-removeauth": "必須登入,才能移除身分核對資取。",
"apierror-mustbeloggedin": "您必須登入才能$1。",
+ "apierror-nochanges": "沒有請求的更改。",
"apierror-nodeleteablefile": "沒有這樣檔案的舊版本。",
"apierror-noedit-anon": "匿名使用者不可編輯頁面。",
"apierror-noedit": "您沒有權限來編輯頁面。",
"apierror-nosuchsection": "沒有 ID 為 $1 的段落。",
"apierror-nosuchsection-what": "在$2裡沒有段落$1。",
"apierror-nosuchuserid": "沒有 ID 為 $1 的使用者。",
+ "apierror-notpatrollable": "因內容過舊,修訂 r$1 無法巡查。",
"apierror-nouploadmodule": "未設定上傳模組。",
+ "apierror-opensearch-json-warnings": "警告不能以 OpenSearch JSON 格式表示。",
"apierror-pagecannotexist": "命名空間不允許實際頁面。",
"apierror-permissiondenied": "您沒有權限$1。",
"apierror-permissiondenied-generic": "權限不足。",
"apiwarn-deprecation-httpsexpected": "當應為 HTTPS 時,HTTP 要被使用。",
"apiwarn-invalidcategory": "「$1」不是一個分類。",
"apiwarn-invalidtitle": "「$1」不是一個有效標題。",
+ "apiwarn-invalidxmlstylesheet": "指定了無效或不存在的樣式表。",
+ "apiwarn-invalidxmlstylesheetns": "樣式表應在 {{ns:MediaWiki}} 命名空間。",
"apiwarn-notfile": "「$1」不是一個檔案。",
+ "apiwarn-nothumb-noimagehandler": "無法建立縮圖,因為$1沒有相關的圖片處理器。",
"apiwarn-parse-nocontentmodel": "未提供 <var>title</var> 或 <var>contentmodel</var>,應是 $1。",
"apiwarn-tokennotallowed": "「$1」操作不允許目前的使用者。",
+ "apiwarn-unrecognizedvalues": "參數 <var>$1</var> 有無法識別的{{PLURAL:$3|值|值}}:$2。",
+ "apiwarn-unsupportedarray": "參數 <var>$1</var> 使用了不被支援的 PHP 陣列語法。",
"apiwarn-validationfailed-badpref": "不是有效的偏好設定。",
"apiwarn-validationfailed-cannotset": "不能透過此模組設定。",
"apiwarn-validationfailed": "<kbd>$1</kbd>驗證錯誤:$2",
$options = [ $options ];
}
- $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
+ $res = $this->select( $table, [ 'value' => $var ], $cond, $fname, $options, $join_conds );
if ( $res === false ) {
return false;
}
$values = [];
foreach ( $res as $row ) {
- $values[] = $row->$var;
+ $values[] = $row->value;
}
return $values;
'AllMyUploads' => \SpecialAllMyUploads::class,
'PermanentLink' => \SpecialPermanentLink::class,
'Redirect' => \SpecialRedirect::class,
+ 'RedirectExternal' => \SpecialRedirectExternal::class,
'Revisiondelete' => \SpecialRevisionDelete::class,
'RunJobs' => \SpecialRunJobs::class,
'Specialpages' => \SpecialSpecialpages::class,
--- /dev/null
+<?php
+
+/**
+ * Implements Special:RedirectExternal.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * An unlisted special page that accepts a URL as the first argument, and redirects the user to
+ * that page. Example: Special:Redirect/https://mediawiki.org
+ *
+ * At the moment, this is intended to be used by the GrowthExperiments project in order
+ * to track outbound visits to certain external links. But it could be extended in the future to
+ * provide parameters for showing a message to the user before redirecting, or explicitly requiring
+ * a user to click on the link. This can help improve security when users follow on-wiki links to
+ * off-wiki sites.
+ */
+class SpecialRedirectExternal extends UnlistedSpecialPage {
+
+ public function __construct() {
+ parent::__construct( 'RedirectExternal' );
+ }
+
+ /**
+ * @param string $url
+ * @return bool
+ * @throws HttpError
+ */
+ public function execute( $url = '' ) {
+ $dispatch = $this->dispatch( $url );
+ if ( $dispatch->getStatusValue()->isGood() ) {
+ $this->getOutput()->redirect( $url );
+ return true;
+ }
+ throw new HttpError( 400, $dispatch->getMessage() );
+ }
+
+ /**
+ * @param string $url
+ * @return Status
+ */
+ public function dispatch( $url ) {
+ if ( !$url ) {
+ return Status::newFatal( 'redirectexternal-no-url' );
+ }
+ $url = filter_var( $url, FILTER_SANITIZE_URL );
+ if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) {
+ return Status::newFatal( 'redirectexternal-invalid-url', $url );
+ }
+ return Status::newGood();
+ }
+}
"botpasswords-label-appid": "Име на бота:",
"botpasswords-label-create": "Създаване",
"botpasswords-label-update": "Обновяване",
- "botpasswords-label-cancel": "Отказване",
+ "botpasswords-label-cancel": "Отказ",
"botpasswords-label-delete": "Изтриване",
"botpasswords-label-resetpassword": "Възстановяване на парола",
"botpasswords-label-grants": "Приложими разрешения:",
"newimages-hidepatrolled": "Скриване на проверените качвания",
"newimages-mediatype": "Файлов тип:",
"noimages": "Няма нищо.",
+ "gallery-slideshow-toggle": "Превключване на миниатюрите",
"ilsubmit": "Търсене",
"bydate": "по дата",
"sp-newimages-showfrom": "Показване на новите файлове, като се започне от $2, $1",
"feedback-bugcheck": "Страхотно! Само проверете дали това не сред вече [$1 докладваните грешки].",
"feedback-bugnew": "Проверих. Докладвай за нова грешка",
"feedback-bugornote": "Ако сте готови подробно да опишете технически проблем, моля [$1 докладвайте го тук].\nВ противен случай, можете да използвате лесния формуляр по-долу. Коментарът ви ще бъде добавен към страницата „[$3 $2]“, наред с вашето потребителско име.",
- "feedback-cancel": "Отказване",
+ "feedback-cancel": "Отказ",
"feedback-close": "Готово",
"feedback-dialog-title": "Изпращане на обратна връзка",
"feedback-error1": "Грешка: Неразпознат резултат от API",
"lag-warn-normal": "Changes newer than $1 {{PLURAL:$1|second|seconds}} may not be shown in this list.",
"lag-warn-high": "Due to high database server lag, changes newer than $1 {{PLURAL:$1|second|seconds}} may not be shown in this list.",
"editwatchlist-summary": "",
+ "redirectexternal-summary": "",
+ "redirectexternal-invalid-url": "$1 is not a valid URL",
+ "redirectexternal-no-url": "No argument was provided to Special:RedirectExternal",
"watchlistedit-normal-title": "Edit watchlist",
"watchlistedit-normal-legend": "Remove titles from watchlist",
"watchlistedit-normal-explain": "Titles on your watchlist are shown below.\nTo remove a title, check the box next to it, and click \"{{int:Watchlistedit-normal-submit}}\".\nYou can also [[Special:EditWatchlist/raw|edit the raw list]].",
"tog-watchdefault": "صفحهها و پروندههایی که ویرایش میکنم به فهرست پیگیریهای من افزوده شود",
"tog-watchmoves": "صفحهها و پروندههایی که منتقل میکنم به فهرست پیگیریهای من افزوده شود",
"tog-watchdeletion": "صفحهها و پروندههایی که حذف میکنم به فهرست پیگیریهای من افزوده شود",
- "tog-watchuploads": "پروندههای جدیدی که بارگذاری میکنم به فهرست پیگیری من افزوده شود",
+ "tog-watchuploads": "پروندههای جدیدی که بارگذاری میکنم به فهرست پیگیریهای من افزوده شود",
"tog-watchrollback": "افزودن صفحاتی که واگردانی میکنم به فهرست پیگیریهای من",
- "tog-minordefault": "همهٔ ویرایشها به طور پیشفرض به عنوان «جزئی» علامت زده شود",
+ "tog-minordefault": "همهٔ ویرایشها به طور پیشفرض به عنوان «جزئی» علامت زده شوند",
"tog-previewontop": "پیشنمایش بالای جعبهٔ ویرایش نمایش داده شود",
"tog-previewonfirst": "پیشنمایش هنگام اولین ویرایش نمایش داده شود",
"tog-enotifwatchlistpages": "اگر صفحه یا پروندهای از فهرست پیگیریهایم ویرایش شد به من ایمیلی فرستاده شود",
"tog-watchlisthideminor": "ویرایشهای جزئی در فهرست پیگیریها پنهان شود",
"tog-watchlisthideliu": "ویرایشهای کاربران وارد شده به سامانه در فهرست پیگیریها پنهان شود",
"tog-watchlistreloadautomatically": "زمانی که یک پالایه تغییر کرد فهرست پیگیری به صورت خودکار به روز شود (نیازمند جاوااسکریپت)",
- "tog-watchlistunwatchlinks": "اÙ\81زÙ\88دÙ\86 Ù\85شخصâ\80\8cÚ©Ù\86Ù\86دÙ\87اÛ\8c عدÙ\85 Ù¾Û\8cÚ¯Û\8cرÛ\8c/Ù¾Û\8cÚ¯Û\8cرÛ\8c ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) بÙ\87 صÙ\81Øات Ù¾Û\8cÚ¯Û\8cرÛ\8c داراÛ\8c تغÛ\8cÛ\8cرات (جاÙ\88اسکرÛ\8cپت Ù\85Ù\85Ú©Ù\86 است Ù\86Û\8cاز Ø´Ù\88د)",
+ "tog-watchlistunwatchlinks": "اÙ\81زÙ\88دÙ\86 Ù\85شخصâ\80\8cÚ©Ù\86Ù\86دÙ\87اÛ\8c عدÙ\85 Ù¾Û\8cÚ¯Û\8cرÛ\8c/Ù¾Û\8cÚ¯Û\8cرÛ\8c ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) بÙ\87 صÙ\81Øات Ù¾Û\8cÚ¯Û\8cرÛ\8c داراÛ\8c تغÛ\8cÛ\8cرات (براÛ\8c عÙ\85Ù\84Û\8cات خاÙ\85Ù\88Ø´â\80\8c Ù\88 رÙ\88Ø´Ù\86 کردÙ\86Ø\8c جاÙ\88اسکرÛ\8cپت Ù\86Û\8cاز است)",
"tog-watchlisthideanons": "ویرایشهای کاربران ناشناس در فهرست پیگیریها پنهان شود",
"tog-watchlisthidepatrolled": "ویرایشهای گشتخورده در فهرست پیگیریها پنهان شود",
"tog-watchlisthidecategorization": "نهفتن ردهبندی صفحهها",
"@metadata": {
"authors": [
"Ninjastrikers",
- "Rul1902"
+ "Rul1902",
+ "Sawmw"
]
},
"underline-always": "ကိုဲၜၠင်",
"searchbutton": "အင်းၰူ့",
"go": "လေဝ်",
"searcharticle": "လေဝ်",
- "history": "á\80\83á\80½á\80¾á\80¬á\80\94á\80·်မေင်ႋစိင်",
+ "history": "á\80\9cá\80á\80\99á\80±á\80¶á\81\9cá\81 á\80¬်မေင်ႋစိင်",
"history_short": "မေင်ႋစိင်",
"history_small": "မေင်ႋစိင်",
"updatedmarker": "လေဝ်ယောဝ်ႋဝေ့အ်ုလါင်ခါင့်ခါ့ အင်းတင်ထဝေ့",
"botpasswords-updated-title": "Bota parole atjaunināta",
"botpasswords-deleted-title": "Bota parole dzēsta",
"botpasswords-restriction-failed": "Botu paroles ierobežojumi liedz šo pieslēgšanos.",
+ "botpasswords-not-exist": "Lietotājam \"$1\" nav bota paroles ar nosaukumu \"$2\".",
"resetpass_forbidden": "Paroles nav iespējams nomainīt",
"resetpass_forbidden-reason": "Paroles nav iespējams nomainīt: $1",
"resetpass-no-info": "Jums ir nepieciešams ieiet, lai tūlīt piekļūtu šai lapai.",
"diff-multi-otherusers": "({{PLURAL:$1|$1 starpversijas|Viena starpversija|$1 starpversijas}}, ko {{PLURAL:$2|saglabājuši|saglabājis|saglabājuši}} {{PLURAL:$2|$2 lietotāji|viens cits lietotājs|$2 lietotāji}}, nav parādīta{{PLURAL:$1||s}})",
"diff-multi-manyusers": "({{PLURAL:$1|$1 starpversijas|$1 starpversija|$1 starpversijas}}, ko saglabājuši vairāk nekā $2 {{PLURAL:$2|lietotāji|lietotājs|lietotāji}}, nav parādīta{{PLURAL:$1||s}})",
"searchresults": "Meklēšanas rezultāti",
+ "search-filter-title-prefix": "Meklē tikai lapas, kuru nosaukums sākas ar \"$1\"",
+ "search-filter-title-prefix-reset": "Meklēt visas lapas",
"searchresults-title": "Meklēšanas rezultāti \"$1\"",
"titlematches": "Rezultāti virsrakstos",
"textmatches": "Rezultāti lapu tekstos",
"http-timed-out": "HTTP pieprasījumam ir iestājies noilgums.",
"http-curl-error": "Kļūda, nolasot URL: $1",
"http-bad-status": "HTTP pieprasījuma laikā atgadījās problēma: $1 $2",
+ "http-internal-error": "HTTP iekšējā kļūda.",
"upload-curl-error6": "URL nevarēja sasniegt",
"upload-curl-error28": "Augšupielādes noildze",
"license": "Licence:",
"protectedpages-noredirect": "Paslēpt pāradresācijas",
"protectedpages-timestamp": "Laika zīmogs",
"protectedpages-page": "Lapa",
+ "protectedpages-expiry": "Beidzas",
+ "protectedpages-performer": "Aizsargājušais lietotājs",
"protectedpages-params": "Aizsardzības parametri",
"protectedpages-reason": "Iemesls",
"protectedpages-submit": "Parādīt lapas",
"nimagelinks": "Used on [[Special:MostLinkedFiles]] to indicate how often a specific file is used.\n\nParameters:\n* $1 - number of pages\nSee also:\n* {{msg-mw|Ntransclusions}}",
"ntransclusions": "Used on [[Special:MostTranscludedPages]] to indicate how often a template is in use.\n\nParameters:\n* $1 - number of pages\nSee also:\n* {{msg-mw|Nimagelinks}}",
"specialpage-empty": "Used on a special page when there is no data. For example on [[Special:Unusedimages]] when all images are used.",
+ "redirectexternal-summary": "{{doc-specialpagessummary|redirectexternal}}",
+ "redirectexternal-invalid-url": "Error message shown when the argument to [[Special:RedirectExternal]] is an invalid URL.\n\nParameters:\n* $1 - The first URL argument to Special:RedirectExternal",
+ "redirectexternal-no-url": "Error message shown when no argument is supplied to [[Special:RedirectExternal]]",
"lonelypages": "{{doc-special|LonelyPages}}",
"lonelypages-summary": "{{doc-specialpagesummary|lonelypages}}",
"lonelypagestext": "Text displayed in [[Special:LonelyPages]]",
"tool-link-userrights": "Промена {{GENDER:$1|корисничких}} групе",
"tool-link-userrights-readonly": "Приказ {{GENDER:$1|корисничких}} група",
"tool-link-emailuser": "Слање имејла {{GENDER:$1|кориснику|корисници}}",
- "imagepage": "Погледај страницу датотеке",
- "mediawikipage": "Погледај страницу поруке",
- "templatepage": "Погледај страницу шаблона",
- "viewhelppage": "Погледај страницу помоћи",
- "categorypage": "Погледај страницу категорије",
+ "imagepage": "Прикажи страницу датотеке",
+ "mediawikipage": "Прикажи страницу поруке",
+ "templatepage": "Прикажи страницу шаблона",
+ "viewhelppage": "Прикажи страницу помоћи",
+ "categorypage": "Прикажи страницу категорије",
"viewtalkpage": "Прикажи дискусију",
"otherlanguages": "На другим језицима",
"redirectedfrom": "(преусмерено са $1)",
"privacypage": "Project:Политика приватности",
"badaccess": "Грешка у дозволама",
"badaccess-group0": "Није вам дозвољено да извршите радњу коју сте захтевали.",
- "badaccess-groups": "Радња коју сте захтевали је ограничена само корисницима у {{PLURAL:$2|следећој групи|следећим групама}}: $1.",
+ "badaccess-groups": "Радња коју сте захтевали је ограничена на кориснике из {{PLURAL:$2|следеће групе|једне од следећих група}}: $1.",
"versionrequired": "Потребна је верзија $1 Медијавикија",
"versionrequiredtext": "Потребна је верзија $1 Медијавикија да бисте користили ову страницу.\nПогледајте страницу [[Special:Version|верзије]].",
"ok": "У реду",
"dellogpagetext": "Aşağıda en son silme işlemlerinin bir listesi bulunmaktadır.",
"deletionlog": "silme günlüğü",
"log-name-create": "Sayfa oluşturma günlüğü",
+ "log-description-create": "Aşağıda en son yeni sayfa oluşturma işlemlerinin bir listesi bulunmaktadır.",
"logentry-create-create": "$1, $3 adlı sayfayı {{GENDER:$2|oluşturdu}}",
"reverted": "Önceki sürüm geri getirildi",
"deletecomment": "Neden:",
"log-action-filter-upload-upload": "Yeni yükleme",
"log-action-filter-upload-overwrite": "Yeniden yükle",
"authmanager-authplugin-setpass-bad-domain": "Geçersiz alanadı.",
+ "authmanager-autocreate-noperm": "Otomatik kullanıcı oluşturma izni yok.",
"authmanager-userdoesnotexist": "\"$1\" kullanıcı hesabı kayıtlı değil.",
"authmanager-email-label": "E-posta",
"authmanager-email-help": "E-posta adresi",
"right-importupload": "імпорт сторінок через завантаження файлів",
"right-patrol": "позначення редагувань патрульованими",
"right-autopatrol": "автоматичне позначення редагувань патрульованими",
- "right-patrolmarks": "Ð\9fеÑ\80еглÑ\8fд паÑ\82Ñ\80Ñ\83лÑ\8cованиÑ\85 Ñ\81Ñ\82оÑ\80Ñ\96нок у нових редагуваннях",
+ "right-patrolmarks": "пеÑ\80еглÑ\8fд познаÑ\87ок паÑ\82Ñ\80Ñ\83лÑ\8eваннÑ\8f у нових редагуваннях",
"right-unwatchedpages": "перегляд списку сторінок, за якими ніхто не спостерігає",
"right-mergehistory": "об'єднання історій редагувань сторінок",
"right-userrights": "зміна всіх прав користувачів",
'Recentchanges' => [ 'RecentChanges' ],
'Recentchangeslinked' => [ 'RecentChangesLinked', 'RelatedChanges' ],
'Redirect' => [ 'Redirect' ],
+ 'RedirectExternal' => [ 'RedirectExternal' ],
'RemoveCredentials' => [ 'RemoveCredentials' ],
'ResetTokens' => [ 'ResetTokens' ],
'Revisiondelete' => [ 'RevisionDelete' ],
}
}, this )
.next( function () {
- var plainMsg, parsedMsg,
+ var $link,
settings = data.settings;
data.contents = data.contents || {};
this.useragentMandatory = settings.useragentCheckbox.mandatory;
this.useragentFieldLayout.toggle( settings.useragentCheckbox.show );
- // HACK: Setting a link in the messages doesn't work. There is already a report
- // about this, and the bug report offers a somewhat hacky work around that
- // includes setting a separate message to be parsed.
- // We want to make sure the user can configure both the title of the page and
- // a separate url, so this must be allowed to parse correctly.
- // See https://phabricator.wikimedia.org/T49395#490610
- mw.messages.set( {
- 'feedback-dialog-temporary-message':
- '<a href="' + this.feedbackPageUrl + '" target="_blank">' + this.feedbackPageName + '</a>'
- } );
- plainMsg = mw.message( 'feedback-dialog-temporary-message' ).plain();
- mw.messages.set( { 'feedback-dialog-temporary-message-parsed': plainMsg } );
- parsedMsg = mw.message( 'feedback-dialog-temporary-message-parsed' );
+ $link = $( '<a>' )
+ .attr( 'href', this.feedbackPageUrl )
+ .attr( 'target', '_blank' )
+ .text( this.feedbackPageName );
this.feedbackMessageLabel.setLabel(
- // Double-parse
- $( '<span>' )
- .append( mw.message( 'feedback-dialog-intro', parsedMsg ).parse() )
+ mw.message( 'feedback-dialog-intro', $link ).parseDom()
);
this.validateFeedbackForm();
$this->assertLastSql( 'BEGIN; SELECT 1; ROLLBACK' );
$this->assertEquals( 0, $this->database->trxLevel() );
}
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::selectFieldValues()
+ */
+ public function testSelectFieldValues() {
+ $this->database->forceNextResult( [
+ (object)[ 'value' => 'row1' ],
+ (object)[ 'value' => 'row2' ],
+ (object)[ 'value' => 'row3' ],
+ ] );
+
+ $this->assertSame(
+ [ 'row1', 'row2', 'row3' ],
+ $this->database->selectFieldValues( 'table', 'table.field', 'conds', __METHOD__ )
+ );
+ $this->assertLastSql( 'SELECT table.field AS value FROM table WHERE conds' );
+ }
}
$this->assertSame( $oldPrefix, $this->db->tablePrefix() );
$this->assertSame( $oldDomain, $this->db->getDomainId() );
}
+
}
--- /dev/null
+<?php
+
+/**
+ * Test class for SpecialRedirectExternal class.
+ *
+ * @license GPL-2.0-or-later
+ */
+class SpecialRedirectExternalTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideDispatch
+ * @covers SpecialRedirectExternal::dispatch
+ * @covers SpecialRedirectExternal
+ * @param $url
+ * @param $expectedStatus
+ */
+ public function testDispatch( $url, $expectedStatus ) {
+ $page = new SpecialRedirectExternal();
+ $this->assertEquals( $expectedStatus, $page->dispatch( $url )->isGood() );
+ }
+
+ /**
+ * @throws HttpError
+ * @expectedException HttpError
+ * @expectedExceptionMessage asdf is not a valid URL
+ * @covers SpecialRedirectExternal::execute
+ */
+ public function testExecuteInvalidUrl() {
+ $page = new SpecialRedirectExternal();
+ $page->execute( 'asdf' );
+ }
+
+ /**
+ * @throws HttpError
+ * @covers SpecialRedirectExternal::execute
+ */
+ public function testValidUrl() {
+ $page = new SpecialRedirectExternal();
+ $this->assertTrue( $page->execute( 'https://www.mediawiki.org' ) );
+ }
+
+ public static function provideDispatch() {
+ return [
+ [ 'asdf', false ],
+ [ null, false ],
+ [ 'https://www.mediawiki.org?test=1', true ],
+ ];
+ }
+}