40 Commits

Author SHA1 Message Date
ShadowEO
981c1e8717 Ignoring build directory from now on since ./compile-executable manages it. 2016-06-07 05:28:59 -04:00
ShadowEO
df02c0fc05 Implemented preliminary Server 2 Server communications and added connection-id injection into API commands. 2016-06-07 02:43:22 -04:00
ShadowEO
ff7ef4de9c Added base Libertine Management functions into WorkstationServices. 2016-05-31 18:52:31 -04:00
ShadowEO
d30f5ce5b4 Refactored defines and core includes, added root permissions check on InputDeviceServices and fixed the weird one character space in the log that occurs after the requires. 2016-05-31 16:57:43 -04:00
ShadowEO
93fb6f9358 Cleaned up input device routing code. 2016-05-31 16:30:47 -04:00
ShadowEO
f9dd46dfd4 trying again. 2016-05-31 16:11:11 -04:00
ShadowEO
c8884d3f5b Attempting to create better shutdown for InputServer 2016-05-31 16:09:28 -04:00
ShadowEO
55231f3ad4 Fixed input server finally. It works now, testing client. 2016-05-31 15:59:57 -04:00
ShadowEO
9aee152e03 More fixes.. 2016-05-31 15:46:36 -04:00
ShadowEO
e4c6b6ce4d Quick embarassing bug fix. 2016-05-31 15:43:31 -04:00
ShadowEO
d3e791a956 Alright round 15. 2016-05-31 15:41:34 -04:00
ShadowEO
960be7e998 Trying again? 2016-05-31 15:39:03 -04:00
ShadowEO
bea81b657c so many tries... 2016-05-31 15:29:43 -04:00
ShadowEO
a306fd5d35 and again... 2016-05-31 15:27:52 -04:00
ShadowEO
f94345e751 And fucking again... 2016-05-31 15:25:04 -04:00
ShadowEO
59df0c14e0 Again. 2016-05-31 15:20:17 -04:00
ShadowEO
52974b1062 Alright, let's try this again. 2016-05-31 15:14:26 -04:00
ShadowEO
1d84a45d25 Moved from native sockets to netcat in InputDeviceServices::Connect() due to ReactPHP not connecting. 2016-05-31 15:11:49 -04:00
ShadowEO
d28090e017 Added connected message to logging. [InputDeviceServices::Connect()] 2016-05-31 15:03:13 -04:00
ShadowEO
8d94936673 Added debug logging for event client. 2016-05-31 15:01:33 -04:00
ShadowEO
6570214a77 Fixed a statement order issue in InputDeviceServices::Connect() 2016-05-31 14:55:35 -04:00
ShadowEO
3374ddd5a5 Switched configuration parsing class to make Overwatch PHP 5.3 BC. 2016-05-31 14:46:18 -04:00
ShadowEO
74285a659e Added stderr redirection to uinput 2016-05-31 14:29:56 -04:00
ShadowEO
09c6a79091 Completely fixed compilation and moved versioning into build root. Build files will be made from git and checked out. They are also stored in the phar for build information. 2016-05-31 05:45:22 -04:00
ShadowEO
3a7448e899 Fixed compilation instructions in composer.json and added a script to facilitate creation of overwatchd.phar. 2016-05-31 05:17:49 -04:00
ShadowEO
55a0032efa Added external configuration loading, external module loading, some conditionals to take advantage of the new compilation support, and lastly some cleanup of unused code in the core and some documentation addition. 2016-05-31 05:15:32 -04:00
CJ Laing
fa5a2f2948 Corrected some strings, made some more concise and made overwatchd able to be compiled into a static archive. More functionality such as external configuration and modules to come. This marks the first major core update in a while. 2016-05-31 04:14:26 -04:00
ShadowEO
04e5e7ea1d Cleaned code with phptidy. 2016-05-30 17:18:20 -04:00
ShadowEO
d5ba04fc11 Implemented response packets for InputDeviceServices API requests. 2016-05-30 16:48:41 -04:00
ShadowEO
9d3033b05c Added reactphp/socket-client to facilitate InputDeviceServices client connection and fully implemented InputDeviceServices. Ready for Testing. 2016-05-30 16:42:16 -04:00
ShadowEO
37891f6a56 InputDevices update 2016-05-30 15:01:06 -04:00
ShadowEO
7522c221c0 Beginning implementation of InputDeviceServices for sharing input devices between Overwatch clients. 2016-05-28 02:27:14 -04:00
ShadowEO
2b2f180b15 Staging wallpaper change API command in WorkstationServices. 2016-05-02 18:19:55 -04:00
CJ Laing
5a87ab8348 Added gtkforphp/gintrospection to submodules 2016-05-02 15:20:07 -04:00
ShadowEO
37e1dbf788 Ignore this commit. Testing saved credentials for VSCode. 2016-05-02 04:08:40 -04:00
ShadowEO
616af572b8 Fixed binding the daemon to all sockets. 2016-05-02 04:06:35 -04:00
ShadowEO
0a024a87f4 Added a LogEcho to UploadResource() to show both client's MD5 and calculated MD5. 2016-05-02 03:54:41 -04:00
ShadowEO
44f407853e Added ASync Filesystem Code, but it is inactive as ReactPHP does not have Filesystem I/O yet. 2016-05-02 03:50:26 -04:00
ShadowEO
6149ba42b2 Cleaned up versioning. 2016-05-02 03:13:10 -04:00
ShadowEO
fa3d439b92 Changed Versioning to allow for Git based versioning. 2016-05-02 03:12:48 -04:00
1140 changed files with 95264 additions and 7946 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "gio-native"]
path = gio-native
url = https://github.com/gtkforphp/gintrospection

View File

@@ -1,130 +1,250 @@
<?php
/**
* ../overwatch/Ov3rw4tch.class.php
* Overwatchd
* @package default
*/
define("OVERWATCH_ROOT", dirname(__FILE__));
define("OVERWATCH_LIBRARIES", OVERWATCH_ROOT."/lib");
define("OVERWATCH_MODULES", OVERWATCH_LIBRARIES."/modules/");
// Load in module support and required classes.
require_once "vendor/autoload.php";
require_once OVERWATCH_LIBRARIES."/libModOverwatch.class.php";
require_once OVERWATCH_LIBRARIES."/libOverwatchAPI.class.php";
require_once OVERWATCH_LIBRARIES."/libAPIQL.class.php";
use Noodlehaus\Config;
echo PHP_EOL;
#error_reporting(0);
$quit = 0;
$arrOverwatch_Config = array();
$arrOverwatch_SystemConfig = array();
$application_channel = "release";
define("OVERWATCH_ROOT", "/home/toxus/.include/overwatch/");
define("OVERWATCH_LIBRARIES", "/home/toxus/.include/overwatch/lib");
define("OVERWATCH_MODULES", "/home/toxus/.include/overwatch/lib/modules/");
define("DEBUG", true);
$version = file_get_contents(OVERWATCH_ROOT."version");
$version = str_replace(PHP_EOL,"",$version);
echo("Overwatch Alpha\r\n");
echo("Version ".$version."\r\n");
echo("---------------\r\n");
// Load version information from built-in version file. (used for releases)
$version = file_get_contents(OVERWATCH_ROOT."/build/version");
// Define engine version so that plugins can check against it.
// We will want to define the local version information (built into the phar if compiled)
// rather than the git version, unless there's a decent way to compare versions provided by git describe.
define("OVERWATCH_VERSION", $version);
if(!defined("OVERWATCH_ROOT"))
{
// Set defaults if the OVERWATCH_ROOT constant isn't defined.
define("OVERWATCH_ROOT", "/usr/local/overwatch/");
define("OVERWATCH_LIBRARIES", "/var/overwatch/lib");
define("OVERWATCH_MODULES", "/var/overwatch/lib/modules/");
define("OVERWATCH_SOCKET", "/var/run/overwatch.sock");
// Load (or override local) version information using git repository.
if (file_exists(".git")) {
$version = `git describe`;
$application_channel = "dev";
}
function LogEcho($str, $level)
{
echo("[$level] $str\r\n");
// Load the core version from the phar if we're running compiled.
$coreversion = file_get_contents(OVERWATCH_ROOT."/build/core-version");
// Now we'll define the core version of the software as well.
$version = str_replace(PHP_EOL, "", $version);
LogEcho("Overwatchd startup, version: $version (built from: $coreversion), channel: $application_channel", "OVERWATCH");
/**
* Allows a "pretty-print" style output.
* @param mixed $str Log message.
* @param mixed $level Log level or specific part of application.
*/
function LogEcho($str, $level) {
$str = str_replace(PHP_EOL, "", $str);
$level = str_replace(PHP_EOL, "", $level);
echo "[$level] $str".PHP_EOL;
}
// Load in module support
require_once(OVERWATCH_ROOT."vendor/autoload.php");
require_once(OVERWATCH_LIBRARIES."/libModOverwatch.class.php");
require_once(OVERWATCH_LIBRARIES."/libOverwatchAPI.class.php");
require_once(OVERWATCH_LIBRARIES."/libAPIQL.class.php");
LogEcho("Loading built-in API Modules...", "OVERWATCH");
LoadAPIModules(OVERWATCH_MODULES);
// We look for a system-wide configuration file for the daemon in /etc. If one is not available,
// we then skip it.
if(file_exists(__FILE__."/overwatchd.conf.dist"))
{
$configFiles = array(__FILE__."/overwatchd.conf.dist");
}
if(file_exists("/etc/overwatchd.conf"))
{
array_push($configFiles,"/etc/overwatchd.conf");
}
if(file_exists(__FILE__."/overwatchd.conf"))
{
array_push($configFiles,__FILE__."/overwatchd.conf");
}
if(isset($configFiles))
{
$arrOverwatch_Config = new Config($configFiles);
}
// We're now looking for a local configuration file for the daemon.
// Allow turning on debug mode from a configuration option.
if(array_key_exists("DEBUG",$arrOverwatch_Config) OR $application_channel == "dev")
{
if($arrOverwatch_Config['DEBUG'] == "true")
{
define("DEBUG",true);
LogEcho("Enabling debug mode due to configuration flag..", "DEBUG");
} elseif ($application_channel == "dev")
{
define("DEBUG", true);
LogEcho("We're running under the dev channel. Debug Ahoy!", "DEBUG");
}
}
LoadModulesFromConfiguration();
$state = array();
#apiql::query("SELECT LOCATION WHERE {Device:Test}");
#apiql::query("SET LOCATION WHERE {Device:Test,LocationY:'28',LocationX:'-50',LocationZ:'50'}");
apiql::register("!SHUTDOWN", "Shutdown");
$quit == 2;
//if(!file_exists("/tmp/overwatch_pipe"))
//{
// umask(0);
// posix_mkfifo("/tmp/overwatch_pipe",0600);
//}
// We're going to register a shutdown command so that we can easily shutdown the system remotely,
// only when we're in debug mode though. We'll also turn on PHP's error messages.
if(defined("DEBUG"))
{
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
apiql::register("!SHUTDOWN", "Shutdown");
$quit == 2;
} else {
error_reporting(0);
}
// Let's set up the event loop
$loop = React\EventLoop\Factory::create();
// Now we're setting up the AutoRemote HTTP server.
$httpsock = new React\Socket\Server($loop);
$http = new React\Http\Server($httpsock, $loop);
$http->on('request', function ($request, $response) {
// TODO: Implement request handling so that AutoRemote requests can be answered as well.
AR_APICall($request->getQuery());
$response->writeHead(200, array('Content-Type' => 'text/plain'));
$response->end("OK\n");
logEcho("HTTP Request String: ". var_dump($request->getQuery(), "AR_LISTENER"));
});
$httpsock->listen(81);
// Now that the rest is set up, let's start the actual server.
$socket = new React\Socket\Server($loop);
$clients = new SplObjectStorage();
$i = 0;
$socket->on('connection', function($connection) use ($clients, &$i) {
$connection->id = ++$i;
// Socket Listener
$connection->on('data', function($message) use ($clients, $connection) {
$connection->lastAPI = str_replace(array("\n", "\r\n", "\r"),'',$message);
$connection->lastResult = apicall($connection->lastAPI);
LogEcho("Received query: ".$connection->lastAPI." from ".$connection->id, "SOCKET-API");
foreach($clients as $client) {
if ($client->id == $connection->id)
{
$client->write($connection->lastResult."\r\n");
} else {
continue;
}
}
});
$clients->attach($connection);
});
LogEcho("Loading system-wide configuration: /etc/overwatch.conf", "STARTUP");
include("/etc/overwatch.conf");
if(empty($API_PORT))
{
$API_PORT="1337";
$connection->id = ++$i;
// Socket Listener
$connection->on('data', function($message) use ($clients, $connection) {
$connection->lastAPI = str_replace(array("\n", "\r\n", "\r"), '', $message);
if ( base64_encode(base64_decode($connection->lastAPI, true)) === $connection->lastAPI){
if(DEBUG)
{
LogEcho("Raw data from conection: $connection->id, $connection->lastAPI","S2S-COMMS-DEBUG");
}
$connection->lastAPI = base64_decode($connection->lastAPI);
}
if(strtoupper($connection->lastAPI) != strtoupper("shutdown"))
{
$connection->lastAPI = str_replace("'", "\"", $connection->lastAPI);
# Let's pull apart the current request to check for a connection-id item. If it doesn't exist, we'll inject it in.
$command = explode ("{", $connection->lastAPI, 2);
$commandJSON = json_decode("{".$command[1], true);
if(!isset($commandJSON['connection-id']))
{
$commandJSON['connection-id'] = $connection->id;
$commandJSON = json_encode($commandJSON);
$connection->lastAPI = rtrim($command[0]," ")." ".$commandJSON;
}
} else {
}
$connection->lastResult = apicall($connection->lastAPI);
LogEcho("Received query: ".$connection->lastAPI." from ".$connection->id, "SOCKET-API");
foreach ($clients as $client) {
if ($client->id == $connection->id) {
if($connection->lastResult != NULL)
{
$client->write($connection->lastResult."\r\n");
}
} else {
continue;
}
}
});
$clients->attach($connection);
});
if (empty($arrOverwatch_Config['API_PORT'])) {
$API_PORT="1337";
} else {
$API_PORT= $arrOverwatch_Config['API_PORT'];
}
// Start listening on the configured port.
$socket->listen($API_PORT);
$socket->listen($API_PORT, "0.0.0.0");
#if($AR_LISTENER_ON = True)
#{
#}
LogEcho("Server running on port: $API_PORT and AutoRemote Listener running on port: $AR_PORT", "OVERWATCH");
LogEcho("Server listening on port: $API_PORT", "OVERWATCH");
// Now we begin the actual event loop.
$loop->run();
function Shutdown()
{
global $quit;
global $loop;
$loop->stop();
$quit = 1;
return "Shutting Down.";
exit(0);
/**
*
* @return unknown
*/
function Shutdown() {
global $quit;
global $loop;
$loop->stop();
$quit = 1;
return "Shutting Down.";
exit(0);
}
/**
*
* @param unknown $call
* @param unknown $apiKey (optional)
* @return unknown
*/
function APIcall($call, $apiKey = null) {
function AR_APICall($call)
{
// TODO: Implement call logic for AutoRemote requests.
#LogEcho("APIcall($call)","S2S-DEBUG");
$APIResponse = apiql::query($call);
if ($APIResponse == NULL) {
$ResponseArray = array('response_type'=>"error",
'response_data'=>array('type'=>'APIcall',
'data'=>$call,
'response'=>$APIResponse),
'response_code'=>'OVW_404');
return "ERROR ".json_encode($ResponseArray);
} else {
if(empty($APIResponse) or $APIResponse == "1")
{
return NULL;
}
return $APIResponse;
}
}
function APIcall($call, $apiKey = null)
function LoadModulesFromConfiguration()
{
$APIResponse = apiql::query($call);
if($APIResponse == null)
{
return "API REQUEST NOT FOUND.";
} else {
return $APIResponse;
}
global $arrOverwatch_Config;
if(array_key_exists("MODULES_DIR", $arrOverwatch_Config))
{
LogEcho("External module directory defined, loading external modules...","OVERWATCH");
LoadAPIModules($arrOverwatch_Config['MODULES_DIR']);
}
if(array_key_exists("SYSTEM_MODULES_DIR", $arrOverwatch_Config))
{
LogEcho("Loading modules from system store: ".$arrOverwatch_Config['SYSTEM_MODULES_DIR']);
LoadAPIModules($arrOverwatch_Config['SYSTEM_MODULES_DIR']);
}
}
?>
function is_json($json)
{
json_decode($json);
return (json_last_error() == JSON_ERROR_NONE);
}
?>

4
compile-executable Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
git describe > ./build/core-version
git tag > ./build/version
php -d phar.readonly=0 vendor/bin/phar-builder package composer.json

View File

@@ -1,8 +1,27 @@
{
"minimum-stability": "dev",
"require": {
"react/react": "^0.4.2",
"react/http": "^0.4.1",
"guzzle/guzzle": "^3.9",
"linfo/linfo": "^3.0"
"linfo/linfo": "^3.0",
"react/socket-client": "^0.5||^0.4||^0.3",
"ext-bcmath": "*",
"symfony/polyfill-mbstring": "^1.2",
"hassankhan/config": "^0.10.0",
"react/filesystem": "dev-master"
},
"require-dev": {
"macfja/phar-builder": "^0.2.4"
},
"extra": {
"phar-builder": {
"compression": "GZip",
"name": "overwatchd.phar",
"output-dir": "../",
"entry-point": "Ov3rw4tch.class.php",
"include": ["lib","build"],
"include-dev": false
}
}
}

1163
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,32 @@ abstract class API_Definition {
public function loadInst() {
API_Definition_Register();
return self;
}
abstract public function API_Definition_Register();
public function module_loaded()
{
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if($attempted == true)
{
return true;
}
if(isset($this->needs_root) and $this->needs_root != "false")
{
if($processUser['name'] == "root")
{
self::API_Register();
$attempted = true;
} else {
$attempted = true;
}
} else {
self::API_Register();
}
return true;
}
abstract public function API_Register();
}
?>

View File

@@ -31,7 +31,7 @@ function LoadAPIModules($path = OVERWATCH_MODULES)
// We're good here
if(defined("DEBUG"))
{
LogEcho("API Module Loaded: $modulename", "OV3RW4TCH-API");
LogEcho("API Module Loaded: $modulename", "OVERWATCH");
}
}
}
@@ -40,7 +40,7 @@ function LoadAPIModules($path = OVERWATCH_MODULES)
function LoadAPIModule($sql)
{
// Force Module Loader to load a specified API Module when sent
// TODO: LoadAPIModule Implementation
}
function UnloadAPIModule($sql) {}

View File

@@ -0,0 +1 @@
a:2:{s:7:"setting";s:32:"713f82c4d3dd6a2caef0bb576a88951f";s:5:"files";a:1:{s:27:"InputDeviceServices.API.php";s:32:"398b6fceb26b4d2476de53cebc6b77ed";}}

View File

@@ -0,0 +1,221 @@
<?php
/**
* InputDeviceServices.API.php
* Provides Input Device Mirroring for Devices, entirely in Userspace.
* @package default
*/
$inputserver_sock = null;
$inputserver_ref = null;
$inputclients = null;
$inputserver_sock = null;
$inputserver_conn = null;
$stream = null;
class InputDeviceServices {
/**
*
* @return unknown
*/
function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
/**
*
* @return unknown
*/
function module_loaded() {
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if($attempted == true)
{
return true;
}
if($processUser['name'] == "root")
{
self::API_Register();
$attempted = true;
} else {
LogEcho("InputDeviceServices requires Overwatchd to be running as root.", "InputDeviceServices");
$attempted = true;
}
return true;
}
/**
*
*/
function API_Register() {
apiql::register("!OPEN/!INPUT/!SERVER[json]", "InputDeviceServices::InputServer");
apiql::register("!CLOSE/!INPUT/!SERVER", "InputDeviceServices::CloseInputServer");
apiql::register("!CONNECT/!INPUT/!DEVICES[json]", "InputDeviceServices::Connect");
apiql::register("!DISCONNECT/!INPUT/!DEVICES", "InputDeviceServices::Disconnect");
}
/**
*
* @param unknown $sql
* @return unknown
*/
function CloseInputServer($sql) {
global $inputserver_ref;
global $inputserver_sock;
$inputserver_sock->shutdown();
$inputserver_ref->terminate();
return true;
}
function InputServer($sql) {
//echo(var_dump($sql));
global $loop;
global $clients;
global $inputserver_ref;
global $inputserver_sock;
global $inputclients;
$keyboard = $sql['SERVER']['keyboard'];
$mouse = $sql['SERVER']['mouse'];
$inputserver_ref = new React\ChildProcess\Process('exec /opt/uinput-mapper/input-read '.$keyboard.' -G '.$mouse.' -D -C');
$inputserver_ref->on('exit', function($exitCode, $termSignal) use ($clients) {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Event server has stopped.",
"response_code"=>"EVT_201",
"event_server_status"=>"stopped");
foreach ($clients as $client) {
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$inputserver_sock = new React\Socket\Server($loop);
$inputclients = new SplObjectStorage();
$i = 0;
$inputserver_sock->on('connection', function($connection) use ($inputclients, &$i) {
global $inputserver_ref;
global $loop;
LogEcho("Connection to Event Server.", "INPUTS");
$connection->id = ++$i;
$connection->on('end',function() use ($inputserver_ref) {
$inputserver_ref->terminate();
});
$inputclients->attach($connection);
if($inputserver_ref->isRunning() == false)
{$inputserver_ref->start($loop);}
$inputserver_ref->stdout->on('data', function($output) use ($inputclients, $connection, &$i) {
global $inputclients;
foreach ($inputclients as $inputclient) {
if ($inputclient->id == $connection->id) {
$inputclient->write($output);
} else {
continue;
}
}
});
$inputserver_ref->stderr->on('data', function($output) {
if($output != "") LogEcho($output,"INPUTS");
});
});
$inputserver_sock->listen("5055", "0.0.0.0");
LogEcho("Event server started on port 5055", "INPUTS");
//$loop->addTimer(0.001, function($timer) use ($inputserver_ref) {
//});
if($sql['SERVER']['client-string-return'] = 'true')
{
return "CONNECT INPUT DEVICES {'host':'".gethostname()."', 'port': 5055}";
}
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Event server started on port 5055",
"response_code"=>"EVT_200",
"event_server_port"=>5055,
"event_server_status"=>"started");
return json_encode($ResponseArray);
}
function Connect($sql) {
$IP = $sql['DEVICES']['host'];
$PORT = $sql['DEVICES']['port'];
global $loop;
global $input_client_process;
global $input_client;
global $stream;
//$client = new steam_socket_client('tcp://'.$IP.":".$PORT);
//$client_connection = new React\Stream\Steam($client, $loop);
$input_client_process = new React\ChildProcess\Process('/opt/uinput-mapper/input-create -C');
$input_client_process->start($loop);
$input_client_process->stderr->on('data', function ($output) {
if($output != "") LogEcho($output,"INPUTC");
});
$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dns = $dnsResolverFactory->createCached($IP, $loop);
$input_client = new React\SocketClient\Connector($loop, $dns);
$input_client->create($IP, $PORT)->then(function (React\Stream\Stream $stream) use ($IP, $PORT) {
LogEcho("Connected to event server on: ".$IP. ":". $PORT,"INPUTC");
$GLOBALS['stream'] = $stream;
$stream->on('data', function($data) {
global $input_client_process;
#if(defined("DEBUG")) LogEcho($data, "INPUTC");
$input_client_process->stdin->write($data);
});
});
#$netcat_process = new React\ChildProcess\Process('exec nc '.$IP.' '.$PORT);
#$netcat_process->start($loop);
#$netcat_process->stdout->on('data', function($output) {
# $input_client_process->stdin->write($output);
# });
//$connection->on('data', function($output) {
// $client_process->stdin->write($output);
//});
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Successfully connected to Event Server socket.",
"response_code"=>"EVT_200C");
return json_encode($ResponseArray);
}
function Disconnect($sql) {
global $input_client_process;
global $input_client;
global $stream;
$input_client_process->terminate();
$stream->end();
//$input_client->end();
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Successfully disconnected from Event Server socket.",
"response_code"=>"EVT_200D");
return json_encode($ResponseArray);
}
}

View File

@@ -0,0 +1,2 @@
#!/bin/bash
/opt/uinput-mapper/input-read $1 -G $2 -D | nc -l 5055

View File

@@ -0,0 +1,121 @@
<?php
class Server2Server {
public function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
public function module_loaded()
{
static $attempted = null;
$processUser = posix_getpwuid(posix_geteuid());
if($attempted == true)
{
return true;
}
if(isset($this->needs_root) and $this->needs_root != "false")
{
if($processUser['name'] == "root")
{
self::API_Register();
$attempted = true;
} else {
$attempted = true;
}
} else {
self::API_Register();
$attempted = true;
}
return true;
}
function API_Register()
{
apiql::register("!S2S/!QUERY[json]", "Server2Server::BeginS2SConnection");
#return true;
}
function BeginS2SConnection($sql)
{
$connectionId = $sql['QUERY']['connection-id'];
if(!isset($sql['QUERY']['server']))
{
return false;
}
# Let's find the client object;
global $clients;
foreach($clients as $client) {
if($client->id == $connectionId) {
$connection = $client;
} else {
continue;
}
}
global $loop;
$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dns = $dnsResolverFactory->createCached(gethostbyname($sql['QUERY']['server']), $loop);
$S2SConnection = new React\SocketClient\Connector($loop, $dns);
$S2SConnection->create($sql['QUERY']['server'], 1337)->then(function (React\Stream\Stream $stream) use ($sql, &$connection, $connectionId) {
LogEcho("Connection ".$connectionId." requested connection to Overwatchd instance: ".$sql['QUERY']['server'].":1337, Query: ".$sql['QUERY']['query'],"Server2Server-Comms");
$buf = "";
$sent = false;
$stream->on('data', function($data) use ($sql,&$connection,$connectionId,$stream,&$sent) {
#$data = $buf;
LogEcho("Data to connection $connectionId: $data", "S2S-DEBUG");
/*
if(is_json($data))
{
$data_from_server = json_decode($data, true);
$ResponseArray = array("response_type"=>"success",
"response_data"=>$data_from_server,
"response_code"=>$data_from_server['response_code']);
$response = json_encode($ResponseArray);
echo(var_dump($response).PHP_EOL);
$connection->write($response);
} else {
$connection->write(apiql::query($data));
}*/
if(isset($sql['QUERY']['exec']) && $sql['QUERY']['exec'] == true)
{
$connection->write(apiql::query($data));
} else {
$connection->write($data);
}
$sent = true;
});
if ( base64_encode(base64_decode($sql['QUERY']['query'], true)) !== $sql['QUERY']['query']){
#LogEcho("Raw data from conection: $connection->id, $connection->lastAPI","S2S-COMMS");
$sql['QUERY']['query'] = base64_encode($sql['QUERY']['query']);
}
$stream->write($sql['QUERY']['query']);
//$stream->end();
});
return true;
}
}

