Added Github API

This commit is contained in:
Varun 2020-10-21 15:08:42 +05:30
parent cea1bc9c20
commit 45a6d6c690
No known key found for this signature in database
GPG key ID: 93FB46DCF16E0D6F
35 changed files with 3245 additions and 5 deletions

6
.gitignore vendored
View file

@ -786,6 +786,6 @@ ASALocalRun/
.all-contributorsrc
.github/
composer/installed.json
composer/LICENSE
milo/github-api/readme.md
toolkit/php/vendor/composer/installed.json
toolkit/php/vendor/composer/LICENSE
toolkit/php/vendor/milo/github-api/readme.md

View file

@ -1,5 +1,13 @@
<?php
include_once '/gh-toolkit/php.php';
#include_once '/gh-toolkit/php.php';
include_once '/gh-toolkit/gh-api.php';
global $github_api;
$data = $github_api->decode( $github_api->get( 'repos/' . gh_env( 'GITHUB_REPOSITORY' ) ) );
gh_log_group_start( 'Github API' );
gh_log( print_r( $data, true ) );
gh_log_group_end();
gh_log( 'Sample Log In Github Actions' );
gh_log_group_start( 'Group Name' );

17
toolkit/gh-api.php Normal file
View file

@ -0,0 +1,17 @@
<?php
if ( ! function_exists( 'gh_log' ) ) {
require_once __DIR__ . '/php.php';
}
require_once __DIR__ . '/php/vendor/autoload.php';
use Milo\Github\Api;
use Milo\Github\OAuth\Token;
gh_validate_env( 'GITHUB_TOKEN', 'Set the GITHUB_TOKEN env variable' );
$github_api = new Api();
$github_api->setToken( new Token( gh_env( 'GITHUB_TOKEN' ) ) );
$GLOBALS['github_api'] = $github_api;

View file

@ -0,0 +1,5 @@
{
"require" : {
"milo/github-api" : "^1.4"
}
}

62
toolkit/php/composer.lock generated Normal file
View file

@ -0,0 +1,62 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "186789314681ef91e22ed86aecad1977",
"packages": [
{
"name": "milo/github-api",
"version": "v1.4.7",
"source": {
"type": "git",
"url": "https://github.com/milo/github-api.git",
"reference": "f72f6a1699f9a648dafdb278cbf9a6dc258b4e1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/milo/github-api/zipball/f72f6a1699f9a648dafdb278cbf9a6dc258b4e1f",
"reference": "f72f6a1699f9a648dafdb278cbf9a6dc258b4e1f",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"nette/tester": "~1.7.0"
},
"suggest": {
"ext-curl": "Allows you to use Http\\CurlClient"
},
"type": "library",
"autoload": {
"classmap": [
"src/Github/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Miloslav Hůla",
"email": "miloslav.hula@gmail.com",
"homepage": "https://github.com/milo"
}
],
"description": "Github API easy access",
"time": "2020-01-03T17:19:16+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "1.1.0"
}

View file

@ -1,6 +1,6 @@
<?php
function gh_log( $content ) {
function gh_log( $content = '' ) {
echo $content . PHP_EOL;
}

7
toolkit/php/vendor/autoload.php vendored Normal file
View file

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit9d96850ef8cfe136562bea1964c9dec8::getLoader();

View file

@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View file

@ -0,0 +1,45 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Milo\\Github\\Api' => $vendorDir . '/milo/github-api/src/Github/Api.php',
'Milo\\Github\\ApiException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\BadRequestException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\ForbiddenException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Helpers' => $vendorDir . '/milo/github-api/src/Github/Helpers.php',
'Milo\\Github\\Http\\AbstractClient' => $vendorDir . '/milo/github-api/src/Github/Http/AbstractClient.php',
'Milo\\Github\\Http\\BadResponseException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Http\\CachedClient' => $vendorDir . '/milo/github-api/src/Github/Http/CachedClient.php',
'Milo\\Github\\Http\\CurlClient' => $vendorDir . '/milo/github-api/src/Github/Http/CurlClient.php',
'Milo\\Github\\Http\\IClient' => $vendorDir . '/milo/github-api/src/Github/Http/IClient.php',
'Milo\\Github\\Http\\Message' => $vendorDir . '/milo/github-api/src/Github/Http/Message.php',
'Milo\\Github\\Http\\Request' => $vendorDir . '/milo/github-api/src/Github/Http/Request.php',
'Milo\\Github\\Http\\Response' => $vendorDir . '/milo/github-api/src/Github/Http/Response.php',
'Milo\\Github\\Http\\StreamClient' => $vendorDir . '/milo/github-api/src/Github/Http/StreamClient.php',
'Milo\\Github\\IException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\InvalidResponseException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\JsonException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\LogicException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\MissingParameterException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\NotFoundException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\OAuth\\Configuration' => $vendorDir . '/milo/github-api/src/Github/OAuth/Configuration.php',
'Milo\\Github\\OAuth\\Login' => $vendorDir . '/milo/github-api/src/Github/OAuth/Login.php',
'Milo\\Github\\OAuth\\LoginException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\OAuth\\Token' => $vendorDir . '/milo/github-api/src/Github/OAuth/Token.php',
'Milo\\Github\\Paginator' => $vendorDir . '/milo/github-api/src/Github/Paginator.php',
'Milo\\Github\\RateLimitExceedException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\RuntimeException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Sanity' => $vendorDir . '/milo/github-api/src/Github/Sanity.php',
'Milo\\Github\\Storages\\FileCache' => $vendorDir . '/milo/github-api/src/Github/Storages/FileCache.php',
'Milo\\Github\\Storages\\ICache' => $vendorDir . '/milo/github-api/src/Github/Storages/ICache.php',
'Milo\\Github\\Storages\\ISessionStorage' => $vendorDir . '/milo/github-api/src/Github/Storages/ISessionStorage.php',
'Milo\\Github\\Storages\\MissingDirectoryException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Storages\\SessionStorage' => $vendorDir . '/milo/github-api/src/Github/Storages/SessionStorage.php',
'Milo\\Github\\UnauthorizedException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\UnexpectedResponseException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\UnprocessableEntityException' => $vendorDir . '/milo/github-api/src/Github/exceptions.php',
);

View file

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View file

@ -0,0 +1,9 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View file

@ -0,0 +1,46 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit9d96850ef8cfe136562bea1964c9dec8
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit9d96850ef8cfe136562bea1964c9dec8', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit9d96850ef8cfe136562bea1964c9dec8', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit9d96850ef8cfe136562bea1964c9dec8::getInitializer($loader));
} else {
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->setClassMapAuthoritative(true);
$loader->register(true);
return $loader;
}
}

View file

