├── .github └── workflows │ └── pull-request-checks.yml ├── .gitignore ├── .markdownlint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── GOVERNANCE.md ├── LICENSE.md ├── README.md ├── USAGE.md ├── Wikimate.php ├── composer.json ├── composer.lock └── examples.php /.github/workflows/pull-request-checks.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Checks" 2 | on: pull_request 3 | jobs: 4 | # Ensure that the changelog file has been updated in this PR 5 | check-changelog-change: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: dangoslen/changelog-enforcer@v3 9 | # Ensure that markdown files are formatted consistently (according to .markdownlint.yml) 10 | lint-markdown-files: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Fetch the Wikimate code into the Actions runner 14 | uses: actions/checkout@v3 15 | - name: Install the markdownlint-problem-matcher action 16 | uses: xt0rted/markdownlint-problem-matcher@v2 17 | - name: Install markdownlint-cli 18 | run: npm install -g markdownlint-cli 19 | - name: List files that will be processed 20 | run: npx --silent glob '**/*.md' 21 | - name: Run markdownlint 22 | run: markdownlint '**/*.md' 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | wikimate_cookie.txt 2 | nbproject/private 3 | vendor 4 | -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | # Default state for all rules 2 | default: true 3 | 4 | # MD004/ul-style 5 | MD004: 6 | # Use dashes for lists, leaving asterisks for emphasis 7 | style: "dash" 8 | 9 | # MD010/no-hard-tabs 10 | MD010: 11 | # Exclude code blocks 12 | code_blocks: false 13 | 14 | # MD013/line-length 15 | MD013: 16 | # Custom length limit (default is 80) 17 | line_length: 100 18 | # Exclude code blocks 19 | code_blocks: false 20 | 21 | # MD024/no-duplicate-heading 22 | MD024: 23 | # Only check sibling headings 24 | siblings_only: true 25 | 26 | # MD026/no-trailing-punctuation 27 | MD026: 28 | # Trailing punctuation characters not allowed in headings 29 | punctuation: ",;:!" 30 | 31 | # MD052/reference-links-images 32 | MD052: 33 | # Validate shortcut-style links 34 | shortcut_syntax: true 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This page lists the changes that were done in each version of Wikimate. 4 | 5 | Since v0.10.0 this project adheres to [Semantic Versioning](https://semver.org/) 6 | and [Keep a Changelog](https://keepachangelog.com/). 7 | 8 | ## Upcoming version 9 | 10 | ### Changed 11 | 12 | - Clarified instructions in `GOVERNANCE.md` for releasing new versions ([#157]) 13 | - Updated API URLs to typical path, updated other links, and use https ([#163]) 14 | - Updated versions of GitHub Actions dependencies ([#161]) 15 | - Fixed markdownlint execution in the CI pipeline ([#154]) 16 | 17 | 18 | [#154]: https://github.com/hamstar/Wikimate/pull/154 19 | [#157]: https://github.com/hamstar/Wikimate/pull/157 20 | [#161]: https://github.com/hamstar/Wikimate/pull/161 21 | [#163]: https://github.com/hamstar/Wikimate/pull/163 22 | 23 | ## Version 1.1.0 - 2023-07-30 24 | 25 | ### Added 26 | 27 | - Added internal mechanism to handle version-dependent parameters of API modules ([#151]) 28 | - Support version-dependent `deletetalk` parameter in `WikiPage::delete()` and `WikiFile::delete()` ([#152]) 29 | - New method `Wikimate::undelete()` ([#153], [#155]) 30 | 31 | ### Changed 32 | 33 | - Applied code formatting rules from [PSR-12](https://www.php-fig.org/psr/psr-12/) ([#142]) 34 | - Resolved static analysis warnings reported by [PHPStan](https://phpstan.org/) & 35 | [PHPMD](https://phpmd.org/) ([#143]) 36 | - Clarified error response for edits denied by a CAPTCHA ([#145]) 37 | - Activated default markdownlint rules ([#146]) 38 | - Updated dependency on rmccue/requests to Version 2.x (fixes PHP 8.1 deprecation warnings) ([#147]) 39 | 40 | ### Fixed 41 | 42 | - Fixed example not using correct array key for invalid login errors ([#148]) 43 | - Prevented warning in `Wikimate::logout()` about `token` parameter that was unsupported 44 | before MediaWiki v1.34 ([#151]) 45 | - Improved various code comments ([#149]) 46 | - Improved more PHPDoc comments ([#150]) 47 | 48 | 49 | [#142]: https://github.com/hamstar/Wikimate/pull/142 50 | [#143]: https://github.com/hamstar/Wikimate/pull/143 51 | [#145]: https://github.com/hamstar/Wikimate/pull/145 52 | [#146]: https://github.com/hamstar/Wikimate/pull/146 53 | [#147]: https://github.com/hamstar/Wikimate/pull/147 54 | [#148]: https://github.com/hamstar/Wikimate/pull/148 55 | [#149]: https://github.com/hamstar/Wikimate/pull/149 56 | [#150]: https://github.com/hamstar/Wikimate/pull/150 57 | [#151]: https://github.com/hamstar/Wikimate/pull/151 58 | [#152]: https://github.com/hamstar/Wikimate/pull/152 59 | [#153]: https://github.com/hamstar/Wikimate/pull/153 60 | [#155]: https://github.com/hamstar/Wikimate/pull/155 61 | 62 | ## Version 1.0.0 - 2021-09-05 63 | 64 | ### Added 65 | 66 | - New exception class `WikimateException` for API communication errors ([#136]) 67 | - Usage documentation about maximum lag and retries ([#134]) 68 | - New GitHub Action to enforce updates to `CHANGELOG.md` ([#131]) 69 | - New `CONTRIBUTING.md` file with contribution guidelines ([#135]) 70 | - New GitHub Action to check markdown files ([#138]) 71 | 72 | ### Changed 73 | 74 | - Centralized API communication checks in `WikiPage::request()` ([#136]) 75 | - Centralized debug logging of API requests/responses in `WikiPage::request()` ([#139]) 76 | - Rewrote installation and invocation instructions using Composer ([#140]) 77 | - Added additional context to `README.md` ([#127]) 78 | - Added semi-linear merge recommendation to `GOVERNANCE.md` ([#130]) 79 | 80 | _The following two entries are backwards incompatible API changes 81 | and may require changes in applications that invoke these methods:_ 82 | 83 | - Error return values for `WikiPage::getSection()` changed from `false` to `null` ([#129]) 84 | - `Wikimate::login()` error code `'login'` is now `'auth'`, also used by `logout()` ([#132]) 85 | 86 | ### Fixed 87 | 88 | - Fixed one error return value in `WikiPage::setText()` ([#129]) 89 | - Fixed exception type/message for `$keyNames` parameter to `WikiPage::getAllSections()` ([#133]) 90 | 91 | ### Removed 92 | 93 | - Method `Wikimate::debugCurlConfig()`, deprecated since v0.10.0 ([#128]) 94 | - File `globals.php`, replaced by expanded Composer instructions ([#140]) 95 | 96 | 97 | [#127]: https://github.com/hamstar/Wikimate/pull/127 98 | [#128]: https://github.com/hamstar/Wikimate/pull/128 99 | [#129]: https://github.com/hamstar/Wikimate/pull/129 100 | [#130]: https://github.com/hamstar/Wikimate/pull/130 101 | [#131]: https://github.com/hamstar/Wikimate/pull/131 102 | [#132]: https://github.com/hamstar/Wikimate/pull/132 103 | [#133]: https://github.com/hamstar/Wikimate/pull/133 104 | [#134]: https://github.com/hamstar/Wikimate/pull/134 105 | [#135]: https://github.com/hamstar/Wikimate/pull/135 106 | [#136]: https://github.com/hamstar/Wikimate/pull/136 107 | [#138]: https://github.com/hamstar/Wikimate/pull/138 108 | [#139]: https://github.com/hamstar/Wikimate/pull/139 109 | [#140]: https://github.com/hamstar/Wikimate/pull/140 110 | 111 | ## Version 0.15.0 - 2021-08-26 112 | 113 | ### Added 114 | 115 | - New methods `WikiFile::revert()` and `Wikimate::filerevert()` ([#123]) 116 | - New method `Wikimate::logout()` ([#124]) 117 | - Added post-release update steps to `GOVERNANCE.md` ([#125]) 118 | 119 | ### Changed 120 | 121 | - Updated `Wikimate::token()` to remember CSRF token and reduce API calls ([#122]) 122 | 123 | ### Fixed 124 | 125 | - Fixed format of user agent string ([#121]) 126 | 127 | 128 | [#121]: https://github.com/hamstar/Wikimate/pull/121 129 | [#122]: https://github.com/hamstar/Wikimate/pull/122 130 | [#123]: https://github.com/hamstar/Wikimate/pull/123 131 | [#124]: https://github.com/hamstar/Wikimate/pull/124 132 | [#125]: https://github.com/hamstar/Wikimate/pull/125 133 | 134 | ## Version 0.14.0 - 2021-08-24 135 | 136 | ### Added 137 | 138 | - Support for the maxlag parameter (with retries) in API requests ([#112]) 139 | - Support for getting/setting user agent for API requests ([#107]) 140 | - Added missing PHPDoc comments for properties, constants, and more ([#109]) 141 | 142 | ### Changed 143 | 144 | - Changed API requests from deprecated PHP format to JSON format ([#111]) 145 | - Grouped sections and added table of contents in `USAGE.md` ([#108]) 146 | 147 | ### Fixed 148 | 149 | - Removed null returns from destructors & fixed PHPDoc comments ([#114]) 150 | - Fixed sections object initialization warning in PHP 7.4+ ([#118]) 151 | 152 | 153 | [#107]: https://github.com/hamstar/Wikimate/pull/107 154 | [#108]: https://github.com/hamstar/Wikimate/pull/108 155 | [#109]: https://github.com/hamstar/Wikimate/pull/109 156 | [#111]: https://github.com/hamstar/Wikimate/pull/111 157 | [#112]: https://github.com/hamstar/Wikimate/pull/112 158 | [#114]: https://github.com/hamstar/Wikimate/pull/114 159 | [#118]: https://github.com/hamstar/Wikimate/pull/118 160 | 161 | ## Version 0.13.0 - 2021-07-05 162 | 163 | ### Added 164 | 165 | - Added more debug logging of MediaWiki requests and responses ([#101], [#106]) 166 | - New `GOVERNANCE.md` file to explicitly codify the project management principles 167 | and provide guidelines for maintenance tasks ([#83], [#105]) 168 | 169 | ### Changed 170 | 171 | - Modernized token handling for login and data-modifying actions. 172 | Requires MediaWiki v1.27 or newer. ([#100], [#106]) 173 | 174 | ### Fixed 175 | 176 | - Prevented PHP notice in `WikiFile::getInfo()` for moved or deleted file ([#85]) 177 | - Fixed capitalization of a built-in PHP class in a comment ([#106]) 178 | 179 | 180 | [#83]: https://github.com/hamstar/Wikimate/pull/83 181 | [#85]: https://github.com/hamstar/Wikimate/pull/85 182 | [#100]: https://github.com/hamstar/Wikimate/pull/100 183 | [#101]: https://github.com/hamstar/Wikimate/pull/101 184 | [#105]: https://github.com/hamstar/Wikimate/pull/105 185 | [#106]: https://github.com/hamstar/Wikimate/pull/106 186 | 187 | ## Version 0.12.0 - 2017-02-03 188 | 189 | ### Added 190 | 191 | - New class WikiFile to retrieve properties of a file, and download and upload its contents. 192 | All properties pertain to the current revision of the file, or a specific older revision. 193 | ([#69], [#71], [#78], [#80]) 194 | - WikiFile also provides the file history 195 | and the ability to delete a file or an older revision of it ([#76]) 196 | 197 | 198 | [#69]: https://github.com/hamstar/Wikimate/pull/69 199 | [#71]: https://github.com/hamstar/Wikimate/pull/71 200 | [#76]: https://github.com/hamstar/Wikimate/pull/76 201 | [#78]: https://github.com/hamstar/Wikimate/pull/78 202 | [#80]: https://github.com/hamstar/Wikimate/pull/80 203 | 204 | ## Version 0.11.0 - 2016-11-16 205 | 206 | ### Added 207 | 208 | - Support for a section name (in addition to an index) 209 | in `WikiPage::setText()` and `WikiPage::setSection()` ([#45]) 210 | - Support for optional domain at authentication ([#28]) 211 | 212 | ### Changed 213 | 214 | - Updated `WikiPage::getSection()` to include subsections by default; 215 | disabling the new `$includeSubsections` option reverts to the old behavior 216 | of returning only the text until the first subsection ([#55]) 217 | - Improved section processing in `WikiPage::getText()` ([#33], [#37], [#50]) 218 | - Ensured that MediaWiki API error responses appear directly in `WikiPage::$error` 219 | rather than a nested 'error' array. 220 | This may require changes in your application's error handling ([#63]) 221 | - Restructured and improved documentation ([#32], [#34], [#47], [#49], [#61]) 222 | 223 | ### Fixed 224 | 225 | - Ensured use of Wikimate user agent by _Requests_ library ([#64]) 226 | - Corrected handling an invalid page title ([#57]) 227 | - Fixed returning an empty section without header in `WikiPage::getSection()` ([#52]) 228 | - Prevented PHP Notices in several methods ([#43], [#67]) 229 | - Corrected handling an unknown section parameter in `WikiPage::getSection()` ([#41]) 230 | - Fixed passing the return value in `WikiPage::setSection()` ([#30]) 231 | - Corrected call to `Wikimate::debugRequestsConfig()` ([#30]) 232 | 233 | 234 | [#28]: https://github.com/hamstar/Wikimate/pull/28 235 | [#30]: https://github.com/hamstar/Wikimate/pull/30 236 | [#32]: https://github.com/hamstar/Wikimate/pull/32 237 | [#33]: https://github.com/hamstar/Wikimate/pull/33 238 | [#34]: https://github.com/hamstar/Wikimate/pull/34 239 | [#37]: https://github.com/hamstar/Wikimate/pull/37 240 | [#41]: https://github.com/hamstar/Wikimate/pull/41 241 | [#43]: https://github.com/hamstar/Wikimate/pull/43 242 | [#45]: https://github.com/hamstar/Wikimate/pull/45 243 | [#47]: https://github.com/hamstar/Wikimate/pull/47 244 | [#49]: https://github.com/hamstar/Wikimate/pull/49 245 | [#50]: https://github.com/hamstar/Wikimate/pull/50 246 | [#52]: https://github.com/hamstar/Wikimate/pull/52 247 | [#55]: https://github.com/hamstar/Wikimate/pull/55 248 | [#57]: https://github.com/hamstar/Wikimate/pull/57 249 | [#61]: https://github.com/hamstar/Wikimate/pull/61 250 | [#63]: https://github.com/hamstar/Wikimate/pull/63 251 | [#64]: https://github.com/hamstar/Wikimate/pull/64 252 | [#67]: https://github.com/hamstar/Wikimate/pull/67 253 | 254 | ## Version 0.10.0 - 2014-06-24 255 | 256 | ### Changed 257 | 258 | - Switched to using the _Requests_ library instead of Curl ([#25]) 259 | 260 | 261 | [#25]: https://github.com/hamstar/Wikimate/pull/25 262 | 263 | ## Version 0.9 - 2014-06-13 264 | 265 | - Bumped version for stable release 266 | 267 | ## Version 0.5 - 2011-09-09 268 | 269 | - Removed the use of constants in favour of constructor arguments 270 | - Added checks that throw an exception if can't write to wikimate_cookie.txt 271 | - Throws exception if curl library not loaded 272 | - Throws exception if can't login 273 | 274 | ## Version 0.4 - 2011-01-15 275 | 276 | - Added `WikiPage::newSection()` and `WikiPage::setSection()` (shortcuts to `WikiPage::setText()`) 277 | - Added the ability to get individual sections of the article with `WikiPage::getSection()` 278 | - Added the ability to get all sections in an array with `WikiPage::getAllSections()` 279 | - Added the ability to get an array showing section offsets and lengths in the page wikicode 280 | with `WikiPage::getSectionOffsets()` 281 | - Added the ability to see how many sections are on a page with `WikiPage::getNumSections()` 282 | 283 | ## Version 0.3 - 2010-12-26 284 | 285 | - Initial commit 286 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | The Wikimate project welcomes contributions of [all kinds](https://allcontributors.org/docs/en/emoji-key), 4 | no matter how small! 5 | Feel free to open [an issue](https://github.com/hamstar/Wikimate/issues/new) 6 | to discuss your suggestion or request, 7 | or submit your changes directly as a 8 | [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) 9 | if you prefer. 10 | 11 | Below are some guidelines that might help you prepare your contribution. 12 | 13 | 1. **Limit pull requests limited to a single feature or bugfix.** 14 | Unrelated changes should be sent as separate PRs. 15 | The exception are minor cleanup changes, 16 | which can be included (as a separate commit) in the PR that prompted them. 17 | 2. **Use atomic commits**. 18 | Try to create commits that are as small as possible 19 | while still representing a self-contained set of changes, 20 | and use descriptive commit messages. 21 | We recommend [these principles](https://cbea.ms/git-commit/#seven-rules) 22 | for writing great commit messages (but they're not enforced, so don't sweat it 🙂). 23 | 3. **Include a `CHANGELOG.md` entry in every pull request**. 24 | This makes it much easier to prepare releases, 25 | and allows the author of each change to properly summarize it. 26 | 4. **Update the documentation in `USAGE.md` if necessary**. 27 | When writing prose, break lines [semantically](https://rhodesmill.org/brandon/2012/one-sentence-per-line/). 28 | We don't have a strict maximum line length 29 | (especially since things like long URLs can easily surpass them), 30 | but you should start thinking about breaking lines 31 | once they're approaching 100 characters in length. 32 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # Governance 2 | 3 | This page contains guidelines for current and prospective maintainers. 4 | 5 | ## Process transparency and contributor onboarding 6 | 7 | The Wikimate project strives for a transparent and welcoming maintenance process. 8 | Discussions are expected to happen in public channels, via issues or pull requests, 9 | including proposals to change this document or the [contribution guidelines](CONTRIBUTING.md). 10 | 11 | Regular contributors are to be given collaborator status 12 | (which includes commit access, the ability to merge PRs, make releases, etc.). 13 | Note that at the moment this can only be done by the repository owner, 14 | [@hamstar](https://github.com/hamstar). 15 | Contributors that have had a few PRs merged are encouraged to ask for collaborator access, 16 | if they would like to help maintaining the repository. 17 | 18 | ## Guidelines for pull requests 19 | 20 | 1. **Every change should be made via pull requests**. 21 | Do not make commits directly to the `master` branch 22 | (except for very minor ones such as spelling fixes). 23 | 2. **Maintainers should not merge their own pull requests**. 24 | This allows every change to be validated by at least another maintainer. 25 | That said, if there's no input by other maintainers in over a week, 26 | the maintainer who authored the pull request can merge their own PR. 27 | 3. **Pull requests should be rebased before merging**. 28 | The merge should be done with the "Create a merge commit" option. 29 | This allows preserving individual atomic commits while keeping them grouped per PR, 30 | and avoiding crossing branches in the git history, which becomes a 31 | [semi-linear graph](https://devblogs.microsoft.com/devops/pull-requests-with-rebase/#semi-linear-merge). 32 | 33 | Note: when creating or handling pull requests, 34 | make sure their contents follow the [contribution guidelines](CONTRIBUTING.md). 35 | 36 | ## Process for releasing a new version of Wikimate 37 | 38 | Create a pull request with all relevant changes to update the repository for the upcoming release. 39 | (See [#126](https://github.com/hamstar/Wikimate/pull/126) for an example.) 40 | It should apply the following actions: 41 | 42 | 1. Change the "Upcoming version" heading in the `CHANGELOG.md` file 43 | to the appropriate version number and date (e.g. `Version 1.2.3 - 2020-12-31`). 44 | Follow the [SemVer](https://semver.org/) conventions 45 | to determine which part of the version number to increase. 46 | 2. Still in the `CHANGELOG.md` file, 47 | add a new "Upcoming version" section heading above the other sections, 48 | with the contents "No changes yet." 49 | 3. Edit `README.md` and replace all references 50 | to the previous version number and release date 51 | with the corresponding data for the new version. 52 | 4. Update all version references in `Wikimate.php` 53 | to the new version. 54 | 55 | Once this PR is merged, create a new git tag and its associated release 56 | in 57 | (collaborator status is required for this step). 58 | The version tag and the title of the release notes should be in the format `v1.2.3`. 59 | The body of the release notes should be a summary of the contents 60 | of the corresponding section in `CHANGELOG.md`. 61 | 62 | Finally, update the Wikimate entry on [Packagist](https://packagist.org/packages/hamstar/wikimate) 63 | (via the "Update Now" link in the right sidebar) 64 | and the corresponding row in MediaWiki.org's 65 | [PHP libraries table](https://www.mediawiki.org/wiki/API:Client_code/All#PHP). 66 | If applicable, also update Wikipedia's 67 | [PHP bot frameworks table](https://en.wikipedia.org/wiki/Wikipedia:PHP_bot_framework_table). 68 | 69 | Once all of this is done, leave a comment in the release preparation PR 70 | to keep a record of the steps taken for completing the release. 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2010-2021 Robert McLeod, Frans P. de Vries, and 4 | [contributors](https://github.com/hamstar/Wikimate/graphs/contributors) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wikimate 2 | 3 | Wikimate is a PHP wrapper for the 4 | [MediaWiki Action API](https://www.mediawiki.org/wiki/Special:MyLanguage/API:Main_page) 5 | that aims to be very easy to use. 6 | It currently consists of three classes: 7 | 8 | - **Wikimate** – Serves as a loader and manager for different wiki objects (e.g. pages). 9 | - **WikiPage** – Provides an interface to getting/editing pages or sections of them. 10 | - **WikiFile** – Provides an interface to downloading/uploading files and getting their properties. 11 | 12 | The [latest released version](https://github.com/hamstar/Wikimate/releases) of Wikimate 13 | is v1.1.0, released on July 30, 2023. 14 | It requires PHP v5.3 or newer and MediaWiki v1.27 or newer. 15 | See [CHANGELOG.md](CHANGELOG.md) for the detailed version history. 16 | 17 | ## Installation 18 | 19 | **Requirements: [PHP](https://php.net), and [Composer](https://getcomposer.org).** 20 | 21 | Before anything else, since Wikimate is written in PHP, a server-side language, 22 | you will need to have PHP installed to run it. 23 | Install it with your preferred package management tool 24 | (for example, on Ubuntu Linux you can run: `sudo apt-get install php`) 25 | 26 | The recommended way to install this library is with Composer. 27 | Composer is a dependency management tool for PHP 28 | that allows you to declare the dependencies your project needs 29 | and installs them into your project. 30 | 31 | Install Composer by following the instructions [here](https://getcomposer.org/doc/00-intro.md). 32 | 33 | Then, run the following command in your project's folder 34 | to download Wikimate and initialise it: 35 | 36 | ```sh 37 | composer require hamstar/Wikimate 38 | ``` 39 | 40 | (or `composer.bat require hamstar/Wikimate` if you're on Windows). 41 | 42 | To use Wikimate within another project, you can add it as a Composer dependency 43 | by adding the following to your existing `composer.json` file: 44 | 45 | ```json 46 | { 47 | "require": { 48 | "hamstar/Wikimate": "^1.1" 49 | } 50 | } 51 | ``` 52 | 53 | You can find out more on how to install Composer, 54 | configure autoloading, and other best-practices for defining dependencies 55 | at [getcomposer.org](https://getcomposer.org). 56 | 57 | ## Usage 58 | 59 | In your script file (e.g. `index.php`), include the project's `autoload.php` file, 60 | and create a new `Wikimate` object with the target wiki's API address. 61 | Then provide a username and password to Wikimate's `login` method, 62 | to log in to that wiki. 63 | 64 | ```php 65 | require __DIR__.'/vendor/autoload.php'; 66 | 67 | $api_url = 'https://example.com/w/api.php'; 68 | $username = 'bot'; 69 | $password = 'password'; 70 | 71 | $wiki = new Wikimate($api_url); 72 | 73 | // You can also pass the domain name: 74 | // $wiki->login($username, $password, $domainName) 75 | if ($wiki->login($username, $password)) 76 | echo 'Success: user logged in.' ; 77 | else { 78 | $error = $wiki->getError(); 79 | echo "Wikimate error: ".$error['auth']; 80 | } 81 | ``` 82 | 83 | This example uses echo statements to output any potential errors. 84 | You should get a meaningful error message if the authentication fails. 85 | Assuming you were able to log in, you're now ready to fully use the API. 86 | 87 | See [USAGE.md](USAGE.md) for detailed example code to perform common tasks. 88 | 89 | ## Contributing 90 | 91 | As an open source project, Wikimate welcomes community contributions. 92 | Please see [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute. 93 | 94 | ## License 95 | 96 | This project is licensed under the MIT license. 97 | See [LICENSE.md](LICENSE.md) for details. 98 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | - [Introduction](#introduction) 4 | - [Getting a page object](#getting-a-page-object) 5 | - [Reading...](#reading) 6 | - [Writing...](#writing) 7 | - [Deleting...](#deleting) 8 | - [Getting a file object](#getting-a-file-object) 9 | - [Downloading...](#downloading) 10 | - [Uploading...](#uploading) 11 | - [Accessing revisions...](#accessing-revisions) 12 | - [Deleting...](#deleting-1) 13 | - [Other stuff](#other-stuff) 14 | - [Running custom queries](#running-custom-queries) 15 | - [Restoring a deleted page or file](#restoring-a-deleted-page-or-file) 16 | - [Customizing the user agent](#customizing-the-user-agent) 17 | - [Maximum lag and retries](#maximum-lag-and-retries) 18 | - [Handling errors and exceptions](#handling-errors-and-exceptions) 19 | - [Debug logging](#debug-logging) 20 | 21 | ## Introduction 22 | 23 | In your script file (e.g. `index.php`), include the project's `autoload.php` file, 24 | and create a new `Wikimate` object with the target wiki's API address. 25 | Then provide a username and password to Wikimate's `login` method, 26 | to log in to that wiki. 27 | 28 | ```php 29 | require __DIR__.'/vendor/autoload.php'; 30 | 31 | $api_url = 'https://example.com/w/api.php'; 32 | $username = 'bot'; 33 | $password = 'password'; 34 | 35 | $wiki = new Wikimate($api_url); 36 | 37 | // You can also pass the domain name: 38 | // $wiki->login($username, $password, $domainName) 39 | if ($wiki->login($username, $password)) 40 | echo 'Success: user logged in.' ; 41 | else { 42 | $error = $wiki->getError(); 43 | echo "Wikimate error: ".$error['auth']; 44 | } 45 | ``` 46 | 47 | This example uses echo statements to output any potential errors. 48 | You should get a meaningful error message if the authentication fails. 49 | 50 | Assuming you were able to log in, you're now ready to fully use the API. 51 | The next sections provide example code for several common tasks. 52 | 53 | ## Getting a page object 54 | 55 | Once logged in you can start playing around with pages. 56 | If the title given to the WikiPage object is invalid, 57 | your `$page` object will be null. 58 | 59 | ```php 60 | // create a new page object 61 | $page = $wiki->getPage('Sausages'); 62 | // check if the page exists or not 63 | if (!$page->exists()) die('Page not found'); 64 | // get the page title 65 | echo $page->getTitle(); 66 | // get the number of sections on the page 67 | echo $page->getNumSections(); 68 | // get an array of where each section starts and its length 69 | echo $page->getSectionOffsets(); 70 | ``` 71 | 72 | ### Reading... 73 | 74 | You can get the text of the page by using the `getText()` method 75 | which returns the text that was obtained when the page object was created. 76 | If you want fresh page text from the wiki 77 | then just put boolean `true` as the first argument. 78 | 79 | ```php 80 | // get the text of the page 81 | $wikiCode = $page->getText(); 82 | // get fresh page text from the api and rebuild sections 83 | $wikiCode = $page->getText(true); 84 | ``` 85 | 86 | You can get sections from the page as well, 87 | via the section index or the section heading: 88 | 89 | ```php 90 | // get the part between the title and the first section 91 | $wikiCode = $page->getSection(0); 92 | // get the part between the title and the first section 93 | $wikiCode = $page->getSection('intro'); 94 | // get the section called History and any subsections, but no heading 95 | $wikiCode = $page->getSection('History'); 96 | // get the 4th section on the page and any subsections, but no heading 97 | $wikiCode = $page->getSection(4); 98 | // get the section called History including the heading, and any subsections 99 | $wikiCode = $page->getSection('History', true); 100 | // get the 4th section on the page including the heading, without subsections 101 | $wikiCode = $page->getSection(4, true, false); 102 | ``` 103 | 104 | You can even get an array with all the sections in it by either index or name: 105 | 106 | ```php 107 | // get all the sections (by index number) 108 | $sections = $page->getAllSections(); 109 | // get all the sections (by index number) with the section heading names 110 | $sections = $page->getAllSections(true); 111 | // get all the sections (by section name) 112 | $sections = $page->getAllSections(false, WikiPage::SECTIONLIST_BY_NAME); 113 | // get all the sections (by section name) 114 | $sections = $page->getAllSections(false, 2); 115 | ``` 116 | 117 | The array looks like this: 118 | 119 | ```text 120 | Array 121 | ( 122 | [intro] => bit between title and first section 123 | [Summary] => The summary goes here 124 | [Context] => This is the context 125 | [Impact] => The impact is here 126 | [Media Articles] => Links go here 127 | [References] => 128 | ) 129 | ``` 130 | 131 | An `UnexpectedValueException` is thrown 132 | if an unsupported value is supplied for the `$keyNames` parameter. 133 | 134 | ### Writing... 135 | 136 | You can modify the whole article using the `setText()` method: 137 | 138 | ```php 139 | // returns true if the edit worked 140 | $page->setText("==Testing==\n\n This is a whole page"); 141 | // the setText() method will overwrite the entire page! 142 | $page->setText("==Changed==\n\n I just changed the whole page"); 143 | ``` 144 | 145 | You can modify only sections of the article 146 | by adding a second parameter to the `setText()` method. 147 | You can use both section names and section indexes here. 148 | 149 | ```php 150 | // provide a section number to overwrite only that section 151 | $page->setText("==Section 4==\n\nThis will appear in section 4", 4); 152 | // ... or overwrite a section by name 153 | $page->setText("==History==\n\nThis will appear in the history section", 'History'); 154 | // ...or make a new section 155 | $page->setText("==New section==\n\nStuff", 'new') 156 | // ...zero is the very first section 157 | $page->setText("Sausages are cylindrical packages of meat.", 0) 158 | ``` 159 | 160 | The minor edit switch and the edit summary description are the third and fourth arguments: 161 | 162 | ```php 163 | $page->setText($text, $section, true, "removing spam!"); 164 | ``` 165 | 166 | Here are some easier methods for editing sections: 167 | 168 | ```php 169 | $page->setSection($text, $section, $summary, $minor); 170 | $page->newSection($sectionTitle, $text); 171 | ``` 172 | 173 | For the latter method, the $sectionTitle is also used as part of the edit summary description. 174 | 175 | ### Deleting... 176 | 177 | If the account you're using has delete permissions, 178 | you can delete entire pages with `delete()`: 179 | 180 | ```php 181 | // returns true if the delete was successful 182 | $page->delete('The page was created accidentally in the first place'); 183 | ``` 184 | 185 | If you pass in a message argument, 186 | it will be recorded as the reason for the deletion. 187 | 188 | ## Getting a file object 189 | 190 | Once connected you can also start playing around with files. 191 | 192 | ```php 193 | // create a new file object 194 | $file = $wiki->getFile('Site-logo.png'); 195 | // check if the file exists or not 196 | if (!$file->exists()) die('File not found'); 197 | // get the file name 198 | echo $file->getFilename(); 199 | ``` 200 | 201 | All available properties of the file are accessible via `get` methods. 202 | 203 | ```php 204 | // get the file size, timestamp, and hash 205 | echo $file->getSize(); 206 | echo $file->getTimestamp(); 207 | echo $file->getSha1(); 208 | // get dimensions and MIME type for an image 209 | echo $file->getHeight(); 210 | echo $file->getWidth(); 211 | echo $file->getMime(); 212 | // get aspect ratio of an image 213 | // this is a convenience method rather than a direct property 214 | echo $file->getAspectRatio(); 215 | ``` 216 | 217 | ### Downloading... 218 | 219 | You can obtain the data of the file by using the `downloadData()` method and use it in your script, 220 | or write it directly to a local file via the `downloadFile()` method. 221 | 222 | ```php 223 | $data = $file->downloadData(); 224 | // process image $data of Site-logo.png 225 | $result = $file->downloadFile('/path/to/sitelogo.png'); 226 | ``` 227 | 228 | ### Uploading... 229 | 230 | You can upload data from your script to the file by using the `uploadData()` method, 231 | or read it directly from a local file via the `uploadFile()` method. 232 | Additionally, uploading from a URL is possible via the `uploadFromUrl()` method. 233 | 234 | A comment for the file's history must be supplied, 235 | and for a new file the text for its associated description page can be provided as well. 236 | If no such text is passed, the comment will be used instead. 237 | 238 | All upload methods guard against uploading data to an existing file, 239 | but allow this when the overwrite flag is set. 240 | 241 | ```php 242 | // construct image $data for Site-logo.png 243 | $result = uploadData($data, 'Upload new site logo', 'New site logo to reflect the new brand', true); 244 | $result = uploadFile('/path/to/newlogo.png', 'Upload new site logo', 'New site logo to reflect the new brand', true); 245 | 246 | // add a new button to the site 247 | $file = $wiki->getFile('New-button.png'); 248 | if ($file->exists()) die('New button already exists'); 249 | $result = uploadFile('/path/to/newbutton.png', 'Upload new button', 'New button to match the new logo'); 250 | 251 | // add an example image from a remote URL 252 | $file = $wiki->getFile('Wiki-example.jpg'); 253 | if ($file->exists()) die('Example image already exists'); 254 | $result = uploadFromUrl('https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg', 'Adopt Wiki example image'); 255 | ``` 256 | 257 | ### Accessing revisions... 258 | 259 | The revision history of a file can be obtained as an array with a properties array per revision: 260 | 261 | ```php 262 | $file = $wiki->getFile('Frequently-changed-file.zip'); 263 | // get only the current revision 264 | $history = $file->getHistory(); 265 | // get the maximum number of revisions (500 for user accounts, 5000 for bot accounts) 266 | $history = $file->getHistory(true); 267 | // get the latest 10 revisions during 2015 (can use all MediaWiki timestamp formats) 268 | $history = $file->getHistory(true, 10, '2015-01-01 00:00:00', '2015-12-31 23:59:59'); 269 | // iterate over revisions and print properties 270 | foreach ($history as $revision => $properties) { 271 | echo "Revision $revision properties:\n"; 272 | print_r($properties); 273 | } 274 | ``` 275 | 276 | A specific revision can be requested by revision sequence number or by exact timestamp, 277 | as can its archive name. 278 | Invoking `getHistory(true[, ...])` is required before any older revisions can be requested. 279 | 280 | ```php 281 | // get the latest 50 revisions 282 | $history = $file->getHistory(true, 50); 283 | // get all properties of the penultimate revision 284 | $revision = $file->getRevision(1); 285 | // get the archive name of the specific revision (must be ISO 8601 format) 286 | $archivename = $file->getArchivename('2016-11-22T33:44:55Z'); 287 | ``` 288 | 289 | All standard file properties can also be obtained for one specific revision: 290 | 291 | ```php 292 | // get the file size of the current revision 293 | echo $file->getSize(0); 294 | // get the hash of the penultimate revision 295 | echo $file->getSha1(1); 296 | // get the aspect ratio of the antepenultimate revision 297 | echo $file->getAspectRatio(2); 298 | // get the URL of the specific revision (must be ISO 8601 format) 299 | echo $file->getUrl('2016-11-22T33:44:55Z'); 300 | ``` 301 | 302 | ### Deleting... 303 | 304 | If the account you're using has delete permissions, you can delete files as well: 305 | 306 | ```php 307 | $file = $wiki->getFile('Old-button.png'); 308 | // returns true if the delete was successful 309 | $file->delete('The button was superseded by a new one'); 310 | ``` 311 | 312 | To delete or revert to a specific older revision of the file, 313 | the archive name is needed: 314 | 315 | ```php 316 | $file = $wiki->getFile('Often-changed-file.zip'); 317 | $history = $file->getHistory(true); 318 | $archivename = $file->getArchivename(3); 319 | $file->delete('This was an inadvertent release', $archivename); 320 | $archivename = $file->getArchivename(1); 321 | $file->revert($archivename, 'Revert to the previous release'); 322 | ``` 323 | 324 | ## Other stuff 325 | 326 | ### Running custom queries 327 | 328 | Wanna run your own queries? 329 | You can use the edit and query commands in Wikimate: 330 | 331 | ```php 332 | $data = array( 333 | 'prop' => 'info|revisions', 334 | 'titles' => 'this|that|other' 335 | ); 336 | 337 | // Send data as a query 338 | $array_result = $wiki->query($data); 339 | 340 | $data = array( 341 | 'title' => 'this', 342 | 'etc' => 'stuff' 343 | ); 344 | 345 | // Send as an edit query with content-type of application/x-www-form-urlencoded 346 | $array_result = $wiki->edit($data); 347 | ``` 348 | 349 | Both methods return an array of the MediaWiki API result. 350 | 351 | ### Restoring a deleted page or file 352 | 353 | A previously deleted page or file can be undeleted via its original path, 354 | including namespace if applicable: 355 | 356 | ```php 357 | $wiki->undelete('Sausages'); 358 | $wiki->undelete('File:Old-button.png'); 359 | ``` 360 | 361 | ### Customizing the user agent 362 | 363 | API requests are made over HTTP with a user agent string to identify 364 | the client to the server. By default the user agent is formatted as: 365 | 366 | `Wikimate/ (https://github.com/hamstar/Wikimate)` 367 | 368 | The string can be retrieved and customized via: 369 | 370 | ```php 371 | $useragent = $wiki->getUserAgent(); 372 | $wiki->setUserAgent('Custom Prefix - ' . $useragent); 373 | $wiki->setUserAgent('Custom User Agent'); 374 | ``` 375 | 376 | In order to use a custom user agent for all requests in the session, 377 | call this method before invoking `Wikimate::login()`. 378 | 379 | ### Maximum lag and retries 380 | 381 | API requests include the [maxlag parameter](https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Maxlag_parameter) 382 | so they time out when the server's time to respond exceeds the specified lag. 383 | The default lag is 5 seconds, which can be obtained via `$wiki->getMaxlag()` 384 | and changed via `$wiki->setMaxlag()`. 385 | Upon a lag error response, 386 | the request is [paused](https://www.php.net/manual/en/function.sleep) 387 | for the number of seconds recommended by the server, and then retried. 388 | Retries continue indefinitely, unless limited via `$wiki->setMaxretries()`. 389 | If a limited number of retries runs out, `WikimateException` is thrown. 390 | 391 | ### Handling errors and exceptions 392 | 393 | Did something go wrong? Check the error array: 394 | 395 | ```php 396 | print_r($page->getError()); 397 | print_r($file->getError()); 398 | ``` 399 | 400 | For MediaWiki API errors, the array contains the 'code' and 'info' key/value pairs 401 | [defined by the API](https://www.mediawiki.org/wiki/Special:MyLanguage/API:Errors_and_warnings#Errors). 402 | For other errors, the following key/value pairs are returned: 403 | 404 | - 'auth' for Wikimate authentication (login & logout) problems 405 | - 'token' for Wikimate token problems 406 | - 'page' for WikiPage errors 407 | - 'file' for WikiFile errors 408 | 409 | In case of an unexpected error while communicating with the API, 410 | i.e. receiving an HTML response or an invalid JSON response, 411 | or running out of maxlag retries, `WikimateException` is thrown. 412 | 413 | ### Debug logging 414 | 415 | In addition to checking the error array, 416 | you can enable/disable debugging with the `$wiki->debugMode($boolean)` method. 417 | Debug logging is printed to [standard output](https://en.wikipedia.org/wiki/Standard_output) 418 | and includes API requests/responses as well as retry messages for [lag errors](#maximum-lag-and-retries). 419 | 420 | Only requests for file uploads are not logged, 421 | because of the (potential) volume of the associated data. 422 | -------------------------------------------------------------------------------- /Wikimate.php: -------------------------------------------------------------------------------- 1 | api = $api; 175 | $this->headers = $headers; 176 | $this->data = $data; 177 | $this->options = $options; 178 | 179 | $this->initRequests(); 180 | } 181 | 182 | /** 183 | * Sets up a Requests_Session with appropriate user agent. 184 | * 185 | * @return void 186 | * @link https://requests.ryanmccue.info/docs/usage-advanced.html#session-handling 187 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Etiquette#The_User-Agent_header 188 | */ 189 | protected function initRequests() 190 | { 191 | $this->useragent = 'Wikimate/' . self::VERSION . ' (https://github.com/hamstar/Wikimate)'; 192 | 193 | $this->session = new WpOrg\Requests\Session($this->api, $this->headers, $this->data, $this->options); 194 | $this->session->useragent = $this->useragent; 195 | } 196 | 197 | /** 198 | * Sends a GET or POST request in JSON format to the API. 199 | * 200 | * This method handles maxlag errors as advised at: 201 | * {@see https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Maxlag_parameter} 202 | * The request is sent with the current maxlag value 203 | * (default: 5 seconds, per {@see Wikimate::MAXLAG_DEFAULT}). 204 | * If a lag error is received, the method waits (sleeps) for the 205 | * recommended time (per the Retry-After header), then tries again. 206 | * It will do this indefinitely unless the number of retries is limited, 207 | * in which case WikimateException is thrown once the limit is reached. 208 | * 209 | * The string type for $data is used only for upload POST requests, 210 | * and must contain the complete multipart body, including maxlag. 211 | * 212 | * @param array|string $data Data for the request 213 | * @param array $headers Optional extra headers to send with the request 214 | * @param boolean $post True to send a POST request, otherwise GET 215 | * @return array The API response 216 | * @throw WikimateException If lagged and ran out of retries, 217 | * or got an unexpected API response 218 | */ 219 | private function request($data, $headers = array(), $post = false) 220 | { 221 | $retries = 0; 222 | 223 | // Add format & maxlag parameter to request 224 | if (is_array($data)) { 225 | $data['format'] = 'json'; 226 | $data['maxlag'] = $this->getMaxlag(); 227 | $action = $data['action']; 228 | } else { 229 | $action = 'upload'; 230 | } 231 | // Define type of HTTP request for messages 232 | $httptype = $post ? 'POST' : 'GET'; 233 | 234 | // Send appropriate type of request, once or multiple times 235 | do { 236 | if ($post) { 237 | // Debug logging of POST requests, except for upload string 238 | if ($this->debugMode && is_array($data)) { 239 | echo "$action $httptype parameters:\n"; 240 | print_r($data); 241 | } 242 | $response = $this->session->post($this->api, $headers, $data); 243 | } else { 244 | // Debug logging of GET requests as a query string 245 | if ($this->debugMode) { 246 | echo "$action $httptype parameters:\n"; 247 | echo http_build_query($data) . "\n"; 248 | } 249 | $response = $this->session->get($this->api . '?' . http_build_query($data), $headers); 250 | } 251 | 252 | // Check for replication lag error 253 | $serverLagged = ($response->headers->offsetGet('X-Database-Lag') !== null); 254 | if ($serverLagged) { 255 | // Determine recommended or default delay 256 | if ($response->headers->offsetGet('Retry-After') !== null) { 257 | $sleep = (int)$response->headers->offsetGet('Retry-After'); 258 | } else { 259 | $sleep = $this->getMaxlag(); 260 | } 261 | 262 | if ($this->debugMode) { 263 | preg_match('/Waiting for [^ ]*: ([0-9.-]+) seconds? lagged/', $response->body, $match); 264 | echo "Server lagged for {$match[1]} seconds; will retry in {$sleep} seconds\n"; 265 | } 266 | sleep($sleep); 267 | 268 | // Check retries limit 269 | if ($this->getMaxretries() >= 0) { 270 | $retries++; 271 | } else { 272 | $retries = -1; // continue indefinitely 273 | } 274 | } 275 | } while ($serverLagged && $retries <= $this->getMaxretries()); 276 | 277 | // Throw exception if we ran out of retries 278 | if ($serverLagged) { 279 | throw new WikimateException("Server lagged ($retries consecutive maxlag responses)"); 280 | } 281 | 282 | // Check if we got the API doc page (invalid request) 283 | if (strpos($response->body, "This is an auto-generated MediaWiki API documentation page") !== false) { 284 | throw new WikimateException("The API could not understand the $action $httptype request"); 285 | } 286 | 287 | // Check if we got a JSON result 288 | $result = json_decode($response->body, true); 289 | if ($result === null) { 290 | throw new WikimateException("The API did not return the $action JSON response"); 291 | } 292 | 293 | if ($this->debugMode) { 294 | echo "$action $httptype response:\n"; 295 | print_r($result); 296 | } 297 | 298 | return $result; 299 | } 300 | 301 | /** 302 | * Obtains a wiki token for logging in or data-modifying actions. 303 | * 304 | * If a CSRF (default) token is requested, it is stored and returned 305 | * upon further such requests, instead of making another API call. 306 | * The stored token is discarded via {@see Wikimate::logout()}. 307 | * 308 | * For now this method, in Wikimate tradition, is kept simple and supports 309 | * only the two token types needed elsewhere in the library. It also 310 | * doesn't support the option to request multiple tokens at once. 311 | * See {@see https://www.mediawiki.org/wiki/Special:MyLanguage/API:Tokens} 312 | * for more information. 313 | * 314 | * @param string $type The token type 315 | * @return mixed The requested token (string), or null if error 316 | */ 317 | protected function token($type = self::TOKEN_DEFAULT) 318 | { 319 | // Check for supported token types 320 | if ($type != self::TOKEN_DEFAULT && $type != self::TOKEN_LOGIN) { 321 | $this->error = array(); 322 | $this->error['token'] = 'The API does not support the token type'; 323 | return null; 324 | } 325 | 326 | // Check for existing CSRF token for this login session 327 | if ($type == self::TOKEN_DEFAULT && $this->csrfToken !== null) { 328 | return $this->csrfToken; 329 | } 330 | 331 | $details = array( 332 | 'action' => 'query', 333 | 'meta' => 'tokens', 334 | 'type' => $type, 335 | ); 336 | 337 | // Send the token request 338 | $tokenResult = $this->request($details, array(), true); 339 | 340 | // Check for errors 341 | if (isset($tokenResult['error'])) { 342 | $this->error = $tokenResult['error']; // Set the error if there was one 343 | return null; 344 | } else { 345 | $this->error = null; // Reset the error status 346 | } 347 | 348 | if ($type == self::TOKEN_LOGIN) { 349 | return $tokenResult['query']['tokens']['logintoken']; 350 | } else { 351 | // Store CSRF token for this login session 352 | $this->csrfToken = $tokenResult['query']['tokens']['csrftoken']; 353 | return $this->csrfToken; 354 | } 355 | } 356 | 357 | /** 358 | * Obtains parameter lists for API modules. 359 | * For specific API modules where supported parameters vary with 360 | * MediaWiki versions, this method is used to fetch and store 361 | * these parameter lists for use by the API calls of those modules. 362 | * Current list of modules for which this mechanism is employed: 363 | * - {@link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Logout logout} 364 | * - {@link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Delete delete} 365 | * - {@link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Undelete undelete} 366 | * 367 | * @return void 368 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parameter_information 369 | */ 370 | private function storeModuleParams() 371 | { 372 | // Obtain parameter info for modules 373 | $details = array( 374 | 'action' => 'paraminfo', 375 | 'modules' => 'logout|delete|undelete', 376 | ); 377 | 378 | // Send the paraminfo request 379 | $paramResult = $this->request($details); 380 | 381 | // Check for errors 382 | if (isset($paramResult['error'])) { 383 | $this->error = $paramResult['error']; // Set the error if there was one 384 | return; 385 | } else { 386 | $this->error = null; // Reset the error status 387 | } 388 | 389 | // Store supported parameters for all requested modules 390 | foreach ($paramResult['paraminfo']['modules'] as $module) { 391 | $this->moduleParams[$module['name']] = array(); 392 | foreach ($module['parameters'] as $param) { 393 | $this->moduleParams[$module['name']][] = $param['name']; 394 | } 395 | } 396 | } 397 | 398 | /** 399 | * Checks API module's parameters for a supported parameter. 400 | * 401 | * @param string $module The module name 402 | * @param string $param The parameter name 403 | * @return boolean True if supported 404 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parameter_information 405 | */ 406 | public function supportsModuleParam($module, $param) 407 | { 408 | return in_array($param, $this->moduleParams[$module]); 409 | } 410 | 411 | /** 412 | * Logs in to the wiki. 413 | * 414 | * @param string $username The user name 415 | * @param string $password The user password 416 | * @param string $domain The domain (optional) 417 | * @return boolean True if logged in 418 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Login#Method_1._login 419 | */ 420 | public function login($username, $password, $domain = null) 421 | { 422 | // Obtain login token first 423 | if (($logintoken = $this->token(self::TOKEN_LOGIN)) === null) { 424 | return false; 425 | } 426 | 427 | $details = array( 428 | 'action' => 'login', 429 | 'lgname' => $username, 430 | 'lgpassword' => $password, 431 | 'lgtoken' => $logintoken, 432 | ); 433 | 434 | // If $domain is provided, set the corresponding detail in the request information array 435 | if (is_string($domain)) { 436 | $details['lgdomain'] = $domain; 437 | } 438 | 439 | // Send the login request 440 | $loginResult = $this->request($details, array(), true); 441 | 442 | // Check for errors 443 | if (isset($loginResult['error'])) { 444 | $this->error = $loginResult['error']; // Set the error if there was one 445 | return false; 446 | } else { 447 | $this->error = null; // Reset the error status 448 | } 449 | 450 | if (isset($loginResult['login']['result']) && $loginResult['login']['result'] != 'Success') { 451 | // Some more comprehensive error checking 452 | $this->error = array(); 453 | switch ($loginResult['login']['result']) { 454 | case 'Failed': 455 | $this->error['auth'] = 'Incorrect username or password'; 456 | break; 457 | default: 458 | $this->error['auth'] = 'The API result was: ' . $loginResult['login']['result']; 459 | break; 460 | } 461 | return false; 462 | } 463 | 464 | // Obtain parameter lists for API modules 465 | $this->storeModuleParams(); 466 | 467 | return true; 468 | } 469 | 470 | /** 471 | * Logs out of the wiki and discards CSRF token. 472 | * 473 | * @return boolean True if logged out 474 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Logout 475 | */ 476 | public function logout() 477 | { 478 | $details = array( 479 | 'action' => 'logout', 480 | ); 481 | 482 | // Check if token parameter is required (it is since MediaWiki v1.34) 483 | if ($this->supportsModuleParam('logout', 'token')) { 484 | // Obtain logout token 485 | if (($logouttoken = $this->token()) === null) { 486 | return false; 487 | } 488 | $details['token'] = $logouttoken; 489 | } 490 | 491 | // Send the logout request 492 | $logoutResult = $this->request($details, array(), true); 493 | 494 | // Check for errors 495 | if (isset($logoutResult['error'])) { 496 | $this->error = $logoutResult['error']; // Set the error if there was one 497 | return false; 498 | } else { 499 | $this->error = null; // Reset the error status 500 | } 501 | 502 | // Discard CSRF token for this login session 503 | $this->csrfToken = null; 504 | return true; 505 | } 506 | 507 | /** 508 | * Gets the current value of the maxlag parameter. 509 | * 510 | * @return integer The maxlag value in seconds 511 | */ 512 | public function getMaxlag() 513 | { 514 | return $this->maxlag; 515 | } 516 | 517 | /** 518 | * Sets the new value of the maxlag parameter. 519 | * 520 | * @param integer $ml The new maxlag value in seconds 521 | * @return Wikimate This object 522 | */ 523 | public function setMaxlag($ml) 524 | { 525 | $this->maxlag = (int)$ml; 526 | return $this; 527 | } 528 | 529 | /** 530 | * Gets the current value of the max retries limit. 531 | * 532 | * @return integer The max retries limit 533 | */ 534 | public function getMaxretries() 535 | { 536 | return $this->maxretries; 537 | } 538 | 539 | /** 540 | * Sets the new value of the max retries limit. 541 | * 542 | * @param integer $mr The new max retries limit 543 | * @return Wikimate This object 544 | */ 545 | public function setMaxretries($mr) 546 | { 547 | $this->maxretries = (int)$mr; 548 | return $this; 549 | } 550 | 551 | /** 552 | * Gets the user agent for API requests. 553 | * 554 | * @return string The default user agent, or the current one defined 555 | * by {@see Wikimate::setUserAgent()} 556 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Etiquette#The_User-Agent_header 557 | */ 558 | public function getUserAgent() 559 | { 560 | return $this->useragent; 561 | } 562 | 563 | /** 564 | * Sets the user agent for API requests. 565 | * 566 | * In order to use a custom user agent for all requests in the session, 567 | * call this method before invoking {@see Wikimate::login()}. 568 | * 569 | * @param string $ua The new user agent 570 | * @return Wikimate This object 571 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Etiquette#The_User-Agent_header 572 | */ 573 | public function setUserAgent($ua) 574 | { 575 | $this->useragent = (string)$ua; 576 | // Update the session 577 | $this->session->useragent = $this->useragent; 578 | return $this; 579 | } 580 | 581 | /** 582 | * Sets the debug mode. 583 | * 584 | * @param boolean $b True to turn debugging on 585 | * @return Wikimate This object 586 | */ 587 | public function setDebugMode($b) 588 | { 589 | $this->debugMode = $b; 590 | return $this; 591 | } 592 | 593 | /** 594 | * Gets or prints the Requests configuration. 595 | * 596 | * @param boolean $echo Whether to echo the session options and headers 597 | * @return mixed Options array if $echo is false, or 598 | * True if options/headers have been echoed to STDOUT 599 | */ 600 | public function debugRequestsConfig($echo = false) 601 | { 602 | if ($echo) { 603 | echo "
Requests options:\n";
 604 |             print_r($this->session->options);
 605 |             echo "Requests headers:\n";
 606 |             print_r($this->session->headers);
 607 |             echo "
