├── .editorconfig ├── .env-example ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .htaccess ├── CONTRIBUTING.md ├── LICENSE.md ├── Procfile ├── README.md ├── composer.json ├── composer.lock ├── index.php └── templates └── recently_purchased.html /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file, no whitespace 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | # PHP style 13 | [*.php] 14 | charset = utf-8 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.env-example: -------------------------------------------------------------------------------- 1 | BC_AUTH_SERVICE=https://login.bigcommerce.com 2 | BC_CLIENT_ID=[CLIENT ID] 3 | BC_CLIENT_SECRET=[CLIENT SECRET] 4 | BC_CALLBACK_URL=http://localhost:8000/auth/callback 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | 3 | 4 | ### Actual behavior 5 | 6 | 7 | ### Steps to reproduce behavior 8 | 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### What? 2 | 3 | A description about what this pull request implements and its purpose. Try to be detailed and describe any technical details to simplify the job of the reviewer and the individual on production support. 4 | 5 | #### Tickets / Documentation 6 | 7 | Add links to any relevant tickets and documentation. 8 | 9 | - [Link 1](http://example.com) 10 | - ... 11 | 12 | #### Screenshots (if appropriate) 13 | 14 | Attach images or add image links here. 15 | 16 | ![Example Image](http://placehold.it/300x200) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | vendor/ 3 | .env 4 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | #RewriteBase / 3 | 4 | # only rewrite if the requested file doesn't exist 5 | RewriteCond %{REQUEST_FILENAME} !-s 6 | 7 | # pass the rest of the request into index.php to handle 8 | RewriteRule ^(.*)$ /index.php/$1 [L] 9 | 10 | # Prevent the web from seeing the readme.md just to be safe 11 | RewriteRule ^README\.MD$ - [L,F] 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Hello World App 2 | 3 | Thanks for showing interest in contributing! 4 | 5 | The following is a set of guidelines for contributing to the BigCommerce Hello World app. These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | #### Table of Contents 8 | 9 | [API Documentation](https://developer.bigcommerce.com/api) 10 | 11 | [How Can I Contribute?](#how-can-i-contribute) 12 | * [Your First Code Contribution](#your-first-code-contribution) 13 | * [Pull Requests](#pull-requests) 14 | 15 | [Styleguides](#styleguides) 16 | * [Git Commit Messages](#git-commit-messages) 17 | * [PHP Styleguide](#php-styleguide) 18 | 19 | ### Your First Code Contribution 20 | 21 | Unsure where to begin contributing to the API client? Check our [forums](https://forum.bigcommerce.com/s/group/0F913000000HLjECAW), our [stackoverflow](https://stackoverflow.com/questions/tagged/bigcommerce) tag, and the reported [issues](https://github.com/bigcommerce/hello-world-app-php-silex/issues). 22 | 23 | ### Pull Requests 24 | 25 | * Fill in [the required template](https://github.com/bigcommerce/hello-world-app-php-silex/pull/new/master) 26 | * Include screenshots and animated GIFs in your pull request whenever possible. 27 | * End files with a newline. 28 | 29 | ## Styleguides 30 | 31 | ### Git Commit Messages 32 | 33 | * Use the present tense ("Add feature" not "Added feature") 34 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 35 | * Limit the first line to 72 characters or less 36 | * Reference pull requests and external links liberally 37 | 38 | 39 | ### PHP Styleguide 40 | 41 | All PHP must adhere to [PSR2 PHP Styleguide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-present, BigCommerce Pty. Ltd. All rights reserved 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: php -S localhost:$PORT 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BigCommerce Sample App: PHP 2 | 3 | This is a small Silex application that implements the OAuth callback flow for BigCommerce [Single Click Apps][single_click_apps] 4 | and uses the [BigCommerce API][api_client] to pull a list of products on a BigCommerce store. For information on how to develop apps 5 | for BigCommerce stores, see our [Developer Portal][devdocs]. 6 | 7 | We hope this sample gives you a good starting point for building your next killer app! What follows are steps specific 8 | to running and installing this sample application. 9 | 10 | ## Prerequisites 11 | * A web server such as MAMP or Apache. A localhost address will work fine for the hello world app. 12 | * PHP 13 | * [Composer](https://getcomposer.org/doc/00-intro.md "Composer") 14 | 15 | ### Registering the app with BigCommerce 16 | 1. Create a trial store on [BigCommerce](https://www.bigcommerce.com/) 17 | 2. Go to the [Developer Portal][devportal] and log in by going to "My Apps" 18 | 3. Click the button "Create an app", enter a name for the new app, and then click "Create" 19 | 4. You don't have to fill out all the details for your app right away, but you do need 20 | to provide some core details in section 4 (Technical). Note that if you are just getting 21 | started, you can use `localhost` for your hostname, but ultimately you'll need to host your 22 | app on the public Internet. 23 | * _Auth Callback URL_: `https:///bigcommerce/callback` 24 | * _Load Callback URL_: `https:///bigcommerce/load` 25 | * _Uninstall Callback URL_: `https:///bigcommerce/uninstall` 26 | * _Remove User Callback URL_: `https:///bigcommerce/remove-user` (if enabling your app for multiple users) 27 | 5. Enable the _Products - Read Only_ scope under _OAuth scopes_, which is what this sample app needs. 28 | **Note:** If you are managing customer information through the API (such as with the _Recently Purchased Products Block_ example below) then you will need to also enable the _Customers_ scope to at least read data. 29 | below) then you will need to also enable the _Customers_ scope to at least read. 30 | 6. Click `Save & Close` on the top right of the dialog. 31 | 7. You'll now see your app in a list in the _My Apps_ section of Developer Portal. Hover over it and click 32 | _View Client ID_. You'll need these values in the next step. 33 | 34 | ### Getting Started 35 | 1. Fork the repo (optional). 36 | 2. Clone the repo. 37 | 38 | git clone https://github.com/bigcommerce/hello-world-app-php-silex 39 | 3. Use Composer to install the dependencies. 40 | 41 | php composer.phar install 42 | 4. Copy `.env-example` to `.env` and set the following environment variables in it. 43 | 44 | BC_AUTH_SERVICE=https://login.bigcommerce.com 45 | BC_CLIENT_ID= 46 | BC_CLIENT_SECRET= 47 | BC_CALLBACK_URL= 48 | 4. Restart the software or the entire host as needed to set the environment variables. 49 | 50 | ### Hosting the app 51 | In order to install this app in a BigCommerce store, it must be hosted on the public Internet. You can get started in development and use `localhost` in your URLs, but ultimately you will need to host it somewhere to use the app anywhere other than your development system. One easy option is to put it on Heroku. 52 | 53 | #### Heroku 54 | _Note: It is assumed that you already have a Heroku account, have the Heroku toolbelt installed, and have authenticated with 55 | the toolbelt. See [Heroku][toolbelt] for details._ 56 | 57 | * Create a new Heroku app: `heroku create ` 58 | * Push the project to Heroku: `git push heroku master -u` 59 | * Set `APP_URL` in .env to `https://.herokuapp.com` 60 | * Add the `heroku-config` plugin: `heroku plugins:install heroku-config` 61 | * Push the local environment variables to heroku: `heroku config:push` 62 | 63 | In the [BigCommerce Developer Portal][devportal], you'll need to update the app's callback URLs: 64 | 65 | * _Auth Callback URL_: `https://.herokuapp.com/auth/callback` 66 | * _Load Callback URL_: `https://.herokuapp.com/load` 67 | * _Uninstall Callback URL_: `https://.herokuapp.com/uninstall` 68 | 69 | ### Installing the app in your trial store 70 | * Login to your trial store 71 | * Go to the Marketplace and click _My Drafts_. Find the app you just created and click it. 72 | * A details dialog will open. Click _Install_ and the draft app will be installed in your store. 73 | 74 | ## Showing the Recently Purchased Products Block with JWT 75 | This example repo contains the ability to securely show recently purchased products. This is how it looks: 76 | 77 | ![](http://monosnap.com/image/iuFxhuS8havstVdzNHQGjz2aDmzDwO.png) 78 | 79 | ### Adding the block to your theme 80 | 1. Edit your `Footer.html` file in blueprint or Footer Scripts if you're using Stencil and add: 81 | ```javascript 82 | 95 | ``` 96 | 2. Put `
` wherever you want the block to appear. If you're using blueprint it is recommended that you put it in default.html right before `%%Panel.SideTopSellers%%`. 97 | 3. Log in as a customer in your store's frontend (or create a customer account if one doesn't exist yet), place an order then go to the section where you added the `
`. You should see the Recently Purchased Products block appear. 98 | 99 | 100 | 101 | [single_click_apps]: https://developer.bigcommerce.com/api/#building-oauth-apps 102 | [api_client]: https://github.com/bigcommerce/bigcommerce-api-php 103 | [devdocs]: https://developer.bigcommerce.com 104 | [devportal]: https://devtools.bigcommerce.com 105 | [toolbelt]: https://toolbelt.heroku.com 106 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "silex/silex": "1.*", 4 | "guzzle/guzzle": "~3.7", 5 | "propel/propel1": "~1.6", 6 | "colinmollenhour/credis": "~1.2", 7 | "realityking/hash_equals": "dev-master", 8 | "vlucas/phpdotenv": "~2.4", 9 | "firebase/php-jwt": "~4.0", 10 | "bigcommerce/api": "~3.0", 11 | "xamin/handlebars.php": "~0.10" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "396dc49584aff803b9c7fc0b4cd3c1e2", 8 | "content-hash": "5f8a1ab5b209354729c6200bc946686d", 9 | "packages": [ 10 | { 11 | "name": "bigcommerce/api", 12 | "version": "3.0.5", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/bigcommerce/bigcommerce-api-php.git", 16 | "reference": "37087f7506295b93f7430d3a02047f19c97bd0c3" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/bigcommerce/bigcommerce-api-php/zipball/37087f7506295b93f7430d3a02047f19c97bd0c3", 21 | "reference": "37087f7506295b93f7430d3a02047f19c97bd0c3", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3.0" 26 | }, 27 | "require-dev": { 28 | "codeless/jugglecode": "1.0", 29 | "phpunit/phpunit": "4.5.*", 30 | "satooshi/php-coveralls": "1.0" 31 | }, 32 | "type": "library", 33 | "autoload": { 34 | "psr-0": { 35 | "Bigcommerce": "src/" 36 | }, 37 | "psr-4": { 38 | "Bigcommerce\\Test\\": "test/" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Bigcommerce", 48 | "homepage": "http://www.bigcommerce.com" 49 | } 50 | ], 51 | "description": "Enables PHP applications to communicate with the Bigcommerce API.", 52 | "homepage": "http://developer.bigcommerce.com", 53 | "keywords": [ 54 | "api", 55 | "business", 56 | "ecommerce", 57 | "http", 58 | "rest" 59 | ], 60 | "time": "2016-06-15 19:29:10" 61 | }, 62 | { 63 | "name": "colinmollenhour/credis", 64 | "version": "1.7", 65 | "source": { 66 | "type": "git", 67 | "url": "https://github.com/colinmollenhour/credis.git", 68 | "reference": "74b2b703da5c58dc07fb97e8954bc63280b469bf" 69 | }, 70 | "dist": { 71 | "type": "zip", 72 | "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/74b2b703da5c58dc07fb97e8954bc63280b469bf", 73 | "reference": "74b2b703da5c58dc07fb97e8954bc63280b469bf", 74 | "shasum": "" 75 | }, 76 | "require": { 77 | "php": ">=5.4.0" 78 | }, 79 | "type": "library", 80 | "autoload": { 81 | "classmap": [ 82 | "Client.php", 83 | "Cluster.php", 84 | "Sentinel.php" 85 | ] 86 | }, 87 | "notification-url": "https://packagist.org/downloads/", 88 | "license": [ 89 | "MIT" 90 | ], 91 | "authors": [ 92 | { 93 | "name": "Colin Mollenhour", 94 | "email": "colin@mollenhour.com" 95 | } 96 | ], 97 | "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", 98 | "homepage": "https://github.com/colinmollenhour/credis", 99 | "time": "2016-03-24 15:50:52" 100 | }, 101 | { 102 | "name": "firebase/php-jwt", 103 | "version": "v4.0.0", 104 | "source": { 105 | "type": "git", 106 | "url": "https://github.com/firebase/php-jwt.git", 107 | "reference": "dccf163dc8ed7ed6a00afc06c51ee5186a428d35" 108 | }, 109 | "dist": { 110 | "type": "zip", 111 | "url": "https://api.github.com/repos/firebase/php-jwt/zipball/dccf163dc8ed7ed6a00afc06c51ee5186a428d35", 112 | "reference": "dccf163dc8ed7ed6a00afc06c51ee5186a428d35", 113 | "shasum": "" 114 | }, 115 | "require": { 116 | "php": ">=5.3.0" 117 | }, 118 | "type": "library", 119 | "autoload": { 120 | "psr-4": { 121 | "Firebase\\JWT\\": "src" 122 | } 123 | }, 124 | "notification-url": "https://packagist.org/downloads/", 125 | "license": [ 126 | "BSD-3-Clause" 127 | ], 128 | "authors": [ 129 | { 130 | "name": "Neuman Vong", 131 | "email": "neuman+pear@twilio.com", 132 | "role": "Developer" 133 | }, 134 | { 135 | "name": "Anant Narayanan", 136 | "email": "anant@php.net", 137 | "role": "Developer" 138 | } 139 | ], 140 | "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", 141 | "homepage": "https://github.com/firebase/php-jwt", 142 | "time": "2016-07-18 04:51:16" 143 | }, 144 | { 145 | "name": "guzzle/guzzle", 146 | "version": "v3.9.3", 147 | "source": { 148 | "type": "git", 149 | "url": "https://github.com/guzzle/guzzle3.git", 150 | "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" 151 | }, 152 | "dist": { 153 | "type": "zip", 154 | "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", 155 | "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", 156 | "shasum": "" 157 | }, 158 | "require": { 159 | "ext-curl": "*", 160 | "php": ">=5.3.3", 161 | "symfony/event-dispatcher": "~2.1" 162 | }, 163 | "replace": { 164 | "guzzle/batch": "self.version", 165 | "guzzle/cache": "self.version", 166 | "guzzle/common": "self.version", 167 | "guzzle/http": "self.version", 168 | "guzzle/inflection": "self.version", 169 | "guzzle/iterator": "self.version", 170 | "guzzle/log": "self.version", 171 | "guzzle/parser": "self.version", 172 | "guzzle/plugin": "self.version", 173 | "guzzle/plugin-async": "self.version", 174 | "guzzle/plugin-backoff": "self.version", 175 | "guzzle/plugin-cache": "self.version", 176 | "guzzle/plugin-cookie": "self.version", 177 | "guzzle/plugin-curlauth": "self.version", 178 | "guzzle/plugin-error-response": "self.version", 179 | "guzzle/plugin-history": "self.version", 180 | "guzzle/plugin-log": "self.version", 181 | "guzzle/plugin-md5": "self.version", 182 | "guzzle/plugin-mock": "self.version", 183 | "guzzle/plugin-oauth": "self.version", 184 | "guzzle/service": "self.version", 185 | "guzzle/stream": "self.version" 186 | }, 187 | "require-dev": { 188 | "doctrine/cache": "~1.3", 189 | "monolog/monolog": "~1.0", 190 | "phpunit/phpunit": "3.7.*", 191 | "psr/log": "~1.0", 192 | "symfony/class-loader": "~2.1", 193 | "zendframework/zend-cache": "2.*,<2.3", 194 | "zendframework/zend-log": "2.*,<2.3" 195 | }, 196 | "suggest": { 197 | "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." 198 | }, 199 | "type": "library", 200 | "extra": { 201 | "branch-alias": { 202 | "dev-master": "3.9-dev" 203 | } 204 | }, 205 | "autoload": { 206 | "psr-0": { 207 | "Guzzle": "src/", 208 | "Guzzle\\Tests": "tests/" 209 | } 210 | }, 211 | "notification-url": "https://packagist.org/downloads/", 212 | "license": [ 213 | "MIT" 214 | ], 215 | "authors": [ 216 | { 217 | "name": "Michael Dowling", 218 | "email": "mtdowling@gmail.com", 219 | "homepage": "https://github.com/mtdowling" 220 | }, 221 | { 222 | "name": "Guzzle Community", 223 | "homepage": "https://github.com/guzzle/guzzle/contributors" 224 | } 225 | ], 226 | "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", 227 | "homepage": "http://guzzlephp.org/", 228 | "keywords": [ 229 | "client", 230 | "curl", 231 | "framework", 232 | "http", 233 | "http client", 234 | "rest", 235 | "web service" 236 | ], 237 | "abandoned": "guzzlehttp/guzzle", 238 | "time": "2015-03-18 18:23:50" 239 | }, 240 | { 241 | "name": "phing/phing", 242 | "version": "2.15.2", 243 | "source": { 244 | "type": "git", 245 | "url": "https://github.com/phingofficial/phing.git", 246 | "reference": "0999ab4e94e609dc00998e3d1b88df843054db7c" 247 | }, 248 | "dist": { 249 | "type": "zip", 250 | "url": "https://api.github.com/repos/phingofficial/phing/zipball/0999ab4e94e609dc00998e3d1b88df843054db7c", 251 | "reference": "0999ab4e94e609dc00998e3d1b88df843054db7c", 252 | "shasum": "" 253 | }, 254 | "require": { 255 | "php": ">=5.2.0" 256 | }, 257 | "require-dev": { 258 | "ext-pdo_sqlite": "*", 259 | "lastcraft/simpletest": "@dev", 260 | "mikey179/vfsstream": "^1.6", 261 | "pdepend/pdepend": "2.x", 262 | "pear/archive_tar": "1.4.x", 263 | "pear/http_request2": "dev-trunk", 264 | "pear/net_growl": "dev-trunk", 265 | "pear/pear-core-minimal": "1.10.1", 266 | "pear/versioncontrol_git": "@dev", 267 | "pear/versioncontrol_svn": "~0.5", 268 | "phpdocumentor/phpdocumentor": "2.x", 269 | "phploc/phploc": "~2.0.6", 270 | "phpmd/phpmd": "~2.2", 271 | "phpunit/phpunit": ">=3.7", 272 | "sebastian/git": "~1.0", 273 | "sebastian/phpcpd": "2.x", 274 | "siad007/versioncontrol_hg": "^1.0", 275 | "squizlabs/php_codesniffer": "~2.2", 276 | "symfony/yaml": "~2.7" 277 | }, 278 | "suggest": { 279 | "pdepend/pdepend": "PHP version of JDepend", 280 | "pear/archive_tar": "Tar file management class", 281 | "pear/versioncontrol_git": "A library that provides OO interface to handle Git repository", 282 | "pear/versioncontrol_svn": "A simple OO-style interface for Subversion, the free/open-source version control system", 283 | "phpdocumentor/phpdocumentor": "Documentation Generator for PHP", 284 | "phploc/phploc": "A tool for quickly measuring the size of a PHP project", 285 | "phpmd/phpmd": "PHP version of PMD tool", 286 | "phpunit/php-code-coverage": "Library that provides collection, processing, and rendering functionality for PHP code coverage information", 287 | "phpunit/phpunit": "The PHP Unit Testing Framework", 288 | "sebastian/phpcpd": "Copy/Paste Detector (CPD) for PHP code", 289 | "siad007/versioncontrol_hg": "A library for interfacing with Mercurial repositories.", 290 | "tedivm/jshrink": "Javascript Minifier built in PHP" 291 | }, 292 | "bin": [ 293 | "bin/phing" 294 | ], 295 | "type": "library", 296 | "extra": { 297 | "branch-alias": { 298 | "dev-master": "2.15.x-dev" 299 | } 300 | }, 301 | "autoload": { 302 | "classmap": [ 303 | "classes/phing/" 304 | ] 305 | }, 306 | "notification-url": "https://packagist.org/downloads/", 307 | "include-path": [ 308 | "classes" 309 | ], 310 | "license": [ 311 | "LGPL-3.0" 312 | ], 313 | "authors": [ 314 | { 315 | "name": "Michiel Rook", 316 | "email": "mrook@php.net" 317 | }, 318 | { 319 | "name": "Phing Community", 320 | "homepage": "https://www.phing.info/trac/wiki/Development/Contributors" 321 | } 322 | ], 323 | "description": "PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.", 324 | "homepage": "https://www.phing.info/", 325 | "keywords": [ 326 | "build", 327 | "phing", 328 | "task", 329 | "tool" 330 | ], 331 | "time": "2016-10-13 09:01:45" 332 | }, 333 | { 334 | "name": "pimple/pimple", 335 | "version": "v1.1.1", 336 | "source": { 337 | "type": "git", 338 | "url": "https://github.com/silexphp/Pimple.git", 339 | "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" 340 | }, 341 | "dist": { 342 | "type": "zip", 343 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", 344 | "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", 345 | "shasum": "" 346 | }, 347 | "require": { 348 | "php": ">=5.3.0" 349 | }, 350 | "type": "library", 351 | "extra": { 352 | "branch-alias": { 353 | "dev-master": "1.1.x-dev" 354 | } 355 | }, 356 | "autoload": { 357 | "psr-0": { 358 | "Pimple": "lib/" 359 | } 360 | }, 361 | "notification-url": "https://packagist.org/downloads/", 362 | "license": [ 363 | "MIT" 364 | ], 365 | "authors": [ 366 | { 367 | "name": "Fabien Potencier", 368 | "email": "fabien@symfony.com" 369 | } 370 | ], 371 | "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", 372 | "homepage": "http://pimple.sensiolabs.org", 373 | "keywords": [ 374 | "container", 375 | "dependency injection" 376 | ], 377 | "time": "2013-11-22 08:30:29" 378 | }, 379 | { 380 | "name": "propel/propel1", 381 | "version": "1.7.1", 382 | "source": { 383 | "type": "git", 384 | "url": "https://github.com/propelorm/Propel.git", 385 | "reference": "af527736586388c21a5a0a5f97914e7264fe580c" 386 | }, 387 | "dist": { 388 | "type": "zip", 389 | "url": "https://api.github.com/repos/propelorm/Propel/zipball/af527736586388c21a5a0a5f97914e7264fe580c", 390 | "reference": "af527736586388c21a5a0a5f97914e7264fe580c", 391 | "shasum": "" 392 | }, 393 | "require": { 394 | "phing/phing": "~2.4", 395 | "php": ">=5.2.4" 396 | }, 397 | "require-dev": { 398 | "pear-pear.php.net/pear_packagefilemanager2": "@stable" 399 | }, 400 | "bin": [ 401 | "generator/bin/propel-gen", 402 | "generator/bin/propel-gen.bat" 403 | ], 404 | "type": "library", 405 | "extra": { 406 | "branch-alias": { 407 | "dev-master": "1.7-dev" 408 | } 409 | }, 410 | "autoload": { 411 | "classmap": [ 412 | "runtime/lib", 413 | "generator/lib" 414 | ] 415 | }, 416 | "notification-url": "https://packagist.org/downloads/", 417 | "include-path": [ 418 | "runtime/lib", 419 | "generator/lib" 420 | ], 421 | "license": [ 422 | "MIT" 423 | ], 424 | "authors": [ 425 | { 426 | "name": "William Durand", 427 | "email": "william.durand1@gmail.com", 428 | "homepage": "http://www.willdurand.fr" 429 | } 430 | ], 431 | "description": "Propel is an open-source Object-Relational Mapping (ORM) for PHP5.", 432 | "homepage": "http://www.propelorm.org/", 433 | "keywords": [ 434 | "Active Record", 435 | "database", 436 | "mapping", 437 | "orm", 438 | "persistence" 439 | ], 440 | "time": "2014-02-25 09:13:58" 441 | }, 442 | { 443 | "name": "psr/log", 444 | "version": "1.0.2", 445 | "source": { 446 | "type": "git", 447 | "url": "https://github.com/php-fig/log.git", 448 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" 449 | }, 450 | "dist": { 451 | "type": "zip", 452 | "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 453 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 454 | "shasum": "" 455 | }, 456 | "require": { 457 | "php": ">=5.3.0" 458 | }, 459 | "type": "library", 460 | "extra": { 461 | "branch-alias": { 462 | "dev-master": "1.0.x-dev" 463 | } 464 | }, 465 | "autoload": { 466 | "psr-4": { 467 | "Psr\\Log\\": "Psr/Log/" 468 | } 469 | }, 470 | "notification-url": "https://packagist.org/downloads/", 471 | "license": [ 472 | "MIT" 473 | ], 474 | "authors": [ 475 | { 476 | "name": "PHP-FIG", 477 | "homepage": "http://www.php-fig.org/" 478 | } 479 | ], 480 | "description": "Common interface for logging libraries", 481 | "homepage": "https://github.com/php-fig/log", 482 | "keywords": [ 483 | "log", 484 | "psr", 485 | "psr-3" 486 | ], 487 | "time": "2016-10-10 12:19:37" 488 | }, 489 | { 490 | "name": "realityking/hash_equals", 491 | "version": "dev-master", 492 | "source": { 493 | "type": "git", 494 | "url": "https://github.com/realityking/hash_equals.git", 495 | "reference": "c94c380358f0649d4efe09c59fa94d16a65607bf" 496 | }, 497 | "dist": { 498 | "type": "zip", 499 | "url": "https://api.github.com/repos/realityking/hash_equals/zipball/c94c380358f0649d4efe09c59fa94d16a65607bf", 500 | "reference": "c94c380358f0649d4efe09c59fa94d16a65607bf", 501 | "shasum": "" 502 | }, 503 | "type": "library", 504 | "autoload": { 505 | "files": [ 506 | "src/hash_equals.php" 507 | ] 508 | }, 509 | "notification-url": "https://packagist.org/downloads/", 510 | "license": [ 511 | "MIT" 512 | ], 513 | "authors": [ 514 | { 515 | "name": "Rouven Weßling", 516 | "homepage": "http://rouvenwessling.de" 517 | } 518 | ], 519 | "description": "Provides functionality for hash_equals() to projects using PHP earlier than version 5.6.", 520 | "homepage": "https://github.com/realityking/hash_equals", 521 | "keywords": [ 522 | "hash", 523 | "timing attack" 524 | ], 525 | "time": "2014-04-14 18:33:30" 526 | }, 527 | { 528 | "name": "silex/silex", 529 | "version": "v1.3.5", 530 | "source": { 531 | "type": "git", 532 | "url": "https://github.com/silexphp/Silex.git", 533 | "reference": "374c7e04040a6f781c90f7d746726a5daa78e783" 534 | }, 535 | "dist": { 536 | "type": "zip", 537 | "url": "https://api.github.com/repos/silexphp/Silex/zipball/374c7e04040a6f781c90f7d746726a5daa78e783", 538 | "reference": "374c7e04040a6f781c90f7d746726a5daa78e783", 539 | "shasum": "" 540 | }, 541 | "require": { 542 | "php": ">=5.3.9", 543 | "pimple/pimple": "~1.0", 544 | "symfony/event-dispatcher": "~2.3|3.0.*", 545 | "symfony/http-foundation": "~2.3|3.0.*", 546 | "symfony/http-kernel": "~2.3|3.0.*", 547 | "symfony/routing": "~2.3|3.0.*" 548 | }, 549 | "require-dev": { 550 | "doctrine/dbal": "~2.2", 551 | "monolog/monolog": "^1.4.1", 552 | "swiftmailer/swiftmailer": "~5", 553 | "symfony/browser-kit": "~2.3|3.0.*", 554 | "symfony/config": "~2.3|3.0.*", 555 | "symfony/css-selector": "~2.3|3.0.*", 556 | "symfony/debug": "~2.3|3.0.*", 557 | "symfony/dom-crawler": "~2.3|3.0.*", 558 | "symfony/finder": "~2.3|3.0.*", 559 | "symfony/form": "~2.3|3.0.*", 560 | "symfony/locale": "~2.3|3.0.*", 561 | "symfony/monolog-bridge": "~2.3|3.0.*", 562 | "symfony/options-resolver": "~2.3|3.0.*", 563 | "symfony/phpunit-bridge": "~2.7", 564 | "symfony/process": "~2.3|3.0.*", 565 | "symfony/security": "~2.3|3.0.*", 566 | "symfony/serializer": "~2.3|3.0.*", 567 | "symfony/translation": "~2.3|3.0.*", 568 | "symfony/twig-bridge": "~2.3|3.0.*", 569 | "symfony/validator": "~2.3|3.0.*", 570 | "twig/twig": "~1.8|~2.0" 571 | }, 572 | "type": "library", 573 | "extra": { 574 | "branch-alias": { 575 | "dev-master": "1.3.x-dev" 576 | } 577 | }, 578 | "autoload": { 579 | "psr-4": { 580 | "Silex\\": "src/Silex" 581 | } 582 | }, 583 | "notification-url": "https://packagist.org/downloads/", 584 | "license": [ 585 | "MIT" 586 | ], 587 | "authors": [ 588 | { 589 | "name": "Fabien Potencier", 590 | "email": "fabien@symfony.com" 591 | }, 592 | { 593 | "name": "Igor Wiedler", 594 | "email": "igor@wiedler.ch" 595 | } 596 | ], 597 | "description": "The PHP micro-framework based on the Symfony Components", 598 | "homepage": "http://silex.sensiolabs.org", 599 | "keywords": [ 600 | "microframework" 601 | ], 602 | "time": "2016-01-06 14:59:35" 603 | }, 604 | { 605 | "name": "symfony/debug", 606 | "version": "v3.1.6", 607 | "source": { 608 | "type": "git", 609 | "url": "https://github.com/symfony/debug.git", 610 | "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8" 611 | }, 612 | "dist": { 613 | "type": "zip", 614 | "url": "https://api.github.com/repos/symfony/debug/zipball/e2b3f74a67fc928adc3c1b9027f73e1bc01190a8", 615 | "reference": "e2b3f74a67fc928adc3c1b9027f73e1bc01190a8", 616 | "shasum": "" 617 | }, 618 | "require": { 619 | "php": ">=5.5.9", 620 | "psr/log": "~1.0" 621 | }, 622 | "conflict": { 623 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" 624 | }, 625 | "require-dev": { 626 | "symfony/class-loader": "~2.8|~3.0", 627 | "symfony/http-kernel": "~2.8|~3.0" 628 | }, 629 | "type": "library", 630 | "extra": { 631 | "branch-alias": { 632 | "dev-master": "3.1-dev" 633 | } 634 | }, 635 | "autoload": { 636 | "psr-4": { 637 | "Symfony\\Component\\Debug\\": "" 638 | }, 639 | "exclude-from-classmap": [ 640 | "/Tests/" 641 | ] 642 | }, 643 | "notification-url": "https://packagist.org/downloads/", 644 | "license": [ 645 | "MIT" 646 | ], 647 | "authors": [ 648 | { 649 | "name": "Fabien Potencier", 650 | "email": "fabien@symfony.com" 651 | }, 652 | { 653 | "name": "Symfony Community", 654 | "homepage": "https://symfony.com/contributors" 655 | } 656 | ], 657 | "description": "Symfony Debug Component", 658 | "homepage": "https://symfony.com", 659 | "time": "2016-09-06 11:02:40" 660 | }, 661 | { 662 | "name": "symfony/event-dispatcher", 663 | "version": "v2.8.13", 664 | "source": { 665 | "type": "git", 666 | "url": "https://github.com/symfony/event-dispatcher.git", 667 | "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934" 668 | }, 669 | "dist": { 670 | "type": "zip", 671 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", 672 | "reference": "25c576abd4e0f212e678fe8b2bd9a9a98c7ea934", 673 | "shasum": "" 674 | }, 675 | "require": { 676 | "php": ">=5.3.9" 677 | }, 678 | "require-dev": { 679 | "psr/log": "~1.0", 680 | "symfony/config": "~2.0,>=2.0.5|~3.0.0", 681 | "symfony/dependency-injection": "~2.6|~3.0.0", 682 | "symfony/expression-language": "~2.6|~3.0.0", 683 | "symfony/stopwatch": "~2.3|~3.0.0" 684 | }, 685 | "suggest": { 686 | "symfony/dependency-injection": "", 687 | "symfony/http-kernel": "" 688 | }, 689 | "type": "library", 690 | "extra": { 691 | "branch-alias": { 692 | "dev-master": "2.8-dev" 693 | } 694 | }, 695 | "autoload": { 696 | "psr-4": { 697 | "Symfony\\Component\\EventDispatcher\\": "" 698 | }, 699 | "exclude-from-classmap": [ 700 | "/Tests/" 701 | ] 702 | }, 703 | "notification-url": "https://packagist.org/downloads/", 704 | "license": [ 705 | "MIT" 706 | ], 707 | "authors": [ 708 | { 709 | "name": "Fabien Potencier", 710 | "email": "fabien@symfony.com" 711 | }, 712 | { 713 | "name": "Symfony Community", 714 | "homepage": "https://symfony.com/contributors" 715 | } 716 | ], 717 | "description": "Symfony EventDispatcher Component", 718 | "homepage": "https://symfony.com", 719 | "time": "2016-10-13 01:43:15" 720 | }, 721 | { 722 | "name": "symfony/http-foundation", 723 | "version": "v3.0.9", 724 | "source": { 725 | "type": "git", 726 | "url": "https://github.com/symfony/http-foundation.git", 727 | "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82" 728 | }, 729 | "dist": { 730 | "type": "zip", 731 | "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49ba00f8ede742169cb6b70abe33243f4d673f82", 732 | "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82", 733 | "shasum": "" 734 | }, 735 | "require": { 736 | "php": ">=5.5.9", 737 | "symfony/polyfill-mbstring": "~1.1" 738 | }, 739 | "require-dev": { 740 | "symfony/expression-language": "~2.8|~3.0" 741 | }, 742 | "type": "library", 743 | "extra": { 744 | "branch-alias": { 745 | "dev-master": "3.0-dev" 746 | } 747 | }, 748 | "autoload": { 749 | "psr-4": { 750 | "Symfony\\Component\\HttpFoundation\\": "" 751 | }, 752 | "exclude-from-classmap": [ 753 | "/Tests/" 754 | ] 755 | }, 756 | "notification-url": "https://packagist.org/downloads/", 757 | "license": [ 758 | "MIT" 759 | ], 760 | "authors": [ 761 | { 762 | "name": "Fabien Potencier", 763 | "email": "fabien@symfony.com" 764 | }, 765 | { 766 | "name": "Symfony Community", 767 | "homepage": "https://symfony.com/contributors" 768 | } 769 | ], 770 | "description": "Symfony HttpFoundation Component", 771 | "homepage": "https://symfony.com", 772 | "time": "2016-07-17 13:54:30" 773 | }, 774 | { 775 | "name": "symfony/http-kernel", 776 | "version": "v3.0.9", 777 | "source": { 778 | "type": "git", 779 | "url": "https://github.com/symfony/http-kernel.git", 780 | "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3" 781 | }, 782 | "dist": { 783 | "type": "zip", 784 | "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d97ba4425e36e79c794e7d14ff36f00f081b37b3", 785 | "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3", 786 | "shasum": "" 787 | }, 788 | "require": { 789 | "php": ">=5.5.9", 790 | "psr/log": "~1.0", 791 | "symfony/debug": "~2.8|~3.0", 792 | "symfony/event-dispatcher": "~2.8|~3.0", 793 | "symfony/http-foundation": "~2.8.8|~3.0.8|~3.1.2|~3.2" 794 | }, 795 | "conflict": { 796 | "symfony/config": "<2.8" 797 | }, 798 | "require-dev": { 799 | "symfony/browser-kit": "~2.8|~3.0", 800 | "symfony/class-loader": "~2.8|~3.0", 801 | "symfony/config": "~2.8|~3.0", 802 | "symfony/console": "~2.8|~3.0", 803 | "symfony/css-selector": "~2.8|~3.0", 804 | "symfony/dependency-injection": "~2.8|~3.0", 805 | "symfony/dom-crawler": "~2.8|~3.0", 806 | "symfony/expression-language": "~2.8|~3.0", 807 | "symfony/finder": "~2.8|~3.0", 808 | "symfony/process": "~2.8|~3.0", 809 | "symfony/routing": "~2.8|~3.0", 810 | "symfony/stopwatch": "~2.8|~3.0", 811 | "symfony/templating": "~2.8|~3.0", 812 | "symfony/translation": "~2.8|~3.0", 813 | "symfony/var-dumper": "~2.8|~3.0" 814 | }, 815 | "suggest": { 816 | "symfony/browser-kit": "", 817 | "symfony/class-loader": "", 818 | "symfony/config": "", 819 | "symfony/console": "", 820 | "symfony/dependency-injection": "", 821 | "symfony/finder": "", 822 | "symfony/var-dumper": "" 823 | }, 824 | "type": "library", 825 | "extra": { 826 | "branch-alias": { 827 | "dev-master": "3.0-dev" 828 | } 829 | }, 830 | "autoload": { 831 | "psr-4": { 832 | "Symfony\\Component\\HttpKernel\\": "" 833 | }, 834 | "exclude-from-classmap": [ 835 | "/Tests/" 836 | ] 837 | }, 838 | "notification-url": "https://packagist.org/downloads/", 839 | "license": [ 840 | "MIT" 841 | ], 842 | "authors": [ 843 | { 844 | "name": "Fabien Potencier", 845 | "email": "fabien@symfony.com" 846 | }, 847 | { 848 | "name": "Symfony Community", 849 | "homepage": "https://symfony.com/contributors" 850 | } 851 | ], 852 | "description": "Symfony HttpKernel Component", 853 | "homepage": "https://symfony.com", 854 | "time": "2016-07-30 09:10:37" 855 | }, 856 | { 857 | "name": "symfony/polyfill-mbstring", 858 | "version": "v1.3.0", 859 | "source": { 860 | "type": "git", 861 | "url": "https://github.com/symfony/polyfill-mbstring.git", 862 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" 863 | }, 864 | "dist": { 865 | "type": "zip", 866 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", 867 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", 868 | "shasum": "" 869 | }, 870 | "require": { 871 | "php": ">=5.3.3" 872 | }, 873 | "suggest": { 874 | "ext-mbstring": "For best performance" 875 | }, 876 | "type": "library", 877 | "extra": { 878 | "branch-alias": { 879 | "dev-master": "1.3-dev" 880 | } 881 | }, 882 | "autoload": { 883 | "psr-4": { 884 | "Symfony\\Polyfill\\Mbstring\\": "" 885 | }, 886 | "files": [ 887 | "bootstrap.php" 888 | ] 889 | }, 890 | "notification-url": "https://packagist.org/downloads/", 891 | "license": [ 892 | "MIT" 893 | ], 894 | "authors": [ 895 | { 896 | "name": "Nicolas Grekas", 897 | "email": "p@tchwork.com" 898 | }, 899 | { 900 | "name": "Symfony Community", 901 | "homepage": "https://symfony.com/contributors" 902 | } 903 | ], 904 | "description": "Symfony polyfill for the Mbstring extension", 905 | "homepage": "https://symfony.com", 906 | "keywords": [ 907 | "compatibility", 908 | "mbstring", 909 | "polyfill", 910 | "portable", 911 | "shim" 912 | ], 913 | "time": "2016-11-14 01:06:16" 914 | }, 915 | { 916 | "name": "symfony/routing", 917 | "version": "v3.0.9", 918 | "source": { 919 | "type": "git", 920 | "url": "https://github.com/symfony/routing.git", 921 | "reference": "9038984bd9c05ab07280121e9e10f61a7231457b" 922 | }, 923 | "dist": { 924 | "type": "zip", 925 | "url": "https://api.github.com/repos/symfony/routing/zipball/9038984bd9c05ab07280121e9e10f61a7231457b", 926 | "reference": "9038984bd9c05ab07280121e9e10f61a7231457b", 927 | "shasum": "" 928 | }, 929 | "require": { 930 | "php": ">=5.5.9" 931 | }, 932 | "conflict": { 933 | "symfony/config": "<2.8" 934 | }, 935 | "require-dev": { 936 | "doctrine/annotations": "~1.0", 937 | "doctrine/common": "~2.2", 938 | "psr/log": "~1.0", 939 | "symfony/config": "~2.8|~3.0", 940 | "symfony/expression-language": "~2.8|~3.0", 941 | "symfony/http-foundation": "~2.8|~3.0", 942 | "symfony/yaml": "~2.8|~3.0" 943 | }, 944 | "suggest": { 945 | "doctrine/annotations": "For using the annotation loader", 946 | "symfony/config": "For using the all-in-one router or any loader", 947 | "symfony/dependency-injection": "For loading routes from a service", 948 | "symfony/expression-language": "For using expression matching", 949 | "symfony/http-foundation": "For using a Symfony Request object", 950 | "symfony/yaml": "For using the YAML loader" 951 | }, 952 | "type": "library", 953 | "extra": { 954 | "branch-alias": { 955 | "dev-master": "3.0-dev" 956 | } 957 | }, 958 | "autoload": { 959 | "psr-4": { 960 | "Symfony\\Component\\Routing\\": "" 961 | }, 962 | "exclude-from-classmap": [ 963 | "/Tests/" 964 | ] 965 | }, 966 | "notification-url": "https://packagist.org/downloads/", 967 | "license": [ 968 | "MIT" 969 | ], 970 | "authors": [ 971 | { 972 | "name": "Fabien Potencier", 973 | "email": "fabien@symfony.com" 974 | }, 975 | { 976 | "name": "Symfony Community", 977 | "homepage": "https://symfony.com/contributors" 978 | } 979 | ], 980 | "description": "Symfony Routing Component", 981 | "homepage": "https://symfony.com", 982 | "keywords": [ 983 | "router", 984 | "routing", 985 | "uri", 986 | "url" 987 | ], 988 | "time": "2016-06-29 05:40:00" 989 | }, 990 | { 991 | "name": "vlucas/phpdotenv", 992 | "version": "v2.4.0", 993 | "source": { 994 | "type": "git", 995 | "url": "https://github.com/vlucas/phpdotenv.git", 996 | "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" 997 | }, 998 | "dist": { 999 | "type": "zip", 1000 | "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", 1001 | "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", 1002 | "shasum": "" 1003 | }, 1004 | "require": { 1005 | "php": ">=5.3.9" 1006 | }, 1007 | "require-dev": { 1008 | "phpunit/phpunit": "^4.8 || ^5.0" 1009 | }, 1010 | "type": "library", 1011 | "extra": { 1012 | "branch-alias": { 1013 | "dev-master": "2.4-dev" 1014 | } 1015 | }, 1016 | "autoload": { 1017 | "psr-4": { 1018 | "Dotenv\\": "src/" 1019 | } 1020 | }, 1021 | "notification-url": "https://packagist.org/downloads/", 1022 | "license": [ 1023 | "BSD-3-Clause-Attribution" 1024 | ], 1025 | "authors": [ 1026 | { 1027 | "name": "Vance Lucas", 1028 | "email": "vance@vancelucas.com", 1029 | "homepage": "http://www.vancelucas.com" 1030 | } 1031 | ], 1032 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 1033 | "keywords": [ 1034 | "dotenv", 1035 | "env", 1036 | "environment" 1037 | ], 1038 | "time": "2016-09-01 10:05:43" 1039 | }, 1040 | { 1041 | "name": "xamin/handlebars.php", 1042 | "version": "v0.10.3", 1043 | "source": { 1044 | "type": "git", 1045 | "url": "https://github.com/XaminProject/handlebars.php.git", 1046 | "reference": "5e1db1d1c7969fae32eab331a3b4d9b93257a709" 1047 | }, 1048 | "dist": { 1049 | "type": "zip", 1050 | "url": "https://api.github.com/repos/XaminProject/handlebars.php/zipball/5e1db1d1c7969fae32eab331a3b4d9b93257a709", 1051 | "reference": "5e1db1d1c7969fae32eab331a3b4d9b93257a709", 1052 | "shasum": "" 1053 | }, 1054 | "require-dev": { 1055 | "phpunit/phpunit": "~4.4", 1056 | "squizlabs/php_codesniffer": "~1.5" 1057 | }, 1058 | "type": "library", 1059 | "autoload": { 1060 | "psr-0": { 1061 | "Handlebars": "src/" 1062 | } 1063 | }, 1064 | "notification-url": "https://packagist.org/downloads/", 1065 | "license": [ 1066 | "MIT" 1067 | ], 1068 | "authors": [ 1069 | { 1070 | "name": "fzerorubigd", 1071 | "email": "fzerorubigd@gmail.com" 1072 | }, 1073 | { 1074 | "name": "Behrooz Shabani (everplays)", 1075 | "email": "everplays@gmail.com" 1076 | } 1077 | ], 1078 | "description": "Handlebars processor for php", 1079 | "homepage": "https://github.com/XaminProject/handlebars.php", 1080 | "time": "2015-08-06 22:16:36" 1081 | } 1082 | ], 1083 | "packages-dev": [], 1084 | "aliases": [], 1085 | "minimum-stability": "stable", 1086 | "stability-flags": { 1087 | "realityking/hash_equals": 20 1088 | }, 1089 | "prefer-stable": false, 1090 | "prefer-lowest": false, 1091 | "platform": [], 1092 | "platform-dev": [] 1093 | } 1094 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | load(); 17 | 18 | $app = new Application(); 19 | $app['debug'] = true; 20 | 21 | $app->get('/load', function (Request $request) use ($app) { 22 | 23 | $data = verifySignedRequest($request->get('signed_payload')); 24 | if (empty($data)) { 25 | return 'Invalid signed_payload.'; 26 | } 27 | $redis = new Credis_Client('localhost'); 28 | $key = getUserKey($data['store_hash'], $data['user']['email']); 29 | $user = json_decode($redis->get($key), true); 30 | if (empty($user)) { 31 | $user = $data['user']; 32 | $redis->set($key, json_encode($user, true)); 33 | } 34 | return 'Welcome ' . json_encode($user, true); 35 | }); 36 | 37 | $app->get('/auth/callback', function (Request $request) use ($app) { 38 | $redis = new Credis_Client('localhost'); 39 | 40 | $payload = array( 41 | 'client_id' => clientId(), 42 | 'client_secret' => clientSecret(), 43 | 'redirect_uri' => callbackUrl(), 44 | 'grant_type' => 'authorization_code', 45 | 'code' => $request->get('code'), 46 | 'scope' => $request->get('scope'), 47 | 'context' => $request->get('context'), 48 | ); 49 | 50 | $client = new Client(bcAuthService()); 51 | $req = $client->post('/oauth2/token', array(), $payload, array( 52 | 'exceptions' => false, 53 | )); 54 | $resp = $req->send(); 55 | 56 | if ($resp->getStatusCode() == 200) { 57 | $data = $resp->json(); 58 | list($context, $storeHash) = explode('/', $data['context'], 2); 59 | $key = getUserKey($storeHash, $data['user']['email']); 60 | 61 | // Store the user data and auth data in our key-value store so we can fetch it later and make requests. 62 | $redis->set($key, json_encode($data['user'], true)); 63 | $redis->set("stores/{$storeHash}/auth", json_encode($data)); 64 | 65 | return 'Hello ' . json_encode($data); 66 | } else { 67 | return 'Something went wrong... [' . $resp->getStatusCode() . '] ' . $resp->getBody(); 68 | } 69 | 70 | }); 71 | 72 | // Endpoint for removing users in a multi-user setup 73 | $app->get('/remove-user', function(Request $request) use ($app) { 74 | $data = verifySignedRequest($request->get('signed_payload')); 75 | if (empty($data)) { 76 | return 'Invalid signed_payload.'; 77 | } 78 | 79 | $key = getUserKey($data['store_hash'], $data['user']['email']); 80 | $redis = new Credis_Client('localhost'); 81 | $redis->del($key); 82 | return '[Remove User] '.$data['user']['email']; 83 | }); 84 | 85 | /** 86 | * GET /storefront/{storeHash}/customers/{jwtToken}/recently_purchased.html 87 | * Fetches the "Recently Purchased Products" HTML block and displays it in the frontend. 88 | */ 89 | $app->get('/storefront/{storeHash}/customers/{jwtToken}/recently_purchased.html', function ($storeHash, $jwtToken) use ($app) { 90 | $headers = ['Access-Control-Allow-Origin' => '*']; 91 | try { 92 | // First let's get the customer's ID from the token and confirm that they're who they say they are. 93 | $customerId = getCustomerIdFromToken($jwtToken); 94 | 95 | // Next let's initialize the BigCommerce API for the store requested so we can pull data from it. 96 | configureBCApi($storeHash); 97 | 98 | // Generate the recently purchased products HTML 99 | $recentlyPurchasedProductsHtml = getRecentlyPurchasedProductsHtml($storeHash, $customerId); 100 | 101 | // Now respond with the generated HTML 102 | $response = new Response($recentlyPurchasedProductsHtml, 200, $headers); 103 | } catch (Exception $e) { 104 | error_log("Error occurred while trying to get recently purchased items: {$e->getMessage()}"); 105 | $response = new Response("", 500, $headers); // Empty string here to make sure we don't display any errors in the storefront. 106 | } 107 | 108 | return $response; 109 | }); 110 | 111 | /** 112 | * Gets the HTML block that displays the recently purchased products for a store. 113 | * @param string $storeHash 114 | * @param string $customerId 115 | * @return string HTML content to display in the storefront 116 | */ 117 | function getRecentlyPurchasedProductsHtml($storeHash, $customerId) 118 | { 119 | $redis = new Credis_Client('localhost'); 120 | $cacheKey = "stores/{$storeHash}/customers/{$customerId}/recently_purchased_products.html"; 121 | $cacheLifetime = 60 * 5; // Set a 5 minute cache lifetime for this HTML block. 122 | 123 | // First let's see if we can find he HTML block in the cache so we don't have to reach out to BigCommerce's servers. 124 | $cachedContent = json_decode($redis->get($cacheKey)); 125 | if (!empty($cachedContent) && (int)$cachedContent->expiresAt > time()) { // Ensure the cache has not expired as well. 126 | return $cachedContent->content; 127 | } 128 | 129 | // Whelp looks like we couldn't find the HTML block in the cache, so we'll have to compile it ourselves. 130 | // First let's get all the customer's recently purchased products. 131 | $products = getRecentlyPurchasedProducts($customerId); 132 | 133 | // Render the template with the recently purchased products fetched from the BigCommerce server. 134 | $htmlContent = (new Handlebars())->render( 135 | file_get_contents('templates/recently_purchased.html'), 136 | ['products' => $products] 137 | ); 138 | $htmlContent = str_ireplace('http', 'https', $htmlContent); // Ensures we have HTTPS links, which for some reason we don't always get. 139 | 140 | // Save the HTML content in the cache so we don't have to reach out to BigCommece's server too often. 141 | $redis->set($cacheKey, json_encode([ 'content' => $htmlContent, 'expiresAt' => time() + $cacheLifetime])); 142 | 143 | return $htmlContent; 144 | } 145 | 146 | /** 147 | * Look at each of the customer's orders, and each of their order products and then pull down each product resource 148 | * that was purchased. 149 | * @param string $customerId ID of the customer that we want to retrieve the recently purchased products list for. 150 | * @return array An array of products from the BigCommerce API 151 | */ 152 | function getRecentlyPurchasedProducts($customerId) 153 | { 154 | $products = []; 155 | 156 | foreach(Bigcommerce::getOrders(['customer_id' => $customerId]) as $order) { 157 | foreach (Bigcommerce::getOrderProducts($order->id) as $orderProduct) { 158 | array_push($products, Bigcommerce::getProduct($orderProduct->product_id)); 159 | } 160 | } 161 | 162 | return $products; 163 | } 164 | 165 | /** 166 | * Configure the static BigCommerce API client with the authorized app's auth token, the client ID from the environment 167 | * and the store's hash as provided. 168 | * @param string $storeHash Store hash to point the BigCommece API to for outgoing requests. 169 | */ 170 | function configureBCApi($storeHash) 171 | { 172 | Bigcommerce::configure(array( 173 | 'client_id' => clientId(), 174 | 'auth_token' => getAuthToken($storeHash), 175 | 'store_hash' => $storeHash 176 | )); 177 | } 178 | 179 | /** 180 | * @param string $storeHash store's hash that we want the access token for 181 | * @return string the oauth Access (aka Auth) Token to use in API requests. 182 | */ 183 | function getAuthToken($storeHash) 184 | { 185 | $redis = new Credis_Client('localhost'); 186 | $authData = json_decode($redis->get("stores/{$storeHash}/auth")); 187 | return $authData->access_token; 188 | } 189 | 190 | /** 191 | * @param string $jwtToken customer's JWT token sent from the storefront. 192 | * @return string customer's ID decoded and verified 193 | */ 194 | function getCustomerIdFromToken($jwtToken) 195 | { 196 | $signedData = JWT::decode($jwtToken, clientSecret(), array('HS256', 'HS384', 'HS512', 'RS256')); 197 | return $signedData->customer->id; 198 | } 199 | 200 | /** 201 | * This is used by the `GET /load` endpoint to load the app in the BigCommerce control panel 202 | * @param string $signedRequest Pull signed data to verify it. 203 | * @return array|null null if bad request, array of data otherwise 204 | */ 205 | function verifySignedRequest($signedRequest) 206 | { 207 | list($encodedData, $encodedSignature) = explode('.', $signedRequest, 2); 208 | 209 | // decode the data 210 | $signature = base64_decode($encodedSignature); 211 | $jsonStr = base64_decode($encodedData); 212 | $data = json_decode($jsonStr, true); 213 | 214 | // confirm the signature 215 | $expectedSignature = hash_hmac('sha256', $jsonStr, clientSecret(), $raw = false); 216 | if (!hash_equals($expectedSignature, $signature)) { 217 | error_log('Bad signed request from BigCommerce!'); 218 | return null; 219 | } 220 | return $data; 221 | } 222 | 223 | /** 224 | * @return string Get the app's client ID from the environment vars 225 | */ 226 | function clientId() 227 | { 228 | $clientId = getenv('BC_CLIENT_ID'); 229 | return $clientId ?: ''; 230 | } 231 | 232 | /** 233 | * @return string Get the app's client secret from the environment vars 234 | */ 235 | function clientSecret() 236 | { 237 | $clientSecret = getenv('BC_CLIENT_SECRET'); 238 | return $clientSecret ?: ''; 239 | } 240 | 241 | /** 242 | * @return string Get the callback URL from the environment vars 243 | */ 244 | function callbackUrl() 245 | { 246 | $callbackUrl = getenv('BC_CALLBACK_URL'); 247 | return $callbackUrl ?: ''; 248 | } 249 | 250 | /** 251 | * @return string Get auth service URL from the environment vars 252 | */ 253 | function bcAuthService() 254 | { 255 | $bcAuthService = getenv('BC_AUTH_SERVICE'); 256 | return $bcAuthService ?: ''; 257 | } 258 | 259 | function getUserKey($storeHash, $email) 260 | { 261 | return "kitty.php:$storeHash:$email"; 262 | } 263 | 264 | $app->run(); 265 | -------------------------------------------------------------------------------- /templates/recently_purchased.html: -------------------------------------------------------------------------------- 1 |
2 |

Your Recently Purchased Products

3 |
4 | 30 |
31 |
​ 32 | --------------------------------------------------------------------------------