View File

@@ -0,0 +1 @@
a:2:{s:7:"setting";s:32:"713f82c4d3dd6a2caef0bb576a88951f";s:5:"files";a:1:{s:22:"SystemServices.API.php";s:32:"624824d5dcd4903a9528bfbe58af279e";}}

View File

@@ -1,183 +1,213 @@
<?php
/**
* SystemServices.API.php
*
* @package default
*/
class SystemServices {
function loadInst()
{
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
function module_loaded()
{
self::API_Register();
return true;
}
function API_Register()
{
apiql::register("!SYSTEM/!INSTALL/!PACKAGE[json]", "SystemServices::InstallPackage");
apiql::register("!SYSTEM/!REMOVE/!PACKAGE[json]", "SystemServices::RemovePackage");
apiql::register("!SYSTEM/!CLEAN/!PACKAGES", "SystemServices::CleanPackages");
apiql::register("!SYSTEM/!GET/!INFO", "SystemServices::GetSystemInfo");
}
function GetSystemInfo()
{
$state['linfo'] = new \Linfo\Linfo;
$state['linfo_parser'] = $state['linfo']->getParser();
$OSArray = parse_ini_file("/etc/os-release");
$OverwatchVer = constant("OVERWATCH_VERSION");
$KernelVer = $state['linfo_parser']->getKernel();
$KernelArch = $state['linfo_parser']->getCPUArchitecture();
$MemArray = $state['linfo_parser']->getRam();
$CPU = $state['linfo_parser']->getCPU();
$Sensors = $state['linfo_parser']->getTemps();
$Devices = $state['linfo_parser']->getDevs();
$Network = $state['linfo_parser']->getNet();
$Battery = $state['linfo_parser']->getBattery();
$Model = $state['linfo_parser']->getModel();
$SysInfo = array('OS'=>$OSArray,
'DaemonVersion'=>$OverwatchVer,
'Kernel'=>$KernelVer,
'CPUArch'=>$KernelArch,
'RAM'=>$MemArray,
'CPU'=>$CPU,
'Sensors'=>$Sensors,
'Devices'=>$Devices,
'Network'=>$Network,
'Battery'=>$Battery,
'Model'=>$Model);
$ResponseArray = array("response_type"=>"success",
"response_content"=>$SysInfo,
"response_code"=>200);
return json_encode($ResponseArray);
}
function CleanPackages()
{
$process = new React\ChildProcess\Process('exec apt-get clean');
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if($exitCode > 0)
{
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Package cache cleanup failed.",
"apt_exit_code"=>$exitCode,
"error_code"=>"APT_500C");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Package cache cleanup succeeded.",
"apt_exit_code"=>$exitCode,
"response_code"=>"APT_200C");
}
foreach($clients as $client)
{
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$process->start($loop);
}
function InstallPackage($sql)
{
global $loop;
$Package = $sql['PACKAGE']['packages'];
#$Reinstall = $sql['REINSTALL'];
if(is_array($Package))
{
foreach($Package as $value)
{
if(empty($PackageLine))
{
$PackageLine = $value;
} else {
$PackageLine .= " ".$value;
}
}
} else {
$PackageLine = $Package;
}
$process = new React\ChildProcess\Process('exec apt-get -y install '.$PackageLine);
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if($exitCode > 0)
{
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Package installation failed.",
"apt_exit_code"=>$exitCode,
"error_code"=>"APT_500I");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Package installation succeeded.",
"apt_exit_code"=>$exitCode,
"response_code"=>"APT_200I");
}
foreach($clients as $client)
{
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$process->start($loop);
}
function RemovePackage($sql)
{
global $loop;
$Package = $sql['PACKAGE']['packages'];
#$Reinstall = $sql['REINSTALL'];
if(is_array($Package))
{
foreach($Package as $value)
{
if(empty($PackageLine))
{
$PackageLine = $value;
} else {
$PackageLine .= " ".$value;
}
}
} else {
$PackageLine = $Package;
}
$process = new React\ChildProcess\Process('exec apt-get -y remove '.$PackageLine);
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if($exitCode > 0)
{
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Package removal failed.",
"apt_exit_code"=>$exitCode,
"error_code"=>"APT_500R");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Package removal succeeded.",
"apt_exit_code"=>$exitCode,
"response_code"=>"APT_200R");
}
foreach($clients as $client)
{
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$process->start($loop);
}
}
/**
*
* @return unknown
*/
function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
/**
*
* @return unknown
*/
function module_loaded() {
self::API_Register();
return true;
}
/**
*
*/
function API_Register() {
apiql::register("!SYSTEM/!INSTALL/!PACKAGE[json]", "SystemServices::InstallPackage");
apiql::register("!SYSTEM/!REMOVE/!PACKAGE[json]", "SystemServices::RemovePackage");
apiql::register("!SYSTEM/!CLEAN/!PACKAGES[json]", "SystemServices::CleanPackages");
apiql::register("!SYSTEM/!GET/!INFO[json]", "SystemServices::GetSystemInfo");
apiql::register("!SYSTEM/!TEST/!S2S[json]", "SystemServices::TestS2SEndpoint");
}
function TestS2SEndpoint($sql)
{
return "TEST!";
}
/**
*
* @return unknown
*/
function GetSystemInfo() {
$state['linfo'] = new \Linfo\Linfo;
$state['linfo_parser'] = $state['linfo']->getParser();
$OSArray = parse_ini_file("/etc/os-release");
$OverwatchVer = constant("OVERWATCH_VERSION");
$KernelVer = $state['linfo_parser']->getKernel();
$KernelArch = $state['linfo_parser']->getCPUArchitecture();
$MemArray = $state['linfo_parser']->getRam();
$CPU = $state['linfo_parser']->getCPU();
$Sensors = $state['linfo_parser']->getTemps();
$Devices = $state['linfo_parser']->getDevs();
$Network = $state['linfo_parser']->getNet();
$Battery = $state['linfo_parser']->getBattery();
$Model = $state['linfo_parser']->getModel();
$SysInfo = array('OS'=>$OSArray,
'DaemonVersion'=>$OverwatchVer,
'Kernel'=>$KernelVer,
'CPUArch'=>$KernelArch,
'RAM'=>$MemArray,
'CPU'=>$CPU,
'Sensors'=>$Sensors,
'Devices'=>$Devices,
'Network'=>$Network,
'Battery'=>$Battery,
'Model'=>$Model);
$ResponseArray = array("response_type"=>"success",
"response_content"=>$SysInfo,
"response_code"=>200);
return json_encode($ResponseArray);
}
function CleanPackages() {
$process = new React\ChildProcess\Process('exec apt-get clean');
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if ($exitCode > 0) {
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Package cache cleanup failed.",
"apt_exit_code"=>$exitCode,
"error_code"=>"APT_500C");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Package cache cleanup succeeded.",
"apt_exit_code"=>$exitCode,
"response_code"=>"APT_200C");
}
foreach ($clients as $client) {
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$process->start($loop);
}
function InstallPackage($sql) {
global $loop;
$Package = $sql['PACKAGE']['packages'];
//$Reinstall = $sql['REINSTALL'];
if (is_array($Package)) {
foreach ($Package as $value)
{
if(empty($PackageLine))
{
$PackageLine = $value;
} else {
$PackageLine .= " ".$value;
}
}
} else {
$PackageLine = $Package;
}
$process = new React\ChildProcess\Process('exec apt-get -y install '.$PackageLine);
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if($exitCode > 0)
{
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Package installation failed.",
"apt_exit_code"=>$exitCode,
"error_code"=>"APT_500I");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Package installation succeeded.",
"apt_exit_code"=>$exitCode,
"response_code"=>"APT_200I");
}
foreach($clients as $client)
{
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$process->start($loop);
}
function RemovePackage($sql)
{
global $loop;
$Package = $sql['PACKAGE']['packages'];
//$Reinstall = $sql['REINSTALL'];
if(is_array($Package))
{
foreach($Package as $value)
{
if(empty($PackageLine))
{
$PackageLine = $value;
} else {
$PackageLine .= " ".$value;
}
}
} else {
$PackageLine = $Package;
}
$process = new React\ChildProcess\Process('exec apt-get -y remove '.$PackageLine);
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if($exitCode > 0)
{
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Package removal failed.",
"apt_exit_code"=>$exitCode,
"error_code"=>"APT_500R");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Package removal succeeded.",
"apt_exit_code"=>$exitCode,
"response_code"=>"APT_200R");
}
foreach($clients as $client)
{
$client->write(json_encode($ResponseArray)."\r\n");
}
return json_encode($ResponseArray);
});
$process->start($loop);
}
}

View File

@@ -0,0 +1 @@
a:2:{s:7:"setting";s:32:"72412490f922618a52e2b5e7974ee4e7";s:5:"files";a:1:{s:27:"WorkstationServices.API.php";s:32:"5f1f15ed8adee57aaf636477b391e164";}}

View File

