@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Balanced Payments API

Summary: Adds the Balanced PHP API to externals/. Ref T2787.

Test Plan: Used in next diff.

Reviewers: btrahan, chad

Reviewed By: chad

CC: aran, aurelijus

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D5764

+7994
+14
externals/balanced-php/.gitignore
··· 1 + # composer 2 + .buildpath 3 + composer.lock 4 + composer.phar 5 + vendor 6 + *~ 7 + *# 8 + # phar 9 + *.phar 10 + 11 + # eclipse-pdt 12 + .settings 13 + .project 14 + *.iml
+8
externals/balanced-php/.travis.yml
··· 1 + language: php 2 + before_script: 3 + - curl -s http://getcomposer.org/installer | php 4 + - php composer.phar install 5 + script: phpunit --bootstrap vendor/autoload.php --exclude-group suite tests/ 6 + php: 7 + - 5.3 8 + - 5.4
+22
externals/balanced-php/LICENSE
··· 1 + Copyright (c) 2012 Balanced 2 + 3 + MIT License 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining 6 + a copy of this software and associated documentation files (the 7 + "Software"), to deal in the Software without restriction, including 8 + without limitation the rights to use, copy, modify, merge, publish, 9 + distribute, sublicense, and/or sell copies of the Software, and to 10 + permit persons to whom the Software is furnished to do so, subject to 11 + the following conditions: 12 + 13 + The above copyright notice and this permission notice shall be 14 + included in all copies or substantial portions of the Software. 15 + 16 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+156
externals/balanced-php/README.md
··· 1 + # Balanced 2 + 3 + Online Marketplace Payments 4 + 5 + [![Build Status](https://secure.travis-ci.org/balanced/balanced-php.png)](http://travis-ci.org/balanced/balanced-php) 6 + 7 + The design of this library was heavily influenced by [Httpful](https://github.com/nategood/httpful). 8 + 9 + ## Requirements 10 + 11 + - [PHP](http://www.php.net) >= 5.3 **with** [cURL](http://www.php.net/manual/en/curl.installation.php) 12 + - [RESTful](https://github.com/bninja/restful) >= 0.1 13 + - [Httpful](https://github.com/nategood/httpful) >= 0.1 14 + 15 + ## Issues 16 + 17 + Please use appropriately tagged github [issues](https://github.com/balanced/balanced-php/issues) to request features or report bugs. 18 + 19 + ## Installation 20 + 21 + You can install using [composer](#composer), a [phar](#phar) package or from [source](#source). Note that Balanced is [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) compliant: 22 + 23 + ### Composer 24 + 25 + If you don't have Composer [install](http://getcomposer.org/doc/00-intro.md#installation) it: 26 + 27 + $ curl -s https://getcomposer.org/installer | php 28 + 29 + Add this to your `composer.json`: 30 + 31 + { 32 + "require": { 33 + "balanced/balanced": "*" 34 + } 35 + } 36 + 37 + Refresh your dependencies: 38 + 39 + $ php composer.phar update 40 + 41 + 42 + Then make sure to `require` the autoloader and initialize all: 43 + 44 + <?php 45 + require(__DIR__ . '/vendor/autoload.php'); 46 + 47 + \Httpful\Bootstrap::init(); 48 + \RESTful\Bootstrap::init(); 49 + \Balanced\Bootstrap::init(); 50 + ... 51 + 52 + ### Phar 53 + 54 + Download an Httpful [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/nategood/httpful/downloads): 55 + 56 + $ curl -s -L -o httpful.phar https://github.com/downloads/nategood/httpful/httpful.phar 57 + 58 + Download a RESTful [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/bninja/restful/downloads): 59 + 60 + $ curl -s -L -o restful.phar https://github.com/bninja/restful/downloads/restful.phar 61 + 62 + Download a Balanced [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/balanced/balanced-php/downloads): 63 + 64 + $ curl -s -L -o balanced.phar https://github.com/balanced/balanced-php/downloads/balanced-{VERSION}.phar 65 + 66 + And then `include` all: 67 + 68 + <?php 69 + include(__DIR__ . '/httpful.phar'); 70 + include(__DIR__ . '/restful.phar'); 71 + include(__DIR__ . '/balanced.phar'); 72 + ... 73 + 74 + ### Source 75 + 76 + Download [Httpful](https://github.com/nategood/httpful) source: 77 + 78 + $ curl -s -L -o httpful.zip https://github.com/nategood/httpful/zipball/master; 79 + $ unzip httpful.zip; mv nategood-httpful* httpful; rm httpful.zip 80 + 81 + Download [RESTful](https://github.com/bninja/restful) source: 82 + 83 + $ curl -s -L -o restful.zip https://github.com/bninja/restful/zipball/master; 84 + $ unzip restful.zip; mv bninja-restful* restful; rm restful.zips 85 + 86 + Download the Balanced source: 87 + 88 + $ curl -s -L -o balanced.zip https://github.com/balanced/balanced-php/zipball/master 89 + $ unzip balanced.zip; mv balanced-balanced-php-* balanced; rm balanced.zip 90 + 91 + And then `require` all bootstrap files: 92 + 93 + <?php 94 + require(__DIR__ . "/httpful/bootstrap.php") 95 + require(__DIR__ . "/restful/bootstrap.php") 96 + require(__DIR__ . "/balanced/bootstrap.php") 97 + ... 98 + 99 + ## Quickstart 100 + 101 + curl -s http://getcomposer.org/installer | php 102 + 103 + echo '{ 104 + "require": { 105 + "balanced/balanced": "*" 106 + } 107 + }' > composer.json 108 + 109 + php composer.phar install 110 + 111 + curl https://raw.github.com/balanced/balanced-php/master/example/example.php > example.php 112 + 113 + php example.php 114 + 115 + curl https://raw.github.com/balanced/balanced-php/master/example/buyer-example.php > buyer-example.php 116 + 117 + php -S 127.0.0.1:9321 buyer-example.php 118 + # now open a browser and go to http://127.0.0.1:9321/ to view how to tokenize cards and add to a buyer 119 + 120 + ## Usage 121 + 122 + See https://www.balancedpayments.com/docs/overview?language=php for tutorials and documentation. 123 + 124 + ## Testing 125 + 126 + $ phpunit --bootstrap vendor/autoload.php tests/ 127 + 128 + Or if you'd like to skip network calls: 129 + 130 + $ phpunit --exclude-group suite --bootstrap vendor/autoload.php tests/ 131 + 132 + ## Publishing 133 + 134 + 1. Ensure that **all** [tests](#testing) pass 135 + 2. Increment minor `VERSION` in `src/Balanced/Settings` and `composer.json` (`git commit -am 'v{VERSION} release'`) 136 + 3. Tag it (`git tag -a v{VERSION} -m 'v{VERSION} release'`) 137 + 4. Push the tag (`git push --tag`) 138 + 5. [Packagist](http://packagist.org/packages/balanced/balanced) will see the new tag and take it from there 139 + 6. Build (`build-phar`) and upload a [phar](http://php.net/manual/en/book.phar.php) file 140 + 141 + ## Contributing 142 + 143 + 1. Fork it 144 + 2. Create your feature branch (`git checkout -b my-new-feature`) 145 + 3. Write your code **and [tests](#testing)** 146 + 4. Ensure all tests still pass (`phpunit --bootstrap vendor/autoload.php tests/`) 147 + 5. Commit your changes (`git commit -am 'Add some feature'`) 148 + 6. Push to the branch (`git push origin my-new-feature`) 149 + 7. Create new pull request 150 + 151 + ## Contributors 152 + 153 + * [Jacob Rus](https://github.com/jrus) 154 + * [Leon Smith](https://github.com/leonsmith) 155 + * [Matt Drollette](https://github.com/MDrollette) 156 + * [You](https://github.com/balanced/balanced-php/issues)!
+4
externals/balanced-php/bootstrap.php
··· 1 + <?php 2 + 3 + require(__DIR__ . '/src/Balanced/Bootstrap.php'); 4 + \Balanced\Bootstrap::init();
+36
externals/balanced-php/build-phar
··· 1 + #!/usr/bin/php 2 + <?php 3 + include('src/Balanced/Settings.php'); 4 + 5 + function exit_unless($condition, $msg = null) { 6 + if ($condition) 7 + return; 8 + echo "[FAIL] $msg"; 9 + exit(1); 10 + } 11 + 12 + echo "Building Phar... "; 13 + $base_dir = dirname(__FILE__); 14 + $source_dir = $base_dir . '/src/Balanced/'; 15 + $phar_name = 'balanced.phar'; 16 + $phar_path = $base_dir . '/' . $phar_name; 17 + $phar = new Phar($phar_path, 0, $phar_name); 18 + $stub = <<<HEREDOC 19 + <?php 20 + // Phar Stub File 21 + Phar::mapPhar('balanced.phar'); 22 + include('phar://balanced.phar/Balanced/Bootstrap.php'); 23 + \Balanced\Bootstrap::pharInit(); 24 + 25 + __HALT_COMPILER(); 26 + HEREDOC; 27 + $phar->setStub($stub); 28 + exit_unless($phar, "Unable to create a phar. Make sure you have phar.readonly=0 set in your ini file."); 29 + $phar->buildFromDirectory(dirname($source_dir)); 30 + echo "[ OK ]\n"; 31 + 32 + echo "Renaming Phar... "; 33 + $phar_versioned_name = 'balanced-' . \Balanced\Settings::VERSION . '.phar'; 34 + $phar_versioned_path = $base_dir . '/' . $phar_versioned_name; 35 + rename($phar_path, $phar_versioned_path); 36 + echo "[ OK ]\n";
+24
externals/balanced-php/composer.json
··· 1 + { 2 + "name": "balanced/balanced", 3 + "description": "Client for Balanced API", 4 + "homepage": "http://github.com/balanced/balanced-php", 5 + "license": "MIT", 6 + "keywords": ["payments", "api"], 7 + "version": "0.7.1", 8 + "authors": [ 9 + { 10 + "name": "Balanced", 11 + "email": "dev@balancedpayments.com", 12 + "homepage": "http://www.balancedpayments.com" 13 + } 14 + ], 15 + "require": { 16 + "nategood/httpful": "*", 17 + "bninja/restful": "*" 18 + }, 19 + "autoload": { 20 + "psr-0": { 21 + "Balanced": "src/" 22 + } 23 + } 24 + }
+54
externals/balanced-php/example/bank-account-debits.php
··· 1 + <?php 2 + // 3 + // Learn how to authenticate a bank account so you can debit with it. 4 + // 5 + 6 + require(__DIR__ . '/vendor/autoload.php'); 7 + 8 + Httpful\Bootstrap::init(); 9 + RESTful\Bootstrap::init(); 10 + Balanced\Bootstrap::init(); 11 + 12 + // create a new marketplace 13 + $key = new Balanced\APIKey(); 14 + $key->save(); 15 + Balanced\Settings::$api_key = $key->secret; 16 + $marketplace = new Balanced\Marketplace(); 17 + $marketplace->save(); 18 + 19 + // create a bank account 20 + $bank_account = $marketplace->createBankAccount("Jack Q Merchant", 21 + "123123123", 22 + "123123123" 23 + ); 24 + $buyer = $marketplace->createAccount("buyer@example.org"); 25 + $buyer->addBankAccount($bank_account); 26 + 27 + print("you can't debit from a bank account until you verify it\n"); 28 + try { 29 + $buyer->debit(100); 30 + } catch (Exception $e) { 31 + printf("Debit failed, %s\n", $e->getMessage()); 32 + } 33 + 34 + // authenticate 35 + $verification = $bank_account->verify(); 36 + 37 + try { 38 + $verification->confirm(1, 2); 39 + } catch (Balanced\Errors\BankAccountVerificationFailure $e) { 40 + printf('Authentication error , %s\n', $e->getMessage()); 41 + print("PROTIP: for TEST bank accounts the valid amount is always 1 and 1\n"); 42 + 43 + } 44 + 45 + $verification->confirm(1, 1); 46 + 47 + $debit = $buyer->debit(100); 48 + printf("debited the bank account %s for %d cents\n", 49 + $debit->source->uri, 50 + $debit->amount 51 + ); 52 + print("and there you have it"); 53 + 54 + ?>
+157
externals/balanced-php/example/buyer-example.php
··· 1 + <?php 2 + 3 + require(__DIR__ . '/vendor/autoload.php'); 4 + 5 + Httpful\Bootstrap::init(); 6 + RESTful\Bootstrap::init(); 7 + Balanced\Bootstrap::init(); 8 + 9 + $API_KEY_SECRET = '5f4db668a5ec11e1b908026ba7e239a9'; 10 + $page = $_SERVER['REQUEST_URI']; 11 + Balanced\Settings::$api_key = $API_KEY_SECRET; 12 + $marketplace = Balanced\Marketplace::mine(); 13 + 14 + if ($page == '/') { 15 + // do nothing 16 + } elseif ($page == '/buyer') { 17 + if (isset($_POST['uri']) and isset($_POST['email_address'])) { 18 + // create in balanced 19 + $email_address = $_POST['email_address']; 20 + $card_uri = $_POST['uri']; 21 + try { 22 + echo create_buyer($email_address, $card_uri)->uri; 23 + return; 24 + } catch (Balanced\Errors\Error $e) { 25 + echo $e->getMessage(); 26 + return; 27 + } 28 + } 29 + } 30 + 31 + function create_buyer($email_address, $card_uri) { 32 + $marketplace = Balanced\Marketplace::mine(); 33 + try { 34 + # new buyer 35 + $buyer = $marketplace->createBuyer( 36 + $email_address, 37 + $card_uri); 38 + } 39 + catch (Balanced\Errors\DuplicateAccountEmailAddress $e) { 40 + # oops, account for $email_address already exists so just add the card 41 + $buyer = Balanced\Account::get($e->extras->account_uri); 42 + $buyer->addCard($card_uri); 43 + } 44 + return $buyer; 45 + } 46 + 47 + ?> 48 + <html> 49 + <head> 50 + <link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" type="text/css"> 51 + <style type="text/css"> 52 + [name="marketplace_eid"] { 53 + width: 300px; 54 + } 55 + [name^="expiration"] { 56 + width: 50px; 57 + } 58 + [name="security_code"] { 59 + width: 50px; 60 + } 61 + code { display: block; } 62 + pre { color: green; } 63 + </style> 64 + </head> 65 + <body> 66 + <h1>Balanced Sample - Collect Credit Card Information</h1> 67 + <div class="row"> 68 + <div class="span6"> 69 + <form id="payment"> 70 + <div> 71 + <label>Email Address</label> 72 + <input name="email_address" value="bob@example.com"> 73 + </div> 74 + <div> 75 + <label>Card Number</label> 76 + <input name="card_number" value="4111111111111111" autocomplete="off"> 77 + </div> 78 + <div> 79 + <label>Expiration</label> 80 + <input name="expiration_month" value="1"> / <input name="expiration_year" value="2020"> 81 + </div> 82 + <div> 83 + <label>Security Code</label> 84 + <input name="security_code" value="123" autocomplete="off"> 85 + </div> 86 + <button>Submit Payment Data</button> 87 + </form> 88 + </div> 89 + </div> 90 + <div id="result"></div> 91 + <script type="text/javascript" src="https://js.balancedpayments.com/v1/balanced.js"></script> 92 + <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> 93 + <script type="text/javascript"> 94 + var marketplaceUri = '<?php echo $marketplace->uri; ?>'; 95 + 96 + var debug = function (tag, content) { 97 + $('<' + tag + '>' + content + '</' + tag + '>').appendTo('#result'); 98 + }; 99 + 100 + try { 101 + balanced.init(marketplaceUri); 102 + } catch (e) { 103 + debug('code', 'You need to set the marketplaceUri variable'); 104 + } 105 + 106 + function accountCreated(response) { 107 + debug('code', 'account create result: ' + response); 108 + } 109 + 110 + function balancedCallback(response) { 111 + var tag = (response.status < 300) ? 'pre' : 'code'; 112 + debug(tag, JSON.stringify(response)); 113 + switch (response.status) { 114 + case 201: 115 + // response.data.uri == uri of the card resource, submit to your server 116 + $.post('/buyer', { 117 + uri: response.data.uri, 118 + email_address: $('[name="email_address"]').val() 119 + }, accountCreated); 120 + case 400: 121 + case 403: 122 + // missing/malformed data - check response.error for details 123 + break; 124 + case 402: 125 + // we couldn't authorize the buyer's credit card - check response.error for details 126 + break; 127 + case 404: 128 + // your marketplace URI is incorrect 129 + break; 130 + default: 131 + // we did something unexpected - check response.error for details 132 + break; 133 + } 134 + } 135 + 136 + var tokenizeCard = function(e) { 137 + e.preventDefault(); 138 + 139 + var $form = $('form#payment'); 140 + var cardData = { 141 + card_number: $form.find('[name="card_number"]').val(), 142 + expiration_month: $form.find('[name="expiration_month"]').val(), 143 + expiration_year: $form.find('[name="expiration_year"]').val(), 144 + security_code: $form.find('[name="security_code"]').val() 145 + }; 146 + 147 + balanced.card.create(cardData, balancedCallback); 148 + }; 149 + 150 + $('#payment').submit(tokenizeCard); 151 + 152 + if (window.location.protocol === 'file:') { 153 + alert("balanced.js does not work when included in pages served over file:// URLs. Try serving this page over a webserver. Contact support@balancedpayments.com if you need assistance."); 154 + } 155 + </script> 156 + </body> 157 + </html>
+5
externals/balanced-php/example/composer.json
··· 1 + { 2 + "require": { 3 + "balanced/balanced": "*" 4 + } 5 + }
+42
externals/balanced-php/example/debit-example.php
··· 1 + <?php 2 + 3 + require('vendor/autoload.php'); 4 + 5 + Httpful\Bootstrap::init(); 6 + RESTful\Bootstrap::init(); 7 + Balanced\Bootstrap::init(); 8 + 9 + $API_KEY_SECRET = '5f4db668a5ec11e1b908026ba7e239a9'; 10 + Balanced\Settings::$api_key = $API_KEY_SECRET; 11 + $marketplace = Balanced\Marketplace::mine(); 12 + 13 + print "create a card\n"; 14 + $card = $marketplace->cards->create(array( 15 + "card_number" => "5105105105105100", 16 + "expiration_month" => "12", 17 + "expiration_year" => "2015" 18 + )); 19 + print "our card: " . $card->uri . "\n"; 20 + 21 + print "create a **buyer** account with that card\n"; 22 + $buyer = $marketplace->createBuyer(null, $card->uri); 23 + print "our buyer account: " . $buyer->uri . "\n"; 24 + 25 + print "debit our buyer, let's say $15\n"; 26 + try { 27 + $debit = $buyer->debit(1500); 28 + print "our buyer debit: " . $debit->uri . "\n"; 29 + } 30 + catch (Balanced\Errors\Declined $e) { 31 + print "oh no, the processor declined the debit!\n"; 32 + } 33 + catch (Balanced\Errors\NoFundingSource $e) { 34 + print "oh no, the buyer has not active funding sources!\n"; 35 + } 36 + catch (Balanced\Errors\CannotDebit $e) { 37 + print "oh no, the buyer has no debitable funding sources!\n"; 38 + } 39 + 40 + print "and there you have it 8)\n"; 41 + 42 + ?>
+59
externals/balanced-php/example/events-and-callbacks.php
··· 1 + <?php 2 + /* 3 + * Welcome weary traveller. Sick of polling for state changes? Well today have 4 + * I got good news for you. Run this example below to see how to get yourself 5 + * some callback goodness and to understand how events work. 6 + */ 7 + require(__DIR__ . "/vendor/autoload.php"); 8 + 9 + Httpful\Bootstrap::init(); 10 + RESTful\Bootstrap::init(); 11 + Balanced\Bootstrap::init(); 12 + 13 + // create a new marketplace 14 + $key = new Balanced\APIKey(); 15 + $key->save(); 16 + Balanced\Settings::$api_key = $key->secret; 17 + $marketplace = new Balanced\Marketplace(); 18 + $marketplace->save(); 19 + 20 + // let"s create a requestb.in 21 + $ch = curl_init("http://requestb.in/api/v1/bins"); 22 + curl_setopt($ch, CURLOPT_POST, true); 23 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 24 + curl_setopt($ch, CURLOPT_HTTPHEADER, array( 25 + 'Content-Type: application/json', 26 + 'Content-Length: ' . 0) 27 + ); 28 + $result = json_decode(curl_exec($ch)); 29 + $bin_name = $result->name; 30 + $callback_url = "http://requestb.in/" . $bin_name; 31 + $requests_url = "http://requestb.in/api/v1/bins/" . $bin_name . "/requests"; 32 + 33 + printf("let's create a callback\n"); 34 + $marketplace->createCallback($callback_url); 35 + 36 + printf("let's create a card and associate it with a new account\n"); 37 + $card = $marketplace->cards->create(array( 38 + "card_number" => "5105105105105100", 39 + "expiration_month" => "12", 40 + "expiration_year" => "2015" 41 + )); 42 + $buyer = $marketplace->createBuyer("buyer@example.org", $card->uri); 43 + 44 + printf("generate a debit (which implicitly creates and captures a hold)\n"); 45 + $buyer->debit(100); 46 + 47 + foreach ($marketplace->events as $event) { 48 + printf("this was a %s event, it occurred at %s\n", 49 + $event->type, 50 + $event->occurred_at 51 + ); 52 + } 53 + 54 + printf("ok, let's check with requestb.in to see if our callbacks fired at %s\n", $callback_url); 55 + printf("we received callbacks, you can view them at http://requestb.in/%s?inspect\n", 56 + $bin_name 57 + ); 58 + 59 + ?>
+120
externals/balanced-php/example/example.php
··· 1 + <?php 2 + 3 + require('vendor/autoload.php'); 4 + 5 + Httpful\Bootstrap::init(); 6 + RESTful\Bootstrap::init(); 7 + Balanced\Bootstrap::init(); 8 + 9 + print "create our new api key\n"; 10 + $key = new Balanced\APIKey(); 11 + $key->save(); 12 + print "Our secret is " . $key->secret . "\n"; 13 + 14 + print "configure with our secret " . $key->secret . "\n"; 15 + Balanced\Settings::$api_key = $key->secret; 16 + 17 + print "create our marketplace"; 18 + $marketplace = new Balanced\Marketplace(); 19 + $marketplace->save(); 20 + 21 + if (Balanced\Merchant::me() == null) { 22 + throw new Exception("Balanced\Merchant::me() should not be null"); 23 + } 24 + 25 + print "What's my merchant? Easy: Balanced\Merchant::me(): " . Balanced\Merchant::me()->uri . "\n"; 26 + 27 + if (Balanced\Marketplace::mine() == null) { 28 + throw new Exception("Balanced\Marketplace::mine() should never be null"); 29 + } 30 + 31 + print "What's my marketplace? Easy: Balanced\Marketplace::mine(): " .Balanced\Marketplace::mine()->uri . "\n"; 32 + 33 + print "My marketplace's name is " . $marketplace->name . "\n"; 34 + print "Changing it to TestFooey\n"; 35 + $marketplace->name = "TestFooey"; 36 + $marketplace->save(); 37 + print "My marketplace name is now " . $marketplace->name . "\n"; 38 + 39 + if ($marketplace->name != "TestFooey") { 40 + throw new Exception("Marketplace name is NOT TestFooey"); 41 + } 42 + 43 + print "Cool, let's create a card\n"; 44 + $card = $marketplace->cards->create(array( 45 + "card_number" => "5105105105105100", 46 + "expiration_month" => "12", 47 + "expiration_year" => "2015" 48 + )); 49 + 50 + print "Our card: " . $card->uri . "\n"; 51 + 52 + print "Create out **buyer** account\n"; 53 + $buyer = $marketplace->createBuyer("buyer@example.org", $card->uri); 54 + print "our buyer account: " . $buyer->uri . "\n"; 55 + 56 + print "hold some amount of funds on the buyer, let's say $15\n"; 57 + $the_hold = $buyer->hold(1500); 58 + 59 + print "ok, no more holds! let's capture it (for the full amount)\n"; 60 + $debit = $the_hold->capture(); 61 + 62 + print "hmm, ho much money do i have in escrow? it should equal the debit amount\n"; 63 + $marketplace = Balanced\Marketplace::mine(); 64 + if ($marketplace->in_escrow != 1500) { 65 + throw new Exception("1500 is not in escrow! This is wrong"); 66 + } 67 + print "I have " . $marketplace->in_escrow . " in escrow!\n"; 68 + 69 + print "Cool. now let me refund the full amount"; 70 + $refund = $debit->refund(); 71 + 72 + print "ok, we have a merchant that's signing up, let's create an account for them first, let's create their bank account\n"; 73 + 74 + $bank_account = $marketplace->createBankAccount("Jack Q Merchant", 75 + "123123123", /* account_number */ 76 + "123123123" /* bank_code (routing number is USA)*/ 77 + ); 78 + 79 + $identity = array( 80 + "type" => "person", 81 + "name" => "Billy Jones", 82 + "street_address" => "801 High St", 83 + "postal_code" => "94301", 84 + "country" => "USA", 85 + "dob" => "1979-02", 86 + "phone_number" => "+16505551234" 87 + ); 88 + 89 + $merchant = $marketplace->createMerchant('merchant@example.org', 90 + $identity, 91 + $bank_account->uri 92 + ); 93 + 94 + print "our buyer is interested in buying something for $130\n"; 95 + $another_debit = $buyer->debit(13000, "MARKETPLACE.COM"); 96 + 97 + print "let's credit our merchant $110\n"; 98 + $credit = $merchant->credit(11000, "Buyer purchase something on Marketplace.com"); 99 + 100 + print "let's assume the marketplace charges 15%, so it earned $20\n"; 101 + $mp_credit = $marketplace->owner_account->credit(2000, 102 + "Commission from MARKETPLACE.COM"); 103 + 104 + print "ok, let's invalidate the card used so it cannot be used again\n"; 105 + $card->is_valid = false; 106 + $card->save(); 107 + 108 + print "how do we look up an existing object from the URI?\n"; 109 + $the_buyer = Balanced\Account::get($buyer->uri); 110 + print "we got the buyer " . $the_buyer->email_address . "\n"; 111 + 112 + $the_debit = Balanced\Debit::get($debit->uri); 113 + print "we got the debit: " . $the_debit->uri . "\n"; 114 + 115 + $the_credit = Balanced\Credit::get($credit->uri); 116 + print "we got the credit: " . $the_credit->uri . "\n"; 117 + 118 + print "and there you have it :)\n"; 119 + 120 + ?>
+71
externals/balanced-php/example/iterate-example.php
··· 1 + <? 2 + require('vendor/autoload.php'); 3 + 4 + Httpful\Bootstrap::init(); 5 + RESTful\Bootstrap::init(); 6 + Balanced\Bootstrap::init(); 7 + 8 + $key = new Balanced\APIKey(); 9 + $key->save(); 10 + Balanced\Settings::$api_key = $key->secret; 11 + $marketplace = new Balanced\Marketplace(); 12 + $marketplace->save(); 13 + 14 + $card = $marketplace->cards->create(array( 15 + "card_number" => "5105105105105100", 16 + "expiration_month" => "12", 17 + "expiration_year" => "2015" 18 + )); 19 + 20 + $buyer = $marketplace->createBuyer("buyer@example.com", $card->uri); 21 + 22 + $debit = $buyer->debit(1500); 23 + $debit->refund(100); 24 + $debit->refund(100); 25 + $debit->refund(100); 26 + 27 + echo $debit->refunds->total() . " refunds" . "\n"; 28 + 29 + $total = 0; 30 + 31 + foreach ($debit->refunds as $r) { 32 + $total += $r->amount; 33 + print "refund = " . $r->amount . "\n"; 34 + } 35 + 36 + print $total . "\n"; 37 + 38 + # bigger pagination example 39 + 40 + print "Create 60 **buyer** with cards accounts\n"; 41 + 42 + for ($i = 0; $i < 60; $i++) { 43 + $card = $marketplace->cards->create(array( 44 + "card_number" => "5105105105105100", 45 + "expiration_month" => "12", 46 + "expiration_year" => "2015" 47 + )); 48 + $buyer = $marketplace->createBuyer("buyer" . $i . "@example.org", $card->uri); 49 + print '.'; 50 + } 51 + 52 + print "\n"; 53 + 54 + $cards = $marketplace->cards; 55 + 56 + print $cards->total() . " cards in Marketplace\n"; 57 + 58 + foreach ($cards as $c) { 59 + print "card " . $c->uri . "\n"; 60 + } 61 + 62 + # let's iterate through cards for just a single account 63 + 64 + foreach ($buyer->cards as $c) { 65 + print "buyer's card " . $c->uri . "\n"; 66 + } 67 + 68 + print "and there you have it :)\n"; 69 + 70 + 71 + ?>
+14
externals/balanced-php/example/test-composer.php
··· 1 + <?php 2 + 3 + // run this file to test your composer install of Balanced 4 + 5 + require(__DIR__ . '/vendor/autoload.php'); 6 + 7 + \Httpful\Bootstrap::init(); 8 + \RESTful\Bootstrap::init(); 9 + \Balanced\Bootstrap::init(); 10 + 11 + echo "[ OK ]\n"; 12 + echo "balanced version -- " . \Balanced\Settings::VERSION . " \n"; 13 + echo "restful version -- " . \RESTful\Settings::VERSION . " \n"; 14 + echo "httpful version -- " . \Httpful\Httpful::VERSION . " \n";
+12
externals/balanced-php/example/test-phar.php
··· 1 + <?php 2 + 3 + // run this file to test your phar install of Balanced 4 + 5 + include(__DIR__ . '/httpful.phar'); 6 + include(__DIR__ . '/restful.phar'); 7 + include(__DIR__ . '/balanced.phar'); 8 + 9 + echo "[ OK ]\n"; 10 + echo "balanced version -- " . \Balanced\Settings::VERSION . " \n"; 11 + echo "restful version -- " . \RESTful\Settings::VERSION . " \n"; 12 + echo "httpful version -- " . \Httpful\Httpful::VERSION . " \n";
+12
externals/balanced-php/example/test-source.php
··· 1 + <?php 2 + 3 + // run this file to test your source install of Balanced 4 + 5 + require(__DIR__ . "/httpful/bootstrap.php"); 6 + require(__DIR__ . "/restful/bootstrap.php"); 7 + require(__DIR__ . "/balanced/bootstrap.php"); 8 + 9 + echo "[ OK ]\n"; 10 + echo "balanced version -- " . \Balanced\Settings::VERSION . " \n"; 11 + echo "restful version -- " . \RESTful\Settings::VERSION . " \n"; 12 + echo "httpful version -- " . \Httpful\Httpful::VERSION . " \n";
+55
externals/balanced-php/src/Balanced/APIKey.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use Balanced\Settings; 7 + use \RESTful\URISpec; 8 + 9 + /** 10 + * Represents an api key. These are used to authenticate you with the api. 11 + * 12 + * Typically you create an initial api key: 13 + * 14 + * <code> 15 + * print \Balanced\Settings::$api_key == null; 16 + * $api_key = new \Balanced\APIKey(); 17 + * $api_key = api_key->save(); 18 + * $secret = $api_key->secret; 19 + * print $secret; 20 + * </code> 21 + * 22 + * Then save the returned secret (we don't store it) and configure the client 23 + * to use it: 24 + * 25 + * <code> 26 + * \Balanced\Settings::$api_key = 'my-api-key-secret'; 27 + * </code> 28 + * 29 + * You can later add another api key if you'd like to rotate or expire old 30 + * ones: 31 + * 32 + * <code> 33 + * $api_key = new \Balanced\APIKey(); 34 + * $api_key = api_key->save(); 35 + * $new_secret = $api_key->secret; 36 + * print $new_secret; 37 + * 38 + * \Balanced\Settings::$api_key = $new_secret; 39 + * 40 + * \Balanced\APIKey::query() 41 + * ->sort(\Balanced\APIKey::f->created_at->desc()) 42 + * ->first() 43 + * ->delete(); 44 + * </code> 45 + */ 46 + class APIKey extends Resource 47 + { 48 + protected static $_uri_spec = null; 49 + 50 + public static function init() 51 + { 52 + self::$_uri_spec = new URISpec('api_keys', 'id', '/v1'); 53 + self::$_registry->add(get_called_class()); 54 + } 55 + }
+217
externals/balanced-php/src/Balanced/Account.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represent a buyer or merchant account on a marketplace. 10 + * 11 + * You create these using Balanced\Marketplace->createBuyer or 12 + * Balanced\Marketplace->createMerchant. 13 + * 14 + * <code> 15 + * $marketplace = \Balanced\Marketplace::mine(); 16 + * 17 + * $card = $marketplace->cards->create(array( 18 + * 'street_address' => $street_address, 19 + * 'city' => 'Jollywood', 20 + * 'region' => 'CA', 21 + * 'postal_code' => '90210', 22 + * 'name' => 'Captain Chunk', 23 + * 'card_number' => '4111111111111111', 24 + * 'expiration_month' => 7, 25 + * 'expiration_year' => 2015 26 + * )); 27 + * 28 + * $buyer = $marketplace->createBuyer( 29 + * 'buyer@example.com', 30 + * $card->uri, 31 + * array( 32 + * 'my_id' => '1212121', 33 + * ) 34 + * ); 35 + * </code> 36 + * 37 + * @see Balanced\Marketplace->createBuyer 38 + * @see Balanced\Marketplace->createMerchant 39 + */ 40 + class Account extends Resource 41 + { 42 + protected static $_uri_spec = null; 43 + 44 + public static function init() 45 + { 46 + self::$_uri_spec = new URISpec('accounts', 'id'); 47 + self::$_registry->add(get_called_class()); 48 + } 49 + 50 + /** 51 + * Credit the account. 52 + * 53 + * @param int amount Amount to credit the account in USD pennies. 54 + * @param string description Optional description of the credit. 55 + * @param array[string]string meta Optional metadata to associate with the credit. 56 + * @param mixed destination Optional URI of a funding destination (i.e. \Balanced\BankAccount) associated with this account to credit. If not specified the funding destination most recently added to the account is used. 57 + * @param string appears_on_statement_as Optional description of the credit as it will appears on the customer's billing statement. 58 + * 59 + * @return \Balanced\Credit 60 + */ 61 + public function credit( 62 + $amount, 63 + $description = null, 64 + $meta = null, 65 + $destination = null, 66 + $appears_on_statement_as = null) 67 + { 68 + if ($destination == null) 69 + $destination_uri = null; 70 + else 71 + $destination_uri = is_string($destination) ? $destination : $destination->uri; 72 + return $this->credits->create(array( 73 + 'amount' => $amount, 74 + 'description' => $description, 75 + 'meta' => $meta, 76 + 'destination_uri' => $destination_uri, 77 + 'appears_on_statement_as' => $appears_on_statement_as 78 + )); 79 + } 80 + 81 + /** 82 + * Debit the account. 83 + * 84 + * @param int amount Amount to debit the account in USD pennies. 85 + * @param string appears_on_statement_as Optional description of the debit as it will appears on the customer's billing statement. 86 + * @param string description Optional description of the debit. 87 + * @param array[string]string meta Optional metadata to associate with the debit. 88 + * @param mixed Optional funding source (i.e. \Balanced\Card) or URI of a funding source associated with this account to debit. If not specified the funding source most recently added to the account is used. 89 + * 90 + * @return \Balanced\Debit 91 + */ 92 + public function debit( 93 + $amount, 94 + $appears_on_statement_as = null, 95 + $description = null, 96 + $meta = null, 97 + $source = null, 98 + $on_behalf_of = null) 99 + { 100 + if ($source == null) { 101 + $source_uri = null; 102 + } else if (is_string($source)) { 103 + $source_uri = $source; 104 + } else { 105 + $source_uri = $source->uri; 106 + } 107 + 108 + if ($on_behalf_of == null) { 109 + $on_behalf_of_uri = null; 110 + } else if (is_string($on_behalf_of)) { 111 + $on_behalf_of_uri = $on_behalf_of; 112 + } else { 113 + $on_behalf_of_uri = $on_behalf_of->uri; 114 + } 115 + 116 + if (isset($this->uri) && $on_behalf_of_uri == $this->uri) 117 + throw new \InvalidArgumentException( 118 + 'The on_behalf_of parameter MAY NOT be the same account as the account you are debiting!' 119 + ); 120 + 121 + return $this->debits->create(array( 122 + 'amount' => $amount, 123 + 'description' => $description, 124 + 'meta' => $meta, 125 + 'source_uri' => $source_uri, 126 + 'on_behalf_of_uri' => $on_behalf_of_uri, 127 + 'appears_on_statement_as' => $appears_on_statement_as 128 + )); 129 + } 130 + 131 + /** 132 + * Create a hold (i.e. a guaranteed pending debit) for account funds. You 133 + * can later capture or void. A hold is associated with a account funding 134 + * source (i.e. \Balanced\Card). If you don't specify the source then the 135 + * current primary funding source for the account is used. 136 + * 137 + * @param int amount Amount of the hold in USD pennies. 138 + * @param string Optional description Description of the hold. 139 + * @param string Optional URI referencing the card to use for the hold. 140 + * @param array[string]string meta Optional metadata to associate with the hold. 141 + * 142 + * @return \Balanced\Hold 143 + */ 144 + public function hold( 145 + $amount, 146 + $description = null, 147 + $source_uri = null, 148 + $meta = null) 149 + { 150 + return $this->holds->create(array( 151 + 'amount' => $amount, 152 + 'description' => $description, 153 + 'source_uri' => $source_uri, 154 + 'meta' => $meta 155 + )); 156 + } 157 + 158 + /** 159 + * Creates or associates a created card with the account. The default 160 + * funding source for the account will be this card. 161 + * 162 + * @see \Balanced\Marketplace->createCard 163 + * 164 + * @param mixed card \Balanced\Card or URI referencing a card to associate with the account. Alternatively it can be an associative array describing a card to create and associate with the account. 165 + * 166 + * @return \Balanced\Account 167 + */ 168 + public function addCard($card) 169 + { 170 + if (is_string($card)) 171 + $this->card_uri = $card; 172 + else if (is_array($card)) 173 + $this->card = $card; 174 + else 175 + $this->card_uri = $card->uri; 176 + return $this->save(); 177 + } 178 + 179 + /** 180 + * Creates or associates a created bank account with the account. The 181 + * new default funding destination for the account will be this bank account. 182 + * 183 + * @see \Balanced\Marketplace->createBankAccount 184 + * 185 + * @param mixed bank_account \Balanced\BankAccount or URI for a bank account to associate with the account. Alternatively it can be an associative array describing a bank account to create and associate with the account. 186 + * 187 + * @return \Balanced\Account 188 + */ 189 + public function addBankAccount($bank_account) 190 + { 191 + if (is_string($bank_account)) 192 + $this->bank_account_uri = $bank_account; 193 + else if (is_array($bank_account)) 194 + $this->bank_account = $bank_account; 195 + else 196 + $this->bank_account_uri = $bank_account->uri; 197 + return $this->save(); 198 + } 199 + 200 + /** 201 + * Promotes a role-less or buyer account to a merchant. 202 + * 203 + * @see Balanced\Marketplace::createMerchant 204 + * 205 + * @param mixed merchant Associative array describing the merchants identity or a URI referencing a created merchant. 206 + * 207 + * @return \Balanced\Account 208 + */ 209 + public function promoteToMerchant($merchant) 210 + { 211 + if (is_string($merchant)) 212 + $this->merchant_uri = $merchant; 213 + else 214 + $this->merchant = $merchant; 215 + return $this->save(); 216 + } 217 + }
+127
externals/balanced-php/src/Balanced/BankAccount.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents an account bank account. 10 + * 11 + * You can create these via Balanced\Marketplace::bank_accounts::create or 12 + * Balanced\Marketplace::createBankAccount. Associate them with a buyer or 13 + * merchant one creation via Balanced\Marketplace::createBuyer or 14 + * Balanced\Marketplace::createMerchant and with an existing buyer or merchant 15 + * use Balanced\Account::addBankAccount. 16 + * 17 + * <code> 18 + * $marketplace = \Balanced\Marketplace::mine(); 19 + * 20 + * $bank_account = $marketplace->bank_accounts->create(array( 21 + * 'name' => 'name', 22 + * 'account_number' => '11223344', 23 + * 'bank_code' => '1313123', 24 + * )); 25 + * 26 + * $account = $marketplace 27 + * ->accounts 28 + * ->query() 29 + * ->filter(Account::f->email_address->eq('merchant@example.com')) 30 + * ->one(); 31 + * $account->addBankAccount($bank_account->uri); 32 + * </code> 33 + */ 34 + class BankAccount extends Resource 35 + { 36 + protected static $_uri_spec = null; 37 + 38 + public static function init() 39 + { 40 + self::$_uri_spec = new URISpec('bank_accounts', 'id', '/v1'); 41 + self::$_registry->add(get_called_class()); 42 + } 43 + 44 + /** 45 + * Credit a bank account. 46 + * 47 + * @param int amount Amount to credit in USD pennies. 48 + * @param string description Optional description of the credit. 49 + * @param string appears_on_statement_as Optional description of the credit as it will appears on the customer's billing statement. 50 + * 51 + * @return \Balanced\Credit 52 + * 53 + * <code> 54 + * $bank_account = new \Balanced\BankAccount(array( 55 + * 'account_number' => '12341234', 56 + * 'name' => 'Fit Finlay', 57 + * 'bank_code' => '325182797', 58 + * 'type' => 'checking', 59 + * )); 60 + * 61 + * $credit = $bank_account->credit(123, 'something descriptive'); 62 + * </code> 63 + */ 64 + public function credit( 65 + $amount, 66 + $description = null, 67 + $meta = null, 68 + $appears_on_statement_as = null) 69 + { 70 + if (!property_exists($this, 'account') || $this->account == null) { 71 + $credit = $this->credits->create(array( 72 + 'amount' => $amount, 73 + 'description' => $description, 74 + )); 75 + } else { 76 + $credit = $this->account->credit( 77 + $amount, 78 + $description, 79 + $meta, 80 + $this->uri, 81 + $appears_on_statement_as 82 + ); 83 + } 84 + return $credit; 85 + } 86 + 87 + public function verify() 88 + { 89 + $response = self::getClient()->post( 90 + $this->verifications_uri, null 91 + ); 92 + $verification = new BankAccountVerification(); 93 + $verification->_objectify($response->body); 94 + return $verification; 95 + } 96 + } 97 + 98 + /** 99 + * Represents an verification for a bank account which is a pre-requisite if 100 + * you want to create debits using the associated bank account. The side-effect 101 + * of creating a verification is that 2 random amounts will be deposited into 102 + * the account which must then be confirmed via the confirm method to ensure 103 + * that you have access to the bank account in question. 104 + * 105 + * You can create these via Balanced\Marketplace::bank_accounts::verify. 106 + * 107 + * <code> 108 + * $marketplace = \Balanced\Marketplace::mine(); 109 + * 110 + * $bank_account = $marketplace->bank_accounts->create(array( 111 + * 'name' => 'name', 112 + * 'account_number' => '11223344', 113 + * 'bank_code' => '1313123', 114 + * )); 115 + * 116 + * $verification = $bank_account->verify(); 117 + * </code> 118 + */ 119 + class BankAccountVerification extends Resource { 120 + 121 + public function confirm($amount1, $amount2) { 122 + $this->amount_1 = $amount1; 123 + $this->amount_2 = $amount2; 124 + $this->save(); 125 + return $this; 126 + } 127 + }
+79
externals/balanced-php/src/Balanced/Bootstrap.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + /** 6 + * Bootstrapper for Balanced does autoloading and resource initialization. 7 + */ 8 + class Bootstrap 9 + { 10 + const DIR_SEPARATOR = DIRECTORY_SEPARATOR; 11 + const NAMESPACE_SEPARATOR = '\\'; 12 + 13 + public static $initialized = false; 14 + 15 + 16 + public static function init() 17 + { 18 + spl_autoload_register(array('\Balanced\Bootstrap', 'autoload')); 19 + self::initializeResources(); 20 + } 21 + 22 + public static function autoload($classname) 23 + { 24 + self::_autoload(dirname(dirname(__FILE__)), $classname); 25 + } 26 + 27 + public static function pharInit() 28 + { 29 + spl_autoload_register(array('\Balanced\Bootstrap', 'pharAutoload')); 30 + self::initializeResources(); 31 + } 32 + 33 + public static function pharAutoload($classname) 34 + { 35 + self::_autoload('phar://balanced.phar', $classname); 36 + } 37 + 38 + private static function _autoload($base, $classname) 39 + { 40 + if (!strncmp($classname, 'Balanced\Errors\\', strlen('Balanced\Errors\\'))) 41 + $classname = 'Balanced\Errors'; 42 + $parts = explode(self::NAMESPACE_SEPARATOR, $classname); 43 + $path = $base . self::DIR_SEPARATOR. implode(self::DIR_SEPARATOR, $parts) . '.php'; 44 + if (file_exists($path)) { 45 + require_once($path); 46 + } 47 + } 48 + 49 + /** 50 + * Initializes resources (i.e. registers them with Resource::_registry). Note 51 + * that if you add a Resource then you must initialize it here. 52 + * 53 + * @internal 54 + */ 55 + private static function initializeResources() 56 + { 57 + if (self::$initialized) 58 + return; 59 + 60 + \Balanced\Errors\Error::init(); 61 + 62 + \Balanced\Resource::init(); 63 + 64 + \Balanced\APIKey::init(); 65 + \Balanced\Marketplace::init(); 66 + \Balanced\Account::init(); 67 + \Balanced\Credit::init(); 68 + \Balanced\Debit::init(); 69 + \Balanced\Refund::init(); 70 + \Balanced\Card::init(); 71 + \Balanced\BankAccount::init(); 72 + \Balanced\Hold::init(); 73 + \Balanced\Merchant::init(); 74 + \Balanced\Callback::init(); 75 + \Balanced\Event::init(); 76 + 77 + self::$initialized = true; 78 + } 79 + }
+24
externals/balanced-php/src/Balanced/Callback.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /* 9 + * A Callback is a publicly accessible location that can receive POSTed JSON 10 + * data whenever an Event is generated. 11 + * 12 + * You create these using Balanced\Marketplace->createCallback. 13 + * 14 + */ 15 + class Callback extends Resource 16 + { 17 + protected static $_uri_spec = null; 18 + 19 + public static function init() 20 + { 21 + self::$_uri_spec = new URISpec('callbacks', 'id'); 22 + self::$_registry->add(get_called_class()); 23 + } 24 + }
+61
externals/balanced-php/src/Balanced/Card.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents an account card. 10 + * 11 + * You can create these via Balanced\Marketplace::cards::create or 12 + * Balanced\Marketplace::createCard. Associate them with a buyer or merchant 13 + * one creation via Marketplace::createBuyer or 14 + * Balanced\Marketplace::createMerchant and with an existing buyer or merchant 15 + * use Balanced\Account::addCard. 16 + * 17 + * <code> 18 + * $marketplace = \Balanced\Marketplace::mine(); 19 + * 20 + * $card = $marketplace->cards->create(array( 21 + * 'name' => 'name', 22 + * 'account_number' => '11223344', 23 + * 'bank_code' => '1313123' 24 + * )); 25 + * 26 + * $account = $marketplace 27 + * ->accounts 28 + * ->query() 29 + * ->filter(Account::f->email_address->eq('buyer@example.com')) 30 + * ->one(); 31 + * $account->addCard($card->uri); 32 + * </code> 33 + */ 34 + class Card extends Resource 35 + { 36 + protected static $_uri_spec = null; 37 + 38 + public static function init() 39 + { 40 + self::$_uri_spec = new URISpec('cards', 'id', '/v1'); 41 + self::$_registry->add(get_called_class()); 42 + } 43 + 44 + public function debit( 45 + $amount, 46 + $appears_on_statement_as = null, 47 + $description = null, 48 + $meta = null, 49 + $source = null) 50 + { 51 + if ($this->account == null) { 52 + throw new \UnexpectedValueException('Card is not associated with an account.'); 53 + } 54 + return $this->account->debit( 55 + $amount, 56 + $appears_on_statement_as, 57 + $description, 58 + $meta, 59 + $this->uri); 60 + } 61 + }
+75
externals/balanced-php/src/Balanced/Credit.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents an account credit transaction. 10 + * 11 + * You create these using Balanced\Account::credit. 12 + * 13 + * <code> 14 + * $marketplace = \Balanced\Marketplace::mine(); 15 + * 16 + * $account = $marketplace 17 + * ->accounts 18 + * ->query() 19 + * ->filter(Account::f->email_address->eq('merchant@example.com')) 20 + * ->one(); 21 + * 22 + * $credit = $account->credit( 23 + * 100, 24 + * 'how it ' 25 + * array( 26 + * 'my_id': '112233' 27 + * ) 28 + * ); 29 + * </code> 30 + */ 31 + class Credit extends Resource 32 + { 33 + protected static $_uri_spec = null; 34 + 35 + public static function init() 36 + { 37 + self::$_uri_spec = new URISpec('credits', 'id', '/v1'); 38 + self::$_registry->add(get_called_class()); 39 + } 40 + 41 + /** 42 + * Credit an unstored bank account. 43 + * 44 + * @param int amount Amount to credit in USD pennies. 45 + * @param string description Optional description of the credit. 46 + * @param mixed bank_account Associative array describing a bank account to credit. The bank account will *not* be stored. 47 + * 48 + * @return \Balanced\Credit 49 + * 50 + * <code> 51 + * $credit = \Balanced\Credit::bankAccount( 52 + * 123, 53 + * array( 54 + * 'account_number' => '12341234', 55 + * 'name' => 'Fit Finlay', 56 + * 'bank_code' => '325182797', 57 + * 'type' => 'checking', 58 + * ), 59 + * 'something descriptive'); 60 + * </code> 61 + */ 62 + public static function bankAccount( 63 + $amount, 64 + $bank_account, 65 + $description = null) 66 + { 67 + $credit = new Credit(array( 68 + 'amount' => $amount, 69 + 'bank_account' => $bank_account, 70 + 'description' => $description 71 + )); 72 + $credit->save(); 73 + return $credit; 74 + } 75 + }
+64
externals/balanced-php/src/Balanced/Debit.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents an account debit transaction. 10 + * 11 + * You create these using Balanced\Account::debit. 12 + * 13 + * <code> 14 + * $marketplace = \Balanced\Marketplace::mine(); 15 + * 16 + * $account = $marketplace 17 + * ->accounts 18 + * ->query() 19 + * ->filter(Account::f->email_address->eq('buyer@example.com')) 20 + * ->one(); 21 + * 22 + * $debit = $account->debit( 23 + * 100, 24 + * 'how it appears on the statement', 25 + * 'a description', 26 + * array( 27 + * 'my_id': '443322' 28 + * ) 29 + * ); 30 + * </code> 31 + */ 32 + class Debit extends Resource 33 + { 34 + protected static $_uri_spec = null; 35 + 36 + public static function init() 37 + { 38 + self::$_uri_spec = new URISpec('debits', 'id'); 39 + self::$_registry->add(get_called_class()); 40 + } 41 + 42 + /** 43 + * Create a refund for this debit. You can create multiple refunds for a 44 + * debit but the total amount of the refunds must be less than the debit 45 + * amount. 46 + * 47 + * @param int amount Optional amount of the refund in USD pennies. If unspecified then the full debit amount is used. 48 + * @param string description Optional description of the refund. 49 + * @param array[string]string meta Optional metadata to associate with the refund. 50 + * 51 + * @return \Balanced\Refund 52 + */ 53 + public function refund( 54 + $amount = null, 55 + $description = null, 56 + $meta = null) 57 + { 58 + return $this->refunds->create(array( 59 + 'amount' => $amount, 60 + 'description' => $description, 61 + 'meta' => $meta 62 + )); 63 + } 64 + }
+135
externals/balanced-php/src/Balanced/Errors.php
··· 1 + <?php 2 + 3 + namespace Balanced\Errors; 4 + 5 + use RESTful\Exceptions\HTTPError; 6 + 7 + class Error extends HTTPError 8 + { 9 + public static $codes = array(); 10 + 11 + public static function init() 12 + { 13 + foreach (get_declared_classes() as $class) { 14 + $parent_class = get_parent_class($class); 15 + if ($parent_class != 'Balanced\Errors\Error') 16 + continue; 17 + foreach ($class::$codes as $type) 18 + self::$codes[$type] = $class; 19 + } 20 + } 21 + } 22 + 23 + class DuplicateAccountEmailAddress extends Error 24 + { 25 + public static $codes = array('duplicate-email-address'); 26 + } 27 + 28 + class InvalidAmount extends Error 29 + { 30 + public static $codes = array('invalid-amount'); 31 + } 32 + 33 + class InvalidRoutingNumber extends Error 34 + { 35 + public static $codes = array('invalid-routing-number'); 36 + } 37 + 38 + class InvalidBankAccountNumber extends Error 39 + { 40 + public static $codes = array('invalid-bank-account-number'); 41 + } 42 + 43 + class Declined extends Error 44 + { 45 + public static $codes = array('funding-destination-declined', 'authorization-failed'); 46 + } 47 + 48 + class CannotAssociateMerchantWithAccount extends Error 49 + { 50 + public static $codes = array('cannot-associate-merchant-with-account'); 51 + } 52 + 53 + class AccountIsAlreadyAMerchant extends Error 54 + { 55 + public static $codes = array('account-already-merchant'); 56 + } 57 + 58 + class NoFundingSource extends Error 59 + { 60 + public static $codes = array('no-funding-source'); 61 + } 62 + 63 + class NoFundingDestination extends Error 64 + { 65 + public static $codes = array('no-funding-destination'); 66 + } 67 + 68 + class CardAlreadyAssociated extends Error 69 + { 70 + public static $codes = array('card-already-funding-src'); 71 + } 72 + 73 + class CannotAssociateCard extends Error 74 + { 75 + public static $codes = array('cannot-associate-card'); 76 + } 77 + 78 + class BankAccountAlreadyAssociated extends Error 79 + { 80 + public static $codes = array('bank-account-already-associated'); 81 + } 82 + 83 + class AddressVerificationFailed extends Error 84 + { 85 + public static $codes = array('address-verification-failed'); 86 + } 87 + 88 + class HoldExpired extends Error 89 + { 90 + public static $codes = array('authorization-expired'); 91 + } 92 + 93 + class MarketplaceAlreadyCreated extends Error 94 + { 95 + public static $codes = array('marketplace-already-created'); 96 + } 97 + 98 + class IdentityVerificationFailed extends Error 99 + { 100 + public static $codes = array('identity-verification-error', 'business-principal-kyc', 'business-kyc', 'person-kyc'); 101 + } 102 + 103 + class InsufficientFunds extends Error 104 + { 105 + public static $codes = array('insufficient-funds'); 106 + } 107 + 108 + class CannotHold extends Error 109 + { 110 + public static $codes = array('funding-source-not-hold'); 111 + } 112 + 113 + class CannotCredit extends Error 114 + { 115 + public static $codes = array('funding-destination-not-creditable'); 116 + } 117 + 118 + class CannotDebit extends Error 119 + { 120 + public static $codes = array('funding-source-not-debitable'); 121 + } 122 + 123 + class CannotRefund extends Error 124 + { 125 + public static $codes = array('funding-source-not-refundable'); 126 + } 127 + 128 + class BankAccountVerificationFailure extends Error 129 + { 130 + public static $codes = array( 131 + 'bank-account-authentication-not-pending', 132 + 'bank-account-authentication-failed', 133 + 'bank-account-authentication-already-exists' 134 + ); 135 + }
+23
externals/balanced-php/src/Balanced/Event.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /* 9 + * An Event is a snapshot of another resource at a point in time when 10 + * something significant occurred. Events are created when resources are 11 + * created, updated, deleted or otherwise change state such as a Credit 12 + * being marked as failed. 13 + */ 14 + class Event extends Resource 15 + { 16 + protected static $_uri_spec = null; 17 + 18 + public static function init() 19 + { 20 + self::$_uri_spec = new URISpec('events', 'id', '/v1'); 21 + self::$_registry->add(get_called_class()); 22 + } 23 + }
+77
externals/balanced-php/src/Balanced/Hold.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents pending debit of funds for an account. The funds for that debit 10 + * are held by the processor. You can later capture the hold, which results in 11 + * debit, or void it, which releases the held funds. 12 + * 13 + * Note that a hold can expire so you should always check 14 + * Balanced\Hold::expires_at. 15 + * 16 + * You create these using \Balanced\Account::hold. 17 + * 18 + * <code> 19 + * $marketplace = \Balanced\Marketplace::mine(); 20 + * 21 + * $account = $marketplace 22 + * ->accounts 23 + * ->query() 24 + * ->filter(Account::f->email_address->eq('buyer@example.com')) 25 + * ->one(); 26 + * 27 + * $hold = $account->hold( 28 + * 100, 29 + * 'a description', 30 + * null, 31 + * array( 32 + * 'my_id': '1293712837' 33 + * ) 34 + * ); 35 + * 36 + * $debit = $hold->capture(); 37 + * </code> 38 + */ 39 + class Hold extends Resource 40 + { 41 + protected static $_uri_spec = null; 42 + 43 + public static function init() 44 + { 45 + self::$_uri_spec = new URISpec('holds', 'id'); 46 + self::$_registry->add(get_called_class()); 47 + } 48 + 49 + /** 50 + ** Voids a pending hold. This releases the held funds. Once voided a hold 51 + * is not longer pending can cannot be re-captured or re-voided. 52 + * 53 + * @return \Balanced\Hold 54 + */ 55 + public function void() 56 + { 57 + $this->is_void = true; 58 + return $this->save(); 59 + } 60 + 61 + /** 62 + * Captures a pending hold. This results in a debit. Once captured a hold 63 + * is not longer pending can cannot be re-captured or re-voided. 64 + * 65 + * @param int amount Optional Portion of the pending hold to capture. If not specified the full amount associated with the hold is captured. 66 + * 67 + * @return \Balanced\Debit 68 + */ 69 + public function capture($amount = null) 70 + { 71 + $this->debit = $this->account->debits->create(array( 72 + 'hold_uri' => $this->uri, 73 + 'amount' => $amount, 74 + )); 75 + return $this->debit; 76 + } 77 + }
+325
externals/balanced-php/src/Balanced/Marketplace.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use Balanced\Errors; 7 + use Balanced\Account; 8 + use \RESTful\URISpec; 9 + 10 + /** 11 + * Represents a marketplace. 12 + * 13 + * To get started you create an api key and then create a marketplace: 14 + * 15 + * <code> 16 + * $api_key = new \Balanced\APIKey(); 17 + * $api_key->save(); 18 + * $secret = $api_key->secret // better save this somewhere 19 + * print $secret; 20 + * \Balanced\Settings::$api_key = $secret; 21 + * 22 + * $marketplace = new \Balanced\Marketplace(); 23 + * $marketplace->save(); 24 + * var_dump($marketplace); 25 + * </code> 26 + * 27 + * Each api key is uniquely associated with an api key so once you've created a 28 + * marketplace: 29 + * 30 + * <code> 31 + * \Balanced\Settings::$api_key = $secret; 32 + * $marketplace = \Balanced\Marketplace::mine(); // this is the marketplace associated with $secret 33 + * </code> 34 + */ 35 + class Marketplace extends Resource 36 + { 37 + protected static $_uri_spec = null; 38 + 39 + public static function init() 40 + { 41 + self::$_uri_spec = new URISpec('marketplaces', 'id', '/v1'); 42 + self::$_registry->add(get_called_class()); 43 + } 44 + 45 + /** 46 + * Get the marketplace associated with the currently configured 47 + * \Balanced\Settings::$api_key. 48 + * 49 + * @throws \RESTful\Exceptions\NoResultFound 50 + * @return \Balanced\Marketplace 51 + */ 52 + public static function mine() 53 + { 54 + return self::query()->one(); 55 + } 56 + 57 + /** 58 + * Create a card. These can later be associated with an account using 59 + * \Balanced\Account->addCard or \Balanced\Marketplace->createBuyer. 60 + * 61 + * @param string street_address Street address. Use null if there is no address for the card. 62 + * @param string city City. Use null if there is no address for the card. 63 + * @param string postal_code Postal code. Use null if there is no address for the card. 64 + * @param string name Name as it appears on the card. 65 + * @param string card_number Card number. 66 + * @param string security_code Card security code. Use null if it is no available. 67 + * @param int expiration_month Expiration month. 68 + * @param int expiration_year Expiration year. 69 + * 70 + * @return \Balanced\Card 71 + */ 72 + public function createCard( 73 + $street_address, 74 + $city, 75 + $region, 76 + $postal_code, 77 + $name, 78 + $card_number, 79 + $security_code, 80 + $expiration_month, 81 + $expiration_year) 82 + { 83 + if ($region != null && strlen($region) > 0) { 84 + trigger_error("The region parameter will be deprecated in the next minor version of balanced-php", E_USER_NOTICE); 85 + } 86 + 87 + return $this->cards->create(array( 88 + 'street_address' => $street_address, 89 + 'city' => $city, 90 + 'region' => $region, 91 + 'postal_code' => $postal_code, 92 + 'name' => $name, 93 + 'card_number' => $card_number, 94 + 'security_code' => $security_code, 95 + 'expiration_month' => $expiration_month, 96 + 'expiration_year' => $expiration_year 97 + )); 98 + } 99 + 100 + /** 101 + * Create a bank account. These can later be associated with an account 102 + * using \Balanced\Account->addBankAccount. 103 + * 104 + * @param string name Name of the account holder. 105 + * @param string account_number Account number. 106 + * @param string routing_number Bank code or routing number. 107 + * @param string type checking or savings 108 + * @param array meta Single level mapping from string keys to string values. 109 + * 110 + * @return \Balanced\BankAccount 111 + */ 112 + public function createBankAccount( 113 + $name, 114 + $account_number, 115 + $routing_number, 116 + $type, 117 + $meta = null 118 + ) 119 + { 120 + return $this->bank_accounts->create(array( 121 + 'name' => $name, 122 + 'account_number' => $account_number, 123 + 'routing_number' => $routing_number, 124 + 'type' => $type, 125 + 'meta' => $meta 126 + )); 127 + } 128 + 129 + /** 130 + * Create a role-less account. You can later turn this into a buyer by 131 + * adding a funding source (e.g a card) or a merchant using 132 + * \Balanced\Account->promoteToMerchant. 133 + * 134 + * @param string email_address Optional email address. There can only be one account with this email address. 135 + * @param array[string]string meta Optional metadata to associate with the account. 136 + * 137 + * @return \Balanced\Account 138 + */ 139 + public function createAccount($email_address = null, $meta = null) 140 + { 141 + return $this->accounts->create(array( 142 + 'email_address' => $email_address, 143 + 'meta' => $meta, 144 + )); 145 + } 146 + 147 + /** 148 + * Find or create a role-less account by email address. You can later turn 149 + * this into a buyer by adding a funding source (e.g a card) or a merchant 150 + * using \Balanced\Account->promoteToMerchant. 151 + * 152 + * @param string email_address Email address. There can only be one account with this email address. 153 + * 154 + * @return \Balanced\Account 155 + */ 156 + function findOrCreateAccountByEmailAddress($email_address) 157 + { 158 + $marketplace = Marketplace::mine(); 159 + try { 160 + $account = $this->accounts->create(array( 161 + 'email_address' => $email_address 162 + )); 163 + } 164 + catch (Errors\DuplicateAccountEmailAddress $e) { 165 + $account = Account::get($e->extras->account_uri); 166 + } 167 + return $account; 168 + } 169 + 170 + /** 171 + * Create a buyer account. 172 + * 173 + * @param string email_address Optional email address. There can only be one account with this email address. 174 + * @param string card_uri URI referencing a card to associate with the account. 175 + * @param array[string]string meta Optional metadata to associate with the account. 176 + * @param string name Optional name of the account. 177 + * 178 + * @return \Balanced\Account 179 + */ 180 + public function createBuyer($email_address, $card_uri, $meta = null, $name = null) 181 + { 182 + return $this->accounts->create(array( 183 + 'email_address' => $email_address, 184 + 'card_uri' => $card_uri, 185 + 'meta' => $meta, 186 + 'name' => $name 187 + )); 188 + } 189 + 190 + /** 191 + * Create a merchant account. 192 + * 193 + * Unlike buyers the identity of a merchant must be established before 194 + * the account can function as a merchant (i.e. be credited). A merchant 195 + * can be either a person or a business. Either way that information is 196 + * represented as an associative array and passed as the merchant parameter 197 + * when creating the merchant account. 198 + * 199 + * For a person the array looks like this: 200 + * 201 + * <code> 202 + * array( 203 + * 'type' => 'person', 204 + * 'name' => 'William James', 205 + * 'tax_id' => '393-48-3992', 206 + * 'street_address' => '167 West 74th Street', 207 + * 'postal_code' => '10023', 208 + * 'dob' => '1842-01-01', 209 + * 'phone_number' => '+16505551234', 210 + * 'country_code' => 'USA' 211 + * ) 212 + * </code> 213 + * 214 + * For a business the array looks like this: 215 + * 216 + * <code> 217 + * array( 218 + * 'type' => 'business', 219 + * 'name' => 'Levain Bakery', 220 + * 'tax_id' => '253912384', 221 + * 'street_address' => '167 West 74th Street', 222 + * 'postal_code' => '10023', 223 + * 'phone_number' => '+16505551234', 224 + * 'country_code' => 'USA', 225 + * 'person' => array( 226 + * 'name' => 'William James', 227 + * 'tax_id' => '393483992', 228 + * 'street_address' => '167 West 74th Street', 229 + * 'postal_code' => '10023', 230 + * 'dob' => '1842-01-01', 231 + * 'phone_number' => '+16505551234', 232 + * 'country_code' => 'USA', 233 + * ) 234 + * ) 235 + * </code> 236 + * 237 + * In some cases the identity of the merchant, person or business, cannot 238 + * be verified in which case a \Balanced\Exceptions\HTTPError is thrown: 239 + * 240 + * <code> 241 + * $identity = array( 242 + * 'type' => 'business', 243 + * 'name' => 'Levain Bakery', 244 + * 'tax_id' => '253912384', 245 + * 'street_address' => '167 West 74th Street', 246 + * 'postal_code' => '10023', 247 + * 'phone_number' => '+16505551234', 248 + * 'country_code' => 'USA', 249 + * 'person' => array( 250 + * 'name' => 'William James', 251 + * 'tax_id' => '393483992', 252 + * 'street_address' => '167 West 74th Street', 253 + * 'postal_code' => '10023', 254 + * 'dob' => '1842-01-01', 255 + * 'phone_number' => '+16505551234', 256 + * 'country_code' => 'USA', 257 + * ), 258 + * ); 259 + * 260 + * try { 261 + * $merchant = \Balanced\Marketplace::mine()->createMerchant( 262 + * 'merchant@example.com', 263 + * $identity, 264 + * ); 265 + * catch (\Balanced\Exceptions\HTTPError $e) { 266 + * if ($e->code != 300) { 267 + * throw $e; 268 + * } 269 + * print e->response->header['Location'] // this is where merchant must signup 270 + * } 271 + * </code> 272 + * 273 + * Once the merchant has completed signup you can use the resulting URI to 274 + * create an account for them on your marketplace: 275 + * 276 + * <code> 277 + * $merchant = self::$marketplace->createMerchant( 278 + * 'merchant@example.com', 279 + * null, 280 + * null, 281 + * $merchant_uri 282 + * ); 283 + * </coe> 284 + * 285 + * @param string email_address Optional email address. There can only be one account with this email address. 286 + * @param array[string]mixed merchant Associative array describing the merchants identity. 287 + * @param string $bank_account_uri Optional URI referencing a bank account to associate with this account. 288 + * @param string $merchant_uri URI of a merchant created via the redirection sign-up flow. 289 + * @param string $name Optional name of the merchant. 290 + * @param array[string]string meta Optional metadata to associate with the account. 291 + * 292 + * @return \Balanced\Account 293 + */ 294 + public function createMerchant( 295 + $email_address = null, 296 + $merchant = null, 297 + $bank_account_uri = null, 298 + $merchant_uri = null, 299 + $name = null, 300 + $meta = null) 301 + { 302 + return $this->accounts->create(array( 303 + 'email_address' => $email_address, 304 + 'merchant' => $merchant, 305 + 'merchant_uri' => $merchant_uri, 306 + 'bank_account_uri' => $bank_account_uri, 307 + 'name' => $name, 308 + 'meta' => $meta, 309 + )); 310 + } 311 + 312 + /* 313 + * Create a callback. 314 + * 315 + * @param string url URL of callback. 316 + */ 317 + public function createCallback( 318 + $url 319 + ) 320 + { 321 + return $this->callbacks->create(array( 322 + 'url' => $url 323 + )); 324 + } 325 + }
+51
externals/balanced-php/src/Balanced/Merchant.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents a merchant identity. 10 + * 11 + * These are optionally created and associated with an account via 12 + * \Balanced\Marketplace::createMerchant which establishes a merchant account 13 + * on a marketplace. 14 + * 15 + * In some cases a merchant may need to be redirected to create a identity (e.g. the 16 + * information provided cannot be verified, more information is needed, etc). That 17 + * redirected signup results in a merchant_uri which is then associated with an 18 + * account on the marketplace via \Balanced\Marketplace::createMerchant. 19 + * 20 + * @see \Balanced\Marketplace 21 + */ 22 + class Merchant extends Resource 23 + { 24 + protected static $_uri_spec = null; 25 + 26 + public static function init() 27 + { 28 + self::$_uri_spec = new URISpec('merchants', 'id', '/v1'); 29 + self::$_registry->add(get_called_class()); 30 + } 31 + 32 + /** 33 + * Return the merchant identity associated with the current 34 + * Balanced\Settings::$api_key. If you are not authenticated (i.e. 35 + * ) then Balanced\Exceptions\NoResult 36 + * will be thrown. 37 + * 38 + * <code> 39 + * $merchant = \Balanced\Merchant::me(); 40 + * $owner_account = \Balanced\Marketplace::mine()->owner_account; 41 + * assert($merchant->id == $owner_account->merchant->id); 42 + * </code> 43 + * 44 + * @throws \RESTful\Exceptions\NoResultFound 45 + * @return \Balanced\Merchant 46 + */ 47 + public static function me() 48 + { 49 + return self::query()->one(); 50 + } 51 + }
+50
externals/balanced-php/src/Balanced/Refund.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Resource; 6 + use \RESTful\URISpec; 7 + 8 + /** 9 + * Represents a refund of an account debit transaction. 10 + * 11 + * You create these via Balanced\Debit::refund. 12 + * 13 + * <code> 14 + * $marketplace = \Balanced\Marketplace::mine(); 15 + * 16 + * $account = $marketplace 17 + * ->accounts 18 + * ->query() 19 + * ->filter(Account::f->email_address->eq('buyer@example.com')) 20 + * ->one(); 21 + * 22 + * $debit = $account->debit( 23 + * 100, 24 + * 'how it appears on the statement', 25 + * 'a description', 26 + * array( 27 + * 'my_id': '443322' 28 + * ) 29 + * ); 30 + * 31 + * $debit->refund( 32 + * 99, 33 + * 'some description', 34 + * array( 35 + * 'my_id': '123123' 36 + * ) 37 + * ); 38 + * </code> 39 + */ 40 + class Refund extends Resource 41 + { 42 + protected static $_uri_spec = null; 43 + 44 + public static function init() 45 + { 46 + self::$_uri_spec = new URISpec('refunds', 'id'); 47 + self::$_registry->add(get_called_class()); 48 + } 49 + } 50 +
+48
externals/balanced-php/src/Balanced/Resource.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + use Balanced\Errors\Error; 6 + use RESTful\Exceptions\HTTPError; 7 + 8 + class Resource extends \RESTful\Resource 9 + { 10 + public static $fields, $f; 11 + 12 + protected static $_client, $_registry, $_uri_spec; 13 + 14 + public static function init() 15 + { 16 + self::$_client = new \RESTful\Client('\Balanced\Settings', null, __NAMESPACE__ .'\Resource::convertError'); 17 + self::$_registry = new \RESTful\Registry(); 18 + self::$f = self::$fields = new \RESTful\Fields(); 19 + } 20 + 21 + public static function convertError($response) 22 + { 23 + if (property_exists($response->body, 'category_code') && 24 + array_key_exists($response->body->category_code, Error::$codes)) 25 + $error = new Error::$codes[$response->body->category_code]($response); 26 + else 27 + $error = new HTTPError($response); 28 + return $error; 29 + } 30 + 31 + public static function getClient() 32 + { 33 + $class = get_called_class(); 34 + return $class::$_client; 35 + } 36 + 37 + public static function getRegistry() 38 + { 39 + $class = get_called_class(); 40 + return $class::$_registry; 41 + } 42 + 43 + public static function getURISpec() 44 + { 45 + $class = get_called_class(); 46 + return $class::$_uri_spec; 47 + } 48 + }
+43
externals/balanced-php/src/Balanced/Settings.php
··· 1 + <?php 2 + 3 + namespace Balanced; 4 + 5 + /** 6 + * Configurable settings. 7 + * 8 + * You can either set these settings individually: 9 + * 10 + * <code> 11 + * \Balanced\Settngs::api_key = 'my-api-key-secret'; 12 + * </code> 13 + * 14 + * or all at once: 15 + * 16 + * <code> 17 + * \Balanced\Settngs::configure( 18 + * 'https://api.balancedpayments.com', 19 + * 'my-api-key-secret' 20 + * ); 21 + * </code> 22 + */ 23 + class Settings 24 + { 25 + const VERSION = '0.7.1'; 26 + 27 + public static $url_root = 'https://api.balancedpayments.com', 28 + $api_key = null, 29 + $agent = 'balanced-php', 30 + $version = Settings::VERSION; 31 + 32 + /** 33 + * Configure all settings. 34 + * 35 + * @param string url_root The root (schema://hostname[:port]) to use when constructing api URLs. 36 + * @param string api_key The api key secret to use for authenticating when talking to the api. If null then api usage is limited to uauthenticated endpoints. 37 + */ 38 + public static function configure($url_root, $api_key) 39 + { 40 + self::$url_root= $url_root; 41 + self::$api_key = $api_key; 42 + } 43 + }
+614
externals/balanced-php/tests/Balanced/ResourceTest.php
··· 1 + <?php 2 + 3 + namespace Balanced\Test; 4 + 5 + \Balanced\Bootstrap::init(); 6 + \RESTful\Bootstrap::init(); 7 + \Httpful\Bootstrap::init(); 8 + 9 + use Balanced\Resource; 10 + use Balanced\Settings; 11 + use Balanced\APIKey; 12 + use Balanced\Marketplace; 13 + use Balanced\Credit; 14 + use Balanced\Debit; 15 + use Balanced\Refund; 16 + use Balanced\Account; 17 + use Balanced\Merchant; 18 + use Balanced\BankAccount; 19 + use Balanced\Card; 20 + use Balanced\Hold; 21 + 22 + use \RESTful\Collection; 23 + 24 + 25 + class APIKeyTest extends \PHPUnit_Framework_TestCase 26 + { 27 + 28 + function testRegistry() 29 + { 30 + $this->expectOutputString(''); 31 + $result = Resource::getRegistry()->match('/v1/api_keys'); 32 + return; 33 + $expected = array( 34 + 'collection' => true, 35 + 'class' => 'Balanced\APIKey', 36 + ); 37 + $this->assertEquals($expected, $result); 38 + $result = Resource::getRegistry()->match('/v1/api_keys/1234'); 39 + $expected = array( 40 + 'collection' => false, 41 + 'class' => 'Balanced\APIKey', 42 + 'ids' => array('id' => '1234'), 43 + ); 44 + $this->assertEquals($expected, $result); 45 + } 46 + } 47 + 48 + class MarketplaceTest extends \PHPUnit_Framework_TestCase 49 + { 50 + function testRegistry() 51 + { 52 + $result = Resource::getRegistry()->match('/v1/marketplaces'); 53 + $expected = array( 54 + 'collection' => true, 55 + 'class' => 'Balanced\Marketplace', 56 + ); 57 + $this->assertEquals($expected, $result); 58 + $result = Resource::getRegistry()->match('/v1/marketplaces/1122'); 59 + $expected = array( 60 + 'collection' => false, 61 + 'class' => 'Balanced\Marketplace', 62 + 'ids' => array('id' => '1122'), 63 + ); 64 + $this->assertEquals($expected, $result); 65 + } 66 + 67 + function testCreateCard() 68 + { 69 + $collection = $this->getMock( 70 + '\RESTful\Collection', 71 + array('create'), 72 + array('\Balanced\Card', 'some/uri', null) 73 + ); 74 + 75 + $collection->expects($this->once()) 76 + ->method('create') 77 + ->with(array( 78 + 'street_address' => '123 Fake Street', 79 + 'city' => 'Jollywood', 80 + 'region' => '', 81 + 'postal_code' => '90210', 82 + 'name' => 'khalkhalash', 83 + 'card_number' => '4112344112344113', 84 + 'security_code' => '123', 85 + 'expiration_month' => 12, 86 + 'expiration_year' => 2013, 87 + )); 88 + 89 + $marketplace = new Marketplace(array('cards' => $collection)); 90 + $marketplace->createCard( 91 + '123 Fake Street', 92 + 'Jollywood', 93 + '', 94 + '90210', 95 + 'khalkhalash', 96 + '4112344112344113', 97 + '123', 98 + 12, 99 + 2013); 100 + } 101 + 102 + function testCreateBankAccount() 103 + { 104 + $collection = $this->getMock( 105 + '\RESTful\Collection', 106 + array('create'), 107 + array('\Balanced\BankAccount', 'some/uri', null) 108 + ); 109 + 110 + $collection->expects($this->once()) 111 + ->method('create') 112 + ->with(array( 113 + 'name' => 'Homer Jay', 114 + 'account_number' => '112233a', 115 + 'routing_number' => '121042882', 116 + 'type' => 'savings', 117 + 'meta' => null 118 + )); 119 + 120 + $marketplace = new Marketplace(array('bank_accounts' => $collection)); 121 + $marketplace->createBankAccount( 122 + 'Homer Jay', 123 + '112233a', 124 + '121042882', 125 + 'savings'); 126 + } 127 + 128 + function testCreateAccount() 129 + { 130 + $collection = $this->getMock( 131 + '\RESTful\Collection', 132 + array('create'), 133 + array('\Balanced\Account', 'some/uri', null) 134 + ); 135 + 136 + $collection->expects($this->once()) 137 + ->method('create') 138 + ->with(array( 139 + 'email_address' => 'role-less@example.com', 140 + 'meta' => array('test#' => 'test_d') 141 + )); 142 + 143 + $marketplace = new Marketplace(array('accounts' => $collection)); 144 + $marketplace->createAccount( 145 + 'role-less@example.com', 146 + array('test#' => 'test_d') 147 + ); 148 + } 149 + 150 + function testCreateBuyer() 151 + { 152 + $collection = $this->getMock( 153 + '\RESTful\Collection', 154 + array('create'), 155 + array('\Balanced\Account', 'some/uri', null) 156 + ); 157 + 158 + $collection->expects($this->once()) 159 + ->method('create') 160 + ->with(array( 161 + 'email_address' => 'buyer@example.com', 162 + 'card_uri' => '/some/card/uri', 163 + 'meta' => array('test#' => 'test_d'), 164 + 'name' => 'Buy Er' 165 + )); 166 + 167 + $marketplace = new Marketplace(array('accounts' => $collection)); 168 + $marketplace->createBuyer( 169 + 'buyer@example.com', 170 + '/some/card/uri', 171 + array('test#' => 'test_d'), 172 + 'Buy Er' 173 + ); 174 + } 175 + } 176 + 177 + class AccountTest extends \PHPUnit_Framework_TestCase 178 + { 179 + function testRegistry() 180 + { 181 + $result = Resource::getRegistry()->match('/v1/accounts'); 182 + $expected = array( 183 + 'collection' => true, 184 + 'class' => 'Balanced\Account', 185 + ); 186 + $this->assertEquals($expected, $result); 187 + $result = Resource::getRegistry()->match('/v1/accounts/0099'); 188 + $expected = array( 189 + 'collection' => false, 190 + 'class' => 'Balanced\Account', 191 + 'ids' => array('id' => '0099'), 192 + ); 193 + $this->assertEquals($expected, $result); 194 + } 195 + 196 + function testCredit() 197 + { 198 + $collection = $this->getMock( 199 + '\RESTful\Collection', 200 + array('create'), 201 + array('\Balanced\Credit', 'some/uri', null) 202 + ); 203 + 204 + $collection 205 + ->expects($this->once()) 206 + ->method('create') 207 + ->with(array( 208 + 'amount' => 101, 209 + 'description' => 'something sweet', 210 + 'meta' => null, 211 + 'destination_uri' => null, 212 + 'appears_on_statement_as' => null 213 + )); 214 + 215 + $account = new Account(array('credits' => $collection)); 216 + $account->credit(101, 'something sweet'); 217 + } 218 + 219 + function testDebit() 220 + { 221 + $collection = $this->getMock( 222 + '\RESTful\Collection', 223 + array('create'), 224 + array('\Balanced\Debit', 'some/uri', null) 225 + ); 226 + 227 + $collection 228 + ->expects($this->once()) 229 + ->method('create') 230 + ->with(array( 231 + 'amount' => 9911, 232 + 'description' => 'something tangy', 233 + 'appears_on_statement_as' => 'BAL*TANG', 234 + 'meta' => null, 235 + 'source_uri' => null, 236 + 'on_behalf_of_uri' => null, 237 + )); 238 + 239 + $account = new Account(array('debits' => $collection)); 240 + $account->debit(9911, 'BAL*TANG', 'something tangy'); 241 + } 242 + 243 + function testHold() 244 + { 245 + $collection = $this->getMock( 246 + '\RESTful\Collection', 247 + array('create'), 248 + array('\Balanced\Hold', 'some/uri', null) 249 + ); 250 + 251 + $collection 252 + ->expects($this->once()) 253 + ->method('create') 254 + ->with(array( 255 + 'amount' => 1243, 256 + 'description' => 'something crispy', 257 + 'source_uri' => '/some/card/uri', 258 + 'meta' => array('test#' => 'test_d') 259 + )); 260 + 261 + $account = new Account(array('holds' => $collection)); 262 + $account->hold( 263 + 1243, 264 + 'something crispy', 265 + '/some/card/uri', 266 + array('test#' => 'test_d') 267 + ); 268 + } 269 + 270 + function testAddCard() 271 + { 272 + $account = $this->getMock( 273 + '\Balanced\Account', 274 + array('save') 275 + ); 276 + 277 + $account 278 + ->expects($this->once()) 279 + ->method('save') 280 + ->with(); 281 + 282 + $account->addCard('/my/new/card/121212'); 283 + $this->assertEquals($account->card_uri, '/my/new/card/121212'); 284 + } 285 + 286 + function testAddBankAccount() 287 + { 288 + $account = $this->getMock( 289 + '\Balanced\Account', 290 + array('save') 291 + ); 292 + 293 + $account 294 + ->expects($this->once()) 295 + ->method('save') 296 + ->with(); 297 + 298 + $account->addBankAccount('/my/new/bank_account/121212'); 299 + $this->assertEquals($account->bank_account_uri, '/my/new/bank_account/121212'); 300 + } 301 + 302 + function testPromotToMerchant() 303 + { 304 + $account = $this->getMock( 305 + '\Balanced\Account', 306 + array('save') 307 + ); 308 + 309 + $account 310 + ->expects($this->once()) 311 + ->method('save') 312 + ->with(); 313 + 314 + $merchant = array( 315 + 'type' => 'person', 316 + 'name' => 'William James', 317 + 'tax_id' => '393-48-3992', 318 + 'street_address' => '167 West 74th Street', 319 + 'postal_code' => '10023', 320 + 'dob' => '1842-01-01', 321 + 'phone_number' => '+16505551234', 322 + 'country_code' => 'USA' 323 + ); 324 + 325 + $account->promoteToMerchant($merchant); 326 + $this->assertEquals($account->merchant, $merchant); 327 + } 328 + } 329 + 330 + class HoldTest extends \PHPUnit_Framework_TestCase 331 + { 332 + function testRegistry() 333 + { 334 + $result = Resource::getRegistry()->match('/v1/holds'); 335 + $expected = array( 336 + 'collection' => true, 337 + 'class' => 'Balanced\Hold', 338 + ); 339 + $this->assertEquals($expected, $result); 340 + $result = Resource::getRegistry()->match('/v1/holds/112233'); 341 + $expected = array( 342 + 'collection' => false, 343 + 'class' => 'Balanced\Hold', 344 + 'ids' => array('id' => '112233'), 345 + ); 346 + $this->assertEquals($expected, $result); 347 + } 348 + 349 + function testVoid() 350 + { 351 + $hold = $this->getMock( 352 + '\Balanced\Hold', 353 + array('save') 354 + ); 355 + 356 + $hold 357 + ->expects($this->once()) 358 + ->method('save') 359 + ->with(); 360 + 361 + $hold->void(); 362 + $this->assertTrue($hold->is_void); 363 + } 364 + 365 + function testCapture() 366 + { 367 + $collection = $this->getMock( 368 + '\RESTful\Collection', 369 + array('create'), 370 + array('\Balanced\Debit', 'some/uri', null) 371 + ); 372 + 373 + $collection 374 + ->expects($this->once()) 375 + ->method('create') 376 + ->with(array( 377 + 'hold_uri' => 'some/hold/uri', 378 + 'amount' => 2211, 379 + )); 380 + 381 + $account = new Account(array('debits' => $collection)); 382 + 383 + $hold = new Hold(array('uri' => 'some/hold/uri', 'account' => $account)); 384 + 385 + $hold->capture(2211); 386 + } 387 + } 388 + 389 + class CreditTest extends \PHPUnit_Framework_TestCase 390 + { 391 + function testRegistry() 392 + { 393 + $result = Resource::getRegistry()->match('/v1/credits'); 394 + $expected = array( 395 + 'collection' => true, 396 + 'class' => 'Balanced\Credit', 397 + ); 398 + $this->assertEquals($expected, $result); 399 + $result = Resource::getRegistry()->match('/v1/credits/9988'); 400 + $expected = array( 401 + 'collection' => false, 402 + 'class' => 'Balanced\Credit', 403 + 'ids' => array('id' => '9988'), 404 + ); 405 + $this->assertEquals($expected, $result); 406 + } 407 + } 408 + 409 + class DebitTest extends \PHPUnit_Framework_TestCase 410 + { 411 + function testRegistry() 412 + { 413 + $result = Resource::getRegistry()->match('/v1/debits'); 414 + $expected = array( 415 + 'collection' => true, 416 + 'class' => 'Balanced\Debit', 417 + ); 418 + $this->assertEquals($expected, $result); 419 + $result = Resource::getRegistry()->match('/v1/debits/4545'); 420 + $expected = array( 421 + 'collection' => false, 422 + 'class' => 'Balanced\Debit', 423 + 'ids' => array('id' => '4545'), 424 + ); 425 + $this->assertEquals($expected, $result); 426 + } 427 + 428 + function testRefund() 429 + { 430 + $collection = $this->getMock( 431 + '\RESTful\Collection', 432 + array('create'), 433 + array('\Balanced\Refund', 'some/uri', null) 434 + ); 435 + 436 + $collection 437 + ->expects($this->once()) 438 + ->method('create') 439 + ->with(array( 440 + 'amount' => 5645, 441 + 'description' => null, 442 + 'meta' => array('test#' => 'test_d') 443 + )); 444 + 445 + $debit = new Debit(array('refunds' => $collection)); 446 + 447 + $debit->refund(5645, null, array('test#' => 'test_d')); 448 + } 449 + } 450 + 451 + class RefundTest extends \PHPUnit_Framework_TestCase 452 + { 453 + function testRegistry() 454 + { 455 + $result = Resource::getRegistry()->match('/v1/refunds'); 456 + $expected = array( 457 + 'collection' => true, 458 + 'class' => 'Balanced\Refund', 459 + ); 460 + $this->assertEquals($expected, $result); 461 + $result = Resource::getRegistry()->match('/v1/refunds/1287'); 462 + $expected = array( 463 + 'collection' => false, 464 + 'class' => 'Balanced\Refund', 465 + 'ids' => array('id' => '1287'), 466 + ); 467 + $this->assertEquals($expected, $result); 468 + } 469 + } 470 + 471 + class BankAccountTest extends \PHPUnit_Framework_TestCase 472 + { 473 + function testRegistry() 474 + { 475 + $result = Resource::getRegistry()->match('/v1/bank_accounts'); 476 + $expected = array( 477 + 'collection' => true, 478 + 'class' => 'Balanced\BankAccount', 479 + ); 480 + $this->assertEquals($expected, $result); 481 + $result = Resource::getRegistry()->match('/v1/bank_accounts/887766'); 482 + $expected = array( 483 + 'collection' => false, 484 + 'class' => 'Balanced\BankAccount', 485 + 'ids' => array('id' => '887766'), 486 + ); 487 + $this->assertEquals($expected, $result); 488 + } 489 + 490 + function testCreditAccount() 491 + { 492 + $collection = $this->getMock( 493 + '\RESTful\Collection', 494 + array('create'), 495 + array('\Balanced\Credit', 'some/uri', null) 496 + ); 497 + 498 + $collection 499 + ->expects($this->once()) 500 + ->method('create') 501 + ->with(array( 502 + 'amount' => 101, 503 + 'description' => 'something super sweet', 504 + 'meta' => null, 505 + 'destination_uri' => '/some/other/uri', 506 + 'appears_on_statement_as' => null 507 + )); 508 + 509 + $account = new Account(array('credits' => $collection)); 510 + $bank_account = new BankAccount(array('uri' => '/some/other/uri', 'account' => $account)); 511 + 512 + $bank_account->credit(101, 'something super sweet'); 513 + } 514 + 515 + function testCreditAccountless() 516 + { 517 + $collection = $this->getMock( 518 + '\RESTful\Collection', 519 + array('create'), 520 + array('\Balanced\Credit', 'some/uri', null) 521 + ); 522 + 523 + $collection 524 + ->expects($this->once()) 525 + ->method('create') 526 + ->with(array( 527 + 'amount' => 101, 528 + 'description' => 'something super sweet', 529 + )); 530 + $bank_account = new BankAccount(array( 531 + 'uri' => '/some/other/uri', 532 + 'account' => null, 533 + 'credits' => $collection, 534 + )); 535 + 536 + $bank_account->credit(101, 'something super sweet'); 537 + } 538 + } 539 + 540 + class CardTest extends \PHPUnit_Framework_TestCase 541 + { 542 + function testRegistry() 543 + { 544 + $result = Resource::getRegistry()->match('/v1/cards'); 545 + $expected = array( 546 + 'collection' => true, 547 + 'class' => 'Balanced\Card', 548 + ); 549 + $this->assertEquals($expected, $result); 550 + $result = Resource::getRegistry()->match('/v1/cards/136asd6713'); 551 + $expected = array( 552 + 'collection' => false, 553 + 'class' => 'Balanced\Card', 554 + 'ids' => array('id' => '136asd6713'), 555 + ); 556 + $this->assertEquals($expected, $result); 557 + } 558 + 559 + function testDebit() 560 + { 561 + $collection = $this->getMock( 562 + '\RESTful\Collection', 563 + array('create'), 564 + array('\Balanced\Debit', 'some/uri', null) 565 + ); 566 + 567 + $account = new Account(array('debits' => $collection)); 568 + $card = new Card(array('uri' => '/some/uri', 'account' => $account )); 569 + 570 + $collection 571 + ->expects($this->once()) 572 + ->method('create') 573 + ->with(array( 574 + 'amount' => 9911, 575 + 'description' => 'something tangy', 576 + 'appears_on_statement_as' => 'BAL*TANG', 577 + 'meta' => null, 578 + 'source_uri' => '/some/uri', 579 + 'on_behalf_of_uri' => null, 580 + )); 581 + 582 + $card->debit(9911, 'BAL*TANG', 'something tangy'); 583 + } 584 + 585 + /** 586 + * @expectedException \UnexpectedValueException 587 + */ 588 + function testNotAssociatedDebit() 589 + { 590 + $card = new Card(array('uri' => '/some/uri', 'account' => null )); 591 + $card->debit(9911, 'BAL*TANG', 'something tangy'); 592 + } 593 + } 594 + 595 + 596 + class MerchantTest extends \PHPUnit_Framework_TestCase 597 + { 598 + function testRegistry() 599 + { 600 + $result = Resource::getRegistry()->match('/v1/merchants'); 601 + $expected = array( 602 + 'collection' => true, 603 + 'class' => 'Balanced\Merchant', 604 + ); 605 + $this->assertEquals($expected, $result); 606 + $result = Resource::getRegistry()->match('/v1/merchants/136asd6713'); 607 + $expected = array( 608 + 'collection' => false, 609 + 'class' => 'Balanced\Merchant', 610 + 'ids' => array('id' => '136asd6713'), 611 + ); 612 + $this->assertEquals($expected, $result); 613 + } 614 + }
+800
externals/balanced-php/tests/Balanced/SuiteTest.php
··· 1 + <?php 2 + 3 + namespace Balanced\Test; 4 + 5 + \Balanced\Bootstrap::init(); 6 + \RESTful\Bootstrap::init(); 7 + \Httpful\Bootstrap::init(); 8 + 9 + use Balanced\Settings; 10 + use Balanced\APIKey; 11 + use Balanced\Marketplace; 12 + use Balanced\Credit; 13 + use Balanced\Debit; 14 + use Balanced\Refund; 15 + use Balanced\Account; 16 + use Balanced\Merchant; 17 + use Balanced\BankAccount; 18 + use Balanced\Card; 19 + 20 + 21 + /** 22 + * Suite test cases. These talk to an API server and so make network calls. 23 + * 24 + * Environment variables can be used to control client settings: 25 + * 26 + * <ul> 27 + * <li>$BALANCED_URL_ROOT If set applies to \Balanced\Settings::$url_root. 28 + * <li>$BALANCED_API_KEY If set applies to \Balanced\Settings::$api_key. 29 + * </ul> 30 + * 31 + * @group suite 32 + */ 33 + class SuiteTest extends \PHPUnit_Framework_TestCase 34 + { 35 + static $key, 36 + $marketplace, 37 + $email_counter = 0; 38 + 39 + static function _createBuyer($email_address = null, $card = null) 40 + { 41 + if ($email_address == null) 42 + $email_address = sprintf('m+%d@poundpay.com', self::$email_counter++); 43 + if ($card == null) 44 + $card = self::_createCard(); 45 + return self::$marketplace->createBuyer( 46 + $email_address, 47 + $card->uri, 48 + array('test#' => 'test_d'), 49 + 'Hobo Joe' 50 + ); 51 + } 52 + 53 + static function _createCard($account = null) 54 + { 55 + $card = self::$marketplace->createCard( 56 + '123 Fake Street', 57 + 'Jollywood', 58 + null, 59 + '90210', 60 + 'khalkhalash', 61 + '4112344112344113', 62 + null, 63 + 12, 64 + 2013); 65 + if ($account != null) { 66 + $account->addCard($card); 67 + $card = Card::get($card->uri); 68 + } 69 + return $card; 70 + } 71 + 72 + static function _createBankAccount($account = null) 73 + { 74 + $bank_account = self::$marketplace->createBankAccount( 75 + 'Homer Jay', 76 + '112233a', 77 + '121042882', 78 + 'checking' 79 + ); 80 + if ($account != null) { 81 + $account->addBankAccount($bank_account); 82 + $bank_account = $account->bank_accounts[0]; 83 + } 84 + return $bank_account; 85 + } 86 + 87 + public static function _createPersonMerchant($email_address = null, $bank_account = null) 88 + { 89 + if ($email_address == null) 90 + $email_address = sprintf('m+%d@poundpay.com', self::$email_counter++); 91 + if ($bank_account == null) 92 + $bank_account = self::_createBankAccount(); 93 + $merchant = array( 94 + 'type' => 'person', 95 + 'name' => 'William James', 96 + 'tax_id' => '393-48-3992', 97 + 'street_address' => '167 West 74th Street', 98 + 'postal_code' => '10023', 99 + 'dob' => '1842-01-01', 100 + 'phone_number' => '+16505551234', 101 + 'country_code' => 'USA' 102 + ); 103 + return self::$marketplace->createMerchant( 104 + $email_address, 105 + $merchant, 106 + $bank_account->uri 107 + ); 108 + } 109 + 110 + public static function _createBusinessMerchant($email_address = null, $bank_account = null) 111 + { 112 + if ($email_address == null) 113 + $email_address = sprintf('m+%d@poundpay.com', self::$email_counter++); 114 + if ($bank_account == null) 115 + $bank_account = self::_createBankAccount(); 116 + $merchant = array( 117 + 'type' => 'business', 118 + 'name' => 'Levain Bakery', 119 + 'tax_id' => '253912384', 120 + 'street_address' => '167 West 74th Street', 121 + 'postal_code' => '10023', 122 + 'phone_number' => '+16505551234', 123 + 'country_code' => 'USA', 124 + 'person' => array( 125 + 'name' => 'William James', 126 + 'tax_id' => '393483992', 127 + 'street_address' => '167 West 74th Street', 128 + 'postal_code' => '10023', 129 + 'dob' => '1842-01-01', 130 + 'phone_number' => '+16505551234', 131 + 'country_code' => 'USA', 132 + ), 133 + ); 134 + return self::$marketplace->createMerchant( 135 + $email_address, 136 + $merchant, 137 + $bank_account->uri 138 + ); 139 + } 140 + 141 + public static function setUpBeforeClass() 142 + { 143 + // url root 144 + $url_root = getenv('BALANCED_URL_ROOT'); 145 + if ($url_root != '') { 146 + Settings::$url_root = $url_root; 147 + } 148 + else 149 + Settings::$url_root = 'https://api.balancedpayments.com'; 150 + 151 + // api key 152 + $api_key = getenv('BALANCED_API_KEY'); 153 + if ($api_key != '') { 154 + Settings::$api_key = $api_key; 155 + } 156 + else { 157 + self::$key = new APIKey(); 158 + self::$key->save(); 159 + Settings::$api_key = self::$key->secret; 160 + } 161 + 162 + // marketplace 163 + try { 164 + self::$marketplace = Marketplace::mine(); 165 + } 166 + catch(\RESTful\Exceptions\NoResultFound $e) { 167 + self::$marketplace = new Marketplace(); 168 + self::$marketplace->save(); 169 + } 170 + } 171 + 172 + function testMarketplaceMine() 173 + { 174 + $marketplace = Marketplace::mine(); 175 + $this->assertEquals($this::$marketplace->id, $marketplace->id); 176 + } 177 + 178 + /** 179 + * @expectedException \RESTful\Exceptions\HTTPError 180 + */ 181 + function testAnotherMarketplace() 182 + { 183 + $marketplace = new Marketplace(); 184 + $marketplace->save(); 185 + } 186 + 187 + /** 188 + * @expectedException \RESTful\Exceptions\HTTPError 189 + */ 190 + function testDuplicateEmailAddress() 191 + { 192 + self::_createBuyer('dupe@poundpay.com'); 193 + self::_createBuyer('dupe@poundpay.com'); 194 + } 195 + 196 + function testIndexMarketplace() 197 + { 198 + $marketplaces = Marketplace::query()->all(); 199 + $this->assertEquals(count($marketplaces), 1); 200 + } 201 + 202 + function testCreateBuyer() 203 + { 204 + self::_createBuyer(); 205 + } 206 + 207 + function testCreateAccountWithoutEmailAddress() 208 + { 209 + self::$marketplace->createAccount(); 210 + } 211 + 212 + function testFindOrCreateAccountByEmailAddress() 213 + { 214 + $account1 = self::$marketplace->createAccount('foc@example.com'); 215 + $account2 = self::$marketplace->findOrCreateAccountByEmailAddress('foc@example.com'); 216 + $this->assertEquals($account2->id, $account2->id); 217 + $account3 = self::$marketplace->findOrCreateAccountByEmailAddress('foc2@example.com'); 218 + $this->assertNotEquals($account3->id, $account1->id); 219 + } 220 + 221 + function testGetBuyer() 222 + { 223 + $buyer1 = self::_createBuyer(); 224 + $buyer2 = Account::get($buyer1->uri); 225 + $this->assertEquals($buyer1->id, $buyer2->id); 226 + } 227 + 228 + 229 + function testMe() 230 + { 231 + $marketplace = Marketplace::mine(); 232 + $merchant = Merchant::me(); 233 + $this->assertEquals($marketplace->id, $merchant->marketplace->id); 234 + } 235 + 236 + function testDebitAndRefundBuyer() 237 + { 238 + $buyer = self::_createBuyer(); 239 + $debit = $buyer->debit( 240 + 1000, 241 + 'Softie', 242 + 'something i bought', 243 + array('hi' => 'bye') 244 + ); 245 + $refund = $debit->refund(100); 246 + } 247 + 248 + /** 249 + * @expectedException \RESTful\Exceptions\HTTPError 250 + */ 251 + function testDebitZero() 252 + { 253 + $buyer = self::_createBuyer(); 254 + $debit = $buyer->debit( 255 + 0, 256 + 'Softie', 257 + 'something i bought' 258 + ); 259 + } 260 + 261 + function testMultipleRefunds() 262 + { 263 + $buyer = self::_createBuyer(); 264 + $debit = $buyer->debit( 265 + 1500, 266 + 'Softie', 267 + 'something tart', 268 + array('hi' => 'bye')); 269 + $refunds = array( 270 + $debit->refund(100), 271 + $debit->refund(100), 272 + $debit->refund(100), 273 + $debit->refund(100)); 274 + $expected_refund_ids = array_map( 275 + function($x) { 276 + return $x->id; 277 + }, $refunds); 278 + sort($expected_refund_ids); 279 + $this->assertEquals($debit->refunds->total(), 4); 280 + 281 + // itemization 282 + $total = 0; 283 + $refund_ids = array(); 284 + foreach ($debit->refunds as $refund) { 285 + $total += $refund->amount; 286 + array_push($refund_ids, $refund->id); 287 + } 288 + sort($refund_ids); 289 + $this->assertEquals($total, 400); 290 + $this->assertEquals($expected_refund_ids, $refund_ids); 291 + 292 + // pagination 293 + $total = 0; 294 + $refund_ids = array(); 295 + foreach ($debit->refunds->paginate() as $page) { 296 + foreach ($page->items as $refund) { 297 + $total += $refund->amount; 298 + array_push($refund_ids, $refund->id); 299 + } 300 + } 301 + sort($refund_ids); 302 + $this->assertEquals($total, 400); 303 + $this->assertEquals($expected_refund_ids, $refund_ids); 304 + } 305 + 306 + function testDebitSource() 307 + { 308 + $buyer = self::_createBuyer(); 309 + $card1 = self::_createCard($buyer); 310 + $card2 = self::_createCard($buyer); 311 + 312 + $credit = $buyer->debit( 313 + 1000, 314 + 'Softie', 315 + 'something i bought' 316 + ); 317 + $this->assertEquals($credit->source->id, $card2->id); 318 + 319 + $credit = $buyer->debit( 320 + 1000, 321 + 'Softie', 322 + 'something i bought', 323 + null, 324 + $card1 325 + ); 326 + $this->assertEquals($credit->source->id, $card1->id); 327 + } 328 + 329 + function testDebitOnBehalfOf() 330 + { 331 + $buyer = self::_createBuyer(); 332 + $merchant = self::$marketplace->createAccount(null); 333 + $card1 = self::_createCard($buyer); 334 + 335 + $debit = $buyer->debit(1000, null, null, null, null, $merchant); 336 + $this->assertEquals($debit->amount, 1000); 337 + // for now just test the debit succeeds. 338 + // TODO: once the on_behalf_of actually shows up on the response, test it. 339 + } 340 + 341 + /** 342 + * @expectedException \InvalidArgumentException 343 + */ 344 + function testDebitOnBehalfOfFailsForBuyer() 345 + { 346 + $buyer = self::_createBuyer(); 347 + $card1 = self::_createCard($buyer); 348 + $debit = $buyer->debit(1000, null, null, null, null, $buyer); 349 + } 350 + 351 + function testCreateAndVoidHold() 352 + { 353 + $buyer = self::_createBuyer(); 354 + $hold = $buyer->hold(1000); 355 + $this->assertEquals($hold->is_void, false); 356 + $hold->void(); 357 + $this->assertEquals($hold->is_void, true); 358 + } 359 + 360 + function testCreateAndCaptureHold() 361 + { 362 + $buyer = self::_createBuyer(); 363 + $hold = $buyer->hold(1000); 364 + $debit = $hold->capture(909); 365 + $this->assertEquals($debit->account->id, $buyer->id); 366 + $this->assertEquals($debit->hold->id, $hold->id); 367 + $this->assertEquals($hold->debit->id, $debit->id); 368 + } 369 + 370 + function testCreatePersonMerchant() 371 + { 372 + $merchant = self::_createPersonMerchant(); 373 + } 374 + 375 + function testCreateBusinessMerchant() 376 + { 377 + $merchant = self::_createBusinessMerchant(); 378 + } 379 + 380 + /** 381 + * @expectedException \RESTful\Exceptions\HTTPError 382 + */ 383 + function testCreditRequiresNonZeroAmount() 384 + { 385 + $buyer = self::_createBuyer(); 386 + $buyer->debit( 387 + 1000, 388 + 'Softie', 389 + 'something i bought' 390 + ); 391 + $merchant = self::_createBusinessMerchant(); 392 + $merchant->credit(0); 393 + } 394 + 395 + /** 396 + * @expectedException \RESTful\Exceptions\HTTPError 397 + */ 398 + function testCreditMoreThanEscrowBalanceFails() 399 + { 400 + $buyer = self::_createBuyer(); 401 + $buyer->credit( 402 + 1000, 403 + 'something i bought', 404 + null, 405 + null, 406 + 'Softie' 407 + ); 408 + $merchant = self::_createBusinessMerchant(); 409 + $merchant->credit(self::$marketplace->in_escrow + 1); 410 + } 411 + 412 + function testCreditDestiation() 413 + { 414 + $buyer = self::_createBuyer(); 415 + $buyer->debit(3000); # NOTE: build up escrow balance to credit 416 + 417 + $merchant = self::_createPersonMerchant(); 418 + $bank_account1 = self::_createBankAccount($merchant); 419 + $bank_account2 = self::_createBankAccount($merchant); 420 + 421 + $credit = $merchant->credit( 422 + 1000, 423 + 'something i sold', 424 + null, 425 + null, 426 + 'Softie' 427 + ); 428 + $this->assertEquals($credit->destination->id, $bank_account2->id); 429 + 430 + $credit = $merchant->credit( 431 + 1000, 432 + 'something i sold', 433 + null, 434 + $bank_account1, 435 + 'Softie' 436 + ); 437 + $this->assertEquals($credit->destination->id, $bank_account1->id); 438 + } 439 + 440 + function testAssociateCard() 441 + { 442 + $merchant = self::_createPersonMerchant(); 443 + $card = self::_createCard(); 444 + $merchant->addCard($card->uri); 445 + } 446 + 447 + function testAssociateBankAccount() 448 + { 449 + $merchant = self::_createPersonMerchant(); 450 + $bank_account = self::_createBankAccount(); 451 + $merchant->addBankAccount($bank_account->uri); 452 + } 453 + 454 + function testCardMasking() 455 + { 456 + $card = self::$marketplace->createCard( 457 + '123 Fake Street', 458 + 'Jollywood', 459 + null, 460 + '90210', 461 + 'khalkhalash', 462 + '4112344112344113', 463 + '123', 464 + 12, 465 + 2013); 466 + $this->assertEquals($card->last_four, '4113'); 467 + $this->assertFalse(property_exists($card, 'number')); 468 + } 469 + 470 + function testBankAccountMasking() 471 + { 472 + $bank_account = self::$marketplace->createBankAccount( 473 + 'Homer Jay', 474 + '112233a', 475 + '121042882', 476 + 'checking' 477 + ); 478 + $this->assertEquals($bank_account->last_four, '233a'); 479 + $this->assertEquals($bank_account->account_number, 'xxx233a'); 480 + } 481 + 482 + function testFilteringAndSorting() 483 + { 484 + $buyer = self::_createBuyer(); 485 + $debit1 = $buyer->debit(1122, null, null, array('tag' => '1')); 486 + $debit2 = $buyer->debit(3322, null, null, array('tag' => '1')); 487 + $debit3 = $buyer->debit(2211, null, null, array('tag' => '2')); 488 + 489 + $getId = function($o) { 490 + return $o->id; 491 + }; 492 + 493 + $debits = ( 494 + self::$marketplace->debits->query() 495 + ->filter(Debit::$f->meta->tag->eq('1')) 496 + ->sort(Debit::$f->created_at->asc()) 497 + ->all()); 498 + $debit_ids = array_map($getId, $debits); 499 + $this->assertEquals($debit_ids, array($debit1->id, $debit2->id)); 500 + 501 + $debits = ( 502 + self::$marketplace->debits->query() 503 + ->filter(Debit::$f->meta->tag->eq('2')) 504 + ->all()); 505 + $debit_ids = array_map($getId, $debits); 506 + $this->assertEquals($debit_ids, array($debit3->id)); 507 + 508 + $debits = ( 509 + self::$marketplace->debits->query() 510 + ->filter(Debit::$f->meta->contains('tag')) 511 + ->sort(Debit::$f->created_at->asc()) 512 + ->all()); 513 + $debit_ids = array_map($getId, $debits); 514 + $this->assertEquals($debit_ids, array($debit1->id, $debit2->id, $debit3->id)); 515 + 516 + $debits = ( 517 + self::$marketplace->debits->query() 518 + ->filter(Debit::$f->meta->contains('tag')) 519 + ->sort(Debit::$f->amount->desc()) 520 + ->all()); 521 + $debit_ids = array_map($getId, $debits); 522 + $this->assertEquals($debit_ids, array($debit2->id, $debit3->id, $debit1->id)); 523 + } 524 + 525 + function testMerchantIdentityFailure() 526 + { 527 + // NOTE: postal_code == '99999' && region == 'EX' triggers identity failure 528 + $identity = array( 529 + 'type' => 'business', 530 + 'name' => 'Levain Bakery', 531 + 'tax_id' => '253912384', 532 + 'street_address' => '167 West 74th Street', 533 + 'postal_code' => '99999', 534 + 'region' => 'EX', 535 + 'phone_number' => '+16505551234', 536 + 'country_code' => 'USA', 537 + 'person' => array( 538 + 'name' => 'William James', 539 + 'tax_id' => '393483992', 540 + 'street_address' => '167 West 74th Street', 541 + 'postal_code' => '99999', 542 + 'region' => 'EX', 543 + 'dob' => '1842-01-01', 544 + 'phone_number' => '+16505551234', 545 + 'country_code' => 'USA', 546 + ), 547 + ); 548 + 549 + try { 550 + self::$marketplace->createMerchant( 551 + sprintf('m+%d@poundpay.com', self::$email_counter++), 552 + $identity); 553 + } 554 + catch(\RESTful\Exceptions\HTTPError $e) { 555 + $this->assertEquals($e->response->code, 300); 556 + $expected = sprintf('https://www.balancedpayments.com/marketplaces/%s/kyc', self::$marketplace->id); 557 + $this->assertEquals($e->redirect_uri, $expected); 558 + $this->assertEquals($e->response->headers['Location'], $expected); 559 + return; 560 + } 561 + $this->fail('Expected exception HTTPError not raised.'); 562 + } 563 + 564 + function testInternationalCard() 565 + { 566 + $payload = array( 567 + 'card_number' => '4111111111111111', 568 + 'city' => '\xe9\x83\xbd\xe7\x95\x99\xe5\xb8\x82', 569 + 'country_code' => 'JPN', 570 + 'expiration_month' => 12, 571 + 'expiration_year' => 2014, 572 + 'name' => 'Johnny Fresh', 573 + 'postal_code' => '4020054', 574 + 'street_address' => '\xe7\x94\xb0\xe5\x8e\x9f\xef\xbc\x93\xe3\x83\xbc\xef\xbc\x98\xe3\x83\xbc\xef\xbc\x91' 575 + ); 576 + $card = self::$marketplace->cards->create($payload); 577 + $this->assertEquals($card->street_address, $payload['street_address']); 578 + } 579 + 580 + /** 581 + * @expectedException \RESTful\Exceptions\NoResultFound 582 + */ 583 + function testAccountWithEmailAddressNotFound() 584 + { 585 + self::$marketplace->accounts->query() 586 + ->filter(Account::$f->email_address->eq('unlikely@address.com')) 587 + ->one(); 588 + } 589 + 590 + function testDebitACard() 591 + { 592 + $buyer = self::_createBuyer(); 593 + $card = self::_createCard($buyer); 594 + $debit = $card->debit( 595 + 1000, 596 + 'Softie', 597 + 'something i bought', 598 + array('hi' => 'bye')); 599 + $this->assertEquals($debit->source->uri, $card->uri); 600 + } 601 + 602 + /** 603 + * @expectedException \UnexpectedValueException 604 + */ 605 + function testDebitAnUnassociatedCard() 606 + { 607 + $card = self::_createCard(); 608 + $card->debit(1000, 'Softie'); 609 + } 610 + 611 + function testCreditABankAccount() 612 + { 613 + $buyer = self::_createBuyer(); 614 + $buyer->debit(101); # NOTE: build up escrow balance to credit 615 + 616 + $merchant = self::_createPersonMerchant(); 617 + $bank_account = self::_createBankAccount($merchant); 618 + $credit = $bank_account->credit(55, 'something sour'); 619 + $this->assertEquals($credit->destination->uri, $bank_account->uri); 620 + } 621 + 622 + function testQuery() 623 + { 624 + $buyer = self::_createBuyer(); 625 + $tag = '123123123123'; 626 + $debit1 = $buyer->debit(1122, null, null, array('tag' => $tag)); 627 + $debit2 = $buyer->debit(3322, null, null, array('tag' => $tag)); 628 + $debit3 = $buyer->debit(2211, null, null, array('tag' => $tag)); 629 + $expected_debit_ids = array($debit1->id, $debit2->id, $debit3->id); 630 + 631 + $query = ( 632 + self::$marketplace->debits->query() 633 + ->filter(Debit::$f->meta->tag->eq($tag)) 634 + ->sort(Debit::$f->created_at->asc()) 635 + ->limit(1)); 636 + 637 + $this->assertEquals($query->total(), 3); 638 + 639 + $debit_ids = array(); 640 + foreach ($query as $debits) { 641 + array_push($debit_ids, $debits->id); 642 + } 643 + $this->assertEquals($debit_ids, $expected_debit_ids); 644 + 645 + $debit_ids = array($query[0]->id, $query[1]->id, $query[2]->id); 646 + $this->assertEquals($debit_ids, $expected_debit_ids); 647 + } 648 + 649 + function testBuyerPromoteToMerchant() 650 + { 651 + $merchant = array( 652 + 'type' => 'person', 653 + 'name' => 'William James', 654 + 'tax_id' => '393-48-3992', 655 + 'street_address' => '167 West 74th Street', 656 + 'postal_code' => '10023', 657 + 'dob' => '1842-01-01', 658 + 'phone_number' => '+16505551234', 659 + 'country_code' => 'USA' 660 + ); 661 + $buyer = self::_createBuyer(); 662 + $buyer->promoteToMerchant($merchant); 663 + } 664 + 665 + function testCreditAccountlessBankAccount() 666 + { 667 + $buyer = self::_createBuyer(); 668 + $buyer->debit(101); # NOTE: build up escrow balance to credit 669 + 670 + $bank_account = self::_createBankAccount(); 671 + $credit = $bank_account->credit(55, 'something sour'); 672 + $this->assertEquals($credit->bank_account->id, $bank_account->id); 673 + $bank_account = $bank_account->get($bank_account->id); 674 + $this->assertEquals($bank_account->credits->total(), 1); 675 + } 676 + 677 + function testCreditUnstoredBankAccount() 678 + { 679 + $buyer = self::_createBuyer(); 680 + $buyer->debit(101); # NOTE: build up escrow balance to credit 681 + 682 + $credit = Credit::bankAccount( 683 + 55, 684 + array( 685 + 'name' => 'Homer Jay', 686 + 'account_number' => '112233a', 687 + 'routing_number' => '121042882', 688 + 'type' => 'checking', 689 + ), 690 + 'something sour'); 691 + $this->assertFalse(property_exists($credit->bank_account, 'uri')); 692 + $this->assertFalse(property_exists($credit->bank_account, 'id')); 693 + $this->assertEquals($credit->bank_account->name, 'Homer Jay'); 694 + $this->assertEquals($credit->bank_account->account_number, 'xxx233a'); 695 + $this->assertEquals($credit->bank_account->type, 'checking'); 696 + } 697 + 698 + function testDeleteBankAccount() 699 + { 700 + $buyer = self::_createBuyer(); 701 + $buyer->debit(101); # NOTE: build up escrow balance to credit 702 + 703 + $bank_account = self::_createBankAccount(); 704 + $credit = $bank_account->credit(55, 'something sour'); 705 + $this->assertTrue(property_exists($credit->bank_account, 'uri')); 706 + $this->assertTrue(property_exists($credit->bank_account, 'id')); 707 + $bank_account = BankAccount::get($bank_account->id); 708 + $bank_account->delete(); 709 + $credit = Credit::get($credit->uri); 710 + $this->assertFalse(property_exists($credit->bank_account, 'uri')); 711 + $this->assertFalse(property_exists($credit->bank_account, 'id')); 712 + } 713 + 714 + function testGetBankAccounById() 715 + { 716 + $bank_account = self::_createBankAccount(); 717 + $bank_account_2 = BankAccount::get($bank_account->id); 718 + $this->assertEquals($bank_account_2->id, $bank_account->id); 719 + } 720 + 721 + /** 722 + * @expectedException \Balanced\Errors\InsufficientFunds 723 + */ 724 + function testInsufficientFunds() 725 + { 726 + $marketplace = Marketplace::get(self::$marketplace->uri); 727 + $amount = $marketplace->in_escrow + 100; 728 + $credit = Credit::bankAccount( 729 + $amount, 730 + array( 731 + 'name' => 'Homer Jay', 732 + 'account_number' => '112233a', 733 + 'routing_number' => '121042882', 734 + 'type' => 'checking', 735 + ), 736 + 'something sour'); 737 + } 738 + 739 + function testCreateCallback() { 740 + $callback = self::$marketplace->createCallback( 741 + 'http://example.com/php' 742 + ); 743 + $this->assertEquals($callback->url, 'http://example.com/php'); 744 + } 745 + 746 + /** 747 + * @expectedException \Balanced\Errors\BankAccountVerificationFailure 748 + */ 749 + function testBankAccountVerificationFailure() { 750 + $bank_account = self::_createBankAccount(); 751 + $buyer = self::_createBuyer(); 752 + $buyer->addBankAccount($bank_account); 753 + $verification = $bank_account->verify(); 754 + $verification->confirm(1, 2); 755 + } 756 + 757 + /** 758 + * @expectedException \Balanced\Errors\BankAccountVerificationFailure 759 + */ 760 + function testBankAccountVerificationDuplicate() { 761 + $bank_account = self::_createBankAccount(); 762 + $buyer = self::_createBuyer(); 763 + $buyer->addBankAccount($bank_account); 764 + $bank_account->verify(); 765 + $bank_account->verify(); 766 + } 767 + 768 + function testBankAccountVerificationSuccess() { 769 + $bank_account = self::_createBankAccount(); 770 + $buyer = self::_createBuyer(); 771 + $buyer->addBankAccount($bank_account); 772 + $verification = $bank_account->verify(); 773 + $verification->confirm(1, 1); 774 + 775 + // this will fail if the bank account is not verified 776 + $debit = $buyer->debit( 777 + 1000, 778 + 'Softie', 779 + 'something i bought', 780 + array('hi' => 'bye'), 781 + $bank_account 782 + ); 783 + $this->assertTrue(strpos($debit->source->uri, 'bank_account') > 0); 784 + } 785 + 786 + function testEvents() { 787 + $prev_num_events = Marketplace::mine()->events->total(); 788 + $account = self::_createBuyer(); 789 + $account->debit(123); 790 + $cur_num_events = Marketplace::mine()->events->total(); 791 + $count = 0; 792 + while ($cur_num_events == $prev_num_events && $count < 10) { 793 + printf("waiting for events - %d, %d == %d\n", $count + 1, $cur_num_events, $prev_num_events); 794 + sleep(2); // 2 seconds 795 + $cur_num_events = Marketplace::mine()->events->total(); 796 + $count += 1; 797 + } 798 + $this->assertTrue($cur_num_events > $prev_num_events); 799 + } 800 + }
+8
externals/balanced-php/tests/phpunit.xml
··· 1 + <phpunit> 2 + <testsuite name="Balanced"> 3 + <directory>.</directory> 4 + </testsuite> 5 + <logging> 6 + <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> 7 + </logging> 8 + </phpunit>
+4
externals/httpful/.gitignore
··· 1 + .DS_Store 2 + composer.lock 3 + vendor 4 + downloads
+5
externals/httpful/.travis.yml
··· 1 + language: php 2 + before_script: cd tests 3 + php: 4 + - 5.3 5 + - 5.4
+7
externals/httpful/LICENSE.txt
··· 1 + Copyright (c) 2012 Nate Good <me@nategood.com> 2 + 3 + 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: 4 + 5 + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 + 7 + 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.
+137
externals/httpful/README.md
··· 1 + # Httpful 2 + 3 + [![Build Status](https://secure.travis-ci.org/nategood/httpful.png?branch=master)](http://travis-ci.org/nategood/httpful) 4 + 5 + [Httpful](http://phphttpclient.com) is a simple Http Client library for PHP 5.3+. There is an emphasis of readability, simplicity, and flexibility – basically provide the features and flexibility to get the job done and make those features really easy to use. 6 + 7 + Features 8 + 9 + - Readable HTTP Method Support (GET, PUT, POST, DELETE, HEAD, PATCH and OPTIONS) 10 + - Custom Headers 11 + - Automatic "Smart" Parsing 12 + - Automatic Payload Serialization 13 + - Basic Auth 14 + - Client Side Certificate Auth 15 + - Request "Templates" 16 + 17 + # Sneak Peak 18 + 19 + Here's something to whet your appetite. Search the twitter API for tweets containing "#PHP". Include a trivial header for the heck of it. Notice that the library automatically interprets the response as JSON (can override this if desired) and parses it as an array of objects. 20 + 21 + $url = "http://search.twitter.com/search.json?q=" . urlencode('#PHP'); 22 + $response = Request::get($url) 23 + ->withXTrivialHeader('Just as a demo') 24 + ->send(); 25 + 26 + foreach ($response->body->results as $tweet) { 27 + echo "@{$tweet->from_user} tweets \"{$tweet->text}\"\n"; 28 + } 29 + 30 + # Installation 31 + 32 + ## Phar 33 + 34 + A [PHP Archive](http://php.net/manual/en/book.phar.php) (or .phar) file is available for [downloading](https://github.com/nategood/httpful/downloads). Simply [download](https://github.com/nategood/httpful/downloads) the .phar, drop it into your project, and include it like you would any other php file. _This method is ideal smaller projects, one off scripts, and quick API hacking_. 35 + 36 + <?php 37 + include('httpful.phar'); 38 + $r = \Httpful\Request::get($uri)->sendIt(); 39 + ... 40 + 41 + ## Composer 42 + 43 + Httpful is PSR-0 compliant and can be installed using [composer](http://getcomposer.org/). Simply add `nategood/httpful` to your composer.json file. _Composer is the sane alternative to PEAR. It is excellent for managing dependancies in larger projects_. 44 + 45 + { 46 + "require": { 47 + "nategood/httpful": "*" 48 + } 49 + } 50 + 51 + ## Install from Source 52 + 53 + Because Httpful is PSR-0 compliant, you can also just clone the Httpful repository and use a PSR-0 compatible autoloader to load the library, like [Symfony's](http://symfony.com/doc/current/components/class_loader.html). Alternatively you can use the PSR-0 compliant autoloader included with the Httpful (simply `require("bootstrap.php")`). 54 + 55 + # Show Me More! 56 + 57 + You can checkout the [Httpful Landing Page](http://phphttpclient.com) for more info including many examples and [documentation](http:://phphttpclient.com/docs). 58 + 59 + # Contributing 60 + 61 + Httpful highly encourages sending in pull requests. When submitting a pull request please: 62 + 63 + - All pull requests should target the `dev` branch (not `master`) 64 + - Make sure your code follows the [coding conventions](http://pear.php.net/manual/en/standards.php) 65 + - Please use soft tabs (four spaces) instead of hard tabs 66 + - Make sure you add appropriate test coverage for your changes 67 + - Run all unit tests in the test directory via `phpunit ./tests` 68 + - Include commenting where appropriate and add a descriptive pull request message 69 + 70 + # Changelog 71 + 72 + ## 0.2.3 73 + 74 + - FIX Overriding default Mime Handlers 75 + - FIX [PR #73](https://github.com/nategood/httpful/pull/73) Parsing http status codes 76 + 77 + ## 0.2.2 78 + 79 + - FEATURE Add support for parsing JSON responses as associative arrays instead of objects 80 + - FEATURE Better support for setting constructor arguments on Mime Handlers 81 + 82 + ## 0.2.1 83 + 84 + - FEATURE [PR #72](https://github.com/nategood/httpful/pull/72) Allow support for custom Accept header 85 + 86 + ## 0.2.0 87 + 88 + - REFACTOR [PR #49](https://github.com/nategood/httpful/pull/49) Broke headers out into their own class 89 + - REFACTOR [PR #54](https://github.com/nategood/httpful/pull/54) Added more specific Exceptions 90 + - FIX [PR #58](https://github.com/nategood/httpful/pull/58) Fixes throwing an error on an empty xml response 91 + - FEATURE [PR #57](https://github.com/nategood/httpful/pull/57) Adds support for digest authentication 92 + 93 + ## 0.1.6 94 + 95 + - Ability to set the number of max redirects via overloading `followRedirects(int max_redirects)` 96 + - Standards Compliant fix to `Accepts` header 97 + - Bug fix for bootstrap process when installed via Composer 98 + 99 + ## 0.1.5 100 + 101 + - Use `DIRECTORY_SEPARATOR` constant [PR #33](https://github.com/nategood/httpful/pull/32) 102 + - [PR #35](https://github.com/nategood/httpful/pull/35) 103 + - Added the raw\_headers property reference to response. 104 + - Compose request header and added raw\_header to Request object. 105 + - Fixed response has errors and added more comments for clarity. 106 + - Fixed header parsing to allow the minimum (status line only) and also cater for the actual CRLF ended headers as per RFC2616. 107 + - Added the perfect test Accept: header for all Acceptable scenarios see @b78e9e82cd9614fbe137c01bde9439c4e16ca323 for details. 108 + - Added default User-Agent header 109 + - `User-Agent: Httpful/0.1.5` + curl version + server software + PHP version 110 + - To bypass this "default" operation simply add a User-Agent to the request headers even a blank User-Agent is sufficient and more than simple enough to produce me thinks. 111 + - Completed test units for additions. 112 + - Added phpunit coverage reporting and helped phpunit auto locate the tests a bit easier. 113 + 114 + ## 0.1.4 115 + 116 + - Add support for CSV Handling [PR #32](https://github.com/nategood/httpful/pull/32) 117 + 118 + ## 0.1.3 119 + 120 + - Handle empty responses in JsonParser and XmlParser 121 + 122 + ## 0.1.2 123 + 124 + - Added support for setting XMLHandler configuration options 125 + - Added examples for overriding XmlHandler and registering a custom parser 126 + - Removed the httpful.php download (deprecated in favor of httpful.phar) 127 + 128 + ## 0.1.1 129 + 130 + - Bug fix serialization default case and phpunit tests 131 + 132 + ## 0.1.0 133 + 134 + - Added Support for Registering Mime Handlers 135 + - Created AbstractMimeHandler type that all Mime Handlers must extend 136 + - Pulled out the parsing/serializing logic from the Request/Response classes into their own MimeHandler classes 137 + - Added ability to register new mime handlers for mime types
+4
externals/httpful/bootstrap.php
··· 1 + <?php 2 + 3 + require(__DIR__ . '/src/Httpful/Bootstrap.php'); 4 + \Httpful\Bootstrap::init();
+51
externals/httpful/build
··· 1 + #!/usr/bin/php 2 + <?php 3 + 4 + /** 5 + * Build the whole library into a single file 6 + * as an easy drop in solution as opposed to 7 + * relying on autoloader. Sometimes we just 8 + * want to hack with an API as a one off thing. 9 + * Httpful should make this easy. 10 + */ 11 + 12 + function exit_unless($condition, $msg = null) { 13 + if ($condition) 14 + return; 15 + echo "[FAIL]\n$msg\n"; 16 + exit(1); 17 + } 18 + 19 + // Create the Httpful Phar 20 + echo "Building Phar... "; 21 + $base_dir = dirname(__FILE__); 22 + $source_dir = $base_dir . '/src/Httpful/'; 23 + $phar_path = $base_dir . '/downloads/httpful.phar'; 24 + $phar = new Phar($phar_path, 0, 'httpful.phar'); 25 + $stub = <<<HEREDOC 26 + <?php 27 + // Phar Stub File 28 + Phar::mapPhar('httpful.phar'); 29 + include('phar://httpful.phar/Httpful/Bootstrap.php'); 30 + \Httpful\Bootstrap::pharInit(); 31 + 32 + __HALT_COMPILER(); 33 + HEREDOC; 34 + try { 35 + $phar->setStub($stub); 36 + } catch(Exception $e) { 37 + $phar = false; 38 + } 39 + exit_unless($phar, "Unable to create a phar. Make certain you have phar.readonly=0 set in your ini file."); 40 + $phar->buildFromDirectory(dirname($source_dir)); 41 + echo "[ OK ]\n"; 42 + 43 + 44 + 45 + // Add it to git! 46 + echo "Adding httpful.phar to the repo... "; 47 + $return_code = 0; 48 + passthru("git add $phar_path", $return_code); 49 + exit_unless($return_code === 0, "Unable to add download files to git."); 50 + echo "[ OK ]\n"; 51 + echo "\nBuild completed sucessfully.\n\n";
+24
externals/httpful/composer.json
··· 1 + { 2 + "name": "nategood/httpful", 3 + "description": "A Readable, Chainable, REST friendly, PHP HTTP Client", 4 + "homepage": "http://github.com/nategood/httpful", 5 + "license": "MIT", 6 + "keywords": ["http", "curl", "rest", "restful", "api", "requests"], 7 + "version": "0.2.3", 8 + "authors": [ 9 + { 10 + "name": "Nate Good", 11 + "email": "me@nategood.com", 12 + "homepage": "http://nategood.com" 13 + } 14 + ], 15 + "require": { 16 + "php": ">=5.3", 17 + "ext-curl": "*" 18 + }, 19 + "autoload": { 20 + "psr-0": { 21 + "Httpful": "src/" 22 + } 23 + } 24 + }
+12
externals/httpful/examples/freebase.php
··· 1 + <?php 2 + /** 3 + * Grab some The Dead Weather albums from Freebase 4 + */ 5 + require(__DIR__ . '/../bootstrap.php'); 6 + 7 + $uri = "https://www.googleapis.com/freebase/v1/mqlread?query=%7B%22type%22:%22/music/artist%22%2C%22name%22:%22The%20Dead%20Weather%22%2C%22album%22:%5B%5D%7D"; 8 + $response = \Httpful\Request::get($uri) 9 + ->expectsJson() 10 + ->sendIt(); 11 + 12 + echo 'The Dead Weather has ' . count($response->body->result->album) . " albums.\n";
+9
externals/httpful/examples/github.php
··· 1 + <?php 2 + // XML Example from GitHub 3 + require(__DIR__ . '/../bootstrap.php'); 4 + use \Httpful\Request; 5 + 6 + $uri = 'https://github.com/api/v2/xml/user/show/nategood'; 7 + $request = Request::get($uri)->send(); 8 + 9 + echo "{$request->body->name} joined GitHub on " . date('M jS', strtotime($request->body->{'created-at'})) ."\n";
+44
externals/httpful/examples/override.php
··· 1 + <?php 2 + require(__DIR__ . '/../bootstrap.php'); 3 + 4 + // We can override the default parser configuration options be registering 5 + // a parser with different configuration options for a particular mime type 6 + 7 + // Example setting a namespace for the XMLHandler parser 8 + $conf = array('namespace' => 'http://example.com'); 9 + \Httpful\Httpful::register(\Httpful\Mime::XML, new \Httpful\Handlers\XmlHandler($conf)); 10 + 11 + // We can also add the parsers with our own... 12 + class SimpleCsvHandler extends \Httpful\Handlers\MimeHandlerAdapter 13 + { 14 + /** 15 + * Takes a response body, and turns it into 16 + * a two dimensional array. 17 + * 18 + * @param string $body 19 + * @return mixed 20 + */ 21 + public function parse($body) 22 + { 23 + return str_getcsv($body); 24 + } 25 + 26 + /** 27 + * Takes a two dimensional array and turns it 28 + * into a serialized string to include as the 29 + * body of a request 30 + * 31 + * @param mixed $payload 32 + * @return string 33 + */ 34 + public function serialize($payload) 35 + { 36 + $serialized = ''; 37 + foreach ($payload as $line) { 38 + $serialized .= '"' . implode('","', $line) . '"' . "\n"; 39 + } 40 + return $serialized; 41 + } 42 + } 43 + 44 + \Httpful\Httpful::register('text/csv', new SimpleCsvHandler());
+24
externals/httpful/examples/showclix.php
··· 1 + <?php 2 + 3 + require(__DIR__ . '/../bootstrap.php'); 4 + 5 + use \Httpful\Request; 6 + 7 + // Get event details for a public event 8 + $uri = "http://api.showclix.com/Event/8175"; 9 + $response = Request::get($uri) 10 + ->expectsType('json') 11 + ->sendIt(); 12 + 13 + // Print out the event details 14 + echo "The event {$response->body->event} will take place on {$response->body->event_start}\n"; 15 + 16 + // Example overriding the default JSON handler with one that encodes the response as an array 17 + \Httpful\Httpful::register(\Httpful\Mime::JSON, new \Httpful\Handlers\JsonHandler(array('decode_as_array' => true))); 18 + 19 + $response = Request::get($uri) 20 + ->expectsType('json') 21 + ->sendIt(); 22 + 23 + // Print out the event details 24 + echo "The event {$response->body['event']} will take place on {$response->body['event_start']}\n";
+13
externals/httpful/examples/twitter.php
··· 1 + <?php 2 + require(__DIR__ . '/../bootstrap.php'); 3 + 4 + $query = urlencode('#PHP'); 5 + $response = \Httpful\Request::get("http://search.twitter.com/search.json?q=$query")->send(); 6 + 7 + if (!$response->hasErrors()) { 8 + foreach ($response->body->results as $tweet) { 9 + echo "@{$tweet->from_user} tweets \"{$tweet->text}\"\n"; 10 + } 11 + } else { 12 + echo "Uh oh. Twitter gave us the old {$response->code} status.\n"; 13 + }
+97
externals/httpful/src/Httpful/Bootstrap.php
··· 1 + <?php 2 + 3 + namespace Httpful; 4 + 5 + /** 6 + * Bootstrap class that facilitates autoloading. A naive 7 + * PSR-0 autoloader. 8 + * 9 + * @author Nate Good <me@nategood.com> 10 + */ 11 + class Bootstrap 12 + { 13 + 14 + const DIR_GLUE = DIRECTORY_SEPARATOR; 15 + const NS_GLUE = '\\'; 16 + 17 + public static $registered = false; 18 + 19 + /** 20 + * Register the autoloader and any other setup needed 21 + */ 22 + public static function init() 23 + { 24 + spl_autoload_register(array('\Httpful\Bootstrap', 'autoload')); 25 + self::registerHandlers(); 26 + } 27 + 28 + /** 29 + * The autoload magic (PSR-0 style) 30 + * 31 + * @param string $classname 32 + */ 33 + public static function autoload($classname) 34 + { 35 + self::_autoload(dirname(dirname(__FILE__)), $classname); 36 + } 37 + 38 + /** 39 + * Register the autoloader and any other setup needed 40 + */ 41 + public static function pharInit() 42 + { 43 + spl_autoload_register(array('\Httpful\Bootstrap', 'pharAutoload')); 44 + self::registerHandlers(); 45 + } 46 + 47 + /** 48 + * Phar specific autoloader 49 + * 50 + * @param string $classname 51 + */ 52 + public static function pharAutoload($classname) 53 + { 54 + self::_autoload('phar://httpful.phar', $classname); 55 + } 56 + 57 + /** 58 + * @param string base 59 + * @param string classname 60 + */ 61 + private static function _autoload($base, $classname) 62 + { 63 + $parts = explode(self::NS_GLUE, $classname); 64 + $path = $base . self::DIR_GLUE . implode(self::DIR_GLUE, $parts) . '.php'; 65 + 66 + if (file_exists($path)) { 67 + require_once($path); 68 + } 69 + } 70 + /** 71 + * Register default mime handlers. Is idempotent. 72 + */ 73 + public static function registerHandlers() 74 + { 75 + if (self::$registered === true) { 76 + return; 77 + } 78 + 79 + // @todo check a conf file to load from that instead of 80 + // hardcoding into the library? 81 + $handlers = array( 82 + \Httpful\Mime::JSON => new \Httpful\Handlers\JsonHandler(), 83 + \Httpful\Mime::XML => new \Httpful\Handlers\XmlHandler(), 84 + \Httpful\Mime::FORM => new \Httpful\Handlers\FormHandler(), 85 + \Httpful\Mime::CSV => new \Httpful\Handlers\CsvHandler(), 86 + ); 87 + 88 + foreach ($handlers as $mime => $handler) { 89 + // Don't overwrite if the handler has already been registered 90 + if (Httpful::hasParserRegistered($mime)) 91 + continue; 92 + Httpful::register($mime, $handler); 93 + } 94 + 95 + self::$registered = true; 96 + } 97 + }
+7
externals/httpful/src/Httpful/Exception/ConnectionErrorException.php
··· 1 + <?php 2 + 3 + namespace Httpful\Exception; 4 + 5 + class ConnectionErrorException extends \Exception 6 + { 7 + }
+50
externals/httpful/src/Httpful/Handlers/CsvHandler.php
··· 1 + <?php 2 + /** 3 + * Mime Type: text/csv 4 + * @author Raja Kapur <rajak@twistedthrottle.com> 5 + */ 6 + 7 + namespace Httpful\Handlers; 8 + 9 + class CsvHandler extends MimeHandlerAdapter 10 + { 11 + /** 12 + * @param string $body 13 + * @return mixed 14 + */ 15 + public function parse($body) 16 + { 17 + if (empty($body)) 18 + return null; 19 + 20 + $parsed = array(); 21 + $fp = fopen('data://text/plain;base64,' . base64_encode($body), 'r'); 22 + while (($r = fgetcsv($fp)) !== FALSE) { 23 + $parsed[] = $r; 24 + } 25 + 26 + if (empty($parsed)) 27 + throw new \Exception("Unable to parse response as CSV"); 28 + return $parsed; 29 + } 30 + 31 + /** 32 + * @param mixed $payload 33 + * @return string 34 + */ 35 + public function serialize($payload) 36 + { 37 + $fp = fopen('php://temp/maxmemory:'. (6*1024*1024), 'r+'); 38 + $i = 0; 39 + foreach ($payload as $fields) { 40 + if($i++ == 0) { 41 + fputcsv($fp, array_keys($fields)); 42 + } 43 + fputcsv($fp, $fields); 44 + } 45 + rewind($fp); 46 + $data = stream_get_contents($fp); 47 + fclose($fp); 48 + return $data; 49 + } 50 + }
+30
externals/httpful/src/Httpful/Handlers/FormHandler.php
··· 1 + <?php 2 + /** 3 + * Mime Type: application/x-www-urlencoded 4 + * @author Nathan Good <me@nategood.com> 5 + */ 6 + 7 + namespace Httpful\Handlers; 8 + 9 + class FormHandler extends MimeHandlerAdapter 10 + { 11 + /** 12 + * @param string $body 13 + * @return mixed 14 + */ 15 + public function parse($body) 16 + { 17 + $parsed = array(); 18 + parse_str($body, $parsed); 19 + return $parsed; 20 + } 21 + 22 + /** 23 + * @param mixed $payload 24 + * @return string 25 + */ 26 + public function serialize($payload) 27 + { 28 + return http_build_query($payload, null, '&'); 29 + } 30 + }
+40
externals/httpful/src/Httpful/Handlers/JsonHandler.php
··· 1 + <?php 2 + /** 3 + * Mime Type: application/json 4 + * @author Nathan Good <me@nategood.com> 5 + */ 6 + 7 + namespace Httpful\Handlers; 8 + 9 + class JsonHandler extends MimeHandlerAdapter 10 + { 11 + private $decode_as_array = false; 12 + 13 + public function init(array $args) 14 + { 15 + $this->decode_as_array = !!(array_key_exists('decode_as_array', $args) ? $args['decode_as_array'] : false); 16 + } 17 + 18 + /** 19 + * @param string $body 20 + * @return mixed 21 + */ 22 + public function parse($body) 23 + { 24 + if (empty($body)) 25 + return null; 26 + $parsed = json_decode($body, $this->decode_as_array); 27 + if (is_null($parsed)) 28 + throw new \Exception("Unable to parse response as JSON"); 29 + return $parsed; 30 + } 31 + 32 + /** 33 + * @param mixed $payload 34 + * @return string 35 + */ 36 + public function serialize($payload) 37 + { 38 + return json_encode($payload); 39 + } 40 + }
+43
externals/httpful/src/Httpful/Handlers/MimeHandlerAdapter.php
··· 1 + <?php 2 + 3 + /** 4 + * Handlers are used to parse and serialize payloads for specific 5 + * mime types. You can register a custom handler via the register 6 + * method. You can also override a default parser in this way. 7 + */ 8 + 9 + namespace Httpful\Handlers; 10 + 11 + class MimeHandlerAdapter 12 + { 13 + public function __construct(array $args = array()) 14 + { 15 + $this->init($args); 16 + } 17 + 18 + /** 19 + * Initial setup of 20 + * @param array $args 21 + */ 22 + public function init(array $args) 23 + { 24 + } 25 + 26 + /** 27 + * @param string $body 28 + * @return mixed 29 + */ 30 + public function parse($body) 31 + { 32 + return $body; 33 + } 34 + 35 + /** 36 + * @param mixed $payload 37 + * @return string 38 + */ 39 + function serialize($payload) 40 + { 41 + return (string) $payload; 42 + } 43 + }
+44
externals/httpful/src/Httpful/Handlers/README.md
··· 1 + # Handlers 2 + 3 + Handlers are simple classes that are used to parse response bodies and serialize request payloads. All Handlers must extend the `MimeHandlerAdapter` class and implement two methods: `serialize($payload)` and `parse($response)`. Let's build a very basic Handler to register for the `text/csv` mime type. 4 + 5 + <?php 6 + 7 + class SimpleCsvHandler extends \Httpful\Handlers\MimeHandlerAdapter 8 + { 9 + /** 10 + * Takes a response body, and turns it into 11 + * a two dimensional array. 12 + * 13 + * @param string $body 14 + * @return mixed 15 + */ 16 + public function parse($body) 17 + { 18 + return str_getcsv($body); 19 + } 20 + 21 + /** 22 + * Takes a two dimensional array and turns it 23 + * into a serialized string to include as the 24 + * body of a request 25 + * 26 + * @param mixed $payload 27 + * @return string 28 + */ 29 + public function serialize($payload) 30 + { 31 + $serialized = ''; 32 + foreach ($payload as $line) { 33 + $serialized .= '"' . implode('","', $line) . '"' . "\n"; 34 + } 35 + return $serialized; 36 + } 37 + } 38 + 39 + 40 + Finally, you must register this handler for a particular mime type. 41 + 42 + Httpful::register('text/csv', new SimpleCsvHandler()); 43 + 44 + After this registering the handler in your source code, by default, any responses with a mime type of text/csv should be parsed by this handler.
+15
externals/httpful/src/Httpful/Handlers/XHtmlHandler.php
··· 1 + <?php 2 + /** 3 + * Mime Type: text/html 4 + * Mime Type: application/html+xml 5 + * 6 + * @author Nathan Good <me@nategood.com> 7 + */ 8 + 9 + namespace Httpful\Handlers; 10 + 11 + class XHtmlHandler extends MimeHandlerAdapter 12 + { 13 + // @todo add html specific parsing 14 + // see DomDocument::load http://docs.php.net/manual/en/domdocument.loadhtml.php 15 + }
+120
externals/httpful/src/Httpful/Handlers/XmlHandler.php
··· 1 + <?php 2 + /** 3 + * Mime Type: application/xml 4 + * 5 + * @author Zack Douglas <zack@zackerydouglas.info> 6 + * @author Nathan Good <me@nategood.com> 7 + */ 8 + 9 + namespace Httpful\Handlers; 10 + 11 + class XmlHandler extends MimeHandlerAdapter 12 + { 13 + /** 14 + * @var string $namespace xml namespace to use with simple_load_string 15 + */ 16 + private $namespace; 17 + 18 + /** 19 + * @var int $libxml_opts see http://www.php.net/manual/en/libxml.constants.php 20 + */ 21 + private $libxml_opts; 22 + 23 + /** 24 + * @param array $conf sets configuration options 25 + */ 26 + public function __construct(array $conf = array()) 27 + { 28 + $this->namespace = isset($conf['namespace']) ? $conf['namespace'] : ''; 29 + $this->libxml_opts = isset($conf['libxml_opts']) ? $conf['libxml_opts'] : 0; 30 + } 31 + 32 + /** 33 + * @param string $body 34 + * @return mixed 35 + * @throws Exception if unable to parse 36 + */ 37 + public function parse($body) 38 + { 39 + if (empty($body)) 40 + return null; 41 + $parsed = simplexml_load_string($body, null, $this->libxml_opts, $this->namespace); 42 + if ($parsed === false) 43 + throw new \Exception("Unable to parse response as XML"); 44 + return $parsed; 45 + } 46 + 47 + /** 48 + * @param mixed $payload 49 + * @return string 50 + * @throws Exception if unable to serialize 51 + */ 52 + public function serialize($payload) 53 + { 54 + list($_, $dom) = $this->_future_serializeAsXml($payload); 55 + return $dom->saveXml(); 56 + } 57 + 58 + /** 59 + * @author Zack Douglas <zack@zackerydouglas.info> 60 + */ 61 + private function _future_serializeAsXml($value, $node = null, $dom = null) 62 + { 63 + if (!$dom) { 64 + $dom = new \DOMDocument; 65 + } 66 + if (!$node) { 67 + if (!is_object($value)) { 68 + $node = $dom->createElement('response'); 69 + $dom->appendChild($node); 70 + } else { 71 + $node = $dom; 72 + } 73 + } 74 + if (is_object($value)) { 75 + $objNode = $dom->createElement(get_class($value)); 76 + $node->appendChild($objNode); 77 + $this->_future_serializeObjectAsXml($value, $objNode, $dom); 78 + } else if (is_array($value)) { 79 + $arrNode = $dom->createElement('array'); 80 + $node->appendChild($arrNode); 81 + $this->_future_serializeArrayAsXml($value, $arrNode, $dom); 82 + } else if (is_bool($value)) { 83 + $node->appendChild($dom->createTextNode($value?'TRUE':'FALSE')); 84 + } else { 85 + $node->appendChild($dom->createTextNode($value)); 86 + } 87 + return array($node, $dom); 88 + } 89 + /** 90 + * @author Zack Douglas <zack@zackerydouglas.info> 91 + */ 92 + private function _future_serializeArrayAsXml($value, &$parent, &$dom) 93 + { 94 + foreach ($value as $k => &$v) { 95 + $n = $k; 96 + if (is_numeric($k)) { 97 + $n = "child-{$n}"; 98 + } 99 + $el = $dom->createElement($n); 100 + $parent->appendChild($el); 101 + $this->_future_serializeAsXml($v, $el, $dom); 102 + } 103 + return array($parent, $dom); 104 + } 105 + /** 106 + * @author Zack Douglas <zack@zackerydouglas.info> 107 + */ 108 + private function _future_serializeObjectAsXml($value, &$parent, &$dom) 109 + { 110 + $refl = new \ReflectionObject($value); 111 + foreach ($refl->getProperties() as $pr) { 112 + if (!$pr->isPrivate()) { 113 + $el = $dom->createElement($pr->getName()); 114 + $parent->appendChild($el); 115 + $this->_future_serializeAsXml($pr->getValue($value), $el, $dom); 116 + } 117 + } 118 + return array($parent, $dom); 119 + } 120 + }
+86
externals/httpful/src/Httpful/Http.php
··· 1 + <?php 2 + 3 + namespace Httpful; 4 + 5 + /** 6 + * @author Nate Good <me@nategood.com> 7 + */ 8 + class Http 9 + { 10 + const HEAD = 'HEAD'; 11 + const GET = 'GET'; 12 + const POST = 'POST'; 13 + const PUT = 'PUT'; 14 + const DELETE = 'DELETE'; 15 + const PATCH = 'PATCH'; 16 + const OPTIONS = 'OPTIONS'; 17 + const TRACE = 'TRACE'; 18 + 19 + /** 20 + * @return array of HTTP method strings 21 + */ 22 + public static function safeMethods() 23 + { 24 + return array(self::HEAD, self::GET, self::OPTIONS, self::TRACE); 25 + } 26 + 27 + /** 28 + * @return bool 29 + * @param string HTTP method 30 + */ 31 + public static function isSafeMethod($method) 32 + { 33 + return in_array($method, self::safeMethods()); 34 + } 35 + 36 + /** 37 + * @return bool 38 + * @param string HTTP method 39 + */ 40 + public static function isUnsafeMethod($method) 41 + { 42 + return !in_array($method, self::safeMethods()); 43 + } 44 + 45 + /** 46 + * @return array list of (always) idempotent HTTP methods 47 + */ 48 + public static function idempotentMethods() 49 + { 50 + // Though it is possible to be idempotent, POST 51 + // is not guarunteed to be, and more often than 52 + // not, it is not. 53 + return array(self::HEAD, self::GET, self::PUT, self::DELETE, self::OPTIONS, self::TRACE, self::PATCH); 54 + } 55 + 56 + /** 57 + * @return bool 58 + * @param string HTTP method 59 + */ 60 + public static function isIdempotent($method) 61 + { 62 + return in_array($method, self::safeidempotentMethodsMethods()); 63 + } 64 + 65 + /** 66 + * @return bool 67 + * @param string HTTP method 68 + */ 69 + public static function isNotIdempotent($method) 70 + { 71 + return !in_array($method, self::idempotentMethods()); 72 + } 73 + 74 + /** 75 + * @deprecated Technically anything *can* have a body, 76 + * they just don't have semantic meaning. So say's Roy 77 + * http://tech.groups.yahoo.com/group/rest-discuss/message/9962 78 + * 79 + * @return array of HTTP method strings 80 + */ 81 + public static function canHaveBody() 82 + { 83 + return array(self::POST, self::PUT, self::PATCH, self::OPTIONS); 84 + } 85 + 86 + }
+46
externals/httpful/src/Httpful/Httpful.php
··· 1 + <?php 2 + 3 + namespace Httpful; 4 + 5 + class Httpful { 6 + const VERSION = '0.1.7'; 7 + 8 + private static $mimeRegistrar = array(); 9 + private static $default = null; 10 + 11 + /** 12 + * @param string $mime_type 13 + * @param MimeHandlerAdapter $handler 14 + */ 15 + public static function register($mimeType, \Httpful\Handlers\MimeHandlerAdapter $handler) 16 + { 17 + self::$mimeRegistrar[$mimeType] = $handler; 18 + } 19 + 20 + /** 21 + * @param string $mime_type defaults to MimeHandlerAdapter 22 + * @return MimeHandlerAdapter 23 + */ 24 + public static function get($mimeType = null) 25 + { 26 + if (isset(self::$mimeRegistrar[$mimeType])) { 27 + return self::$mimeRegistrar[$mimeType]; 28 + } 29 + 30 + if (empty(self::$default)) { 31 + self::$default = new \Httpful\Handlers\MimeHandlerAdapter(); 32 + } 33 + 34 + return self::$default; 35 + } 36 + 37 + /** 38 + * Does this particular Mime Type have a parser registered 39 + * for it? 40 + * @return bool 41 + */ 42 + public static function hasParserRegistered($mimeType) 43 + { 44 + return isset(self::$mimeRegistrar[$mimeType]); 45 + } 46 + }
+58
externals/httpful/src/Httpful/Mime.php
··· 1 + <?php 2 + 3 + namespace Httpful; 4 + 5 + /** 6 + * Class to organize the Mime stuff a bit more 7 + * @author Nate Good <me@nategood.com> 8 + */ 9 + class Mime 10 + { 11 + const JSON = 'application/json'; 12 + const XML = 'application/xml'; 13 + const XHTML = 'application/html+xml'; 14 + const FORM = 'application/x-www-form-urlencoded'; 15 + const PLAIN = 'text/plain'; 16 + const JS = 'text/javascript'; 17 + const HTML = 'text/html'; 18 + const YAML = 'application/x-yaml'; 19 + const CSV = 'text/csv'; 20 + 21 + /** 22 + * Map short name for a mime type 23 + * to a full proper mime type 24 + */ 25 + public static $mimes = array( 26 + 'json' => self::JSON, 27 + 'xml' => self::XML, 28 + 'form' => self::FORM, 29 + 'plain' => self::PLAIN, 30 + 'text' => self::PLAIN, 31 + 'html' => self::HTML, 32 + 'xhtml' => self::XHTML, 33 + 'js' => self::JS, 34 + 'javascript'=> self::JS, 35 + 'yaml' => self::YAML, 36 + 'csv' => self::CSV, 37 + ); 38 + 39 + /** 40 + * Get the full Mime Type name from a "short name". 41 + * Returns the short if no mapping was found. 42 + * @return string full mime type (e.g. application/json) 43 + * @param string common name for mime type (e.g. json) 44 + */ 45 + public static function getFullMime($short_name) 46 + { 47 + return array_key_exists($short_name, self::$mimes) ? self::$mimes[$short_name] : $short_name; 48 + } 49 + 50 + /** 51 + * @return bool 52 + * @param string $short_name 53 + */ 54 + public static function supportsMimeType($short_name) 55 + { 56 + return array_key_exists($short_name, self::$mimes); 57 + } 58 + }
+984
externals/httpful/src/Httpful/Request.php
··· 1 + <?php 2 + 3 + namespace Httpful; 4 + 5 + use Httpful\Exception\ConnectionErrorException; 6 + 7 + /** 8 + * Clean, simple class for sending HTTP requests 9 + * in PHP. 10 + * 11 + * There is an emphasis of readability without loosing concise 12 + * syntax. As such, you will notice that the library lends 13 + * itself very nicely to "chaining". You will see several "alias" 14 + * methods: more readable method definitions that wrap 15 + * their more concise counterparts. You will also notice 16 + * no public constructor. This two adds to the readability 17 + * and "chainabilty" of the library. 18 + * 19 + * @author Nate Good <me@nategood.com> 20 + */ 21 + class Request 22 + { 23 + 24 + // Option constants 25 + const SERIALIZE_PAYLOAD_NEVER = 0; 26 + const SERIALIZE_PAYLOAD_ALWAYS = 1; 27 + const SERIALIZE_PAYLOAD_SMART = 2; 28 + 29 + const MAX_REDIRECTS_DEFAULT = 25; 30 + 31 + public $uri, 32 + $method = Http::GET, 33 + $headers = array(), 34 + $raw_headers = '', 35 + $strict_ssl = false, 36 + $content_type, 37 + $expected_type, 38 + $additional_curl_opts = array(), 39 + $auto_parse = true, 40 + $serialize_payload_method = self::SERIALIZE_PAYLOAD_SMART, 41 + $username, 42 + $password, 43 + $serialized_payload, 44 + $payload, 45 + $parse_callback, 46 + $error_callback, 47 + $follow_redirects = false, 48 + $max_redirects = self::MAX_REDIRECTS_DEFAULT, 49 + $payload_serializers = array(); 50 + 51 + // Options 52 + // private $_options = array( 53 + // 'serialize_payload_method' => self::SERIALIZE_PAYLOAD_SMART 54 + // 'auto_parse' => true 55 + // ); 56 + 57 + // Curl Handle 58 + public $_ch, 59 + $_debug; 60 + 61 + // Template Request object 62 + private static $_template; 63 + 64 + /** 65 + * We made the constructor private to force the factory style. This was 66 + * done to keep the syntax cleaner and better the support the idea of 67 + * "default templates". Very basic and flexible as it is only intended 68 + * for internal use. 69 + * @param array $attrs hash of initial attribute values 70 + */ 71 + private function __construct($attrs = null) 72 + { 73 + if (!is_array($attrs)) return; 74 + foreach ($attrs as $attr => $value) { 75 + $this->$attr = $value; 76 + } 77 + } 78 + 79 + // Defaults Management 80 + 81 + /** 82 + * Let's you configure default settings for this 83 + * class from a template Request object. Simply construct a 84 + * Request object as much as you want to and then pass it to 85 + * this method. It will then lock in those settings from 86 + * that template object. 87 + * The most common of which may be default mime 88 + * settings or strict ssl settings. 89 + * Again some slight memory overhead incurred here but in the grand 90 + * scheme of things as it typically only occurs once 91 + * @param Request $template 92 + */ 93 + public static function ini(Request $template) 94 + { 95 + self::$_template = clone $template; 96 + } 97 + 98 + /** 99 + * Reset the default template back to the 100 + * library defaults. 101 + */ 102 + public static function resetIni() 103 + { 104 + self::_initializeDefaults(); 105 + } 106 + 107 + /** 108 + * Get default for a value based on the template object 109 + * @return mixed default value 110 + * @param string|null $attr Name of attribute (e.g. mime, headers) 111 + * if null just return the whole template object; 112 + */ 113 + public static function d($attr) 114 + { 115 + return isset($attr) ? self::$_template->$attr : self::$_template; 116 + } 117 + 118 + // Accessors 119 + 120 + /** 121 + * @return bool does the request have a timeout? 122 + */ 123 + public function hasTimeout() 124 + { 125 + return isset($this->timeout); 126 + } 127 + 128 + /** 129 + * @return bool has the internal curl request been initialized? 130 + */ 131 + public function hasBeenInitialized() 132 + { 133 + return isset($this->_ch); 134 + } 135 + 136 + /** 137 + * @return bool Is this request setup for basic auth? 138 + */ 139 + public function hasBasicAuth() 140 + { 141 + return isset($this->password) && isset($this->username); 142 + } 143 + 144 + /** 145 + * @return bool Is this request setup for digest auth? 146 + */ 147 + public function hasDigestAuth() 148 + { 149 + return isset($this->password) && isset($this->username) && $this->additional_curl_opts['CURLOPT_HTTPAUTH'] = CURLAUTH_DIGEST; 150 + } 151 + 152 + /** 153 + * Specify a HTTP timeout 154 + * @return Request $this 155 + * @param |int $timeout seconds to timeout the HTTP call 156 + */ 157 + public function timeout($timeout) 158 + { 159 + $this->timeout = $timeout; 160 + return $this; 161 + } 162 + 163 + /** 164 + * If the response is a 301 or 302 redirect, automatically 165 + * send off another request to that location 166 + * @return Request $this 167 + * @param bool|int $follow follow or not to follow or maximal number of redirects 168 + */ 169 + public function followRedirects($follow = true) 170 + { 171 + $this->max_redirects = $follow === true ? self::MAX_REDIRECTS_DEFAULT : max(0, $follow); 172 + $this->follow_redirects = (bool) $follow; 173 + return $this; 174 + } 175 + 176 + /** 177 + * @return Request $this 178 + * @see Request::followRedirects() 179 + */ 180 + public function doNotFollowRedirects() 181 + { 182 + return $this->followRedirects(false); 183 + } 184 + 185 + /** 186 + * Actually send off the request, and parse the response 187 + * @return string|associative array of parsed results 188 + * @throws ConnectionErrorException when unable to parse or communicate w server 189 + */ 190 + public function send() 191 + { 192 + if (!$this->hasBeenInitialized()) 193 + $this->_curlPrep(); 194 + 195 + $result = curl_exec($this->_ch); 196 + 197 + if ($result === false) { 198 + $this->_error(curl_error($this->_ch)); 199 + throw new ConnectionErrorException('Unable to connect.'); 200 + } 201 + 202 + $info = curl_getinfo($this->_ch); 203 + $response = explode("\r\n\r\n", $result, 2 + $info['redirect_count']); 204 + 205 + $body = array_pop($response); 206 + $headers = array_pop($response); 207 + 208 + return new Response($body, $headers, $this); 209 + } 210 + public function sendIt() 211 + { 212 + return $this->send(); 213 + } 214 + 215 + // Setters 216 + 217 + /** 218 + * @return Request this 219 + * @param string $uri 220 + */ 221 + public function uri($uri) 222 + { 223 + $this->uri = $uri; 224 + return $this; 225 + } 226 + 227 + /** 228 + * User Basic Auth. 229 + * Only use when over SSL/TSL/HTTPS. 230 + * @return Request this 231 + * @param string $username 232 + * @param string $password 233 + */ 234 + public function basicAuth($username, $password) 235 + { 236 + $this->username = $username; 237 + $this->password = $password; 238 + return $this; 239 + } 240 + // @alias of basicAuth 241 + public function authenticateWith($username, $password) 242 + { 243 + return $this->basicAuth($username, $password); 244 + } 245 + // @alias of basicAuth 246 + public function authenticateWithBasic($username, $password) 247 + { 248 + return $this->basicAuth($username, $password); 249 + } 250 + 251 + /** 252 + * User Digest Auth. 253 + * @return Request this 254 + * @param string $username 255 + * @param string $password 256 + */ 257 + public function digestAuth($username, $password) 258 + { 259 + $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); 260 + return $this->basicAuth($username, $password); 261 + } 262 + 263 + // @alias of digestAuth 264 + public function authenticateWithDigest($username, $password) 265 + { 266 + return $this->digestAuth($username, $password); 267 + } 268 + 269 + /** 270 + * @return is this request setup for client side cert? 271 + */ 272 + public function hasClientSideCert() { 273 + return isset($this->client_cert) && isset($this->client_key); 274 + } 275 + 276 + /** 277 + * Use Client Side Cert Authentication 278 + * @return Response $this 279 + * @param string $key file path to client key 280 + * @param string $cert file path to client cert 281 + * @param string $passphrase for client key 282 + * @param string $encoding default PEM 283 + */ 284 + public function clientSideCert($cert, $key, $passphrase = null, $encoding = 'PEM') 285 + { 286 + $this->client_cert = $cert; 287 + $this->client_key = $key; 288 + $this->client_passphrase = $passphrase; 289 + $this->client_encoding = $encoding; 290 + 291 + return $this; 292 + } 293 + // @alias of basicAuth 294 + public function authenticateWithCert($cert, $key, $passphrase = null, $encoding = 'PEM') 295 + { 296 + return $this->clientSideCert($cert, $key, $passphrase, $encoding); 297 + } 298 + 299 + /** 300 + * Set the body of the request 301 + * @return Request this 302 + * @param mixed $payload 303 + * @param string $mimeType 304 + */ 305 + public function body($payload, $mimeType = null) 306 + { 307 + $this->mime($mimeType); 308 + $this->payload = $payload; 309 + // Iserntentially don't call _serializePayload yet. Wait until 310 + // we actually send off the request to convert payload to string. 311 + // At that time, the `serialized_payload` is set accordingly. 312 + return $this; 313 + } 314 + 315 + /** 316 + * Helper function to set the Content type and Expected as same in 317 + * one swoop 318 + * @return Request this 319 + * @param string $mime mime type to use for content type and expected return type 320 + */ 321 + public function mime($mime) 322 + { 323 + if (empty($mime)) return $this; 324 + $this->content_type = $this->expected_type = Mime::getFullMime($mime); 325 + return $this; 326 + } 327 + // @alias of mime 328 + public function sendsAndExpectsType($mime) 329 + { 330 + return $this->mime($mime); 331 + } 332 + // @alias of mime 333 + public function sendsAndExpects($mime) 334 + { 335 + return $this->mime($mime); 336 + } 337 + 338 + /** 339 + * Set the method. Shouldn't be called often as the preferred syntax 340 + * for instantiation is the method specific factory methods. 341 + * @return Request this 342 + * @param string $method 343 + */ 344 + public function method($method) 345 + { 346 + if (empty($method)) return $this; 347 + $this->method = $method; 348 + return $this; 349 + } 350 + 351 + /** 352 + * @return Request this 353 + * @param string $mime 354 + */ 355 + public function expects($mime) 356 + { 357 + if (empty($mime)) return $this; 358 + $this->expected_type = Mime::getFullMime($mime); 359 + return $this; 360 + } 361 + // @alias of expects 362 + public function expectsType($mime) 363 + { 364 + return $this->expects($mime); 365 + } 366 + 367 + /** 368 + * @return Request this 369 + * @param string $mime 370 + */ 371 + public function contentType($mime) 372 + { 373 + if (empty($mime)) return $this; 374 + $this->content_type = Mime::getFullMime($mime); 375 + return $this; 376 + } 377 + // @alias of contentType 378 + public function sends($mime) 379 + { 380 + return $this->contentType($mime); 381 + } 382 + // @alias of contentType 383 + public function sendsType($mime) 384 + { 385 + return $this->contentType($mime); 386 + } 387 + 388 + /** 389 + * Do we strictly enforce SSL verification? 390 + * @return Request this 391 + * @param bool $strict 392 + */ 393 + public function strictSSL($strict) 394 + { 395 + $this->strict_ssl = $strict; 396 + return $this; 397 + } 398 + public function withoutStrictSSL() 399 + { 400 + return $this->strictSSL(false); 401 + } 402 + public function withStrictSSL() 403 + { 404 + return $this->strictSSL(true); 405 + } 406 + 407 + /** 408 + * Determine how/if we use the built in serialization by 409 + * setting the serialize_payload_method 410 + * The default (SERIALIZE_PAYLOAD_SMART) is... 411 + * - if payload is not a scalar (object/array) 412 + * use the appropriate serialize method according to 413 + * the Content-Type of this request. 414 + * - if the payload IS a scalar (int, float, string, bool) 415 + * than just return it as is. 416 + * When this option is set SERIALIZE_PAYLOAD_ALWAYS, 417 + * it will always use the appropriate 418 + * serialize option regardless of whether payload is scalar or not 419 + * When this option is set SERIALIZE_PAYLOAD_NEVER, 420 + * it will never use any of the serialization methods. 421 + * Really the only use for this is if you want the serialize methods 422 + * to handle strings or not (e.g. Blah is not valid JSON, but "Blah" 423 + * is). Forcing the serialization helps prevent that kind of error from 424 + * happening. 425 + * @return Request $this 426 + * @param int $mode 427 + */ 428 + public function serializePayload($mode) 429 + { 430 + $this->serialize_payload_method = $mode; 431 + return $this; 432 + } 433 + 434 + /** 435 + * @see Request::serializePayload() 436 + * @return Request 437 + */ 438 + public function neverSerializePayload() 439 + { 440 + return $this->serializePayload(self::SERIALIZE_PAYLOAD_NEVER); 441 + } 442 + 443 + /** 444 + * This method is the default behavior 445 + * @see Request::serializePayload() 446 + * @return Request 447 + */ 448 + public function smartSerializePayload() 449 + { 450 + return $this->serializePayload(self::SERIALIZE_PAYLOAD_SMART); 451 + } 452 + 453 + /** 454 + * @see Request::serializePayload() 455 + * @return Request 456 + */ 457 + public function alwaysSerializePayload() 458 + { 459 + return $this->serializePayload(self::SERIALIZE_PAYLOAD_ALWAYS); 460 + } 461 + 462 + /** 463 + * Add an additional header to the request 464 + * Can also use the cleaner syntax of 465 + * $Request->withMyHeaderName($my_value); 466 + * @see Request::__call() 467 + * 468 + * @return Request this 469 + * @param string $header_name 470 + * @param string $value 471 + */ 472 + public function addHeader($header_name, $value) 473 + { 474 + $this->headers[$header_name] = $value; 475 + return $this; 476 + } 477 + 478 + /** 479 + * Add group of headers all at once. Note: This is 480 + * here just as a convenience in very specific cases. 481 + * The preferred "readable" way would be to leverage 482 + * the support for custom header methods. 483 + * @return Response $this 484 + * @param array $headers 485 + */ 486 + public function addHeaders(array $headers) 487 + { 488 + foreach ($headers as $header => $value) { 489 + $this->addHeader($header, $value); 490 + } 491 + return $this; 492 + } 493 + 494 + /** 495 + * @return Request 496 + * @param bool $auto_parse perform automatic "smart" 497 + * parsing based on Content-Type or "expectedType" 498 + * If not auto parsing, Response->body returns the body 499 + * as a string. 500 + */ 501 + public function autoParse($auto_parse = true) 502 + { 503 + $this->auto_parse = $auto_parse; 504 + return $this; 505 + } 506 + 507 + /** 508 + * @see Request::autoParse() 509 + * @return Request 510 + */ 511 + public function withoutAutoParsing() 512 + { 513 + return $this->autoParse(false); 514 + } 515 + 516 + /** 517 + * @see Request::autoParse() 518 + * @return Request 519 + */ 520 + public function withAutoParsing() 521 + { 522 + return $this->autoParse(true); 523 + } 524 + 525 + /** 526 + * Use a custom function to parse the response. 527 + * @return Request this 528 + * @param \Closure $callback Takes the raw body of 529 + * the http response and returns a mixed 530 + */ 531 + public function parseWith(\Closure $callback) 532 + { 533 + $this->parse_callback = $callback; 534 + return $this; 535 + } 536 + 537 + /** 538 + * @see Request::parseResponsesWith() 539 + * @return Request $this 540 + * @param \Closure $callback 541 + */ 542 + public function parseResponsesWith(\Closure $callback) 543 + { 544 + return $this->parseWith($callback); 545 + } 546 + 547 + /** 548 + * Register a callback that will be used to serialize the payload 549 + * for a particular mime type. When using "*" for the mime 550 + * type, it will use that parser for all responses regardless of the mime 551 + * type. If a custom '*' and 'application/json' exist, the custom 552 + * 'application/json' would take precedence over the '*' callback. 553 + * 554 + * @return Request $this 555 + * @param string $mime mime type we're registering 556 + * @param Closure $callback takes one argument, $payload, 557 + * which is the payload that we'll be 558 + */ 559 + public function registerPayloadSerializer($mime, \Closure $callback) 560 + { 561 + $this->payload_serializers[Mime::getFullMime($mime)] = $callback; 562 + return $this; 563 + } 564 + 565 + /** 566 + * @see Request::registerPayloadSerializer() 567 + * @return Request $this 568 + * @param Closure $callback 569 + */ 570 + public function serializePayloadWith(\Closure $callback) 571 + { 572 + return $this->regregisterPayloadSerializer('*', $callback); 573 + } 574 + 575 + /** 576 + * Magic method allows for neatly setting other headers in a 577 + * similar syntax as the other setters. This method also allows 578 + * for the sends* syntax. 579 + * @return Request this 580 + * @param string $method "missing" method name called 581 + * the method name called should be the name of the header that you 582 + * are trying to set in camel case without dashes e.g. to set a 583 + * header for Content-Type you would use contentType() or more commonly 584 + * to add a custom header like X-My-Header, you would use xMyHeader(). 585 + * To promote readability, you can optionally prefix these methods with 586 + * "with" (e.g. withXMyHeader("blah") instead of xMyHeader("blah")). 587 + * @param array $args in this case, there should only ever be 1 argument provided 588 + * and that argument should be a string value of the header we're setting 589 + */ 590 + public function __call($method, $args) 591 + { 592 + // This method supports the sends* methods 593 + // like sendsJSON, sendsForm 594 + //!method_exists($this, $method) && 595 + if (substr($method, 0, 5) === 'sends') { 596 + $mime = strtolower(substr($method, 5)); 597 + if (Mime::supportsMimeType($mime)) { 598 + $this->sends(Mime::getFullMime($mime)); 599 + return $this; 600 + } 601 + // else { 602 + // throw new \Exception("Unsupported Content-Type $mime"); 603 + // } 604 + } 605 + if (substr($method, 0, 7) === 'expects') { 606 + $mime = strtolower(substr($method, 7)); 607 + if (Mime::supportsMimeType($mime)) { 608 + $this->expects(Mime::getFullMime($mime)); 609 + return $this; 610 + } 611 + // else { 612 + // throw new \Exception("Unsupported Content-Type $mime"); 613 + // } 614 + } 615 + 616 + // This method also adds the custom header support as described in the 617 + // method comments 618 + if (count($args) === 0) 619 + return; 620 + 621 + // Strip the sugar. If it leads with "with", strip. 622 + // This is okay because: No defined HTTP headers begin with with, 623 + // and if you are defining a custom header, the standard is to prefix it 624 + // with an "X-", so that should take care of any collisions. 625 + if (substr($method, 0, 4) === 'with') 626 + $method = substr($method, 4); 627 + 628 + // Precede upper case letters with dashes, uppercase the first letter of method 629 + $header = ucwords(implode('-', preg_split('/([A-Z][^A-Z]*)/', $method, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY))); 630 + $this->addHeader($header, $args[0]); 631 + return $this; 632 + } 633 + 634 + // Internal Functions 635 + 636 + /** 637 + * This is the default template to use if no 638 + * template has been provided. The template 639 + * tells the class which default values to use. 640 + * While there is a slight overhead for object 641 + * creation once per execution (not once per 642 + * Request instantiation), it promotes readability 643 + * and flexibility within the class. 644 + */ 645 + private static function _initializeDefaults() 646 + { 647 + // This is the only place you will 648 + // see this constructor syntax. It 649 + // is only done here to prevent infinite 650 + // recusion. Do not use this syntax elsewhere. 651 + // It goes against the whole readability 652 + // and transparency idea. 653 + self::$_template = new Request(array('method' => Http::GET)); 654 + 655 + // This is more like it... 656 + self::$_template 657 + ->withoutStrictSSL(); 658 + } 659 + 660 + /** 661 + * Set the defaults on a newly instantiated object 662 + * Doesn't copy variables prefixed with _ 663 + * @return Request this 664 + */ 665 + private function _setDefaults() 666 + { 667 + if (!isset(self::$_template)) 668 + self::_initializeDefaults(); 669 + foreach (self::$_template as $k=>$v) { 670 + if ($k[0] != '_') 671 + $this->$k = $v; 672 + } 673 + return $this; 674 + } 675 + 676 + private function _error($error) 677 + { 678 + // Default actions write to error log 679 + // TODO add in support for various Loggers 680 + error_log($error); 681 + } 682 + 683 + /** 684 + * Factory style constructor works nicer for chaining. This 685 + * should also really only be used internally. The Request::get, 686 + * Request::post syntax is preferred as it is more readable. 687 + * @return Request 688 + * @param string $method Http Method 689 + * @param string $mime Mime Type to Use 690 + */ 691 + public static function init($method = null, $mime = null) 692 + { 693 + // Setup our handlers, can call it here as it's idempotent 694 + Bootstrap::init(); 695 + 696 + // Setup the default template if need be 697 + if (!isset(self::$_template)) 698 + self::_initializeDefaults(); 699 + 700 + $request = new Request(); 701 + return $request 702 + ->_setDefaults() 703 + ->method($method) 704 + ->sendsType($mime) 705 + ->expectsType($mime); 706 + } 707 + 708 + /** 709 + * Does the heavy lifting. Uses de facto HTTP 710 + * library cURL to set up the HTTP request. 711 + * Note: It does NOT actually send the request 712 + * @return Request $this; 713 + */ 714 + public function _curlPrep() 715 + { 716 + // Check for required stuff 717 + if (!isset($this->uri)) 718 + throw new \Exception('Attempting to send a request before defining a URI endpoint.'); 719 + 720 + $ch = curl_init($this->uri); 721 + 722 + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method); 723 + 724 + if ($this->hasBasicAuth()) { 725 + curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password); 726 + } 727 + 728 + if ($this->hasClientSideCert()) { 729 + 730 + if (!file_exists($this->client_key)) 731 + throw new \Exception('Could not read Client Key'); 732 + 733 + if (!file_exists($this->client_cert)) 734 + throw new \Exception('Could not read Client Certificate'); 735 + 736 + curl_setopt($ch, CURLOPT_SSLCERTTYPE, $this->client_encoding); 737 + curl_setopt($ch, CURLOPT_SSLKEYTYPE, $this->client_encoding); 738 + curl_setopt($ch, CURLOPT_SSLCERT, $this->client_cert); 739 + curl_setopt($ch, CURLOPT_SSLKEY, $this->client_key); 740 + curl_setopt($ch, CURLOPT_SSLKEYPASSWD, $this->client_passphrase); 741 + // curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $this->client_cert_passphrase); 742 + } 743 + 744 + if ($this->hasTimeout()) { 745 + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); 746 + } 747 + 748 + if ($this->follow_redirects) { 749 + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 750 + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects); 751 + } 752 + 753 + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->strict_ssl); 754 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 755 + 756 + $headers = array(); 757 + // https://github.com/nategood/httpful/issues/37 758 + // Except header removes any HTTP 1.1 Continue from response headers 759 + $headers[] = 'Expect:'; 760 + 761 + if (!isset($this->headers['User-Agent'])) { 762 + $headers[] = $this->buildUserAgent(); 763 + } 764 + 765 + $headers[] = "Content-Type: {$this->content_type}"; 766 + 767 + // allow custom Accept header if set 768 + if (!isset($this->headers['Accept'])) { 769 + // http://pretty-rfc.herokuapp.com/RFC2616#header.accept 770 + $accept = 'Accept: */*; q=0.5, text/plain; q=0.8, text/html;level=3;'; 771 + 772 + if (!empty($this->expected_type)) { 773 + $accept .= "q=0.9, {$this->expected_type}"; 774 + } 775 + 776 + $headers[] = $accept; 777 + } 778 + 779 + foreach ($this->headers as $header => $value) { 780 + $headers[] = "$header: $value"; 781 + } 782 + 783 + $url = \parse_url($this->uri); 784 + $path = (isset($url['path']) ? $url['path'] : '/').(isset($url['query']) ? '?'.$url['query'] : ''); 785 + $this->raw_headers = "{$this->method} $path HTTP/1.1\r\n"; 786 + $host = (isset($url['host']) ? $url['host'] : 'localhost').(isset($url['port']) ? ':'.$url['port'] : ''); 787 + $this->raw_headers .= "Host: $host\r\n"; 788 + $this->raw_headers .= \implode("\r\n", $headers); 789 + $this->raw_headers .= "\r\n"; 790 + 791 + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 792 + 793 + if (isset($this->payload)) { 794 + $this->serialized_payload = $this->_serializePayload($this->payload); 795 + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->serialized_payload); 796 + } 797 + 798 + if ($this->_debug) { 799 + curl_setopt($ch, CURLOPT_VERBOSE, true); 800 + } 801 + 802 + curl_setopt($ch, CURLOPT_HEADER, 1); 803 + 804 + // If there are some additional curl opts that the user wants 805 + // to set, we can tack them in here 806 + foreach ($this->additional_curl_opts as $curlopt => $curlval) { 807 + curl_setopt($ch, $curlopt, $curlval); 808 + } 809 + 810 + $this->_ch = $ch; 811 + 812 + return $this; 813 + } 814 + 815 + public function buildUserAgent() { 816 + $user_agent = 'User-Agent: Httpful/' . Httpful::VERSION . ' (cURL/'; 817 + $curl = \curl_version(); 818 + 819 + if (isset($curl['version'])) { 820 + $user_agent .= $curl['version']; 821 + } else { 822 + $user_agent .= '?.?.?'; 823 + } 824 + 825 + $user_agent .= ' PHP/'. PHP_VERSION . ' (' . PHP_OS . ')'; 826 + 827 + if (isset($_SERVER['SERVER_SOFTWARE'])) { 828 + $user_agent .= ' ' . \preg_replace('~PHP/[\d\.]+~U', '', 829 + $_SERVER['SERVER_SOFTWARE']); 830 + } else { 831 + if (isset($_SERVER['TERM_PROGRAM'])) { 832 + $user_agent .= " {$_SERVER['TERM_PROGRAM']}"; 833 + } 834 + 835 + if (isset($_SERVER['TERM_PROGRAM_VERSION'])) { 836 + $user_agent .= "/{$_SERVER['TERM_PROGRAM_VERSION']}"; 837 + } 838 + } 839 + 840 + if (isset($_SERVER['HTTP_USER_AGENT'])) { 841 + $user_agent .= " {$_SERVER['HTTP_USER_AGENT']}"; 842 + } 843 + 844 + $user_agent .= ')'; 845 + 846 + return $user_agent; 847 + } 848 + 849 + /** 850 + * Semi-reluctantly added this as a way to add in curl opts 851 + * that are not otherwise accessible from the rest of the API. 852 + * @return Request $this 853 + * @param string $curlopt 854 + * @param mixed $curloptval 855 + */ 856 + public function addOnCurlOption($curlopt, $curloptval) 857 + { 858 + $this->additional_curl_opts[$curlopt] = $curloptval; 859 + return $this; 860 + } 861 + 862 + /** 863 + * Turn payload from structured data into 864 + * a string based on the current Mime type. 865 + * This uses the auto_serialize option to determine 866 + * it's course of action. See serialize method for more. 867 + * Renamed from _detectPayload to _serializePayload as of 868 + * 2012-02-15. 869 + * 870 + * Added in support for custom payload serializers. 871 + * The serialize_payload_method stuff still holds true though. 872 + * @see Request::registerPayloadSerializer() 873 + * 874 + * @return string 875 + * @param mixed $payload 876 + */ 877 + private function _serializePayload($payload) 878 + { 879 + if (empty($payload) || $this->serialize_payload_method === self::SERIALIZE_PAYLOAD_NEVER) 880 + return $payload; 881 + 882 + // When we are in "smart" mode, don't serialize strings/scalars, assume they are already serialized 883 + if ($this->serialize_payload_method === self::SERIALIZE_PAYLOAD_SMART && is_scalar($payload)) 884 + return $payload; 885 + 886 + // Use a custom serializer if one is registered for this mime type 887 + if (isset($this->payload_serializers['*']) || isset($this->payload_serializers[$this->content_type])) { 888 + $key = isset($this->payload_serializers[$this->content_type]) ? $this->content_type : '*'; 889 + return call_user_func($this->payload_serializers[$key], $payload); 890 + } 891 + 892 + return Httpful::get($this->content_type)->serialize($payload); 893 + } 894 + 895 + /** 896 + * HTTP Method Get 897 + * @return Request 898 + * @param string $uri optional uri to use 899 + * @param string $mime expected 900 + */ 901 + public static function get($uri, $mime = null) 902 + { 903 + return self::init(Http::GET)->uri($uri)->mime($mime); 904 + } 905 + 906 + 907 + /** 908 + * Like Request:::get, except that it sends off the request as well 909 + * returning a response 910 + * @return Response 911 + * @param string $uri optional uri to use 912 + * @param string $mime expected 913 + */ 914 + public static function getQuick($uri, $mime = null) 915 + { 916 + return self::get($uri, $mime)->send(); 917 + } 918 + 919 + /** 920 + * HTTP Method Post 921 + * @return Request 922 + * @param string $uri optional uri to use 923 + * @param string $payload data to send in body of request 924 + * @param string $mime MIME to use for Content-Type 925 + */ 926 + public static function post($uri, $payload = null, $mime = null) 927 + { 928 + return self::init(Http::POST)->uri($uri)->body($payload, $mime); 929 + } 930 + 931 + /** 932 + * HTTP Method Put 933 + * @return Request 934 + * @param string $uri optional uri to use 935 + * @param string $payload data to send in body of request 936 + * @param string $mime MIME to use for Content-Type 937 + */ 938 + public static function put($uri, $payload = null, $mime = null) 939 + { 940 + return self::init(Http::PUT)->uri($uri)->body($payload, $mime); 941 + } 942 + 943 + /** 944 + * HTTP Method Patch 945 + * @return Request 946 + * @param string $uri optional uri to use 947 + * @param string $payload data to send in body of request 948 + * @param string $mime MIME to use for Content-Type 949 + */ 950 + public static function patch($uri, $payload = null, $mime = null) 951 + { 952 + return self::init(Http::PATCH)->uri($uri)->body($payload, $mime); 953 + } 954 + 955 + /** 956 + * HTTP Method Delete 957 + * @return Request 958 + * @param string $uri optional uri to use 959 + */ 960 + public static function delete($uri, $mime = null) 961 + { 962 + return self::init(Http::DELETE)->uri($uri)->mime($mime); 963 + } 964 + 965 + /** 966 + * HTTP Method Head 967 + * @return Request 968 + * @param string $uri optional uri to use 969 + */ 970 + public static function head($uri) 971 + { 972 + return self::init(Http::HEAD)->uri($uri); 973 + } 974 + 975 + /** 976 + * HTTP Method Options 977 + * @return Request 978 + * @param string $uri optional uri to use 979 + */ 980 + public static function options($uri) 981 + { 982 + return self::init(Http::OPTIONS)->uri($uri); 983 + } 984 + }
+189
externals/httpful/src/Httpful/Response.php
··· 1 + <?php 2 + 3 + namespace Httpful; 4 + 5 + /** 6 + * Models an HTTP response 7 + * 8 + * @author Nate Good <me@nategood.com> 9 + */ 10 + class Response 11 + { 12 + 13 + public $body, 14 + $raw_body, 15 + $headers, 16 + $raw_headers, 17 + $request, 18 + $code = 0, 19 + $content_type, 20 + $parent_type, 21 + $charset, 22 + $is_mime_vendor_specific = false, 23 + $is_mime_personal = false; 24 + 25 + private $parsers; 26 + /** 27 + * @param string $body 28 + * @param string $headers 29 + * @param Request $request 30 + */ 31 + public function __construct($body, $headers, Request $request) 32 + { 33 + $this->request = $request; 34 + $this->raw_headers = $headers; 35 + $this->raw_body = $body; 36 + 37 + $this->code = $this->_parseCode($headers); 38 + $this->headers = Response\Headers::fromString($headers); 39 + 40 + $this->_interpretHeaders(); 41 + 42 + $this->body = $this->_parse($body); 43 + } 44 + 45 + /** 46 + * Status Code Definitions 47 + * 48 + * Informational 1xx 49 + * Successful 2xx 50 + * Redirection 3xx 51 + * Client Error 4xx 52 + * Server Error 5xx 53 + * 54 + * http://pretty-rfc.herokuapp.com/RFC2616#status.codes 55 + * 56 + * @return bool Did we receive a 4xx or 5xx? 57 + */ 58 + public function hasErrors() 59 + { 60 + return $this->code >= 400; 61 + } 62 + 63 + /** 64 + * @return return bool 65 + */ 66 + public function hasBody() 67 + { 68 + return !empty($this->body); 69 + } 70 + 71 + /** 72 + * Parse the response into a clean data structure 73 + * (most often an associative array) based on the expected 74 + * Mime type. 75 + * @return array|string|object the response parse accordingly 76 + * @param string Http response body 77 + */ 78 + public function _parse($body) 79 + { 80 + // If the user decided to forgo the automatic 81 + // smart parsing, short circuit. 82 + if (!$this->request->auto_parse) { 83 + return $body; 84 + } 85 + 86 + // If provided, use custom parsing callback 87 + if (isset($this->request->parse_callback)) { 88 + return call_user_func($this->request->parse_callback, $body); 89 + } 90 + 91 + // Decide how to parse the body of the response in the following order 92 + // 1. If provided, use the mime type specifically set as part of the `Request` 93 + // 2. If a MimeHandler is registered for the content type, use it 94 + // 3. If provided, use the "parent type" of the mime type from the response 95 + // 4. Default to the content-type provided in the response 96 + $parse_with = $this->request->expected_type; 97 + if (empty($this->request->expected_type)) { 98 + $parse_with = Httpful::hasParserRegistered($this->content_type) 99 + ? $this->content_type 100 + : $this->parent_type; 101 + } 102 + 103 + return Httpful::get($parse_with)->parse($body); 104 + } 105 + 106 + /** 107 + * Parse text headers from response into 108 + * array of key value pairs 109 + * @return array parse headers 110 + * @param string $headers raw headers 111 + */ 112 + public function _parseHeaders($headers) 113 + { 114 + $headers = preg_split("/(\r|\n)+/", $headers, -1, \PREG_SPLIT_NO_EMPTY); 115 + $parse_headers = array(); 116 + for ($i = 1; $i < count($headers); $i++) { 117 + list($key, $raw_value) = explode(':', $headers[$i], 2); 118 + $key = trim($key); 119 + $value = trim($raw_value); 120 + if (array_key_exists($key, $parse_headers)) { 121 + // See HTTP RFC Sec 4.2 Paragraph 5 122 + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 123 + // If a header appears more than once, it must also be able to 124 + // be represented as a single header with a comma-separated 125 + // list of values. We transform accordingly. 126 + $parse_headers[$key] .= ',' . $value; 127 + } else { 128 + $parse_headers[$key] = $value; 129 + } 130 + } 131 + return $parse_headers; 132 + } 133 + 134 + public function _parseCode($headers) 135 + { 136 + $parts = explode(' ', substr($headers, 0, strpos($headers, "\r\n"))); 137 + if (count($parts) < 2 || !is_numeric($parts[1])) { 138 + throw new \Exception("Unable to parse response code from HTTP response due to malformed response"); 139 + } 140 + return intval($parts[1]); 141 + } 142 + 143 + /** 144 + * After we've parse the headers, let's clean things 145 + * up a bit and treat some headers specially 146 + */ 147 + public function _interpretHeaders() 148 + { 149 + // Parse the Content-Type and charset 150 + $content_type = isset($this->headers['Content-Type']) ? $this->headers['Content-Type'] : ''; 151 + $content_type = explode(';', $content_type); 152 + 153 + $this->content_type = $content_type[0]; 154 + if (count($content_type) == 2 && strpos($content_type[1], '=') !== false) { 155 + list($nill, $this->charset) = explode('=', $content_type[1]); 156 + } 157 + 158 + // RFC 2616 states "text/*" Content-Types should have a default 159 + // charset of ISO-8859-1. "application/*" and other Content-Types 160 + // are assumed to have UTF-8 unless otherwise specified. 161 + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 162 + // http://www.w3.org/International/O-HTTP-charset.en.php 163 + if (!isset($this->charset)) { 164 + $this->charset = substr($this->content_type, 5) === 'text/' ? 'iso-8859-1' : 'utf-8'; 165 + } 166 + 167 + // Is vendor type? Is personal type? 168 + if (strpos($this->content_type, '/') !== false) { 169 + list($type, $sub_type) = explode('/', $this->content_type); 170 + $this->is_mime_vendor_specific = substr($sub_type, 0, 4) === 'vnd.'; 171 + $this->is_mime_personal = substr($sub_type, 0, 4) === 'prs.'; 172 + } 173 + 174 + // Parent type (e.g. xml for application/vnd.github.message+xml) 175 + $this->parent_type = $this->content_type; 176 + if (strpos($this->content_type, '+') !== false) { 177 + list($vendor, $this->parent_type) = explode('+', $this->content_type, 2); 178 + $this->parent_type = Mime::getFullMime($this->parent_type); 179 + } 180 + } 181 + 182 + /** 183 + * @return string 184 + */ 185 + public function __toString() 186 + { 187 + return $this->raw_body; 188 + } 189 + }
+58
externals/httpful/src/Httpful/Response/Headers.php
··· 1 + <?php 2 + 3 + namespace Httpful\Response; 4 + 5 + final class Headers implements \ArrayAccess, \Countable { 6 + 7 + private $headers; 8 + 9 + private function __construct($headers) 10 + { 11 + $this->headers = $headers; 12 + } 13 + 14 + public static function fromString($string) 15 + { 16 + $lines = preg_split("/(\r|\n)+/", $string, -1, PREG_SPLIT_NO_EMPTY); 17 + array_shift($lines); // HTTP HEADER 18 + $headers = array(); 19 + foreach ($lines as $line) { 20 + list($name, $value) = explode(':', $line, 2); 21 + $headers[strtolower(trim($name))] = trim($value); 22 + } 23 + return new self($headers); 24 + } 25 + 26 + public function offsetExists($offset) 27 + { 28 + return isset($this->headers[strtolower($offset)]); 29 + } 30 + 31 + public function offsetGet($offset) 32 + { 33 + if (isset($this->headers[$name = strtolower($offset)])) { 34 + return $this->headers[$name]; 35 + } 36 + } 37 + 38 + public function offsetSet($offset, $value) 39 + { 40 + throw new \Exception("Headers are read-only."); 41 + } 42 + 43 + public function offsetUnset($offset) 44 + { 45 + throw new \Exception("Headers are read-only."); 46 + } 47 + 48 + public function count() 49 + { 50 + return count($this->headers); 51 + } 52 + 53 + public function toArray() 54 + { 55 + return $this->headers; 56 + } 57 + 58 + }
+458
externals/httpful/tests/Httpful/HttpfulTest.php
··· 1 + <?php 2 + /** 3 + * Port over the original tests into a more traditional PHPUnit 4 + * format. Still need to hook into a lightweight HTTP server to 5 + * better test some things (e.g. obscure cURL settings). I've moved 6 + * the old tests and node.js server to the tests/.legacy directory. 7 + * 8 + * @author Nate Good <me@nategood.com> 9 + */ 10 + namespace Httpful\Test; 11 + 12 + require(dirname(dirname(dirname(__FILE__))) . '/bootstrap.php'); 13 + \Httpful\Bootstrap::init(); 14 + 15 + use Httpful\Httpful; 16 + use Httpful\Request; 17 + use Httpful\Mime; 18 + use Httpful\Http; 19 + use Httpful\Response; 20 + 21 + class HttpfulTest extends \PHPUnit_Framework_TestCase 22 + { 23 + const TEST_SERVER = '127.0.0.1:8008'; 24 + const TEST_URL = 'http://127.0.0.1:8008'; 25 + const TEST_URL_400 = 'http://127.0.0.1:8008/400'; 26 + 27 + const SAMPLE_JSON_HEADER = 28 + "HTTP/1.1 200 OK 29 + Content-Type: application/json 30 + Connection: keep-alive 31 + Transfer-Encoding: chunked\r\n"; 32 + const SAMPLE_JSON_RESPONSE = '{"key":"value","object":{"key":"value"},"array":[1,2,3,4]}'; 33 + const SAMPLE_CSV_HEADER = 34 + "HTTP/1.1 200 OK 35 + Content-Type: text/csv 36 + Connection: keep-alive 37 + Transfer-Encoding: chunked\r\n"; 38 + const SAMPLE_CSV_RESPONSE = 39 + "Key1,Key2 40 + Value1,Value2 41 + \"40.0\",\"Forty\""; 42 + const SAMPLE_XML_RESPONSE = '<stdClass><arrayProp><array><k1><myClass><intProp>2</intProp></myClass></k1></array></arrayProp><stringProp>a string</stringProp><boolProp>TRUE</boolProp></stdClass>'; 43 + const SAMPLE_XML_HEADER = 44 + "HTTP/1.1 200 OK 45 + Content-Type: application/xml 46 + Connection: keep-alive 47 + Transfer-Encoding: chunked\r\n"; 48 + const SAMPLE_VENDOR_HEADER = 49 + "HTTP/1.1 200 OK 50 + Content-Type: application/vnd.nategood.message+xml 51 + Connection: keep-alive 52 + Transfer-Encoding: chunked\r\n"; 53 + const SAMPLE_VENDOR_TYPE = "application/vnd.nategood.message+xml"; 54 + const SAMPLE_MULTI_HEADER = 55 + "HTTP/1.1 200 OK 56 + Content-Type: application/json 57 + Connection: keep-alive 58 + Transfer-Encoding: chunked 59 + X-My-Header:Value1 60 + X-My-Header:Value2\r\n"; 61 + function testInit() 62 + { 63 + $r = Request::init(); 64 + // Did we get a 'Request' object? 65 + $this->assertEquals('Httpful\Request', get_class($r)); 66 + } 67 + 68 + function testMethods() 69 + { 70 + $valid_methods = array('get', 'post', 'delete', 'put', 'options', 'head'); 71 + $url = 'http://example.com/'; 72 + foreach ($valid_methods as $method) { 73 + $r = call_user_func(array('Httpful\Request', $method), $url); 74 + $this->assertEquals('Httpful\Request', get_class($r)); 75 + $this->assertEquals(strtoupper($method), $r->method); 76 + } 77 + } 78 + 79 + function testDefaults() 80 + { 81 + // Our current defaults are as follows 82 + $r = Request::init(); 83 + $this->assertEquals(Http::GET, $r->method); 84 + $this->assertFalse($r->strict_ssl); 85 + } 86 + 87 + function testShortMime() 88 + { 89 + // Valid short ones 90 + $this->assertEquals(Mime::JSON, Mime::getFullMime('json')); 91 + $this->assertEquals(Mime::XML, Mime::getFullMime('xml')); 92 + $this->assertEquals(Mime::HTML, Mime::getFullMime('html')); 93 + $this->assertEquals(Mime::CSV, Mime::getFullMime('csv')); 94 + 95 + // Valid long ones 96 + $this->assertEquals(Mime::JSON, Mime::getFullMime(Mime::JSON)); 97 + $this->assertEquals(Mime::XML, Mime::getFullMime(Mime::XML)); 98 + $this->assertEquals(Mime::HTML, Mime::getFullMime(Mime::HTML)); 99 + $this->assertEquals(Mime::CSV, Mime::getFullMime(Mime::CSV)); 100 + 101 + // No false positives 102 + $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::HTML)); 103 + $this->assertNotEquals(Mime::JSON, Mime::getFullMime(Mime::XML)); 104 + $this->assertNotEquals(Mime::HTML, Mime::getFullMime(Mime::JSON)); 105 + $this->assertNotEquals(Mime::XML, Mime::getFullMime(Mime::CSV)); 106 + } 107 + 108 + function testSettingStrictSsl() 109 + { 110 + $r = Request::init() 111 + ->withStrictSsl(); 112 + 113 + $this->assertTrue($r->strict_ssl); 114 + 115 + $r = Request::init() 116 + ->withoutStrictSsl(); 117 + 118 + $this->assertFalse($r->strict_ssl); 119 + } 120 + 121 + function testSendsAndExpectsType() 122 + { 123 + $r = Request::init() 124 + ->sendsAndExpectsType(Mime::JSON); 125 + $this->assertEquals(Mime::JSON, $r->expected_type); 126 + $this->assertEquals(Mime::JSON, $r->content_type); 127 + 128 + $r = Request::init() 129 + ->sendsAndExpectsType('html'); 130 + $this->assertEquals(Mime::HTML, $r->expected_type); 131 + $this->assertEquals(Mime::HTML, $r->content_type); 132 + 133 + $r = Request::init() 134 + ->sendsAndExpectsType('form'); 135 + $this->assertEquals(Mime::FORM, $r->expected_type); 136 + $this->assertEquals(Mime::FORM, $r->content_type); 137 + 138 + $r = Request::init() 139 + ->sendsAndExpectsType('application/x-www-form-urlencoded'); 140 + $this->assertEquals(Mime::FORM, $r->expected_type); 141 + $this->assertEquals(Mime::FORM, $r->content_type); 142 + 143 + $r = Request::init() 144 + ->sendsAndExpectsType(Mime::CSV); 145 + $this->assertEquals(Mime::CSV, $r->expected_type); 146 + $this->assertEquals(Mime::CSV, $r->content_type); 147 + } 148 + 149 + function testIni() 150 + { 151 + // Test setting defaults/templates 152 + 153 + // Create the template 154 + $template = Request::init() 155 + ->method(Http::POST) 156 + ->withStrictSsl() 157 + ->expectsType(Mime::HTML) 158 + ->sendsType(Mime::FORM); 159 + 160 + Request::ini($template); 161 + 162 + $r = Request::init(); 163 + 164 + $this->assertTrue($r->strict_ssl); 165 + $this->assertEquals(Http::POST, $r->method); 166 + $this->assertEquals(Mime::HTML, $r->expected_type); 167 + $this->assertEquals(Mime::FORM, $r->content_type); 168 + 169 + // Test the default accessor as well 170 + $this->assertTrue(Request::d('strict_ssl')); 171 + $this->assertEquals(Http::POST, Request::d('method')); 172 + $this->assertEquals(Mime::HTML, Request::d('expected_type')); 173 + $this->assertEquals(Mime::FORM, Request::d('content_type')); 174 + 175 + Request::resetIni(); 176 + } 177 + 178 + function testAccept() 179 + { 180 + $r = Request::get('http://example.com/') 181 + ->expectsType(Mime::JSON); 182 + 183 + $this->assertEquals(Mime::JSON, $r->expected_type); 184 + $r->_curlPrep(); 185 + $this->assertContains('application/json', $r->raw_headers); 186 + } 187 + 188 + function testCustomAccept() 189 + { 190 + $accept = 'application/api-1.0+json'; 191 + $r = Request::get('http://example.com/') 192 + ->addHeader('Accept', $accept); 193 + 194 + $r->_curlPrep(); 195 + $this->assertContains($accept, $r->raw_headers); 196 + $this->assertEquals($accept, $r->headers['Accept']); 197 + } 198 + 199 + function testUserAgent() 200 + { 201 + $r = Request::get('http://example.com/') 202 + ->withUserAgent('ACME/1.2.3'); 203 + 204 + $this->assertArrayHasKey('User-Agent', $r->headers); 205 + $r->_curlPrep(); 206 + $this->assertContains('User-Agent: ACME/1.2.3', $r->raw_headers); 207 + $this->assertNotContains('User-Agent: HttpFul/1.0', $r->raw_headers); 208 + 209 + $r = Request::get('http://example.com/') 210 + ->withUserAgent(''); 211 + 212 + $this->assertArrayHasKey('User-Agent', $r->headers); 213 + $r->_curlPrep(); 214 + $this->assertContains('User-Agent:', $r->raw_headers); 215 + $this->assertNotContains('User-Agent: HttpFul/1.0', $r->raw_headers); 216 + } 217 + 218 + function testAuthSetup() 219 + { 220 + $username = 'nathan'; 221 + $password = 'opensesame'; 222 + 223 + $r = Request::get('http://example.com/') 224 + ->authenticateWith($username, $password); 225 + 226 + $this->assertEquals($username, $r->username); 227 + $this->assertEquals($password, $r->password); 228 + $this->assertTrue($r->hasBasicAuth()); 229 + } 230 + 231 + function testDigestAuthSetup() 232 + { 233 + $username = 'nathan'; 234 + $password = 'opensesame'; 235 + 236 + $r = Request::get('http://example.com/') 237 + ->authenticateWithDigest($username, $password); 238 + 239 + $this->assertEquals($username, $r->username); 240 + $this->assertEquals($password, $r->password); 241 + $this->assertTrue($r->hasDigestAuth()); 242 + } 243 + 244 + function testJsonResponseParse() 245 + { 246 + $req = Request::init()->sendsAndExpects(Mime::JSON); 247 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 248 + 249 + $this->assertEquals("value", $response->body->key); 250 + $this->assertEquals("value", $response->body->object->key); 251 + $this->assertInternalType('array', $response->body->array); 252 + $this->assertEquals(1, $response->body->array[0]); 253 + } 254 + 255 + function testXMLResponseParse() 256 + { 257 + $req = Request::init()->sendsAndExpects(Mime::XML); 258 + $response = new Response(self::SAMPLE_XML_RESPONSE, self::SAMPLE_XML_HEADER, $req); 259 + $sxe = $response->body; 260 + $this->assertEquals("object", gettype($sxe)); 261 + $this->assertEquals("SimpleXMLElement", get_class($sxe)); 262 + $bools = $sxe->xpath('/stdClass/boolProp'); 263 + list( , $bool ) = each($bools); 264 + $this->assertEquals("TRUE", (string) $bool); 265 + $ints = $sxe->xpath('/stdClass/arrayProp/array/k1/myClass/intProp'); 266 + list( , $int ) = each($ints); 267 + $this->assertEquals("2", (string) $int); 268 + $strings = $sxe->xpath('/stdClass/stringProp'); 269 + list( , $string ) = each($strings); 270 + $this->assertEquals("a string", (string) $string); 271 + } 272 + 273 + function testCsvResponseParse() 274 + { 275 + $req = Request::init()->sendsAndExpects(Mime::CSV); 276 + $response = new Response(self::SAMPLE_CSV_RESPONSE, self::SAMPLE_CSV_HEADER, $req); 277 + 278 + $this->assertEquals("Key1", $response->body[0][0]); 279 + $this->assertEquals("Value1", $response->body[1][0]); 280 + $this->assertInternalType('string', $response->body[2][0]); 281 + $this->assertEquals("40.0", $response->body[2][0]); 282 + } 283 + 284 + function testParsingContentTypeCharset() 285 + { 286 + $req = Request::init()->sendsAndExpects(Mime::JSON); 287 + // $response = new Response(SAMPLE_JSON_RESPONSE, "", $req); 288 + // // Check default content type of iso-8859-1 289 + $response = new Response(self::SAMPLE_JSON_RESPONSE, "HTTP/1.1 200 OK 290 + Content-Type: text/plain; charset=utf-8\r\n", $req); 291 + $this->assertInstanceOf('Httpful\Response\Headers', $response->headers); 292 + $this->assertEquals($response->headers['Content-Type'], 'text/plain; charset=utf-8'); 293 + $this->assertEquals($response->content_type, 'text/plain'); 294 + $this->assertEquals($response->charset, 'utf-8'); 295 + } 296 + 297 + function testEmptyResponseParse() 298 + { 299 + $req = Request::init()->sendsAndExpects(Mime::JSON); 300 + $response = new Response("", self::SAMPLE_JSON_HEADER, $req); 301 + $this->assertEquals(null, $response->body); 302 + 303 + $reqXml = Request::init()->sendsAndExpects(Mime::XML); 304 + $responseXml = new Response("", self::SAMPLE_XML_HEADER, $reqXml); 305 + $this->assertEquals(null, $responseXml->body); 306 + } 307 + 308 + function testNoAutoParse() 309 + { 310 + $req = Request::init()->sendsAndExpects(Mime::JSON)->withoutAutoParsing(); 311 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 312 + $this->assertInternalType('string', $response->body); 313 + $req = Request::init()->sendsAndExpects(Mime::JSON)->withAutoParsing(); 314 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 315 + $this->assertInternalType('object', $response->body); 316 + } 317 + 318 + function testParseHeaders() 319 + { 320 + $req = Request::init()->sendsAndExpects(Mime::JSON); 321 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 322 + $this->assertEquals('application/json', $response->headers['Content-Type']); 323 + } 324 + 325 + function testRawHeaders() 326 + { 327 + $req = Request::init()->sendsAndExpects(Mime::JSON); 328 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 329 + $this->assertContains('Content-Type: application/json', $response->raw_headers); 330 + } 331 + 332 + function testHasErrors() 333 + { 334 + $req = Request::init()->sendsAndExpects(Mime::JSON); 335 + $response = new Response('', "HTTP/1.1 100 Continue\r\n", $req); 336 + $this->assertFalse($response->hasErrors()); 337 + $response = new Response('', "HTTP/1.1 200 OK\r\n", $req); 338 + $this->assertFalse($response->hasErrors()); 339 + $response = new Response('', "HTTP/1.1 300 Multiple Choices\r\n", $req); 340 + $this->assertFalse($response->hasErrors()); 341 + $response = new Response('', "HTTP/1.1 400 Bad Request\r\n", $req); 342 + $this->assertTrue($response->hasErrors()); 343 + $response = new Response('', "HTTP/1.1 500 Internal Server Error\r\n", $req); 344 + $this->assertTrue($response->hasErrors()); 345 + } 346 + 347 + function test_parseCode() 348 + { 349 + $req = Request::init()->sendsAndExpects(Mime::JSON); 350 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 351 + $code = $response->_parseCode("HTTP/1.1 406 Not Acceptable\r\n"); 352 + $this->assertEquals(406, $code); 353 + } 354 + 355 + function testToString() 356 + { 357 + $req = Request::init()->sendsAndExpects(Mime::JSON); 358 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 359 + $this->assertEquals(self::SAMPLE_JSON_RESPONSE, (string)$response); 360 + } 361 + 362 + function test_parseHeaders() 363 + { 364 + $parse_headers = Response\Headers::fromString(self::SAMPLE_JSON_HEADER); 365 + $this->assertCount(3, $parse_headers); 366 + $this->assertEquals('application/json', $parse_headers['Content-Type']); 367 + $this->assertTrue(isset($parse_headers['Connection'])); 368 + } 369 + 370 + function testMultiHeaders() 371 + { 372 + $req = Request::init(); 373 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_MULTI_HEADER, $req); 374 + $parse_headers = $response->_parseHeaders(self::SAMPLE_MULTI_HEADER); 375 + $this->assertEquals('Value1,Value2', $parse_headers['X-My-Header']); 376 + } 377 + 378 + function testDetectContentType() 379 + { 380 + $req = Request::init(); 381 + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); 382 + $this->assertEquals('application/json', $response->headers['Content-Type']); 383 + } 384 + 385 + function testMissingBodyContentType() 386 + { 387 + $body = 'A string'; 388 + $request = Request::post(HttpfulTest::TEST_URL, $body)->_curlPrep(); 389 + $this->assertEquals($body, $request->serialized_payload); 390 + } 391 + 392 + function testParentType() 393 + { 394 + // Parent type 395 + $request = Request::init()->sendsAndExpects(Mime::XML); 396 + $response = new Response('<xml><name>Nathan</name></xml>', self::SAMPLE_VENDOR_HEADER, $request); 397 + 398 + $this->assertEquals("application/xml", $response->parent_type); 399 + $this->assertEquals(self::SAMPLE_VENDOR_TYPE, $response->content_type); 400 + $this->assertTrue($response->is_mime_vendor_specific); 401 + 402 + // Make sure we still parsed as if it were plain old XML 403 + $this->assertEquals("Nathan", $response->body->name->__toString()); 404 + } 405 + 406 + function testMissingContentType() 407 + { 408 + // Parent type 409 + $request = Request::init()->sendsAndExpects(Mime::XML); 410 + $response = new Response('<xml><name>Nathan</name></xml>', 411 + "HTTP/1.1 200 OK 412 + Connection: keep-alive 413 + Transfer-Encoding: chunked\r\n", $request); 414 + 415 + $this->assertEquals("", $response->content_type); 416 + } 417 + 418 + function testCustomMimeRegistering() 419 + { 420 + // Register new mime type handler for "application/vnd.nategood.message+xml" 421 + Httpful::register(self::SAMPLE_VENDOR_TYPE, new DemoMimeHandler()); 422 + 423 + $this->assertTrue(Httpful::hasParserRegistered(self::SAMPLE_VENDOR_TYPE)); 424 + 425 + $request = Request::init(); 426 + $response = new Response('<xml><name>Nathan</name></xml>', self::SAMPLE_VENDOR_HEADER, $request); 427 + 428 + $this->assertEquals(self::SAMPLE_VENDOR_TYPE, $response->content_type); 429 + $this->assertEquals('custom parse', $response->body); 430 + } 431 + 432 + public function testShorthandMimeDefinition() 433 + { 434 + $r = Request::init()->expects('json'); 435 + $this->assertEquals(Mime::JSON, $r->expected_type); 436 + 437 + $r = Request::init()->expectsJson(); 438 + $this->assertEquals(Mime::JSON, $r->expected_type); 439 + } 440 + 441 + public function testOverrideXmlHandler() 442 + { 443 + // Lazy test... 444 + $prev = \Httpful\Httpful::get(\Httpful\Mime::XML); 445 + $this->assertEquals($prev, new \Httpful\Handlers\XmlHandler()); 446 + $conf = array('namespace' => 'http://example.com'); 447 + \Httpful\Httpful::register(\Httpful\Mime::XML, new \Httpful\Handlers\XmlHandler($conf)); 448 + $new = \Httpful\Httpful::get(\Httpful\Mime::XML); 449 + $this->assertNotEquals($prev, $new); 450 + } 451 + } 452 + 453 + class DemoMimeHandler extends \Httpful\Handlers\MimeHandlerAdapter { 454 + public function parse($body) { 455 + return 'custom parse'; 456 + } 457 + } 458 +
+9
externals/httpful/tests/phpunit.xml
··· 1 + <phpunit> 2 + <testsuite name="Httpful"> 3 + <directory>.</directory> 4 + </testsuite> 5 + <logging> 6 + <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> 7 + </logging> 8 + </phpunit> 9 +
+12
externals/restful/.gitignore
··· 1 + # composer 2 + .buildpath 3 + composer.lock 4 + composer.phar 5 + vendor 6 + 7 + # phar 8 + *.phar 9 + 10 + # eclipse-pdt 11 + .settings 12 + .project
+8
externals/restful/.travis.yml
··· 1 + language: php 2 + before_script: 3 + - curl -s http://getcomposer.org/installer | php 4 + - php composer.phar install --prefer-source 5 + script: phpunit --bootstrap vendor/autoload.php tests/ 6 + php: 7 + - 5.3 8 + - 5.4
+22
externals/restful/LICENSE
··· 1 + Copyright (c) 2012 Noone 2 + 3 + MIT License 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining 6 + a copy of this software and associated documentation files (the 7 + "Software"), to deal in the Software without restriction, including 8 + without limitation the rights to use, copy, modify, merge, publish, 9 + distribute, sublicense, and/or sell copies of the Software, and to 10 + permit persons to whom the Software is furnished to do so, subject to 11 + the following conditions: 12 + 13 + The above copyright notice and this permission notice shall be 14 + included in all copies or substantial portions of the Software. 15 + 16 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+111
externals/restful/README.md
··· 1 + # RESTful 2 + 3 + Library for writing RESTful PHP clients. 4 + 5 + [![Build Status](https://secure.travis-ci.org/bninja/restful.png)](http://travis-ci.org/bninja/restful) 6 + 7 + The design of this library was heavily influenced by [Httpful](https://github.com/nategood/httpful). 8 + 9 + ## Requirements 10 + 11 + - [PHP](http://www.php.net) >= 5.3 **with** [cURL](http://www.php.net/manual/en/curl.installation.php) 12 + - [Httpful](https://github.com/nategood/httpful) >= 0.1 13 + 14 + ## Issues 15 + 16 + Please use appropriately tagged github [issues](https://github.com/bninja/restful/issues) to request features or report bugs. 17 + 18 + ## Installation 19 + 20 + You can install using [composer](#composer), a [phar](#phar) package or from [source](#source). Note that RESTful is [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) compliant: 21 + 22 + ### Composer 23 + 24 + If you don't have Composer [install](http://getcomposer.org/doc/00-intro.md#installation) it: 25 + 26 + $ curl -s https://getcomposer.org/installer | php 27 + 28 + Add this to your `composer.json`: 29 + 30 + { 31 + "require": { 32 + "bninja/restful": "*" 33 + } 34 + } 35 + 36 + Refresh your dependencies: 37 + 38 + $ php composer.phar update 39 + 40 + 41 + Then make sure to `require` the autoloader and initialize both: 42 + 43 + <?php 44 + require(__DIR__ . '/vendor/autoload.php'); 45 + 46 + Httpful\Bootstrap::init(); 47 + RESTful\Bootstrap::init(); 48 + ... 49 + 50 + ### Phar 51 + 52 + Download an Httpful [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/nategood/httpful/downloads): 53 + 54 + $ curl -s -L -o httpful.phar https://github.com/downloads/nategood/httpful/httpful.phar 55 + 56 + Download a RESTful [phar](http://php.net/manual/en/book.phar.php) file, which are all [here](https://github.com/bninja/restful/downloads): 57 + 58 + $ curl -s -L -o restful.phar https://github.com/bninja/restful/downloads/restful-{VERSION}.phar 59 + 60 + And then `include` both: 61 + 62 + <?php 63 + include(__DIR__ . '/httpful.phar'); 64 + include(__DIR__ . '/restful.phar'); 65 + ... 66 + 67 + ### Source 68 + 69 + Download [Httpful](https://github.com/nategood/httpful) source: 70 + 71 + $ curl -s -L -o httpful.zip https://github.com/nategood/httpful/zipball/master; 72 + $ unzip httpful.zip; mv nategood-httpful* httpful; rm httpful.zip 73 + 74 + Download the RESTful source: 75 + 76 + $ curl -s -L -o restful.zip https://github.com/bninja/restful/zipball/master 77 + $ unzip restful.zip; mv bninja-restful-* restful; rm restful.zip 78 + 79 + And then `require` both bootstrap files: 80 + 81 + <?php 82 + require(__DIR__ . "/httpful/bootstrap.php") 83 + require(__DIR__ . "/restful/bootstrap.php") 84 + ... 85 + 86 + ## Usage 87 + 88 + TODO 89 + 90 + ## Testing 91 + 92 + $ phpunit --bootstrap vendor/autoload.php tests/ 93 + 94 + ## Publishing 95 + 96 + 1. Ensure that **all** [tests](#testing) pass 97 + 2. Increment minor `VERSION` in `src/RESTful/Settings` and `composer.json` (`git commit -am 'v{VERSION} release'`) 98 + 3. Tag it (`git tag -a v{VERSION} -m 'v{VERSION} release'`) 99 + 4. Push the tag (`git push --tag`) 100 + 5. [Packagist](http://packagist.org/packages/bninja/restful) will see the new tag and take it from there 101 + 6. Build (`build-phar`) and upload a [phar](http://php.net/manual/en/book.phar.php) file 102 + 103 + ## Contributing 104 + 105 + 1. Fork it 106 + 2. Create your feature branch (`git checkout -b my-new-feature`) 107 + 3. Write your code **and [tests](#testing)** 108 + 4. Ensure all tests still pass (`phpunit --bootstrap vendor/autoload.php tests/`) 109 + 5. Commit your changes (`git commit -am 'Add some feature'`) 110 + 6. Push to the branch (`git push origin my-new-feature`) 111 + 7. Create new pull request
+4
externals/restful/bootstrap.php
··· 1 + <?php 2 + 3 + require(__DIR__ . '/src/RESTful/Bootstrap.php'); 4 + \RESTful\Bootstrap::init();
+36
externals/restful/build-phar
··· 1 + #!/usr/bin/php 2 + <?php 3 + include('src/RESTful/Settings.php'); 4 + 5 + function exit_unless($condition, $msg = null) { 6 + if ($condition) 7 + return; 8 + echo "[FAIL] $msg"; 9 + exit(1); 10 + } 11 + 12 + echo "Building Phar... "; 13 + $base_dir = dirname(__FILE__); 14 + $source_dir = $base_dir . '/src/RESTful/'; 15 + $phar_name = 'restful.phar'; 16 + $phar_path = $base_dir . '/' . $phar_name; 17 + $phar = new Phar($phar_path, 0, $phar_name); 18 + $stub = <<<HEREDOC 19 + <?php 20 + // Phar Stub File 21 + Phar::mapPhar('restful.phar'); 22 + include('phar://restful.phar/RESTful/Bootstrap.php'); 23 + \RESTful\Bootstrap::pharInit(); 24 + 25 + __HALT_COMPILER(); 26 + HEREDOC; 27 + $phar->setStub($stub); 28 + exit_unless($phar, "Unable to create a phar. Make sure you have phar.readonly=0 set in your ini file."); 29 + $phar->buildFromDirectory(dirname($source_dir)); 30 + echo "[ OK ]\n"; 31 + 32 + echo "Renaming Phar... "; 33 + $phar_versioned_name = 'restful-' . \RESTful\Settings::VERSION . '.phar'; 34 + $phar_versioned_path = $base_dir . '/' . $phar_versioned_name; 35 + rename($phar_path, $phar_versioned_path); 36 + echo "[ OK ]\n";
+18
externals/restful/composer.json
··· 1 + { 2 + "name": "bninja/restful", 3 + "description": "Library for writing RESTful PHP clients.", 4 + "homepage": "http://github.com/bninja/restful", 5 + "license": "MIT", 6 + "keywords": ["http", "api", "client", "rest"], 7 + "version": "0.1.7", 8 + "authors": [ 9 + ], 10 + "require": { 11 + "nategood/httpful": "*" 12 + }, 13 + "autoload": { 14 + "psr-0": { 15 + "RESTful": "src/" 16 + } 17 + } 18 + }
+44
externals/restful/src/RESTful/Bootstrap.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + /** 6 + * Bootstrapper for RESTful does autoloading. 7 + */ 8 + class Bootstrap 9 + { 10 + const DIR_SEPARATOR = DIRECTORY_SEPARATOR; 11 + const NAMESPACE_SEPARATOR = '\\'; 12 + 13 + public static $initialized = false; 14 + 15 + 16 + public static function init() 17 + { 18 + spl_autoload_register(array('\RESTful\Bootstrap', 'autoload')); 19 + } 20 + 21 + public static function autoload($classname) 22 + { 23 + self::_autoload(dirname(dirname(__FILE__)), $classname); 24 + } 25 + 26 + public static function pharInit() 27 + { 28 + spl_autoload_register(array('\RESTful\Bootstrap', 'pharAutoload')); 29 + } 30 + 31 + public static function pharAutoload($classname) 32 + { 33 + self::_autoload('phar://restful.phar', $classname); 34 + } 35 + 36 + private static function _autoload($base, $classname) 37 + { 38 + $parts = explode(self::NAMESPACE_SEPARATOR, $classname); 39 + $path = $base . self::DIR_SEPARATOR . implode(self::DIR_SEPARATOR, $parts) . '.php'; 40 + if (file_exists($path)) { 41 + require_once($path); 42 + } 43 + } 44 + }
+78
externals/restful/src/RESTful/Client.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + use RESTful\Exceptions\HTTPError; 6 + use RESTful\Settings; 7 + 8 + class Client 9 + { 10 + public function __construct($settings_class, $request_class = null, $convert_error = null) 11 + { 12 + $this->request_class = $request_class == null ? '\Httpful\Request' : $request_class; 13 + $this->settings_class = $settings_class; 14 + $this->convert_error = $convert_error; 15 + } 16 + 17 + public function get($uri) 18 + { 19 + $settings_class = $this->settings_class; 20 + $url = $settings_class::$url_root . $uri; 21 + $request_class = $this->request_class; 22 + $request = $request_class::get($url); 23 + 24 + return $this->_op($request); 25 + } 26 + 27 + public function post($uri, $payload) 28 + { 29 + $settings_class = $this->settings_class; 30 + $url = $settings_class::$url_root . $uri; 31 + $request_class = $this->request_class; 32 + $request = $request_class::post($url, $payload, 'json'); 33 + 34 + return $this->_op($request); 35 + } 36 + 37 + public function put($uri, $payload) 38 + { 39 + $settings_class = $this->settings_class; 40 + $url = $settings_class::$url_root . $uri; 41 + $request_class = $this->request_class; 42 + $request = $request_class::put($url, $payload, 'json'); 43 + 44 + return $this->_op($request); 45 + } 46 + 47 + public function delete($uri) 48 + { 49 + $settings_class = $this->settings_class; 50 + $url = $settings_class::$url_root . $uri; 51 + $request_class = $this->request_class; 52 + $request = $request_class::delete($url); 53 + 54 + return $this->_op($request); 55 + } 56 + 57 + private function _op($request) 58 + { 59 + $settings_class = $this->settings_class; 60 + $user_agent = $settings_class::$agent . '/' . $settings_class::$version; 61 + $request->headers['User-Agent'] = $user_agent; 62 + if ($settings_class::$api_key != null) { 63 + $request = $request->authenticateWith($settings_class::$api_key, ''); 64 + } 65 + $request->expects('json'); 66 + $response = $request->sendIt(); 67 + if ($response->hasErrors() || $response->code == 300) { 68 + if ($this->convert_error != null) { 69 + $error = call_user_func($this->convert_error, $response); 70 + } else { 71 + $error = new HTTPError($response); 72 + } 73 + throw $error; 74 + } 75 + 76 + return $response; 77 + } 78 + }
+49
externals/restful/src/RESTful/Collection.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Collection extends Itemization 6 + { 7 + public function __construct($resource, $uri, $data = null) 8 + { 9 + parent::__construct($resource, $uri, $data); 10 + $this->_parseUri(); 11 + } 12 + 13 + private function _parseUri() 14 + { 15 + $parsed = parse_url($this->uri); 16 + $this->_uri = $parsed['path']; 17 + if (array_key_exists('query', $parsed)) { 18 + foreach (explode('&', $parsed['query']) as $param) { 19 + $param = explode('=', $param); 20 + $key = urldecode($param[0]); 21 + $val = (count($param) == 1) ? null : urldecode($param[1]); 22 + 23 + // size 24 + if ($key == 'limit') { 25 + $this->_size = $val; 26 + } 27 + } 28 + } 29 + } 30 + 31 + public function create($payload) 32 + { 33 + $class = $this->resource; 34 + $client = $class::getClient(); 35 + $response = $client->post($this->uri, $payload); 36 + 37 + return new $this->resource($response->body); 38 + } 39 + 40 + public function query() 41 + { 42 + return new Query($this->resource, $this->uri); 43 + } 44 + 45 + public function paginate() 46 + { 47 + return new Pagination($this->resource, $this->uri); 48 + } 49 + }
+10
externals/restful/src/RESTful/Exceptions/Base.php
··· 1 + <?php 2 + 3 + namespace RESTful\Exceptions; 4 + 5 + /** 6 + * Base class for all RESTful\Exceptions. 7 + */ 8 + class Base extends \Exception 9 + { 10 + }
+28
externals/restful/src/RESTful/Exceptions/HTTPError.php
··· 1 + <?php 2 + 3 + namespace RESTful\Exceptions; 4 + 5 + /** 6 + * Indicates an HTTP level error has occurred. The underlying HTTP response is 7 + * stored as response member. The response payload fields if any are stored as 8 + * members of the same name. 9 + * 10 + * @see \Httpful\Response 11 + */ 12 + class HTTPError extends Base 13 + { 14 + public $response; 15 + 16 + public function __construct($response) 17 + { 18 + $this->response = $response; 19 + $this->_objectify($this->response->body); 20 + } 21 + 22 + protected function _objectify($fields) 23 + { 24 + foreach ($fields as $key => $val) { 25 + $this->$key = $val; 26 + } 27 + } 28 + }
+11
externals/restful/src/RESTful/Exceptions/MultipleResultsFound.php
··· 1 + <?php 2 + 3 + namespace RESTful\Exceptions; 4 + 5 + /** 6 + * Indicates that a query unexpectedly returned multiple results when at most 7 + * one was expected. 8 + */ 9 + class MultipleResultsFound extends Base 10 + { 11 + }
+10
externals/restful/src/RESTful/Exceptions/NoResultFound.php
··· 1 + <?php 2 + 3 + namespace RESTful\Exceptions; 4 + 5 + /** 6 + * Indicates that a query unexpectedly returned no results. 7 + */ 8 + class NoResultFound extends Base 9 + { 10 + }
+85
externals/restful/src/RESTful/Field.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Field 6 + { 7 + public $name; 8 + 9 + public function __construct($name) 10 + { 11 + $this->name = $name; 12 + } 13 + 14 + public function __get($name) 15 + { 16 + return new Field($this->name . '.' . $name); 17 + } 18 + 19 + public function in($vals) 20 + { 21 + return new FilterExpression($this->name, 'in', $vals, '!in'); 22 + } 23 + 24 + public function startswith($prefix) 25 + { 26 + if (!is_string($prefix)) { 27 + throw new \InvalidArgumentException('"startswith" prefix must be a string'); 28 + } 29 + 30 + return new FilterExpression($this->name, 'contains', $prefix); 31 + } 32 + 33 + public function endswith($suffix) 34 + { 35 + if (!is_string($suffix)) { 36 + throw new \InvalidArgumentException('"endswith" suffix must be a string'); 37 + } 38 + 39 + return new FilterExpression($this->name, 'contains', $suffix); 40 + } 41 + 42 + public function contains($fragment) 43 + { 44 + if (!is_string($fragment)) { 45 + throw new \InvalidArgumentException('"contains" fragment must be a string'); 46 + } 47 + 48 + return new FilterExpression($this->name, 'contains', $fragment, '!contains'); 49 + } 50 + 51 + public function eq($val) 52 + { 53 + return new FilterExpression($this->name, '=', $val, '!eq'); 54 + } 55 + 56 + public function lt($val) 57 + { 58 + return new FilterExpression($this->name, '<', $val, '>='); 59 + } 60 + 61 + public function lte($val) 62 + { 63 + return new FilterExpression($this->name, '<=', $val, '>'); 64 + } 65 + 66 + public function gt($val) 67 + { 68 + return new FilterExpression($this->name, '>', $val, '<='); 69 + } 70 + 71 + public function gte($val) 72 + { 73 + return new FilterExpression($this->name, '>=', $val, '<'); 74 + } 75 + 76 + public function asc() 77 + { 78 + return new SortExpression($this->name, true); 79 + } 80 + 81 + public function desc() 82 + { 83 + return new SortExpression($this->name, false); 84 + } 85 + }
+11
externals/restful/src/RESTful/Fields.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Fields 6 + { 7 + public function __get($name) 8 + { 9 + return new Field($name); 10 + } 11 + }
+31
externals/restful/src/RESTful/FilterExpression.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class FilterExpression 6 + { 7 + public $field, 8 + $op, 9 + $val, 10 + $not_op; 11 + 12 + public function __construct($field, $op, $val, $not_op = null) 13 + { 14 + $this->field = $field; 15 + $this->op = $op; 16 + $this->val = $val; 17 + $this->not_op = $not_op; 18 + } 19 + 20 + public function not() 21 + { 22 + if (null === $this->not_op) { 23 + throw new \LogicException(sprintf('Filter cannot be inverted')); 24 + } 25 + $temp = $this->op; 26 + $this->op = $this->not_op; 27 + $this->not_op = $temp; 28 + 29 + return $this; 30 + } 31 + }
+99
externals/restful/src/RESTful/Itemization.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Itemization implements \IteratorAggregate, \ArrayAccess 6 + { 7 + public $resource, 8 + $uri; 9 + 10 + protected $_page, 11 + $_offset = 0, 12 + $_size = 25; 13 + 14 + public function __construct($resource, $uri, $data = null) 15 + { 16 + $this->resource = $resource; 17 + $this->uri = $uri; 18 + if ($data != null) { 19 + $this->_page = new Page($resource, $uri, $data); 20 + } else { 21 + $this->_page = null; 22 + } 23 + } 24 + 25 + protected function _getPage($offset = null) 26 + { 27 + if ($this->_page == null) { 28 + $this->_offset = ($offset == null) ? 0 : $offset * $this->_size; 29 + $uri = $this->_buildUri(); 30 + $this->_page = new Page($this->resource, $uri); 31 + } elseif ($offset != null) { 32 + $offset = $offset * $this->_size; 33 + if ($offset != $this->_offset) { 34 + $this->_offset = $offset; 35 + $uri = $this->_buildUri(); 36 + $this->_page = new Page($this->resource, $uri); 37 + } 38 + } 39 + 40 + return $this->_page; 41 + } 42 + 43 + protected function _getItem($offset) 44 + { 45 + $page_offset = floor($offset / $this->_size); 46 + $page = $this->_getPage($page_offset); 47 + 48 + return $page->items[$offset - $page->offset]; 49 + } 50 + 51 + public function total() 52 + { 53 + return $this->_getPage()->total; 54 + } 55 + 56 + protected function _buildUri($offset = null) 57 + { 58 + # TODO: hacky but works for now 59 + $offset = ($offset == null) ? $this->_offset : $offset; 60 + if (strpos($this->uri, '?') === false) { 61 + $uri = $this->uri . '?'; 62 + } else { 63 + $uri = $this->uri . '&'; 64 + } 65 + $uri = $uri . 'offset=' . strval($offset); 66 + 67 + return $uri; 68 + } 69 + 70 + // IteratorAggregate 71 + public function getIterator() 72 + { 73 + $uri = $this->_buildUri($offset = 0); 74 + $uri = $this->_buildUri($offset = 0); 75 + 76 + return new ItemizationIterator($this->resource, $uri); 77 + } 78 + 79 + // ArrayAccess 80 + public function offsetSet($offset, $value) 81 + { 82 + throw new \BadMethodCallException(get_class($this) . ' array access is read-only'); 83 + } 84 + 85 + public function offsetExists($offset) 86 + { 87 + return (0 <= $offset && $offset < $this->total()); 88 + } 89 + 90 + public function offsetUnset($offset) 91 + { 92 + throw new \BadMethodCallException(get_class($this) . ' array access is read-only'); 93 + } 94 + 95 + public function offsetGet($offset) 96 + { 97 + return $this->_getItem($offset); 98 + } 99 + }
+45
externals/restful/src/RESTful/ItemizationIterator.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class ItemizationIterator implements \Iterator 6 + { 7 + protected $_page, 8 + $_offset = 0; 9 + 10 + public function __construct($resource, $uri, $data = null) 11 + { 12 + $this->_page = new Page($resource, $uri, $data); 13 + } 14 + 15 + // Iterator 16 + public function current() 17 + { 18 + return $this->_page->items[$this->_offset]; 19 + } 20 + 21 + public function key() 22 + { 23 + return $this->_page->offset + $this->_offset; 24 + } 25 + 26 + public function next() 27 + { 28 + $this->_offset += 1; 29 + if ($this->_offset >= count($this->_page->items)) { 30 + $this->_offset = 0; 31 + $this->_page = $this->_page->next(); 32 + } 33 + } 34 + 35 + public function rewind() 36 + { 37 + $this->_page = $this->_page->first(); 38 + $this->_offset = 0; 39 + } 40 + 41 + public function valid() 42 + { 43 + return ($this->_page != null && $this->_offset < count($this->_page->items)); 44 + } 45 + }
+72
externals/restful/src/RESTful/Page.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Page 6 + { 7 + public $resource, 8 + $total, 9 + $items, 10 + $offset, 11 + $limit; 12 + 13 + private $_first_uri, 14 + $_previous_uri, 15 + $_next_uri, 16 + $_last_uri; 17 + 18 + public function __construct($resource, $uri, $data = null) 19 + { 20 + $this->resource = $resource; 21 + if ($data == null) { 22 + $client = $resource::getClient(); 23 + $data = $client->get($uri)->body; 24 + } 25 + $this->total = $data->total; 26 + $this->items = array_map( 27 + function ($x) use ($resource) { 28 + return new $resource($x); 29 + }, 30 + $data->items); 31 + $this->offset = $data->offset; 32 + $this->limit = $data->limit; 33 + $this->_first_uri = property_exists($data, 'first_uri') ? $data->first_uri : null; 34 + $this->_previous_uri = property_exists($data, 'previous_uri') ? $data->previous_uri : null; 35 + $this->_next_uri = property_exists($data, 'next_uri') ? $data->next_uri : null; 36 + $this->_last_uri = property_exists($data, 'last_uri') ? $data->last_uri : null; 37 + } 38 + 39 + public function first() 40 + { 41 + return new Page($this->resource, $this->_first_uri); 42 + } 43 + 44 + public function next() 45 + { 46 + if (!$this->hasNext()) { 47 + return null; 48 + } 49 + 50 + return new Page($this->resource, $this->_next_uri); 51 + } 52 + 53 + public function hasNext() 54 + { 55 + return $this->_next_uri != null; 56 + } 57 + 58 + public function previous() 59 + { 60 + return new Page($this->resource, $this->_previous_uri); 61 + } 62 + 63 + public function hasPrevious() 64 + { 65 + return $this->_previous_uri != null; 66 + } 67 + 68 + public function last() 69 + { 70 + return new Page($this->resource, $this->_last_uri); 71 + } 72 + }
+90
externals/restful/src/RESTful/Pagination.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Pagination implements \IteratorAggregate, \ArrayAccess 6 + { 7 + public $resource, 8 + $uri; 9 + 10 + protected $_page, 11 + $_offset = 0, 12 + $_size = 25; 13 + 14 + public function __construct($resource, $uri, $data = null) 15 + { 16 + $this->resource = $resource; 17 + $this->uri = $uri; 18 + if ($data != null) { 19 + $this->_page = new Page($resource, $uri, $data); 20 + } else { 21 + $this->_page = null; 22 + } 23 + } 24 + 25 + protected function _getPage($offset = null) 26 + { 27 + if ($this->_page == null) { 28 + $this->_offset = ($offset == null) ? 0 : $offset * $this->_size; 29 + $uri = $this->_buildUri(); 30 + $this->_page = new Page($this->resource, $uri); 31 + } elseif ($offset != null) { 32 + $offset = $offset * $this->_size; 33 + if ($offset != $this->_offset) { 34 + $this->_offset = $offset; 35 + $uri = $this->_buildUri(); 36 + $this->_page = new Page($this->resource, $uri); 37 + } 38 + } 39 + 40 + return $this->_page; 41 + } 42 + 43 + public function total() 44 + { 45 + return floor($this->_getPage()->total / $this->_size); 46 + } 47 + 48 + protected function _buildUri($offset = null) 49 + { 50 + # TODO: hacky but works for now 51 + $offset = ($offset == null) ? $this->_offset : $offset; 52 + if (strpos($this->uri, '?') === false) { 53 + $uri = $this->uri . '?'; 54 + } else { 55 + $uri = $this->uri . '&'; 56 + } 57 + $uri = $uri . 'offset=' . strval($offset); 58 + 59 + return $uri; 60 + } 61 + 62 + // IteratorAggregate 63 + public function getIterator() 64 + { 65 + $uri = $this->_buildUri($offset = 0); 66 + 67 + return new PaginationIterator($this->resource, $uri); 68 + } 69 + 70 + // ArrayAccess 71 + public function offsetSet($offset, $value) 72 + { 73 + throw new \BadMethodCallException(get_class($this) . ' array access is read-only'); 74 + } 75 + 76 + public function offsetExists($offset) 77 + { 78 + return (0 <= $offset && $offset < $this->total()); 79 + } 80 + 81 + public function offsetUnset($offset) 82 + { 83 + throw new \BadMethodCallException(get_class($this) . ' array access is read-only'); 84 + } 85 + 86 + public function offsetGet($offset) 87 + { 88 + return $this->_getPage($offset); 89 + } 90 + }
+37
externals/restful/src/RESTful/PaginationIterator.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class PaginationIterator implements \Iterator 6 + { 7 + public function __construct($resource, $uri, $data = null) 8 + { 9 + $this->_page = new Page($resource, $uri, $data); 10 + } 11 + 12 + // Iterator 13 + public function current() 14 + { 15 + return $this->_page; 16 + } 17 + 18 + public function key() 19 + { 20 + return $this->_page->index; 21 + } 22 + 23 + public function next() 24 + { 25 + $this->_page = $this->_page->next(); 26 + } 27 + 28 + public function rewind() 29 + { 30 + $this->_page = $this->_page->first(); 31 + } 32 + 33 + public function valid() 34 + { 35 + return $this->_page != null; 36 + } 37 + }
+161
externals/restful/src/RESTful/Query.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + use RESTful\Exceptions\NoResultFound; 6 + use RESTful\Exceptions\MultipleResultsFound; 7 + 8 + class Query extends Itemization 9 + { 10 + public $filters = array(), 11 + $sorts = array(), 12 + $size; 13 + 14 + public function __construct($resource, $uri) 15 + { 16 + parent::__construct($resource, $uri); 17 + $this->size = $this->_size; 18 + $this->_parseUri($uri); 19 + } 20 + 21 + private function _parseUri($uri) 22 + { 23 + $parsed = parse_url($uri); 24 + $this->uri = $parsed['path']; 25 + if (array_key_exists('query', $parsed)) { 26 + foreach (explode('&', $parsed['query']) as $param) { 27 + $param = explode('=', $param); 28 + $key = urldecode($param[0]); 29 + $val = (count($param) == 1) ? null : urldecode($param[1]); 30 + 31 + // limit 32 + if ($key == 'limit') { 33 + $this->size = $this->_size = $val; 34 + } // sorts 35 + else if ($key == 'sort') { 36 + array_push($this->sorts, $val); 37 + } // everything else 38 + else { 39 + if (!array_key_exists($key, $this->filters)) { 40 + $this->filters[$key] = array(); 41 + } 42 + if (!is_array($val)) { 43 + $val = array($val); 44 + } 45 + $this->filters[$key] = array_merge($this->filters[$key], $val); 46 + } 47 + } 48 + } 49 + } 50 + 51 + protected function _buildUri($offset = null) 52 + { 53 + // params 54 + $params = array_merge( 55 + $this->filters, 56 + array( 57 + 'sort' => $this->sorts, 58 + 'limit' => $this->_size, 59 + 'offset' => ($offset == null) ? $this->_offset : $offset 60 + ) 61 + ); 62 + $getSingle = function ($v) { 63 + if (is_array($v) && count($v) == 1) 64 + return $v[0]; 65 + return $v; 66 + }; 67 + $params = array_map($getSingle, $params); 68 + 69 + // url encode params 70 + // NOTE: http://stackoverflow.com/a/8171667/1339571 71 + $qs = http_build_query($params); 72 + $qs = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $qs); 73 + 74 + return $this->uri . '?' . $qs; 75 + } 76 + 77 + private function _reset() 78 + { 79 + $this->_page = null; 80 + } 81 + 82 + public function filter($expression) 83 + { 84 + if ($expression->op == '=') { 85 + $field = $expression->field; 86 + } else { 87 + $field = $expression->field . '[' . $expression->op . ']'; 88 + } 89 + if (is_array($expression->val)) { 90 + $val = implode(',', $expression->val); 91 + } else { 92 + $val = $expression->val; 93 + } 94 + if (!array_key_exists($field, $this->filters)) { 95 + $this->filters[$field] = array(); 96 + } 97 + array_push($this->filters[$field], $val); 98 + $this->_reset(); 99 + 100 + return $this; 101 + } 102 + 103 + public function sort($expression) 104 + { 105 + $dir = $expression->ascending ? 'asc' : 'desc'; 106 + array_push($this->sorts, $expression->field . ',' . $dir); 107 + $this->_reset(); 108 + 109 + return $this; 110 + } 111 + 112 + public function limit($limit) 113 + { 114 + $this->size = $this->_size = $limit; 115 + $this->_reset(); 116 + 117 + return $this; 118 + } 119 + 120 + public function all() 121 + { 122 + $items = array(); 123 + foreach ($this as $item) { 124 + array_push($items, $item); 125 + } 126 + 127 + return $items; 128 + } 129 + 130 + public function first() 131 + { 132 + $prev_size = $this->_size; 133 + $this->_size = 1; 134 + $page = new Page($this->resource, $this->_buildUri()); 135 + $this->_size = $prev_size; 136 + $item = count($page->items) != 0 ? $page->items[0] : null; 137 + 138 + return $item; 139 + } 140 + 141 + public function one() 142 + { 143 + $prev_size = $this->_size; 144 + $this->_size = 2; 145 + $page = new Page($this->resource, $this->_buildUri()); 146 + $this->_size = $prev_size; 147 + if (count($page->items) == 1) { 148 + return $page->items[0]; 149 + } 150 + if (count($page->items) == 0) { 151 + throw new NoResultFound(); 152 + } 153 + 154 + throw new MultipleResultsFound(); 155 + } 156 + 157 + public function paginate() 158 + { 159 + return new Pagination($this->resource, $this->_buildUri()); 160 + } 161 + }
+29
externals/restful/src/RESTful/Registry.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class Registry 6 + { 7 + protected $_resources = array(); 8 + 9 + public function add($resource) 10 + { 11 + array_push($this->_resources, $resource); 12 + } 13 + 14 + public function match($uri) 15 + { 16 + foreach ($this->_resources as $resource) { 17 + $spec = $resource::getURISpec(); 18 + $result = $spec->match($uri); 19 + if ($result == null) { 20 + continue; 21 + } 22 + $result['class'] = $resource; 23 + 24 + return $result; 25 + } 26 + 27 + return null; 28 + } 29 + }
+205
externals/restful/src/RESTful/Resource.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + abstract class Resource 6 + { 7 + protected $_collection_uris, 8 + $_member_uris; 9 + 10 + public static function getClient() 11 + { 12 + $class = get_called_class(); 13 + 14 + return $class::$_client; 15 + } 16 + 17 + public static function getRegistry() 18 + { 19 + $class = get_called_class(); 20 + 21 + return $class::$_registry; 22 + } 23 + 24 + public static function getURISpec() 25 + { 26 + $class = get_called_class(); 27 + 28 + return $class::$_uri_spec; 29 + } 30 + 31 + public function __construct($fields = null) 32 + { 33 + if ($fields == null) { 34 + $fields = array(); 35 + } 36 + $this->_objectify($fields); 37 + } 38 + 39 + public function __get($name) 40 + { 41 + // collection uri 42 + if (array_key_exists($name, $this->_collection_uris)) { 43 + $result = $this->_collection_uris[$name]; 44 + $this->$name = new Collection($result['class'], $result['uri']); 45 + 46 + return $this->$name; 47 + } // member uri 48 + else if (array_key_exists($name, $this->_member_uris)) { 49 + $result = $this->$_collection_uris[$name]; 50 + $response = self::getClient() . get($result['uri']); 51 + $class = $result['class']; 52 + $this->$name = new $class($response->body); 53 + 54 + return $this->$name; 55 + } 56 + 57 + // unknown 58 + $trace = debug_backtrace(); 59 + trigger_error( 60 + sprintf('Undefined property via __get(): %s in %s on line %s', $name, $trace[0]['file'], $trace[0]['line']), 61 + E_USER_NOTICE 62 + ); 63 + 64 + return null; 65 + } 66 + 67 + public function __isset($name) 68 + { 69 + if (array_key_exists($name, $this->_collection_uris) || array_key_exists($name, $this->_member_uris)) { 70 + return true; 71 + } 72 + 73 + return false; 74 + } 75 + 76 + protected function _objectify($fields) 77 + { 78 + // initialize uris 79 + $this->_collection_uris = array(); 80 + $this->_member_uris = array(); 81 + 82 + foreach ($fields as $key => $val) { 83 + // nested uri 84 + if ((strlen($key) - 3) == strrpos($key, 'uri', 0) && $key != 'uri') { 85 + $result = self::getRegistry()->match($val); 86 + if ($result != null) { 87 + $name = substr($key, 0, -4); 88 + $class = $result['class']; 89 + if ($result['collection']) { 90 + $this->_collection_uris[$name] = array( 91 + 'class' => $class, 92 + 'uri' => $val, 93 + ); 94 + } else { 95 + $this->_member_uris[$name] = array( 96 + 'class' => $class, 97 + 'uri' => $val, 98 + ); 99 + } 100 + 101 + continue; 102 + } 103 + } elseif (is_object($val) && property_exists($val, 'uri')) { 104 + // nested 105 + $result = self::getRegistry()->match($val->uri); 106 + if ($result != null) { 107 + $class = $result['class']; 108 + if ($result['collection']) { 109 + $this->$key = new Collection($class, $val['uri'], $val); 110 + } else { 111 + $this->$key = new $class($val); 112 + } 113 + 114 + continue; 115 + } 116 + } elseif (is_array($val) && array_key_exists('uri', $val)) { 117 + $result = self::getRegistry()->match($val['uri']); 118 + if ($result != null) { 119 + $class = $result['class']; 120 + if ($result['collection']) { 121 + $this->$key = new Collection($class, $val['uri'], $val); 122 + } else { 123 + $this->$key = new $class($val); 124 + } 125 + 126 + continue; 127 + } 128 + } 129 + 130 + // default 131 + $this->$key = $val; 132 + } 133 + } 134 + 135 + public static function query() 136 + { 137 + $uri_spec = self::getURISpec(); 138 + if ($uri_spec == null || $uri_spec->collection_uri == null) { 139 + $msg = sprintf('Cannot directly query %s resources', get_called_class()); 140 + throw new \LogicException($msg); 141 + } 142 + 143 + return new Query(get_called_class(), $uri_spec->collection_uri); 144 + } 145 + 146 + public static function get($uri) 147 + { 148 + # id 149 + if (strncmp($uri, '/', 1)) { 150 + $uri_spec = self::getURISpec(); 151 + if ($uri_spec == null || $uri_spec->collection_uri == null) { 152 + $msg = sprintf('Cannot get %s resources by id %s', $class, $uri); 153 + throw new \LogicException($msg); 154 + } 155 + $uri = $uri_spec->collection_uri . '/' . $uri; 156 + } 157 + 158 + $response = self::getClient()->get($uri); 159 + $class = get_called_class(); 160 + 161 + return new $class($response->body); 162 + } 163 + 164 + public function save() 165 + { 166 + // payload 167 + $payload = array(); 168 + foreach ($this as $key => $val) { 169 + if ($key[0] == '_' || is_object($val)) { 170 + continue; 171 + } 172 + $payload[$key] = $val; 173 + } 174 + 175 + // update 176 + if (array_key_exists('uri', $payload)) { 177 + $uri = $payload['uri']; 178 + unset($payload['uri']); 179 + $response = self::getClient()->put($uri, $payload); 180 + } else { 181 + // create 182 + $class = get_class($this); 183 + if ($class::$_uri_spec == null || $class::$_uri_spec->collection_uri == null) { 184 + $msg = sprintf('Cannot directly create %s resources', $class); 185 + throw new \LogicException($msg); 186 + } 187 + $response = self::getClient()->post($class::$_uri_spec->collection_uri, $payload); 188 + } 189 + 190 + // re-objectify 191 + foreach ($this as $key => $val) { 192 + unset($this->$key); 193 + } 194 + $this->_objectify($response->body); 195 + 196 + return $this; 197 + } 198 + 199 + public function delete() 200 + { 201 + self::getClient()->delete($this->uri); 202 + 203 + return $this; 204 + } 205 + }
+12
externals/restful/src/RESTful/Settings.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + /** 6 + * Settings. 7 + * 8 + */ 9 + class Settings 10 + { 11 + const VERSION = '0.1.7'; 12 + }
+15
externals/restful/src/RESTful/SortExpression.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class SortExpression 6 + { 7 + public $name, 8 + $ascending; 9 + 10 + public function __construct($field, $ascending = true) 11 + { 12 + $this->field = $field; 13 + $this->ascending = $ascending; 14 + } 15 + }
+58
externals/restful/src/RESTful/URISpec.php
··· 1 + <?php 2 + 3 + namespace RESTful; 4 + 5 + class URISpec 6 + { 7 + public $collection_uri = null, 8 + $name, 9 + $idNames; 10 + 11 + public function __construct($name, $idNames, $root = null) 12 + { 13 + $this->name = $name; 14 + if (!is_array($idNames)) { 15 + $idNames = array($idNames); 16 + } 17 + $this->idNames = $idNames; 18 + if ($root != null) { 19 + if ($root == '' || substr($root, -1) == '/') { 20 + $this->collection_uri = $root . $name; 21 + } else { 22 + $this->collection_uri = $root . '/' . $name; 23 + } 24 + } 25 + } 26 + 27 + public function match($uri) 28 + { 29 + $parts = explode('/', rtrim($uri, "/")); 30 + 31 + // collection 32 + if ($parts[count($parts) - 1] == $this->name) { 33 + 34 + return array( 35 + 'collection' => true, 36 + ); 37 + } 38 + 39 + // non-member 40 + if (count($parts) < count($this->idNames) + 1 || 41 + $parts[count($parts) - 1 - count($this->idNames)] != $this->name 42 + ) { 43 + return null; 44 + } 45 + 46 + // member 47 + $ids = array_combine( 48 + $this->idNames, 49 + array_slice($parts, -count($this->idNames)) 50 + ); 51 + $result = array( 52 + 'collection' => false, 53 + 'ids' => $ids, 54 + ); 55 + 56 + return $result; 57 + } 58 + }
+241
externals/restful/tests/RESTful/CoreTest.php
··· 1 + <?php 2 + 3 + namespace RESTful\Test; 4 + 5 + \RESTful\Bootstrap::init(); 6 + \Httpful\Bootstrap::init(); 7 + 8 + use RESTful\URISpec; 9 + use RESTful\Client; 10 + use RESTful\Registry; 11 + use RESTful\Fields; 12 + use RESTful\Query; 13 + use RESTful\Page; 14 + 15 + class Settings 16 + { 17 + public static $url_root = 'http://api.example.com'; 18 + 19 + public static $agent = 'example-php'; 20 + 21 + public static $version = '0.1.0'; 22 + 23 + public static $api_key = null; 24 + } 25 + 26 + class Resource extends \RESTful\Resource 27 + { 28 + public static $fields, $f; 29 + 30 + protected static $_client, $_registry, $_uri_spec; 31 + 32 + public static function init() 33 + { 34 + self::$_client = new Client('Settings'); 35 + self::$_registry = new Registry(); 36 + self::$f = self::$fields = new Fields(); 37 + } 38 + 39 + public static function getClient() 40 + { 41 + $class = get_called_class(); 42 + return $class::$_client; 43 + } 44 + 45 + public static function getRegistry() 46 + { 47 + $class = get_called_class(); 48 + return $class::$_registry; 49 + } 50 + 51 + public static function getURISpec() 52 + { 53 + $class = get_called_class(); 54 + return $class::$_uri_spec; 55 + } 56 + } 57 + 58 + Resource::init(); 59 + 60 + class A extends Resource 61 + { 62 + protected static $_uri_spec = null; 63 + 64 + public static function init() 65 + { 66 + self::$_uri_spec = new URISpec('as', 'id', '/'); 67 + self::$_registry->add(get_called_class()); 68 + } 69 + } 70 + 71 + A::init(); 72 + 73 + class B extends Resource 74 + { 75 + protected static $_uri_spec = null; 76 + 77 + public static function init() 78 + { 79 + self::$_uri_spec = new URISpec('bs', 'id', '/'); 80 + self::$_registry->add(get_called_class()); 81 + } 82 + } 83 + 84 + B::init(); 85 + 86 + class URISpecTest extends \PHPUnit_Framework_TestCase 87 + { 88 + public function testNoRoot() 89 + { 90 + $uri_spec = new URISpec('grapes', 'seed'); 91 + $this->assertEquals($uri_spec->collection_uri, null); 92 + 93 + $result = $uri_spec->match('/some/raisins'); 94 + $this->assertEquals($result, null); 95 + 96 + $result = $uri_spec->match('/some/grapes'); 97 + $this->assertEquals($result, array('collection' => true)); 98 + 99 + $result = $uri_spec->match('/some/grapes/1234'); 100 + $expected = array( 101 + 'collection' => false, 102 + 'ids' => array('seed' => '1234') 103 + ); 104 + $this->assertEquals($expected, $result); 105 + } 106 + 107 + public function testSingleId() 108 + { 109 + $uri_spec = new URISpec('tomatoes', 'stem', '/v1'); 110 + $this->assertNotEquals($uri_spec->collection_uri, null); 111 + 112 + $result = $uri_spec->match('/some/tomatoes/that/are/green'); 113 + $this->assertEquals($result, null); 114 + 115 + $result = $uri_spec->match('/some/tomatoes'); 116 + $this->assertEquals($result, array('collection' => true)); 117 + 118 + $result = $uri_spec->match('/some/tomatoes/4321'); 119 + $expected = array( 120 + 'collection' => false, 121 + 'ids' => array('stem' => '4321') 122 + ); 123 + $this->assertEquals($expected, $result); 124 + } 125 + 126 + public function testMultipleIds() 127 + { 128 + $uri_spec = new URISpec('tomatoes', array('stem', 'root'), '/v1'); 129 + $this->assertNotEquals($uri_spec->collection_uri, null); 130 + 131 + $result = $uri_spec->match('/some/tomatoes/that/are/green'); 132 + $this->assertEquals($result, null); 133 + 134 + $result = $uri_spec->match('/some/tomatoes'); 135 + $this->assertEquals($result, array('collection' => true)); 136 + 137 + $result = $uri_spec->match('/some/tomatoes/4321/1234'); 138 + $expected = array( 139 + 'collection' => false, 140 + 'ids' => array('stem' => '4321', 'root' => '1234') 141 + ); 142 + $this->assertEquals($expected, $result); 143 + } 144 + } 145 + 146 + class QueryTest extends \PHPUnit_Framework_TestCase 147 + { 148 + public function testParse() 149 + { 150 + $uri = '/some/uri?field2=123&sort=field5%2Cdesc&limit=101&field3.field4%5Bcontains%5D=hi'; 151 + $query = new Query('Resource', $uri); 152 + $expected = array( 153 + 'field2' => array('123'), 154 + 'field3.field4[contains]' => array('hi') 155 + ); 156 + $this->assertEquals($query->filters, $expected); 157 + $expected = array('field5,desc'); 158 + $this->assertEquals($query->sorts, $expected); 159 + $this->assertEquals($query->size, 101); 160 + } 161 + 162 + public function testBuild() 163 + { 164 + $query = new Query('Resource', '/some/uri'); 165 + $query->filter(Resource::$f->name->eq('Wonka Chocs')) 166 + ->filter(Resource::$f->support_email->endswith('gmail.com')) 167 + ->filter(Resource::$f->variable_fee_percentage->gte(3.5)) 168 + ->sort(Resource::$f->name->asc()) 169 + ->sort(Resource::$f->variable_fee_percentage->desc()) 170 + ->limit(101); 171 + $this->assertEquals( 172 + $query->filters, 173 + array( 174 + 'name' => array('Wonka Chocs'), 175 + 'support_email[contains]' => array('gmail.com'), 176 + 'variable_fee_percentage[>=]'=> array(3.5) 177 + ) 178 + ); 179 + $this->assertEquals( 180 + $query->sorts, 181 + array('name,asc', 'variable_fee_percentage,desc') 182 + ); 183 + $this->assertEquals( 184 + $query->size, 185 + 101 186 + ); 187 + } 188 + } 189 + 190 + class PageTest extends \PHPUnit_Framework_TestCase 191 + { 192 + public function testConstruct() 193 + { 194 + $data = new \stdClass(); 195 + $data->first_uri = 'some/first/uri'; 196 + $data->previous_uri = 'some/previous/uri'; 197 + $data->next_uri = null; 198 + $data->last_uri = 'some/last/uri'; 199 + $data->limit= 25; 200 + $data->offset = 0; 201 + $data->total = 101; 202 + $data->items = array(); 203 + 204 + $page = new Page( 205 + 'Resource', 206 + '/some/uri', 207 + $data 208 + ); 209 + 210 + $this->assertEquals($page->resource, 'Resource'); 211 + $this->assertEquals($page->total, 101); 212 + $this->assertEquals($page->items, array()); 213 + $this->assertTrue($page->hasPrevious()); 214 + $this->assertFalse($page->hasNext()); 215 + } 216 + } 217 + 218 + class ResourceTest extends \PHPUnit_Framework_TestCase 219 + { 220 + public function testQuery() 221 + { 222 + $query = A::query(); 223 + $this->assertEquals(get_class($query), 'RESTful\Query'); 224 + } 225 + 226 + public function testObjectify() 227 + { 228 + $a = new A(array( 229 + 'uri' => '/as/123', 230 + 'field1' => 123, 231 + 'b' => array( 232 + 'uri' => '/bs/321', 233 + 'field2' => 321 234 + )) 235 + ); 236 + $this->assertEquals(get_class($a), 'RESTful\Test\A'); 237 + $this->assertEquals($a->field1, 123); 238 + $this->assertEquals(get_class($a->b), 'RESTful\Test\B'); 239 + $this->assertEquals($a->b->field2, 321); 240 + } 241 + }
+8
externals/restful/tests/phpunit.xml
··· 1 + <phpunit> 2 + <testsuite name="RESTful"> 3 + <directory>.</directory> 4 + </testsuite> 5 + <logging> 6 + <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> 7 + </logging> 8 + </phpunit>