@ -0,0 +1,55 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit9d96850ef8cfe136562bea1964c9dec8
{
public static $classMap = array (
'Milo\\Github\\Api' => __DIR__ . '/..' . '/milo/github-api/src/Github/Api.php',
'Milo\\Github\\ApiException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\BadRequestException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\ForbiddenException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Helpers' => __DIR__ . '/..' . '/milo/github-api/src/Github/Helpers.php',
'Milo\\Github\\Http\\AbstractClient' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/AbstractClient.php',
'Milo\\Github\\Http\\BadResponseException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Http\\CachedClient' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/CachedClient.php',
'Milo\\Github\\Http\\CurlClient' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/CurlClient.php',
'Milo\\Github\\Http\\IClient' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/IClient.php',
'Milo\\Github\\Http\\Message' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/Message.php',
'Milo\\Github\\Http\\Request' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/Request.php',
'Milo\\Github\\Http\\Response' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/Response.php',
'Milo\\Github\\Http\\StreamClient' => __DIR__ . '/..' . '/milo/github-api/src/Github/Http/StreamClient.php',
'Milo\\Github\\IException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\InvalidResponseException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\JsonException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\LogicException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\MissingParameterException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\NotFoundException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\OAuth\\Configuration' => __DIR__ . '/..' . '/milo/github-api/src/Github/OAuth/Configuration.php',
'Milo\\Github\\OAuth\\Login' => __DIR__ . '/..' . '/milo/github-api/src/Github/OAuth/Login.php',
'Milo\\Github\\OAuth\\LoginException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\OAuth\\Token' => __DIR__ . '/..' . '/milo/github-api/src/Github/OAuth/Token.php',
'Milo\\Github\\Paginator' => __DIR__ . '/..' . '/milo/github-api/src/Github/Paginator.php',
'Milo\\Github\\RateLimitExceedException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\RuntimeException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Sanity' => __DIR__ . '/..' . '/milo/github-api/src/Github/Sanity.php',
'Milo\\Github\\Storages\\FileCache' => __DIR__ . '/..' . '/milo/github-api/src/Github/Storages/FileCache.php',
'Milo\\Github\\Storages\\ICache' => __DIR__ . '/..' . '/milo/github-api/src/Github/Storages/ICache.php',
'Milo\\Github\\Storages\\ISessionStorage' => __DIR__ . '/..' . '/milo/github-api/src/Github/Storages/ISessionStorage.php',
'Milo\\Github\\Storages\\MissingDirectoryException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\Storages\\SessionStorage' => __DIR__ . '/..' . '/milo/github-api/src/Github/Storages/SessionStorage.php',
'Milo\\Github\\UnauthorizedException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\UnexpectedResponseException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
'Milo\\Github\\UnprocessableEntityException' => __DIR__ . '/..' . '/milo/github-api/src/Github/exceptions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->classMap = ComposerStaticInit9d96850ef8cfe136562bea1964c9dec8::$classMap;
}, null, ClassLoader::class);
}
}

View file