@@ -1,121 +1,306 @@
<?php
include("IniFile.class.php");
class WorkstationServices{
function loadInst()
{
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
function API_Register()
{
apiql::register("!DESKTOP/!REMOVE/!SHORTCUT[json]", "WorkstationServices::RemoveDesktopFile");
apiql::register("!DESKTOP/!CREATE/!SHORTCUT[json]", "WorkstationServices::CreateDesktopFile");
apiql::register("!DESKTOP/!SET/!ICON[json]","WorkstationServices::SetLauncherIcon");
apiql::register("!STORAGE/!GET/!FREE/!SPACE[json]", "WorkstationServices::GetFreeSpace");
apiql::register("!STORAGE/!GET/!TOTAL/!SPACE[json]", "WorkstationServices::GetStorageCapacity");
apiql::register("!UPLOAD/!LOCAL/!RESOURCE[json]","WorkstationServices::UploadResource");
}
function SetLauncherIcon($sql)
{
$DesktopArray = $sql['ICON'];
if(!file_exists("/home/phablet"))
{
$ErrorArray = array("response_type"=>"error", "error_msg"=>"This device is not an Ubuntu Touch workstation.", "error_code"=>505);
return json_encode($ErrorArray);
}
if(!file_exists("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop")) {
$ErrorArray = array("response_type"=>"error","error_msg"=>"Desktop File does not Exist.", "error_code"=>404);
return json_encode($ErrorArray);
}
$desktop = IniFile::LoadFromFile("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
$desktop->SetKey("Desktop Entry","Icon",$DesktopArray['icon']);
$desktop->Save();
}
function UploadResource($sql)
{
$FileArray = $sql['RESOURCE'];
$fileMD5 = $FileArray['MD5'];
$fileContents = base64_decode($FileArray['contents']);
$destination = $FileArray['dest'];
file_put_contents($destination, $fileContents);
$MD5 = md5_file($destination);
if($MD5 != $fileMD5)
{
$ErrorArray = array("response_type"=>"error",
"error_msg"=>"MD5 Checksums do not match. File corrupt.",
"error_code"=>500);
return json_encode($ErrorArray);
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"File uploaded successfully to ".$destination,
"response_code"=>200);
return json_encode($ResponseArray);
}
}
function module_loaded()
{
self::API_Register();
return true;
}
function GetFreeSpace($sql)
{
$resultArray = array("response_type"=>"success",
"response_msg"=>disk_free_space($sql['SPACE']['volume']),
"response_code"=>200);
return json_encode($resultArray);
}
function GetStorageCapacity($sql)
{
$resultArray = array("response_type"=>"success",
"response_msg"=>disk_total_space($sql['SPACE']['volume']),
"response_code"=>200);
return json_encode($resultArray);
}
function CreateDesktopFile($sql)
{
# Check for the existance of the requested .desktop file.
$DesktopArray = $sql['SHORTCUT'];
if(!file_exists("/usr/share/applications/".$DesktopArray['name'].".desktop")) {
$ErrorArray = array("response_type"=>"error","error_msg"=>"Desktop File does not Exist.", "error_code"=>404);
return json_encode($ErrorArray);
}
if(!file_exists("/home/phablet"))
{
$ErrorArray = array("response_type"=>"error", "error_msg"=>"This device is not an Ubuntu Touch workstation.", "error_code"=>505);
return json_encode($ErrorArray);
}
copy("/usr/share/applications/".$DesktopArray['name'].".desktop", "/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
$desktop = IniFile::LoadFromFile("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
$ExecLine = $desktop->GetKey("Desktop Entry","Exec");
$ExecLine = "/home/phablet/bin/wm-wrapper " . $ExecLine;
$desktop->SetKey("Desktop Entry", "Exec", $ExecLine);
$desktop->SetKey("Desktop Entry", "X-Ubuntu-Touch", "true");
$desktop->SetKey("Desktop Entry", "X-Ubuntu-XMir-Enable", "true");
$desktop->Save();
$ResultArray = array("response_type"=>"success", "response_msg"=>"Desktop file created successfully.", "response_code"=>200);
}
function RemoveDesktopFile($sql)
{
$DesktopArray = $sql['SHORTCUT'];
unlink("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
}
/**
* WorkstationServices.API.php
*
* @package default
*/
include "IniFile.class.php";
class WorkstationServices {
/**
*
* @return unknown
*/
function loadInst() {
static $inst = null;
if ($inst === null) {
$inst = new self;
}
return $inst;
}
/**
*
*/
function API_Register() {
apiql::register("!STORAGE/!GET/!FREE/!SPACE[json]", "WorkstationServices::GetFreeSpace");
apiql::register("!STORAGE/!GET/!TOTAL/!SPACE[json]", "WorkstationServices::GetStorageCapacity");
apiql::register("!DESKTOP/!CHANGE/!WALLPAPER[json]", "WorkstationServices::SetWallpaper");
apiql::register("!WORKSTATION/!INSTALL/!PACKAGE[json]", "WorkstationServices::Libertine_InstallPackage");
apiql::register("!WORKSTATION/!REMOVE/!PACKAGE[json]", "WorkstationServices::Libertine_RemovePackage");
apiql::register("!WORKSTATION/!CREATE/!CONTAINER[json]", "WorkstationService::Libertine_CreateContainer");
apiql::register("!WORKSTATION/!DELETE/!CONTAINER[json]", "WorkstationService::Libertine_DeleteContainer");
apiql::register("!WORKSTATION/!CONF-APT/!REPOSITORY[json]", "WorkstationService::Libertine_ConfAptRepository");
apiql::register("!WORKSTATION/!EXEC/!CONTAINER[json]", "WorkstationSerivces::Libertine_ExecContainer");
apiql::register("!WORKSTATION/!GET/!CONTAINER/!STATUS[json]", "WorkstationServices::Libertine_GetContainers");
}
function Libretine_InstallPackage($sql) {}
function Libertine_RemovePackage($sql) {}
function Libertine_CreateContainer($sql) {
if(isset($sql['CONTAINER']['container-settings']['id'])) $ContainerID = 'applications';
if(isset($sql['CONTAINER']['container-settings']['name'])) $ContainerName = 'Applications';
}
function Libertine_ConfAptRepository($sql)
{
if($sql['REPOSITORY']['operation'] == "add") $process = new React\ChildProcess\Process('exec sudo -u phablet libertine-container-manager configure -a '.$sql['REPOSITORY']['repository']);
if($sql['REPOSITORY']['operation'] == "del" OR $sql['REPOSITORY']['operation'] == "delete") $process = new React\ChildProcess\Process('exec sudo -u phablet libertine-container-manager configure -d '.$sql['REPOSITORY']['repository']);
if(!$process)
{
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Incorrect operation specified. Accepted options: add, del, delete",
"response_code"=>"LCM_500");
return json_encode($ResponseArray);
} else {
$process->on('exit', function($exitCode, $termSignal) {
global $clients;
if($exitCode > 0) {
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Repository management request failed.",
"lcm_exit_code"=>$exitCode,
"response_code"=>"LCM_500");
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"Repository management request succeeded.",
"lcm_exit_code"=>$exitCode,
"response_code"=>"LCM_200");
}
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"LCM spawned to process management request.",
"response_code"=>"LCM_200");
return json_encode($ResponseArray);
}
}
function Libertine_GetContainers($sql)
{
if(file_exists("/home/phablet/.local/share/libertine/ContainersConfig.json"))
{
$ContainersConfig = file_get_contents("/home/phablet/.local/share/libertine/ContainersConfig.json");
$Containers = json_decode($ContainerConfig);
} else {
$ResponseArray = array("response_type"=>"error",
"response_msg"=>"Container Configuration wsa not foudn.",
"error_code"=>"LCM_500NC");
return json_encode($ResponseArray);
}
if(isset($sql['STATUS']['container-id']))
{
foreach ($Containers->containerList as $Container)
{
if($Container->id == $sql['STATUS']['container-id'])
{
$ContainerArray = array('id'=>$Container->id,
'name'=>$Contianer->name,
'distribution'=>$Container->distro,
'status'=>$Container->installStatus,
'applications'=>$Container->installedApps);
} else { continue; }
}
$ResponseArray = array("resposne_type"=>"success",
"response_data"=>$ContainerArray,
"response_code"=>"LCM_200S");
} else {
foreach ($Containers->containerLsit as $Container)
{
if($Container->id == $Containers->defaultContainer)
{
$ContainerArray = array('id'=>$Container->id,
'name'=>$Contianer->name,
'distribution'=>$Container->distro,
'status'=>$Container->installStatus,
'applications'=>$Container->installedApps);
} else { continue; }
}
$ResponseArray = array("resposne_type"=>"success",
"response_data"=>$ContainerArray,
"response_code"=>"LCM_200S");
}
return json_encode($ResponseArray);
}
function Libertine_ExecContainer($sql)
{
global $loop;
$process = new React\ChildProcess\Process('exec libertine-container-manager exec -c "'.$sql['CONTAINER']['command']);
$programRequestCode = sha1($sql['CONTAINER']['command']);
$ResponseArray = array();
$ResponseArray['response_process_marker'] = $programRequestCode;
$process->on('exit', function($exitCode, $termSignal) use ($ResponseArray) {
global $clients;
if($exitCode == 0)
{
$ResponseArray['response_type'] = "success";
$ResponseArray['response_code'] = "LCM_200E";
} else {
$ResponseArray['response_type'] = "failed";
$ResponseArray['response_code'] = "LCM_500E";
}
$ResponseArray['libertine_exit_code'] = $exitCode;
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$process->stdout->on('data', function($output) use ($ResponseArray) {
global $clients;
$ResponseArray['response_type'] = "status";
$ResponseArray['response_msg'] = $output;
$ResponseArray['response_code'] = "LCM_STDO";
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
$process->stderr->on('data', function($output) use ($ResponseArray) {
global $clients;
$ResponseArray['response_type'] = "status";
$ResponseArray['response_msg'] = $output;
$ResponseArray['response_code'] = "LCM_STDE";
foreach ($clients as $client)
{
$client->write(json_encode($ResponseArray));
}
});
}
/**
*
* @param unknown $sql
*/
function SetWallpaper($sql) {
}
/**
*
* @param unknown $sql
* @return unknown
*/
function SetLauncherIcon($sql) {
$DesktopArray = $sql['ICON'];
if (!file_exists("/home/phablet")) {
$ErrorArray = array("response_type"=>"error", "error_msg"=>"This device is not an Ubuntu Touch workstation.", "error_code"=>505);
return json_encode($ErrorArray);
}
if (!file_exists("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop")) {
$ErrorArray = array("response_type"=>"error", "error_msg"=>"Desktop File does not Exist.", "error_code"=>404);
return json_encode($ErrorArray);
}
$desktop = IniFile::LoadFromFile("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
$desktop->SetKey("Desktop Entry", "Icon",$DesktopArray['icon']);
$desktop->Save();
}
function UploadResource($sql) {
$FileArray = $sql['RESOURCE'];
$fileMD5 = $FileArray['MD5'];
$fileContents = base64_decode($FileArray['contents']);
$destination = $FileArray['dest'];
// This code is ready for use, but inactive as ReactPHP does not have Filesystem I/O yet.
//$filesystem = \React\Filesystem\Filesystem::create($loop);
//$filesystem->file($destination)->open('cwt')->then(function ($stream) {
// $stream->end($fileContents);
//});
file_put_contents($destination, $fileContents);
$MD5 = md5_file($destination);
LogEcho("Uploaded MD5: ".$fileMD5.", Local MD5:".$MD5, "WorkstationServices");
if ($MD5 != $fileMD5) {
$ErrorArray = array("response_type"=>"error",
"error_msg"=>"MD5 Checksums do not match. File may be corrupt.",
"error_code"=>500);
return json_encode($ErrorArray);
} else {
$ResponseArray = array("response_type"=>"success",
"response_msg"=>"File uploaded successfully to ".$destination,
"response_code"=>200);
return json_encode($ResponseArray);
}
}
function module_loaded() {
self::API_Register();
return true;
}
function GetFreeSpace($sql) {
$resultArray = array("response_type"=>"success",
"response_msg"=>disk_free_space($sql['SPACE']['volume']),
"response_code"=>200);
return json_encode($resultArray);
}
function GetStorageCapacity($sql) {
$resultArray = array("response_type"=>"success",
"response_msg"=>disk_total_space($sql['SPACE']['volume']),
"response_code"=>200);
return json_encode($resultArray);
}
function CreateDesktopFile($sql) {
// Check for the existance of the requested .desktop file.
$DesktopArray = $sql['SHORTCUT'];
if (!file_exists("/usr/share/applications/".$DesktopArray['name'].".desktop")) {
$ErrorArray = array("response_type"=>"error","error_msg"=>"Desktop File does not Exist.", "error_code"=>404);
return json_encode($ErrorArray);
}
if (!file_exists("/home/phablet")) {
$ErrorArray = array("response_type"=>"error", "error_msg"=>"This device is not an Ubuntu Touch workstation.", "error_code"=>505);
return json_encode($ErrorArray);
}
copy("/usr/share/applications/".$DesktopArray['name'].".desktop", "/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
$desktop = IniFile::LoadFromFile("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
$ExecLine = $desktop->GetKey("Desktop Entry","Exec");
$ExecLine = "/home/phablet/bin/wm-wrapper " . $ExecLine;
$desktop->SetKey("Desktop Entry", "Exec", $ExecLine);
$desktop->SetKey("Desktop Entry", "X-Ubuntu-Touch", "true");
$desktop->SetKey("Desktop Entry", "X-Ubuntu-XMir-Enable", "true");
$desktop->Save();
$ResultArray = array("response_type"=>"success", "response_msg"=>"Desktop file created successfully.", "response_code"=>200);
}
function RemoveDesktopFile($sql) {
$DesktopArray = $sql['SHORTCUT'];
unlink("/home/phablet/.local/share/applications/".$DesktopArray['name'].".desktop");
}
}
?>
?>

1
vendor/bin/phar-builder vendored Symbolic link
View File

@@ -0,0 +1 @@
../macfja/phar-builder/bin/phar-builder

1
vendor/bin/phar-builder.php vendored Symbolic link
View File

@@ -0,0 +1 @@
../macfja/phar-builder/bin/phar-builder.php

1
vendor/bin/phar-composer vendored Symbolic link
View File

@@ -0,0 +1 @@
../clue/phar-composer/bin/phar-composer

1
vendor/clue/phar-composer/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/vendor

19
vendor/clue/phar-composer/.travis.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
language: php
php:
- 5.3
- 5.6
- 7
- hhvm
matrix:
allow_failures:
- php: 7
- php: hhvm
install:
- composer install --prefer-source --no-interaction
script:
- phpunit --coverage-text

133
vendor/clue/phar-composer/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,133 @@
# Changelog
## 1.0.0 (2015-11-15)
* First stable release, now following SemVer.
* Feature: Can now be installed as a `require-dev` Composer dependency and
supports running as `./vendor/bin/phar-composer`.
(#36 by @radford)
* Fix: Actually exclude `vendor/` directory. This prevents processing all
vendor files twice and reduces build time by 50%.
(#38 by @radford)
* Fix: Fix error reporting when processing invalid project paths.
(#56 by @staabm and @clue)
* Fix: Fix description of `phar-composer install` command.
(#47 by @staabm)
* Updated documentation, tests and project structure.
(#54, #57, #58 and #59 by @clue)
## 0.5.0 (2014-07-10)
* Feature: The `search` command is the new default if you do not pass any command
([#13](https://github.com/clue/phar-composer/pull/13)).
You can now use the following command to get started:
```bash
$ phar-composer
```
* Fix: Pass through STDERR output of child processes instead of aborting
([#33](https://github.com/clue/phar-composer/pull/33))
* Fix: Do not timeout when child process takes longer than 60s.
This also helps users with slower internet connections.
([#31](https://github.com/clue/phar-composer/pull/31))
* Fix: Update broken dependencies
([#18](https://github.com/clue/phar-composer/pull/18))
* Fix: Fixed an undocumented config key
([#14](https://github.com/clue/phar-composer/pull/14), thanks @mikey179)
## 0.4.0 (2013-09-12)
* Feature: New `install` command will now both build the given package and then
install it into the system-wide bin directory `/usr/local/bin` (usually already
in your `$PATH`). This works for any package name or URL just like with the
`build` command, e.g.:
```bash
$ phar-composer install phpunit/phpunit
```
After some (lengthy) build output, you should now be able to run it by just issuing:
```bash
$ phpunit
```
* Feature: New `search` command provides an interactive command line search.
It will ask for the package name and issue an search via packagist.org's API and
present a list of matching packages. So if you don't know the exact package name,
you can now use the following command:
```bash
$ phar-composer search boris
```
* Feature: Both `build` and `install` commands now also optionally accept an
additional target directory to place the resulting phar into.
## 0.3.0 (2013-08-21)
* Feature: Resulting phar files can now be executed on systems without
ext-phar (#8). This vastly improves portability for legacy setups by including
a small startup script which self-extracts the current archive into a temporary
directory.
* Feature: Resulting phar files can now be executed without the phar file name
extension. E.g. this convenient feature now allows you to move your `~demo.phar`
to `/usr/bin/demo` for easy system wide installations.
* Fix: Resolving absolute paths to `vendor/autoload.php`
## 0.2.0 (2013-08-15)
* Feature: Packages can now also be cloned from any git URLs (#9), like this:
```bash
$ phar-composer build https://github.com/clue/phar-composer.git
```
The above will clone the repository and check out the default branch.
You can also specify either a tag or branch name very similar to how composer works:
```bash
$ phar-composer build https://github.com/clue/phar-composer.git:dev-master
```
## 0.1.0 (2013-08-12)
* Feature: Packages listed on packagist.org can now automatically be downloaded and installed
prior to generating phar (#7), like this:
```bash
$ phar-composer build clue/phar-composer
```
The above will download and install the latest stable tagged release (if any).
You can also specify a tagged version like this:
```bash
$ phar-composer build clue/phar-composer:0.1.*
```
Or you can specify to install the head of a given branch like this:
```bash
$ phar-composer build clue/phar-composer:dev-master
```
## 0.0.2 (2013-05-25)
* Feature: Bundle complete project directories
## 0.0.1 (2013-05-18)
* First tagged release

21
vendor/clue/phar-composer/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Christian Lück
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

240
vendor/clue/phar-composer/README.md vendored Normal file
View File

@@ -0,0 +1,240 @@
# clue/phar-composer [![Build Status](https://travis-ci.org/clue/phar-composer.png?branch=master)](https://travis-ci.org/clue/phar-composer)
Simple phar creation for any project managed via composer.
It takes your existing project's `composer.json` and builds an executable phar
for your project among with its bundled dependencies.
* Create a single executable phar archive, including its dependencies (i.e. vendor directory included)
* Automated build process
* Zero additional configuration
**Table of contents**
* [Usage](#usage)
* [phar-composer](#phar-composer)
* [phar-composer build](#phar-composer-build)
* [phar-composer install](#phar-composer-install)
* [phar-composer search](#phar-composer-search)
* [Install](#install)
* [As a phar (recommended)](#as-a-phar-recommended)
* [Updating phar](#updating-phar)
* [Installation using Composer](#installation-using-composer)
* [Updating dependency](#updating-dependency)
* [Manual Installation from Source](#manual-installation-from-source)
* [Updating manually](#updating-manually)
* [License](#license)
## Usage
Once clue/phar-composer is [installed](#install), you can use it via command line like this.
### phar-composer
This tool supports several sub-commands. To get you started, you can now use the following simple command:
```bash
$ phar-composer
```
This will actually execute the `search` command that allows you to interactively search and build any package
listed on packagist (see below description of the [search command](#phar-composer-search) for more details).
### phar-composer build
The `build` command can be used to build an executable single-file phar (php archive) for any project
managed by composer:
```bash
$ phar-composer build ~/path/to/your/project
```
The second argument can be pretty much everything that can be resolved to a valid project managed by composer.
Besides creating phar archives for locally installed packages like above, you can also easily download and
bundle packages from packagist.org like this:
```bash
$ phar-composer build d11wtq/boris
```
The above will download and install the latest stable tagged release (if any).
You can also specify a tagged version like this:
```bash
$ phar-composer build clue/phar-composer:~1.0
```
Or you can specify to install the head of a given branch like this:
```bash
$ phar-composer build clue/phar-composer:dev-master
```
A similar syntax can be used to clone a package from any git URL. This is particularly
useful for private packages or temporary git clones not otherwise listed on packagist:
```bash
$ phar-composer build https://github.com/composer/composer.git
```
The above will clone the repository and check out the default branch.
Again, you can specify either a tag or branch name very similar to how composer works:
```bash
$ phar-composer build https://github.com/composer/composer.git:dev-master
```
### phar-composer install
The `install` command will both build the given package and then
install it into the system-wide bin directory `/usr/local/bin` (usually already
in your `$PATH`). This works for any package name or URL just like with the
`build` command, e.g.:
```bash
$ phar-composer install phpunit/phpunit
```
After some (lengthy) build output, you should now be able to run it by just issuing:
```bash
$ phpunit
```
> In essence, the `install` command will basically just issue a `build` and then
`sudo mv $target.phar /usr/local/bin/$target`. It will ask you for your sudo password
when necessary, so it's not needed (and in fact not *recommended*) to run the whole
comamnd via `sudo`.
### phar-composer search
The `search` command provides an interactive command line search.
It will ask for the package name and issue an search via packagist.org's API and
present a list of matching packages. So if you don't know the exact package name,
you can use the following command:
```bash
$ phar-composer search boris
```
It uses an interactive command line menu to ask you for the matching package name,
its version and will then offer you to either `build` or `install` it.
## Install
You can grab a copy of clue/phar-composer in either of the following ways.
### As a phar (recommended)
You can simply download a pre-compiled and ready-to-use version as a Phar
to any directory.
Simply download the latest `phar-composer.phar` file from our
[releases page](https://github.com/clue/phar-composer/releases):
[Latest release](https://github.com/clue/phar-composer/releases/latest)
That's it already. You can now verify everything works by running this:
```bash
$ cd ~/Downloads
$ php phar-composer.phar --version
```
The above usage examples assume you've installed phar-composer system-wide to your $PATH (recommended),
so you have the following options:
1. Only use phar-composer locally and adjust the usage examples: So instead of
running `$ phar-composer --version`, you have to type `$ php phar-composer.phar --version`.
2. Use phar-composer's `install` command to install itself to your $PATH by running:
```bash
$ php phar-composer.phar install clue/phar-composer
```
3. Or you can manually make the `phar-composer.phar` executable and move it to your $PATH by running:
```bash
$ chmod 755 phar-composer.phar
$ sudo mv phar-composer.phar /usr/local/bin/phar-composer
```
If you have installed phar-composer system-wide, you can now verify everything works by running:
```bash
$ phar-composer --version
```
#### Updating phar
There's no separate `update` procedure, simply download the latest release again
and overwrite the existing phar.
Again, if you have already installed phar-composer system-wide, this is as easy as
running a self-installation like this:
```bash
$ phar-composer install clue/phar-composer
```
### Installation using Composer
Alternatively, you can also install phar-composer as part of your development dependencies.
You will likely want to use the `require-dev` section to exclude phar-composer in your production environment.
You can either modify your `composer.json` manually or run the following command to include the latest tagged release:
```bash
$ composer require --dev clue/phar-composer
```
Now you should be able to invoke the following command in your project root:
```bash
$ ./vendor/bin/phar-composer --version
```
> Note: You should only invoke and rely on the main phar-composer bin file.
Installing this project as a non-dev dependency in order to use its
source code as a library is *not supported*.
#### Updating dependency
Just run `composer update clue/phar-composer` to update to the latest release.
### Manual Installation from Source
This project requires PHP 5.3+ and Composer:
```bash
$ git clone https://github.com/clue/phar-composer.git
$ cd phar-composer
$ curl -s https://getcomposer.org/installer | php
$ php composer.phar install
```
You can now verify everything works by running phar-composer like this:
```bash
$ php bin/phar-composer --version
```
Optionally, you can now build the above mentioned `phar-composer.phar` yourself by issuing:
```bash
$ php bin/phar-composer build
```
Optionally, you can now follow the above instructions for a [system-wide installation](#as-a-phar-recommended).
#### Updating manually
```bash
$ git pull
$ php composer.phar install
```
## License
MIT

14
vendor/clue/phar-composer/bin/phar-composer vendored Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env php
<?php
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../../../autoload.php';
} else {
fwrite(STDERR, 'ERROR: Composer dependencies not properly set up! Run "composer install" or see README.md for more details' . PHP_EOL);
exit(1);
}
$app = new Clue\PharComposer\App();
$app->run();

29
vendor/clue/phar-composer/composer.json vendored Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "clue/phar-composer",
"description": "Simple phar creation for your projects managed via composer",
"keywords": ["executable phar", "build process", "bundle dependencies"],
"homepage": "https://github.com/clue/phar-composer",
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"email": "christian@lueck.tv"
}
],
"require": {
"herrera-io/box": "~1.5",
"symfony/console": "~2.1",
"symfony/finder": "~2.1",
"symfony/process": "~2.1",
"knplabs/packagist-api": "~1.0"
},
"autoload": {
"psr-0": {"Clue": "src/"}
},
"bin": ["bin/phar-composer"],
"extra": {
"phar": {
"bundler": "composer"
}
}
}

702
vendor/clue/phar-composer/composer.lock generated vendored Normal file
View File

@@ -0,0 +1,702 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "fa5d19b9023764157514f2b1b36431ff",
"packages": [
{
"name": "doctrine/inflector",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/inflector.git",
"reference": "54b8333d2a5682afdc690060c1cf384ba9f47f08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/inflector/zipball/54b8333d2a5682afdc690060c1cf384ba9f47f08",
"reference": "54b8333d2a5682afdc690060c1cf384ba9f47f08",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"type": "library",
"autoload": {
"psr-0": {
"Doctrine\\Common\\Inflector\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com",
"homepage": "http://www.jwage.com/",
"role": "Creator"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com",
"homepage": "http://www.instaclick.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com",
"homepage": "http://jmsyst.com",
"role": "Developer of wrapped JMSSerializerBundle"
}
],
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"inflection",
"pluarlize",
"singuarlize",
"string"
],
"time": "2013-01-10 21:49:15"
},
{
"name": "guzzle/common",
"version": "v3.8.0",
"target-dir": "Guzzle/Common",
"source": {
"type": "git",
"url": "https://github.com/guzzle/common.git",
"reference": "eb4e34cac1b18583f0ee74bf6a9dda96bd771a1e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/common/zipball/eb4e34cac1b18583f0ee74bf6a9dda96bd771a1e",
"reference": "eb4e34cac1b18583f0ee74bf6a9dda96bd771a1e",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/event-dispatcher": ">=2.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Common": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Common libraries used by Guzzle",
"homepage": "http://guzzlephp.org/",
"keywords": [
"collection",
"common",
"event",
"exception"
],
"time": "2013-12-05 23:39:20"
},
{
"name": "guzzle/http",
"version": "v3.8.0",
"target-dir": "Guzzle/Http",
"source": {
"type": "git",
"url": "https://github.com/guzzle/http.git",
"reference": "b497e6b6a5a85751ae0c6858d677f7c4857dd171"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/http/zipball/b497e6b6a5a85751ae0c6858d677f7c4857dd171",
"reference": "b497e6b6a5a85751ae0c6858d677f7c4857dd171",
"shasum": ""
},
"require": {
"guzzle/common": "self.version",
"guzzle/parser": "self.version",
"guzzle/stream": "self.version",
"php": ">=5.3.2"
},
"suggest": {
"ext-curl": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Http": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "HTTP libraries used by Guzzle",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"client",
"curl",
"http",
"http client"
],
"time": "2013-12-04 22:21:25"
},
{
"name": "guzzle/parser",
"version": "v3.8.0",
"target-dir": "Guzzle/Parser",
"source": {
"type": "git",
"url": "https://github.com/guzzle/parser.git",
"reference": "77cae6425bc3466e1e47bdf6d2eebc15a092dbcc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/parser/zipball/77cae6425bc3466e1e47bdf6d2eebc15a092dbcc",
"reference": "77cae6425bc3466e1e47bdf6d2eebc15a092dbcc",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Parser": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Interchangeable parsers used by Guzzle",
"homepage": "http://guzzlephp.org/",
"keywords": [
"URI Template",
"cookie",
"http",
"message",
"url"
],
"time": "2013-10-24 00:04:09"
},
{
"name": "guzzle/stream",
"version": "v3.8.0",
"target-dir": "Guzzle/Stream",
"source": {
"type": "git",
"url": "https://github.com/guzzle/stream.git",
"reference": "a86111d9ac7db31d65a053c825869409fe8fc83f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/stream/zipball/a86111d9ac7db31d65a053c825869409fe8fc83f",
"reference": "a86111d9ac7db31d65a053c825869409fe8fc83f",
"shasum": ""
},
"require": {
"guzzle/common": "self.version",
"php": ">=5.3.2"
},
"suggest": {
"guzzle/http": "To convert Guzzle request objects to PHP streams"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7-dev"
}
},
"autoload": {
"psr-0": {
"Guzzle\\Stream": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle stream wrapper component",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"component",
"stream"
],
"time": "2013-07-30 22:07:23"
},
{
"name": "herrera-io/box",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/herrera-io/php-box.git",
"reference": "596278d729b45ba2dab3f53897d62ac51e0db909"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/herrera-io/php-box/zipball/596278d729b45ba2dab3f53897d62ac51e0db909",
"reference": "596278d729b45ba2dab3f53897d62ac51e0db909",
"shasum": ""
},
"require": {
"ext-phar": "*",
"phine/path": "~1.0",
"php": ">=5.3.3"
},
"require-dev": {
"herrera-io/annotations": "~1.0",
"herrera-io/phpunit-test-case": "1.*",
"mikey179/vfsstream": "1.1.0",
"phpseclib/phpseclib": "~0.3",
"phpunit/phpunit": "3.7.*"
},
"suggest": {
"herrera-io/annotations": "For compacting annotated docblocks.",
"phpseclib/phpseclib": "For verifying OpenSSL signed phars without the phar extension."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Herrera\\Box": "src/lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kevin Herrera",
"email": "kevin@herrera.io",
"homepage": "http://kevin.herrera.io"
}
],
"description": "A library for simplifying the PHAR build process.",
"homepage": "http://herrera-io.github.com/php-box",
"keywords": [
"phar"
],
"time": "2013-11-09 17:22:29"
},
{
"name": "knplabs/packagist-api",
"version": "v1.1",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/packagist-api.git",
"reference": "5d46003cfb1aca37efc84e8a0f1e8ba276a8245e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/packagist-api/zipball/5d46003cfb1aca37efc84e8a0f1e8ba276a8245e",
"reference": "5d46003cfb1aca37efc84e8a0f1e8ba276a8245e",
"shasum": ""
},
"require": {
"doctrine/inflector": "~1.0",
"guzzle/http": "~3.0",
"php": ">=5.3.2"
},
"require-dev": {
"phpspec/php-diff": "*@dev",
"phpspec/phpspec2": "1.0.*@dev"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-0": {
"Packagist\\Api\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "KnpLabs Team",
"homepage": "http://knplabs.com"
}
],
"description": "Packagist API client.",
"homepage": "http://knplabs.com",
"keywords": [
"api",
"composer",
"packagist"
],
"time": "2013-11-22 09:55:31"
},
{
"name": "phine/exception",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/phine/lib-exception.git",
"reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phine/lib-exception/zipball/150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
"reference": "150c6b6090b2ebc53c60e87cb20c7f1287b7b68a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"league/phpunit-coverage-listener": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Phine\\Exception": "src/lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kevin Herrera",
"email": "kevin@herrera.io",
"homepage": "http://kevin.herrera.io"
}
],
"description": "A PHP library for improving the use of exceptions.",
"homepage": "https://github.com/phine/lib-exception",
"keywords": [
"exception"
],
"time": "2013-08-27 17:43:25"
},
{
"name": "phine/path",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/phine/lib-path.git",
"reference": "cbe1a5eb6cf22958394db2469af9b773508abddd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phine/lib-path/zipball/cbe1a5eb6cf22958394db2469af9b773508abddd",
"reference": "cbe1a5eb6cf22958394db2469af9b773508abddd",
"shasum": ""
},
"require": {
"phine/exception": "~1.0",
"php": ">=5.3.3"
},
"require-dev": {
"league/phpunit-coverage-listener": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-0": {
"Phine\\Path": "src/lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kevin Herrera",
"email": "kevin@herrera.io",
"homepage": "http://kevin.herrera.io"
}
],
"description": "A PHP library for improving the use of file system paths.",
"homepage": "https://github.com/phine/lib-path",
"keywords": [
"file",
"path",
"system"
],
"time": "2013-10-15 22:58:04"
},
{
"name": "symfony/console",
"version": "v2.4.1",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "4c1ed2ff514bd85ee186eebb010ccbdeeab05af7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/4c1ed2ff514bd85ee186eebb010ccbdeeab05af7",
"reference": "4c1ed2ff514bd85ee186eebb010ccbdeeab05af7",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/event-dispatcher": "~2.1"
},
"suggest": {
"symfony/event-dispatcher": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2014-01-01 08:14:50"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.4.1",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
"reference": "e3ba42f6a70554ed05749e61b829550f6ac33601"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/e3ba42f6a70554ed05749e61b829550f6ac33601",
"reference": "e3ba42f6a70554ed05749e61b829550f6ac33601",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"symfony/dependency-injection": "~2.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
"time": "2013-12-28 08:12:03"
},
{
"name": "symfony/finder",
"version": "v2.4.1",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
"reference": "6904345cf2b3bbab1f6d6e4ce1724cb99df9f00a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Finder/zipball/6904345cf2b3bbab1f6d6e4ce1724cb99df9f00a",
"reference": "6904345cf2b3bbab1f6d6e4ce1724cb99df9f00a",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Finder\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
"time": "2014-01-01 08:14:50"
},
{
"name": "symfony/process",
"version": "v2.4.1",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
"reference": "58fdccb311e44f28866f976c2d7b3227e9f713db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Process/zipball/58fdccb311e44f28866f976c2d7b3227e9f713db",
"reference": "58fdccb311e44f28866f976c2d7b3227e9f713db",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Process\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com",
"time": "2014-01-05 02:10:50"
}
],
"packages-dev": [
],
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": [
],
"platform": [
],
"platform-dev": [
]
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="PharComposer Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,64 @@
<?php
namespace Clue\PharComposer;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Input\InputInterface;
class App extends BaseApplication
{
private $isDefault = false;
public function __construct()
{
parent::__construct('phar-composer', '@git_tag@');
$this->add(new Command\Build());
$this->add(new Command\Search());
$this->add(new Command\Install());
// GUI feature disabled for now, see #35
// $this->add(new Command\Gui());
}
/**
* Gets the name of the command based on input.
*
* @param InputInterface $input The input interface
*
* @return string The command name
*/
protected function getCommandName(InputInterface $input)
{
if ($input->getFirstArgument() === null && !$input->hasParameterOption(array('--help', '-h'))) {
$this->isDefault = true;
return $this->getDefaultCommandName();
}
return parent::getCommandName($input);
}
private function getDefaultCommandName()
{
$gui = $this->has('gui') ? $this->get('gui') : null;
if ($gui instanceof Command\Gui && $gui->hasZenity()) {
return 'gui';
}
return 'search';
}
/**
* Overridden so that the application doesn't expect the command
* name to be the first argument.
*/
public function getDefinition()
{
$inputDefinition = parent::getDefinition();
if ($this->isDefault) {
// clear out the normal first argument, which is the command name
$inputDefinition->setArguments();
}
return $inputDefinition;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Clue\PharComposer\Phar\PharComposer;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use Clue\PharComposer\Phar\Packager;
class Build extends Command
{
protected function configure()
{
$this->setName('build')
->setDescription('Build phar for the given composer project')
->addArgument('path', InputArgument::OPTIONAL, 'Path to project directory or composer.json', '.')
->addArgument('target', InputArgument::OPTIONAL, 'Path to write phar output to (defaults to project name)');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$packager = new Packager();
$packager->setOutput(function ($line) use ($output) {
$output->write($line);
});
$packager->coerceWritable();
$pharer = $packager->getPharer($input->getArgument('path'));
$target = $input->getArgument('target');
if ($target !== null) {
$pharer->setTarget($target);
}
$pharer->build();
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Clue\PharComposer\Phar\PharComposer;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use Packagist\Api\Client;
use Packagist\Api\Result\Result;
use Packagist\Api\Result\Package;
use Packagist\Api\Result\Package\Version;
use Clue\PharComposer\Phar\Packager;
use React\EventLoop\Factory;
use Clue\Zenity\React\Launcher;
use Clue\Zenity\React\Builder;
class Gui extends Command
{
protected function configure()
{
$this->setName('gui')
->setDescription('Interactive GUI (requires Zenity, likely only on Linux/etc.)');
}
public function hasZenity()
{
return $this->hasBin('zenity');
}
private function hasBin($bin)
{
foreach (explode(PATH_SEPARATOR, getenv('PATH')) as $path) {
$path = rtrim($path, '/') . '/' . $bin;
if (file_exists($path)) {
return true;
}
}
return false;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$loop = Factory::create();
$launcher = new Launcher($loop);
$builder = new Builder($launcher);
$packager = new Packager();
$packager->setOutput(function ($line) use ($builder) {
$builder->info(strip_tags($line))->waitReturn();
});
$packager->coerceWritable(0);
foreach (array('gksudo', 'kdesudo', 'cocoasudo', 'sudo') as $bin) {
if ($this->hasBin($bin)) {
$packager->setBinSudo($bin);
break;
}
}
$packager->setOutput($output);
$menu = $builder->listMenu(
array(
'search' => 'Search package online',
'local' => 'Select local package',
'url' => 'Build from git-Repository',
'about' => 'About clue/phar-composer'
),
'Action'
);
$menu->setTitle('clue/phar-composer');
$menu->setWindowIcon('info');
$menu->setCancelLabel('Quit');
$selection = $menu->waitReturn();
if ($selection === 'search') {
$pharer = $this->doSearch($builder, $packager);
} elseif ($selection === 'local') {
do {
$dir = $builder->directorySelection()->waitReturn();
if ($dir === false) {
return;
}
try {
$pharer = $packager->getPharer($dir);
break;
}
catch (\Exception $e) {
$builder->error('Could not initialize composer package:' . PHP_EOL . PHP_EOL . $e->getMessage())->waitReturn();
}
} while(true);
} elseif ($selection === 'url') {
$pharer = $this->doUrl($builder, $packager);
} else {
return;
}
if ($pharer === null) {
return;
}
$action = $builder->listMenu(
array(
'build' => 'Build project',
'install' => 'Install project system-wide'
),
'Action for "' . $pharer->getPackageRoot()->getName() .'"' /*,
'Quit' */
)->waitReturn();
if ($action === 'build') {
$this->doBuild($builder, $packager, $pharer);
} elseif ($action ==='install') {
$this->doInstall($builder, $packager, $pharer);
} else {
return;
}
$builder->info('Successfully built ' . $pharer->getTarget() . '!')->waitReturn();
}
protected function doSearch(Builder $builder, Packager $packager)
{
$oldname = null;
do {
$dialog = $builder->entry('Search (partial) project name', $oldname);
$dialog->setTitle('Search project name');
$name = $dialog->waitReturn();
if ($name === false) {
return;
}
$oldname = $name;
$pulsate = $builder->pulsate('Searching for "' . $name . '"...');
$pulsate->setNoCancel(true);
$pulsate->run();
$packagist = new Client();
$choices = array();
foreach ($packagist->search($name) as $package) {
/* @var $package Result */
$choices[$package->getName()] = array(
$package->getName(),
mb_strimwidth($package->getDescription(), 0, 80, '…', 'utf-8'),
$package->getDownloads()
);
}
$pulsate->close();
if (!$choices) {
$builder->warning('No package matching "' . $name .'" found!')->waitReturn();
$name = false;
continue;
}
$table = $builder->table($choices, array('Name', 'Description', 'Downloads'), 'Select matching package');
$table->setTitle('Select matching package');
$table->setCancelLabel('Back to Search');
$table->setWidth(1000);
$table->setHeight(600);
$name = $table->waitReturn();
} while (is_bool($name));
$pulsate = $builder->pulsate('Selected <info>' . $name . '</info>, listing versions...');
$pulsate->setNoCancel(true);
$pulsate->run();
$package = $packagist->get($name);
/* @var $package Package */
$choices = array();
foreach ($package->getVersions() as $version) {
/* @var $version Version */
$time = new \DateTime($version->getTime());
$time = $time->format('Y-m-d H:i:s');
$bin = $version->getBin();
if ($bin) {
$bin = '☑ ' . array_shift($bin);
} else {
$bin = '☐ no executable bin';
}
$choices[$version->getVersion()] = array(
$version->getVersion(),
$time,
$bin
);
}
$pulsate->close();
if (!$choices) {
$builder->warning('No versions for package "' . $name .'" found!')->waitReturn();
return;
}
$dialog = $builder->table($choices, array('Version', 'Date', 'Binary'), 'Select available version');
$dialog->setWidth(800);
$dialog->setHeight(300);
$version = $dialog->waitReturn();
if (is_bool($version)) {
return;
}
$pulsate = $builder->pulsate('Installing to temporary directory...')->run();
$pharer = $packager->getPharer($name, $version);
$pulsate->close();
return $pharer;
}
protected function doUrl(Builder $builder, Packager $packager)
{
do {
$url = $builder->entry('Git URL to clone')->waitReturn();
if ($url === false) {
return;
}
$pulsate = $builder->pulsate('Cloning and installing from git...')->run();
try {
$pharer = $packager->getPharer($url);
$pulsate->close();
return $pharer;
}
catch (\Exception $e) {
$pulsate->close();
$builder->error('Unable to clone repository:' . PHP_EOL . PHP_EOL . $e->getMessage())->waitReturn();
}
} while(true);
}
protected function doInstall(Builder $builder, Packager $packager, PharComposer $pharer)
{
$pulsate = $builder->pulsate('Installing...')->run();
$path = $packager->getSystemBin($pharer);
$packager->install($pharer, $path);
$pulsate->close();
}
protected function doBuild(Builder $builder, Packager $packager, PharComposer $pharer)
{
$pulsate = $builder->pulsate('Waiting for target file name...')->run();
$save = $builder->fileSave('Location to write file to', $pharer->getTarget());
$target = $save->waitReturn();
if ($target === false) {
return;
}
$pulsate->close();
$pulsate = $builder->pulsate('Building target file...')->run();
$pharer->setTarget($target);
$pharer->build();
$pulsate->close();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Command\Command;
use Clue\PharComposer\Phar\Packager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\DialogHelper;
class Install extends Command
{
protected function configure()
{
$this->setName('install')
->setDescription('Install phar into system wide binary directory')
->addArgument('name', InputArgument::OPTIONAL, 'Project name or path', '.')
->addArgument('path', InputArgument::OPTIONAL, 'Path to install to', '/usr/local/bin');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$packager = new Packager();
$packager->setOutput($output);
$packager->coerceWritable();
$pharer = $packager->getPharer($input->getArgument('name'));
$path = $packager->getSystemBin($pharer, $input->getArgument('path'));
if (is_file($path)) {
$dialog = $this->getHelperSet()->get('dialog');
/* @var $dialog DialogHelper */
if (!$dialog->askConfirmation($output, 'Overwrite existing file <info>' . $path . '</info>? [y] > ')) {
$output->writeln('Aborting');
return;
}
}
$packager->install($pharer, $path);
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Clue\PharComposer\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Clue\PharComposer\Phar\PharComposer;
use InvalidArgumentException;
use UnexpectedValueException;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use Packagist\Api\Client;
use Packagist\Api\Result\Result;
use Packagist\Api\Result\Package;
use Packagist\Api\Result\Package\Version;
use Clue\PharComposer\Phar\Packager;
class Search extends Command
{
protected function configure()
{
$this->setName('search')
->setDescription('Interactive search for project name')
->addArgument('name', InputArgument::OPTIONAL, 'Project name or path', null);
}
protected function select(OutputInterface $output, $label, array $choices, $abortable = null)
{
$dialog = $this->getHelperSet()->get('dialog');
/* @var $dialog DialogHelper */
if (!$choices) {
$output->writeln('<error>No matching packages found</error>');
return;
}
// TODO: skip dialog, if exact match
if ($abortable === true) {
$abortable = '<hl>Abort</hl>';
} elseif ($abortable === false) {
$abortable = null;
}
$select = array_merge(array(0 => $abortable), array_values($choices));
if ($abortable === null) {
unset($select[0]);
}
$index = $dialog->select($output, $label, $select);
if ($index == 0) {
return null;
}
$indices = array_keys($choices);
return $indices[$index - 1];
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$packager = new Packager();
$packager->setOutput($output);
$packager->coerceWritable();
$dialog = $this->getHelperSet()->get('dialog');
/* @var $dialog DialogHelper */
$name = $input->getArgument('name');
do {
if ($name === null) {
// ask for input
$name = $dialog->ask($output, 'Enter (partial) project name > ');
} else {
$output->writeln('Searching for <info>' . $name . '</info>...');
}
$packagist = new Client();
$choices = array();
foreach ($packagist->search($name) as $package) {
/* @var $package Result */
$label = str_pad($package->getName(), 39) . ' ';
$label = str_replace($name, '<info>' . $name . '</info>', $label);
$label .= $package->getDescription();
$label .= ' (⤓' . $package->getDownloads() . ')';
$choices[$package->getName()] = $label;
}
$name = $this->select($output, 'Select matching package', $choices, 'Start new search');
} while ($name === null);
$output->writeln('Selected <info>' . $name . '</info>, listing versions...');
$package = $packagist->get($name);
/* @var $package Package */
$choices = array();
foreach ($package->getVersions() as $version) {
/* @var $version Version */
$label = $version->getVersion();
$bin = $version->getBin();
if ($bin === null) {
$label .= ' (<error>no executable bin</error>)';
} else {
$label .= ' (☑ executable bin)';
}
$choices[$version->getVersion()] = $label;
}
$version = $this->select($output, 'Select available version', $choices);
$action = $this->select(
$output,
'Action',
array(
'build' => 'Build project',
'install' => 'Install project system-wide'
),
'Quit'
);
if ($action === null) {
return;
}
$pharer = $packager->getPharer($name, $version);
if ($action === 'install') {
$path = $packager->getSystemBin($pharer);
$packager->install($pharer, $path);
} else {
$pharer->build();
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Clue\PharComposer;
/**
* Interface for logging outout.
*
* TODO: should be used in the Command classes as well
*/
class Logger
{
private $output = true;
/**
* set output function to use to output log messages
*
* @param callable|boolean $output callable that receives a single $line argument or boolean echo
*
* TODO: think about whether this should be a constructor instead
*/
public function setOutput($output)
{
$this->output = $output;
}
public function log($message)
{
$this->output($message . PHP_EOL);
}
private function output($message)
{
if ($this->output === true) {
echo $message;
} elseif ($this->output !== false) {
call_user_func($this->output, $message);
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Clue\PharComposer\Package;
/**
* Provides access to pathes defined in package autoload configuration.
*
* @see Package
*/
class Autoload
{
/**
* package autoload definition
*
* @type array
*/
private $autoload;
public function __construct(array $autoload)
{
$this->autoload = $autoload;
}
/**
* returns all pathes defined by PSR-0
*
* @return string[]
*/
public function getPsr0()
{
if (!isset($this->autoload['psr-0'])) {
return array();
}
$resources = array();
foreach ($this->autoload['psr-0'] as $namespace => $paths) {
foreach($this->correctSinglePath($paths) as $path) {
// TODO: this is not correct actually... should work for most repos nevertheless
// TODO: we have to take target-dir into account
$resources[] = $this->buildNamespacePath($namespace, $path);
}
}
return $resources;
}
/**
* corrects a single path into a list of pathes
*
* PSR autoloader may define a single or multiple paths.
*
* @param string|string[] $paths
* @return string[]
*/
private function correctSinglePath($paths)
{
if (is_array($paths)) {
return $paths;
}
return array($paths);
}
/**
* builds namespace path from given namespace and given path
*
* @param string $namespace
* @param string $path
* @return string
*/
private function buildNamespacePath($namespace, $path)
{
if ($namespace === '') {
return $path;
}
$namespace = str_replace('\\', '/', $namespace);
if ($path === '') {
// namespace in project root => namespace is path
return $namespace;
}
// namespace in sub-directory => add namespace to path
return rtrim($path, '/') . '/' . $namespace;
}
/**
* returns list of class file resources
*
* @return string[]
*/
public function getClassmap()
{
if (!isset($this->autoload['classmap'])) {
return array();
}
return $this->autoload['classmap'];
}
/**
* returns list of files defined in autoload
*
* @return string[]
*/
public function getFiles()
{
if (!isset($this->autoload['files'])) {
return array();
}
return $this->autoload['files'];
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Clue\PharComposer\Package;
use Symfony\Component\Finder\Finder;
use Clue\PharComposer\Logger;
/**
* A bundle represents all resources from a package that should be bundled into
* the target phar.
*/
class Bundle implements \IteratorAggregate
{
/**
* list of resources in this bundle
*
* @type array
*/
private $resources = array();
/**
* create bundle from given package
*
* @param Package $package
* @param Logger $logger
* @return Bundle
*/
public static function from(Package $package, Logger $logger)
{
return $package->getBundler($logger)->bundle();
}
/**
* add given file to bundle
*
* @param string $file
* @return Bundle
*/
public function addFile($file)
{
$this->resources[] = $file;
return $this;
}
/**
* add given directory to bundle
*
* @param Finder $dir
* @return Bundle
*/
public function addDir(Finder $dir)
{
$this->resources[] = $dir;
return $this;
}
/**
* checks if a bundle contains given resource
*
* @param string $resource
* @return bool
*/
public function contains($resource)
{
foreach ($this->resources as $containedResource) {
if (is_string($containedResource) && $containedResource == $resource) {
return true;
}
if ($containedResource instanceof Finder && $this->directoryContains($containedResource, $resource)) {
return true;
}
}
return false;
}
/**
* checks if given directory contains given resource
*
* @param Finder $dir
* @param string $resource
* @return bool
*/
private function directoryContains(Finder $dir, $resource)
{
foreach ($dir as $containedResource) {
/* @var $containedResource \SplFileInfo */
if (substr($containedResource->getRealPath(), 0, strlen($resource)) == $resource) {
return true;
}
}
return false;
}
/**
* returns list of resources
*
* @return \Traversable
*/
public function getIterator()
{
return new \ArrayIterator($this->resources);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Clue\PharComposer\Package\Bundler;
/**
* The Bundler is responsible for creating a Bundle containing all files which belong to a given package
*/
interface BundlerInterface
{
/**
* returns a bundle
*
* @return Bundle
*/
public function bundle();
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Clue\PharComposer\Package\Bundler;
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Logger;
use Clue\PharComposer\Package\Package;
use Symfony\Component\Finder\Finder;
/**
* The default Bundler instance which bundles the whole package directory
*/
class Complete implements BundlerInterface
{
/**
* package the bundler is for
*
* @type Package
*/
private $package;
/**
*
* @type Logger
*/
private $logger;
public function __construct(Package $package, Logger $logger)
{
$this->package = $package;
$this->logger = $logger;
}
/**
* returns a bundle
*
* @return Bundle
*/
public function bundle()
{
$bundle = new Bundle();
$iterator = Finder::create()
->files()
->ignoreVCS(true)
->filter($this->package->getBlacklistFilter())
->exclude($this->package->getPathVendorRelative())
->in($this->package->getDirectory());
$this->logger->log(' Adding whole project directory "' . $this->package->getDirectory() . '"');
return $bundle->addDir($iterator);
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Clue\PharComposer\Package\Bundler;
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Logger;
use Clue\PharComposer\Package\Package;
use Clue\PharComposer\Package\Autoload;
use Symfony\Component\Finder\Finder;
/**
* Only bundle files explicitly defined in this package's bin and autoload section
*/
class Explicit implements BundlerInterface
{
/**
* package the bundler is for
*
* @type Package
*/
private $package;
/**
*
* @type Logger
*/
private $logger;
public function __construct(Package $package, Logger $logger)
{
$this->package = $package;
$this->logger = $logger;
}
/**
* returns a bundle
*
* @return Bundle
*/
public function bundle()
{
$bundle = new Bundle();
$this->bundleBins($bundle);
$autoload = $this->package->getAutoload();
$this->bundlePsr0($bundle, $autoload);
$this->bundleClassmap($bundle, $autoload);
$this->bundleFiles($bundle, $autoload);
return $bundle;
}
private function bundleBins(Bundle $bundle)
{
foreach ($this->package->getBins() as $bin) {
$this->logger->log(' adding "' . $bin . '"');
$bundle->addFile($bin);
}
}
private function bundlePsr0(Bundle $bundle, Autoload $autoload)
{
foreach ($autoload->getPsr0() as $path) {
$this->addDir($bundle, $path);
}
}
private function bundleClassmap(Bundle $bundle, Autoload $autoload)
{
foreach($autoload->getClassmap() as $path) {
$this->addFile($bundle, $path);
}
}
private function bundleFiles(Bundle $bundle, Autoload $autoload)
{
foreach($autoload->getFiles() as $path) {
$this->addFile($bundle, $path);
}
}
private function addFile(Bundle $bundle, $file)
{
$this->logger->log(' adding "' . $file . '"');
$bundle->addFile($this->package->getAbsolutePath($file));
}
private function addDir(Bundle $bundle, $dir)
{
$dir = $this->package->getAbsolutePath(rtrim($dir, '/') . '/');
$this->logger->log(' adding "' . $dir . '"');
$bundle->addDir(Finder::create()
->files()
//->filter($this->getBlacklistFilter())
->ignoreVCS(true)
->in($dir));
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace Clue\PharComposer\Package;;
use Symfony\Component\Finder\SplFileInfo;
use Clue\PharComposer\Package\Bundler\Explicit as ExplicitBundler;
use Clue\PharComposer\Package\Bundler\Complete as CompleteBundler;
use Clue\PharComposer\Package\Autoload;
use Clue\PharComposer\Logger;
/**
* The package represents either the main/root package or one of the vendor packages.
*/
class Package
{
/**
* Instantiate package
*
* @param array $package package information (parsed composer.json)
* @param string $directory base directory of this package
*/
public function __construct(array $package, $directory)
{
$this->package = $package;
$this->directory = $directory;
}
/**
* get package name as defined in composer.json
*
* @return string
*/
public function getName()
{
return isset($this->package['name']) ? $this->package['name'] : 'unknown';
}
/**
* Get path to vendor directory (relative to package directory)
*
* @return string
*/
public function getPathVendorRelative()
{
$vendor = 'vendor';
if (isset($this->package['config']['vendor-dir'])) {
$vendor = $this->package['config']['vendor-dir'];
}
return $vendor;
}
/**
* Get absolute path to vendor directory
*
* @return string
*/
public function getPathVendor()
{
return $this->getAbsolutePath($this->getPathVendorRelative() . '/');
}
/**
* Get package directory (the directory containing its composer.json)
*
* @return string
*/
public function getDirectory()
{
return $this->directory;
}
/**
* Get Bundler instance to bundle this package
*
* @param Logger $logger
* @return BundlerInterface
*/
public function getBundler(Logger $logger)
{
$bundlerName = 'complete';
if (isset($this->package['extra']['phar']['bundler'])) {
$bundlerName = $this->package['extra']['phar']['bundler'];
}
if ($bundlerName === 'composer') {
return new ExplicitBundler($this, $logger);
} elseif ($bundlerName === 'complete') {
return new CompleteBundler($this, $logger);
} else {
$logger->log('Invalid bundler "' . $bundlerName . '" specified in package "' . $this->getName() . '", will fall back to "complete" bundler');
return new CompleteBundler($this, $logger);
}
}
/**
* Get Autoload instance containing all autoloading information
*
* Only used for ExplicitBundler at the moment.
*
* @return Autoload
*/
public function getAutoload()
{
return new Autoload(isset($this->package['autoload']) ? $this->package['autoload'] : array());
}
/**
* Get list of files defined as "bin" (absolute paths)
*
* @return string[]
*/
public function getBins()
{
if (!isset($this->package['bin'])) {
return array();
}
$bins = array();
foreach ($this->package['bin'] as $bin) {
$bins []= $this->getAbsolutePath($bin);
}
return $bins;
}
/**
* Get blacklisted files which are not to be included
*
* Hardcoded to exclude composer.phar and phar-composer.phar at the moment.
*
* @return string[]
*/
public function getBlacklist()
{
return array(
$this->getAbsolutePath('composer.phar'),
$this->getAbsolutePath('phar-composer.phar')
);
}
/**
* Gets a filter function to exclude blacklisted files
*
* Only used for CompleteBundler at the moment
*
* @return Closure
* @uses self::getBlacklist()
*/
public function getBlacklistFilter()
{
$blacklist = $this->getBlacklist();
return function (SplFileInfo $file) use ($blacklist) {
return in_array($file->getPathname(), $blacklist) ? false : null;
};
}
/**
* Get absolute path for the given package-relative path
*
* @param string $path
* @return string
*/
public function getAbsolutePath($path)
{
return $this->directory . ltrim($path, '/');
}
}

View File

@@ -0,0 +1,313 @@
<?php
namespace Clue\PharComposer\Phar;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ExecutableFinder;
use UnexpectedValueException;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Console\Output\OutputInterface;
class Packager
{
const PATH_BIN = '/usr/local/bin';
private $output;
private $binSudo = 'sudo';
public function __construct()
{
$this->setOutput(true);
}
private function log($message)
{
$fn = $this->output;
$fn($message . PHP_EOL);
}
public function setBinSudo($bin)
{
$this->binSudo = $bin;
}
public function setOutput($fn)
{
if ($fn instanceof OutputInterface) {
$fn = function ($line) use ($fn) {
$fn->write($line);
};
} elseif ($fn === true) {
$fn = function ($line) {
echo $line;
};
} elseif ($fn === false) {
$fn = function () { };
}
$this->output = $fn;
}
/**
* ensure writing phar files is enabled or respawn with PHP setting which allows writing
*
* @param int $wait
* @return void
* @uses assertWritable()
*/
public function coerceWritable($wait = 1)
{
try {
$this->assertWritable();
}
catch (UnexpectedValueException $e) {
if (!function_exists('pcntl_exec')) {
$this->log('<error>' . $e->getMessage() . '</error>');
return;
}
$this->log('<info>' . $e->getMessage() . ', trying to re-spawn with correct config</info>');
if ($wait) {
sleep($wait);
}
$args = array_merge(array('php', '-d phar.readonly=off'), $_SERVER['argv']);
if (pcntl_exec('/usr/bin/env', $args) === false) {
$this->log('<error>Unable to switch into new configuration</error>');
return;
}
}
}
/**
* ensure writing phar files is enabled or throw an exception
*
* @throws UnexpectedValueException
*/
public function assertWritable()
{
if (ini_get('phar.readonly') === '1') {
throw new UnexpectedValueException('Your configuration disabled writing phar files (phar.readonly = On), please update your configuration or run with "php -d phar.readonly=off ' . $_SERVER['argv'][0].'"');
}
}
public function getPharer($path, $version = null)
{
if ($version !== null) {
// TODO: should be the other way around
$path .= ':' . $version;
}
$step = 1;
$steps = 1;
if ($this->isPackageUrl($path)) {
$url = $path;
$version = null;
$steps = 3;
if (preg_match('/(.+)\:((?:dev\-|v\d)\S+)$/i', $url, $match)) {
$url = $match[1];
$version = $match[2];
if (substr($version, 0, 4) === 'dev-') {
$version = substr($version, 4);
}
}
$path = $this->getDirTemporary();
$finder = new ExecutableFinder();
$git = $finder->find('git', '/usr/bin/git');
$that = $this;
$this->displayMeasure(
'[' . $step++ . '/' . $steps.'] Cloning <info>' . $url . '</info> into temporary directory <info>' . $path . '</info>',
function() use ($that, $url, $path, $version, $git) {
$that->exec($git . ' clone ' . escapeshellarg($url) . ' ' . escapeshellarg($path));
if ($version !== null) {
$this->exec($git . ' checkout ' . escapeshellarg($version) . ' 2>&1', $path);
}
},
'Cloning base repository completed'
);
$pharcomposer = new PharComposer($path . '/composer.json');
$package = $pharcomposer->getPackageRoot()->getName();
if (is_file('composer.phar')) {
$command = $finder->find('php', '/usr/bin/php') . ' composer.phar';
} else {
$command = $finder->find('composer', '/usr/bin/composer');
}
$command .= ' install --no-dev --no-progress --no-scripts';
$this->displayMeasure(
'[' . $step++ . '/' . $steps.'] Installing dependencies for <info>' . $package . '</info> into <info>' . $path . '</info> (using <info>' . $command . '</info>)',
function () use ($that, $command, $path) {
try {
$that->exec($command, $path);
}
catch (UnexpectedValueException $e) {
throw new UnexpectedValueException('Installing dependencies via composer failed', 0, $e);
}
},
'Downloading dependencies completed'
);
} elseif ($this->isPackageName($path)) {
if (is_dir($path)) {
$this->log('<info>There\'s also a directory with the given name</info>');
}
$steps = 2;
$package = $path;
$path = $this->getDirTemporary();
$finder = new ExecutableFinder();
if (is_file('composer.phar')) {
$command = $finder->find('php', '/usr/bin/php') . ' composer.phar';
} else {
$command = $finder->find('composer', '/usr/bin/composer');
}
$command .= ' create-project ' . escapeshellarg($package) . ' ' . escapeshellarg($path) . ' --no-dev --no-progress --no-scripts';
$that = $this;
$this->displayMeasure(
'[' . $step++ . '/' . $steps.'] Installing <info>' . $package . '</info> to temporary directory <info>' . $path . '</info> (using <info>' . $command . '</info>)',
function () use ($that, $command) {
try {
$that->exec($command);
}
catch (UnexpectedValueException $e) {
throw new UnexpectedValueException('Installing package via composer failed', 0, $e);
}
},
'Downloading package completed'
);
}
if (is_dir($path)) {
$path = rtrim($path, '/') . '/composer.json';
}
if (!is_file($path)) {
throw new InvalidArgumentException('The given path "' . $path . '" is not a readable file');
}
$pharer = new PharComposer($path);
$pharer->setOutput($this->output);
$pharer->setStep($step);
$pathVendor = $pharer->getPathVendor();
if (!is_dir($pathVendor)) {
throw new RuntimeException('Project is not installed via composer. Run "composer install" manually');
}
return $pharer;
}
public function measure($fn)
{
$time = microtime(true);
$fn();
return max(microtime(true) - $time, 0);
}
public function displayMeasure($title, $fn, $success)
{
$this->log($title);
$time = $this->measure($fn);
$this->log('');
$this->log(' <info>OK</info> - ' . $success .' (after ' . round($time, 1) . 's)');
}
public function exec($cmd, $chdir = null)
{
$nl = true;
//
$output = $this->output;
$process = new Process($cmd, $chdir);
$process->setTimeout(null);
$process->start();
$code = $process->wait(function($type, $data) use ($output, &$nl) {
if ($nl === true) {
$data = "\n" . $data;
$nl = false;
}
if (substr($data, -1) === "\n") {
$nl = true;
$data = substr($data, 0, -1);
}
$data = str_replace("\n", "\n ", $data);
$output($data);
});
if ($nl) {
$this->log('');
}
if ($code !== 0) {
throw new UnexpectedValueException('Error status code: ' . $process->getExitCodeText() . ' (code ' . $code . ')');
}
}
public function install(PharComposer $pharer, $path)
{
$pharer->build();
$this->log('Move resulting phar to <info>' . $path . '</info>');
$this->exec($this->binSudo . ' -- mv -f ' . escapeshellarg($pharer->getTarget()) . ' ' . escapeshellarg($path));
$this->log('');
$this->log(' <info>OK</info> - Moved to <info>' . $path . '</info>');
}
public function getSystemBin(PharComposer $pharer, $path = null)
{
// no path given => place in system bin path
if ($path === null) {
$path = self::PATH_BIN;
}
// no slash => path is relative to system bin path
if (strpos($path, '/') === false) {
$path = self::PATH_BIN . '/' . $path;
}
// TODO: check path is in $PATH environment
// path is actually a directory => append package name
if (is_dir($path)) {
$path = rtrim($path, '/') . '/' . basename($pharer->getTarget(), '.phar');
}
return $path;
}
private function isPackageName($path)
{
return !!preg_match('/^[^\s\/]+\/[^\s\/]+(\:[^\s]+)?$/i', $path);
}
private function isPackageUrl($path)
{
return (strpos($path, '://') !== false && @parse_url($path) !== false);
}
private function getDirTemporary()
{
$path = sys_get_temp_dir() . '/phar-composer' . mt_rand(0,9);
while (is_dir($path)) {
$path .= mt_rand(0, 9);
}
return $path;
}
}

View File

@@ -0,0 +1,262 @@
<?php
namespace Clue\PharComposer\Phar;
use Symfony\Component\Finder\Finder;
use Herrera\Box\StubGenerator;
use UnexpectedValueException;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Finder\SplFileInfo;
use Clue\PharComposer\Logger;
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Package\Package;
/**
* The PharComposer is responsible for collecting options and then building the target phar
*/
class PharComposer
{
private $pathProject;
private $package;
private $main = null;
private $target = null;
private $logger;
private $step = '?';
public function __construct($path)
{
$path = realpath($path);
$this->package = $this->loadJson($path);
$this->pathProject = dirname($path) . '/';
$this->logger = new Logger();
}
/**
* set output function to use to output log messages
*
* @param callable|boolean $output callable that receives a single $line argument or boolean echo
*/
public function setOutput($output)
{
$this->logger->setOutput($output);
}
public function getTarget()
{
if ($this->target === null) {
if (isset($this->package['name'])) {
// skip vendor name from package name
$this->target = substr($this->package['name'], strpos($this->package['name'], '/') + 1);
} else {
$this->target = basename($this->pathProject);
}
$this->target .= '.phar';
}
return $this->target;
}
public function setTarget($target)
{
// path is actually a directory => append package name
if (is_dir($target)) {
$this->target = null;
$target = rtrim($target, '/') . '/' . $this->getTarget();
}
$this->target = $target;
return $this;
}
public function getMain()
{
if ($this->main === null) {
foreach ($this->getPackageRoot()->getBins() as $path) {
if (!file_exists($path)) {
throw new UnexpectedValueException('Bin file "' . $path . '" does not exist');
}
$this->main = $path;
break;
}
}
return $this->main;
}
public function setMain($main)
{
$this->main = $main;
return $this;
}
/**
* base project path. all files MUST BE relative to this location
*
* @return string
*/
public function getBase()
{
return $this->pathProject;
}
/**
* get absolute path to vendor directory
*
* @return string
*/
public function getPathVendor()
{
return $this->getPackageRoot()->getPathVendor();
}
/**
*
* @return Package
*/
public function getPackageRoot()
{
return new Package($this->package, $this->pathProject);
}
/**
*
* @return Package[]
*/
public function getPackagesDependencies()
{
$packages = array();
$pathVendor = $this->getPathVendor();
// load all installed packages (use installed.json which also includes version instead of composer.lock)
if (is_file($pathVendor . 'composer/installed.json')) {
// file does not exist if there's nothing to be installed
$installed = $this->loadJson($pathVendor . 'composer/installed.json');
foreach ($installed as $package) {
$dir = $package['name'] . '/';
if (isset($package['target-dir'])) {
$dir .= trim($package['target-dir'], '/') . '/';
}
$dir = $pathVendor . $dir;
$packages []= new Package($package, $dir);
}
}
return $packages;
}
public function build()
{
$this->log('[' . $this->step . '/' . $this->step.'] Creating phar <info>' . $this->getTarget() . '</info>');
$time = microtime(true);
$pathVendor = $this->getPathVendor();
if (!is_dir($pathVendor)) {
throw new RuntimeException('Directory "' . $pathVendor . '" not properly installed, did you run "composer install"?');
}
$target = $this->getTarget();
if (file_exists($target)) {
$this->log(' - Remove existing file <info>' . $target . '</info> (' . $this->getSize($target) . ')');
if(unlink($target) === false) {
throw new UnexpectedValueException('Unable to remove existing phar archive "'.$target.'"');
}
}
$targetPhar = TargetPhar::create($target, $this);
$this->log(' - Adding main package');
$targetPhar->addBundle(Bundle::from($this->getPackageRoot(), $this->logger));
$this->log(' - Adding composer base files');
// explicitly add composer autoloader
$targetPhar->addFile($pathVendor . 'autoload.php');
// TODO: check for vendor/bin !
// only add composer base directory (no sub-directories!)
$targetPhar->buildFromIterator(new \GlobIterator($pathVendor . 'composer/*.*', \FilesystemIterator::KEY_AS_FILENAME));
foreach ($this->getPackagesDependencies() as $package) {
$this->log(' - Adding dependency "' . $package->getName() . '" from "' . $this->getPathLocalToBase($package->getDirectory()) . '"');
$targetPhar->addBundle(Bundle::from($package, $this->logger));
}
$this->log(' - Setting main/stub');
$chmod = 0755;
$main = $this->getMain();
if ($main === null) {
$this->log(' WARNING: No main bin file defined! Resulting phar will NOT be executable');
} else {
$generator = StubGenerator::create()
->index($this->getPathLocalToBase($main))
->extract(true)
->banner("Bundled by phar-composer with the help of php-box.\n\n@link https://github.com/clue/phar-composer");
$lines = file($main, FILE_IGNORE_NEW_LINES);
if (substr($lines[0], 0, 2) === '#!') {
$this->log(' Using referenced shebang "'. $lines[0] . '"');
$generator->shebang($lines[0]);
// remove shebang from main file and add (overwrite)
unset($lines[0]);
$targetPhar->addFromString($this->getPathLocalToBase($main), implode("\n", $lines));
}
$targetPhar->setStub($generator->generate());
$chmod = octdec(substr(decoct(fileperms($main)),-4));
$this->log(' Using referenced chmod ' . sprintf('%04o', $chmod));
}
$targetPhar->finalize();
if ($chmod !== null) {
$this->log(' Applying chmod ' . sprintf('%04o', $chmod));
if (chmod($target, $chmod) === false) {
throw new UnexpectedValueException('Unable to chmod target file "' . $target .'"');
}
}
$time = max(microtime(true) - $time, 0);
$this->log('');
$this->log(' <info>OK</info> - Creating <info>' . $this->getTarget() .'</info> (' . $this->getSize($this->getTarget()) . ') completed after ' . round($time, 1) . 's');
}
private function getSize($path)
{
return round(filesize($path) / 1024, 1) . ' KiB';
}
public function getPathLocalToBase($path)
{
if (strpos($path, $this->pathProject) !== 0) {
throw new UnexpectedValueException('Path "' . $path . '" is not within base project path "' . $this->pathProject . '"');
}
return substr($path, strlen($this->pathProject));
}
public function log($message)
{
$this->logger->log($message);
}
public function setStep($step)
{
$this->step = $step;
}
private function loadJson($path)
{
$ret = json_decode(file_get_contents($path), true);
if ($ret === null) {
var_dump(json_last_error(), JSON_ERROR_SYNTAX);
throw new InvalidArgumentException('Unable to parse given path "' . $path . '"');
}
return $ret;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Clue\PharComposer\Phar;
use Herrera\Box\Box;
use Traversable;
use Clue\PharComposer\Package\Bundle;
/**
* Represents the target phar to be created.
*
* TODO: replace PharComposer with a new BasePath class
*/
class TargetPhar
{
/**
*
* @type PharComposer
*/
private $pharComposer;
/**
*
* @type Box
*/
private $box;
/**
* constructor
*
* @param Box $box
* @param PharComposer $pharComposer
*/
public function __construct(Box $box, PharComposer $pharComposer)
{
$this->box = $box;
$this->box->getPhar()->startBuffering();
$this->pharComposer = $pharComposer;
}
/**
* create new instance in target path
*
* @param string $target
* @param PharComposer $pharComposer
* @return TargetPhar
*/
public static function create($target, PharComposer $pharComposer)
{
return new self(Box::create($target), $pharComposer);
}
/**
* finalize writing of phar file
*/
public function finalize()
{
$this->box->getPhar()->stopBuffering();
}
/**
* adds given list of resources to phar
*
* @param Bundle $bundle
*/
public function addBundle(Bundle $bundle)
{
foreach ($bundle as $resource) {
if (is_string($resource)) {
$this->addFile($resource);
} else {
$this->buildFromIterator($resource);
}
}
}
/**
* Adds a file to the Phar, after compacting it and replacing its
* placeholders.
*
* @param string $file The file name.
*/
public function addFile($file)
{
$this->box->addFile($file, $this->pharComposer->getPathLocalToBase($file));
}
/**
* Similar to Phar::buildFromIterator(), except the files will be compacted
* and their placeholders replaced.
*
* @param Traversable $iterator The iterator.
*/
public function buildFromIterator(Traversable $iterator)
{
$this->box->buildFromIterator($iterator, $this->pharComposer->getBase());
}
/**
* Used to set the PHP loader or bootstrap stub of a Phar archive
*
* @param string $stub
*/
public function setStub($stub)
{
$this->box->getPhar()->setStub($stub);
}
public function addFromString($local, $contents)
{
$this->box->addFromString($local, $contents);
}
}

View File

@@ -0,0 +1,44 @@
<?php
use Clue\PharComposer\Logger as Logger;
class LoggerTest extends TestCase
{
/**
* instance to test
*
* @type Logger
*/
private $logger;
/**
* set up test environment
*/
public function setUp()
{
$this->logger = new Logger();
}
/**
* @test
*/
public function echosToStdOutByDefault()
{
ob_start();
$this->logger->log('some informational message');
$this->assertEquals('some informational message' . PHP_EOL,
ob_get_contents()
);
ob_end_clean();
}
/**
* @test
*/
public function callsGivenOutputFunctionWhenSet()
{
$that = $this;
$this->logger->setOutput(function($message) use($that) { $that->assertEquals('some informational message' . PHP_EOL, $message);});
$this->logger->log('some informational message');
}
}

View File

@@ -0,0 +1,86 @@
<?php
use Clue\PharComposer\Package\Autoload;
class AutoloadTest extends TestCase
{
private function createAutoload(array $autoload)
{
return new Autoload($autoload);
}
/**
* @test
*/
public function returnsEmptyPsr0ListIfNotDefined()
{
$this->assertEquals(array(),
$this->createAutoload(array())->getPsr0()
);
}
/**
* @test
*/
public function returnsAllPathesDefinedByPsr0WithSinglePath()
{
$path = realpath(__DIR__ . '/../src');
$this->assertEquals(array($path . '/Clue'),
$this->createAutoload(array('psr-0' => array('Clue' => $path)))
->getPsr0()
);
}
/**
* @test
*/
public function returnsAllPathesDefinedByPsr0WithSeveralPathes()
{
$path = realpath(__DIR__ . '/../src');
$this->assertEquals(array($path . '/Clue', $path . '/Clue'),
$this->createAutoload(array('psr-0' => array('Clue' => array($path, $path))))
->getPsr0()
);
}
/**
* @test
*/
public function returnsEmptyClassmapIfNotDefined()
{
$this->assertEquals(array(),
$this->createAutoload(array())->getClassmap()
);
}
/**
* @test
*/
public function returnsClassmapAsDefined()
{
$this->assertEquals(array('Example/SomeClass' => 'src/Example/SomeClass.php'),
$this->createAutoload(array('classmap' => array('Example/SomeClass' => 'src/Example/SomeClass.php')))
->getClassmap()
);
}
/**
* @test
*/
public function returnsEmptyFilelistIfNotDefined()
{
$this->assertEquals(array(),
$this->createAutoload(array())->getFiles()
);
}
/**
* @test
*/
public function returnsFilelistAsDefined()
{
$this->assertEquals(array('foo.php', 'bar.php'),
$this->createAutoload(array('files' => array('foo.php', 'bar.php')))->getFiles()
);
}
}

View File

@@ -0,0 +1,103 @@
<?php
use Clue\PharComposer\Package\Bundler\Explicit as ExplicitBundler;
use Clue\PharComposer\Package\Package;
class ExplicitBundlerTest extends TestCase
{
/**
* instance to test
*
* @type ExplicitBundler
*/
private $explicitBundler;
private $package;
private $path;
/**
* set up test environment
*/
public function setUp()
{
$this->path = realpath(__DIR__ . '/../../../');
$this->package = new Package(array('bin' => array('bin/example'),
'autoload' => array('files' => array('foo.php'),
'classmap' => array('src/Example/SomeClass.php'),
'psr-0' => array('Clue' => 'src')
),
),
$this->path . '/'
);
$this->explicitBundler = new ExplicitBundler($this->package, $this->createMock('Clue\PharComposer\Logger'));
}
private function createMock($class)
{
return $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
}
/**
* @test
*/
public function addsBinariesDefinedByPackage()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/bin/example'),
'Failed asserting that "bin/example" is contained in bundle'
);
}
/**
* @test
*/
public function addsFilesDefinedByAutoload()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/foo.php'),
'Failed asserting that "foo.php" is contained in bundle'
);
}
/**
* @test
*/
public function addsFilesDefinedByClassmap()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/src/Example/SomeClass.php'),
'Failed asserting that "src/Example/SomeClass.php" is contained in bundle'
);
}
/**
* @test
*/
public function addsAllPathesDefinedByPsr0WithSinglePath()
{
$this->assertTrue($this->explicitBundler->bundle()->contains($this->path . '/src/Clue/'),
'Failed asserting that ' . $this->path . '/src/Clue/ is contained in bundle'
);
}
/**
* @test
*/
public function addsAllPathesDefinedByPsr0WithSeveralPathes()
{
$this->package = new Package(array('autoload' => array('psr-0' => array('Clue' => array('src/',
'src/'
)
)
)
),
$this->path . '/'
);
$this->explicitBundler = new ExplicitBundler($this->package, $this->createMock('Clue\PharComposer\Logger'));
$bundle = $this->explicitBundler->bundle();
$this->assertTrue($bundle->contains($this->path . '/src'),
'Failed asserting that ' . $this->path . '/src' . ' is contained in bundle'
);
}
}

View File

@@ -0,0 +1,99 @@
<?php
use Clue\PharComposer\Package\Package;
use Clue\PharComposer\Package\Autoload;
class PackageTest extends TestCase
{
public function testConstructorDefaults()
{
$package = new Package(array(), 'dir/');
$this->assertEquals(new Autoload(array()), $package->getAutoload());
$this->assertEquals(array(), $package->getBins());
$this->assertEquals('dir/', $package->getDirectory());
$this->assertEquals('unknown', $package->getName());
$this->assertEquals('dir/vendor/', $package->getPathVendor());
}
public function testConstructorData()
{
$package = new Package(array(
'name' => 'test/test',
'bin' => array('bin/main', 'bin2'),
'config' => array(
'vendor-dir' => 'src/vendors'
)
), 'dir/');
$this->assertEquals(array('dir/bin/main', 'dir/bin2'), $package->getBins());
$this->assertEquals('test/test', $package->getName());
$this->assertEquals('dir/src/vendors/', $package->getPathVendor());
}
private function createMockLogger()
{
return $this->getMockBuilder('Clue\PharComposer\Logger')
->disableOriginalConstructor()
->getMock();
}
public function testConstructorBundlerComposer()
{
$package = new Package(array(
'extra' => array(
'phar' => array(
'bundler' => 'composer'
)
)
), 'dir/');
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Explicit',
$package->getBundler($this->createMockLogger())
);
}
public function testConstructorBundlerCompleteWithExplicitConfig()
{
$package = new Package(array(
'extra' => array(
'phar' => array(
'bundler' => 'complete'
)
)
), 'dir/');
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Complete',
$package->getBundler($this->createMockLogger())
);
}
public function testConstructorBundlerCompleteAsDefault()
{
$package = new Package(array(), 'dir/');
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Complete',
$package->getBundler($this->createMockLogger())
);
}
public function testConstructorBundlerInvalid()
{
$package = new Package(array(
'name' => 'cool-package',
'extra' => array(
'phar' => array(
'bundler' => 'foo'
)
)
), 'dir/');
$mockLogger = $this->createMockLogger();
$mockLogger->expects($this->once())
->method('log')
->with($this->equalTo('Invalid bundler "foo" specified in package "cool-package", will fall back to "complete" bundler'));
$this->assertInstanceOf('Clue\PharComposer\Package\Bundler\Complete',
$package->getBundler($mockLogger)
);
}
}

View File

@@ -0,0 +1,62 @@
<?php
use Clue\PharComposer\Phar\Packager;
class PackagerTest extends TestCase
{
private $packager;
public function setUp()
{
$this->packager = new Packager();
}
/**
*
* @param string $expectedOutput
* @param string $command
* @dataProvider provideExecCommands
*/
public function testExec($expectedOutput, $command)
{
$this->expectOutputString($expectedOutput);
$this->packager->exec($command);
}
public function provideExecCommands()
{
return array(
array("\n output\n", 'echo output'),
array("\n error\n", 'echo error >&2'),
array("\n mixed\n errors\n", 'echo mixed && echo errors >&1'),
);
}
/**
* @expectedException RuntimeException
* @expectedExceptionMessage not installed
*/
public function testEmptyNotInstalled()
{
$this->packager->getPharer(__DIR__ . '/../fixtures/01-empty');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage not a readable file
*/
public function testNoComposer()
{
$this->packager->getPharer(__DIR__ . '/../fixtures/02-no-composer');
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage not a readable file
*/
public function testNoComposerMissing()
{
$this->packager->getPharer(__DIR__ . '/../fixtures/02-no-composer/composer.json');
}
}

View File

@@ -0,0 +1,42 @@
<?php
use Clue\PharComposer\Phar\PharComposer;
class PharComposerTest extends TestCase
{
public function testConstructor()
{
$pharcomposer = new PharComposer(__DIR__ . '/../../composer.json');
$this->assertEquals($this->getPathProjectAbsolute('/') . '/', $pharcomposer->getBase());
$this->assertEquals($this->getPathProjectAbsolute('bin/phar-composer'), $pharcomposer->getMain());
$this->assertInstanceOf('Clue\PharComposer\Package\Package', $pharcomposer->getPackageRoot());
$this->assertNotCount(0, $pharcomposer->getPackagesDependencies());
$this->assertEquals($this->getPathProjectAbsolute('vendor') . '/', $pharcomposer->getPathVendor());
$this->assertEquals('phar-composer.phar', $pharcomposer->getTarget());
return $pharcomposer;
}
/**
* @param PharComposer $pharcomposer
* @depends testConstructor
*/
public function testSetters(PharComposer $pharcomposer)
{
$pharcomposer->setMain('example/phar-composer.php');
$this->assertEquals('example/phar-composer.php', $pharcomposer->getMain());
$pharcomposer->setTarget('test.phar');
$this->assertEquals('test.phar', $pharcomposer->getTarget());
return $pharcomposer;
}
private function getPathProjectAbsolute($path)
{
return realpath(__DIR__ . '/../../' . $path);
}
}

View File

@@ -0,0 +1,117 @@
<?php
use Clue\PharComposer\Package\Bundle;
use Clue\PharComposer\Phar\TargetPhar;
class TargetPharTest extends TestCase
{
/**
* instance to test
*
* @ype TargetPhar
*/
private $targetPhar;
private $mockPhar;
private $mockBox;
private $mockPharComposer;
/**
* set up test environment
*/
public function setUp()
{
$this->mockPhar = $this->createMock('\Phar');
$this->mockBox = $this->createMock('Herrera\Box\Box');
$this->mockBox->expects($this->any())
->method('getPhar')
->will($this->returnValue($this->mockPhar));
$this->mockPharComposer = $this->createMock('Clue\PharComposer\Phar\PharComposer');
$this->targetPhar = new TargetPhar($this->mockBox, $this->mockPharComposer);
}
private function createMock($class)
{
return $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
}
/**
* @test
*/
public function addFileCalculatesLocalPartForBox()
{
$this->mockPharComposer->expects($this->once())
->method('getPathLocalToBase')
->with($this->equalTo('path/to/package/file.php'))
->will($this->returnValue('file.php'));
$this->mockBox->expects($this->once())
->method('addFile')
->with($this->equalTo('path/to/package/file.php'), $this->equalTo('file.php'));
$this->targetPhar->addFile('path/to/package/file.php');
}
/**
* @test
*/
public function buildFromIteratorProvidesBasePathForBox()
{
$mockTraversable = $this->getMock('\Iterator');
$this->mockPharComposer->expects($this->once())
->method('getBase')
->will($this->returnValue('path/to/package'));
$this->mockBox->expects($this->once())
->method('buildFromIterator')
->with($this->equalTo($mockTraversable), $this->equalTo('path/to/package'));
$this->targetPhar->buildFromIterator($mockTraversable);
}
/**
* @test
*/
public function addPackageAddsResourcesFromCalculatedBundle()
{
$bundle = new Bundle();
$bundle->addFile('path/to/package/file.php');
$this->mockPharComposer->expects($this->once())
->method('getPathLocalToBase')
->with($this->equalTo('path/to/package/file.php'))
->will($this->returnValue('file.php'));
$this->mockBox->expects($this->once())
->method('addFile')
->with($this->equalTo('path/to/package/file.php'), $this->equalTo('file.php'));
$mockFinder = $this->createMock('Symfony\Component\Finder\Finder');
$bundle->addDir($mockFinder);
$this->mockPharComposer->expects($this->once())
->method('getBase')
->will($this->returnValue('path/to/package'));
$this->mockBox->expects($this->once())
->method('buildFromIterator')
->with($this->equalTo($mockFinder), $this->equalTo('path/to/package'));
$this->targetPhar->addBundle($bundle);
}
/**
* @test
*/
public function setsStubOnUnderlyingPhar()
{
$this->mockPhar->expects($this->once())
->method('setStub')
->with($this->equalTo('some stub code'));
$this->targetPhar->setStub('some stub code');
}
/**
* @test
*/
public function finalizeStopsBufferingOnUnderlyingPhar()
{
$this->mockPhar->expects($this->once())
->method('stopBuffering');
$this->targetPhar->finalize();
}
}

View File

@@ -0,0 +1,8 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
class TestCase extends PHPUnit_Framework_TestCase
{
}

View File

@@ -0,0 +1,3 @@
{
"name": "vendor/empty"
}

View File

@@ -0,0 +1,3 @@
# Readme
Not a valid project directory (does not contain a `composer.json` file).

View File

@@ -7,5 +7,13 @@ $baseDir = dirname($vendorDir);
return array(
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
'c14aa555f2c09024d741b443e292daa4' => $vendorDir . '/guzzlehttp/psr7/src/functions.php',
'540044e0591873ddd06a920c6c94cf8f' => $vendorDir . '/wyrihaximus/ticking-promise/src/functions_include.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'2cce0c19c44d4e6b9b83d392d5dcc239' => $vendorDir . '/wyrihaximus/react-child-process-promise/src/functions_include.php',
'7c9b72b4e40cc7adcca6fd17b1bf4c8d' => $vendorDir . '/indigophp/hash-compat/src/hash_equals.php',
'43d9263e52ab88b5668a28ee36bd4e65' => $vendorDir . '/indigophp/hash-compat/src/hash_pbkdf2.php',
'e0925b39a86673e84a647fb972717393' => $vendorDir . '/wyrihaximus/cpu-core-detector/src/functions_include.php',
'f3b28a95ab3f0417f7cb7996f4fa734a' => $vendorDir . '/wyrihaximus/react-child-process-pool/src/functions_include.php',
'5e93f83f32d7c16b062898884b9a20d8' => $vendorDir . '/react/filesystem/src/functions_include.php',
);

View File

@@ -6,7 +6,10 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'SamBurns\\ConfigFileParser\\' => array($vendorDir . '/samburns/config-file-parser/src'),
'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'),
'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'),
'Evenement' => array($vendorDir . '/evenement/evenement/src'),
'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib'),
'' => array($vendorDir . '/webignition/readable-duration/src'),
);

View File

@@ -6,18 +6,32 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'WyriHaximus\\React\\ChildProcess\\Pool\\' => array($vendorDir . '/wyrihaximus/react-child-process-pool/src'),
'WyriHaximus\\React\\ChildProcess\\Messenger\\' => array($vendorDir . '/wyrihaximus/react-child-process-messenger/src'),
'WyriHaximus\\React\\' => array($vendorDir . '/wyrihaximus/ticking-promise/src', $vendorDir . '/wyrihaximus/react-child-process-promise/src'),
'WyriHaximus\\CpuCoreDetector\\' => array($vendorDir . '/wyrihaximus/cpu-core-detector/src'),
'Tivie\\OS\\' => array($vendorDir . '/tivie/php-os-detector/src'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Rych\\ByteSize\\' => array($vendorDir . '/rych/bytesize/src'),
'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
'React\\SocketClient\\' => array($vendorDir . '/react/socket-client/src'),
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
'React\\Http\\' => array($vendorDir . '/react/http/src'),
'React\\HttpClient\\' => array($vendorDir . '/react/http-client/src'),
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop'),
'React\\Dns\\' => array($vendorDir . '/react/dns'),
'React\\Filesystem\\' => array($vendorDir . '/react/filesystem/src'),
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
'React\\ChildProcess\\' => array($vendorDir . '/react/child-process'),
'React\\Cache\\' => array($vendorDir . '/react/cache'),
'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
'Noodlehaus\\' => array($vendorDir . '/hassankhan/config/src'),
'MacFJA\\Symfony\\Console\\Filechooser\\' => array($vendorDir . '/macfja/symfony-console-filechooser/lib'),
'MacFJA\\PharBuilder\\' => array($vendorDir . '/macfja/phar-builder/app'),
'Linfo\\' => array($vendorDir . '/linfo/linfo/src/Linfo'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
);

View File

@@ -8,22 +8,47 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
'c14aa555f2c09024d741b443e292daa4' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions.php',
'540044e0591873ddd06a920c6c94cf8f' => __DIR__ . '/..' . '/wyrihaximus/ticking-promise/src/functions_include.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'2cce0c19c44d4e6b9b83d392d5dcc239' => __DIR__ . '/..' . '/wyrihaximus/react-child-process-promise/src/functions_include.php',
'7c9b72b4e40cc7adcca6fd17b1bf4c8d' => __DIR__ . '/..' . '/indigophp/hash-compat/src/hash_equals.php',
'43d9263e52ab88b5668a28ee36bd4e65' => __DIR__ . '/..' . '/indigophp/hash-compat/src/hash_pbkdf2.php',
'e0925b39a86673e84a647fb972717393' => __DIR__ . '/..' . '/wyrihaximus/cpu-core-detector/src/functions_include.php',
'f3b28a95ab3f0417f7cb7996f4fa734a' => __DIR__ . '/..' . '/wyrihaximus/react-child-process-pool/src/functions_include.php',
'5e93f83f32d7c16b062898884b9a20d8' => __DIR__ . '/..' . '/react/filesystem/src/functions_include.php',
);
public static $prefixLengthsPsr4 = array (
'W' =>
array (
'WyriHaximus\\React\\ChildProcess\\Pool\\' => 36,
'WyriHaximus\\React\\ChildProcess\\Messenger\\' => 41,
'WyriHaximus\\React\\' => 18,
'WyriHaximus\\CpuCoreDetector\\' => 28,
),
'T' =>
array (
'Tivie\\OS\\' => 9,
),
'S' =>
array (
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\Console\\' => 26,
),
'R' =>
array (
'Rych\\ByteSize\\' => 14,
'React\\Stream\\' => 13,
'React\\Socket\\' => 13,
'React\\SocketClient\\' => 19,
'React\\Promise\\' => 14,
'React\\Http\\' => 11,
'React\\HttpClient\\' => 17,
'React\\Filesystem\\' => 17,
'React\\EventLoop\\' => 16,
'React\\Dns\\' => 10,
'React\\ChildProcess\\' => 19,
@@ -33,6 +58,15 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
array (
'Psr\\Http\\Message\\' => 17,
),
'N' =>
array (
'Noodlehaus\\' => 11,
),
'M' =>
array (
'MacFJA\\Symfony\\Console\\Filechooser\\' => 35,
'MacFJA\\PharBuilder\\' => 19,
),
'L' =>
array (
'Linfo\\' => 6,
@@ -44,10 +78,51 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
);
public static $prefixDirsPsr4 = array (
'WyriHaximus\\React\\ChildProcess\\Pool\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/react-child-process-pool/src',
),
'WyriHaximus\\React\\ChildProcess\\Messenger\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/react-child-process-messenger/src',
),
'WyriHaximus\\React\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/ticking-promise/src',
1 => __DIR__ . '/..' . '/wyrihaximus/react-child-process-promise/src',
),
'WyriHaximus\\CpuCoreDetector\\' =>
array (
0 => __DIR__ . '/..' . '/wyrihaximus/cpu-core-detector/src',
),
'Tivie\\OS\\' =>
array (
0 => __DIR__ . '/..' . '/tivie/php-os-detector/src',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\Finder\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Rych\\ByteSize\\' =>
array (
0 => __DIR__ . '/..' . '/rych/bytesize/src',
),
'React\\Stream\\' =>
array (
0 => __DIR__ . '/..' . '/react/stream/src',
@@ -72,13 +147,17 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
array (
0 => __DIR__ . '/..' . '/react/http-client/src',
),
'React\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/react/filesystem/src',
),
'React\\EventLoop\\' =>
array (
0 => __DIR__ . '/..' . '/react/event-loop',
0 => __DIR__ . '/..' . '/react/event-loop/src',
),
'React\\Dns\\' =>
array (
0 => __DIR__ . '/..' . '/react/dns',
0 => __DIR__ . '/..' . '/react/dns/src',
),
'React\\ChildProcess\\' =>
array (
@@ -86,12 +165,24 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
),
'React\\Cache\\' =>
array (
0 => __DIR__ . '/..' . '/react/cache',
0 => __DIR__ . '/..' . '/react/cache/src',
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Noodlehaus\\' =>
array (
0 => __DIR__ . '/..' . '/hassankhan/config/src',
),
'MacFJA\\Symfony\\Console\\Filechooser\\' =>
array (
0 => __DIR__ . '/..' . '/macfja/symfony-console-filechooser/lib',
),
'MacFJA\\PharBuilder\\' =>
array (
0 => __DIR__ . '/..' . '/macfja/phar-builder/app',
),
'Linfo\\' =>
array (
0 => __DIR__ . '/..' . '/linfo/linfo/src/Linfo',
@@ -103,6 +194,13 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
);
public static $prefixesPsr0 = array (
'S' =>
array (
'SamBurns\\ConfigFileParser\\' =>
array (
0 => __DIR__ . '/..' . '/samburns/config-file-parser/src',
),
),
'G' =>
array (
'Guzzle\\Tests' =>
@@ -121,6 +219,17 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
0 => __DIR__ . '/..' . '/evenement/evenement/src',
),
),
'D' =>
array (
'Doctrine\\Common\\Inflector\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib',
),
),
);
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/..' . '/webignition/readable-duration/src',
);
public static function getInitializer(ClassLoader $loader)
@@ -129,6 +238,7 @@ class ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f
$loader->prefixLengthsPsr4 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$prefixesPsr0;
$loader->fallbackDirsPsr0 = ComposerStaticInit0cf5e3b00d3012d92e986b80c38c2a9f::$fallbackDirsPsr0;
}, null, ClassLoader::class);
}

File diff suppressed because it is too large Load Diff

4
vendor/doctrine/inflector/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
vendor/
composer.lock
composer.phar
phpunit.xml

21
vendor/doctrine/inflector/.travis.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
language: php
sudo: false
cache:
directory:
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
install:
- composer install -n
script:
- phpunit

19
vendor/doctrine/inflector/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2015 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
vendor/doctrine/inflector/README.md vendored Normal file
View File

@@ -0,0 +1,6 @@
# Doctrine Inflector
Doctrine Inflector is a small library that can perform string manipulations
with regard to upper-/lowercase and singular/plural forms of words.
[![Build Status](https://travis-ci.org/doctrine/inflector.svg?branch=master)](https://travis-ci.org/doctrine/inflector)

29
vendor/doctrine/inflector/composer.json vendored Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "doctrine/inflector",
"type": "library",
"description": "Common String Manipulations with regard to casing and singular/plural rules.",
"keywords": ["string", "inflection", "singularize", "pluralize"],
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.*"
},
"autoload": {
"psr-0": { "Doctrine\\Common\\Inflector\\": "lib/" }
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
}
}

View File

@@ -0,0 +1,482 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\Common\Inflector;
/**
* Doctrine inflector has static methods for inflecting text.
*
* The methods in these classes are from several different sources collected
* across several different php projects and several different authors. The
* original author names and emails are not known.
*
* Pluralize & Singularize implementation are borrowed from CakePHP with some modifications.
*
* @link www.doctrine-project.org
* @since 1.0
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class Inflector
{
/**
* Plural inflector rules.
*
* @var array
*/
private static $plural = array(
'rules' => array(
'/(s)tatus$/i' => '\1\2tatuses',
'/(quiz)$/i' => '\1zes',
'/^(ox)$/i' => '\1\2en',
'/([m|l])ouse$/i' => '\1ice',
'/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
'/(x|ch|ss|sh)$/i' => '\1es',
'/([^aeiouy]|qu)y$/i' => '\1ies',
'/(hive)$/i' => '\1s',
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
'/sis$/i' => 'ses',
'/([ti])um$/i' => '\1a',
'/(p)erson$/i' => '\1eople',
'/(m)an$/i' => '\1en',
'/(c)hild$/i' => '\1hildren',
'/(f)oot$/i' => '\1eet',
'/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
'/us$/i' => 'uses',
'/(alias)$/i' => '\1es',
'/(analys|ax|cris|test|thes)is$/i' => '\1es',
'/s$/' => 's',
'/^$/' => '',
'/$/' => 's',
),
'uninflected' => array(
'.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie'
),
'irregular' => array(
'atlas' => 'atlases',
'axe' => 'axes',
'beef' => 'beefs',
'brother' => 'brothers',
'cafe' => 'cafes',
'chateau' => 'chateaux',
'child' => 'children',
'cookie' => 'cookies',
'corpus' => 'corpuses',
'cow' => 'cows',
'criterion' => 'criteria',
'curriculum' => 'curricula',
'demo' => 'demos',
'domino' => 'dominoes',
'echo' => 'echoes',
'foot' => 'feet',
'fungus' => 'fungi',
'ganglion' => 'ganglions',
'genie' => 'genies',
'genus' => 'genera',
'graffito' => 'graffiti',
'hippopotamus' => 'hippopotami',
'hoof' => 'hoofs',
'human' => 'humans',
'iris' => 'irises',
'leaf' => 'leaves',
'loaf' => 'loaves',
'man' => 'men',
'medium' => 'media',
'memorandum' => 'memoranda',
'money' => 'monies',
'mongoose' => 'mongooses',
'motto' => 'mottoes',
'move' => 'moves',
'mythos' => 'mythoi',
'niche' => 'niches',
'nucleus' => 'nuclei',
'numen' => 'numina',
'occiput' => 'occiputs',
'octopus' => 'octopuses',
'opus' => 'opuses',
'ox' => 'oxen',
'penis' => 'penises',
'person' => 'people',
'plateau' => 'plateaux',
'runner-up' => 'runners-up',
'sex' => 'sexes',
'soliloquy' => 'soliloquies',
'son-in-law' => 'sons-in-law',
'syllabus' => 'syllabi',
'testis' => 'testes',
'thief' => 'thieves',
'tooth' => 'teeth',
'tornado' => 'tornadoes',
'trilby' => 'trilbys',
'turf' => 'turfs',
'volcano' => 'volcanoes',
)
);
/**
* Singular inflector rules.
*
* @var array
*/
private static $singular = array(
'rules' => array(
'/(s)tatuses$/i' => '\1\2tatus',
'/^(.*)(menu)s$/i' => '\1\2',
'/(quiz)zes$/i' => '\\1',
'/(matr)ices$/i' => '\1ix',
'/(vert|ind)ices$/i' => '\1ex',
'/^(ox)en/i' => '\1',
'/(alias)(es)*$/i' => '\1',
'/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
'/([ftw]ax)es/i' => '\1',
'/(analys|ax|cris|test|thes)es$/i' => '\1is',
'/(shoe|slave)s$/i' => '\1',
'/(o)es$/i' => '\1',
'/ouses$/' => 'ouse',
'/([^a])uses$/' => '\1us',
'/([m|l])ice$/i' => '\1ouse',
'/(x|ch|ss|sh)es$/i' => '\1',
'/(m)ovies$/i' => '\1\2ovie',
'/(s)eries$/i' => '\1\2eries',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/([lr])ves$/i' => '\1f',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1',
'/([^fo])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um',
'/(p)eople$/i' => '\1\2erson',
'/(m)en$/i' => '\1an',
'/(c)hildren$/i' => '\1\2hild',
'/(f)eet$/i' => '\1oot',
'/(n)ews$/i' => '\1\2ews',
'/eaus$/' => 'eau',
'/^(.*us)$/' => '\\1',
'/s$/i' => '',
),
'uninflected' => array(
'.*[nrlm]ese',
'.*deer',
'.*fish',
'.*measles',
'.*ois',
'.*pox',
'.*sheep',
'.*ss',
),
'irregular' => array(
'criteria' => 'criterion',
'curves' => 'curve',
'emphases' => 'emphasis',
'foes' => 'foe',
'hoaxes' => 'hoax',
'media' => 'medium',
'neuroses' => 'neurosis',
'waves' => 'wave',
'oases' => 'oasis',
)
);
/**
* Words that should not be inflected.
*
* @var array
*/
private static $uninflected = array(
'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus',
'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps',
'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder',
'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings',
'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media',
'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese',
'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese',
'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine',
'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting',
'wildebeest', 'Yengeese'
);
/**
* Method cache array.
*
* @var array
*/
private static $cache = array();
/**
* The initial state of Inflector so reset() works.
*
* @var array
*/
private static $initialState = array();
/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*
* @param string $word The word to tableize.
*
* @return string The tableized word.
*/
public static function tableize($word)
{
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
}
/**
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
*
* @param string $word The word to classify.
*
* @return string The classified word.
*/
public static function classify($word)
{
return str_replace(" ", "", ucwords(strtr($word, "_-", " ")));
}
/**
* Camelizes a word. This uses the classify() method and turns the first character to lowercase.
*
* @param string $word The word to camelize.
*
* @return string The camelized word.
*/
public static function camelize($word)
{
return lcfirst(self::classify($word));
}
/**
* Uppercases words with configurable delimeters between words.
*
* Takes a string and capitalizes all of the words, like PHP's built-in
* ucwords function. This extends that behavior, however, by allowing the
* word delimeters to be configured, rather than only separating on
* whitespace.
*
* Here is an example:
* <code>
* <?php
* $string = 'top-o-the-morning to all_of_you!';
* echo \Doctrine\Common\Inflector\Inflector::ucwords($string);
* // Top-O-The-Morning To All_of_you!
*
* echo \Doctrine\Common\Inflector\Inflector::ucwords($string, '-_ ');
* // Top-O-The-Morning To All_Of_You!
* ?>
* </code>
*
* @param string $string The string to operate on.
* @param string $delimiters A list of word separators.
*
* @return string The string with all delimeter-separated words capitalized.
*/
public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-")
{
return preg_replace_callback(
'/[^' . preg_quote($delimiters, '/') . ']+/',
function($matches) {
return ucfirst($matches[0]);
},
$string
);
}
/**
* Clears Inflectors inflected value caches, and resets the inflection
* rules to the initial values.
*
* @return void
*/
public static function reset()
{
if (empty(self::$initialState)) {
self::$initialState = get_class_vars('Inflector');
return;
}
foreach (self::$initialState as $key => $val) {
if ($key != 'initialState') {
self::${$key} = $val;
}
}
}
/**
* Adds custom inflection $rules, of either 'plural' or 'singular' $type.
*
* ### Usage:
*
* {{{
* Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
* Inflector::rules('plural', array(
* 'rules' => array('/^(inflect)ors$/i' => '\1ables'),
* 'uninflected' => array('dontinflectme'),
* 'irregular' => array('red' => 'redlings')
* ));
* }}}
*
* @param string $type The type of inflection, either 'plural' or 'singular'
* @param array $rules An array of rules to be added.
* @param boolean $reset If true, will unset default inflections for all
* new rules that are being defined in $rules.
*
* @return void
*/
public static function rules($type, $rules, $reset = false)
{
foreach ($rules as $rule => $pattern) {
if ( ! is_array($pattern)) {
continue;
}
if ($reset) {
self::${$type}[$rule] = $pattern;
} else {
self::${$type}[$rule] = ($rule === 'uninflected')
? array_merge($pattern, self::${$type}[$rule])
: $pattern + self::${$type}[$rule];
}
unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]);
if (isset(self::${$type}['merged'][$rule])) {
unset(self::${$type}['merged'][$rule]);
}
if ($type === 'plural') {
self::$cache['pluralize'] = self::$cache['tableize'] = array();
} elseif ($type === 'singular') {
self::$cache['singularize'] = array();
}
}
self::${$type}['rules'] = $rules + self::${$type}['rules'];
}
/**
* Returns a word in plural form.
*
* @param string $word The word in singular form.
*
* @return string The word in plural form.
*/
public static function pluralize($word)
{
if (isset(self::$cache['pluralize'][$word])) {
return self::$cache['pluralize'][$word];
}
if (!isset(self::$plural['merged']['irregular'])) {
self::$plural['merged']['irregular'] = self::$plural['irregular'];
}
if (!isset(self::$plural['merged']['uninflected'])) {
self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected);
}
if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) {
self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')';
self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1);
return self::$cache['pluralize'][$word];
}
if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['pluralize'][$word] = $word;
return $word;
}
foreach (self::$plural['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['pluralize'][$word];
}
}
}
/**
* Returns a word in singular form.
*
* @param string $word The word in plural form.
*
* @return string The word in singular form.
*/
public static function singularize($word)
{
if (isset(self::$cache['singularize'][$word])) {
return self::$cache['singularize'][$word];
}
if (!isset(self::$singular['merged']['uninflected'])) {
self::$singular['merged']['uninflected'] = array_merge(
self::$singular['uninflected'],
self::$uninflected
);
}
if (!isset(self::$singular['merged']['irregular'])) {
self::$singular['merged']['irregular'] = array_merge(
self::$singular['irregular'],
array_flip(self::$plural['irregular'])
);
}
if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) {
self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')';
self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')';
}
if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) {
self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1);
return self::$cache['singularize'][$word];
}
if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) {
self::$cache['singularize'][$word] = $word;
return $word;
}
foreach (self::$singular['rules'] as $rule => $replacement) {
if (preg_match($rule, $word)) {
self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
return self::$cache['singularize'][$word];
}
}
self::$cache['singularize'][$word] = $word;
return $word;
}
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/Doctrine/Tests/TestInit.php"
>
<testsuites>
<testsuite name="Doctrine Inflector Test Suite">
<directory>./tests/Doctrine/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./lib/Doctrine/</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
</exclude>
</groups>
</phpunit>

View File

@@ -0,0 +1,395 @@
<?php
namespace Doctrine\Tests\Common\Inflector;
use Doctrine\Tests\DoctrineTestCase;
use Doctrine\Common\Inflector\Inflector;
class InflectorTest extends DoctrineTestCase
{
/**
* Singular & Plural test data. Returns an array of sample words.
*
* @return array
*/
public function dataSampleWords()
{
Inflector::reset();
// In the format array('singular', 'plural')
return array(
array('', ''),
array('Alias', 'Aliases'),
array('alumnus', 'alumni'),
array('analysis', 'analyses'),
array('aquarium', 'aquaria'),
array('arch', 'arches'),
array('atlas', 'atlases'),
array('axe', 'axes'),
array('baby', 'babies'),
array('bacillus', 'bacilli'),
array('bacterium', 'bacteria'),
array('bureau', 'bureaus'),
array('bus', 'buses'),
array('Bus', 'Buses'),
array('cactus', 'cacti'),
array('cafe', 'cafes'),
array('calf', 'calves'),
array('categoria', 'categorias'),
array('chateau', 'chateaux'),
array('cherry', 'cherries'),
array('child', 'children'),
array('church', 'churches'),
array('circus', 'circuses'),
array('city', 'cities'),
array('cod', 'cod'),
array('cookie', 'cookies'),
array('copy', 'copies'),
array('crisis', 'crises'),
array('criterion', 'criteria'),
array('curriculum', 'curricula'),
array('curve', 'curves'),
array('deer', 'deer'),
array('demo', 'demos'),
array('dictionary', 'dictionaries'),
array('domino', 'dominoes'),
array('dwarf', 'dwarves'),
array('echo', 'echoes'),
array('elf', 'elves'),
array('emphasis', 'emphases'),
array('family', 'families'),
array('fax', 'faxes'),
array('fish', 'fish'),
array('flush', 'flushes'),
array('fly', 'flies'),
array('focus', 'foci'),
array('foe', 'foes'),
array('food_menu', 'food_menus'),
array('FoodMenu', 'FoodMenus'),
array('foot', 'feet'),
array('fungus', 'fungi'),
array('glove', 'gloves'),
array('half', 'halves'),
array('hero', 'heroes'),
array('hippopotamus', 'hippopotami'),
array('hoax', 'hoaxes'),
array('house', 'houses'),
array('human', 'humans'),
array('identity', 'identities'),
array('index', 'indices'),
array('iris', 'irises'),
array('kiss', 'kisses'),
array('knife', 'knives'),
array('leaf', 'leaves'),
array('life', 'lives'),
array('loaf', 'loaves'),
array('man', 'men'),
array('matrix', 'matrices'),
array('matrix_row', 'matrix_rows'),
array('medium', 'media'),
array('memorandum', 'memoranda'),
array('menu', 'menus'),
array('Menu', 'Menus'),
array('mess', 'messes'),
array('moose', 'moose'),
array('motto', 'mottoes'),
array('mouse', 'mice'),
array('neurosis', 'neuroses'),
array('news', 'news'),
array('NodeMedia', 'NodeMedia'),
array('nucleus', 'nuclei'),
array('oasis', 'oases'),
array('octopus', 'octopuses'),
array('pass', 'passes'),
array('person', 'people'),
array('plateau', 'plateaux'),
array('potato', 'potatoes'),
array('powerhouse', 'powerhouses'),
array('quiz', 'quizzes'),
array('radius', 'radii'),
array('reflex', 'reflexes'),
array('roof', 'roofs'),
array('runner-up', 'runners-up'),
array('scarf', 'scarves'),
array('scratch', 'scratches'),
array('series', 'series'),
array('sheep', 'sheep'),
array('shelf', 'shelves'),
array('shoe', 'shoes'),
array('son-in-law', 'sons-in-law'),
array('species', 'species'),
array('splash', 'splashes'),
array('spy', 'spies'),
array('stimulus', 'stimuli'),
array('stitch', 'stitches'),
array('story', 'stories'),
array('syllabus', 'syllabi'),
array('tax', 'taxes'),
array('terminus', 'termini'),
array('thesis', 'theses'),
array('thief', 'thieves'),
array('tomato', 'tomatoes'),
array('tooth', 'teeth'),
array('tornado', 'tornadoes'),
array('try', 'tries'),
array('vertex', 'vertices'),
array('virus', 'viri'),
array('volcano', 'volcanoes'),
array('wash', 'washes'),
array('watch', 'watches'),
array('wave', 'waves'),
array('wharf', 'wharves'),
array('wife', 'wives'),
array('woman', 'women'),
);
}
/**
* testInflectingSingulars method
*
* @dataProvider dataSampleWords
* @return void
*/
public function testInflectingSingulars($singular, $plural)
{
$this->assertEquals(
$singular,
Inflector::singularize($plural),
"'$plural' should be singularized to '$singular'"
);
}
/**
* testInflectingPlurals method
*
* @dataProvider dataSampleWords
* @return void
*/
public function testInflectingPlurals($singular, $plural)
{
$this->assertEquals(
$plural,
Inflector::pluralize($singular),
"'$singular' should be pluralized to '$plural'"
);
}
/**
* testCustomPluralRule method
*
* @return void
*/
public function testCustomPluralRule()
{
Inflector::reset();
Inflector::rules('plural', array('/^(custom)$/i' => '\1izables'));
$this->assertEquals(Inflector::pluralize('custom'), 'customizables');
Inflector::rules('plural', array('uninflected' => array('uninflectable')));
$this->assertEquals(Inflector::pluralize('uninflectable'), 'uninflectable');
Inflector::rules('plural', array(
'rules' => array('/^(alert)$/i' => '\1ables'),
'uninflected' => array('noflect', 'abtuse'),
'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes')
));
$this->assertEquals(Inflector::pluralize('noflect'), 'noflect');
$this->assertEquals(Inflector::pluralize('abtuse'), 'abtuse');
$this->assertEquals(Inflector::pluralize('alert'), 'alertables');
$this->assertEquals(Inflector::pluralize('amaze'), 'amazable');
$this->assertEquals(Inflector::pluralize('phone'), 'phonezes');
}
/**
* testCustomSingularRule method
*
* @return void
*/
public function testCustomSingularRule()
{
Inflector::reset();
Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1'));
$this->assertEquals(Inflector::singularize('epler'), 'eple');
$this->assertEquals(Inflector::singularize('jenter'), 'jente');
Inflector::rules('singular', array(
'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'),
'uninflected' => array('singulars'),
'irregular' => array('spins' => 'spinor')
));
$this->assertEquals(Inflector::singularize('inflectors'), 'inflecta');
$this->assertEquals(Inflector::singularize('contributors'), 'contributa');
$this->assertEquals(Inflector::singularize('spins'), 'spinor');
$this->assertEquals(Inflector::singularize('singulars'), 'singulars');
}
/**
* test that setting new rules clears the inflector caches.
*
* @return void
*/
public function testRulesClearsCaches()
{
Inflector::reset();
$this->assertEquals(Inflector::singularize('Bananas'), 'Banana');
$this->assertEquals(Inflector::pluralize('Banana'), 'Bananas');
Inflector::rules('singular', array(
'rules' => array('/(.*)nas$/i' => '\1zzz')
));
$this->assertEquals('Banazzz', Inflector::singularize('Bananas'), 'Was inflected with old rules.');
Inflector::rules('plural', array(
'rules' => array('/(.*)na$/i' => '\1zzz'),
'irregular' => array('corpus' => 'corpora')
));
$this->assertEquals(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules.');
$this->assertEquals(Inflector::pluralize('corpus'), 'corpora', 'Was inflected with old irregular form.');
}
/**
* Test resetting inflection rules.
*
* @return void
*/
public function testCustomRuleWithReset()
{
Inflector::reset();
$uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x');
$pluralIrregular = array('as' => 'ases');
Inflector::rules('singular', array(
'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'),
'uninflected' => $uninflected,
), true);
Inflector::rules('plural', array(
'rules' => array(
'/^(.*)(a|e|o|u)l$/i' => '\1\2is',
),
'uninflected' => $uninflected,
'irregular' => $pluralIrregular
), true);
$this->assertEquals(Inflector::pluralize('Alcool'), 'Alcoois');
$this->assertEquals(Inflector::pluralize('Atlas'), 'Atlas');
$this->assertEquals(Inflector::singularize('Alcoois'), 'Alcool');
$this->assertEquals(Inflector::singularize('Atlas'), 'Atlas');
}
/**
* Test basic ucwords functionality.
*
* @return void
*/
public function testUcwords()
{
$this->assertSame('Top-O-The-Morning To All_of_you!', Inflector::ucwords( 'top-o-the-morning to all_of_you!'));
}
/**
* Test ucwords functionality with custom delimeters.
*
* @return void
*/
public function testUcwordsWithCustomDelimeters()
{
$this->assertSame('Top-O-The-Morning To All_Of_You!', Inflector::ucwords( 'top-o-the-morning to all_of_you!', '-_ '));
}
/**
* @param $expected
* @param $word
*
* @dataProvider dataStringsTableize
* @return void
*/
public function testTableize($expected, $word)
{
$this->assertSame($expected, Inflector::tableize($word));
}
/**
* Strings which are used for testTableize.
*
* @return array
*/
public function dataStringsTableize()
{
// In the format array('expected', 'word')
return array(
array('', ''),
array('foo_bar', 'FooBar'),
array('f0o_bar', 'F0oBar'),
);
}
/**
* @param $expected
* @param $word
*
* @dataProvider dataStringsClassify
* @return void
*/
public function testClassify($expected, $word)
{
$this->assertSame($expected, Inflector::classify($word));
}
/**
* Strings which are used for testClassify.
*
* @return array
*/
public function dataStringsClassify()
{
// In the format array('expected', 'word')
return array(
array('', ''),
array('FooBar', 'foo_bar'),
array('FooBar', 'foo bar'),
array('F0oBar', 'f0o bar'),
array('F0oBar', 'f0o bar'),
array('FooBar', 'foo_bar_'),
);
}
/**
* @param $expected
* @param $word
*
* @dataProvider dataStringsCamelize
* @return void
*/
public function testCamelize($expected, $word)
{
$this->assertSame($expected, Inflector::camelize($word));
}
/**
* Strings which are used for testCamelize.
*
* @return array
*/
public function dataStringsCamelize()
{
// In the format array('expected', 'word')
return array(
array('', ''),
array('fooBar', 'foo_bar'),
array('fooBar', 'foo bar'),
array('f0oBar', 'f0o bar'),
array('f0oBar', 'f0o bar'),
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Doctrine\Tests;
/**
* Base testcase class for all Doctrine testcases.
*/
abstract class DoctrineTestCase extends \PHPUnit_Framework_TestCase
{
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file bootstraps the test environment.
*/
namespace Doctrine\Tests;
error_reporting(E_ALL | E_STRICT);
// register silently failing autoloader
spl_autoload_register(function($class)
{
if (0 === strpos($class, 'Doctrine\Tests\\')) {
$path = __DIR__.'/../../'.strtr($class, '\\', '/').'.php';
if (is_file($path) && is_readable($path)) {
require_once $path;
return true;
}
} else if (0 === strpos($class, 'Doctrine\Common\\')) {
$path = __DIR__.'/../../../lib/'.($class = strtr($class, '\\', '/')).'.php';
if (is_file($path) && is_readable($path)) {
require_once $path;
return true;
}
}
});

View File

@@ -14,10 +14,4 @@ before_script:
- composer install --no-interaction --prefer-source --dev
- ~/.nvm/nvm.sh install v0.6.14
script: vendor/bin/phpunit
matrix:
allow_failures:
- php: 5.6
- php: hhvm
fast_finish: true
script: composer test

View File

@@ -1,22 +1,25 @@
CHANGELOG
=========
# CHANGELOG
3.9.2 (2014-09-10)
------------------
## 3.9.3 - 2015-03-18
* Ensuring Content-Length is not stripped from a request when it is `0`.
* Added more information to stream wrapper exceptions.
* Message parser will no longer throw warnings for malformed messages.
* Giving a valid cache TTL when max-age is 0.
## 3.9.2 - 2014-09-10
* Retrying "Connection died, retrying a fresh connect" curl errors.
* Automatically extracting the cacert from the phar in client constructor.
* Added EntityBody support for OPTIONS requests.
3.9.1 (2014-05-07)
------------------
## 3.9.1 - 2014-05-07
* Added a fix to ReadLimitEntityBody to ensure it doesn't infinitely loop.
* Added a fix to the stream checksum function so that when the first read
returns a falsey value, it still continues to consume the stream until EOF.
3.9.0 (2014-04-23)
------------------
## 3.9.0 - 2014-04-23
* `null`, `false`, and `"_guzzle_blank_"` all now serialize as an empty value
with no trailing "=". See dc1d824277.
@@ -34,8 +37,7 @@ CHANGELOG
* OAuth parameters are now sorted using lexicographical byte value ordering
* Fixing invalid usage of an out of range PHP feature in the ErrorResponsePlugin
3.8.1 (2014-01-28)
------------------
## 3.8.1 -2014-01-28
* Bug: Always using GET requests when redirecting from a 303 response
* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
@@ -53,8 +55,7 @@ CHANGELOG
* Now properly escaping the regular expression delimiter when matching Cookie domains.
* Network access is now disabled when loading XML documents
3.8.0 (2013-12-05)
------------------
## 3.8.0 - 2013-12-05
* Added the ability to define a POST name for a file
* JSON response parsing now properly walks additionalProperties
@@ -74,8 +75,7 @@ CHANGELOG
* Various fixes to the AsyncPlugin
* Cleaned up build scripts
3.7.4 (2013-10-02)
------------------
## 3.7.4 - 2013-10-02
* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
@@ -85,8 +85,7 @@ CHANGELOG
* Updated the bundled cacert.pem (#419)
* OauthPlugin now supports adding authentication to headers or query string (#425)
3.7.3 (2013-09-08)
------------------
## 3.7.3 - 2013-09-08
* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
`CommandTransferException`.
@@ -102,8 +101,7 @@ CHANGELOG
* Bug fix: Properly parsing headers that contain commas contained in quotes
* Bug fix: mimetype guessing based on a filename is now case-insensitive
3.7.2 (2013-08-02)
------------------
## 3.7.2 - 2013-08-02
* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
See https://github.com/guzzle/guzzle/issues/371
@@ -118,8 +116,7 @@ CHANGELOG
https://github.com/guzzle/guzzle/pull/380
* cURL multi cleanup and optimizations
3.7.1 (2013-07-05)
------------------
## 3.7.1 - 2013-07-05
* Bug fix: Setting default options on a client now works
* Bug fix: Setting options on HEAD requests now works. See #352
@@ -134,8 +131,7 @@ CHANGELOG
* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin
3.7.0 (2013-06-10)
------------------
## 3.7.0 - 2013-06-10
* See UPGRADING.md for more information on how to upgrade.
* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
@@ -219,8 +215,7 @@ CHANGELOG
CanCacheStrategyInterface $canCache = null)`
* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`
3.6.0 (2013-05-29)
------------------
## 3.6.0 - 2013-05-29
* ServiceDescription now implements ToArrayInterface
* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
@@ -257,8 +252,7 @@ CHANGELOG
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
* Added the ability to cast Model objects to a string to view debug information.
3.5.0 (2013-05-13)
------------------
## 3.5.0 - 2013-05-13
* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
* Bug: Better cleanup of one-time events accross the board (when an event is meant to fire once, it will now remove
@@ -280,14 +274,12 @@ CHANGELOG
and responses that are sent over the wire
* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects
3.4.3 (2013-04-30)
------------------
## 3.4.3 - 2013-04-30
* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
* Added a check to re-extract the temp cacert bundle from the phar before sending each request
3.4.2 (2013-04-29)
------------------
## 3.4.2 - 2013-04-29
* Bug fix: Stream objects now work correctly with "a" and "a+" modes
* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
@@ -302,8 +294,7 @@ CHANGELOG
* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
* Configuration loading now allows remote files
3.4.1 (2013-04-16)
------------------
## 3.4.1 - 2013-04-16
* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
@@ -316,8 +307,7 @@ CHANGELOG
* Added support for oauth_verifier in OAuth signatures
* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection
3.4.0 (2013-04-11)
------------------
## 3.4.0 - 2013-04-11
* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
@@ -347,8 +337,7 @@ CHANGELOG
POST fields or files (the latter is only used when emulating a form POST in the browser).
* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest
3.3.1 (2013-03-10)
------------------
## 3.3.1 - 2013-03-10
* Added the ability to create PHP streaming responses from HTTP requests
* Bug fix: Running any filters when parsing response headers with service descriptions
@@ -359,8 +348,7 @@ CHANGELOG
* RequestFactory::create() now uses the key of a POST file when setting the POST file name
* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set
3.3.0 (2013-03-03)
------------------
## 3.3.0 - 2013-03-03
* A large number of performance optimizations have been made
* Bug fix: Added 'wb' as a valid write mode for streams
@@ -384,8 +372,7 @@ CHANGELOG
* Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
* Debug headers can now added to cached response in the CachePlugin
3.2.0 (2013-02-14)
------------------
## 3.2.0 - 2013-02-14
* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
* URLs with no path no longer contain a "/" by default
@@ -404,8 +391,7 @@ CHANGELOG
* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives
3.1.2 (2013-01-27)
------------------
## 3.1.2 - 2013-01-27
* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
@@ -414,15 +400,13 @@ CHANGELOG
* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
* Setting default headers on a client after setting the user-agent will not erase the user-agent setting
3.1.1 (2013-01-20)
------------------
## 3.1.1 - 2013-01-20
* Adding wildcard support to Guzzle\Common\Collection::getPath()
* Adding alias support to ServiceBuilder configs
* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface
3.1.0 (2013-01-12)
------------------
## 3.1.0 - 2013-01-12
* BC: CurlException now extends from RequestException rather than BadResponseException
* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
@@ -439,23 +423,20 @@ CHANGELOG
* Added `extends` attributes to service description parameters
* Added getModels to ServiceDescriptionInterface
3.0.7 (2012-12-19)
------------------
## 3.0.7 - 2012-12-19
* Fixing phar detection when forcing a cacert to system if null or true
* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
* Cleaning up `Guzzle\Common\Collection::inject` method
* Adding a response_body location to service descriptions
3.0.6 (2012-12-09)
------------------
## 3.0.6 - 2012-12-09
* CurlMulti performance improvements
* Adding setErrorResponses() to Operation
* composer.json tweaks
3.0.5 (2012-11-18)
------------------
## 3.0.5 - 2012-11-18
* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
* Bug: Response body can now be a string containing "0"
@@ -465,8 +446,7 @@ CHANGELOG
* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
* Added better mimetype guessing to requests and post files
3.0.4 (2012-11-11)
------------------
## 3.0.4 - 2012-11-11
* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
* Bug: Cookies can now be added that have a name, domain, or value set to "0"
@@ -477,8 +457,7 @@ CHANGELOG
* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
* Added the ability to create any sort of hash for a stream rather than just an MD5 hash
3.0.3 (2012-11-04)
------------------
## 3.0.3 - 2012-11-04
* Implementing redirects in PHP rather than cURL
* Added PECL URI template extension and using as default parser if available
@@ -487,23 +466,20 @@ CHANGELOG
* Adding ToArrayInterface throughout library
* Fixing OauthPlugin to create unique nonce values per request
3.0.2 (2012-10-25)
------------------
## 3.0.2 - 2012-10-25
* Magic methods are enabled by default on clients
* Magic methods return the result of a command
* Service clients no longer require a base_url option in the factory
* Bug: Fixed an issue with URI templates where null template variables were being expanded
3.0.1 (2012-10-22)
------------------
## 3.0.1 - 2012-10-22
* Models can now be used like regular collection objects by calling filter, map, etc
* Models no longer require a Parameter structure or initial data in the constructor
* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`
3.0.0 (2012-10-15)
------------------
## 3.0.0 - 2012-10-15
* Rewrote service description format to be based on Swagger
* Now based on JSON schema
@@ -535,13 +511,11 @@ CHANGELOG
* Cleaning up Collection class and removing default values from the get method
* Fixed ZF2 cache adapters
2.8.8 (2012-10-15)
------------------
## 2.8.8 - 2012-10-15
* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did
2.8.7 (2012-09-30)
------------------
## 2.8.7 - 2012-09-30
* Bug: Fixed config file aliases for JSON includes
* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
@@ -555,8 +529,7 @@ CHANGELOG
* Added the ability to remove POST fields from OAuth signatures
* OAuth plugin now supports 2-legged OAuth
2.8.6 (2012-09-05)
------------------
## 2.8.6 - 2012-09-05
* Added the ability to modify and build service descriptions
* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
@@ -570,8 +543,7 @@ CHANGELOG
* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
'_default' with a default JSON configuration file.
2.8.5 (2012-08-29)
------------------
## 2.8.5 - 2012-08-29
* Bug: Suppressed empty arrays from URI templates
* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
@@ -579,8 +551,7 @@ CHANGELOG
* AbstractCommand commands are now invokable
* Added a way to get the data used when signing an Oauth request before a request is sent
2.8.4 (2012-08-15)
------------------
## 2.8.4 - 2012-08-15
* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
@@ -594,8 +565,7 @@ CHANGELOG
* Added the ability of the MockPlugin to consume mocked request bodies
* LogPlugin now exposes request and response objects in the extras array
2.8.3 (2012-07-30)
------------------
## 2.8.3 - 2012-07-30
* Bug: Fixed a case where empty POST requests were sent as GET requests
* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
@@ -605,8 +575,7 @@ CHANGELOG
* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles
2.8.2 (2012-07-24)
------------------
## 2.8.2 - 2012-07-24
* Bug: Query string values set to 0 are no longer dropped from the query string
* Bug: A Collection object is no longer created each time a call is made to ``Guzzle\Service\Command\AbstractCommand::getRequestHeaders()``
@@ -614,14 +583,12 @@ CHANGELOG
* QueryString and Collection performance improvements
* Allowing dot notation for class paths in filters attribute of a service descriptions
2.8.1 (2012-07-16)
------------------
## 2.8.1 - 2012-07-16
* Loosening Event Dispatcher dependency
* POST redirects can now be customized using CURLOPT_POSTREDIR
2.8.0 (2012-07-15)
------------------
## 2.8.0 - 2012-07-15
* BC: Guzzle\Http\Query
* Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
@@ -634,8 +601,7 @@ CHANGELOG
* Cookies are no longer URL decoded by default
* Bug: URI template variables set to null are no longer expanded
2.7.2 (2012-07-02)
------------------
## 2.7.2 - 2012-07-02
* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
@@ -645,14 +611,12 @@ CHANGELOG
* Allowing deeply nested arrays for composite variables in URI templates
* Batch divisors can now return iterators or arrays
2.7.1 (2012-06-26)
------------------
## 2.7.1 - 2012-06-26
* Minor patch to update version number in UA string
* Updating build process
2.7.0 (2012-06-25)
------------------
## 2.7.0 - 2012-06-25
* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
* BC: Removed magic setX methods from commands
@@ -669,8 +633,7 @@ CHANGELOG
* Fixed some tests so that they pass more reliably
* Added Guzzle\Common\Log\ArrayLogAdapter
2.6.6 (2012-06-10)
------------------
## 2.6.6 - 2012-06-10
* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
* BC: Removing Guzzle\Service\Command\CommandSet
@@ -680,8 +643,7 @@ CHANGELOG
* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
* Bug: Changed the default cookie header casing back to 'Cookie'
2.6.5 (2012-06-03)
------------------
## 2.6.5 - 2012-06-03
* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
@@ -693,8 +655,7 @@ CHANGELOG
* Adding getCookies() to request interface.
* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()
2.6.4 (2012-05-30)
------------------
## 2.6.4 - 2012-05-30
* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
@@ -711,8 +672,7 @@ CHANGELOG
* Allowing the result of a command object to be changed
* Parsing location and type sub values when instantiating a service description rather than over and over at runtime
2.6.3 (2012-05-23)
------------------
## 2.6.3 - 2012-05-23
* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
@@ -726,13 +686,11 @@ CHANGELOG
* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
* CS updates
2.6.2 (2012-05-19)
------------------
## 2.6.2 - 2012-05-19
* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method.
2.6.1 (2012-05-19)
------------------
## 2.6.1 - 2012-05-19
* [BC] Removing 'path' support in service descriptions. Use 'uri'.
* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
@@ -743,8 +701,7 @@ CHANGELOG
* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.
2.6.0 (2012-05-15)
------------------
## 2.6.0 - 2012-05-15
* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
* [BC] Executing a Command returns the result of the command rather than the command
@@ -772,8 +729,7 @@ CHANGELOG
* Adding the ability to include other service builder config files from within XML and JSON files
* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.
2.5.0 (2012-05-08)
------------------
## 2.5.0 - 2012-05-08
* Major performance improvements
* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated.

View File

@@ -3,12 +3,15 @@ Guzzle, PHP HTTP client and webservice framework
# This is an old version of Guzzle
This repository is for Guzzle 3.x. Guzzle 4.x, the new version of Guzzle, has
This repository is for Guzzle 3.x. Guzzle 5.x, the new version of Guzzle, has
been released and is available at
[https://github.com/guzzle/guzzle](https://github.com/guzzle/guzzle). The
documentation for Guzzle version 4+ can be found at
documentation for Guzzle version 5+ can be found at
[http://guzzlephp.org](http://guzzlephp.org).
Guzzle 3 is only maintained for bug and security fixes. Guzzle 3 will be EOL
at some point in late 2015.
### About Guzzle 3
[![Composer Downloads](https://poser.pugx.org/guzzle/guzzle/d/total.png)](https://packagist.org/packages/guzzle/guzzle)
@@ -38,3 +41,17 @@ After installing, you need to require Composer's autoloader:
```php
require 'vendor/autoload.php';
```
## Known Issues
1. Problem following a specific redirect: https://github.com/guzzle/guzzle/issues/385.
This has been fixed in Guzzle 4/5.
2. Root XML attributes not serialized in a service description: https://github.com/guzzle/guzzle3/issues/5.
This has been fixed in Guzzle 4/5.
3. Accept-Encoding not preserved when following redirect: https://github.com/guzzle/guzzle3/issues/9
Fixed in Guzzle 4/5.
4. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10
Fixed in Guzzle 4/5.
5. Recursive model references with array items: https://github.com/guzzle/guzzle3/issues/13
Fixed in Guzzle 4/5
6. String "Array" Transmitted w/ PostFiles and Duplicate Aggregator: https://github.com/guzzle/guzzle3/issues/10
Fixed in Guzzle 4/5.

View File

@@ -1,7 +1,7 @@
{
"name": "guzzle/guzzle",
"type": "library",
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle",
"keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
"homepage": "http://guzzlephp.org/",
"license": "MIT",
@@ -56,6 +56,14 @@
}
},
"suggest": {
"guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated."
},
"scripts": {
"test": "phpunit"
},
"require-dev": {
"doctrine/cache": "~1.3",
"symfony/class-loader": "~2.1",

View File

@@ -71,7 +71,7 @@
"type": "number",
"description": "HTTP response status code of the error"
},
"phrase": {
"reason": {
"type": "string",
"description": "Response reason phrase or description of the error"
},

View File

@@ -99,7 +99,7 @@ endpoint and HTTP method. If an API has a ``DELETE /users/:id`` operation, a sat
"errorResponses": [
{
"code": 500,
"phrase": "Unexpected Error",
"reason": "Unexpected Error",
"class": "string"
}
],
@@ -125,7 +125,7 @@ endpoint and HTTP method. If an API has a ``DELETE /users/:id`` operation, a sat
"responseNotes", "string", "A description of the response returned by the operation"
"responseType", "string", "The type of response that the operation creates: one of primitive, class, model, or documentation. If not specified, this value will be automatically inferred based on whether or not there is a model matching the name, if a matching class name is found, or set to 'primitive' by default."
"deprecated", "boolean", "Whether or not the operation is deprecated"
"errorResponses", "array", "Errors that could occur while executing the operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), 'phrase' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this error is encountered)"
"errorResponses", "array", "Errors that could occur while executing the operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error), 'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this error is encountered)"
"data", "object", "Any arbitrary data to associate with the operation"
"parameters", "object containing :ref:`parameter-schema` objects", "Parameters of the operation. Parameters are used to define how input data is serialized into a HTTP request."
"additionalParameters", "A single :ref:`parameter-schema` object", "Validation and serialization rules for any parameter supplied to the operation that was not explicitly defined."
@@ -232,7 +232,7 @@ errorResponses
``errorResponses`` is an array containing objects that define the errors that could occur while executing the
operation. Each item of the array is an object that can contain a 'code' (HTTP response status code of the error),
'phrase' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this
'reason' (reason phrase or description of the error), and 'class' (an exception class that will be raised when this
error is encountered).
ErrorResponsePlugin
@@ -246,7 +246,7 @@ checked against the list of error responses for an exact match using the followi
1. Does the errorResponse have a defined ``class``?
2. Is the errorResponse ``code`` equal to the status code of the response?
3. Is the errorResponse ``phrase`` equal to the reason phrase of the response?
3. Is the errorResponse ``reason`` equal to the reason phrase of the response?
4. Throw the exception stored in the ``class`` attribute of the errorResponse.
The ``class`` attribute must point to a class that implements

View File

@@ -36,6 +36,6 @@ class DoctrineCacheAdapter extends AbstractCacheAdapter
public function save($id, $data, $lifeTime = false, array $options = null)
{
return $this->cache->save($id, $data, $lifeTime);
return $this->cache->save($id, $data, $lifeTime !== false ? $lifeTime : 0);
}
}

View File

@@ -7,7 +7,7 @@ namespace Guzzle\Common;
*/
class Version
{
const VERSION = '3.9.2';
const VERSION = '3.9.3';
/**
* @var bool Set this value to true to enable warnings for deprecated functionality use. This should be on in your

View File

@@ -193,7 +193,7 @@ class CurlHandle
}
// Add the content-length header back if it was temporarily removed
if ($tempContentLength) {
if (null !== $tempContentLength) {
$request->setHeader('Content-Length', $tempContentLength);
}

View File

@@ -2,6 +2,8 @@
namespace Guzzle\Http;
use Guzzle\Stream\StreamInterface;
/**
* EntityBody decorator used to return only a subset of an entity body
*/
@@ -30,7 +32,21 @@ class ReadLimitEntityBody extends AbstractEntityBodyDecorator
*/
public function __toString()
{
return substr((string) $this->body, $this->offset, $this->limit) ?: '';
if (!$this->body->isReadable() ||
(!$this->body->isSeekable() && $this->body->isConsumed())
) {
return '';
}
$originalPos = $this->body->ftell();
$this->body->seek($this->offset);
$data = '';
while (!$this->feof()) {
$data .= $this->read(1048576);
}
$this->body->seek($originalPos);
return (string) $data ?: '';
}
public function isConsumed()

View File

@@ -1,18 +1,21 @@
##
## ca-bundle.crt -- Bundle of CA Root Certificates
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Tue Apr 22 08:29:31 2014
## Certificate data from Mozilla downloaded on: Wed Aug 13 21:49:32 2014
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
## file (certdata.txt). This file can be found in the mozilla source tree:
## http://mxr.mozilla.org/mozilla-release/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl verison 1.22.
## SHA1: bf2c15b3019e696660321d2227d942936dc50aa7
##
GTE CyberTrust Global Root
@@ -3864,3 +3867,4 @@ TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----

View File

@@ -33,7 +33,7 @@ class MessageParser extends AbstractMessageParser
'body' => $parts['body']
);
$parsed['request_url'] = $this->getUrlPartsFromMessage($parts['start_line'][1], $parsed);
$parsed['request_url'] = $this->getUrlPartsFromMessage(isset($parts['start_line'][1]) ? $parts['start_line'][1] : '' , $parsed);
return $parsed;
}

View File

@@ -39,11 +39,26 @@ class DefaultCacheStorage implements CacheStorageInterface
public function cache(RequestInterface $request, Response $response)
{
$currentTime = time();
$ttl = $request->getParams()->get('cache.override_ttl') ?: $response->getMaxAge() ?: $this->defaultTtl;
$overrideTtl = $request->getParams()->get('cache.override_ttl');
if ($overrideTtl) {
$ttl = $overrideTtl;
} else {
$maxAge = $response->getMaxAge();
if ($maxAge !== null) {
$ttl = $maxAge;
} else {
$ttl = $this->defaultTtl;
}
}
if ($cacheControl = $response->getHeader('Cache-Control')) {
$stale = $cacheControl->getDirective('stale-if-error');
$ttl += $stale == true ? $ttl : $stale;
if ($stale === true) {
$ttl += $ttl;
} else if (is_numeric($stale)) {
$ttl += $stale;
}
}
// Determine which manifest key should be used

View File

@@ -86,7 +86,7 @@ class Operation implements OperationInterface
* name, if a matching PSR-0 compliant class name is found, or set to 'primitive' by default.
* - deprecated: (bool) Set to true if this is a deprecated command
* - errorResponses: (array) Errors that could occur when executing the command. Array of hashes, each with a
* 'code' (the HTTP response code), 'phrase' (response reason phrase or description of the
* 'code' (the HTTP response code), 'reason' (response reason phrase or description of the
* error), and 'class' (a custom exception class that would be thrown if the error is
* encountered).
* - data: (array) Any extra data that might be used to help build or serialize the operation

View File

@@ -257,16 +257,24 @@ class PhpStreamRequestFactory implements StreamRequestFactoryInterface
*/
protected function createResource($callback)
{
// Turn off error reporting while we try to initiate the request
$level = error_reporting(0);
$errors = null;
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
$errors[] = array(
'message' => $msg,
'file' => $file,
'line' => $line
);
return true;
});
$resource = call_user_func($callback);
error_reporting($level);
restore_error_handler();
// If the resource could not be created, then grab the last error and throw an exception
if (false === $resource) {
if (!$resource) {
$message = 'Error creating resource. ';
foreach (error_get_last() as $key => $value) {
$message .= "[{$key}] {$value} ";
foreach ($errors as $err) {
foreach ($err as $key => $value) {
$message .= "[$key] $value" . PHP_EOL;
}
}
throw new RuntimeException(trim($message));
}

View File

@@ -1,5 +1,39 @@
# CHANGELOG
## 1.3.0 - 2016-04-13
* Added remaining interfaces needed for full PSR7 compatibility
(ServerRequestInterface, UploadedFileInterface, etc.).
* Added support for stream_for from scalars.
* Can now extend Uri.
* Fixed a bug in validating request methods by making it more permissive.
## 1.2.3 - 2016-02-18
* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
streams, which can sometimes return fewer bytes than requested with `fread`.
* Fixed handling of gzipped responses with FNAME headers.
## 1.2.2 - 2016-01-22
* Added support for URIs without any authority.
* Added support for HTTP 451 'Unavailable For Legal Reasons.'
* Added support for using '0' as a filename.
* Added support for including non-standard ports in Host headers.
## 1.2.1 - 2015-11-02
* Now supporting negative offsets when seeking to SEEK_END.
## 1.2.0 - 2015-08-15
* Body as `"0"` is now properly added to a response.
* Now allowing forward seeking in CachingStream.
* Now properly parsing HTTP requests that contain proxy targets in
`parse_request`.
* functions.php is now conditionally required.
* user-info is no longer dropped when resolving URIs.
## 1.1.0 - 2015-06-24
* URIs can now be relative.

View File

@@ -9,5 +9,21 @@ coverage:
view-coverage:
open artifacts/coverage/index.html
check-tag:
$(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
tag: check-tag
@echo Tagging $(TAG)
chag update $(TAG)
git commit -a -m '$(TAG) release'
chag tag
@echo "Release has been created. Push using 'make release'"
@echo "Changes made in the release commit"
git diff HEAD~1 HEAD
release: check-tag
git push origin master
git push origin $(TAG)
clean:
rm -rf artifacts/*

View File

@@ -6,6 +6,9 @@ functionality like query string parsing. Currently missing
ServerRequestInterface and UploadedFileInterface; a pull request for these features is welcome.
[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7)
# Stream implementation
This package comes with a number of stream implementations and stream
@@ -25,7 +28,7 @@ $a = Psr7\stream_for('abc, ');
$b = Psr7\stream_for('123.');
$composed = new Psr7\AppendStream([$a, $b]);
$composed->addStream(Psr7\stream_for(' Above all listen to me').
$composed->addStream(Psr7\stream_for(' Above all listen to me'));
echo $composed(); // abc, 123. Above all listen to me.
```
@@ -35,7 +38,7 @@ echo $composed(); // abc, 123. Above all listen to me.
`GuzzleHttp\Psr7\BufferStream`
Provides a buffer stream that can be written to to fill a buffer, and read
Provides a buffer stream that can be written to fill a buffer, and read
from to remove bytes from the buffer.
This stream returns a "hwm" metadata value that tells upstream consumers
@@ -92,7 +95,7 @@ $stream = Psr7\stream_for();
// Start dropping data when the stream has more than 10 bytes
$dropping = new Psr7\DroppingStream($stream, 10);
$stream->write('01234567890123456789');
$dropping->write('01234567890123456789');
echo $stream; // 0123456789
```
@@ -103,7 +106,7 @@ echo $stream; // 0123456789
Compose stream implementations based on a hash of functions.
Allows for easy testing and extension of a provided stream without needing to
Allows for easy testing and extension of a provided stream without needing
to create a concrete class for a simple extension point.
```php

View File

@@ -25,7 +25,7 @@
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": ["src/functions.php"]
"files": ["src/functions_include.php"]
},
"extra": {
"branch-alias": {

View File

@@ -41,30 +41,35 @@ class CachingStream implements StreamInterface
$this->seek(0);
}
/**
* {@inheritdoc}
* @throws \RuntimeException When seeking with SEEK_END or when seeking
* past the total size of the buffer stream
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($whence == SEEK_SET) {
$byte = $offset;
} elseif ($whence == SEEK_CUR) {
$byte = $offset + $this->tell();
} elseif ($whence == SEEK_END) {
$size = $this->remoteStream->getSize();
if ($size === null) {
$size = $this->cacheEntireStream();
}
$byte = $size + $offset;
} else {
throw new \RuntimeException('CachingStream::seek() supports SEEK_SET and SEEK_CUR');
throw new \InvalidArgumentException('Invalid whence');
}
// You cannot skip ahead past where you've read from the remote stream
if ($byte > $this->stream->getSize()) {
throw new \RuntimeException(
sprintf('Cannot seek to byte %d when the buffered stream only'
. ' contains %d bytes', $byte, $this->stream->getSize())
);
}
$diff = $byte - $this->stream->getSize();
$this->stream->seek($byte);
if ($diff > 0) {
// Read the remoteStream until we have read in at least the amount
// of bytes requested, or we reach the end of the file.
while ($diff > 0 && !$this->remoteStream->eof()) {
$this->read($diff);
$diff = $byte - $this->stream->getSize();
}
} else {
// We can just do a normal seek since we've already seen this byte.
$this->stream->seek($byte);
}
}
public function read($length)
@@ -122,4 +127,12 @@ class CachingStream implements StreamInterface
{
$this->remoteStream->close() && $this->stream->close();
}
private function cacheEntireStream()
{
$target = new FnStream(['write' => 'strlen']);
copy_to_stream($this, $target);
return $this->tell();
}
}

View File

@@ -20,10 +20,33 @@ class InflateStream implements StreamInterface
public function __construct(StreamInterface $stream)
{
// Skip the first 10 bytes
$stream = new LimitStream($stream, -1, 10);
// read the first 10 bytes, ie. gzip header
$header = $stream->read(10);
$filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
// Skip the header, that is 10 + length of filename + 1 (nil) bytes
$stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
$resource = StreamWrapper::getResource($stream);
stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
$this->stream = new Stream($resource);
}
/**
* @param StreamInterface $stream
* @param $header
* @return int
*/
private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
{
$filename_header_length = 0;
if (substr(bin2hex($header), 6, 2) === '08') {
// we have a filename, read until nil
$filename_header_length = 1;
while ($stream->read(1) !== chr(0)) {
$filename_header_length++;
}
}
return $filename_header_length;
}
}

View File

@@ -113,7 +113,7 @@ class MultipartStream implements StreamInterface
// Set a default content-disposition header if one was no provided
$disposition = $this->getHeader($headers, 'content-disposition');
if (!$disposition) {
$headers['Content-Disposition'] = $filename
$headers['Content-Disposition'] = ($filename === '0' || $filename)
? sprintf('form-data; name="%s"; filename="%s"',
$name,
basename($filename))
@@ -130,7 +130,7 @@ class MultipartStream implements StreamInterface
// Set a default Content-Type if one was not supplied
$type = $this->getHeader($headers, 'content-type');
if (!$type && $filename) {
if (!$type && ($filename === '0' || $filename)) {
if ($type = mimetype_from_filename($filename)) {
$headers['Content-Type'] = $type;
}

View File

@@ -26,8 +26,8 @@ class Request implements RequestInterface
/**
* @param null|string $method HTTP method for the request.
* @param null|string $uri URI for the request.
* @param array $headers Headers for the message.
* @param null|string|UriInterface $uri URI for the request.
* @param array $headers Headers for the message.
* @param string|resource|StreamInterface $body Message body.
* @param string $protocolVersion HTTP protocol version.
*

View File

@@ -59,6 +59,7 @@ class Response implements ResponseInterface
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
@@ -93,7 +94,7 @@ class Response implements ResponseInterface
) {
$this->statusCode = (int) $status;
if ($body) {
if ($body !== null) {
$this->stream = stream_for($body);
}

View File

@@ -0,0 +1,348 @@
<?php
namespace GuzzleHttp\Psr7;
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
/**
* Server-side HTTP request
*
* Extends the Request definition to add methods for accessing incoming data,
* specifically server parameters, cookies, matched path parameters, query
* string arguments, body parameters, and upload file information.
*
* "Attributes" are discovered via decomposing the request (and usually
* specifically the URI path), and typically will be injected by the application.
*
* Requests are considered immutable; all methods that might change state are
* implemented such that they retain the internal state of the current
* message and return a new instance that contains the changed state.
*/
class ServerRequest extends Request implements ServerRequestInterface
{
/**
* @var array
*/
private $attributes = [];
/**
* @var array
*/
private $cookieParams = [];
/**
* @var null|array|object
*/
private $parsedBody;
/**
* @var array
*/
private $queryParams = [];
/**
* @var array
*/
private $serverParams;
/**
* @var array
*/
private $uploadedFiles = [];
/**
* @param null|string $method HTTP method for the request
* @param null|string|UriInterface $uri URI for the request
* @param array $headers Headers for the message
* @param string|resource|StreamInterface $body Message body
* @param string $protocolVersion HTTP protocol version
* @param array $serverParams the value of $_SERVER superglobal
*
* @throws InvalidArgumentException for an invalid URI
*/
public function __construct(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1',
array $serverParams = []
) {
$this->serverParams = $serverParams;
parent::__construct($method, $uri, $headers, $body, $protocolVersion);
}
/**
* Return an UploadedFile instance array.
*
* @param array $files A array which respect $_FILES structure
* @throws InvalidArgumentException for unrecognized values
* @return array
*/
public static function normalizeFiles(array $files)
{
$normalized = [];
foreach ($files as $key => $value) {
if ($value instanceof UploadedFileInterface) {
$normalized[$key] = $value;
} elseif (is_array($value) && isset($value['tmp_name'])) {
$normalized[$key] = self::createUploadedFileFromSpec($value);
} elseif (is_array($value)) {
$normalized[$key] = self::normalizeFiles($value);
continue;
} else {
throw new InvalidArgumentException('Invalid value in files specification');
}
}
return $normalized;
}
/**
* Create and return an UploadedFile instance from a $_FILES specification.
*
* If the specification represents an array of values, this method will
* delegate to normalizeNestedFileSpec() and return that return value.
*
* @param array $value $_FILES struct
* @return array|UploadedFileInterface
*/
private static function createUploadedFileFromSpec(array $value)
{
if (is_array($value['tmp_name'])) {
return self::normalizeNestedFileSpec($value);
}
return new UploadedFile(
$value['tmp_name'],
(int) $value['size'],
(int) $value['error'],
$value['name'],
$value['type']
);
}
/**
* Normalize an array of file specifications.
*
* Loops through all nested files and returns a normalized array of
* UploadedFileInterface instances.
*
* @param array $files
* @return UploadedFileInterface[]
*/
private static function normalizeNestedFileSpec(array $files = [])
{
$normalizedFiles = [];
foreach (array_keys($files['tmp_name']) as $key) {
$spec = [
'tmp_name' => $files['tmp_name'][$key],
'size' => $files['size'][$key],
'error' => $files['error'][$key],
'name' => $files['name'][$key],
'type' => $files['type'][$key],
];
$normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
}
return $normalizedFiles;
}
/**
* Return a ServerRequest populated with superglobals:
* $_GET
* $_POST
* $_COOKIE
* $_FILES
* $_SERVER
*
* @return ServerRequestInterface
*/
public static function fromGlobals()
{
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
$headers = function_exists('getallheaders') ? getallheaders() : [];
$uri = self::getUriFromGlobals();
$body = new LazyOpenStream('php://input', 'r+');
$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
$serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
return $serverRequest
->withCookieParams($_COOKIE)
->withQueryParams($_GET)
->withParsedBody($_POST)
->withUploadedFiles(self::normalizeFiles($_FILES));
}
/**
* Get a Uri populated with values from $_SERVER.
*
* @return UriInterface
*/
public static function getUriFromGlobals() {
$uri = new Uri('');
if (isset($_SERVER['HTTPS'])) {
$uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
}
if (isset($_SERVER['HTTP_HOST'])) {
$uri = $uri->withHost($_SERVER['HTTP_HOST']);
} elseif (isset($_SERVER['SERVER_NAME'])) {
$uri = $uri->withHost($_SERVER['SERVER_NAME']);
}
if (isset($_SERVER['SERVER_PORT'])) {
$uri = $uri->withPort($_SERVER['SERVER_PORT']);
}
if (isset($_SERVER['REQUEST_URI'])) {
$uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
}
if (isset($_SERVER['QUERY_STRING'])) {
$uri = $uri->withQuery($_SERVER['QUERY_STRING']);
}
return $uri;
}
/**
* {@inheritdoc}
*/
public function getServerParams()
{
return $this->serverParams;
}
/**
* {@inheritdoc}
*/
public function getUploadedFiles()
{
return $this->uploadedFiles;
}
/**
* {@inheritdoc}
*/
public function withUploadedFiles(array $uploadedFiles)
{
$new = clone $this;
$new->uploadedFiles = $uploadedFiles;
return $new;
}
/**
* {@inheritdoc}
*/
public function getCookieParams()
{
return $this->cookieParams;
}
/**
* {@inheritdoc}
*/
public function withCookieParams(array $cookies)
{
$new = clone $this;
$new->cookieParams = $cookies;
return $new;
}
/**
* {@inheritdoc}
*/
public function getQueryParams()
{
return $this->queryParams;
}
/**
* {@inheritdoc}
*/
public function withQueryParams(array $query)
{
$new = clone $this;
$new->queryParams = $query;
return $new;
}
/**
* {@inheritdoc}
*/
public function getParsedBody()
{
return $this->parsedBody;
}
/**
* {@inheritdoc}
*/
public function withParsedBody($data)
{
$new = clone $this;
$new->parsedBody = $data;
return $new;
}
/**
* {@inheritdoc}
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* {@inheritdoc}
*/
public function getAttribute($attribute, $default = null)
{
if (false === array_key_exists($attribute, $this->attributes)) {
return $default;
}
return $this->attributes[$attribute];
}
/**
* {@inheritdoc}
*/
public function withAttribute($attribute, $value)
{
$new = clone $this;
$new->attributes[$attribute] = $value;
return $new;
}
/**
* {@inheritdoc}
*/
public function withoutAttribute($attribute)
{
if (false === isset($this->attributes[$attribute])) {
return clone $this;
}
$new = clone $this;
unset($new->attributes[$attribute]);
return $new;
}
}

View File

@@ -38,7 +38,7 @@ class Stream implements StreamInterface
* This constructor accepts an associative array of options.
*
* - size: (int) If a read stream would otherwise have an indeterminate
* size, but the size is known due to foreknownledge, then you can
* size, but the size is known due to foreknowledge, then you can
* provide that size, in bytes.
* - metadata: (array) Any additional metadata to return when the metadata
* of the stream is accessed.

View File

@@ -0,0 +1,316 @@
<?php
namespace GuzzleHttp\Psr7;
use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;
class UploadedFile implements UploadedFileInterface
{
/**
* @var int[]
*/
private static $errors = [
UPLOAD_ERR_OK,
UPLOAD_ERR_INI_SIZE,
UPLOAD_ERR_FORM_SIZE,
UPLOAD_ERR_PARTIAL,
UPLOAD_ERR_NO_FILE,
UPLOAD_ERR_NO_TMP_DIR,
UPLOAD_ERR_CANT_WRITE,
UPLOAD_ERR_EXTENSION,
];
/**
* @var string
*/
private $clientFilename;
/**
* @var string
*/
private $clientMediaType;
/**
* @var int
*/
private $error;
/**
* @var null|string
*/
private $file;
/**
* @var bool
*/
private $moved = false;
/**
* @var int
*/
private $size;
/**
* @var StreamInterface|null
*/
private $stream;
/**
* @param StreamInterface|string|resource $streamOrFile
* @param int $size
* @param int $errorStatus
* @param string|null $clientFilename
* @param string|null $clientMediaType
*/
public function __construct(
$streamOrFile,
$size,
$errorStatus,
$clientFilename = null,
$clientMediaType = null
) {
$this->setError($errorStatus);
$this->setSize($size);
$this->setClientFilename($clientFilename);
$this->setClientMediaType($clientMediaType);
if ($this->isOk()) {
$this->setStreamOrFile($streamOrFile);
}
}
/**
* Depending on the value set file or stream variable
*
* @param mixed $streamOrFile
* @throws InvalidArgumentException
*/
private function setStreamOrFile($streamOrFile)
{
if (is_string($streamOrFile)) {
$this->file = $streamOrFile;
} elseif (is_resource($streamOrFile)) {
$this->stream = new Stream($streamOrFile);
} elseif ($streamOrFile instanceof StreamInterface) {
$this->stream = $streamOrFile;
} else {
throw new InvalidArgumentException(
'Invalid stream or file provided for UploadedFile'
);
}
}
/**
* @param int $error
* @throws InvalidArgumentException
*/
private function setError($error)
{
if (false === is_int($error)) {
throw new InvalidArgumentException(
'Upload file error status must be an integer'
);
}
if (false === in_array($error, UploadedFile::$errors)) {
throw new InvalidArgumentException(
'Invalid error status for UploadedFile'
);
}
$this->error = $error;
}
/**
* @param int $size
* @throws InvalidArgumentException
*/
private function setSize($size)
{
if (false === is_int($size)) {
throw new InvalidArgumentException(
'Upload file size must be an integer'
);
}
$this->size = $size;
}
/**
* @param mixed $param
* @return boolean
*/
private function isStringOrNull($param)
{
return in_array(gettype($param), ['string', 'NULL']);
}
/**
* @param mixed $param
* @return boolean
*/
private function isStringNotEmpty($param)
{
return is_string($param) && false === empty($param);
}
/**
* @param string|null $clientFilename
* @throws InvalidArgumentException
*/
private function setClientFilename($clientFilename)
{
if (false === $this->isStringOrNull($clientFilename)) {
throw new InvalidArgumentException(
'Upload file client filename must be a string or null'
);
}
$this->clientFilename = $clientFilename;
}
/**
* @param string|null $clientMediaType
* @throws InvalidArgumentException
*/
private function setClientMediaType($clientMediaType)
{
if (false === $this->isStringOrNull($clientMediaType)) {
throw new InvalidArgumentException(
'Upload file client media type must be a string or null'
);
}
$this->clientMediaType = $clientMediaType;
}
/**
* Return true if there is no upload error
*
* @return boolean
*/
private function isOk()
{
return $this->error === UPLOAD_ERR_OK;
}
/**
* @return boolean
*/
public function isMoved()
{
return $this->moved;
}
/**
* @throws RuntimeException if is moved or not ok
*/
private function validateActive()
{
if (false === $this->isOk()) {
throw new RuntimeException('Cannot retrieve stream due to upload error');
}
if ($this->isMoved()) {
throw new RuntimeException('Cannot retrieve stream after it has already been moved');
}
}
/**
* {@inheritdoc}
* @throws RuntimeException if the upload was not successful.
*/
public function getStream()
{
$this->validateActive();
if ($this->stream instanceof StreamInterface) {
return $this->stream;
}
return new LazyOpenStream($this->file, 'r+');
}
/**
* {@inheritdoc}
*
* @see http://php.net/is_uploaded_file
* @see http://php.net/move_uploaded_file
* @param string $targetPath Path to which to move the uploaded file.
* @throws RuntimeException if the upload was not successful.
* @throws InvalidArgumentException if the $path specified is invalid.
* @throws RuntimeException on any error during the move operation, or on
* the second or subsequent call to the method.
*/
public function moveTo($targetPath)
{
$this->validateActive();
if (false === $this->isStringNotEmpty($targetPath)) {
throw new InvalidArgumentException(
'Invalid path provided for move operation; must be a non-empty string'
);
}
if ($this->file) {
$this->moved = php_sapi_name() == 'cli'
? rename($this->file, $targetPath)
: move_uploaded_file($this->file, $targetPath);
} else {
copy_to_stream(
$this->getStream(),
new LazyOpenStream($targetPath, 'w')
);
$this->moved = true;
}
if (false === $this->moved) {
throw new RuntimeException(
sprintf('Uploaded file could not be moved to %s', $targetPath)
);
}
}
/**
* {@inheritdoc}
*
* @return int|null The file size in bytes or null if unknown.
*/
public function getSize()
{
return $this->size;
}
/**
* {@inheritdoc}
*
* @see http://php.net/manual/en/features.file-upload.errors.php
* @return int One of PHP's UPLOAD_ERR_XXX constants.
*/
public function getError()
{
return $this->error;
}
/**
* {@inheritdoc}
*
* @return string|null The filename sent by the client or null if none
* was provided.
*/
public function getClientFilename()
{
return $this->clientFilename;
}
/**
* {@inheritdoc}
*/
public function getClientMediaType()
{
return $this->clientMediaType;
}
}

View File

@@ -123,44 +123,33 @@ class Uri implements UriInterface
return $base;
}
if ($rel instanceof UriInterface) {
$relParts = [
'scheme' => $rel->getScheme(),
'host' => $rel->getHost(),
'port' => $rel->getPort(),
'path' => $rel->getPath(),
'query' => $rel->getQuery(),
'fragment' => $rel->getFragment()
];
} else {
$relParts = parse_url($rel) + [
'scheme' => '',
'host' => '',
'port' => '',
'path' => '',
'query' => '',
'fragment' => ''
];
if (!($rel instanceof UriInterface)) {
$rel = new self($rel);
}
if (!empty($relParts['scheme']) && !empty($relParts['host'])) {
return $rel instanceof UriInterface
? $rel
: self::fromParts($relParts);
// Return the relative uri as-is if it has a scheme.
if ($rel->getScheme()) {
return $rel->withPath(static::removeDotSegments($rel->getPath()));
}
$parts = [
'scheme' => $base->getScheme(),
'host' => $base->getHost(),
'port' => $base->getPort(),
'path' => $base->getPath(),
'query' => $base->getQuery(),
'fragment' => $base->getFragment()
$relParts = [
'scheme' => $rel->getScheme(),
'authority' => $rel->getAuthority(),
'path' => $rel->getPath(),
'query' => $rel->getQuery(),
'fragment' => $rel->getFragment()
];
if (!empty($relParts['host'])) {
$parts['host'] = $relParts['host'];
$parts['port'] = $relParts['port'];
$parts = [
'scheme' => $base->getScheme(),
'authority' => $base->getAuthority(),
'path' => $base->getPath(),
'query' => $base->getQuery(),
'fragment' => $base->getFragment()
];
if (!empty($relParts['authority'])) {
$parts['authority'] = $relParts['authority'];
$parts['path'] = self::removeDotSegments($relParts['path']);
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
@@ -170,7 +159,7 @@ class Uri implements UriInterface
$parts['query'] = $relParts['query'];
$parts['fragment'] = $relParts['fragment'];
} else {
if (!empty($parts['host']) && empty($parts['path'])) {
if (!empty($parts['authority']) && empty($parts['path'])) {
$mergedPath = '/';
} else {
$mergedPath = substr($parts['path'], 0, strrpos($parts['path'], '/') + 1);
@@ -185,7 +174,13 @@ class Uri implements UriInterface
$parts['fragment'] = $relParts['fragment'];
}
return static::fromParts($parts);
return new self(self::createUriString(
$parts['scheme'],
$parts['authority'],
$parts['path'],
$parts['query'],
$parts['fragment']
));
}
/**
@@ -482,21 +477,28 @@ class Uri implements UriInterface
$uri = '';
if (!empty($scheme)) {
$uri .= $scheme . '://';
$uri .= $scheme . ':';
}
$hierPart = '';
if (!empty($authority)) {
$uri .= $authority;
if (!empty($scheme)) {
$hierPart .= '//';
}
$hierPart .= $authority;
}
if ($path != null) {
// Add a leading slash if necessary.
if ($uri && substr($path, 0, 1) !== '/') {
$uri .= '/';
if ($hierPart && substr($path, 0, 1) !== '/') {
$hierPart .= '/';
}
$uri .= $path;
$hierPart .= $path;
}
$uri .= $hierPart;
if ($query != null) {
$uri .= '?' . $query;
}
@@ -526,7 +528,7 @@ class Uri implements UriInterface
return false;
}
return !isset(static::$schemes[$scheme]) || $port !== static::$schemes[$scheme];
return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
}
/**

View File

@@ -68,22 +68,24 @@ function uri_for($uri)
* - metadata: Array of custom metadata.
* - size: Size of the stream.
*
* @param resource|string|StreamInterface $resource Entity body data
* @param array $options Additional options
* @param resource|int|string|float|bool|StreamInterface $resource Entity body data
* @param array $options Additional options
*
* @return Stream
* @throws \InvalidArgumentException if the $resource arg is not valid.
*/
function stream_for($resource = '', array $options = [])
{
if (is_scalar($resource)) {
$stream = fopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
}
return new Stream($stream, $options);
}
switch (gettype($resource)) {
case 'string':
$stream = fopen('php://temp', 'r+');
if ($resource !== '') {
fwrite($stream, $resource);
fseek($stream, 0);
}
return new Stream($stream, $options);
case 'resource':
return new Stream($resource, $options);
case 'object':
@@ -209,6 +211,14 @@ function modify_request(RequestInterface $request, array $changes)
// Remove the host header if one is on the URI
if ($host = $changes['uri']->getHost()) {
$changes['set_headers']['Host'] = $host;
if ($port = $changes['uri']->getPort()) {
$standardPorts = ['http' => 80, 'https' => 443];
$scheme = $changes['uri']->getScheme();
if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
$changes['set_headers']['Host'] .= ':'.$port;
}
}
}
$uri = $changes['uri'];
}
@@ -440,19 +450,22 @@ function readline(StreamInterface $stream, $maxLength = null)
function parse_request($message)
{
$data = _parse_message($message);
if (!preg_match('/^[a-zA-Z]+\s+\/.*/', $data['start-line'])) {
$matches = [];
if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
throw new \InvalidArgumentException('Invalid request string');
}
$parts = explode(' ', $data['start-line'], 3);
$version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
return new Request(
$request = new Request(
$parts[0],
_parse_request_uri($parts[1], $data['headers']),
$matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
$data['headers'],
$data['body'],
$version
);
return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
}
/**

View File

@@ -0,0 +1,6 @@
<?php
// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\Psr7\str')) {
require __DIR__ . '/functions.php';
}

View File

@@ -32,22 +32,43 @@ class CachingStreamTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(4, $caching->getSize());
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Cannot seek to byte 10
*/
public function testCannotSeekPastWhatHasBeenRead()
public function testReadsUntilCachedToByte()
{
$this->body->seek(10);
$this->body->seek(5);
$this->assertEquals('n', $this->body->read(1));
$this->body->seek(0);
$this->assertEquals('t', $this->body->read(1));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage CachingStream::seek() supports SEEK_SET and SEEK_CUR
*/
public function testCannotUseSeekEnd()
public function testCanSeekNearEndWithSeekEnd()
{
$this->body->seek(2, SEEK_END);
$baseStream = Psr7\stream_for(implode('', range('a', 'z')));
$cached = new CachingStream($baseStream);
$cached->seek(-1, SEEK_END);
$this->assertEquals(25, $baseStream->tell());
$this->assertEquals('z', $cached->read(1));
$this->assertEquals(26, $cached->getSize());
}
public function testCanSeekToEndWithSeekEnd()
{
$baseStream = Psr7\stream_for(implode('', range('a', 'z')));
$cached = new CachingStream($baseStream);
$cached->seek(0, SEEK_END);
$this->assertEquals(26, $baseStream->tell());
$this->assertEquals('', $cached->read(1));
$this->assertEquals(26, $cached->getSize());
}
public function testCanUseSeekEndWithUnknownSize()
{
$baseStream = Psr7\stream_for('testing');
$decorated = Psr7\FnStream::decorate($baseStream, [
'getSize' => function () { return null; }
]);
$cached = new CachingStream($decorated);
$cached->seek(-1, SEEK_END);
$this->assertEquals('g', $cached->read(1));
}
public function testRewindUsesSeek()
@@ -77,6 +98,33 @@ class CachingStreamTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('ing', $this->body->read(3));
}
public function testCanSeekToReadBytesWithPartialBodyReturned()
{
$stream = fopen('php://temp', 'r+');
fwrite($stream, 'testing');
fseek($stream, 0);
$this->decorated = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream')
->setConstructorArgs([$stream])
->setMethods(['read'])
->getMock();
$this->decorated->expects($this->exactly(2))
->method('read')
->willReturnCallback(function($length) use ($stream){
return fread($stream, 2);
});
$this->body = new CachingStream($this->decorated);
$this->assertEquals(0, $this->body->tell());
$this->body->seek(4, SEEK_SET);
$this->assertEquals(4, $this->body->tell());
$this->body->seek(0);
$this->assertEquals('test', $this->body->read(4));
}
public function testWritesToBufferStream()
{
$this->body->read(2);
@@ -134,4 +182,12 @@ class CachingStreamTest extends \PHPUnit_Framework_TestCase
$d->close();
$this->assertFalse(is_resource($s));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testEnsuresValidWhence()
{
$this->body->seek(10, -123456);
}
}

View File

@@ -270,6 +270,25 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('http://foo.com/', (string) $request->getUri());
}
public function testParsesRequestMessagesWithFullUri()
{
$req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
$request = Psr7\parse_request($req);
$this->assertEquals('GET', $request->getMethod());
$this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
$this->assertEquals('', (string) $request->getBody());
$this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
}
public function testParsesRequestMessagesWithCustomMethod()
{
$req = "GET_DATA / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
$request = Psr7\parse_request($req);
$this->assertEquals('GET_DATA', $request->getMethod());
}
/**
* @expectedException \InvalidArgumentException
*/
@@ -534,6 +553,16 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
}
public function testCanModifyRequestWithUriAndPort()
{
$r1 = new Psr7\Request('GET', 'http://foo.com:8000');
$r2 = Psr7\modify_request($r1, [
'uri' => new Psr7\Uri('http://www.foo.com:8000')
]);
$this->assertEquals('http://www.foo.com:8000', (string) $r2->getUri());
$this->assertEquals('www.foo.com:8000', (string) $r2->getHeaderLine('host'));
}
public function testCanModifyRequestWithCaseInsensitiveHeader()
{
$r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']);

Some files were not shown because too many files have changed in this diff Show More