diff --git a/.github/workflows/dependencies b/.github/workflows/dependencies index f9af29f..980cf39 100644 --- a/.github/workflows/dependencies +++ b/.github/workflows/dependencies @@ -1,3 +1,4 @@ CategoryTree Disambiguator +PageViewInfo Wikibase diff --git a/.phan/config.php b/.phan/config.php index f59ba0b..6489702 100644 --- a/.phan/config.php +++ b/.phan/config.php @@ -7,6 +7,7 @@ [ '../../extensions/CategoryTree', '../../extensions/Disambiguator', + '../../extensions/PageViewInfo', '../../extensions/Wikibase', ] ); @@ -16,6 +17,7 @@ [ '../../extensions/CategoryTree', '../../extensions/Disambiguator', + '../../extensions/PageViewInfo', '../../extensions/Wikibase', ] ); diff --git a/composer.json b/composer.json index ee525ae..ff2b176 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,9 @@ { "name": "femiwiki/unified-extension-for-femiwiki", "description": "an extension for femiwiki.", + "require": { + "google/apiclient": "^2.9" + }, "require-dev": { "mediawiki/mediawiki-codesniffer": "36.0.0", "mediawiki/mediawiki-phan-config": "0.10.6", diff --git a/composer.lock b/composer.lock index 15647e0..1f9e306 100644 --- a/composer.lock +++ b/composer.lock @@ -4,21 +4,1048 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "362089d66fbda2095c9870fa3f3f015d", - "packages": [], + "content-hash": "dbd480929684b6451ebbc666dab907c8", + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "3c2d70f2e64e2922345e89f2ceae47d2463faae1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/3c2d70f2e64e2922345e89f2ceae47d2463faae1", + "reference": "3c2d70f2e64e2922345e89f2ceae47d2463faae1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <=9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v5.3.0" + }, + "time": "2021-05-20T17:37:02+00:00" + }, + { + "name": "google/apiclient", + "version": "v2.9.2", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client.git", + "reference": "e9ef4c26a044b8d39a46bcf296be795fe24a1849" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/e9ef4c26a044b8d39a46bcf296be795fe24a1849", + "reference": "e9ef4c26a044b8d39a46bcf296be795fe24a1849", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~2.0||~3.0||~4.0||~5.0", + "google/apiclient-services": "~0.13", + "google/auth": "^1.10", + "guzzlehttp/guzzle": "~5.3.3||~6.0||~7.0", + "guzzlehttp/psr7": "^1.2", + "monolog/monolog": "^1.17|^2.0", + "php": "^5.6|^7.0|^8.0", + "phpseclib/phpseclib": "~2.0||^3.0.2" + }, + "require-dev": { + "cache/filesystem-adapter": "^0.3.2|^1.1", + "composer/composer": "^1.10.22", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "phpcompatibility/php-compatibility": "^9.2", + "phpunit/phpunit": "^5.7||^8.5.13", + "squizlabs/php_codesniffer": "~2.3", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "suggest": { + "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Google\\": "src/" + }, + "files": [ + "src/aliases.php" + ], + "classmap": [ + "src/aliases.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client/issues", + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.9.2" + }, + "time": "2021-06-09T22:15:08+00:00" + }, + { + "name": "google/apiclient-services", + "version": "v0.181.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client-services.git", + "reference": "a4ea5fd96887d654d10d446b239e1ff60240e2c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/a4ea5fd96887d654d10d446b239e1ff60240e2c1", + "reference": "a4ea5fd96887d654d10d446b239e1ff60240e2c1", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5.7||^8.5.13" + }, + "type": "library", + "autoload": { + "psr-0": { + "Google_Service_": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client-services/issues", + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.181.0" + }, + "time": "2021-06-13T11:20:02+00:00" + }, + { + "name": "google/auth", + "version": "v1.15.1", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "4e0c9367719df9703e96f5ad613041b87742471c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/4e0c9367719df9703e96f5ad613041b87742471c", + "reference": "4e0c9367719df9703e96f5ad613041b87742471c", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~2.0|~3.0|~4.0|~5.0", + "guzzlehttp/guzzle": "^5.3.1|^6.2.1|^7.0", + "guzzlehttp/psr7": "^1.2", + "php": ">=5.4", + "psr/cache": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "guzzlehttp/promises": "0.1.1|^1.3", + "kelvinmo/simplejwt": "^0.2.5|^0.5.1", + "phpseclib/phpseclib": "^2.0.31", + "phpunit/phpunit": "^4.8.36|^5.7", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "http://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "support": { + "docs": "https://googleapis.github.io/google-auth-library-php/master/", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.15.1" + }, + "time": "2021-04-21T17:42:05+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7008573787b430c1c1f650e3722d9bba59967628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.3-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2021-03-23T11:33:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.1" + }, + "time": "2021-03-07T09:25:29+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.8.2" + }, + "time": "2021-04-26T09:17:50+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90 <7.0.1", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-12-14T13:15:25+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "reference": "f34c2b11eb9d2c9318e13540a1dbc2a3afbd939c", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2020-12-06T15:14:20+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.8", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "d9615a6fb970d9933866ca8b4036ec3407b020b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d9615a6fb970d9933866ca8b4036ec3407b020b6", + "reference": "d9615a6fb970d9933866ca8b4036ec3407b020b6", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^5.7|^6.0|^9.4", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.8" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2021-04-19T03:20:48+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + } + ], "packages-dev": [ { "name": "composer/semver", - "version": "3.2.4", + "version": "3.2.5", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464" + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", - "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464", + "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", + "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", "shasum": "" }, "require": { @@ -67,6 +1094,11 @@ "validation", "versioning" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.5" + }, "funding": [ { "url": "https://packagist.com", @@ -81,7 +1113,7 @@ "type": "tidelift" } ], - "time": "2020-11-13T08:59:24+00:00" + "time": "2021-05-24T12:41:47+00:00" }, { "name": "composer/spdx-licenses", @@ -141,6 +1173,11 @@ "spdx", "validator" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.5" + }, "funding": [ { "url": "https://packagist.com", @@ -200,6 +1237,11 @@ "Xdebug", "performance" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/1.4.6" + }, "funding": [ { "url": "https://packagist.com", @@ -218,20 +1260,20 @@ }, { "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e" + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/06f0b06043c7438959dbdeed8bb3f699a19be22e", - "reference": "06f0b06043c7438959dbdeed8bb3f699a19be22e", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", "shasum": "" }, "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "php": "^7.1 || ^8.0", "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" }, @@ -255,7 +1297,11 @@ } ], "description": "A more advanced JSONRPC implementation", - "time": "2021-01-10T17:48:47+00:00" + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" }, { "name": "mediawiki/mediawiki-codesniffer", @@ -302,6 +1348,9 @@ "codesniffer", "mediawiki" ], + "support": { + "source": "https://github.com/wikimedia/mediawiki-tools-codesniffer/tree/v36.0.0" + }, "time": "2021-04-29T02:21:21+00:00" }, { @@ -349,6 +1398,10 @@ ], "description": "Standard MediaWiki phan configuration", "homepage": "https://www.mediawiki.org/wiki/Continuous_integration/Phan", + "support": { + "issues": "https://github.com/wikimedia/mediawiki-tools-phan/issues", + "source": "https://github.com/wikimedia/mediawiki-tools-phan/tree/0.10.6" + }, "time": "2020-12-14T15:30:53+00:00" }, { @@ -395,6 +1448,9 @@ ], "description": "Removes executable bit from files that shouldn't be executable", "homepage": "https://www.mediawiki.org/wiki/MinusX", + "support": { + "source": "https://github.com/wikimedia/mediawiki-tools-minus-x/tree/1.1.1" + }, "time": "2021-01-06T01:11:18+00:00" }, { @@ -459,6 +1515,12 @@ "static", "taint" ], + "support": { + "irc": "irc://freenode.net/wikimedia-dev", + "issues": "https://phabricator.wikimedia.org/maniphest/task/edit/form/1/?projects=securitycheckplugin", + "source": "https://phabricator.wikimedia.org/diffusion/MTPS/", + "wiki": "https://www.mediawiki.org/wiki/SecurityCheckPlugin" + }, "time": "2020-12-14T11:11:43+00:00" }, { @@ -500,20 +1562,24 @@ } ], "description": "Tolerant PHP-to-AST parser designed for IDE usage scenarios", + "support": { + "issues": "https://github.com/microsoft/tolerant-php-parser/issues", + "source": "https://github.com/microsoft/tolerant-php-parser/tree/v0.0.23" + }, "time": "2020-09-13T17:29:12+00:00" }, { "name": "netresearch/jsonmapper", - "version": "v2.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e" + "reference": "ba09f0e456d4f00cef84e012da5715625594407c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/e0f1e33a71587aca81be5cffbb9746510e1fe04e", - "reference": "e0f1e33a71587aca81be5cffbb9746510e1fe04e", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/ba09f0e456d4f00cef84e012da5715625594407c", + "reference": "ba09f0e456d4f00cef84e012da5715625594407c", "shasum": "" }, "require": { @@ -546,7 +1612,12 @@ } ], "description": "Map nested JSON structures onto PHP classes", - "time": "2020-04-16T18:48:43+00:00" + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v3.1.1" + }, + "time": "2020-11-02T19:19:54+00:00" }, { "name": "phan/phan", @@ -619,6 +1690,10 @@ "php", "static" ], + "support": { + "issues": "https://github.com/phan/phan/issues", + "source": "https://github.com/phan/phan/tree/3.2.6" + }, "time": "2020-11-27T19:39:49+00:00" }, { @@ -664,6 +1739,10 @@ "email": "jakub.onderka@gmail.com" } ], + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/master" + }, "time": "2020-05-14T05:47:14+00:00" }, { @@ -713,6 +1792,10 @@ } ], "description": "Highlight PHP code in terminal", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues", + "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/master" + }, "time": "2020-05-13T07:37:49+00:00" }, { @@ -766,6 +1849,10 @@ ], "description": "This tool check syntax of PHP files about 20x faster than serial check.", "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", + "support": { + "issues": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/issues", + "source": "https://github.com/php-parallel-lint/PHP-Parallel-Lint/tree/v1.3.0" + }, "time": "2021-04-07T14:42:48+00:00" }, { @@ -815,6 +1902,10 @@ "reflection", "static analysis" ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, "time": "2020-06-27T09:03:43+00:00" }, { @@ -867,6 +1958,10 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, "time": "2020-09-03T19:13:55+00:00" }, { @@ -912,6 +2007,10 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, "time": "2020-09-17T18:55:26+00:00" }, { @@ -956,54 +2055,11 @@ "container-interop", "psr" ], - "time": "2021-03-05T17:36:06+00:00" - }, - { - "name": "psr/log", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-03-05T17:36:06+00:00" }, { "name": "sabre/event", @@ -1064,6 +2120,11 @@ "reactor", "signal" ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "issues": "https://github.com/sabre-io/event/issues", + "source": "https://github.com/fruux/sabre-event" + }, "time": "2020-10-03T11:02:22+00:00" }, { @@ -1115,24 +2176,30 @@ "phpcs", "standards" ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, "time": "2021-04-09T00:54:41+00:00" }, { "name": "symfony/console", - "version": "v5.2.6", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d" + "reference": "058553870f7809087fa80fa734704a21b9bcaeb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d", - "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d", + "url": "https://api.github.com/repos/symfony/console/zipball/058553870f7809087fa80fa734704a21b9bcaeb2", + "reference": "058553870f7809087fa80fa734704a21b9bcaeb2", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", "symfony/polyfill-php80": "^1.15", @@ -1195,6 +2262,76 @@ "console", "terminal" ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-26T17:43:10+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1209,20 +2346,20 @@ "type": "tidelift" } ], - "time": "2021-03-28T09:42:18+00:00" + "time": "2021-03-23T23:28:01+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -1234,7 +2371,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1271,6 +2408,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1285,20 +2425,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", "shasum": "" }, "require": { @@ -1310,7 +2450,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1349,6 +2489,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1363,20 +2506,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "shasum": "" }, "require": { @@ -1388,7 +2531,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1430,6 +2573,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1444,20 +2590,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", "shasum": "" }, "require": { @@ -1469,7 +2615,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1507,6 +2653,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1521,20 +2670,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { @@ -1543,7 +2692,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1583,6 +2732,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1597,20 +2749,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", "shasum": "" }, "require": { @@ -1619,7 +2771,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1663,6 +2815,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1677,25 +2832,25 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.0" + "psr/container": "^1.1" }, "suggest": { "symfony/service-implementation": "" @@ -1703,7 +2858,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -1739,6 +2894,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1753,20 +2911,20 @@ "type": "tidelift" } ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2021-04-01T10:43:52+00:00" }, { "name": "symfony/string", - "version": "v5.2.6", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572" + "reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", - "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", + "url": "https://api.github.com/repos/symfony/string/zipball/a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", + "reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", "shasum": "" }, "require": { @@ -1819,6 +2977,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.3.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1833,7 +2994,7 @@ "type": "tidelift" } ], - "time": "2021-03-17T17:12:15+00:00" + "time": "2021-05-26T17:43:10+00:00" }, { "name": "webmozart/assert", @@ -1887,6 +3048,10 @@ "check", "validate" ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, "time": "2021-03-09T10:59:23+00:00" } ], @@ -1897,5 +3062,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/extension.json b/extension.json index 4f8b7b2..6f20040 100644 --- a/extension.json +++ b/extension.json @@ -36,9 +36,10 @@ "BeforePageDisplay": "default", "HtmlPageLinkRendererBegin": "default", "LinkerMakeExternalLink": "default", + "OutputPageParserOutput": "RelatedArticles", + "PageViewInfoAfterPageViewService": "MediaWiki\\Extension\\UnifiedExtensionForFemiwiki\\Hooks\\PageViewInfoHandler::onPageViewInfoAfterPageViewService", "SidebarBeforeOutput": "default", - "SkinAddFooterLinks": "default", - "OutputPageParserOutput": "RelatedArticles" + "SkinAddFooterLinks": "default" }, "SpecialPages": { "OrderedWhatlinkshere": { @@ -58,10 +59,20 @@ "config": { "GoogleAnalyticsTrackingID": { "value": "" + }, + "UnifiedExtensionForFemiwikiGoogleAnalyticsCredentialsFile": { + "value": false, + "description": "The file name of the credentials from the Google API Console." + }, + "UnifiedExtensionForFemiwikiGoogleAnalyticsProfileId": { + "value": false, + "description": "Profile(View) ID of the Google Analytics View. You can use the Account Explorer to find a View ID" } }, + "load_composer_autoloader": true, "TestAutoloadNamespaces": { - "MediaWiki\\Extension\\UnifiedExtensionForFemiwiki\\Tests\\Unit\\": "tests/phpunit/unit/" + "MediaWiki\\Extension\\UnifiedExtensionForFemiwiki\\Tests\\Unit\\": "tests/phpunit/unit/", + "MediaWiki\\Extension\\UnifiedExtensionForFemiwiki\\Tests\\Integration\\": "tests/phpunit/integration/" }, "manifest_version": 2 } diff --git a/includes/GoogleAnalyticsPageViewService.php b/includes/GoogleAnalyticsPageViewService.php new file mode 100644 index 0000000..de80cb5 --- /dev/null +++ b/includes/GoogleAnalyticsPageViewService.php @@ -0,0 +1,345 @@ +verifyApiOptions( $options ); + + // Skip the current day for which only partial information is available + $this->lastCompleteDay = strtotime( '0:0 1 day ago' ); + + $this->logger = new NullLogger(); + + $client = new Google_Client(); + $client->setApplicationName( 'PageViewInfo' ); + if ( $options['credentialsFile'] ) { + $client->setAuthConfig( $options['credentialsFile'] ); + } + $client->addScope( Google_Service_AnalyticsReporting::ANALYTICS_READONLY ); + + $this->analytics = new Google_Service_AnalyticsReporting( $client ); + + $this->profileId = $options['profileId']; + } + + /** + * @inheritDoc + */ + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; + } + + /** + * @param WebRequest|string[] $originalRequest See the 'originalRequest' parameter of + * Http::request(). + */ + public function setOriginalRequest( $originalRequest ) { + $this->originalRequest = $originalRequest; + } + + /** + * @inheritDoc + */ + public function supports( $metric, $scope ) { + return true; + } + + /** + * @inheritDoc + */ + public function getPageData( array $titles, $days, $metric = self::METRIC_VIEW ) { + if ( !$titles ) { + return StatusValue::newGood( [] ); + } + if ( $days <= 0 ) { + throw new InvalidArgumentException( 'Invalid days: ' . $days ); + } + + $status = StatusValue::newGood(); + $result = []; + foreach ( $titles as $title ) { + /** @var Title $title */ + $result[$title->getPrefixedDBkey()] = $this->getEmptyDateRange( $days ); + + // Create the DateRange object. + $dateRange = new Google_Service_AnalyticsReporting_DateRange(); + $dateRange->setStartDate( $days . 'daysAgo' ); + $dateRange->setEndDate( "1daysAgo" ); + + // Create the Metrics object. + $gaMetric = new Google_Service_AnalyticsReporting_Metric(); + if ( $metric === self::METRIC_VIEW ) { + $gaMetric->setExpression( 'ga:pageviews' ); + } elseif ( $metric === self::METRIC_UNIQUE ) { + $gaMetric->setExpression( 'ga:uniquePageviews' ); + } else { + throw new InvalidArgumentException( 'Invalid metric: ' . $metric ); + } + + // Create the Dimension object. + $dimension = new Google_Service_AnalyticsReporting_Dimension(); + $dimension->setName( 'ga:date' ); + + // Create the DimensionFilterClause object. + // TODO Use unique custom dimension instead of ga:pageTitle and provide the instruction to the end-users. + $dimensionFilter = new Google_Service_AnalyticsReporting_DimensionFilter(); + $dimensionFilter->setDimensionName( 'ga:pageTitle' ); + $dimensionFilter->setOperator( 'REGEXP' ); + $dimensionFilter->setExpressions( [ + '^' . str_replace( '_', ' ', $title->getPrefixedDBkey() ) . ' - [^-]+$' ] ); + $dimensionFilterClause = new Google_Service_AnalyticsReporting_DimensionFilterClause(); + $dimensionFilterClause->setFilters( [ $dimensionFilter ] ); + + // Create the ReportRequest object. + $request = new Google_Service_AnalyticsReporting_ReportRequest(); + $request->setViewId( $this->profileId ); + $request->setDateRanges( [ $dateRange ] ); + $request->setMetrics( [ $gaMetric ] ); + $request->setDimensions( [ $dimension ] ); + $request->setDimensionFilterClauses( [ $dimensionFilterClause ] ); + + $body = new Google_Service_AnalyticsReporting_GetReportsRequest(); + $body->setReportRequests( [ $request ] ); + + try { + $data = $this->analytics->reports->batchGet( $body ); + $rows = $data->getReports()[0]->getData()->getRows(); + foreach ( $rows as $row ) { + $ts = $row->dimensions[0]; + $day = substr( $ts, 0, 4 ) . '-' . substr( $ts, 4, 2 ) . '-' . substr( $ts, 6, 2 ); + $count = (int)$row->metrics[0]->values[0]; + $result[$title->getPrefixedDBkey()][$day] = $count; + } + $status->success[$title->getPrefixedDBkey()] = true; + } catch ( RuntimeException $e ) { + $status->error( 'pvi-invalidresponse' ); + $status->success[$title->getPrefixedDBkey()] = false; + continue; + } + } + $status->successCount = count( array_filter( $status->success ) ); + $status->failCount = count( $status->success ) - $status->successCount; + $status->setResult( (bool)$status->successCount, $result ); + return $status; + } + + /** + * @inheritDoc + */ + public function getSiteData( $days, $metric = self::METRIC_VIEW ) { + if ( $metric !== self::METRIC_VIEW && $metric !== self::METRIC_UNIQUE ) { + throw new InvalidArgumentException( 'Invalid metric: ' . $metric ); + } + if ( $days <= 0 ) { + throw new InvalidArgumentException( 'Invalid days: ' . $days ); + } + $result = $this->getEmptyDateRange( $days ); + + // Create the DateRange object. + $dateRange = new Google_Service_AnalyticsReporting_DateRange(); + $dateRange->setStartDate( $days . 'daysAgo' ); + $dateRange->setEndDate( '1daysAgo' ); + + // Create the Metrics object. + $gaMetric = new Google_Service_AnalyticsReporting_Metric(); + if ( $metric === self::METRIC_VIEW ) { + $gaMetric->setExpression( 'ga:pageviews' ); + } elseif ( $metric === self::METRIC_UNIQUE ) { + $gaMetric->setExpression( 'ga:uniquePageviews' ); + } else { + throw new InvalidArgumentException( 'Invalid metric: ' . $metric ); + } + + // Create the Dimension object. + $dimension = new Google_Service_AnalyticsReporting_Dimension(); + $dimension->setName( 'ga:date' ); + + // Create the ReportRequest object. + $request = new Google_Service_AnalyticsReporting_ReportRequest(); + $request->setViewId( $this->profileId ); + $request->setDateRanges( [ $dateRange ] ); + $request->setMetrics( [ $gaMetric ] ); + $request->setDimensions( [ $dimension ] ); + + $body = new Google_Service_AnalyticsReporting_GetReportsRequest(); + $body->setReportRequests( [ $request ] ); + + $status = Status::newGood(); + try { + $data = $this->analytics->reports->batchGet( $body ); + $rows = $data->getReports()[0]->getData()->getRows(); + + foreach ( $rows as $row ) { + $ts = $row->dimensions[0]; + $day = substr( $ts, 0, 4 ) . '-' . substr( $ts, 4, 2 ) . '-' . substr( $ts, 6, 2 ); + $count = (int)$row->metrics[0]->values[0]; + $result[$day] = $count; + } + $status->setResult( $status->isOK(), $result ); + } catch ( RuntimeException $e ) { + $status->fatal( 'pvi-invalidresponse' ); + } + return $status; + } + + /** + * @inheritDoc + */ + public function getTopPages( $metric = self::METRIC_VIEW ) { + $result = []; + if ( !in_array( $metric, [ self::METRIC_VIEW, self::METRIC_UNIQUE ] ) ) { + throw new InvalidArgumentException( 'Invalid metric: ' . $metric ); + } + + // Create the DateRange object. + $dateRange = new Google_Service_AnalyticsReporting_DateRange(); + $dateRange->setStartDate( '2daysAgo' ); + $dateRange->setEndDate( '1daysAgo' ); + + // Create the Metrics object and OrderBy object. + $gaMetric = new Google_Service_AnalyticsReporting_Metric(); + $orderBy = new Google_Service_AnalyticsReporting_OrderBy(); + $orderBy->setSortOrder( 'DESCENDING' ); + if ( $metric === self::METRIC_VIEW ) { + $gaMetric->setExpression( 'ga:pageviews' ); + $orderBy->setFieldName( 'ga:pageviews' ); + } elseif ( $metric === self::METRIC_UNIQUE ) { + $gaMetric->setExpression( 'ga:uniquePageviews' ); + $orderBy->setFieldName( 'ga:uniquePageviews' ); + } + + // Create the Dimension object. + $dimension = new Google_Service_AnalyticsReporting_Dimension(); + $dimension->setName( 'ga:pageTitle' ); + + // Create the ReportRequest object. + $request = new Google_Service_AnalyticsReporting_ReportRequest(); + $request->setViewId( $this->profileId ); + $request->setDateRanges( [ $dateRange ] ); + $request->setMetrics( [ $gaMetric ] ); + $request->setDimensions( [ $dimension ] ); + $request->setOrderBys( [ $orderBy ] ); + + $body = new Google_Service_AnalyticsReporting_GetReportsRequest(); + $body->setReportRequests( [ $request ] ); + + $status = Status::newGood(); + try { + $data = $this->analytics->reports->batchGet( $body ); + $rows = $data->getReports()[0]->getData()->getRows(); + + foreach ( $rows as $row ) { + $title = $row->dimensions[0]; + $title = preg_replace( '/ - [^-]+$/', '', $title ); + $title = preg_replace( '/ /', '_', $title ); + $count = (int)$row->metrics[0]->values[0]; + $result[$title] = $count; + } + $status->setResult( $status->isOK(), $result ); + } catch ( RuntimeException $e ) { + $status->fatal( 'pvi-invalidresponse' ); + } + return $status; + } + + /** + * @inheritDoc + */ + public function getCacheExpiry( $metric, $scope ) { + // data is valid until the end of the day + $endOfDay = strtotime( '0:0 next day' ); + return $endOfDay - time(); + } + + /** + * @param array $apiOptions + * @throws InvalidArgumentException + */ + protected function verifyApiOptions( array $apiOptions ) { + if ( !isset( $apiOptions['credentialsFile'] ) ) { + throw new InvalidArgumentException( "'credentialsFile' is required" ); + } elseif ( !isset( $apiOptions['profileId'] ) ) { + throw new InvalidArgumentException( "'profileId' is required" ); + } + } + + /** + * The API omits dates if there is no data. Fill it with nulls to make client-side + * processing easier. + * @param int $days + * @return array YYYY-MM-DD => null + */ + protected function getEmptyDateRange( $days ) { + if ( !$this->range ) { + $this->range = []; + // we only care about the date part, so add some hours to avoid errors when there is a + // leap second or some other weirdness + $end = $this->lastCompleteDay + 12 * 3600; + $start = $end - ( $days - 1 ) * 24 * 3600; + for ( $ts = $start; $ts <= $end; $ts += 24 * 3600 ) { + $this->range[gmdate( 'Y-m-d', $ts )] = null; + } + } + return $this->range; + } + + /** + * Get start and end timestamp in YYYYMMDDHH format + * @param int $days + * @return string[] + */ + protected function getStartEnd( $days ) { + $end = $this->lastCompleteDay + 12 * 3600; + $start = $end - ( $days - 1 ) * 24 * 3600; + return [ gmdate( 'Ymd', $start ) . '00', gmdate( 'Ymd', $end ) . '00' ]; + } +} diff --git a/includes/Hooks/Handler.php b/includes/Hooks/Handler.php index 895ed4d..eda0d32 100644 --- a/includes/Hooks/Handler.php +++ b/includes/Hooks/Handler.php @@ -170,7 +170,12 @@ public function onHtmlPageLinkRendererBegin( $linkRenderer, $target, &$text, &$customAttribs, &$query, &$ret ) { // See https://github.com/femiwiki/UnifiedExtensionForFemiwiki/issues/23 - if ( defined( 'MW_PHPUNIT_TEST' ) && ( $target == 'Rights Page' || $target == 'Parser test' ) ) { + if ( defined( 'MW_PHPUNIT_TEST' ) && in_array( $target, [ + 'Link to nowhere', + 'Link', + 'Parser test', + 'Rights Page', + ] ) ) { return true; } diff --git a/includes/Hooks/PageViewInfoHandler.php b/includes/Hooks/PageViewInfoHandler.php new file mode 100644 index 0000000..92f51ad --- /dev/null +++ b/includes/Hooks/PageViewInfoHandler.php @@ -0,0 +1,26 @@ +getMainConfig(); + $credentialsFile = $config->get( 'UnifiedExtensionForFemiwikiGoogleAnalyticsCredentialsFile' ); + $profileId = $config->get( 'UnifiedExtensionForFemiwikiGoogleAnalyticsProfileId' ); + + $service = new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => $credentialsFile, + 'profileId' => $profileId + ] ); + + return false; + } +} diff --git a/tests/phpunit/integration/GoogleAnalyticsPageViewServiceTest.php b/tests/phpunit/integration/GoogleAnalyticsPageViewServiceTest.php new file mode 100644 index 0000000..831843d --- /dev/null +++ b/tests/phpunit/integration/GoogleAnalyticsPageViewServiceTest.php @@ -0,0 +1,178 @@ +assertInstanceOf( $class, $e ); + return; + } + $this->fail( 'No exception was thrown, expected ' . $class ); + } + + /** + * Prepare the mock \Google_Service_Analytics which will be used for the next call + * @param GoogleAnalyticsPageViewService $service + * @param array|false $rows If false error is thrown + * @throws RuntimeException + */ + protected function mockNextBatchGet( + GoogleAnalyticsPageViewService $service, $rows + ) { + self::$batches[] = $rows; + $wrapper = TestingAccessWrapper::newFromObject( $service ); + $wrapper->analytics = (object)[ + 'reports' => new class { + public function batchGet() { + return new class { + public function getReports() { + return [ + new class { + public function getData() { + return new class { + public function getRows() { + $batch = array_shift( GoogleAnalyticsPageViewServiceTest::$batches ); + if ( $batch === false ) { + throw new RuntimeException(); + } + return $batch; + } + }; + } + } + ]; + } + }; + } + } + ]; + } + + /** + * Changes the start/end dates + * @param GoogleAnalyticsPageViewService $service + * @param string $end YYYY-MM-DD + */ + protected function mockDate( GoogleAnalyticsPageViewService $service, $end ) { + $wrapper = TestingAccessWrapper::newFromObject( $service ); + $wrapper->lastCompleteDay = strtotime( $end . 'T00:00Z' ); + $wrapper->range = null; + } + + public function testGetPageData() { + $service = new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => false, + 'profileId' => '123456' + ] ); + $this->mockDate( $service, '2000-01-05' ); + + // valid request + foreach ( [ 'Foo', 'Bar' ] as $page ) { + $this->mockNextBatchGet( $service, [ + (object)[ + 'dimensions' => [ '20000101' ], + 'metrics' => [ (object)[ 'values' => [ $page === 'Foo' ? '1000' : '500' ] ] ] + ], + (object)[ + 'dimensions' => [ '20000102' ], + 'metrics' => [ (object)[ 'values' => [ $page === 'Foo' ? '100' : '50' ] ] ] + ], + (object)[ + 'dimensions' => [ '20000104' ], + 'metrics' => [ (object)[ 'values' => [ $page === 'Foo' ? '10' : '5' ] ] ] + ], + ] ); + } + + $status = $service->getPageData( [ + Title::newFromText( 'Foo' ), + Title::newFromText( 'Bar' ) + ], 5 ); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + 'Foo' => [ + '2000-01-01' => 1000, + '2000-01-02' => 100, + '2000-01-03' => null, + '2000-01-04' => 10, + '2000-01-05' => null, + ], + 'Bar' => [ + '2000-01-01' => 500, + '2000-01-02' => 50, + '2000-01-03' => null, + '2000-01-04' => 5, + '2000-01-05' => null, + ], + ], $status->getValue() ); + $this->assertSame( [ 'Foo' => true, 'Bar' => true ], $status->success ); + $this->assertSame( 2, $status->successCount ); + $this->assertSame( 0, $status->failCount ); + + $this->mockDate( $service, '2000-01-01' ); + // valid, no result and error, combined + self::$batches = []; + $this->mockNextBatchGet( $service, [ + (object)[ + 'dimensions' => [ '20000101' ], + 'metrics' => [ (object)[ 'values' => [ '1' ] ] ] + ] + ] ); + $this->mockNextBatchGet( $service, [] ); + $this->mockNextBatchGet( $service, false ); + $status = $service->getPageData( [ Title::newFromText( 'A' ), + Title::newFromText( 'B' ), Title::newFromText( 'C' ) ], 1 ); + $this->assertFalse( $status->isGood() ); + if ( !$status->isOK() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + 'A' => [ + '2000-01-01' => 1, + ], + 'B' => [ + '2000-01-01' => null, + ], + 'C' => [ + '2000-01-01' => null, + ], + ], $status->getValue() ); + $this->assertSame( [ 'A' => true, 'B' => true, 'C' => false ], $status->success ); + $this->assertSame( 2, $status->successCount ); + $this->assertSame( 1, $status->failCount ); + + // all error out + self::$batches = []; + $this->mockNextBatchGet( $service, false ); + $this->mockNextBatchGet( $service, false ); + $status = $service->getPageData( [ Title::newFromText( 'A' ), Title::newFromText( 'B' ) ], 1 ); + $this->assertFalse( $status->isOK() ); + $this->assertSame( [ 'A' => false, 'B' => false ], $status->success ); + $this->assertSame( 0, $status->successCount ); + $this->assertSame( 2, $status->failCount ); + } +} diff --git a/tests/phpunit/unit/GoogleAnalyticsPageViewServiceTest.php b/tests/phpunit/unit/GoogleAnalyticsPageViewServiceTest.php new file mode 100644 index 0000000..126d97f --- /dev/null +++ b/tests/phpunit/unit/GoogleAnalyticsPageViewServiceTest.php @@ -0,0 +1,260 @@ +assertInstanceOf( $class, $e ); + return; + } + $this->fail( 'No exception was thrown, expected ' . $class ); + } + + /** + * Prepare the mock \Google_Service_Analytics which will be used for the next call + * @param GoogleAnalyticsPageViewService $service + * @param array|false $rows If false error is thrown + * @throws RuntimeException + */ + protected function mockNextBatchGet( + GoogleAnalyticsPageViewService $service, $rows + ) { + self::$batches[] = $rows; + $wrapper = TestingAccessWrapper::newFromObject( $service ); + $wrapper->analytics = (object)[ + 'reports' => new class { + public function batchGet() { + return new class { + public function getReports() { + return [ + new class { + public function getData() { + return new class { + public function getRows() { + $batch = array_shift( GoogleAnalyticsPageViewServiceTest::$batches ); + if ( $batch === false ) { + throw new RuntimeException(); + } + return $batch; + } + }; + } + } + ]; + } + }; + } + } + ]; + } + + /** + * Changes the start/end dates + * @param GoogleAnalyticsPageViewService $service + * @param string $end YYYY-MM-DD + */ + protected function mockDate( GoogleAnalyticsPageViewService $service, $end ) { + $wrapper = TestingAccessWrapper::newFromObject( $service ); + $wrapper->lastCompleteDay = strtotime( $end . 'T00:00Z' ); + $wrapper->range = null; + } + + public function testConstructor() { + $this->assertThrows( InvalidArgumentException::class, static function () { + new GoogleAnalyticsPageViewService( [] ); + } ); + $this->assertThrows( InvalidArgumentException::class, static function () { + new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => 'non-exist-file.json', + 'profileId' => 'foobar' + ] ); + } ); + new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => false, + 'profileId' => '123456' + ] ); + } + + public function testGetSiteData() { + $service = new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => false, + 'profileId' => '123456' + ] ); + $this->mockDate( $service, '2000-01-05' ); + + // valid request + $this->mockNextBatchGet( $service, [ + (object)[ + 'dimensions' => [ '20000101' ], + 'metrics' => [ (object)[ 'values' => [ '1000' ] ] ] + ], + (object)[ + 'dimensions' => [ '20000102' ], + 'metrics' => [ (object)[ 'values' => [ '100' ] ] ] + ], + (object)[ + 'dimensions' => [ '20000104' ], + 'metrics' => [ (object)[ 'values' => [ '10' ] ] ] + ] + ] ); + $status = $service->getSiteData( 5 ); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + '2000-01-01' => 1000, + '2000-01-02' => 100, + '2000-01-03' => null, + '2000-01-04' => 10, + '2000-01-05' => null, + ], $status->getValue() ); + + // no result + self::$batches = []; + $this->mockNextBatchGet( $service, [] ); + $status = $service->getSiteData( 5 ); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + '2000-01-01' => null, + '2000-01-02' => null, + '2000-01-03' => null, + '2000-01-04' => null, + '2000-01-05' => null, + ], $status->getValue() ); + + // genuine error + self::$batches = []; + $this->mockNextBatchGet( $service, false ); + $status = $service->getSiteData( 5 ); + $this->assertFalse( $status->isOK() ); + } + + public function testGetSiteData_unique() { + $service = new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => false, + 'profileId' => '123456' + ] ); + $this->mockDate( $service, '2000-01-05' ); + + // valid request + $this->mockNextBatchGet( $service, [ + (object)[ + 'dimensions' => [ '20000101' ], + 'metrics' => [ (object)[ 'values' => [ '1000' ] ] ] + ], + (object)[ + 'dimensions' => [ '20000102' ], + 'metrics' => [ (object)[ 'values' => [ '100' ] ] ] + ], + (object)[ + 'dimensions' => [ '20000104' ], + 'metrics' => [ (object)[ 'values' => [ '10' ] ] ] + ] + ] ); + $status = $service->getSiteData( 5, PageViewService::METRIC_UNIQUE ); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + '2000-01-01' => 1000, + '2000-01-02' => 100, + '2000-01-03' => null, + '2000-01-04' => 10, + '2000-01-05' => null, + ], $status->getValue() ); + + // no result + self::$batches = []; + $this->mockNextBatchGet( $service, [] ); + $status = $service->getSiteData( 5, PageViewService::METRIC_UNIQUE ); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + '2000-01-01' => null, + '2000-01-02' => null, + '2000-01-03' => null, + '2000-01-04' => null, + '2000-01-05' => null, + ], $status->getValue() ); + + // genuine error + self::$batches = []; + $this->mockNextBatchGet( $service, false ); + $status = $service->getSiteData( 5, PageViewService::METRIC_UNIQUE ); + $this->assertFalse( $status->isOK() ); + } + + public function testGetTopPages() { + $service = new GoogleAnalyticsPageViewService( [ + 'credentialsFile' => false, + 'profileId' => '123456' + ] ); + $this->mockDate( $service, '2000-01-05' ); + + // valid request + $this->mockNextBatchGet( $service, [ + (object)[ + 'dimensions' => [ "Main Page - ExampleWiki" ], + 'metrics' => [ (object)[ 'values' => [ '1000' ] ] ] + ], + (object)[ + 'dimensions' => [ 'Special:Search - ExampleWiki' ], + 'metrics' => [ (object)[ 'values' => [ '100' ] ] ] + ], + (object)[ + 'dimensions' => [ '404.php' ], + 'metrics' => [ (object)[ 'values' => [ '10' ] ] ] + ] + ] ); + $status = $service->getTopPages(); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [ + 'Main_Page' => 1000, + 'Special:Search' => 100, + '404.php' => 10, + ], $status->getValue() ); + + // no result + self::$batches = []; + $this->mockNextBatchGet( $service, [] ); + $status = $service->getTopPages(); + if ( !$status->isGood() ) { + $this->fail( Status::wrap( $status )->getWikiText() ); + } + $this->assertSame( [], $status->getValue() ); + + // genuine error + self::$batches = []; + $this->mockNextBatchGet( $service, false ); + $status = $service->getTopPages(); + $this->assertFalse( $status->isOK() ); + } +}