"; 608 | return true; 609 | } 610 | return $this->session->options; 611 | } 612 | 613 | /** 614 | * Returns a WikiPage object populated with the page data. 615 | * 616 | * @param string $title The name of the wiki article 617 | * @return WikiPage The page object 618 | */ 619 | public function getPage($title) 620 | { 621 | return new WikiPage($title, $this); 622 | } 623 | 624 | /** 625 | * Returns a WikiFile object populated with the file data. 626 | * 627 | * @param string $filename The name of the wiki file 628 | * @return WikiFile The file object 629 | */ 630 | public function getFile($filename) 631 | { 632 | return new WikiFile($filename, $this); 633 | } 634 | 635 | /** 636 | * Performs a query to the wiki API with the given details. 637 | * 638 | * @param array $array Array of details to be passed in the query 639 | * @return array Decoded JSON output from the wiki API 640 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query 641 | */ 642 | public function query($array) 643 | { 644 | $array['action'] = 'query'; 645 | 646 | return $this->request($array); 647 | } 648 | 649 | /** 650 | * Performs a parse query to the wiki API. 651 | * 652 | * @param array $array Array of details to be passed in the query 653 | * @return array Decoded JSON output from the wiki API 654 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext 655 | */ 656 | public function parse($array) 657 | { 658 | $array['action'] = 'parse'; 659 | 660 | return $this->request($array); 661 | } 662 | 663 | /** 664 | * Performs an edit query to the wiki API. 665 | * 666 | * @param array $array Array of details to be passed in the query 667 | * @return array|boolean Decoded JSON output from the wiki API 668 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Edit 669 | */ 670 | public function edit($array) 671 | { 672 | // Obtain default token first 673 | if (($edittoken = $this->token()) === null) { 674 | return false; 675 | } 676 | 677 | $headers = array( 678 | 'Content-Type' => "application/x-www-form-urlencoded" 679 | ); 680 | 681 | $array['action'] = 'edit'; 682 | $array['token'] = $edittoken; 683 | 684 | return $this->request($array, $headers, true); 685 | } 686 | 687 | /** 688 | * Performs a delete query to the wiki API. 689 | * 690 | * @param array $array Array of details to be passed in the query 691 | * @return array|boolean Decoded JSON output from the wiki API 692 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Delete 693 | */ 694 | public function delete($array) 695 | { 696 | // Obtain default token first 697 | if (($deletetoken = $this->token()) === null) { 698 | return false; 699 | } 700 | 701 | $headers = array( 702 | 'Content-Type' => "application/x-www-form-urlencoded" 703 | ); 704 | 705 | $array['action'] = 'delete'; 706 | $array['token'] = $deletetoken; 707 | 708 | return $this->request($array, $headers, true); 709 | } 710 | 711 | /** 712 | * Performs an undelete query to the wiki API. 713 | * 714 | * @param string $name The original name of the wiki page or file 715 | * @param string $reason Reason for the restore 716 | * @param boolean $talktoo True to restore talk page, if any; 717 | * false to leave talk page deleted (MW 1.39+) 718 | * @return array|boolean Decoded JSON output from the wiki API 719 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Undelete 720 | */ 721 | public function undelete($name, $reason = null, $talktoo = true) 722 | { 723 | // Obtain default token first 724 | if (($undeletetoken = $this->token()) === null) { 725 | return false; 726 | } 727 | 728 | $headers = array( 729 | 'Content-Type' => "application/x-www-form-urlencoded" 730 | ); 731 | 732 | // Set options from arguments 733 | $array = array( 734 | 'action' => 'undelete', 735 | 'title' => $name, 736 | 'token' => $undeletetoken 737 | ); 738 | if (!is_null($reason)) { 739 | $array['reason'] = $reason; 740 | } 741 | // Check if undeletetalk parameter is supported (it is since MediaWiki v1.39) 742 | if ($this->supportsModuleParam('undelete', 'undeletetalk') && $talktoo) { 743 | $array['undeletetalk'] = $talktoo; 744 | } 745 | 746 | return $this->request($array, $headers, true); 747 | } 748 | 749 | /** 750 | * Downloads data from the given URL. 751 | * 752 | * @param string $url The URL to download from 753 | * @return mixed The downloaded data (string), or null if error 754 | */ 755 | public function download($url) 756 | { 757 | $getResult = $this->session->get($url); 758 | 759 | if (!$getResult->success) { 760 | // Debug logging of Requests_Response only on failed download 761 | if ($this->debugMode) { 762 | echo "download GET response:\n"; 763 | print_r($getResult); 764 | } 765 | $this->error = array(); 766 | $this->error['file'] = 'Download error (HTTP status: ' . $getResult->status_code . ')'; 767 | $this->error['http'] = $getResult->status_code; 768 | return null; 769 | } 770 | return $getResult->body; 771 | } 772 | 773 | /** 774 | * Uploads a file to the wiki API. 775 | * 776 | * @param array $array Array of details to be used in the upload 777 | * @return array|boolean Decoded JSON output from the wiki API 778 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Upload 779 | */ 780 | public function upload($array) 781 | { 782 | // Obtain default token first 783 | if (($uploadtoken = $this->token()) === null) { 784 | return false; 785 | } 786 | 787 | $array['action'] = 'upload'; 788 | $array['format'] = 'json'; 789 | $array['maxlag'] = $this->getMaxlag(); 790 | $array['token'] = $uploadtoken; 791 | 792 | // Construct multipart body: 793 | // https://www.mediawiki.org/w/index.php?title=API:Upload&oldid=2293685#Sample_Raw_Upload 794 | // https://www.mediawiki.org/w/index.php?title=API:Upload&oldid=2339771#Sample_Raw_POST_of_a_single_chunk 795 | $boundary = '---Wikimate-' . md5(microtime()); 796 | $body = ''; 797 | foreach ($array as $fieldName => $fieldData) { 798 | $body .= "--{$boundary}\r\n"; 799 | $body .= 'Content-Disposition: form-data; name="' . $fieldName . '"'; 800 | // Process the (binary) file 801 | if ($fieldName == 'file') { 802 | $body .= '; filename="' . $array['filename'] . '"' . "\r\n"; 803 | $body .= "Content-Type: application/octet-stream; charset=UTF-8\r\n"; 804 | $body .= "Content-Transfer-Encoding: binary\r\n"; 805 | // Process text parameters 806 | } else { 807 | $body .= "\r\n"; 808 | $body .= "Content-Type: text/plain; charset=UTF-8\r\n"; 809 | $body .= "Content-Transfer-Encoding: 8bit\r\n"; 810 | } 811 | $body .= "\r\n{$fieldData}\r\n"; 812 | } 813 | $body .= "--{$boundary}--\r\n"; 814 | 815 | // Construct multipart headers 816 | $headers = array( 817 | 'Content-Type' => "multipart/form-data; boundary={$boundary}", 818 | 'Content-Length' => strlen($body), 819 | ); 820 | 821 | return $this->request($body, $headers, true); 822 | } 823 | 824 | /** 825 | * Performs a file revert query to the wiki API. 826 | * 827 | * @param array $array Array of details to be passed in the query 828 | * @return array|boolean Decoded JSON output from the wiki API 829 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Filerevert 830 | */ 831 | public function filerevert($array) 832 | { 833 | // Obtain default token first 834 | if (($reverttoken = $this->token()) === null) { 835 | return false; 836 | } 837 | 838 | $array['action'] = 'filerevert'; 839 | $array['token'] = $reverttoken; 840 | 841 | $headers = array( 842 | 'Content-Type' => "application/x-www-form-urlencoded" 843 | ); 844 | 845 | return $this->request($array, $headers, true); 846 | } 847 | 848 | /** 849 | * Returns the latest error if there is one. 850 | * 851 | * @return mixed The error array, or null if no error 852 | */ 853 | public function getError() 854 | { 855 | return $this->error; 856 | } 857 | } 858 | 859 | 860 | /** 861 | * Defines Wikimate's exception for unexpected run-time errors 862 | * while communicating with the API. 863 | * WikimateException can be thrown only from {@see Wikimate::request()}, 864 | * and is propagated to callers of this library. 865 | * 866 | * @author Frans P. de Vries 867 | * @since 1.0.0 August 2021 868 | * @link https://www.php.net/manual/en/class.runtimeexception.php 869 | */ 870 | class WikimateException extends RuntimeException 871 | { 872 | } 873 | 874 | 875 | /** 876 | * Models a wiki article page that can have its text altered and retrieved. 877 | * 878 | * @author Robert McLeod & Frans P. de Vries 879 | * @since 0.2 December 2010 880 | */ 881 | class WikiPage 882 | { 883 | /** 884 | * Identifier for page sections by index. 885 | * Use section indexes as keys in return array 886 | * of {@see WikiPage::getAllSections()}. 887 | * 888 | * @var integer 889 | */ 890 | const SECTIONLIST_BY_INDEX = 1; 891 | 892 | /** 893 | * Identifier for page sections by name. 894 | * Use section names as keys in return array 895 | * of {@see WikiPage::getAllSections()}. 896 | * 897 | * @var integer 898 | */ 899 | const SECTIONLIST_BY_NAME = 2; 900 | 901 | /** 902 | * The title of the page 903 | * 904 | * @var string|null 905 | */ 906 | protected $title = null; 907 | 908 | /** 909 | * Wikimate object for API requests 910 | * 911 | * @var Wikimate|null 912 | */ 913 | protected $wikimate = null; 914 | 915 | /** 916 | * Whether the page exists 917 | * 918 | * @var boolean 919 | */ 920 | protected $exists = false; 921 | 922 | /** 923 | * Whether the page is invalid 924 | * 925 | * @var boolean 926 | */ 927 | protected $invalid = false; 928 | 929 | /** 930 | * Error array with API and WikiPage errors 931 | * 932 | * @var array|null 933 | */ 934 | protected $error = null; 935 | 936 | /** 937 | * Stores the timestamp for detection of edit conflicts 938 | * 939 | * @var integer|null 940 | */ 941 | protected $starttimestamp = null; 942 | 943 | /** 944 | * The complete text of the page 945 | * 946 | * @var string|null 947 | */ 948 | protected $text = null; 949 | 950 | /** 951 | * The sections object for the page 952 | * 953 | * @var stdClass|null 954 | */ 955 | protected $sections = null; 956 | 957 | /* 958 | * 959 | * Magic methods 960 | * 961 | */ 962 | 963 | /** 964 | * Constructs a WikiPage object from the title given 965 | * and associates it with the passed Wikimate object. 966 | * 967 | * @param string $title Name of the wiki article 968 | * @param Wikimate $wikimate Wikimate object 969 | */ 970 | public function __construct($title, $wikimate) 971 | { 972 | $this->wikimate = $wikimate; 973 | $this->title = $title; 974 | $this->sections = new \stdClass(); 975 | $this->text = $this->getText(true); 976 | 977 | if ($this->invalid) { 978 | $this->error['page'] = 'Invalid page title - cannot create WikiPage'; 979 | } 980 | } 981 | 982 | /** 983 | * Forgets all object properties. 984 | */ 985 | public function __destruct() 986 | { 987 | $this->title = null; 988 | $this->wikimate = null; 989 | $this->exists = false; 990 | $this->invalid = false; 991 | $this->error = null; 992 | $this->starttimestamp = null; 993 | $this->text = null; 994 | $this->sections = null; 995 | } 996 | 997 | /** 998 | * Returns the wikicode of the page. 999 | * 1000 | * @return string String of wikicode 1001 | */ 1002 | public function __toString() 1003 | { 1004 | return $this->text; 1005 | } 1006 | 1007 | /** 1008 | * Returns an array of sections with the section name as the key 1009 | * and the text as the element. 1010 | * 1011 | * For example: 1012 | * array( 1013 | * 'intro' => 'this text is the introduction', 1014 | * 'History' => 'this is text under the history section' 1015 | *) 1016 | * 1017 | * @return array Array of sections 1018 | */ 1019 | public function __invoke() 1020 | { 1021 | return $this->getAllSections(false, self::SECTIONLIST_BY_NAME); 1022 | } 1023 | 1024 | /** 1025 | * Returns the page existence status. 1026 | * 1027 | * @return boolean True if page exists 1028 | */ 1029 | public function exists() 1030 | { 1031 | return $this->exists; 1032 | } 1033 | 1034 | /** 1035 | * Forgets all object properties. 1036 | * Alias of {@see WikiPage::__destruct()}. 1037 | * 1038 | * @return void 1039 | */ 1040 | public function destroy() 1041 | { 1042 | $this->__destruct(); 1043 | } 1044 | 1045 | /* 1046 | * 1047 | * Page meta methods 1048 | * 1049 | */ 1050 | 1051 | /** 1052 | * Returns the latest error if there is one. 1053 | * 1054 | * @return mixed The error array, or null if no error 1055 | */ 1056 | public function getError() 1057 | { 1058 | return $this->error; 1059 | } 1060 | 1061 | /** 1062 | * Returns the title of this page. 1063 | * 1064 | * @return string The title of this page 1065 | */ 1066 | public function getTitle() 1067 | { 1068 | return $this->title; 1069 | } 1070 | 1071 | /** 1072 | * Returns the number of sections in this page. 1073 | * 1074 | * @return integer The number of sections in this page 1075 | */ 1076 | public function getNumSections() 1077 | { 1078 | return count($this->sections->byIndex); 1079 | } 1080 | 1081 | /** 1082 | * Returns the sections with offsets and lengths. 1083 | * 1084 | * @return stdClass Section class 1085 | */ 1086 | public function getSectionOffsets() 1087 | { 1088 | return $this->sections; 1089 | } 1090 | 1091 | /* 1092 | * 1093 | * Getter methods 1094 | * 1095 | */ 1096 | 1097 | /** 1098 | * Gets the text of the page. 1099 | * If refresh is true, then this method will query the wiki API 1100 | * again for the page details. 1101 | * 1102 | * @param boolean $refresh True to query the wiki API again 1103 | * @return mixed The text of the page (string), or null if error 1104 | */ 1105 | public function getText($refresh = false) 1106 | { 1107 | if ($refresh) { // We want to query the API 1108 | // Specify relevant page properties to retrieve 1109 | $data = array( 1110 | 'titles' => $this->title, 1111 | 'prop' => 'info|revisions', 1112 | 'rvprop' => 'content', // Need to get page text 1113 | 'curtimestamp' => 1, 1114 | ); 1115 | 1116 | $r = $this->wikimate->query($data); // Run the query 1117 | 1118 | // Check for errors 1119 | if (isset($r['error'])) { 1120 | $this->error = $r['error']; // Set the error if there was one 1121 | return null; 1122 | } else { 1123 | $this->error = null; // Reset the error status 1124 | } 1125 | 1126 | // Get the page (there should only be one) 1127 | $page = array_pop($r['query']['pages']); 1128 | 1129 | // Abort if invalid page title 1130 | if (isset($page['invalid'])) { 1131 | $this->invalid = true; 1132 | return null; 1133 | } 1134 | 1135 | $this->starttimestamp = $r['curtimestamp']; 1136 | unset($r, $data); 1137 | 1138 | if (!isset($page['missing'])) { 1139 | // Update the existence if the page is there 1140 | $this->exists = true; 1141 | // Put the content into text 1142 | $this->text = $page['revisions'][0]['*']; 1143 | } 1144 | unset($page); 1145 | 1146 | // Now we need to get the section headers, if any 1147 | preg_match_all('/(={1,6}).*?\1 *(?:\n|$)/', $this->text, $matches); 1148 | 1149 | // Set the intro section (between title and first section) 1150 | $this->sections->byIndex[0]['offset'] = 0; 1151 | $this->sections->byName['intro']['offset'] = 0; 1152 | 1153 | // Check for section header matches 1154 | if (empty($matches[0])) { 1155 | // Define lengths for page consisting only of intro section 1156 | $this->sections->byIndex[0]['length'] = strlen($this->text); 1157 | $this->sections->byName['intro']['length'] = strlen($this->text); 1158 | } else { 1159 | // Array of section header matches 1160 | $sections = $matches[0]; 1161 | 1162 | // Set up the current section 1163 | $currIndex = 0; 1164 | $currName = 'intro'; 1165 | 1166 | // Collect offsets and lengths from section header matches 1167 | foreach ($sections as $section) { 1168 | // Get the current offset 1169 | $currOffset = strpos($this->text, $section, $this->sections->byIndex[$currIndex]['offset']); 1170 | 1171 | // Are we still on the first section? 1172 | if ($currIndex == 0) { 1173 | $this->sections->byIndex[$currIndex]['length'] = $currOffset; 1174 | $this->sections->byIndex[$currIndex]['depth'] = 0; 1175 | $this->sections->byName[$currName]['length'] = $currOffset; 1176 | $this->sections->byName[$currName]['depth'] = 0; 1177 | } 1178 | 1179 | // Get the current name and index 1180 | $currName = trim(str_replace('=', '', $section)); 1181 | $currIndex++; 1182 | 1183 | // Search for existing name and create unique one 1184 | $cName = $currName; 1185 | for ($seq = 2; array_key_exists($cName, $this->sections->byName); $seq++) { 1186 | $cName = $currName . '_' . $seq; 1187 | } 1188 | if ($seq > 2) { 1189 | $currName = $cName; 1190 | } 1191 | 1192 | // Set the offset and depth (from the matched ='s) for the current section 1193 | $this->sections->byIndex[$currIndex]['offset'] = $currOffset; 1194 | $this->sections->byIndex[$currIndex]['depth'] = strlen($matches[1][$currIndex - 1]); 1195 | $this->sections->byName[$currName]['offset'] = $currOffset; 1196 | $this->sections->byName[$currName]['depth'] = strlen($matches[1][$currIndex - 1]); 1197 | 1198 | // If there is a section after this, set the length of this one 1199 | if (isset($sections[$currIndex])) { 1200 | // Get the offset of the next section 1201 | $nextOffset = strpos($this->text, $sections[$currIndex], $currOffset); 1202 | // Calculate the length of this one 1203 | $length = $nextOffset - $currOffset; 1204 | 1205 | // Set the length of this section 1206 | $this->sections->byIndex[$currIndex]['length'] = $length; 1207 | $this->sections->byName[$currName]['length'] = $length; 1208 | } else { 1209 | // Set the length of last section 1210 | $this->sections->byIndex[$currIndex]['length'] = strlen($this->text) - $currOffset; 1211 | $this->sections->byName[$currName]['length'] = strlen($this->text) - $currOffset; 1212 | } 1213 | } 1214 | } 1215 | } 1216 | 1217 | return $this->text; // Return the text in any case 1218 | } 1219 | 1220 | /** 1221 | * Returns the requested section, with its subsections, if any. 1222 | * 1223 | * Section can be the following: 1224 | * - section name (string, e.g. "History") 1225 | * - section index (int, e.g. 3) 1226 | * 1227 | * @param mixed $section The section to get 1228 | * @param boolean $includeHeading False to get section text only, 1229 | * true to include heading too 1230 | * @param boolean $includeSubsections False to get section text only, 1231 | * true to include subsections too 1232 | * @return mixed Wikitext of the section on the page, 1233 | * or null if section is undefined 1234 | */ 1235 | public function getSection($section, $includeHeading = false, $includeSubsections = true) 1236 | { 1237 | // Check if we have a section name or index 1238 | if (is_int($section)) { 1239 | if (!isset($this->sections->byIndex[$section])) { 1240 | return null; 1241 | } 1242 | $coords = $this->sections->byIndex[$section]; 1243 | } elseif (is_string($section)) { 1244 | if (!isset($this->sections->byName[$section])) { 1245 | return null; 1246 | } 1247 | $coords = $this->sections->byName[$section]; 1248 | } else { 1249 | $coords = array(); 1250 | } 1251 | 1252 | // Extract the offset, depth and (initial) length 1253 | @extract($coords); 1254 | // Find subsections if requested, and not the intro 1255 | if ($includeSubsections && $offset > 0) { 1256 | $found = false; 1257 | foreach ($this->sections->byName as $section) { 1258 | if ($found) { 1259 | // Include length of this subsection 1260 | if ($depth < $section['depth']) { 1261 | $length += $section['length']; 1262 | // Done if not a subsection 1263 | } else { 1264 | break; 1265 | } 1266 | } else { 1267 | // Found our section if same offset 1268 | if ($offset == $section['offset']) { 1269 | $found = true; 1270 | } 1271 | } 1272 | } 1273 | } 1274 | // Extract text of section, and its subsections if requested 1275 | $text = substr($this->text, $offset, $length); 1276 | 1277 | // Whack off the heading if requested, and not the intro 1278 | if (!$includeHeading && $offset > 0) { 1279 | // Chop off the first line 1280 | $text = substr($text, strpos($text, "\n")); 1281 | } 1282 | 1283 | return $text; 1284 | } 1285 | 1286 | /** 1287 | * Returns all the sections of the page in an array. 1288 | * The keys can be set to name or index by using the following 1289 | * for the $keyNames parameter: 1290 | * - self::SECTIONLIST_BY_NAME 1291 | * - self::SECTIONLIST_BY_INDEX (default) 1292 | * 1293 | * @param boolean $includeHeading False to get section text only 1294 | * @param integer $keyNames Modifier for the array key names 1295 | * @return array Array of sections 1296 | * @throw UnexpectedValueException If $keyNames is not a supported constant 1297 | */ 1298 | public function getAllSections($includeHeading = false, $keyNames = self::SECTIONLIST_BY_INDEX) 1299 | { 1300 | $sections = array(); 1301 | 1302 | switch ($keyNames) { 1303 | case self::SECTIONLIST_BY_INDEX: 1304 | $array = array_keys($this->sections->byIndex); 1305 | break; 1306 | case self::SECTIONLIST_BY_NAME: 1307 | $array = array_keys($this->sections->byName); 1308 | break; 1309 | default: 1310 | throw new \UnexpectedValueException("Unexpected keyNames parameter " . 1311 | "($keyNames) passed to WikiPage::getAllSections()"); 1312 | } 1313 | 1314 | foreach ($array as $key) { 1315 | $sections[$key] = $this->getSection($key, $includeHeading); 1316 | } 1317 | 1318 | return $sections; 1319 | } 1320 | 1321 | /* 1322 | * 1323 | * Setter methods 1324 | * 1325 | */ 1326 | 1327 | /** 1328 | * Sets the text in the page. 1329 | * Updates the starttimestamp to the timestamp after the page edit 1330 | * (if the edit is successful). 1331 | * 1332 | * Section can be the following: 1333 | * - section name (string, e.g. "History") 1334 | * - section index (int, e.g. 3) 1335 | * - a new section (the string "new") 1336 | * - the whole page (null) 1337 | * 1338 | * @param string $text The article text 1339 | * @param string $section The section to edit (whole page by default) 1340 | * @param boolean $minor True for minor edit 1341 | * @param string $summary Summary text, and section header in case 1342 | * of new section 1343 | * @return boolean True if page was edited successfully 1344 | */ 1345 | public function setText($text, $section = null, $minor = false, $summary = null) 1346 | { 1347 | $data = array( 1348 | 'title' => $this->title, 1349 | 'text' => $text, 1350 | 'md5' => md5($text), 1351 | 'bot' => "true", 1352 | 'starttimestamp' => $this->starttimestamp, 1353 | ); 1354 | 1355 | // Set options from arguments 1356 | if (!is_null($section)) { 1357 | // Obtain section index in case it is a name 1358 | $data['section'] = $this->findSection($section); 1359 | if ($data['section'] == -1) { 1360 | return false; 1361 | } 1362 | } 1363 | if ($minor) { 1364 | $data['minor'] = $minor; 1365 | } 1366 | if (!is_null($summary)) { 1367 | $data['summary'] = $summary; 1368 | } 1369 | 1370 | // Make sure we don't create a page by accident or overwrite another one 1371 | if (!$this->exists) { 1372 | $data['createonly'] = "true"; // createonly if not exists 1373 | } else { 1374 | $data['nocreate'] = "true"; // Don't create, it should exist 1375 | } 1376 | 1377 | $r = $this->wikimate->edit($data); // The edit query 1378 | 1379 | // Check if it worked 1380 | if (isset($r['edit']['result']) && $r['edit']['result'] == 'Success') { 1381 | $this->exists = true; 1382 | 1383 | if (is_null($section)) { 1384 | $this->text = $text; 1385 | } 1386 | 1387 | // Get the new starttimestamp 1388 | $data = array( 1389 | 'titles' => $this->title, 1390 | 'prop' => 'info', 1391 | 'curtimestamp' => 1, 1392 | ); 1393 | 1394 | $r = $this->wikimate->query($data); 1395 | 1396 | // Check for errors 1397 | if (isset($r['error'])) { 1398 | $this->error = $r['error']; // Set the error if there was one 1399 | return false; 1400 | } else { 1401 | $this->error = null; // Reset the error status 1402 | } 1403 | 1404 | $this->starttimestamp = $r['curtimestamp']; // Update the starttimestamp 1405 | return true; 1406 | } 1407 | 1408 | // Return error response 1409 | if (isset($r['error'])) { 1410 | $this->error = $r['error']; 1411 | } else { 1412 | $this->error = array(); 1413 | if (isset($r['edit']['captcha'])) { 1414 | $this->error['page'] = 'Edit denied by CAPTCHA'; 1415 | } else { 1416 | $this->error['page'] = 'Unexpected edit response: ' . $r['edit']['result']; 1417 | } 1418 | } 1419 | return false; 1420 | } 1421 | 1422 | /** 1423 | * Sets the text of the given section. 1424 | * Essentially an alias of {@see WikiPage::setText()} 1425 | * with the summary and minor parameters switched. 1426 | * 1427 | * Section can be the following: 1428 | * - section name (string, e.g. "History") 1429 | * - section index (int, e.g. 3) 1430 | * - a new section (the string "new") 1431 | * - the whole page (null) 1432 | * 1433 | * @param string $text The text of the section 1434 | * @param mixed $section The section to edit (intro by default) 1435 | * @param string $summary Summary text, and section header in case 1436 | * of new section 1437 | * @param boolean $minor True for minor edit 1438 | * @return boolean True if the section was saved 1439 | */ 1440 | public function setSection($text, $section = 0, $summary = null, $minor = false) 1441 | { 1442 | return $this->setText($text, $section, $minor, $summary); 1443 | } 1444 | 1445 | /** 1446 | * Appends a new section to the page. 1447 | * Alias of {@see WikiPage::setSection()} where the section heading 1448 | * also becomes the edit summary. 1449 | * 1450 | * @param string $name The heading name for the new section 1451 | * @param string $text The text of the new section 1452 | * @return boolean True if the section was saved 1453 | */ 1454 | public function newSection($name, $text) 1455 | { 1456 | return $this->setSection($text, 'new', $name, false); 1457 | } 1458 | 1459 | /** 1460 | * Deletes the page. 1461 | * 1462 | * @param string $reason Reason for the deletion 1463 | * @param boolean $talktoo True to delete talk page too (MW 1.38+) 1464 | * @return boolean True if page was deleted successfully 1465 | */ 1466 | public function delete($reason = null, $talktoo = false) 1467 | { 1468 | $data = array( 1469 | 'title' => $this->title, 1470 | ); 1471 | 1472 | // Set options from arguments 1473 | if (!is_null($reason)) { 1474 | $data['reason'] = $reason; 1475 | } 1476 | // Check if deletetalk parameter is supported (it is since MediaWiki v1.38) 1477 | if ($this->wikimate->supportsModuleParam('delete', 'deletetalk') && $talktoo) { 1478 | $data['deletetalk'] = $talktoo; 1479 | } 1480 | 1481 | $r = $this->wikimate->delete($data); // The delete query 1482 | 1483 | // Check if it worked 1484 | if (isset($r['delete'])) { 1485 | $this->exists = false; // The page was deleted 1486 | 1487 | $this->error = null; // Reset the error status 1488 | return true; 1489 | } 1490 | 1491 | $this->error = $r['error']; // Return error response 1492 | return false; 1493 | } 1494 | 1495 | /* 1496 | * 1497 | * Private methods 1498 | * 1499 | */ 1500 | 1501 | /** 1502 | * Finds a section's index by name. 1503 | * If a section index or 'new' is passed, it is returned directly. 1504 | * 1505 | * @param mixed $section The section name or index to find 1506 | * @return mixed The section index, or -1 if not found 1507 | */ 1508 | private function findSection($section) 1509 | { 1510 | // Check section type 1511 | if (is_int($section) || $section === 'new') { 1512 | return $section; 1513 | } elseif (is_string($section)) { 1514 | // Search section names for related index 1515 | $sections = array_keys($this->sections->byName); 1516 | $index = array_search($section, $sections); 1517 | 1518 | // Return index if found 1519 | if ($index !== false) { 1520 | return $index; 1521 | } 1522 | } 1523 | 1524 | // Return error message and value 1525 | $this->error = array(); 1526 | $this->error['page'] = "Section '$section' was not found on this page"; 1527 | return -1; 1528 | } 1529 | } 1530 | 1531 | 1532 | /** 1533 | * Models a wiki file that can have its properties retrieved and 1534 | * its contents downloaded and uploaded. 1535 | * All properties pertain to the current revision of the file. 1536 | * 1537 | * @author Robert McLeod & Frans P. de Vries 1538 | * @since 0.12.0 October 2016 1539 | */ 1540 | class WikiFile 1541 | { 1542 | /** 1543 | * The name of the file 1544 | * 1545 | * @var string|null 1546 | */ 1547 | protected $filename = null; 1548 | 1549 | /** 1550 | * Wikimate object for API requests 1551 | * 1552 | * @var Wikimate|null 1553 | */ 1554 | protected $wikimate = null; 1555 | 1556 | /** 1557 | * Whether the file exists 1558 | * 1559 | * @var boolean 1560 | */ 1561 | protected $exists = false; 1562 | 1563 | /** 1564 | * Whether the file is invalid 1565 | * 1566 | * @var boolean 1567 | */ 1568 | protected $invalid = false; 1569 | 1570 | /** 1571 | * Error array with API and WikiFile errors 1572 | * 1573 | * @var array|null 1574 | */ 1575 | protected $error = null; 1576 | 1577 | /** 1578 | * Image info for the current file revision 1579 | * 1580 | * @var array|null 1581 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Imageinfo 1582 | */ 1583 | protected $info = null; 1584 | 1585 | /** 1586 | * Image info for all file revisions 1587 | * 1588 | * @var array|null 1589 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Imageinfo 1590 | */ 1591 | protected $history = null; 1592 | 1593 | /* 1594 | * 1595 | * Magic methods 1596 | * 1597 | */ 1598 | 1599 | /** 1600 | * Constructs a WikiFile object from the filename given 1601 | * and associates it with the passed Wikimate object. 1602 | * 1603 | * @param string $filename Name of the wiki file 1604 | * @param Wikimate $wikimate Wikimate object 1605 | */ 1606 | public function __construct($filename, $wikimate) 1607 | { 1608 | $this->wikimate = $wikimate; 1609 | $this->filename = $filename; 1610 | $this->info = $this->getInfo(true); 1611 | 1612 | if ($this->invalid) { 1613 | $this->error['file'] = 'Invalid filename - cannot create WikiFile'; 1614 | } 1615 | } 1616 | 1617 | /** 1618 | * Forgets all object properties. 1619 | */ 1620 | public function __destruct() 1621 | { 1622 | $this->filename = null; 1623 | $this->wikimate = null; 1624 | $this->exists = false; 1625 | $this->invalid = false; 1626 | $this->error = null; 1627 | $this->info = null; 1628 | $this->history = null; 1629 | } 1630 | 1631 | /** 1632 | * Returns the file existence status. 1633 | * 1634 | * @return boolean True if file exists 1635 | */ 1636 | public function exists() 1637 | { 1638 | return $this->exists; 1639 | } 1640 | 1641 | /** 1642 | * Forgets all object properties. 1643 | * Alias of {@see WikiFile::__destruct()}. 1644 | * 1645 | * @return void 1646 | */ 1647 | public function destroy() 1648 | { 1649 | $this->__destruct(); 1650 | } 1651 | 1652 | /* 1653 | * 1654 | * File meta methods 1655 | * 1656 | */ 1657 | 1658 | /** 1659 | * Returns the latest error if there is one. 1660 | * 1661 | * @return mixed The error array, or null if no error 1662 | */ 1663 | public function getError() 1664 | { 1665 | return $this->error; 1666 | } 1667 | 1668 | /** 1669 | * Returns the name of this file. 1670 | * 1671 | * @return string The name of this file 1672 | */ 1673 | public function getFilename() 1674 | { 1675 | return $this->filename; 1676 | } 1677 | 1678 | /* 1679 | * 1680 | * Getter methods 1681 | * 1682 | */ 1683 | 1684 | /** 1685 | * Gets the information of the file. 1686 | * If refresh is true, then this method will query the wiki API 1687 | * again for the file details. 1688 | * 1689 | * @param boolean $refresh True to query the wiki API again 1690 | * @param array $history An optional array of revision history parameters 1691 | * @return mixed The info of the file (array), or null if error 1692 | */ 1693 | public function getInfo($refresh = false, $history = null) 1694 | { 1695 | if ($refresh) { // We want to query the API 1696 | // Specify relevant file properties to retrieve 1697 | $data = array( 1698 | 'titles' => 'File:' . $this->filename, 1699 | 'prop' => 'info|imageinfo', 1700 | 'iiprop' => 'bitdepth|canonicaltitle|comment|parsedcomment|' 1701 | . 'commonmetadata|metadata|extmetadata|mediatype|' 1702 | . 'mime|thumbmime|sha1|size|timestamp|url|user|userid', 1703 | ); 1704 | // Add optional history parameters 1705 | if (is_array($history)) { 1706 | foreach ($history as $key => $val) { 1707 | $data[$key] = $val; 1708 | } 1709 | // Retrieve archive name property as well 1710 | $data['iiprop'] .= '|archivename'; 1711 | } 1712 | 1713 | $r = $this->wikimate->query($data); // Run the query 1714 | 1715 | // Check for errors 1716 | if (isset($r['error'])) { 1717 | $this->error = $r['error']; // Set the error if there was one 1718 | return null; 1719 | } else { 1720 | $this->error = null; // Reset the error status 1721 | } 1722 | 1723 | // Get the page (there should only be one) 1724 | $page = array_pop($r['query']['pages']); 1725 | unset($r, $data); 1726 | 1727 | // Abort if invalid file title 1728 | if (isset($page['invalid'])) { 1729 | $this->invalid = true; 1730 | return null; 1731 | } 1732 | 1733 | // Check that file is present and has info 1734 | if (!isset($page['missing']) && isset($page['imageinfo'])) { 1735 | // Update the existence if the file is there 1736 | $this->exists = true; 1737 | // Put the content into info & history 1738 | $this->info = $page['imageinfo'][0]; 1739 | $this->history = $page['imageinfo']; 1740 | } 1741 | unset($page); 1742 | } 1743 | 1744 | return $this->info; // Return the info in any case 1745 | } 1746 | 1747 | /** 1748 | * Returns the anonymous flag of this file, 1749 | * or a given revision of it, if specified. 1750 | * If true, then getUser()'s value represents an anonymous IP address. 1751 | * 1752 | * @param mixed $revision The index or timestamp of the revision (optional) 1753 | * @return mixed The anonymous flag of this file (boolean), 1754 | * or null if revision not found 1755 | */ 1756 | public function getAnon($revision = null) 1757 | { 1758 | // Without revision, use current info 1759 | if (!isset($revision)) { 1760 | // Check for anon flag 1761 | return isset($this->info['anon']) ? true : false; 1762 | } 1763 | 1764 | // Obtain the properties of the revision 1765 | if (($info = $this->getRevision($revision)) === null) { 1766 | return null; 1767 | } 1768 | 1769 | // Check for anon flag 1770 | return isset($info['anon']) ? true : false; 1771 | } 1772 | 1773 | /** 1774 | * Returns the aspect ratio of this image, 1775 | * or a given revision of it, if specified. 1776 | * Returns 0 if file is not an image (and thus has no dimensions). 1777 | * 1778 | * @param mixed $revision The index or timestamp of the revision (optional) 1779 | * @return float The aspect ratio of this image, or 0 if no dimensions, 1780 | * or -1 if revision not found 1781 | */ 1782 | public function getAspectRatio($revision = null) 1783 | { 1784 | // Without revision, use current info 1785 | if (!isset($revision)) { 1786 | // Check for dimensions 1787 | if ($this->info['height'] > 0) { 1788 | return $this->info['width'] / $this->info['height']; 1789 | } else { 1790 | return 0; 1791 | } 1792 | } 1793 | 1794 | // Obtain the properties of the revision 1795 | if (($info = $this->getRevision($revision)) === null) { 1796 | return -1; 1797 | } 1798 | 1799 | // Check for dimensions 1800 | if (isset($info['height'])) { 1801 | return $info['width'] / $info['height']; 1802 | } else { 1803 | return 0; 1804 | } 1805 | } 1806 | 1807 | /** 1808 | * Returns the bit depth of this file, 1809 | * or a given revision of it, if specified. 1810 | * 1811 | * @param mixed $revision The index or timestamp of the revision (optional) 1812 | * @return integer The bit depth of this file, 1813 | * or -1 if revision not found 1814 | */ 1815 | public function getBitDepth($revision = null) 1816 | { 1817 | // Without revision, use current info 1818 | if (!isset($revision)) { 1819 | return (int)$this->info['bitdepth']; 1820 | } 1821 | 1822 | // Obtain the properties of the revision 1823 | if (($info = $this->getRevision($revision)) === null) { 1824 | return -1; 1825 | } 1826 | 1827 | return (int)$info['bitdepth']; 1828 | } 1829 | 1830 | /** 1831 | * Returns the canonical title of this file, 1832 | * or a given revision of it, if specified. 1833 | * 1834 | * @param mixed $revision The index or timestamp of the revision (optional) 1835 | * @return mixed The canonical title of this file (string), 1836 | * or null if revision not found 1837 | */ 1838 | public function getCanonicalTitle($revision = null) 1839 | { 1840 | // Without revision, use current info 1841 | if (!isset($revision)) { 1842 | return $this->info['canonicaltitle']; 1843 | } 1844 | 1845 | // Obtain the properties of the revision 1846 | if (($info = $this->getRevision($revision)) === null) { 1847 | return null; 1848 | } 1849 | 1850 | return $info['canonicaltitle']; 1851 | } 1852 | 1853 | /** 1854 | * Returns the edit comment of this file, 1855 | * or a given revision of it, if specified. 1856 | * 1857 | * @param mixed $revision The index or timestamp of the revision (optional) 1858 | * @return mixed The edit comment of this file (string), 1859 | * or null if revision not found 1860 | */ 1861 | public function getComment($revision = null) 1862 | { 1863 | // Without revision, use current info 1864 | if (!isset($revision)) { 1865 | return $this->info['comment']; 1866 | } 1867 | 1868 | // Obtain the properties of the revision 1869 | if (($info = $this->getRevision($revision)) === null) { 1870 | return null; 1871 | } 1872 | 1873 | return $info['comment']; 1874 | } 1875 | 1876 | /** 1877 | * Returns the common metadata of this file, 1878 | * or a given revision of it, if specified. 1879 | * 1880 | * @param mixed $revision The index or timestamp of the revision (optional) 1881 | * @return mixed The common metadata of this file (array), 1882 | * or null if revision not found 1883 | */ 1884 | public function getCommonMetadata($revision = null) 1885 | { 1886 | // Without revision, use current info 1887 | if (!isset($revision)) { 1888 | return $this->info['commonmetadata']; 1889 | } 1890 | 1891 | // Obtain the properties of the revision 1892 | if (($info = $this->getRevision($revision)) === null) { 1893 | return null; 1894 | } 1895 | 1896 | return $info['commonmetadata']; 1897 | } 1898 | 1899 | /** 1900 | * Returns the description URL of this file, 1901 | * or a given revision of it, if specified. 1902 | * 1903 | * @param mixed $revision The index or timestamp of the revision (optional) 1904 | * @return mixed The description URL of this file (string), 1905 | * or null if revision not found 1906 | */ 1907 | public function getDescriptionUrl($revision = null) 1908 | { 1909 | // Without revision, use current info 1910 | if (!isset($revision)) { 1911 | return $this->info['descriptionurl']; 1912 | } 1913 | 1914 | // Obtain the properties of the revision 1915 | if (($info = $this->getRevision($revision)) === null) { 1916 | return null; 1917 | } 1918 | 1919 | return $info['descriptionurl']; 1920 | } 1921 | 1922 | /** 1923 | * Returns the extended metadata of this file, 1924 | * or a given revision of it, if specified. 1925 | * 1926 | * @param mixed $revision The index or timestamp of the revision (optional) 1927 | * @return mixed The extended metadata of this file (array), 1928 | * or null if revision not found 1929 | */ 1930 | public function getExtendedMetadata($revision = null) 1931 | { 1932 | // Without revision, use current info 1933 | if (!isset($revision)) { 1934 | return $this->info['extmetadata']; 1935 | } 1936 | 1937 | // Obtain the properties of the revision 1938 | if (($info = $this->getRevision($revision)) === null) { 1939 | return null; 1940 | } 1941 | 1942 | return $info['extmetadata']; 1943 | } 1944 | 1945 | /** 1946 | * Returns the height of this file, 1947 | * or a given revision of it, if specified. 1948 | * 1949 | * @param mixed $revision The index or timestamp of the revision (optional) 1950 | * @return integer The height of this file, or -1 if revision not found 1951 | */ 1952 | public function getHeight($revision = null) 1953 | { 1954 | // Without revision, use current info 1955 | if (!isset($revision)) { 1956 | return (int)$this->info['height']; 1957 | } 1958 | 1959 | // Obtain the properties of the revision 1960 | if (($info = $this->getRevision($revision)) === null) { 1961 | return -1; 1962 | } 1963 | 1964 | return (int)$info['height']; 1965 | } 1966 | 1967 | /** 1968 | * Returns the media type of this file, 1969 | * or a given revision of it, if specified. 1970 | * 1971 | * @param mixed $revision The index or timestamp of the revision (optional) 1972 | * @return mixed The media type of this file (string), 1973 | * or null if revision not found 1974 | */ 1975 | public function getMediaType($revision = null) 1976 | { 1977 | // Without revision, use current info 1978 | if (!isset($revision)) { 1979 | return $this->info['mediatype']; 1980 | } 1981 | 1982 | // Obtain the properties of the revision 1983 | if (($info = $this->getRevision($revision)) === null) { 1984 | return null; 1985 | } 1986 | 1987 | return $info['mediatype']; 1988 | } 1989 | 1990 | /** 1991 | * Returns the Exif metadata of this file, 1992 | * or a given revision of it, if specified. 1993 | * 1994 | * @param mixed $revision The index or timestamp of the revision (optional) 1995 | * @return mixed The metadata of this file (array), 1996 | * or null if revision not found 1997 | */ 1998 | public function getMetadata($revision = null) 1999 | { 2000 | // Without revision, use current info 2001 | if (!isset($revision)) { 2002 | return $this->info['metadata']; 2003 | } 2004 | 2005 | // Obtain the properties of the revision 2006 | if (($info = $this->getRevision($revision)) === null) { 2007 | return null; 2008 | } 2009 | 2010 | return $info['metadata']; 2011 | } 2012 | 2013 | /** 2014 | * Returns the MIME type of this file, 2015 | * or a given revision of it, if specified. 2016 | * 2017 | * @param mixed $revision The index or timestamp of the revision (optional) 2018 | * @return mixed The MIME type of this file (string), 2019 | * or null if revision not found 2020 | */ 2021 | public function getMime($revision = null) 2022 | { 2023 | // Without revision, use current info 2024 | if (!isset($revision)) { 2025 | return $this->info['mime']; 2026 | } 2027 | 2028 | // Obtain the properties of the revision 2029 | if (($info = $this->getRevision($revision)) === null) { 2030 | return null; 2031 | } 2032 | 2033 | return $info['mime']; 2034 | } 2035 | 2036 | /** 2037 | * Returns the parsed edit comment of this file, 2038 | * or a given revision of it, if specified. 2039 | * 2040 | * @param mixed $revision The index or timestamp of the revision (optional) 2041 | * @return mixed The parsed edit comment of this file (string), 2042 | * or null if revision not found 2043 | */ 2044 | public function getParsedComment($revision = null) 2045 | { 2046 | // Without revision, use current info 2047 | if (!isset($revision)) { 2048 | return $this->info['parsedcomment']; 2049 | } 2050 | 2051 | // Obtain the properties of the revision 2052 | if (($info = $this->getRevision($revision)) === null) { 2053 | return null; 2054 | } 2055 | 2056 | return $info['parsedcomment']; 2057 | } 2058 | 2059 | /** 2060 | * Returns the SHA-1 hash of this file, 2061 | * or a given revision of it, if specified. 2062 | * 2063 | * @param mixed $revision The index or timestamp of the revision (optional) 2064 | * @return mixed The SHA-1 hash of this file (string), 2065 | * or null if revision not found 2066 | */ 2067 | public function getSha1($revision = null) 2068 | { 2069 | // Without revision, use current info 2070 | if (!isset($revision)) { 2071 | return $this->info['sha1']; 2072 | } 2073 | 2074 | // Obtain the properties of the revision 2075 | if (($info = $this->getRevision($revision)) === null) { 2076 | return null; 2077 | } 2078 | 2079 | return $info['sha1']; 2080 | } 2081 | 2082 | /** 2083 | * Returns the size of this file, 2084 | * or a given revision of it, if specified. 2085 | * 2086 | * @param mixed $revision The index or timestamp of the revision (optional) 2087 | * @return integer The size of this file, or -1 if revision not found 2088 | */ 2089 | public function getSize($revision = null) 2090 | { 2091 | // Without revision, use current info 2092 | if (!isset($revision)) { 2093 | return (int)$this->info['size']; 2094 | } 2095 | 2096 | // Obtain the properties of the revision 2097 | if (($info = $this->getRevision($revision)) === null) { 2098 | return -1; 2099 | } 2100 | 2101 | return (int)$info['size']; 2102 | } 2103 | 2104 | /** 2105 | * Returns the MIME type of this file's thumbnail, 2106 | * or a given revision of it, if specified. 2107 | * Returns empty string if property not available for this file type. 2108 | * 2109 | * @param mixed $revision The index or timestamp of the revision (optional) 2110 | * @return mixed The MIME type of this file's thumbnail (string), 2111 | * or '' if unavailable, or null if revision not found 2112 | */ 2113 | public function getThumbMime($revision = null) 2114 | { 2115 | // Without revision, use current info 2116 | if (!isset($revision)) { 2117 | return (isset($this->info['thumbmime']) ? $this->info['thumbmime'] : ''); 2118 | } 2119 | 2120 | // Obtain the properties of the revision 2121 | if (($info = $this->getRevision($revision)) === null) { 2122 | return null; 2123 | } 2124 | 2125 | // Check for thumbnail MIME type 2126 | return (isset($info['thumbmime']) ? $info['thumbmime'] : ''); 2127 | } 2128 | 2129 | /** 2130 | * Returns the timestamp of this file, 2131 | * or a given revision of it, if specified. 2132 | * 2133 | * @param mixed $revision The index or timestamp of the revision (optional) 2134 | * @return mixed The timestamp of this file (string), 2135 | * or null if revision not found 2136 | */ 2137 | public function getTimestamp($revision = null) 2138 | { 2139 | // Without revision, use current info 2140 | if (!isset($revision)) { 2141 | return $this->info['timestamp']; 2142 | } 2143 | 2144 | // Obtain the properties of the revision 2145 | if (($info = $this->getRevision($revision)) === null) { 2146 | return null; 2147 | } 2148 | 2149 | return $info['timestamp']; 2150 | } 2151 | 2152 | /** 2153 | * Returns the URL of this file, 2154 | * or a given revision of it, if specified. 2155 | * 2156 | * @param mixed $revision The index or timestamp of the revision (optional) 2157 | * @return mixed The URL of this file (string), 2158 | * or null if revision not found 2159 | */ 2160 | public function getUrl($revision = null) 2161 | { 2162 | // Without revision, use current info 2163 | if (!isset($revision)) { 2164 | return $this->info['url']; 2165 | } 2166 | 2167 | // Obtain the properties of the revision 2168 | if (($info = $this->getRevision($revision)) === null) { 2169 | return null; 2170 | } 2171 | 2172 | return $info['url']; 2173 | } 2174 | 2175 | /** 2176 | * Returns the user who uploaded this file, 2177 | * or a given revision of it, if specified. 2178 | * 2179 | * @param mixed $revision The index or timestamp of the revision (optional) 2180 | * @return mixed The user of this file (string), 2181 | * or null if revision not found 2182 | */ 2183 | public function getUser($revision = null) 2184 | { 2185 | // Without revision, use current info 2186 | if (!isset($revision)) { 2187 | return $this->info['user']; 2188 | } 2189 | 2190 | // Obtain the properties of the revision 2191 | if (($info = $this->getRevision($revision)) === null) { 2192 | return null; 2193 | } 2194 | 2195 | return $info['user']; 2196 | } 2197 | 2198 | /** 2199 | * Returns the ID of the user who uploaded this file, 2200 | * or a given revision of it, if specified. 2201 | * 2202 | * @param mixed $revision The index or timestamp of the revision (optional) 2203 | * @return integer The user ID of this file, 2204 | * or -1 if revision not found 2205 | */ 2206 | public function getUserId($revision = null) 2207 | { 2208 | // Without revision, use current info 2209 | if (!isset($revision)) { 2210 | return (int)$this->info['userid']; 2211 | } 2212 | 2213 | // Obtain the properties of the revision 2214 | if (($info = $this->getRevision($revision)) === null) { 2215 | return -1; 2216 | } 2217 | 2218 | return (int)$info['userid']; 2219 | } 2220 | 2221 | /** 2222 | * Returns the width of this file, 2223 | * or a given revision of it, if specified. 2224 | * 2225 | * @param mixed $revision The index or timestamp of the revision (optional) 2226 | * @return integer The width of this file, or -1 if revision not found 2227 | */ 2228 | public function getWidth($revision = null) 2229 | { 2230 | // Without revision, use current info 2231 | if (!isset($revision)) { 2232 | return (int)$this->info['width']; 2233 | } 2234 | 2235 | // Obtain the properties of the revision 2236 | if (($info = $this->getRevision($revision)) === null) { 2237 | return -1; 2238 | } 2239 | 2240 | return (int)$info['width']; 2241 | } 2242 | 2243 | /* 2244 | * 2245 | * File history & deletion methods 2246 | * 2247 | */ 2248 | 2249 | /** 2250 | * Returns the revision history of this file with all properties. 2251 | * The initial history at object creation contains only the 2252 | * current revision of the file. To obtain more revisions, 2253 | * set $refresh to true and also optionally set $limit and 2254 | * the timestamps. 2255 | * 2256 | * The maximum limit is 500 for user accounts and 5000 for bot accounts. 2257 | * 2258 | * Timestamps can be in several formats as described here: 2259 | * {@see https://www.mediawiki.org/w/api.php?action=help&modules=main#main.2Fdatatypes} 2260 | * 2261 | * @param boolean $refresh True to query the wiki API again 2262 | * @param integer $limit The number of file revisions to return 2263 | * (the maximum number by default) 2264 | * @param string $startts The start timestamp of the listing (optional) 2265 | * @param string $endts The end timestamp of the listing (optional) 2266 | * @return mixed The array of selected file revisions, or null if error 2267 | */ 2268 | public function getHistory($refresh = false, $limit = null, $startts = null, $endts = null) 2269 | { 2270 | if ($refresh) { // We want to query the API 2271 | // Collect optional history parameters 2272 | $history = array(); 2273 | if (!is_null($limit)) { 2274 | $history['iilimit'] = $limit; 2275 | } else { 2276 | $history['iilimit'] = 'max'; 2277 | } 2278 | if (!is_null($startts)) { 2279 | $history['iistart'] = $startts; 2280 | } 2281 | if (!is_null($endts)) { 2282 | $history['iiend'] = $endts; 2283 | } 2284 | 2285 | // Get file revision history 2286 | if ($this->getInfo($refresh, $history) === null) { 2287 | return null; 2288 | } 2289 | } 2290 | 2291 | return $this->history; 2292 | } 2293 | 2294 | /** 2295 | * Returns the properties of the specified file revision. 2296 | * 2297 | * Revision can be the following: 2298 | * - revision timestamp (string, e.g. "2001-01-15T14:56:00Z") 2299 | * - revision index (int, e.g. 3) 2300 | * 2301 | * The most recent revision has index 0, 2302 | * and it increments towards older revisions. 2303 | * A timestamp must be in ISO 8601 format. 2304 | * 2305 | * @param mixed $revision The index or timestamp of the revision 2306 | * @return mixed The properties (array), or null if not found 2307 | */ 2308 | public function getRevision($revision) 2309 | { 2310 | // Select revision by index 2311 | if (is_int($revision)) { 2312 | if (isset($this->history[$revision])) { 2313 | return $this->history[$revision]; 2314 | } 2315 | // Search revision by timestamp 2316 | } else { 2317 | foreach ($this->history as $history) { 2318 | if ($history['timestamp'] == $revision) { 2319 | return $history; 2320 | } 2321 | } 2322 | } 2323 | 2324 | // Return error message 2325 | $this->error = array(); 2326 | $this->error['file'] = "Revision '$revision' was not found for this file"; 2327 | return null; 2328 | } 2329 | 2330 | /** 2331 | * Returns the archive name of the specified file revision. 2332 | * 2333 | * Revision can be the following: 2334 | * - revision timestamp (string, e.g. "2001-01-15T14:56:00Z") 2335 | * - revision index (int, e.g. 3) 2336 | * 2337 | * The most recent revision has index 0, 2338 | * and it increments towards older revisions. 2339 | * A timestamp must be in ISO 8601 format. 2340 | * 2341 | * @param mixed $revision The index or timestamp of the revision 2342 | * @return mixed The archive name (string), or null if not found 2343 | */ 2344 | public function getArchivename($revision) 2345 | { 2346 | // Obtain the properties of the revision 2347 | if (($info = $this->getRevision($revision)) === null) { 2348 | return null; 2349 | } 2350 | 2351 | // Check for archive name 2352 | if (!isset($info['archivename'])) { 2353 | // Return error message 2354 | $this->error = array(); 2355 | $this->error['file'] = 'This revision contains no archive name'; 2356 | return null; 2357 | } 2358 | 2359 | return $info['archivename']; 2360 | } 2361 | 2362 | /** 2363 | * Deletes the file, or only an older revision of it. 2364 | * 2365 | * @param string $reason Reason for the deletion 2366 | * @param string $archivename The archive name of the older revision 2367 | * @param boolean $talktoo True to delete talk page too (MW 1.38+) 2368 | * @return boolean True if file (revision) was deleted successfully 2369 | */ 2370 | public function delete($reason = null, $archivename = null, $talktoo = false) 2371 | { 2372 | $data = array( 2373 | 'title' => 'File:' . $this->filename, 2374 | ); 2375 | 2376 | // Set options from arguments 2377 | if (!is_null($reason)) { 2378 | $data['reason'] = $reason; 2379 | } 2380 | if (!is_null($archivename)) { 2381 | $data['oldimage'] = $archivename; 2382 | } 2383 | // Check if deletetalk parameter is supported (it is since MediaWiki v1.38) 2384 | if ($this->wikimate->supportsModuleParam('delete', 'deletetalk') && $talktoo) { 2385 | $data['deletetalk'] = $talktoo; 2386 | } 2387 | 2388 | $r = $this->wikimate->delete($data); // The delete query 2389 | 2390 | // Check if it worked 2391 | if (isset($r['delete'])) { 2392 | if (is_null($archivename)) { 2393 | $this->exists = false; // The file was deleted altogether 2394 | } 2395 | 2396 | $this->error = null; // Reset the error status 2397 | return true; 2398 | } 2399 | 2400 | $this->error = $r['error']; // Return error response 2401 | return false; 2402 | } 2403 | 2404 | /** 2405 | * Reverts file to an older revision. 2406 | * 2407 | * @param string $archivename The archive name of the older revision 2408 | * @param string $reason Reason for the revert 2409 | * @return boolean True if reverting was successful 2410 | * @link https://www.mediawiki.org/wiki/Special:MyLanguage/API:Filerevert 2411 | */ 2412 | public function revert($archivename, $reason = null) 2413 | { 2414 | // Set options from arguments 2415 | $data = array( 2416 | 'filename' => $this->filename, 2417 | 'archivename' => $archivename, 2418 | ); 2419 | if (!is_null($reason)) { 2420 | $data['comment'] = $reason; 2421 | } 2422 | 2423 | $r = $this->wikimate->filerevert($data); // The revert query 2424 | 2425 | // Check if it worked 2426 | if (isset($r['filerevert']['result']) && $r['filerevert']['result'] == 'Success') { 2427 | $this->error = null; // Reset the error status 2428 | return true; 2429 | } 2430 | 2431 | $this->error = $r['error']; // Return error response 2432 | return false; 2433 | } 2434 | 2435 | /* 2436 | * 2437 | * File contents methods 2438 | * 2439 | */ 2440 | 2441 | /** 2442 | * Downloads and returns the current file's contents, 2443 | * or null if an error occurs. 2444 | * 2445 | * @return mixed Contents (string), or null if error 2446 | */ 2447 | public function downloadData() 2448 | { 2449 | // Download file, or handle error 2450 | $data = $this->wikimate->download($this->getUrl()); 2451 | if ($data === null) { 2452 | $this->error = $this->wikimate->getError(); // Copy error if there was one 2453 | } else { 2454 | $this->error = null; // Reset the error status 2455 | } 2456 | 2457 | return $data; 2458 | } 2459 | 2460 | /** 2461 | * Downloads the current file's contents and writes it to the given path. 2462 | * 2463 | * @param string $path The file path to write to 2464 | * @return boolean True if path was written successfully 2465 | */ 2466 | public function downloadFile($path) 2467 | { 2468 | // Download contents of current file 2469 | if (($data = $this->downloadData()) === null) { 2470 | return false; 2471 | } 2472 | 2473 | // Write contents to specified path 2474 | if (@file_put_contents($path, $data) === false) { 2475 | $this->error = array(); 2476 | $this->error['file'] = "Unable to write file '$path'"; 2477 | return false; 2478 | } 2479 | 2480 | return true; 2481 | } 2482 | 2483 | /** 2484 | * Uploads to the current file using the given parameters. 2485 | * $text is only used for the page contents of a new file, 2486 | * not an existing one (update that via {@see WikiPage::setText()}). 2487 | * If no $text is specified, $comment will be used as new page text. 2488 | * 2489 | * @param array $params The upload parameters 2490 | * @param string $comment Upload comment for the file 2491 | * @param string $text The article text for the file page 2492 | * @param boolean $overwrite True to overwrite existing file 2493 | * @return boolean True if uploading was successful 2494 | */ 2495 | private function uploadCommon(array $params, $comment, $text = null, $overwrite = false) 2496 | { 2497 | // Check whether to overwrite existing file 2498 | if ($this->exists && !$overwrite) { 2499 | $this->error = array(); 2500 | $this->error['file'] = 'Cannot overwrite existing file'; 2501 | return false; 2502 | } 2503 | 2504 | // Collect upload parameters 2505 | $params['filename'] = $this->filename; 2506 | $params['comment'] = $comment; 2507 | $params['ignorewarnings'] = $overwrite; 2508 | if (!is_null($text)) { 2509 | $params['text'] = $text; 2510 | } 2511 | 2512 | // Upload file, or handle error 2513 | $r = $this->wikimate->upload($params); 2514 | 2515 | if (isset($r['upload']['result']) && $r['upload']['result'] == 'Success') { 2516 | // Update the file's properties 2517 | $this->info = $r['upload']['imageinfo']; 2518 | 2519 | $this->error = null; // Reset the error status 2520 | return true; 2521 | } 2522 | 2523 | // Return error response 2524 | if (isset($r['error'])) { 2525 | $this->error = $r['error']; 2526 | } else { 2527 | $this->error = array(); 2528 | $this->error['file'] = 'Unexpected upload response: ' . $r['upload']['result']; 2529 | } 2530 | return false; 2531 | } 2532 | 2533 | /** 2534 | * Uploads the given contents to the current file. 2535 | * $text is only used for the page contents of a new file, 2536 | * not an existing one (update that via {@see WikiPage::setText()}). 2537 | * If no $text is specified, $comment will be used as new page text. 2538 | * 2539 | * @param string $data The data to upload 2540 | * @param string $comment Upload comment for the file 2541 | * @param string $text The article text for the file page 2542 | * @param boolean $overwrite True to overwrite existing file 2543 | * @return boolean True if uploading was successful 2544 | */ 2545 | public function uploadData($data, $comment, $text = null, $overwrite = false) 2546 | { 2547 | // Collect upload parameter 2548 | $params = array( 2549 | 'file' => $data, 2550 | ); 2551 | 2552 | // Upload contents to current file 2553 | return $this->uploadCommon($params, $comment, $text, $overwrite); 2554 | } 2555 | 2556 | /** 2557 | * Reads contents from the given path and uploads it to the current file. 2558 | * $text is only used for the page contents of a new file, 2559 | * not an existing one (update that via {@see WikiPage::setText()}). 2560 | * If no $text is specified, $comment will be used as new page text. 2561 | * 2562 | * @param string $path The file path to upload 2563 | * @param string $comment Upload comment for the file 2564 | * @param string $text The article text for the file page 2565 | * @param boolean $overwrite True to overwrite existing file 2566 | * @return boolean True if uploading was successful 2567 | */ 2568 | public function uploadFile($path, $comment, $text = null, $overwrite = false) 2569 | { 2570 | // Read contents from specified path 2571 | if (($data = @file_get_contents($path)) === false) { 2572 | $this->error = array(); 2573 | $this->error['file'] = "Unable to read file '$path'"; 2574 | return false; 2575 | } 2576 | 2577 | // Upload contents to current file 2578 | return $this->uploadData($data, $comment, $text, $overwrite); 2579 | } 2580 | 2581 | /** 2582 | * Uploads file contents from the given URL to the current file. 2583 | * $text is only used for the page contents of a new file, 2584 | * not an existing one (update that via {@see WikiPage::setText()}). 2585 | * If no $text is specified, $comment will be used as new page text. 2586 | * 2587 | * @param string $url The URL from which to upload 2588 | * @param string $comment Upload comment for the file 2589 | * @param string $text The article text for the file page 2590 | * @param boolean $overwrite True to overwrite existing file 2591 | * @return boolean True if uploading was successful 2592 | */ 2593 | public function uploadFromUrl($url, $comment, $text = null, $overwrite = false) 2594 | { 2595 | // Collect upload parameter 2596 | $params = array( 2597 | 'url' => $url, 2598 | ); 2599 | 2600 | // Upload URL to current file 2601 | return $this->uploadCommon($params, $comment, $text, $overwrite); 2602 | } 2603 | } 2604 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hamstar/wikimate", 3 | "type": "library", 4 | "description": "Wikimate is a wrapper for the MediaWiki API that aims to be very easy to use.", 5 | "license": "MIT", 6 | "homepage": "https://github.com/hamstar/Wikimate", 7 | "authors": [ 8 | { 9 | "name": "Robert McLeod", 10 | "email": "hamstar@telescum.co.nz", 11 | "homepage": "https://github.com/hamstar" 12 | }, 13 | { 14 | "name": "Frans P. de Vries", 15 | "homepage": "https://github.com/xymph" 16 | }, 17 | { 18 | "name" : "Contributors", 19 | "homepage" : "https://github.com/hamstar/Wikimate/graphs/contributors" 20 | } 21 | ], 22 | "autoload": { 23 | "classmap": [ 24 | "Wikimate.php" 25 | ] 26 | }, 27 | "require": { 28 | "rmccue/requests": "^2.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "1cc8fd4a2bc2faaa80d3ecd28f21dcca", 8 | "packages": [ 9 | { 10 | "name": "rmccue/requests", 11 | "version": "v1.8.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/WordPress/Requests.git", 15 | "reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/WordPress/Requests/zipball/afbe4790e4def03581c4a0963a1e8aa01f6030f1", 20 | "reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.2" 25 | }, 26 | "require-dev": { 27 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7", 28 | "php-parallel-lint/php-console-highlighter": "^0.5.0", 29 | "php-parallel-lint/php-parallel-lint": "^1.3", 30 | "phpcompatibility/php-compatibility": "^9.0", 31 | "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5", 32 | "requests/test-server": "dev-master", 33 | "squizlabs/php_codesniffer": "^3.5", 34 | "wp-coding-standards/wpcs": "^2.0" 35 | }, 36 | "type": "library", 37 | "autoload": { 38 | "psr-0": { 39 | "Requests": "library/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "ISC" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Ryan McCue", 49 | "homepage": "http://ryanmccue.info" 50 | } 51 | ], 52 | "description": "A HTTP library written in PHP, for human beings.", 53 | "homepage": "http://github.com/WordPress/Requests", 54 | "keywords": [ 55 | "curl", 56 | "fsockopen", 57 | "http", 58 | "idna", 59 | "ipv6", 60 | "iri", 61 | "sockets" 62 | ], 63 | "support": { 64 | "issues": "https://github.com/WordPress/Requests/issues", 65 | "source": "https://github.com/WordPress/Requests/tree/v1.8.0" 66 | }, 67 | "time": "2021-04-27T11:05:25+00:00" 68 | } 69 | ], 70 | "packages-dev": [], 71 | "aliases": [], 72 | "minimum-stability": "stable", 73 | "stability-flags": [], 74 | "prefer-stable": false, 75 | "prefer-lowest": false, 76 | "platform": [], 77 | "platform-dev": [], 78 | "plugin-api-version": "2.0.0" 79 | } 80 | -------------------------------------------------------------------------------- /examples.php: -------------------------------------------------------------------------------- 1 | setDebugMode(TRUE); 16 | 17 | echo "Attempting to log in . . . "; 18 | if ($wiki->login($username, $password)) { 19 | echo "Success.\n"; 20 | } else { 21 | $error = $wiki->getError(); 22 | echo "\nWikimate error: ".$error['auth']."\n"; 23 | exit(1); 24 | } 25 | 26 | echo "Fetching 'Sausages'...\n"; 27 | $page = $wiki->getPage('Sausages'); 28 | 29 | // check if the page exists or not 30 | if (!$page->exists() ) { 31 | echo "'Sausages' doesn't exist. Creating...\n"; 32 | // compile initial page text 33 | $pagetext = "Intro about '''sausages'''.\n"; 34 | $pagetext .= "\n== Meat ==\n"; 35 | $pagetext .= "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"; 36 | $pagetext .= "\n== Veggie ==\n"; 37 | $pagetext .= "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n"; 38 | // store page and check for error 39 | if ($page->setText($pagetext, null, false, 'Create initial page')) { 40 | echo "\n'Sausages' created.\n"; 41 | } else { 42 | $error = $page->getError(); 43 | echo "\nError: " . print_r($error, true) . "\n"; 44 | } 45 | } else { 46 | // get the page title 47 | echo "Title: ".$page->getTitle()."\n"; 48 | // get the number of sections on the page 49 | echo "Number of sections: ".$page->getNumSections()."\n"; 50 | // get an array of where each section starts and its length 51 | echo "Section offsets: ".print_r($page->getSectionOffsets(), true)."\n"; 52 | // get and update intro section 53 | $introtext = $page->getSection(0); 54 | $introtext .= "\nMore about sausage variants.\n"; 55 | // store intro and check for error 56 | if ($page->setSection($introtext, 0, 'Update intro section', true)) { 57 | echo "\n'Sausages' intro updated.\n"; 58 | } else { 59 | $error = $page->getError(); 60 | echo "\nError: " . print_r($error, true) . "\n"; 61 | } 62 | } 63 | --------------------------------------------------------------------------------