@ -0,0 +1,606 @@
<?php
namespace Milo\Github;
/**
* Github API client library. Read readme.md in repository {@link http://github.com/milo/github-api}
*
* @see https://developer.github.com/v3/
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Api extends Sanity
{
/** @var string */
private $url = 'https://api.github.com';
/** @var string */
private $defaultAccept = 'application/vnd.github.v3+json';
/** @var array|NULL */
private $defaultParameters = [];
/** @var Http\IClient */
private $client;
/** @var OAuth\Token|NULL */
private $token;
public function __construct(Http\IClient $client = NULL)
{
$this->client = $client ?: Helpers::createDefaultClient();
}
/**
* @return self
*/
public function setToken(OAuth\Token $token = NULL)
{
$this->token = $token;
return $this;
}
/**
* @return OAuth\Token|NULL
*/
public function getToken()
{
return $this->token;
}
/**
* @param array
* @return self
*/
public function setDefaultParameters(array $defaults = NULL)
{
$this->defaultParameters = $defaults ?: [];
return $this;
}
/**
* @return array
*/
public function getDefaultParameters()
{
return $this->defaultParameters;
}
/**
* @param string
* @return self
*/
public function setDefaultAccept($accept)
{
$this->defaultAccept = $accept;
return $this;
}
/**
* @return string
*/
public function getDefaultAccept()
{
return $this->defaultAccept;
}
/**
* @see createRequest()
* @see request()
*
* @param string
* @return Http\Response
*
* @throws MissingParameterException
*/
public function delete($urlPath, array $parameters = [], array $headers = [])
{
return $this->request(
$this->createRequest(Http\Request::DELETE, $urlPath, $parameters, $headers)
);
}
/**
* @see createRequest()
* @see request()
*
* @param string
* @return Http\Response
*
* @throws MissingParameterException
*/
public function get($urlPath, array $parameters = [], array $headers = [])
{
return $this->request(
$this->createRequest(Http\Request::GET, $urlPath, $parameters, $headers)
);
}
/**
* @see createRequest()
* @see request()
*
* @param string
* @return Http\Response
*
* @throws MissingParameterException
*/
public function head($urlPath, array $parameters = [], array $headers = [])
{
return $this->request(
$this->createRequest(Http\Request::HEAD, $urlPath, $parameters, $headers)
);
}
/**
* @see createRequest()
* @see request()
*
* @param string
* @param mixed
* @return Http\Response
*
* @throws MissingParameterException
* @throws JsonException
*/
public function patch($urlPath, $content, array $parameters = [], array $headers = [])
{
return $this->request(
$this->createRequest(Http\Request::PATCH, $urlPath, $parameters, $headers, $content)
);
}
/**
* @see createRequest()
* @see request()
*
* @param string
* @param mixed
* @return Http\Response
*
* @throws MissingParameterException
* @throws JsonException
*/
public function post($urlPath, $content, array $parameters = [], array $headers = [])
{
return $this->request(
$this->createRequest(Http\Request::POST, $urlPath, $parameters, $headers, $content)
);
}
/**
* @see createRequest()
* @see request()
*
* @param string
* @param mixed
* @return Http\Response
*
* @throws MissingParameterException
* @throws JsonException
*/
public function put($urlPath, $content = NULL, array $parameters = [], array $headers = [])
{
return $this->request(
$this->createRequest(Http\Request::PUT, $urlPath, $parameters, $headers, $content)
);
}
/**
* @return Http\Response
*
* @throws Http\BadResponseException
*/
public function request(Http\Request $request)
{
$request = clone $request;
$request->addHeader('Accept', $this->defaultAccept);
$request->addHeader('Time-Zone', date_default_timezone_get());
$request->addHeader('User-Agent', 'milo/github-api');
if ($this->token) {
/** @todo Distinguish token type? */
$request->addHeader('Authorization', "token {$this->token->getValue()}");
}
return $this->client->request($request);
}
/**
* @param string Http\Request::GET|POST|...
* @param string path like '/users/:user/repos' where ':user' is substitution
* @param array[name => value] replaces substitutions in $urlPath, the rest is appended as query string to URL
* @param array[name => value] name is case-insensitive
* @param mixed|NULL arrays and objects are encoded to JSON and Content-Type is set
* @return Http\Request
*
* @throws MissingParameterException when substitution is used in URL but parameter is missing
* @throws JsonException when encoding to JSON fails
*/
public function createRequest($method, $urlPath, array $parameters = [], array $headers = [], $content = NULL)
{
if (stripos($urlPath, $this->url) === 0) { # Allows non-HTTPS URLs
$baseUrl = $this->url;
$urlPath = substr($urlPath, strlen($this->url));
} elseif (preg_match('#^(https://[^/]+)(/.*)?$#', $urlPath, $m)) {
$baseUrl = $m[1];
$urlPath = isset($m[2]) ? $m[2] : '';
} else {
$baseUrl = $this->url;
}
if (strpos($urlPath, '{') === FALSE) {
$urlPath = $this->expandColonParameters($urlPath, $parameters, $this->defaultParameters);
} else {
$urlPath = $this->expandUriTemplate($urlPath, $parameters, $this->defaultParameters);
}
$url = rtrim($baseUrl, '/') . '/' . ltrim($urlPath, '/');
if ($content !== NULL && (is_array($content) || is_object($content))) {
$headers['Content-Type'] = 'application/json; charset=utf-8';
$content = Helpers::jsonEncode($content);
}
return new Http\Request($method, $url, $headers, $content);
}
/**
* @param Http\Response
* @param array|NULL these codes are treated as success; code < 300 if NULL
* @return mixed
*
* @throws ApiException
*/
public function decode(Http\Response $response, array $okCodes = NULL)
{
$content = $response->getContent();
if (preg_match('~application/json~i', $response->getHeader('Content-Type', ''))) {
try {
$content = Helpers::jsonDecode($response->getContent());
} catch (JsonException $e) {
throw new InvalidResponseException('JSON decoding failed.', 0, $e, $response);
}
if (!is_array($content) && !is_object($content)) {
throw new InvalidResponseException('Decoded JSON is not an array or object.', 0, NULL, $response);
}
}
$code = $response->getCode();
if (($okCodes === NULL && $code >= 300) || (is_array($okCodes) && !in_array($code, $okCodes))) {
/** @var $content \stdClass */
switch ($code) {
case Http\Response::S400_BAD_REQUEST:
throw new BadRequestException(self::errorMessage($content), $code, NULL, $response);
case Http\Response::S401_UNAUTHORIZED:
throw new UnauthorizedException(self::errorMessage($content), $code, NULL, $response);
case Http\Response::S403_FORBIDDEN:
if ($response->getHeader('X-RateLimit-Remaining') === '0') {
throw new RateLimitExceedException(self::errorMessage($content), $code, NULL, $response);
}
throw new ForbiddenException(self::errorMessage($content), $code, NULL, $response);
case Http\Response::S404_NOT_FOUND:
throw new NotFoundException('Resource not found or not authorized to access.', $code, NULL, $response);
case Http\Response::S422_UNPROCESSABLE_ENTITY:
throw new UnprocessableEntityException(self::errorMessage($content), $code, NULL, $response);
}
$message = $okCodes === NULL ? '< 300' : implode(' or ', $okCodes);
throw new UnexpectedResponseException("Expected response with code $message.", $code, NULL, $response);
}
return $content;
}
/**
* Creates paginator for HTTP GET requests.
*
* @see get()
*
* @param string
* @return Paginator
*
* @throws MissingParameterException
*/
public function paginator($urlPath, array $parameters = [], array $headers = [])
{
return new Paginator(
$this,
$this->createRequest(Http\Request::GET, $urlPath, $parameters, $headers)
);
}
/**
* @return Http\IClient
*/
public function getClient()
{
return $this->client;
}
/**
* @param string
* @return Api
*/
public function withUrl($url)
{
$api = clone $this;
$api->setUrl($url);
return $api;
}
/**
* @param string
* @return self
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* @param string
* @return string
*
* @throws MissingParameterException
*/
protected function expandColonParameters($url, array $parameters, array $defaultParameters)
{
$parameters += $defaultParameters;
$url = preg_replace_callback('#(^|/|\.):([^/.]+)#', function($m) use ($url, & $parameters) {
if (!isset($parameters[$m[2]])) {
throw new MissingParameterException("Missing parameter '$m[2]' for URL path '$url'.");
}
$parameter = $parameters[$m[2]];
unset($parameters[$m[2]]);
return $m[1] . rawurlencode($parameter);
}, $url);
$url = rtrim($url, '/');
if (count($parameters)) {
$url .= '?' . http_build_query($parameters);
}
return $url;
}
/**
* Expands URI template (RFC 6570).
*
* @see http://tools.ietf.org/html/rfc6570
* @todo Inject remaining default parameters into query string?
*
* @param string
* @return string
*/
protected function expandUriTemplate($url, array $parameters, array $defaultParameters)
{
$parameters += $defaultParameters;
static $operatorFlags = [
'' => ['prefix' => '', 'separator' => ',', 'named' => FALSE, 'ifEmpty' => '', 'reserved' => FALSE],
'+' => ['prefix' => '', 'separator' => ',', 'named' => FALSE, 'ifEmpty' => '', 'reserved' => TRUE],
'#' => ['prefix' => '#', 'separator' => ',', 'named' => FALSE, 'ifEmpty' => '', 'reserved' => TRUE],
'.' => ['prefix' => '.', 'separator' => '.', 'named' => FALSE, 'ifEmpty' => '', 'reserved' => FALSE],
'/' => ['prefix' => '/', 'separator' => '/', 'named' => FALSE, 'ifEmpty' => '', 'reserved' => FALSE],
';' => ['prefix' => ';', 'separator' => ';', 'named' => TRUE, 'ifEmpty' => '', 'reserved' => FALSE],
'?' => ['prefix' => '?', 'separator' => '&', 'named' => TRUE, 'ifEmpty' => '=', 'reserved' => FALSE],
'&' => ['prefix' => '&', 'separator' => '&', 'named' => TRUE, 'ifEmpty' => '=', 'reserved' => FALSE],
];
return preg_replace_callback('~{([+#./;?&])?([^}]+?)}~', function($m) use ($url, & $parameters, $operatorFlags) {
$flags = $operatorFlags[$m[1]];
$translated = [];
foreach (explode(',', $m[2]) as $name) {
$explode = FALSE;
$maxLength = NULL;
if (preg_match('~^(.+)(?:(\*)|:(\d+))$~', $name, $tmp)) { // TODO: Speed up?
$name = $tmp[1];
if (isset($tmp[3])) {
$maxLength = (int) $tmp[3];
} else {
$explode = TRUE;
}
}
if (!isset($parameters[$name])) { // TODO: Throw exception?
continue;
}
$value = $parameters[$name];
if (is_scalar($value)) {
$translated[] = $this->prefix($flags, $name, $this->escape($flags, $value, $maxLength));
} else {
$value = (array) $value;
$isAssoc = key($value) !== 0;
// The '*' (explode) modifier
if ($explode) {
$parts = [];
if ($isAssoc) {
$this->walk($value, function ($v, $k) use (& $parts, $flags, $maxLength) {
$parts[] = $this->prefix(['named' => TRUE] + $flags, $k, $this->escape($flags, $v, $maxLength));
});
} elseif ($flags['named']) {
$this->walk($value, function ($v) use (& $parts, $flags, $name, $maxLength) {
$parts[] = $this->prefix($flags, $name, $this->escape($flags, $v, $maxLength));
});
} else {
$this->walk($value, function ($v) use (& $parts, $flags, $maxLength) {
$parts[] = $this->escape($flags, $v, $maxLength);
});
}
if (isset($parts[0])) {
if ($flags['named']) {
$translated[] = implode($flags['separator'], $parts);
} else {
$translated[] = $this->prefix($flags, $name, implode($flags['separator'], $parts));
}
}
} else {
$parts = [];
$this->walk($value, function($v, $k) use (& $parts, $isAssoc, $flags, $maxLength) {
if ($isAssoc) {
$parts[] = $this->escape($flags, $k);
}
$parts[] = $this->escape($flags, $v, $maxLength);
});
if (isset($parts[0])) {
$translated[] = $this->prefix($flags, $name, implode(',', $parts));
}
}
}
}
if (isset($translated[0])) {
return $flags['prefix'] . implode($flags['separator'], $translated);
}
return '';
}, $url);
}
/**
* @param array
* @param string
* @param string already escaped
* @return string
*/
private function prefix(array $flags, $name, $value)
{
$prefix = '';
if ($flags['named']) {
$prefix .= $this->escape($flags, $name);
if (isset($value[0])) {
$prefix .= '=';
} else {
$prefix .= $flags['ifEmpty'];
}
}
return $prefix . $value;
}
/**
* @param array
* @param mixed
* @param int|NULL
* @return string
*/
private function escape(array $flags, $value, $maxLength = NULL)
{
$value = (string) $value;
if ($maxLength !== NULL) {
if (preg_match('~^(.{' . $maxLength . '}).~u', $value, $m)) {
$value = $m[1];
} elseif (strlen($value) > $maxLength) { # when malformed UTF-8
$value = substr($value, 0, $maxLength);
}
}
if ($flags['reserved']) {
$parts = preg_split('~(%[0-9a-fA-F]{2}|[:/?#[\]@!$&\'()*+,;=])~', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
$parts[] = '';
$escaped = '';
for ($i = 0, $count = count($parts); $i < $count; $i += 2) {
$escaped .= rawurlencode($parts[$i]) . $parts[$i + 1];
}
return $escaped;
}
return rawurlencode($value);
}
/**
* @param array
* @param callable
*/
private function walk(array $array, $cb)
{
foreach ($array as $k => $v) {
if ($v === NULL) {
continue;
}
$cb($v, $k);
}
}
/**
* @param \stdClass
* @return string
*/
private static function errorMessage($content)
{
$message = isset($content->message)
? $content->message
: 'Unknown error';
if (isset($content->errors)) {
$message .= implode(', ', array_map(function($error) {
return '[' . implode(':', (array) $error) . ']';
}, $content->errors));
}
return $message;
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Milo\Github;
/**
* Just helpers.
*
* The JSON encode/decode methods are stolen from Nette Utils (https://github.com/nette/utils).
*
* @author David Grudl
* @author Miloslav Hůla (https://github.com/milo)
*/
class Helpers
{
private static $jsonMessages = [
JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
JSON_ERROR_STATE_MISMATCH => 'Syntax error, malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Invalid UTF-8 sequence',
];
/** @var Http\IClient */
private static $client;
/**
* @param mixed
* @return string
*
* @throws JsonException
*/
public static function jsonEncode($value)
{
if (PHP_VERSION_ID < 50500) {
set_error_handler(function($severity, $message) { // needed to receive 'recursion detected' error
restore_error_handler();
throw new JsonException($message);
});
}
$json = json_encode($value, JSON_UNESCAPED_UNICODE);
if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
if ($error = json_last_error()) {
$message = isset(static::$jsonMessages[$error])
? static::$jsonMessages[$error]
: (PHP_VERSION_ID >= 50500 ? json_last_error_msg() : 'Unknown error');
throw new JsonException($message, $error);
}
$json = str_replace(array("\xe2\x80\xa8", "\xe2\x80\xa9"), array('\u2028', '\u2029'), $json);
return $json;
}
/**
* @param mixed
* @return string
*
* @throws JsonException
*/
public static function jsonDecode($json)
{
$json = (string) $json;
if (!preg_match('##u', $json)) {
throw new JsonException('Invalid UTF-8 sequence', 5); // PECL JSON-C
}
$value = json_decode($json, FALSE, 512, (defined('JSON_C_VERSION') && PHP_INT_SIZE > 4) ? 0 : JSON_BIGINT_AS_STRING);
if ($value === NULL && $json !== '' && strcasecmp($json, 'null')) { // '' does not clear json_last_error()
$error = json_last_error();
throw new JsonException(isset(static::$jsonMessages[$error]) ? static::$jsonMessages[$error] : 'Unknown error', $error);
}
return $value;
}
/**
* @param bool
* @return Http\IClient
*/
public static function createDefaultClient($newInstance = FALSE)
{
if (self::$client === NULL || $newInstance) {
self::$client = extension_loaded('curl')
? new Http\CurlClient
: new Http\StreamClient;
}
return self::$client;
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Milo\Github\Http;
use Milo\Github;
/**
* Ancestor for HTTP clients. Cares about redirecting and debug events.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
abstract class AbstractClient extends Github\Sanity implements IClient
{
/** @var int[] will follow Location header on these response codes */
public $redirectCodes = [
Response::S301_MOVED_PERMANENTLY,
Response::S302_FOUND,
Response::S307_TEMPORARY_REDIRECT,
];
/** @var int maximum redirects per request*/
public $maxRedirects = 5;
/** @var callable|NULL */
private $onRequest;
/** @var callable|NULL */
private $onResponse;
/**
* @see https://developer.github.com/v3/#http-redirects
*
* @return Response
*
* @throws BadResponseException
*/
public function request(Request $request)
{
$request = clone $request;
$counter = $this->maxRedirects;
$previous = NULL;
do {
$this->setupRequest($request);
$this->onRequest && call_user_func($this->onRequest, $request);
$response = $this->process($request);
$this->onResponse && call_user_func($this->onResponse, $response);
$previous = $response->setPrevious($previous);
if ($counter > 0 && in_array($response->getCode(), $this->redirectCodes) && $response->hasHeader('Location')) {
/** @todo Use the same HTTP $method for redirection? Set $content to NULL? */
$request = new Request(
$request->getMethod(),
$response->getHeader('Location'),
$request->getHeaders(),
$request->getContent()
);
$counter--;
continue;
}
break;
} while (TRUE);
return $response;
}
/**
* @param callable|NULL function(Request $request)
* @return self
*/
public function onRequest($callback)
{
$this->onRequest = $callback;
return $this;
}
/**
* @param callable|NULL function(Response $response)
* @return self
*/
public function onResponse($callback)
{
$this->onResponse = $callback;
return $this;
}
protected function setupRequest(Request $request)
{
$request->addHeader('Expect', '');
}
/**
* @return Response
*
* @throws BadResponseException
*/
abstract protected function process(Request $request);
}

View file

@ -0,0 +1,143 @@
<?php
namespace Milo\Github\Http;
use Milo\Github;
use Milo\Github\Storages;
/**
* Caching for HTTP clients.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class CachedClient extends Github\Sanity implements IClient
{
/** @var Storages\ICache|NULL */
private $cache;
/** @var IClient */
private $client;
/** @var bool */
private $forbidRecheck;
/** @var callable|NULL */
private $onResponse;
/**
* @param Storages\ICache
* @param IClient
* @param bool forbid checking Github for new data; more or less development purpose only
*/
public function __construct(Storages\ICache $cache, IClient $client = NULL, $forbidRecheck = FALSE)
{
$this->cache = $cache;
$this->client = $client ?: Github\Helpers::createDefaultClient();
$this->forbidRecheck = (bool) $forbidRecheck;
}
/**
* @return IClient
*/
public function getInnerClient()
{
return $this->client;
}
/**
* @return Response
*
* @throws BadResponseException
*/
public function request(Request $request)
{
$request = clone $request;
$cacheKey = implode('.', [
$request->getMethod(),
$request->getUrl(),
/** @todo This should depend on Vary: header */
$request->getHeader('Accept'),
$request->getHeader('Accept-Encoding'),
$request->getHeader('Authorization')
]);
if ($cached = $this->cache->load($cacheKey)) {
if ($this->forbidRecheck) {
$cached = clone $cached;
$this->onResponse && call_user_func($this->onResponse, $cached);
return $cached;
}
/** @var $cached Response */
if ($cached->hasHeader('Last-Modified')) {
$request->addHeader('If-Modified-Since', $cached->getHeader('Last-Modified'));
}
if ($cached->hasHeader('ETag')) {
$request->addHeader('If-None-Match', $cached->getHeader('ETag'));
}
}
$response = $this->client->request($request);
if ($this->isCacheable($response)) {
$this->cache->save($cacheKey, clone $response);
}
if (isset($cached) && $response->getCode() === Response::S304_NOT_MODIFIED) {
$cached = clone $cached;
/** @todo Should be responses somehow combined into one? */
$response = $cached->setPrevious($response);
}
$this->onResponse && call_user_func($this->onResponse, $response);
return $response;
}
/**
* @param callable|NULL function(Request $request)
* @return self
*/
public function onRequest($callback)
{
$this->client->onRequest($callback);
return $this;
}
/**
* @param callable|NULL function(Response $response)
* @return self
*/
public function onResponse($callback)
{
$this->client->onResponse(NULL);
$this->onResponse = $callback;
return $this;
}
/**
* @return bool
*/
protected function isCacheable(Response $response)
{
/** @todo Do it properly. Vary:, Pragma:, TTL... */
if (!$response->isCode(200)) {
return FALSE;
} elseif (preg_match('#max-age=0|must-revalidate#i', $response->getHeader('Cache-Control', ''))) {
return FALSE;
}
return $response->hasHeader('ETag') || $response->hasHeader('Last-Modified');
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace Milo\Github\Http;
use Milo\Github;
/**
* HTTP client which use the cURL extension functions.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class CurlClient extends AbstractClient
{
/** @var array|NULL */
private $options;
/** @var resource */
private $curl;
/**
* @param array cURL options {@link http://php.net/manual/en/function.curl-setopt.php}
*
* @throws Github\LogicException
*/
public function __construct(array $options = NULL)
{
if (!extension_loaded('curl')) {
throw new Github\LogicException('cURL extension is not loaded.');
}
$this->options = $options;
}
protected function setupRequest(Request $request)
{
parent::setupRequest($request);
$request->addHeader('Connection', 'keep-alive');
}
/**
* @return Response
*
* @throws BadResponseException
*/
protected function process(Request $request)
{
$headers = [];
foreach ($request->getHeaders() as $name => $value) {
$headers[] = "$name: $value";
}
$responseHeaders = [];
$softOptions = [
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => 1,
CURLOPT_CAINFO => realpath(__DIR__ . '/../../ca-chain.crt'),
];
$hardOptions = [
CURLOPT_FOLLOWLOCATION => FALSE, # Github sets the Location header for 201 code too and redirection is not required for us
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => $request->getMethod(),
CURLOPT_NOBODY => $request->isMethod(Request::HEAD),
CURLOPT_URL => $request->getUrl(),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_POSTFIELDS => $request->getContent(),
CURLOPT_HEADER => FALSE,
CURLOPT_HEADERFUNCTION => function($curl, $line) use (& $responseHeaders, & $last) {
if (strncasecmp($line, 'HTTP/', 5) === 0) {
/** @todo Set proxy response as Response::setPrevious($proxyResponse)? */
# The HTTP/x.y may occur multiple times with proxy (HTTP/1.1 200 Connection Established)
$responseHeaders = [];
} elseif (in_array(substr($line, 0, 1), [' ', "\t"], TRUE)) {
$responseHeaders[$last] .= ' ' . trim($line); # RFC2616, 2.2
} elseif ($line !== "\r\n") {
list($name, $value) = explode(':', $line, 2);
$responseHeaders[$last = trim($name)] = trim($value);
}
return strlen($line);
},
];
if (defined('CURLOPT_PROTOCOLS')) { # HHVM issue. Even cURL v7.26.0, constants are missing.
$hardOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
}
if (!$this->curl) {
$this->curl = curl_init();
if ($this->curl === FALSE) {
throw new BadResponseException('Cannot init cURL handler.');
}
}
$result = curl_setopt_array($this->curl, $hardOptions + ($this->options ?: []) + $softOptions);
if ($result === FALSE) {
throw new BadResponseException('Setting cURL options failed: ' . curl_error($this->curl), curl_errno($this->curl));
}
$content = curl_exec($this->curl);
if ($content === FALSE) {
throw new BadResponseException(curl_error($this->curl), curl_errno($this->curl));
}
$code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
if ($code === FALSE) {
throw new BadResponseException('HTTP status code is missing:' . curl_error($this->curl), curl_errno($this->curl));
}
return new Response($code, $responseHeaders, $content);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Milo\Github\Http;
/**
* HTTP client interface.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
interface IClient
{
/**
* @return Response
*/
function request(Request $request);
/**
* @param callable|NULL
* @return self
*/
function onRequest($callback);
/**
* @param callable|NULL
* @return self
*/
function onResponse($callback);
}

View file

@ -0,0 +1,108 @@
<?php
namespace Milo\Github\Http;
use Milo\Github;
/**
* HTTP request or response ascendant.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
abstract class Message extends Github\Sanity
{
/** @var array[name => value] */
private $headers = [];
/** @var string|NULL */
private $content;
/**
* @param array
* @param string|NULL
*/
public function __construct(array $headers = [], $content = NULL)
{
$this->headers = array_change_key_case($headers, CASE_LOWER);
$this->content = $content;
}
/**
* @param string
* @return bool
*/
public function hasHeader($name)
{
return array_key_exists(strtolower($name), $this->headers);
}
/**
* @param string
* @param mixed
* @return mixed
*/
public function getHeader($name, $default = NULL)
{
$name = strtolower($name);
return array_key_exists($name, $this->headers)
? $this->headers[$name]
: $default;
}
/**
* @param string
* @param string
* @return self
*/
protected function addHeader($name, $value)
{
$name = strtolower($name);
if (!array_key_exists($name, $this->headers) && $value !== NULL) {
$this->headers[$name] = $value;
}
return $this;
}
/**
* @param string
* @param string|NULL
* @return self
*/
protected function setHeader($name, $value)
{
$name = strtolower($name);
if ($value === NULL) {
unset($this->headers[$name]);
} else {
$this->headers[$name] = $value;
}
return $this;
}
/**
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* @return string|NULL
*/
public function getContent()
{
return $this->content;
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Milo\Github\Http;
use Milo\Github;
/**
* HTTP request envelope.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Request extends Message
{
/** HTTP request method */
const
DELETE = 'DELETE',
GET = 'GET',
HEAD = 'HEAD',
PATCH = 'PATCH',
POST = 'POST',
PUT = 'PUT';
/** @var string */
private $method;
/** @var string */
private $url;
/**
* @param string
* @param string
* @param array
* @param string|NULL
*/
public function __construct($method, $url, array $headers = [], $content = NULL)
{
$this->method = $method;
$this->url = $url;
parent::__construct($headers, $content);
}
/**
* @param string
* @return bool
*/
public function isMethod($method)
{
return strcasecmp($this->method, $method) === 0;
}
/**
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* @param string
* @param string
* @return self
*/
public function addHeader($name, $value)
{
return parent::addHeader($name, $value);
}
/**
* @param string
* @param string|NULL
* @return self
*/
public function setHeader($name, $value)
{
return parent::setHeader($name, $value);
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Milo\Github\Http;
use Milo\Github;
/**
* HTTP response envelope.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Response extends Message
{
/** HTTP 1.1 code */
const
S200_OK = 200,
S301_MOVED_PERMANENTLY = 301,
S302_FOUND = 302,
S304_NOT_MODIFIED = 304,
S307_TEMPORARY_REDIRECT = 307,
S400_BAD_REQUEST = 400,
S401_UNAUTHORIZED = 401,
S403_FORBIDDEN = 403,
S404_NOT_FOUND = 404,
S422_UNPROCESSABLE_ENTITY = 422;
/** @var int */
private $code;
/** @var Response */
private $previous;
/**
* @param int
* @param array
* @param string
*/
public function __construct($code, array $headers, $content)
{
$this->code = (int) $code;
parent::__construct($headers, $content);
}
/**
* HTTP code.
* @return int
*/
public function getCode()
{
return $this->code;
}
/**
* @param int
* @return bool
*/
public function isCode($code)
{
return $this->code === (int) $code;
}
/**
* @return Response|NULL
*/
public function getPrevious()
{
return $this->previous;
}
/**
* @return self
*
* @throws Github\LogicException
*/
public function setPrevious(Response $previous = NULL)
{
if ($this->previous) {
throw new Github\LogicException('Previous response is already set.');
}
$this->previous = $previous;
return $this;
}
}

View file

@ -0,0 +1,117 @@
<?php
namespace Milo\Github\Http;
/**
* Client which use the file_get_contents() with a HTTP context options.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class StreamClient extends AbstractClient
{
/** @var array|NULL */
private $sslOptions;
/**
* @param array SSL context options {@link http://php.net/manual/en/context.ssl.php}
*/
public function __construct(array $sslOptions = NULL)
{
$this->sslOptions = $sslOptions;
}
protected function setupRequest(Request $request)
{
parent::setupRequest($request);
$request->setHeader('Connection', 'close');
}
/**
* @return Response
*
* @throws BadResponseException
*/
protected function process(Request $request)
{
$headerStr = [];
foreach ($request->getHeaders() as $name => $value) {
foreach ((array) $value as $v) {
$headerStr[] = "$name: $v";
}
}
$options = [
'http' => [
'method' => $request->getMethod(),
'header' => implode("\r\n", $headerStr) . "\r\n",
'follow_location' => 0, # Github sets the Location header for 201 code too and redirection is not required for us
'protocol_version' => 1.1,
'ignore_errors' => TRUE,
],
'ssl' => [
'verify_peer' => TRUE,
'cafile' => realpath(__DIR__ . '/../../ca-chain.crt'),
'disable_compression' => TRUE, # Effective since PHP 5.4.13
],
];
if (($content = $request->getContent()) !== NULL) {
$options['http']['content'] = $content;
}
if ($this->sslOptions) {
$options['ssl'] = $this->sslOptions + $options['ssl'];
}
list($code, $headers, $content) = $this->fileGetContents($request->getUrl(), $options);
return new Response($code, $headers, $content);
}
/**
* @internal
* @param string
* @param array
* @return array
*
* @throws BadResponseException
*/
protected function fileGetContents($url, array $contextOptions)
{
$context = stream_context_create($contextOptions);
$e = NULL;
set_error_handler(function($severity, $message, $file, $line) use (& $e) {
$e = new \ErrorException($message, 0, $severity, $file, $line, $e);
}, E_WARNING);
$content = file_get_contents($url, FALSE, $context);
restore_error_handler();
if (!isset($http_response_header)) {
throw new BadResponseException('Missing HTTP headers, request failed.', 0, $e);
}
if (!isset($http_response_header[0]) || !preg_match('~^HTTP/1[.]. (\d{3})~i', $http_response_header[0], $m)) {
throw new BadResponseException('HTTP status code is missing.', 0, $e);
}
unset($http_response_header[0]);
$headers = [];
foreach ($http_response_header as $header) {
if (in_array(substr($header, 0, 1), [' ', "\t"], TRUE)) {
$headers[$last] .= ' ' . trim($header); # RFC2616, 2.2
} else {
list($name, $value) = explode(':', $header, 2) + [NULL, NULL];
$headers[$last = trim($name)] = trim($value);
}
}
return [$m[1], $headers, $content];
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Milo\Github\OAuth;
use Milo\Github;
/**
* Configuration for OAuth token obtaining.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Configuration extends Github\Sanity
{
/** @var string */
public $clientId;
/** @var string */
public $clientSecret;
/** @var string[] */
public $scopes;
/**
* @param string
* @param string
* @param string[]
*/
public function __construct($clientId, $clientSecret, array $scopes = [])
{
$this->clientId = $clientId;
$this->clientSecret = $clientSecret;
$this->scopes = $scopes;
}
/**
* @return Configuration
*/
public static function fromArray(array $conf)
{
return new static($conf['clientId'], $conf['clientSecret'], isset($conf['scopes']) ? $conf['scopes'] : []);
}
}

View file

@ -0,0 +1,171 @@
<?php
namespace Milo\Github\OAuth;
use Milo\Github;
use Milo\Github\Storages;
use Milo\Github\Http;
/**
* OAuth token obtaining process.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Login extends Github\Sanity
{
/** @var string */
private $authUrl = 'https://github.com/login/oauth/authorize';
/** @var string */
private $tokenUrl = 'https://github.com/login/oauth/access_token';
/** @var Configuration */
private $conf;
/** @var Storages\ISessionStorage */
private $storage;
/** @var Http\IClient */
private $client;
public function __construct(Configuration $conf, Storages\ISessionStorage $storage = NULL, Http\IClient $client = NULL)
{
$this->conf = $conf;
$this->storage = $storage ?: new Storages\SessionStorage;
$this->client = $client ?: Github\Helpers::createDefaultClient();
}
/**
* @return Http\IClient
*/
public function getClient()
{
return $this->client;
}
/**
* @param string URL to redirect back from Github when user approves the permissions request
* @param callable function($githubUrl) makes HTTP redirect to Github
*/
public function askPermissions($backUrl, $redirectCb = NULL)
{
/** @todo Something more safe? */
$state = sha1(uniqid(microtime(TRUE), TRUE));
$params = [
'client_id' => $this->conf->clientId,
'redirect_uri' => $backUrl,
'scope' => implode(',', $this->conf->scopes),
'state' => $state,
];
$this->storage->set('auth.state', $state);
$url = $this->authUrl . '?' . http_build_query($params);
if ($redirectCb === NULL) {
header("Location: $url");
die();
} else {
call_user_func($redirectCb, $url);
}
}
/**
* @param string
* @param string
* @return Token
*
* @throws LoginException
*/
public function obtainToken($code, $state)
{
if ($state !== $this->storage->get('auth.state')) {
throw new LoginException('OAuth security state does not match.');
}
$params = [
'client_id' => $this->conf->clientId,
'client_secret' => $this->conf->clientSecret,
'code' => $code,
];
$headers = [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
];
$request = new Http\Request(Http\Request::POST, $this->tokenUrl, $headers, http_build_query($params));
try {
$response = $this->client->request($request);
} catch (Http\BadResponseException $e) {
throw new LoginException('HTTP request failed.', 0, $e);
}
try {
/** @var $json \stdClass */
if ($response->isCode(Http\Response::S404_NOT_FOUND)) {
$json = Github\Helpers::jsonDecode($response->getContent());
throw new LoginException($json->error, $response->getCode());
} elseif (!$response->isCode(Http\Response::S200_OK)) {
throw new LoginException('Unexpected response.', $response->getCode());
}
$json = Github\Helpers::jsonDecode($response->getContent());
} catch (Github\JsonException $e) {
throw new LoginException('Bad JSON in response.', 0, $e);
}
$token = new Token($json->access_token, $json->token_type, strlen($json->scope) ? explode(',', $json->scope) : []);
$this->storage->set('auth.token', $token->toArray());
$this->storage->remove('auth.state');
return $token;
}
/**
* @return bool
*/
public function hasToken()
{
return $this->storage->get('auth.token') !== NULL;
}
/**
* @return Token
*
* @throws Github\LogicException when token has not been obtained yet
*/
public function getToken()
{
$token = $this->storage->get('auth.token');
if ($token === NULL) {
throw new Github\LogicException('Token has not been obtained yet.');
} elseif ($token instanceof Token) {
/** @deprecated */
$token = $token->toArray();
$this->storage->set('auth.token', $token);
}
return Token::createFromArray($token);
}
/**
* @return self
*/
public function dropToken()
{
$this->storage->remove('auth.token');
return $this;
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Milo\Github\OAuth;
use Milo\Github;
/**
* OAuth token envelope.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Token extends Github\Sanity
{
/** @var string */
private $value;
/** @var string */
private $type;
/** @var string[] */
private $scopes;
/**
* @param string
* @param string
* @param string[]
*/
public function __construct($value, $type = '', array $scopes = [])
{
$this->value = $value;
$this->type = $type;
$this->scopes = $scopes;
}
/**
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string[]
*/
public function getScopes()
{
return $this->scopes;
}
/**
* @see https://developer.github.com/v3/oauth/#scopes
*
* @param string
* @return bool
*/
public function hasScope($scope)
{
if (in_array($scope, $this->scopes, TRUE)) {
return TRUE;
}
static $superiors = [
'user:email' => 'user',
'user:follow' => 'user',
'notifications' => 'repo',
];
if (array_key_exists($scope, $superiors) && in_array($superiors[$scope], $this->scopes, TRUE)) {
return TRUE;
}
return FALSE;
}
/** @internal */
public function toArray()
{
return [
'value' => $this->value,
'type' => $this->type,
'scopes' => $this->scopes,
];
}
/** @internal */
public static function createFromArray(array $data)
{
return new static($data['value'], $data['type'], $data['scopes']);
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Milo\Github;
/**
* Iterates through the Github API responses by Link: header.
*
* @see https://developer.github.com/guides/traversing-with-pagination/
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class Paginator extends Sanity implements \Iterator
{
/** @var Api */
private $api;
/** @var Http\Request */
private $firstRequest;
/** @var Http\Request|NULL */
private $request;
/** @var Http\Response|NULL */
private $response;
/** @var int */
private $limit;
/** @var int */
private $counter = 0;
public function __construct(Api $api, Http\Request $request)
{
$this->api = $api;
$this->firstRequest = clone $request;
}
/**
* Limits maximum steps of iteration.
*
* @param int|NULL
* @return self
*/
public function limit($limit)
{
$this->limit = $limit === NULL
? NULL
: (int) $limit;
return $this;
}
/**
* @return void
*/
public function rewind()
{
$this->request = $this->firstRequest;
$this->response = NULL;
$this->counter = 0;
}
/**
* @return bool
*/
public function valid()
{
return $this->request !== NULL && ($this->limit === NULL || $this->counter < $this->limit);
}
/**
* @return Http\Response
*/
public function current()
{
$this->load();
return $this->response;
}
/**
* @return int
*/
public function key()
{
return static::parsePage($this->request->getUrl());
}
/**
* @return void
*/
public function next()
{
$this->load();
if ($url = static::parseLink($this->response->getHeader('Link'), 'next')) {
$this->request = new Http\Request(
$this->request->getMethod(),
$url,
$this->request->getHeaders(),
$this->request->getContent()
);
} else {
$this->request = NULL;
}
$this->response = NULL;
$this->counter++;
}
private function load()
{
if ($this->response === NULL) {
$this->response = $this->api->request($this->request);
}
}
/**
* @param string
* @return int
*/
public static function parsePage($url)
{
list (, $parametersStr) = explode('?', $url, 2) + ['', ''];
parse_str($parametersStr, $parameters);
return isset($parameters['page'])
? max(1, (int) $parameters['page'])
: 1;
}
/**
* @see https://developer.github.com/guides/traversing-with-pagination/#navigating-through-the-pages
*
* @param string
* @param string
* @return string|NULL
*/
public static function parseLink($link, $rel)
{
if (!preg_match('(<([^>]+)>;\s*rel="' . preg_quote($rel) . '")', $link, $match)) {
return NULL;
}
return $match[1];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Milo\Github;
/**
* Undefined member access check. Stolen from Nette\Object (http://nette.org).
*/
abstract class Sanity
{
/**
* @throws LogicException
*/
public function & __get($name)
{
throw new LogicException('Cannot read an undeclared property ' . get_class($this) . "::\$$name.");
}
/**
* @throws LogicException
*/
public function __set($name, $value)
{
throw new LogicException('Cannot write to an undeclared property ' . get_class($this) . "::\$$name.");
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace Milo\Github\Storages;
use Milo\Github;
/**
* Naive file cache implementation.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class FileCache extends Github\Sanity implements ICache
{
/** @var string */
private $dir;
/**
* @param string temporary directory
*
* @throws MissingDirectoryException
*/
public function __construct($tempDir)
{
if (!is_dir($tempDir)) {
throw new MissingDirectoryException("Directory '$tempDir' is missing.");
}
$dir = $tempDir . DIRECTORY_SEPARATOR . 'milo.github-api';
if (!is_dir($dir)) {
set_error_handler(function($severity, $message, $file, $line) use ($dir, & $valid) {
restore_error_handler();
if (!is_dir($dir)) {
throw new MissingDirectoryException("Cannot create '$dir' directory.", 0, new \ErrorException($message, 0, $severity, $file, $line));
}
});
mkdir($dir);
restore_error_handler();
}
$this->dir = $dir;
}
/**
* @param string
* @param mixed
* @return mixed stored value
*/
public function save($key, $value)
{
file_put_contents(
$this->filePath($key),
serialize($value),
LOCK_EX
);
return $value;
}
/**
* @param string
* @return mixed|NULL
*/
public function load($key)
{
$path = $this->filePath($key);
if (is_file($path) && ($fd = fopen($path, 'rb')) && flock($fd, LOCK_SH)) {
$cached = stream_get_contents($fd);
flock($fd, LOCK_UN);
fclose($fd);
$success = TRUE;
set_error_handler(function() use (& $success) { return $success = FALSE; }, E_NOTICE);
$cached = unserialize($cached);
restore_error_handler();
if ($success) {
return $cached;
}
}
}
/**
* @param string
* @return string
*/
private function filePath($key)
{
return $this->dir . DIRECTORY_SEPARATOR . sha1($key) . '.php';
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Milo\Github\Storages;
interface ICache
{
/**
* @param string
* @param mixed
* @return mixed stored value
*/
function save($key, $value);
/**
* @param string
* @return mixed|NULL
*/
function load($key);
}

View file

@ -0,0 +1,32 @@
<?php
namespace Milo\Github\Storages;
/**
* Cross-request session storage.
*/
interface ISessionStorage
{
/**
* @param string
* @param mixed
* @return self
*/
function set($name, $value);
/**
* @param string
* @return mixed
*/
function get($name);
/**
* @param string
* @return self
*/
function remove($name);
}

View file

@ -0,0 +1,86 @@
<?php
namespace Milo\Github\Storages;
use Milo\Github;
/**
* Session storage which uses $_SESSION directly. Session must be started already before use.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
class SessionStorage extends Github\Sanity implements ISessionStorage
{
const SESSION_KEY = 'milo.github-api';
/** @var string */
private $sessionKey;
/**
* @param string
*/
public function __construct($sessionKey = self::SESSION_KEY)
{
$this->sessionKey = $sessionKey;
}
/**
* @param string
* @param mixed
* @return self
*/
public function set($name, $value)
{
if ($value === NULL) {
return $this->remove($name);
}
$this->check(__METHOD__);
$_SESSION[$this->sessionKey][$name] = $value;
return $this;
}
/**
* @param string
* @return mixed
*/
public function get($name)
{
$this->check(__METHOD__);
return isset($_SESSION[$this->sessionKey][$name])
? $_SESSION[$this->sessionKey][$name]
: NULL;
}
/**
* @param string
* @return self
*/
public function remove($name)
{
$this->check(__METHOD__);
unset($_SESSION[$this->sessionKey][$name]);
return $this;
}
/**
* @param string
*/
private function check($method)
{
if (!isset($_SESSION)) {
trigger_error("Start session before using $method().", E_USER_WARNING);
}
}
}

View file

@ -0,0 +1,176 @@
<?php
/**
* All Milo\Github exceptions at one place. Whole library does not throw anything else.
*
* @author Miloslav Hůla (https://github.com/milo)
*/
namespace Milo\Github {
/**
* Marker interface.
*/
interface IException
{}
/**
* Wrong algorithm. API is used in wrong way. Application code should be changed.
*/
class LogicException extends \LogicException implements IException
{}
/**
* Substitution is used in URL path but corresponding parameter is missing.
*/
class MissingParameterException extends LogicException
{}
/**
* Unpredictable situation occurred.
*/
abstract class RuntimeException extends \RuntimeException implements IException
{}
/**
* Github API returned a non-success HTTP code or data are somehow wrong. See all following descendants.
*
* @see Api::decode()
* @see https://developer.github.com/v3/#client-errors
*/
abstract class ApiException extends RuntimeException
{
/** @var Http\Response|NULL */
private $response;
/**
* @param string
* @param int
*/
public function __construct($message = '', $code = 0, \Exception $previous = NULL, Http\Response $response = NULL)
{
parent::__construct($message, $code, $previous);
$this->response = clone $response;
}
/**
* @return Http\Response|NULL
*/
final public function getResponse()
{
return $this->response;
}
}
/**
* Invalid credentials (e.g. revoked token).
*/
class UnauthorizedException extends ApiException
{}
/**
* Invalid JSON sent to Github API.
*/
class BadRequestException extends ApiException
{}
/**
* Invalid structure sent to Github API (e.g. some required fields are missing).
*/
class UnprocessableEntityException extends ApiException
{}
/**
* Access denied.
* @see https://developer.github.com/v3/#authentication
*/
class ForbiddenException extends ApiException
{}
/**
* Rate limit exceed.
* @see https://developer.github.com/v3/#rate-limiting
*/
class RateLimitExceedException extends ForbiddenException
{}
/**
* Resource not found.
* @see https://developer.github.com/v3/#authentication
*/
class NotFoundException extends ApiException
{}
/**
* Response cannot be classified.
*/
class UnexpectedResponseException extends ApiException
{}
/**
* Response from Github is somehow wrong (e.g. invalid JSON decoding).
*/
class InvalidResponseException extends ApiException
{}
/**
* JSON cannot be decoded, or value cannot be encoded to JSON.
*/
class JsonException extends RuntimeException
{
}
}
namespace Milo\Github\Http {
use Milo\Github;
/**
* HTTP response is somehow wrong and cannot be processed.
*/
class BadResponseException extends Github\RuntimeException
{}
}
namespace Milo\Github\OAuth {
use Milo\Github;
/**
* Something fails during the token obtaining.
*/
class LoginException extends Github\RuntimeException
{}
}
namespace Milo\Github\Storages {
use Milo\Github;
/**
* Directory is missing and/or cannot be created.
*/
class MissingDirectoryException extends Github\RuntimeException
{}
}

View file

@ -0,0 +1,59 @@
#
# DigiCert High Assurance EV Root CA
#
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
#
# DigiCert SHA2 High Assurance Server CA
#
-----BEGIN CERTIFICATE-----
MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3Vy
YW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2
4C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMIC
Kq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1
itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn
4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0X
sh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcft
bZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEA
MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
NAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
dC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29t
L0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIG
BFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ
UzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7D
aQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwd
aOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNH
E+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly
/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zu
xICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF
0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0Ae
cPUeybQ=
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
<?php
require __DIR__ . '/Github/exceptions.php';
require __DIR__ . '/Github/Sanity.php';
require __DIR__ . '/Github/Helpers.php';
require __DIR__ . '/Github/Storages/ICache.php';
require __DIR__ . '/Github/Storages/ISessionStorage.php';
require __DIR__ . '/Github/Http/IClient.php';
require __DIR__ . '/Github/Storages/FileCache.php';
require __DIR__ . '/Github/Storages/SessionStorage.php';
require __DIR__ . '/Github/Http/Message.php';
require __DIR__ . '/Github/Http/Request.php';
require __DIR__ . '/Github/Http/Response.php';
require __DIR__ . '/Github/Http/CachedClient.php';
require __DIR__ . '/Github/Http/AbstractClient.php';
require __DIR__ . '/Github/Http/CurlClient.php';
require __DIR__ . '/Github/Http/StreamClient.php';
require __DIR__ . '/Github/OAuth/Configuration.php';
require __DIR__ . '/Github/OAuth/Token.php';
require __DIR__ . '/Github/OAuth/Login.php';
require __DIR__ . '/Github/Api.php';
require __DIR__ . '/Github/Paginator.php';