diff --git a/.gitignore b/.gitignore index 933c555..022d1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,16 @@ /.settings /.project /.buildpath +/.composer /satis.phar /vendor /satis.json /web/packages.json /web/include +/web/dist !/web/robots.txt /config.yml /cache/* /logs/* -/config/deploy.rb +/satis/* +/database.sqlite diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3879cba..0cb4648 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Contributing Guidelines This document outlines the recommended way to contribute to Packages. -> More information can be found in the [project documentation](http://docs.terramarlabs.com/packages/3.1). +> More information can be found in the [project documentation](http://docs.terramarlabs.com/packages/3.2). Getting started --------------- @@ -29,7 +29,7 @@ Other considerations * The master branch contains active development. * Create or a feature branch or don't-- whatever you prefer! -* The project follows PSR-4 and, somewhat loosely, PSR-2. +* The project follows PSR-1, -2, and -4. * Consider writing unit tests for your change. ### And don't be afraid to ask for help! diff --git a/README.md b/README.md index 32cd47c..d3abc47 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,130 @@ Packages [![Build Status](https://img.shields.io/travis/terramar-labs/packages/master.svg?style=flat-square)](https://travis-ci.org/terramar-labs/packages) -Packages is a PHP 5.6 and 7.x application providing an interface and tools for maintaining a private composer repository. Packages extends [Satis](https://github.com/composer/satis), adding a web frontend and useful management functionality like GitHub and GitLab integration. +Packages is a PHP 5.6 and 7.x application providing an interface and tools for maintaining a private [Composer](https://getcomposer.org) repository. Packages extends [Satis](https://github.com/composer/satis), adding a web frontend and useful management functionality like GitHub and GitLab integration. -Packages automatically registers GitLab and GitHub project web hooks to keep Satis up to date every time you push code. Packages also features a web management interface that allows for easy management of exposed packages and configured source control repositories. +Packages automatically registers GitLab and GitHub project webhooks to keep Satis up to date every time you push code. Packages also features a web management interface that allows for easy management of exposed packages and configured source control repositories. Packages version 3 works on a plugin based system based around source code repositories. Packages can trigger, with each code push, many automated tasks like documentation generation or code analysis. The simple event-based architecture allows easy creation of new automation tasks. -[View the docs online](http://docs.terramarlabs.com/packages/3.1) +[View the docs online](http://docs.terramarlabs.com/packages/3.2). + +Installation +------------ + +Pre-requisites: + * PHP 5.6 or later + * [Composer](https://getcomposer.org) + * Some database platform supported by [Doctrine 2](http://doctrine-project.org) (sqlite works great!) + * [Redis](https://redis.io/) + +Download the [latest release](https://github.com/terramar-labs/packages/releases/latest), or clone the repository. + +```bash +git clone git@github.com:terramar-labs/packages.git +``` + +### Install dependencies + +Switch to the project root directory and run `composer install`. + +```bash +cd packages +composer install +``` + +### Edit configuration + +Copy `config.yml.dist` to `config.yml` and edit as appropriate. + +```bash +cp config.yml.dist config.yml +vi config.yml +``` + +### Generate the database schema + +Packages uses Doctrine ORM to auto-generate the database schema for your configured platform. + +```bash +bin/console orm:schema-tool:create +``` + +Running the application +----------------------- + +Start PHP's built-in webserver to run the Packages web application with minimal effort. + +```bash +# Visit http://localhost:8080 to see the landing page. +php -S localhost:8080 -t web +``` + +### Start a Resque worker + +For fully-automatic integration with GitHub, GitLab, and your Satis repository, you must always have at least one Resque worker running. + +```bash +bin/console resque:worker:start +``` + +> For more information on Resque workers, check [the dedicated section](http://docs.terramarlabs.com/packages/3.2/managing-packages/resque). + + +Using the application +--------------------- + +Read the [usage and design documentation](http://docs.terramarlabs.com/packages/3.2/getting-started/usage) for an overview of Packages functionality. + + +### Development/debug mode + +Visit `index_dev.php` in your browser to view the site with the `dev` environment configuration. In this env, views and the service container are not cached, so changes made are immediately visible. + +### Customizing + +Check out the [Contributing Guide](CONTRIBUTING.md) for the recommended way to set up your development environment. + +Some tips: + +* Views are written using Twig and stored in `views/`. + * Views are cached in `prod` env; use `http://localhost:8080/index_dev.php` to develop. + * All pages inherit from `views/base.html.twig`, except for + * Public landing page views inherit from `views/Default/base.html.twig`. +* [Composer Components](http://robloach.github.io/component-installer/) are used to manage front-end dependencies. The respective `web/images/`, `web/js/bootstrap.min.js`, and such are symlinks pointing to the real files installed by Composer in `vendor/`. +* Check [the documentation](http://docs.terramarlabs.com/packages/3.2/getting-started/customizing) for additional information. + + +Docker support +-------------- + +Packages comes with an example `docker-compose.yml` that starts an nginx container and a Redis container, ready to get up and running quickly. + +Visit [the documentation](http://docs.terramarlabs.com/packages/3.2/getting-started/docker) to get started. + +Troubleshooting +--------------- + +1. `index_dev.php` contains a non-localhost block, comment it out when using Docker. +2. Manage Resque and Satis using the `bin/console` command-line utility. + * **Build the Satis `packages.json` file** with the `satis:build` command. + ```bash + bin/console satis:build + ``` + * **View queued Resque jobs in Redis** with the `resque:queue:list` command. + ```bash + bin/console resque:queue:list + ``` + * **View active Resque workers** with the `resque:worker:list` command. + ```bash + bin/console resque:worker:list + ``` + * **Start a Resque worker** with `resque:worker:start`. + ```bash + bin/console resque:worker:start + ``` +3. Check the Resque logs to see if Resque is working properly. + ```bash + tail -f logs/resque.log + ``` + \ No newline at end of file diff --git a/composer.json b/composer.json index 810cb6d..6cfc843 100644 --- a/composer.json +++ b/composer.json @@ -22,20 +22,32 @@ "composer/composer": "1.4.3", "sami/sami": "~1.4", "m4tthumphrey/php-gitlab-api": "~7.13", - "knplabs/github-api": "~1.4" + "knplabs/github-api": "~1.4", + "php-http/guzzle6-adapter": "^1.1", + "datatables/datatables": "^1.10", + "twbs/bootstrap": "3.3.7", + "fortawesome/font-awesome": "^4.7", + "components/jquery": "^3.2" + }, + "config": { + "platform": { + "php": "5.6" + }, + "component-dir": "vendor" }, "require-dev": { "phpunit/phpunit": "~3.7" }, "autoload": { "psr-4": { - "Terramar\\Packages\\": "src/" + "Terramar\\Packages\\": "src/", + "Composer\\": "lib/Composer/" } }, "extra": { "branch-alias": { "dev-master": "3.1-dev", - "dev-2-1-master": "2.1-dev" + "dev-3.2.x": "3.2-dev" } }, "minimum-stability": "beta", diff --git a/composer.lock b/composer.lock index 4a7dd66..2af682e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "306db3a15935a295f81e3cbe5c7a5467", + "content-hash": "822b1fc1c79bb5a4de8ed34bc50eb42e", "packages": [ { "name": "chrisboulton/php-resque", @@ -64,16 +64,16 @@ }, { "name": "colinmollenhour/credis", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/colinmollenhour/credis.git", - "reference": "e8e9b2081fb9b61097486dcaf8b6c11a7399da2d" + "reference": "049ccfb2c680e4dfa6adcfa97f2f29d086919abd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/e8e9b2081fb9b61097486dcaf8b6c11a7399da2d", - "reference": "e8e9b2081fb9b61097486dcaf8b6c11a7399da2d", + "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/049ccfb2c680e4dfa6adcfa97f2f29d086919abd", + "reference": "049ccfb2c680e4dfa6adcfa97f2f29d086919abd", "shasum": "" }, "require": { @@ -100,7 +100,49 @@ ], "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", "homepage": "https://github.com/colinmollenhour/credis", - "time": "2017-09-26T05:03:25+00:00" + "time": "2017-10-05T20:28:58+00:00" + }, + { + "name": "components/jquery", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/components/jquery.git", + "reference": "e5534d4ab8e80c159553d507e358ed20a806d3bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/components/jquery/zipball/e5534d4ab8e80c159553d507e358ed20a806d3bf", + "reference": "e5534d4ab8e80c159553d507e358ed20a806d3bf", + "shasum": "" + }, + "type": "component", + "extra": { + "component": { + "scripts": [ + "jquery.js" + ], + "files": [ + "jquery.min.js", + "jquery.min.map", + "jquery.slim.js", + "jquery.slim.min.js", + "jquery.slim.min.map" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "JS Foundation and other contributors" + } + ], + "description": "jQuery JavaScript Library", + "homepage": "http://jquery.com", + "time": "2017-03-21T07:01:49+00:00" }, { "name": "composer/ca-bundle", @@ -427,6 +469,29 @@ ], "time": "2017-04-03T19:08:52+00:00" }, + { + "name": "datatables/datatables", + "version": "1.10.16", + "source": { + "type": "git", + "url": "https://github.com/DataTables/DataTables.git", + "reference": "75a665f64f02982c0f4666b15a25c4670e5e6b18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DataTables/DataTables/zipball/75a665f64f02982c0f4666b15a25c4670e5e6b18", + "reference": "75a665f64f02982c0f4666b15a25c4670e5e6b18", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, which will add advanced interaction controls to any HTML table.", + "homepage": "http://www.datatables.net/", + "time": "2017-08-31T13:52:17+00:00" + }, { "name": "doctrine/annotations", "version": "v1.4.0", @@ -1101,24 +1166,72 @@ ], "time": "2017-09-18T06:50:20+00:00" }, + { + "name": "fortawesome/font-awesome", + "version": "v4.7.0", + "source": { + "type": "git", + "url": "https://github.com/FortAwesome/Font-Awesome.git", + "reference": "a8386aae19e200ddb0f6845b5feeee5eb7013687" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FortAwesome/Font-Awesome/zipball/a8386aae19e200ddb0f6845b5feeee5eb7013687", + "reference": "a8386aae19e200ddb0f6845b5feeee5eb7013687", + "shasum": "" + }, + "require-dev": { + "jekyll": "1.0.2", + "lessc": "1.4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.6.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OFL-1.1", + "MIT" + ], + "authors": [ + { + "name": "Dave Gandy", + "email": "dave@fontawesome.io", + "homepage": "http://twitter.com/davegandy", + "role": "Developer" + } + ], + "description": "The iconic font and CSS framework", + "homepage": "http://fontawesome.io/", + "keywords": [ + "FontAwesome", + "awesome", + "bootstrap", + "font", + "icon" + ], + "time": "2016-10-24T15:52:54+00:00" + }, { "name": "guzzle/guzzle", - "version": "v3.9.3", + "version": "v3.8.1", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle3.git", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", "shasum": "" }, "require": { "ext-curl": "*", "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" + "symfony/event-dispatcher": ">=2.1" }, "replace": { "guzzle/batch": "self.version", @@ -1145,21 +1258,18 @@ "guzzle/stream": "self.version" }, "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", + "doctrine/cache": "*", + "monolog/monolog": "1.*", "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.9-dev" + "dev-master": "3.8-dev" } }, "autoload": { @@ -1183,7 +1293,7 @@ "homepage": "https://github.com/guzzle/guzzle/contributors" } ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", "homepage": "http://guzzlephp.org/", "keywords": [ "client", @@ -1195,7 +1305,188 @@ "web service" ], "abandoned": "guzzlehttp/guzzle", - "time": "2015-03-18T18:23:50+00:00" + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-06-22T18:50:49+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" }, { "name": "ircmaxell/password-compat", @@ -1241,16 +1532,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "5.2.1", + "version": "5.2.4", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "429be236f296ca249d61c65649cdf2652f4a5e80" + "reference": "7ccb0e67ea8ace0f84c40900ca3c8a234467628c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/429be236f296ca249d61c65649cdf2652f4a5e80", - "reference": "429be236f296ca249d61c65649cdf2652f4a5e80", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/7ccb0e67ea8ace0f84c40900ca3c8a234467628c", + "reference": "7ccb0e67ea8ace0f84c40900ca3c8a234467628c", "shasum": "" }, "require": { @@ -1259,7 +1550,6 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.1", "json-schema/json-schema-test-suite": "1.2.0", - "phpdocumentor/phpdocumentor": "^2.7", "phpunit/phpunit": "^4.8.22" }, "bin": [ @@ -1304,7 +1594,7 @@ "json", "schema" ], - "time": "2017-05-16T21:06:09+00:00" + "time": "2017-10-04T20:57:36+00:00" }, { "name": "knplabs/github-api", @@ -1901,6 +2191,172 @@ ], "time": "2015-08-09T04:28:19+00:00" }, + { + "name": "php-http/guzzle6-adapter", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ], + "time": "2016-05-10T06:13:32+00:00" + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "time": "2016-08-31T08:30:17+00:00" + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2016-01-26T13:27:02+00:00" + }, { "name": "pimple/pimple", "version": "v1.0.2", @@ -1947,6 +2403,56 @@ ], "time": "2013-03-08T08:21:40+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, { "name": "psr/log", "version": "1.0.2", @@ -2194,16 +2700,16 @@ }, { "name": "symfony/config", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "0b8541d18507d10204a08384640ff6df3c739ebe" + "reference": "1dbeaa8e2db4b29159265867efff075ad961558c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/0b8541d18507d10204a08384640ff6df3c739ebe", - "reference": "0b8541d18507d10204a08384640ff6df3c739ebe", + "url": "https://api.github.com/repos/symfony/config/zipball/1dbeaa8e2db4b29159265867efff075ad961558c", + "reference": "1dbeaa8e2db4b29159265867efff075ad961558c", "shasum": "" }, "require": { @@ -2246,20 +2752,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-04-12T14:07:15+00:00" + "time": "2017-10-04T18:56:36+00:00" }, { "name": "symfony/console", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c0807a2ca978e64d8945d373a9221a5c35d1a253" + "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c0807a2ca978e64d8945d373a9221a5c35d1a253", - "reference": "c0807a2ca978e64d8945d373a9221a5c35d1a253", + "url": "https://api.github.com/repos/symfony/console/zipball/f81549d2c5fdee8d711c9ab3c7e7362353ea5853", + "reference": "f81549d2c5fdee8d711c9ab3c7e7362353ea5853", "shasum": "" }, "require": { @@ -2307,20 +2813,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-08-27T14:29:03+00:00" + "time": "2017-10-01T21:00:16+00:00" }, { "name": "symfony/debug", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "efc9656dcb227e1459905d5aa51e43dfec76e752" + "reference": "eaaec993ca5e8067e204b2ee653cdd142961f33e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/efc9656dcb227e1459905d5aa51e43dfec76e752", - "reference": "efc9656dcb227e1459905d5aa51e43dfec76e752", + "url": "https://api.github.com/repos/symfony/debug/zipball/eaaec993ca5e8067e204b2ee653cdd142961f33e", + "reference": "eaaec993ca5e8067e204b2ee653cdd142961f33e", "shasum": "" }, "require": { @@ -2364,20 +2870,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2017-08-27T14:29:03+00:00" + "time": "2017-10-01T21:00:16+00:00" }, { "name": "symfony/dependency-injection", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "fbeea992f0d30e3c400694c85f60c9bfd10454a3" + "reference": "2562562610dbdabbb98c6ceb60459a351811c734" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/fbeea992f0d30e3c400694c85f60c9bfd10454a3", - "reference": "fbeea992f0d30e3c400694c85f60c9bfd10454a3", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2562562610dbdabbb98c6ceb60459a351811c734", + "reference": "2562562610dbdabbb98c6ceb60459a351811c734", "shasum": "" }, "require": { @@ -2427,31 +2933,31 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-08-10T14:42:21+00:00" + "time": "2017-10-02T07:17:52+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.27", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d" + "reference": "54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d", - "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00", + "reference": "54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.9" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^2.0.5|~3.0.0", - "symfony/dependency-injection": "~2.6|~3.0.0", - "symfony/expression-language": "~2.6|~3.0.0", - "symfony/stopwatch": "~2.3|~3.0.0" + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" }, "suggest": { "symfony/dependency-injection": "", @@ -2460,7 +2966,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2487,20 +2993,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-06-02T07:47:27+00:00" + "time": "2016-07-19T10:44:15+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "714b1036010c354ae2b25d7f9ca27e14e265e9f2" + "reference": "5e3af878f144089faddd4060a48cadae4fc44dee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/714b1036010c354ae2b25d7f9ca27e14e265e9f2", - "reference": "714b1036010c354ae2b25d7f9ca27e14e265e9f2", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/5e3af878f144089faddd4060a48cadae4fc44dee", + "reference": "5e3af878f144089faddd4060a48cadae4fc44dee", "shasum": "" }, "require": { @@ -2536,20 +3042,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-07-11T07:12:11+00:00" + "time": "2017-10-02T08:46:46+00:00" }, { "name": "symfony/finder", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4f4e84811004e065a3bb5ceeb1d9aa592630f9ad" + "reference": "a945724b201f74d543e356f6059c930bb8d10c92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4f4e84811004e065a3bb5ceeb1d9aa592630f9ad", - "reference": "4f4e84811004e065a3bb5ceeb1d9aa592630f9ad", + "url": "https://api.github.com/repos/symfony/finder/zipball/a945724b201f74d543e356f6059c930bb8d10c92", + "reference": "a945724b201f74d543e356f6059c930bb8d10c92", "shasum": "" }, "require": { @@ -2585,7 +3091,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-06-01T20:52:29+00:00" + "time": "2017-10-01T21:00:16+00:00" }, { "name": "symfony/http-foundation", @@ -2642,16 +3148,16 @@ }, { "name": "symfony/http-kernel", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b07102898dcba281d8e91d24ea43872f897d6203" + "reference": "d912b76d7db324f7650da9d1132be78c5f7ceb93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b07102898dcba281d8e91d24ea43872f897d6203", - "reference": "b07102898dcba281d8e91d24ea43872f897d6203", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d912b76d7db324f7650da9d1132be78c5f7ceb93", + "reference": "d912b76d7db324f7650da9d1132be78c5f7ceb93", "shasum": "" }, "require": { @@ -2721,7 +3227,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2017-08-28T19:21:40+00:00" + "time": "2017-10-05T23:24:02+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2784,16 +3290,16 @@ }, { "name": "symfony/process", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8" + "reference": "26c9fb02bf06bd6b90f661a5bd17e510810d0176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8", - "reference": "57e52a0a6a80ea0aec4fc1b785a7920a95cb88a8", + "url": "https://api.github.com/repos/symfony/process/zipball/26c9fb02bf06bd6b90f661a5bd17e510810d0176", + "reference": "26c9fb02bf06bd6b90f661a5bd17e510810d0176", "shasum": "" }, "require": { @@ -2829,20 +3335,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-07-03T08:04:30+00:00" + "time": "2017-10-01T21:00:16+00:00" }, { "name": "symfony/templating", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/templating.git", - "reference": "3867ab005281fd8b32d68f47c0caaad017c26f52" + "reference": "ab3764461e860ce4ff16a509ecd93c44c1f5b0b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/templating/zipball/3867ab005281fd8b32d68f47c0caaad017c26f52", - "reference": "3867ab005281fd8b32d68f47c0caaad017c26f52", + "url": "https://api.github.com/repos/symfony/templating/zipball/ab3764461e860ce4ff16a509ecd93c44c1f5b0b1", + "reference": "ab3764461e860ce4ff16a509ecd93c44c1f5b0b1", "shasum": "" }, "require": { @@ -2884,20 +3390,20 @@ ], "description": "Symfony Templating Component", "homepage": "https://symfony.com", - "time": "2017-06-01T20:52:29+00:00" + "time": "2017-10-01T21:00:16+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.27", + "version": "v2.8.28", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5" + "reference": "842fb6df22180244b4c65935ce1a88d324e5ff9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5", - "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/842fb6df22180244b4c65935ce1a88d324e5ff9e", + "reference": "842fb6df22180244b4c65935ce1a88d324e5ff9e", "shasum": "" }, "require": { @@ -2933,7 +3439,58 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-06-01T20:52:29+00:00" + "time": "2017-10-05T14:38:30+00:00" + }, + { + "name": "twbs/bootstrap", + "version": "v3.3.7", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86", + "shasum": "" + }, + "replace": { + "twitter/bootstrap": "self.version" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jacob Thornton", + "email": "jacobthornton@gmail.com" + }, + { + "name": "Mark Otto", + "email": "markdotto@gmail.com" + } + ], + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "homepage": "http://getbootstrap.com", + "keywords": [ + "JS", + "css", + "framework", + "front-end", + "less", + "mobile-first", + "responsive", + "web" + ], + "time": "2016-07-25T15:51:55+00:00" }, { "name": "twig/twig", @@ -3494,5 +4051,8 @@ "ext-pcntl": "*", "ext-posix": "*" }, - "platform-dev": [] + "platform-dev": [], + "platform-overrides": { + "php": "5.6" + } } diff --git a/config.yml.dist b/config.yml.dist index 22e26f0..7da6d02 100644 --- a/config.yml.dist +++ b/config.yml.dist @@ -12,22 +12,39 @@ doctrine: database: # Any Doctrine driver driver: pdo_sqlite - + # Options path: %app.root_dir%/database.sqlite # host: 127.0.0.1 # user: root # password: # dbname: packages - + packages: - site_name: 'Private Composer Repository' - homepage: 'https://github.com/terramar-labs/packages' + # Defines the name used in the page titles and landing page. + name: 'Terramar Labs' + + # If set, the homepage will be linked to from the landing page. + homepage: 'https://github.com/terramar-labs/packages' + + # If set, the contact email is displayed on the landing page. + contact_email: 'contact@terramarlabs.com' + + # Needs to be set to generate a dist archive + base_path: 'https://localhost' + + # If set, will place a copy of every tagged package version in the web/dist folder + archive: true + + # If set, username and password will be required when attempting to access + # Satis-generated files. + secure_satis: false + resque: # Redis server host. - host: 'unix:///tmp/redis.sock' + host: 'redis://redis-master' # Format as either unix:///path/to/socket (note the 3 slashes) or redis://host - #host: 'redis://127.0.0.1' + #host: 'unix:///var/run/redis.sock' # If Redis is configured to require a password, you can pass it in the host: #host: 'redis://ignored:password@host' # Note that the username portion of the hostname is ignored and can be any value. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..deb3064 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '2' +services: + web: + image: webdevops/php-nginx:7.1 + volumes: + - .:/app + - ./docker/nginx/vhost.common.d/vhost.common.conf:/opt/docker/etc/nginx/vhost.common.d/10-location-root.conf + links: + - redis + environment: + - WEB_DOCUMENT_ROOT=/app/web + ports: + - "127.0.0.1:80:80" # Access Packages in your browser at http://127.0.0.1:80 + - "127.0.0.1:443:443" # Access Packages in your browser at https://127.0.0.1:443 + redis: + image: redis:3.2 + expose: + - "6379" diff --git a/docker/nginx/vhost.common.d/vhost.common.conf b/docker/nginx/vhost.common.d/vhost.common.conf new file mode 100644 index 0000000..1968c0b --- /dev/null +++ b/docker/nginx/vhost.common.d/vhost.common.conf @@ -0,0 +1,3 @@ +location / { + try_files $uri $uri/ /index_dev.php$is_args$args; +} \ No newline at end of file diff --git a/lib/Composer/LICENSE b/lib/Composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/lib/Composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/lib/Composer/NOTES.md b/lib/Composer/NOTES.md new file mode 100644 index 0000000..20778e3 --- /dev/null +++ b/lib/Composer/NOTES.md @@ -0,0 +1,116 @@ +# Explain yourself! + +This "monkey patch" is necessary to allow Packages to use multiple different +GitHub and GitLab auth tokens. Composer and Satis do not allow for this, and +it is not yet clear on how or if it will be implemented upstream. + +> Yes, the warnings after `composer install` and `composer update` are expected. + +The changes to the GitHubDriver and GitLabDriver are very small, so +maintaining these "forked" files shouldn't be that painful. The warning from +composer sure is annoying though. + + +# Details, please + +Packages 3.1 and earlier rely on `git` and having the user's environment +configured. In practice, this means having a valid private SSH key on +your server with access to every repository you need. + +With Packages 3.2, the github or gitlab auth token are loaded from their +respective `RemoteConfiguration` and inserted into the repository options in +the generated Satis configuration. This means that Packages no longer requires +that the user running the Packages application to have a bunch of private keys +different SSH keys configured, and adding a Remote no longer requires logging +in to the server to add such a key. + +It boils down to adding handling for two new keys in a repository: +`github-token` and `gitlab-token`. + +```json +{ + "repositories": [ + { + "type": "vcs", + "url": "git@github.com:somereally/neatproject.git", + "github-token": "123abcdef456", + }, + { + "type": "vcs", + "url": "git@gitlab.example.com:another/awesomeproject.git", + "gitlab-token": "xyz123", + } + ] +} +``` + +If these values exist, the modified `GitHubDriver` or `GitLabDriver` sets the +token on the Composer IO so the respective API can be used. And since this +is done for every repository, multiple different users on the same GitLab +instance can now easily share a Packages instance. + +Three lines in each file were all that were necessary to accomplish the task: + +```php +repoConfig['github-token'])) { + $this->io->setAuthentication("github.com", $this->repoConfig['github-token']); + } + + // ... + } + + // ... +} +``` + +```php +repoConfig['gitlab-token'])) { + $this->io->setAuthentication($this->originUrl, $this->repoConfig['gitlab-token'], 'private-token'); + } + + // ... + } + + // ... +} +``` + +The final piece of the puzzle is setting up a new PSR-0 root in `composer.json`. + +```json + // ... + "autoload": { + // ... + "psr-0": { + "Composer\\": "lib/Composer/" + } + }, + // ... +} +``` \ No newline at end of file diff --git a/lib/Composer/Repository/Vcs/GitHubDriver.php b/lib/Composer/Repository/Vcs/GitHubDriver.php new file mode 100644 index 0000000..88d1576 --- /dev/null +++ b/lib/Composer/Repository/Vcs/GitHubDriver.php @@ -0,0 +1,531 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Downloader\TransportException; +use Composer\Json\JsonFile; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Util\GitHub; + +/** + * @author Jordi Boggiano + */ +class GitHubDriver extends VcsDriver +{ + protected $cache; + protected $owner; + protected $repository; + protected $tags; + protected $branches; + protected $rootIdentifier; + protected $repoData; + protected $hasIssues; + protected $infoCache = array(); + protected $isPrivate = false; + + /** + * Git Driver + * + * @var GitDriver + */ + protected $gitDriver; + + /** + * {@inheritDoc} + */ + public function initialize() + { + preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match); + $this->owner = $match[3]; + $this->repository = $match[4]; + $this->originUrl = !empty($match[1]) ? $match[1] : $match[2]; + if ($this->originUrl === 'www.github.com') { + $this->originUrl = 'github.com'; + } + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); + + if (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) { + $this->setupGitDriver($this->url); + + return; + } + + $this->fetchRootIdentifier(); + } + + public function getRepositoryUrl() + { + return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository; + } + + /** + * {@inheritDoc} + */ + public function getRootIdentifier() + { + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->rootIdentifier; + } + + /** + * {@inheritDoc} + */ + public function getUrl() + { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } + + return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + + /** + * {@inheritDoc} + */ + protected function getApiUrl() + { + if ('github.com' === $this->originUrl) { + $apiUrl = 'api.github.com'; + } else { + $apiUrl = $this->originUrl . '/api/v3'; + } + + return 'https://' . $apiUrl; + } + + /** + * {@inheritDoc} + */ + public function getSource($identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } + if ($this->isPrivate) { + // Private GitHub repositories should be accessed using the + // SSH version of the URL. + $url = $this->generateSshUrl(); + } else { + $url = $this->getUrl(); + } + + return array('type' => 'git', 'url' => $url, 'reference' => $identifier); + } + + /** + * {@inheritDoc} + */ + public function getDist($identifier) + { + $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier; + + return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); + } + + /** + * {@inheritDoc} + */ + public function getComposerInformation($identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getComposerInformation($identifier); + } + + if (!isset($this->infoCache[$identifier])) { + if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) { + return $this->infoCache[$identifier] = JsonFile::parseJson($res); + } + + $composer = $this->getBaseComposerInformation($identifier); + if ($composer) { + + // specials for github + if (!isset($composer['support']['source'])) { + $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier; + $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label); + } + if (!isset($composer['support']['issues']) && $this->hasIssues) { + $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository); + } + } + + if ($this->shouldCache($identifier)) { + $this->cache->write($identifier, json_encode($composer)); + } + + $this->infoCache[$identifier] = $composer; + } + + return $this->infoCache[$identifier]; + } + + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + $notFoundRetries = 2; + while ($notFoundRetries) { + try { + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier); + $resource = JsonFile::parseJson($this->getContents($resource)); + if (empty($resource['content']) || $resource['encoding'] !== 'base64' || !($content = base64_decode($resource['content']))) { + throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier); + } + + return $content; + } catch (TransportException $e) { + if (404 !== $e->getCode()) { + throw $e; + } + + // TODO should be removed when possible + // retry fetching if github returns a 404 since they happen randomly + $notFoundRetries--; + + return null; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier); + $commit = JsonFile::parseJson($this->getContents($resource), $resource); + + return new \DateTime($commit['commit']['committer']['date']); + } + + /** + * {@inheritDoc} + */ + public function getTags() + { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } + if (null === $this->tags) { + $this->tags = array(); + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100'; + + do { + $tagsData = JsonFile::parseJson($this->getContents($resource), $resource); + foreach ($tagsData as $tag) { + $this->tags[$tag['name']] = $tag['commit']['sha']; + } + + $resource = $this->getNextPage(); + } while ($resource); + } + + return $this->tags; + } + + /** + * {@inheritDoc} + */ + public function getBranches() + { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } + if (null === $this->branches) { + $this->branches = array(); + $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100'; + + $branchBlacklist = array('gh-pages'); + + do { + $branchData = JsonFile::parseJson($this->getContents($resource), $resource); + foreach ($branchData as $branch) { + $name = substr($branch['ref'], 11); + if (!in_array($name, $branchBlacklist)) { + $this->branches[$name] = $branch['object']['sha']; + } + } + + $resource = $this->getNextPage(); + } while ($resource); + } + + return $this->branches; + } + + /** + * {@inheritDoc} + */ + public static function supports(IOInterface $io, Config $config, $url, $deep = false) + { + if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) { + return false; + } + + $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3]; + if (!in_array(preg_replace('{^www\.}i', '', $originUrl), $config->get('github-domains'))) { + return false; + } + + if (!extension_loaded('openssl')) { + $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } + + /** + * Gives back the loaded /repos// result + * + * @return array|null + */ + public function getRepoData() + { + $this->fetchRootIdentifier(); + + return $this->repoData; + } + + /** + * Generate an SSH URL + * + * @return string + */ + protected function generateSshUrl() + { + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; + } + + /** + * {@inheritDoc} + */ + protected function getContents($url, $fetchingRepoData = false) + { + if (isset($this->repoConfig['github-token'])) { + $this->io->setAuthentication("github.com", $this->repoConfig['github-token']); + } + + try { + return parent::getContents($url); + } catch (TransportException $e) { + $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem); + + switch ($e->getCode()) { + case 401: + case 404: + // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 + if (!$fetchingRepoData) { + throw $e; + } + + if ($gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive()) { + return $this->attemptCloneFallback(); + } + + $scopesIssued = array(); + $scopesNeeded = array(); + if ($headers = $e->getHeaders()) { + if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-OAuth-Scopes')) { + $scopesIssued = explode(' ', $scopes); + } + if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { + $scopesNeeded = explode(' ', $scopes); + } + } + $scopesFailed = array_diff($scopesNeeded, $scopesIssued); + if (!$headers || count($scopesFailed)) { + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata ('.$this->url.')'); + } + + return parent::getContents($url); + + case 403: + if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + return $this->attemptCloneFallback(); + } + + $rateLimited = false; + foreach ($e->getHeaders() as $header) { + if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) { + $rateLimited = true; + } + } + + if (!$this->io->hasAuthentication($this->originUrl)) { + if (!$this->io->isInteractive()) { + $this->io->writeError('GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit'); + throw $e; + } + + $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit ('.$this->url.')'); + + return parent::getContents($url); + } + + if ($rateLimited) { + $rateLimit = $this->getRateLimit($e->getHeaders()); + $this->io->writeError(sprintf( + 'GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests', + $rateLimit['limit'], + $rateLimit['reset'] + )); + } + + throw $e; + + default: + throw $e; + } + } finally { + + } + } + + /** + * Extract ratelimit from response. + * + * @param array $headers Headers from Composer\Downloader\TransportException. + * + * @return array Associative array with the keys limit and reset. + */ + protected function getRateLimit(array $headers) + { + $rateLimit = array( + 'limit' => '?', + 'reset' => '?', + ); + + foreach ($headers as $header) { + $header = trim($header); + if (false === strpos($header, 'X-RateLimit-')) { + continue; + } + list($type, $value) = explode(':', $header, 2); + switch ($type) { + case 'X-RateLimit-Limit': + $rateLimit['limit'] = (int) trim($value); + break; + case 'X-RateLimit-Reset': + $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value)); + break; + } + } + + return $rateLimit; + } + + /** + * Fetch root identifier from GitHub + * + * @throws TransportException + */ + protected function fetchRootIdentifier() + { + if ($this->repoData) { + return; + } + + $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository; + + $this->repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl); + if (null === $this->repoData && null !== $this->gitDriver) { + return; + } + + $this->owner = $this->repoData['owner']['login']; + $this->repository = $this->repoData['name']; + + $this->isPrivate = !empty($this->repoData['private']); + if (isset($this->repoData['default_branch'])) { + $this->rootIdentifier = $this->repoData['default_branch']; + } elseif (isset($this->repoData['master_branch'])) { + $this->rootIdentifier = $this->repoData['master_branch']; + } else { + $this->rootIdentifier = 'master'; + } + $this->hasIssues = !empty($this->repoData['has_issues']); + } + + protected function attemptCloneFallback() + { + $this->isPrivate = true; + + try { + // If this repository may be private (hard to say for sure, + // GitHub returns 404 for private repositories) and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($this->generateSshUrl()); + + return; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials'); + throw $e; + } + } + + protected function setupGitDriver($url) + { + $this->gitDriver = new GitDriver( + array('url' => $url), + $this->io, + $this->config, + $this->process, + $this->remoteFilesystem + ); + $this->gitDriver->initialize(); + } + + protected function getNextPage() + { + $headers = $this->remoteFilesystem->getLastHeaders(); + foreach ($headers as $header) { + if (substr($header, 0, 5) === 'Link:') { + $links = explode(',', substr($header, 5)); + foreach ($links as $link) { + if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) { + return $match[1]; + } + } + } + } + } +} diff --git a/lib/Composer/Repository/Vcs/GitLabDriver.php b/lib/Composer/Repository/Vcs/GitLabDriver.php new file mode 100644 index 0000000..f74900d --- /dev/null +++ b/lib/Composer/Repository/Vcs/GitLabDriver.php @@ -0,0 +1,442 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository\Vcs; + +use Composer\Config; +use Composer\Cache; +use Composer\IO\IOInterface; +use Composer\Json\JsonFile; +use Composer\Downloader\TransportException; +use Composer\Util\RemoteFilesystem; +use Composer\Util\GitLab; + +/** + * Driver for GitLab API, use the Git driver for local checkouts. + * + * @author Henrik Bjørnskov + * @author Jérôme Tamarelle + */ +class GitLabDriver extends VcsDriver +{ + private $scheme; + private $owner; + private $repository; + + /** + * @var array Project data returned by GitLab API + */ + private $project; + + /** + * @var array Keeps commits returned by GitLab API + */ + private $commits = array(); + + /** + * @var array List of tag => reference + */ + private $tags; + + /** + * @var array List of branch => reference + */ + private $branches; + + /** + * Git Driver + * + * @var GitDriver + */ + protected $gitDriver; + + /** + * Defaults to true unless we can make sure it is public + * + * @var bool defines whether the repo is private or not + */ + private $isPrivate = true; + + const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)/|git@(?P[^:]+):)(?P[^/]+)/(?P[^/]+?)(?:\.git|/)?$#'; + + /** + * Extracts information from the repository url. + * + * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. + * + * {@inheritDoc} + */ + public function initialize() + { + if (!preg_match(self::URL_REGEX, $this->url, $match)) { + throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.'); + } + + $this->scheme = !empty($match['scheme']) ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https'); + $this->originUrl = !empty($match['domain']) ? $match['domain'] : $match['domain2']; + $this->owner = $match['owner']; + $this->repository = preg_replace('#(\.git)$#', '', $match['repo']); + + $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository); + + $this->fetchProject(); + } + + /** + * Updates the RemoteFilesystem instance. + * Mainly useful for tests. + * + * @internal + */ + public function setRemoteFilesystem(RemoteFilesystem $remoteFilesystem) + { + $this->remoteFilesystem = $remoteFilesystem; + } + + /** + * {@inheritdoc} + */ + public function getFileContent($file, $identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getFileContent($file, $identifier); + } + + // Convert the root identifier to a cachable commit id + if (!preg_match('{[a-f0-9]{40}}i', $identifier)) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + + $resource = $this->getApiUrl().'/repository/blobs/'.$identifier.'?filepath=' . $file; + + try { + $content = $this->getContents($resource); + } catch (TransportException $e) { + if ($e->getCode() !== 404) { + throw $e; + } + + return null; + } + + return $content; + } + + /** + * {@inheritdoc} + */ + public function getChangeDate($identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getChangeDate($identifier); + } + + if (isset($this->commits[$identifier])) { + return new \DateTime($this->commits[$identifier]['committed_date']); + } + + return new \DateTime(); + } + + /** + * {@inheritDoc} + */ + public function getRepositoryUrl() + { + return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo']; + } + + /** + * {@inheritDoc} + */ + public function getUrl() + { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } + + return $this->project['web_url']; + } + + /** + * {@inheritDoc} + */ + public function getDist($identifier) + { + $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier; + + return array('type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''); + } + + /** + * {@inheritDoc} + */ + public function getSource($identifier) + { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } + + return array('type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier); + } + + /** + * {@inheritDoc} + */ + public function getRootIdentifier() + { + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); + } + + return $this->project['default_branch']; + } + + /** + * {@inheritDoc} + */ + public function getBranches() + { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } + + if (!$this->branches) { + $this->branches = $this->getReferences('branches'); + } + + return $this->branches; + } + + /** + * {@inheritDoc} + */ + public function getTags() + { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } + + if (!$this->tags) { + $this->tags = $this->getReferences('tags'); + } + + return $this->tags; + } + + /** + * @return string Base URL for GitLab API v3 + */ + public function getApiUrl() + { + return $this->scheme.'://'.$this->originUrl.'/api/v3/projects/'.$this->urlEncodeAll($this->owner).'%2F'.$this->urlEncodeAll($this->repository); + } + + /** + * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.` + * + * @param string $string + * @return string + */ + private function urlEncodeAll($string) + { + $encoded = ''; + for ($i = 0; isset($string[$i]); $i++) { + $character = $string[$i]; + if (!ctype_alnum($character) && !in_array($character, array('-', '_'), true)) { + $character = '%' . sprintf('%02X', ord($character)); + } + $encoded .= $character; + } + + return $encoded; + } + + /** + * @param string $type + * + * @return string[] where keys are named references like tags or branches and the value a sha + */ + protected function getReferences($type) + { + $resource = $this->getApiUrl().'/repository/'.$type; + + $data = JsonFile::parseJson($this->getContents($resource), $resource); + + $references = array(); + + foreach ($data as $datum) { + $references[$datum['name']] = $datum['commit']['id']; + + // Keep the last commit date of a reference to avoid + // unnecessary API call when retrieving the composer file. + $this->commits[$datum['commit']['id']] = $datum['commit']; + } + + return $references; + } + + protected function fetchProject() + { + // we need to fetch the default branch from the api + $resource = $this->getApiUrl(); + $this->project = JsonFile::parseJson($this->getContents($resource, true), $resource); + $this->isPrivate = !$this->project['public']; + } + + protected function attemptCloneFallback() + { + try { + if ($this->isPrivate === false) { + $url = $this->generatePublicUrl(); + } else { + $url = $this->generateSshUrl(); + } + + // If this repository may be private and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->setupGitDriver($url); + + return; + } catch (\RuntimeException $e) { + $this->gitDriver = null; + + $this->io->writeError('Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials'); + throw $e; + } + } + + /** + * Generate an SSH URL + * + * @return string + */ + protected function generateSshUrl() + { + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; + } + + protected function generatePublicUrl() + { + return $this->scheme . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + + protected function setupGitDriver($url) + { + $this->gitDriver = new GitDriver( + array('url' => $url), + $this->io, + $this->config, + $this->process, + $this->remoteFilesystem + ); + $this->gitDriver->initialize(); + } + + /** + * {@inheritDoc} + */ + protected function getContents($url, $fetchingRepoData = false) + { + if (isset($this->repoConfig['gitlab-token'])) { + $this->io->setAuthentication($this->originUrl, $this->repoConfig['gitlab-token'], 'private-token'); + } + + try { + $res = parent::getContents($url); + + if ($fetchingRepoData) { + $json = JsonFile::parseJson($res, $url); + + // force auth as the unauthenticated version of the API is broken + if (!isset($json['default_branch'])) { + if (!empty($json['id'])) { + $this->isPrivate = false; + } + + throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401); + } + } + + return $res; + } catch (TransportException $e) { + $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->remoteFilesystem); + + switch ($e->getCode()) { + case 401: + case 404: + // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404 + if (!$fetchingRepoData) { + throw $e; + } + + if ($gitLabUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive()) { + return $this->attemptCloneFallback(); + } + $this->io->writeError('Failed to download ' . $this->owner . '/' . $this->repository . ':' . $e->getMessage() . ''); + $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata ('.$this->url.')'); + + return parent::getContents($url); + + case 403: + if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) { + return parent::getContents($url); + } + + if (!$this->io->isInteractive() && $fetchingRepoData) { + return $this->attemptCloneFallback(); + } + + throw $e; + + default: + throw $e; + } + } + } + + /** + * Uses the config `gitlab-domains` to see if the driver supports the url for the + * repository given. + * + * {@inheritDoc} + */ + public static function supports(IOInterface $io, Config $config, $url, $deep = false) + { + if (!preg_match(self::URL_REGEX, $url, $match)) { + return false; + } + + $scheme = !empty($match['scheme']) ? $match['scheme'] : null; + $originUrl = !empty($match['domain']) ? $match['domain'] : $match['domain2']; + + if (!in_array($originUrl, (array) $config->get('gitlab-domains'))) { + return false; + } + + if ('https' === $scheme && !extension_loaded('openssl')) { + $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE); + + return false; + } + + return true; + } +} diff --git a/satis/.gitkeep b/satis/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Application.php b/src/Application.php index 9408075..401f9ea 100644 --- a/src/Application.php +++ b/src/Application.php @@ -18,18 +18,18 @@ use Symfony\Component\Yaml\Yaml; use Terramar\Packages\DependencyInjection\PackagesExtension; use Terramar\Packages\Plugin\CloneProject\Plugin as CloneProjectPlugin; +use Terramar\Packages\Plugin\GitHub\Plugin as GitHubPlugin; use Terramar\Packages\Plugin\GitLab\Plugin as GitLabPlugin; +use Terramar\Packages\Plugin\PluginInterface; use Terramar\Packages\Plugin\Sami\Plugin as SamiPlugin; use Terramar\Packages\Plugin\Satis\Plugin as SatisPlugin; -use Terramar\Packages\Plugin\GitHub\Plugin as GitHubPlugin; -use Terramar\Packages\Plugin\PluginInterface; class Application extends BaseApplication { /** * @var array|PluginInterface[] */ - private $plugins = array(); + private $plugins = []; /** * Register default extensions. @@ -40,32 +40,30 @@ protected function registerDefaultExtensions() $this->registerDefaultPlugins(); - $config = Yaml::parse(file_get_contents($this->getRootDir().'/config.yml')); - $security = isset($config['security']) ? $config['security'] : array(); - $doctrine = isset($config['doctrine']) ? $config['doctrine'] : array(); - $resque = isset($config['packages']) - ? (isset($config['packages']['resque']) ? $config['packages']['resque'] : null) - : null; + $config = Yaml::parse(file_get_contents($this->getRootDir() . '/config.yml')); + $security = isset($config['security']) ? $config['security'] : []; + $doctrine = isset($config['doctrine']) ? $config['doctrine'] : []; + $packages = isset($config['packages']) ? $config['packages'] : []; + if (!isset($packages['resque'])) { + $packages['resque'] = []; + } - $this->appendExtension(new PackagesExtension($this->plugins, array( - 'output_dir' => $this->getRootDir().'/web', - 'resque' => $resque, - ))); + $this->appendExtension(new PackagesExtension($this->plugins, $packages)); $this->appendExtension(new DoctrineOrmExtension($doctrine)); $this->appendExtension(new SessionExtension()); $this->appendExtension(new TemplatingExtension()); $this->appendExtension(new TwigExtension()); - $this->appendExtension(new SecurityExtension(array( - 'authenticator' => array( - 'type' => 'username', - 'username' => isset($security['username']) ? $security['username'] : null, - 'password' => isset($security['password']) - ? password_hash($security['password'], PASSWORD_DEFAULT) - : null, - ), - 'firewall' => '^/manage', - 'success_path' => '/manage', - ))); + $this->appendExtension(new SecurityExtension([ + 'authenticator' => [ + 'type' => 'username', + 'username' => isset($security['username']) ? $security['username'] : null, + 'password' => isset($security['password']) + ? password_hash($security['password'], PASSWORD_DEFAULT) + : null, + ], + 'firewall' => '^/manage', + 'success_path' => '/manage', + ])); } /** diff --git a/src/Console/Application.php b/src/Console/Application.php index d2b32ab..7b1f157 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -9,18 +9,20 @@ namespace Terramar\Packages\Console; +use Composer\Composer; +use Composer\Factory; +use Composer\IO\ConsoleIO; +use Composer\IO\IOInterface; +use Composer\Util\ErrorHandler; use Doctrine\ORM\Tools\Console\ConsoleRunner; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\QuestionHelper; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Terramar\Packages\Application as AppKernel; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Composer\IO\ConsoleIO; -use Composer\Factory; -use Composer\Util\ErrorHandler; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Terramar\Packages\Application as AppKernel; use Terramar\Packages\Version; /** @@ -28,7 +30,9 @@ */ class Application extends BaseApplication { + /** @var IOInterface */ protected $io; + /** @var Composer */ protected $composer; /** @@ -58,29 +62,12 @@ public function doRun(InputInterface $input, OutputInterface $output) return parent::doRun($input, $output); } - /** - * @return Composer - */ - public function getComposer($required = true, $config = null) - { - if (null === $this->composer) { - try { - $this->composer = Factory::create($this->io, $config); - } catch (\InvalidArgumentException $e) { - $this->io->write($e->getMessage()); - exit(1); - } - } - - return $this->composer; - } - /** * Initializes all the composer commands. */ protected function registerCommands() { - $this->addCommands(array( + $this->addCommands([ // Resque Commands new \Terramar\Packages\Console\Command\Worker\StartCommand(), new \Terramar\Packages\Console\Command\Worker\ListCommand(), @@ -117,7 +104,7 @@ protected function registerCommands() new \Doctrine\DBAL\Migrations\Tools\Console\Command\MigrateCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\StatusCommand(), new \Doctrine\DBAL\Migrations\Tools\Console\Command\VersionCommand(), - )); + ]); $registry = $this->app->get('packages.command_registry'); foreach ($registry->getCommands() as $commandClass) { $this->add(new $commandClass()); @@ -144,6 +131,31 @@ public function add(Command $command) return parent::add($command); } + /** + * @return Composer + */ + public function getComposer($required = true, $config = null) + { + if (null === $this->composer) { + try { + $this->composer = Factory::create($this->io, $config); + } catch (\InvalidArgumentException $e) { + $this->io->write($e->getMessage()); + exit(1); + } + } + + return $this->composer; + } + + /** + * @return IOInterface + */ + public function getIO() + { + return $this->io; + } + /** * @return array */ diff --git a/src/Console/Command/Queue/ClearCommand.php b/src/Console/Command/Queue/ClearCommand.php index 30936de..3b8d14c 100755 --- a/src/Console/Command/Queue/ClearCommand.php +++ b/src/Console/Command/Queue/ClearCommand.php @@ -35,7 +35,8 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($jobs <= 0) { $output->writeln(sprintf('Queue "%s" is empty', $queue)); } else { - $output->writeln(sprintf('Removed %d %s from queue "%s"', $jobs, count($jobs) !== 1 ? 'jobs' : 'job', $queue)); + $output->writeln(sprintf('Removed %d %s from queue "%s"', $jobs, count($jobs) !== 1 ? 'jobs' : 'job', + $queue)); } $output->writeln(''); diff --git a/src/Console/Command/Worker/ListCommand.php b/src/Console/Command/Worker/ListCommand.php index acb2e0b..b66ba8c 100644 --- a/src/Console/Command/Worker/ListCommand.php +++ b/src/Console/Command/Worker/ListCommand.php @@ -31,10 +31,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $workers = \Resque_Worker::all(); if (count($workers) <= 0) { - $output->writeln(array( + $output->writeln([ 'No workers running', '', - )); + ]); return 0; } @@ -44,34 +44,34 @@ protected function execute(InputInterface $input, OutputInterface $output) $queueFilter = false; } - $workerOutput = array(); + $workerOutput = []; $longestName = 0; foreach ($workers as $worker) { - $queues = ($worker->queues(true) ?: array('*')); + $queues = ($worker->queues(true) ?: ['*']); if ($queueFilter) { if (!in_array('*', $queues) && !in_array($queueFilter, $queues)) { continue; } } - $name = (string) $worker; + $name = (string)$worker; $job = ($job = $worker->job()) - ? 'Processing '.json_encode($job) + ? 'Processing ' . json_encode($job) : 'Waiting for job'; if (strlen($job) > 20) { - $job = substr($job, 0, 20).'...'; + $job = substr($job, 0, 20) . '...'; } - $workerOutput[] = array($name, $job); + $workerOutput[] = [$name, $job]; if (($thisLength = strlen($name)) > $longestName) { $longestName = $thisLength; } } - $output->writeln(sprintf('%-'.$longestName."s\t%s", 'Worker ID', 'Current Job')); - $loopFormat = '%-'.$longestName."s\t%s"; + $output->writeln(sprintf('%-' . $longestName . "s\t%s", 'Worker ID', 'Current Job')); + $loopFormat = '%-' . $longestName . "s\t%s"; foreach ($workerOutput as $worker) { $output->writeln(sprintf($loopFormat, $worker[0], $worker[1])); } diff --git a/src/Console/Command/Worker/StartCommand.php b/src/Console/Command/Worker/StartCommand.php index 0ac9eb0..6516216 100755 --- a/src/Console/Command/Worker/StartCommand.php +++ b/src/Console/Command/Worker/StartCommand.php @@ -9,12 +9,12 @@ namespace Terramar\Packages\Console\Command\Worker; -use Symfony\Component\Process\PhpExecutableFinder; -use Symfony\Component\Process\Process; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; use Terramar\Packages\Console\Command\ContainerAwareCommand; use Terramar\Packages\Helper\ResqueHelper; @@ -27,42 +27,43 @@ protected function configure() ->setDescription('Start a resque worker') ->addArgument('queues', InputArgument::OPTIONAL, 'Queue names (separate using comma)', '*') ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many workers to fork', 1) - ->addOption('interval', 'i', InputOption::VALUE_REQUIRED, 'How often to check for new jobs across the queues', 5) + ->addOption('interval', 'i', InputOption::VALUE_REQUIRED, + 'How often to check for new jobs across the queues', 5) ->addOption('foreground', 'f', InputOption::VALUE_NONE, 'Should the worker run in foreground') - ->addOption('memory-limit', 'm', InputOption::VALUE_REQUIRED, 'Force cli memory_limit (expressed in Mbytes)') - ; + ->addOption('memory-limit', 'm', InputOption::VALUE_REQUIRED, + 'Force cli memory_limit (expressed in Mbytes)'); } protected function execute(InputInterface $input, OutputInterface $output) { - $env = array( - 'QUEUE' => $input->getArgument('queues'), - 'VERBOSE' => $input->getOption('verbose'), - 'COUNT' => $input->getOption('count'), + $env = [ + 'QUEUE' => $input->getArgument('queues'), + 'VERBOSE' => $input->getOption('verbose'), + 'COUNT' => $input->getOption('count'), 'INTERVAL' => $input->getOption('interval'), - 'PREFIX' => 'resque:', - ); + 'PREFIX' => 'resque:', + ]; $resqueConfig = ResqueHelper::autoConfigure($this->container); $env['REDIS_BACKEND'] = $resqueConfig['backend']; $env['REDIS_BACKEND_DB'] = $resqueConfig['database']; $opt = ''; - if (0 !== $m = (int) $input->getOption('memory-limit')) { + if (0 !== $m = (int)$input->getOption('memory-limit')) { $opt = sprintf('-d memory_limit=%dM', $m); } - $workerCommand = strtr('%bin% %opt% %dir%/bin/resque', array( + $workerCommand = strtr('%bin% %opt% %dir%/bin/resque', [ '%bin%' => $this->getPhpBinary(), '%opt%' => $opt, '%dir%' => $this->container->getParameter('app.root_dir'), - )); + ]); if (!$input->getOption('foreground')) { - $workerCommand = strtr('nohup %cmd% > %logs_dir%/resque.log 2>&1 & echo $!', array( - '%cmd%' => $workerCommand, + $workerCommand = strtr('nohup %cmd% > %logs_dir%/resque.log 2>&1 & echo $!', [ + '%cmd%' => $workerCommand, '%logs_dir%' => $this->container->getParameter('app.log_dir'), - )); + ]); } // In windows: When you pass an environment to CMD it replaces the old environment @@ -70,7 +71,7 @@ protected function execute(InputInterface $input, OutputInterface $output) // this is a workaround where we add the vars to the existing environment. if (defined('PHP_WINDOWS_VERSION_BUILD')) { foreach ($env as $key => $value) { - putenv($key.'='.$value); + putenv($key . '=' . $value); } $env = null; } diff --git a/src/Console/Command/Worker/StopCommand.php b/src/Console/Command/Worker/StopCommand.php index 5c67160..278e9a7 100755 --- a/src/Console/Command/Worker/StopCommand.php +++ b/src/Console/Command/Worker/StopCommand.php @@ -9,11 +9,11 @@ namespace Terramar\Packages\Console\Command\Worker; -use Terramar\Packages\Console\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Terramar\Packages\Console\Command\ContainerAwareCommand; use Terramar\Packages\Helper\ResqueHelper; class StopCommand extends ContainerAwareCommand @@ -43,14 +43,14 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $workers = $worker ? array($worker) : array(); + $workers = $worker ? [$worker] : []; } if (count($workers) <= 0) { - $output->writeln(array( - 'No workers running', - '', - )); + $output->writeln([ + 'No workers running', + '', + ]); return; } @@ -58,7 +58,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $signal = $input->getOption('force') ? SIGTERM : SIGQUIT; foreach ($workers as $worker) { $output->writeln(sprintf('%s %s...', $signal === SIGTERM ? 'Force stopping' : 'Stopping', $worker)); - list(, $pid) = explode(':', (string) $worker); + list(, $pid) = explode(':', (string)$worker); posix_kill($pid, $signal); } diff --git a/src/Console/CommandRegistry.php b/src/Console/CommandRegistry.php index b62ff1f..c5d9045 100644 --- a/src/Console/CommandRegistry.php +++ b/src/Console/CommandRegistry.php @@ -11,7 +11,7 @@ class CommandRegistry { - private $commands = array(); + private $commands = []; public function addCommand($className) { diff --git a/src/Controller/ContainerAwareController.php b/src/Controller/ContainerAwareController.php index 2951815..c7bcee4 100644 --- a/src/Controller/ContainerAwareController.php +++ b/src/Controller/ContainerAwareController.php @@ -14,16 +14,16 @@ abstract class ContainerAwareController implements ContainerAwareInterface { - /** - * @var ContainerInterface|null - */ - protected $container; + /** + * @var ContainerInterface|null + */ + protected $container; - /** - * {@inheritdoc} - */ - public function setContainer(ContainerInterface $container = null) - { - $this->container = $container; - } + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } } \ No newline at end of file diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index 52c2211..bba474e 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -19,16 +19,16 @@ class DefaultController public function indexAction(Application $app, Request $request) { $rootDir = $app->getRootDir(); - $packagesJson = $rootDir.'/web/packages.json'; + $packagesJson = $rootDir . '/web/packages.json'; $mtime = null; if (file_exists($packagesJson)) { - $mtime = new \DateTime('@'.filemtime($packagesJson)); + $mtime = new \DateTime('@' . filemtime($packagesJson)); } return new Response( - $app->get('templating')->render('Default/index.html.twig', array( - 'updatedAt' => $mtime, - ))); + $app->get('templating')->render('Default/index.html.twig', [ + 'updatedAt' => $mtime, + ])); } public function loginAction(Application $app, Request $request) @@ -40,6 +40,6 @@ public function loginAction(Application $app, Request $request) $session->remove(AuthenticationFailureSubscriber::AUTHENTICATION_ERROR); } - return new Response($app->get('templating')->render('Default/login.html.twig', array('error' => $error))); + return new Response($app->get('templating')->render('Default/login.html.twig', ['error' => $error])); } } diff --git a/src/Controller/ManageController.php b/src/Controller/ManageController.php index b285243..3ccc3d9 100644 --- a/src/Controller/ManageController.php +++ b/src/Controller/ManageController.php @@ -29,9 +29,9 @@ public function indexAction(Application $app, Request $request) ->where('c.enabled = true') ->getQuery()->getSingleScalarResult(); - return new Response($app->get('templating')->render('Manage/index.html.twig', array( - 'packages' => $packages, - 'remotes' => $remotes, - ))); + return new Response($app->get('templating')->render('Manage/index.html.twig', [ + 'packages' => $packages, + 'remotes' => $remotes, + ])); } } diff --git a/src/Controller/PackageController.php b/src/Controller/PackageController.php index c8d552b..c0b73e8 100644 --- a/src/Controller/PackageController.php +++ b/src/Controller/PackageController.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Terramar\Packages\Entity\Package; use Terramar\Packages\Event\PackageEvent; use Terramar\Packages\Events; use Terramar\Packages\Plugin\Actions; @@ -31,37 +32,44 @@ public function indexAction(Application $app, Request $request) ->join('p.remote', 'r', 'WITH', 'r.enabled = true') ->getQuery()->getResult(); - return new Response($app->get('templating')->render('Package/index.html.twig', array( - 'packages' => $packages, - ))); + return new Response($app->get('templating')->render('Package/index.html.twig', [ + 'packages' => $packages, + ])); } public function editAction(Application $app, $id) { - /** @var \Doctrine\ORM\EntityManager $entityManager */ + /** @var EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); + /** @var Package $package */ $package = $entityManager->getRepository('Terramar\Packages\Entity\Package')->find($id); if (!$package) { throw new NotFoundHttpException('Unable to locate Package'); } - return new Response($app->get('templating')->render('Package/edit.html.twig', array( - 'package' => $package, - 'remotes' => $this->getRemotes($app->get('doctrine.orm.entity_manager')), - ))); + return new Response($app->get('templating')->render('Package/edit.html.twig', [ + 'package' => $package, + 'remotes' => $this->getRemotes($entityManager), + ])); + } + + protected function getRemotes(EntityManager $entityManager) + { + return $entityManager->getRepository('Terramar\Packages\Entity\Remote')->findBy(['enabled' => true]); } public function updateAction(Application $app, Request $request, $id) { - /** @var \Doctrine\ORM\EntityManager $entityManager */ + /** @var EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); + /** @var Package $package */ $package = $entityManager->getRepository('Terramar\Packages\Entity\Package')->find($id); if (!$package) { throw new NotFoundHttpException('Unable to locate Package'); } $enabledBefore = $package->isEnabled(); - $enabledAfter = (bool) $request->get('enabled', false); + $enabledAfter = (bool)$request->get('enabled', false); $package->setName($request->request->get('name')); $package->setDescription($request->request->get('description')); @@ -78,9 +86,9 @@ public function updateAction(Application $app, Request $request, $id) /** @var \Terramar\Packages\Helper\PluginHelper $helper */ $helper = $app->get('packages.helper.plugin'); - $helper->invokeAction($request, Actions::PACKAGE_UPDATE, array_merge($request->request->all(), array( - 'id' => $id, - ))); + $helper->invokeAction($request, Actions::PACKAGE_UPDATE, array_merge($request->request->all(), [ + 'id' => $id, + ])); $entityManager->persist($package); $entityManager->flush(); @@ -112,9 +120,4 @@ public function toggleAction(Application $app, $id) return new RedirectResponse($app->get('router.url_generator')->generate('manage_packages')); } - - protected function getRemotes(EntityManager $entityManager) - { - return $entityManager->getRepository('Terramar\Packages\Entity\Remote')->findBy(array('enabled' => true)); - } } diff --git a/src/Controller/RemoteController.php b/src/Controller/RemoteController.php index 6e04acd..26bc8e8 100644 --- a/src/Controller/RemoteController.php +++ b/src/Controller/RemoteController.php @@ -13,6 +13,8 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Terramar\Packages\Entity\Remote; use Terramar\Packages\Event\RemoteEvent; @@ -28,19 +30,29 @@ public function indexAction(Application $app, Request $request) $remotes = $entityManager->getRepository('Terramar\Packages\Entity\Remote')->findAll(); - return new Response($app->get('templating')->render('Remote/index.html.twig', array( - 'remotes' => $remotes, - ))); + $error = null; + $flashBag = $request->getSession()->getBag('flashes'); + if ($flashBag instanceof FlashBagInterface) { + $errors = $flashBag->get('error'); + if (count($errors) > 0) { + $error = $errors[0]; + } + } + + return new Response($app->get('templating')->render('Remote/index.html.twig', [ + 'remotes' => $remotes, + 'last_error' => $error, + ])); } public function newAction(Application $app) { $adapters = $app->get('packages.helper.sync')->getAdapters(); - return new Response($app->get('templating')->render('Remote/new.html.twig', array( - 'adapters' => $adapters, - 'remote' => new Remote(), - ))); + return new Response($app->get('templating')->render('Remote/new.html.twig', [ + 'adapters' => $adapters, + 'remote' => new Remote(), + ])); } public function createAction(Application $app, Request $request) @@ -72,9 +84,9 @@ public function editAction(Application $app, $id) throw new NotFoundHttpException('Unable to locate Remote'); } - return new Response($app->get('templating')->render('Remote/edit.html.twig', array( - 'remote' => $remote, - ))); + return new Response($app->get('templating')->render('Remote/edit.html.twig', [ + 'remote' => $remote, + ])); } public function updateAction(Application $app, Request $request, $id) @@ -89,7 +101,7 @@ public function updateAction(Application $app, Request $request, $id) $remote->setName($request->get('name')); $enabledBefore = $remote->isEnabled(); - $enabledAfter = (bool) $request->get('enabled', false); + $enabledAfter = (bool)$request->get('enabled', false); $remote->setEnabled($enabledAfter); @@ -104,9 +116,9 @@ public function updateAction(Application $app, Request $request, $id) /** @var \Terramar\Packages\Helper\PluginHelper $helper */ $helper = $app->get('packages.helper.plugin'); - $helper->invokeAction($request, Actions::REMOTE_UPDATE, array_merge($request->request->all(), array( - 'id' => $id, - ))); + $helper->invokeAction($request, Actions::REMOTE_UPDATE, array_merge($request->request->all(), [ + 'id' => $id, + ])); $entityManager->persist($remote); $entityManager->flush(); @@ -114,7 +126,7 @@ public function updateAction(Application $app, Request $request, $id) return new RedirectResponse($app->get('router.url_generator')->generate('manage_remotes')); } - public function syncAction(Application $app, $id) + public function syncAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); @@ -125,7 +137,15 @@ public function syncAction(Application $app, $id) /** @var \Terramar\Packages\Helper\SyncHelper $helper */ $helper = $app->get('packages.helper.sync'); - $packages = $helper->synchronizePackages($remote); + try { + $packages = $helper->synchronizePackages($remote); + } catch (\RuntimeException $e) { + $flashBag = $request->getSession()->getBag('flashes'); + if ($flashBag instanceof FlashBagInterface) { + $flashBag->add('error', $e->getMessage()); + } + return new RedirectResponse($app->get('router.url_generator')->generate('manage_remotes')); + } foreach ($packages as $package) { $entityManager->persist($package); } diff --git a/src/Controller/WebHookController.php b/src/Controller/WebHookController.php index 2511196..7df1d1a 100644 --- a/src/Controller/WebHookController.php +++ b/src/Controller/WebHookController.php @@ -20,11 +20,14 @@ class WebHookController extends ContainerAwareController { public function receiveAction(Application $app, Request $request, $id) { - ResqueHelper::autoConfigure($this->container); + ResqueHelper::autoConfigure($this->container); /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $package = $entityManager->getRepository('Terramar\Packages\Entity\Package')->findOneBy(array('id' => $id, 'enabled' => true)); + $package = $entityManager->getRepository('Terramar\Packages\Entity\Package')->findOneBy([ + 'id' => $id, + 'enabled' => true, + ]); if (!$package || !$package->isEnabled() || !$package->getRemote()->isEnabled()) { return new Response('Project not found', 404); } diff --git a/src/DependencyInjection/Compiler/OverrideServicesCompilerPass.php b/src/DependencyInjection/Compiler/OverrideServicesCompilerPass.php new file mode 100644 index 0000000..3034d53 --- /dev/null +++ b/src/DependencyInjection/Compiler/OverrideServicesCompilerPass.php @@ -0,0 +1,28 @@ +getDefinition('security.security_subscriber') + ->setClass('Terramar\Packages\Security\FirewallSubscriber'); + } +} \ No newline at end of file diff --git a/src/DependencyInjection/PackagesConfiguration.php b/src/DependencyInjection/PackagesConfiguration.php index e70a922..31949a0 100644 --- a/src/DependencyInjection/PackagesConfiguration.php +++ b/src/DependencyInjection/PackagesConfiguration.php @@ -27,8 +27,13 @@ public function getConfigTreeBuilder() $rootNode ->children() ->scalarNode('site_name')->defaultValue('Private Composer Repository')->end() - ->scalarNode('homepage')->defaultValue('https://github.com/terramar-labs/packages')->end() - ->scalarNode('output_dir')->defaultValue('%app.root_dir%/web')->end() + ->scalarNode('name')->defaultNull()->end() + ->scalarNode('homepage')->defaultValue('')->end() + ->scalarNode('base_path')->defaultValue('')->end() + ->scalarNode('archive')->defaultValue(false)->end() + ->scalarNode('contact_email')->defaultValue('')->end() + ->scalarNode('secure_satis')->defaultFalse()->end() + ->scalarNode('output_dir')->defaultValue('%app.root_dir%/satis')->end() ->arrayNode('resque') ->addDefaultsIfNotSet() ->children() @@ -36,8 +41,7 @@ public function getConfigTreeBuilder() ->scalarNode('port')->defaultValue('6379')->end() ->scalarNode('database')->defaultNull()->end() ->end() - ->end() - ->end(); + ->end(); return $treeBuilder; } diff --git a/src/DependencyInjection/PackagesExtension.php b/src/DependencyInjection/PackagesExtension.php index ea802f3..b59c2c2 100644 --- a/src/DependencyInjection/PackagesExtension.php +++ b/src/DependencyInjection/PackagesExtension.php @@ -9,52 +9,45 @@ namespace Terramar\Packages\DependencyInjection; +use Nice\DependencyInjection\CompilerAwareExtensionInterface; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Reference; +use Terramar\Packages\DependencyInjection\Compiler\OverrideServicesCompilerPass; +use Terramar\Packages\Plugin\CompilerAwarePluginInterface; use Terramar\Packages\Plugin\PluginInterface; +use Terramar\Packages\Plugin\RouterPluginInterface; -class PackagesExtension extends Extension +class PackagesExtension extends Extension implements CompilerAwareExtensionInterface { /** * @var array */ - private $options = array(); + private $options = []; /** * @var array|PluginInterface[] */ - private $plugins = array(); + private $plugins = []; /** * Constructor. * + * @param array $plugins * @param array $options */ - public function __construct(array $plugins = array(), array $options = array()) + public function __construct(array $plugins = [], array $options = []) { $this->plugins = $plugins; $this->options = $options; } - /** - * Returns extension configuration. - * - * @param array $config An array of configuration values - * @param ContainerBuilder $container A ContainerBuilder instance - * - * @return PackagesConfiguration - */ - public function getConfiguration(array $config, ContainerBuilder $container) - { - return new PackagesConfiguration(); - } - /** * Loads a specific configuration. * - * @param array $configs An array of configuration values + * @param array $configs An array of configuration values * @param ContainerBuilder $container A ContainerBuilder instance * * @throws \InvalidArgumentException When provided tag is not defined in this extension @@ -65,7 +58,17 @@ public function load(array $configs, ContainerBuilder $container) $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $container->register('router.collector', 'Terramar\Packages\Router\RouteCollector') + $container->setParameter('packages.configuration', [ + 'name' => empty($config['name']) ? $config['site_name'] : $config['name'], + 'homepage' => $config['homepage'], + 'base_path' => $config['base_path'], + 'archive' => $config['archive'], + 'output_dir' => $container->getParameterBag()->resolveValue($config['output_dir']), + 'contact_email' => $config['contact_email'], + 'secure_satis' => $config['secure_satis'], + ]); + + $collector = $container->register('router.collector', 'Terramar\Packages\Router\RouteCollector') ->addArgument(new Reference('router.parser')) ->addArgument(new Reference('router.data_generator')); @@ -74,12 +77,6 @@ public function load(array $configs, ContainerBuilder $container) $container->register('packages.helper.sync', 'Terramar\Packages\Helper\SyncHelper') ->addArgument(new Reference('event_dispatcher')); - $container->setParameter('packages.configuration', array( - 'name' => $config['site_name'], - 'homepage' => $config['homepage'], - 'output_dir' => $config['output_dir'], - )); - $container->register('packages.helper.resque', 'Terramar\Packages\Helper\ResqueHelper'); $container->setParameter('packages.resque.host', $config['resque']['host']); @@ -88,52 +85,108 @@ public function load(array $configs, ContainerBuilder $container) $container->register('packages.controller_manager', 'Terramar\Packages\Plugin\ControllerManager'); - $container->register('packages.fragment_handler.inline_renderer', 'Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer') + $container->register('packages.fragment_handler.inline_renderer', + 'Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer') ->addArgument(new Reference('http_kernel')) ->addArgument(new Reference('event_dispatcher')); $container->register('packages.fragment_handler', 'Symfony\Component\HttpKernel\Fragment\FragmentHandler') - ->addArgument(array( - new Reference('packages.fragment_handler.inline_renderer'), - )) + ->addArgument([ + new Reference('packages.fragment_handler.inline_renderer'), + ]) ->addArgument(false) - ->addMethodCall('setRequest', array(new Reference( + ->addMethodCall('setRequest', [ + new Reference( 'request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false - ))); + ), + ]); + + $container->register('packages.twig_extension.packages_conf', 'Terramar\Packages\Twig\PackagesConfigExtension') + ->addArgument('%packages.configuration%') + ->addTag('twig.extension'); $container->register('packages.twig_extension.plugin', 'Terramar\Packages\Twig\PluginControllerExtension') ->addArgument(new Reference('packages.controller_manager')) ->addArgument(new Reference('packages.fragment_handler')) - ->addMethodCall('setRequest', array(new Reference( + ->addMethodCall('setRequest', [ + new Reference( + 'request', + ContainerInterface::NULL_ON_INVALID_REFERENCE, + false + ), + ]) + ->addTag('twig.extension'); + + $container->register('packages.twig_extension.security', 'Terramar\Packages\Twig\SecurityExtension') + ->addMethodCall('setRequest', [ + new Reference( 'request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false - ))) + ), + ]) ->addTag('twig.extension'); $container->register('packages.fragment_handler.uri_signer', 'Symfony\Component\HttpKernel\UriSigner') ->addArgument(''); - $container->register('packages.fragment_handler.listener', 'Symfony\Component\HttpKernel\EventListener\FragmentListener') + $container->register('packages.fragment_handler.listener', + 'Symfony\Component\HttpKernel\EventListener\FragmentListener') ->addArgument(new Reference('packages.fragment_handler.uri_signer')) ->addTag('kernel.event_subscriber'); $container->register('packages.helper.plugin', 'Terramar\Packages\Helper\PluginHelper') ->addArgument(new Reference('packages.controller_manager')) ->addArgument(new Reference('packages.fragment_handler')) - ->addMethodCall('setRequest', array(new Reference( + ->addMethodCall('setRequest', [ + new Reference( 'request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false - ))); + ), + ]); - $plugins = array(); + $plugins = []; foreach ($this->plugins as $plugin) { + $name = preg_replace('/[^a-z0-9]/', '', strtolower($plugin->getName())); + $container->register('packages.plugin.'.$name, get_class($plugin)); $plugin->configure($container); - $plugins[$plugin->getName()] = $plugin->getVersion(); + $plugins[$name] = $plugin->getVersion(); + if ($plugin instanceof RouterPluginInterface) { + $collector->addMethodCall('registerPlugin', [new Reference('packages.plugin.'.$name)]); + } } $container->setParameter('packages.registered_plugins', $plugins); } + + /** + * Returns extension configuration. + * + * @param array $config An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @return PackagesConfiguration + */ + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new PackagesConfiguration(); + } + + /** + * Gets the CompilerPasses this extension requires. + * + * @return array|CompilerPassInterface[] + */ + public function getCompilerPasses() + { + $passes = [new OverrideServicesCompilerPass()]; + foreach ($this->plugins as $plugin) { + if ($plugin instanceof CompilerAwarePluginInterface) { + $passes = array_merge($passes, $plugin->getCompilerPasses()); + } + } + return $passes; + } } diff --git a/src/Entity/Package.php b/src/Entity/Package.php index 6c91319..9180a36 100644 --- a/src/Entity/Package.php +++ b/src/Entity/Package.php @@ -12,7 +12,7 @@ use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity + * @ORM\Entity * @ORM\Table(name="packages") */ class Package @@ -75,7 +75,7 @@ class Package */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** @@ -107,7 +107,7 @@ public function getDescription() */ public function setDescription($description) { - $this->description = (string) $description; + $this->description = (string)$description; } /** @@ -123,7 +123,7 @@ public function getName() */ public function setName($name) { - $this->name = (string) $name; + $this->name = (string)$name; } /** @@ -155,7 +155,7 @@ public function getExternalId() */ public function setExternalId($externalId) { - $this->externalId = (string) $externalId; + $this->externalId = (string)$externalId; } /** @@ -171,7 +171,7 @@ public function getHookExternalId() */ public function setHookExternalId($hookExternalId) { - $this->hookExternalId = (string) $hookExternalId; + $this->hookExternalId = (string)$hookExternalId; } /** @@ -187,7 +187,7 @@ public function getFqn() */ public function setFqn($fqn) { - $this->fqn = (string) $fqn; + $this->fqn = (string)$fqn; } /** @@ -203,7 +203,7 @@ public function getSshUrl() */ public function setSshUrl($sshUrl) { - $this->sshUrl = (string) $sshUrl; + $this->sshUrl = (string)$sshUrl; } /** @@ -219,6 +219,14 @@ public function getWebUrl() */ public function setWebUrl($webUrl) { - $this->webUrl = (string) $webUrl; + $this->webUrl = (string)$webUrl; + } + + /** + * @return string + */ + public function __toString() + { + return sprintf("%d:%d", $this->remote->getId(), $this->externalId); } } diff --git a/src/Entity/Remote.php b/src/Entity/Remote.php index 4b8e3cf..bfe2c1c 100644 --- a/src/Entity/Remote.php +++ b/src/Entity/Remote.php @@ -52,7 +52,7 @@ public function isEnabled() */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** @@ -76,7 +76,7 @@ public function getName() */ public function setName($name) { - $this->name = (string) $name; + $this->name = (string)$name; } /** @@ -92,6 +92,6 @@ public function getAdapter() */ public function setAdapter($adapter) { - $this->adapter = (string) $adapter; + $this->adapter = (string)$adapter; } } diff --git a/src/Event/PackageUpdateEvent.php b/src/Event/PackageUpdateEvent.php index f953a19..2958f18 100644 --- a/src/Event/PackageUpdateEvent.php +++ b/src/Event/PackageUpdateEvent.php @@ -20,9 +20,9 @@ class PackageUpdateEvent extends PackageEvent /** * Constructor. - * + * * @param Package $package The updated package - * @param mixed $payload Any data received from the remote host + * @param mixed $payload Any data received from the remote host */ public function __construct(Package $package, $payload) { diff --git a/src/Events.php b/src/Events.php index 6c7e2e4..f845a70 100644 --- a/src/Events.php +++ b/src/Events.php @@ -12,7 +12,7 @@ /** * Events defines constants describing the possible events to subscribe to. * - * @see http://docs.terramarlabs.com/packages/3.1/plugins/event-reference + * @see http://docs.terramarlabs.com/packages/3.2/plugins/event-reference */ final class Events { diff --git a/src/Helper/PluginHelper.php b/src/Helper/PluginHelper.php index dbd0de2..a7e7cfb 100644 --- a/src/Helper/PluginHelper.php +++ b/src/Helper/PluginHelper.php @@ -35,7 +35,7 @@ class PluginHelper * Constructor. * * @param ControllerManagerInterface $manager - * @param FragmentHandler $handler + * @param FragmentHandler $handler */ public function __construct(ControllerManagerInterface $manager, FragmentHandler $handler) { @@ -53,13 +53,14 @@ public function setRequest(Request $request = null) /** * Invoke the given action using the given Request. - * + * * @param Request $request - * @param string $action + * @param string $action */ - public function invokeAction(Request $request, $action, $params = array()) + public function invokeAction(Request $request, $action, $params = []) { - $controllers = $this->getControllers($action, $params, array_merge($request->query->all(), $request->request->all())); + $controllers = $this->getControllers($action, $params, + array_merge($request->query->all(), $request->request->all())); foreach ($controllers as $controller) { $this->handler->render($controller); @@ -71,12 +72,12 @@ public function invokeAction(Request $request, $action, $params = array()) * * @return array|ControllerReference[] */ - private function getControllers($action, $params = array(), $query = array()) + private function getControllers($action, $params = [], $query = []) { $params['app'] = $this->request->get('app'); return array_map(function ($controller) use ($params, $query) { - return new ControllerReference($controller, $params, $query); - }, $this->manager->getControllers($action)); + return new ControllerReference($controller, $params, $query); + }, $this->manager->getControllers($action)); } } diff --git a/src/Helper/ResqueHelper.php b/src/Helper/ResqueHelper.php index aaf9295..78f8a0c 100644 --- a/src/Helper/ResqueHelper.php +++ b/src/Helper/ResqueHelper.php @@ -13,78 +13,54 @@ class ResqueHelper { - /** - * Configure Resque - * - * @param ContainerInterface $container - * @return array - */ - public static function autoConfigure(ContainerInterface $container) - { - $redisHost = $container->getParameter('packages.resque.host'); - $redisPort = $container->getParameter('packages.resque.port'); - $redisDatabase = $container->getParameter('packages.resque.database'); - - if (!isset($redisDatabase)) { - $redisDatabase = 0; - } - - $backend = strpos($redisHost, 'unix:') === false ? $redisHost.':'.$redisPort : $redisHost; - - \Resque::setBackend($backend, $redisDatabase); - - return array( - 'backend' => $backend, - 'database' => $redisDatabase - ); - } - /** - * Get jobs for the given queue. - * - * @param string $queue The name of the queue or "*" for all queues + * Configure Resque * - * @return array|\Resque_Job[] + * @param ContainerInterface $container + * @return array */ - public function getJobs($queue = null) + public static function autoConfigure(ContainerInterface $container) { - if (!$queue || $queue === '*') { - $queues = \Resque::queues(); - $jobs = array(); - foreach ($queues as $queue) { - $jobs = array_merge($jobs, $this->getJobs($queue)); - } + $redisHost = $container->getParameter('packages.resque.host'); + $redisPort = $container->getParameter('packages.resque.port'); + $redisDatabase = $container->getParameter('packages.resque.database'); - return $jobs; + if (!isset($redisDatabase)) { + $redisDatabase = 0; } - return array_map(function ($job) use ($queue) { - return new \Resque_Job($queue, json_decode($job, true)); - }, \Resque::redis()->lrange('queue:'.$queue, 0, -1)); + $backend = strpos($redisHost, 'unix:') === false ? $redisHost . ':' . $redisPort : $redisHost; + + \Resque::setBackend($backend, $redisDatabase); + + return [ + 'backend' => $backend, + 'database' => $redisDatabase, + ]; } /** * Clear the given queue. - * + * * @param string $queue The name of the queue * * @return int The number of removed items */ public function clearQueue($queue) { - $length = \Resque::redis()->llen('queue:'.$queue); - \Resque::redis()->del('queue:'.$queue); + $length = \Resque::redis()->llen('queue:' . $queue); + \Resque::redis()->del('queue:' . $queue); return $length; } /** * Enqueue a job, but only if it is not already in the queue. - * + * * @param string $queue * @param string $class - * @param array $args - * @param bool $trackStatus + * @param array $args + * @param bool $trackStatus * * @return string The enqueued job ID */ @@ -99,13 +75,37 @@ public function enqueueOnce($queue, $class, $args = null, $trackStatus = false) return $this->enqueue($queue, $class, $args, $trackStatus); } + /** + * Get jobs for the given queue. + * + * @param string $queue The name of the queue or "*" for all queues + * + * @return array|\Resque_Job[] + */ + public function getJobs($queue = null) + { + if (!$queue || $queue === '*') { + $queues = \Resque::queues(); + $jobs = []; + foreach ($queues as $queue) { + $jobs = array_merge($jobs, $this->getJobs($queue)); + } + + return $jobs; + } + + return array_map(function ($job) use ($queue) { + return new \Resque_Job($queue, json_decode($job, true)); + }, \Resque::redis()->lrange('queue:' . $queue, 0, -1)); + } + /** * Enqueue a job. - * + * * @param string $queue * @param string $class - * @param array $args - * @param bool $trackStatus + * @param array $args + * @param bool $trackStatus * * @return string The enqueued job ID */ diff --git a/src/Helper/SyncAdapterInterface.php b/src/Helper/SyncAdapterInterface.php index a9d1abf..ccdeaae 100644 --- a/src/Helper/SyncAdapterInterface.php +++ b/src/Helper/SyncAdapterInterface.php @@ -9,14 +9,14 @@ namespace Terramar\Packages\Helper; -use Terramar\Packages\Entity\Remote; use Terramar\Packages\Entity\Package; +use Terramar\Packages\Entity\Remote; interface SyncAdapterInterface { /** * Returns true if the adapter supports the given configuration. - * + * * @param Remote $remote * * @return bool @@ -25,7 +25,7 @@ public function supports(Remote $remote); /** * Synchronizes the given adapter, returning any new Packages. - * + * * @param Remote $remote * * @return Package[] @@ -34,7 +34,7 @@ public function synchronizePackages(Remote $remote); /** * Gets the name of the adapter. - * + * * @return string */ public function getName(); diff --git a/src/Helper/SyncHelper.php b/src/Helper/SyncHelper.php index a894550..2faa7e1 100644 --- a/src/Helper/SyncHelper.php +++ b/src/Helper/SyncHelper.php @@ -24,11 +24,11 @@ class SyncHelper /** * @var array|SyncAdapterInterface[] */ - private $adapters = array(); + private $adapters = []; /** * Constructor. - * + * * @param EventDispatcherInterface $eventDispatcher */ public function __construct(EventDispatcherInterface $eventDispatcher) @@ -38,7 +38,7 @@ public function __construct(EventDispatcherInterface $eventDispatcher) /** * Register an adapter with the helper. - * + * * @param SyncAdapterInterface $adapter */ public function registerAdapter(SyncAdapterInterface $adapter) @@ -48,7 +48,7 @@ public function registerAdapter(SyncAdapterInterface $adapter) /** * Synchronize packages in the given configuration. - * + * * @param Remote $configuration * * @return \Terramar\Packages\Entity\Package[] @@ -67,14 +67,6 @@ public function synchronizePackages(Remote $configuration) return $packages; } - /** - * @return array|SyncAdapterInterface[] - */ - public function getAdapters() - { - return array_values($this->adapters); - } - private function getAdapter(Remote $configuration) { foreach ($this->adapters as $adapter) { @@ -85,4 +77,12 @@ private function getAdapter(Remote $configuration) throw new \RuntimeException('No adapter registered supports the given configuration'); } + + /** + * @return array|SyncAdapterInterface[] + */ + public function getAdapters() + { + return array_values($this->adapters); + } } diff --git a/src/Job/ContainerAwareJob.php b/src/Job/ContainerAwareJob.php index 71b6031..c89bad7 100644 --- a/src/Job/ContainerAwareJob.php +++ b/src/Job/ContainerAwareJob.php @@ -27,13 +27,23 @@ abstract class ContainerAwareJob /** * @var array The job args */ - public $args = array(); + public $args = []; /** * @var Application */ private $app = null; + /** + * Perform the work. + */ + public function perform() + { + $this->run($this->args); + } + + abstract protected function run($args); + /** * @return ContainerInterface */ @@ -58,14 +68,4 @@ protected function createApplication() isset($this->args['app.cache']) ? $this->args['app.cache'] : true ); } - - /** - * Perform the work. - */ - public function perform() - { - $this->run($this->args); - } - - abstract protected function run($args); } diff --git a/src/Migration/Version3100.php b/src/Migration/Version3100.php index dcf80f6..16d806e 100644 --- a/src/Migration/Version3100.php +++ b/src/Migration/Version3100.php @@ -25,7 +25,7 @@ public function up(Schema $schema) { $table = $schema->createTable('packages_sami_configurations'); $table->addColumn('id', 'integer'); - $table->setPrimaryKey(array('id')); + $table->setPrimaryKey(['id']); $table->addColumn('enabled', 'boolean'); $table->addColumn('package_id', 'integer'); $table->addColumn('docs_path', 'string'); @@ -36,14 +36,14 @@ public function up(Schema $schema) $table->addColumn('tags', 'string'); $table->addColumn('refs', 'string'); $table->addColumn('templates_dir', 'string'); - $table->addForeignKeyConstraint('packages', array('package_id'), array('id')); + $table->addForeignKeyConstraint('packages', ['package_id'], ['id']); $table = $schema->createTable('packages_cloneproject_configurations'); $table->addColumn('id', 'integer'); - $table->setPrimaryKey(array('id')); + $table->setPrimaryKey(['id']); $table->addColumn('enabled', 'boolean'); $table->addColumn('package_id', 'integer'); - $table->addForeignKeyConstraint('packages', array('package_id'), array('id')); + $table->addForeignKeyConstraint('packages', ['package_id'], ['id']); } public function postUp(Schema $schema) diff --git a/src/Plugin/Actions.php b/src/Plugin/Actions.php index 6808f8f..dd0cabb 100644 --- a/src/Plugin/Actions.php +++ b/src/Plugin/Actions.php @@ -12,7 +12,7 @@ /** * Actions defines constants for the various actions available to plugins. * - * @see http://docs.terramarlabs.com/packages/3.1/plugins/action-reference + * @see http://docs.terramarlabs.com/packages/3.2/plugins/action-reference */ final class Actions { diff --git a/src/Plugin/CloneProject/CloneProjectJob.php b/src/Plugin/CloneProject/CloneProjectJob.php index a719d92..78fd3a5 100644 --- a/src/Plugin/CloneProject/CloneProjectJob.php +++ b/src/Plugin/CloneProject/CloneProjectJob.php @@ -17,30 +17,6 @@ class CloneProjectJob extends ContainerAwareJob { - /** - * @return string - */ - private function getCacheDir(Package $package) - { - return $this->getContainer()->getParameter('app.cache_dir').'/cloned_project/'.$package->getFqn(); - } - - /** - * @return EntityManager - */ - private function getEntityManager() - { - return $this->getContainer()->get('doctrine.orm.entity_manager'); - } - - /** - * @return EventDispatcherInterface - */ - private function getEventDispatcher() - { - return $this->getContainer()->get('event_dispatcher'); - } - public function run($args) { $package = $this->getEntityManager()->find('Terramar\Packages\Entity\Package', $args['id']); @@ -56,7 +32,7 @@ public function run($args) mkdir($directory, 0777, true); - $builder = new ProcessBuilder(array('clone', $package->getSshUrl(), $directory)); + $builder = new ProcessBuilder(['clone', $package->getSshUrl(), $directory]); $builder->setPrefix('git'); $process = $builder->getProcess(); @@ -70,13 +46,37 @@ public function run($args) } } + /** + * @return EntityManager + */ + private function getEntityManager() + { + return $this->getContainer()->get('doctrine.orm.entity_manager'); + } + + /** + * @return string + */ + private function getCacheDir(Package $package) + { + return $this->getContainer()->getParameter('app.cache_dir') . '/cloned_project/' . $package->getFqn(); + } + private function emptyAndRemoveDirectory($directory) { - $files = array_diff(scandir($directory), array('.', '..')); + $files = array_diff(scandir($directory), ['.', '..']); foreach ($files as $file) { (is_dir("$directory/$file")) ? $this->emptyAndRemoveDirectory("$directory/$file") : unlink("$directory/$file"); } return rmdir($directory); } + + /** + * @return EventDispatcherInterface + */ + private function getEventDispatcher() + { + return $this->getContainer()->get('event_dispatcher'); + } } diff --git a/src/Plugin/CloneProject/Controller.php b/src/Plugin/CloneProject/Controller.php index 89637af..b67c0ea 100644 --- a/src/Plugin/CloneProject/Controller.php +++ b/src/Plugin/CloneProject/Controller.php @@ -19,22 +19,22 @@ public function editAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\CloneProject\PackageConfiguration')->findOneBy(array( - 'package' => $id, - )); + $config = $entityManager->getRepository('Terramar\Packages\Plugin\CloneProject\PackageConfiguration')->findOneBy([ + 'package' => $id, + ]); - return new Response($app->get('twig')->render('Plugin/CloneProject/edit.html.twig', array( - 'config' => $config, - ))); + return new Response($app->get('twig')->render('Plugin/CloneProject/edit.html.twig', [ + 'config' => $config, + ])); } public function updateAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\CloneProject\PackageConfiguration')->findOneBy(array( - 'package' => $id, - )); + $config = $entityManager->getRepository('Terramar\Packages\Plugin\CloneProject\PackageConfiguration')->findOneBy([ + 'package' => $id, + ]); $config->setEnabled($request->get('cloneproject_enabled') ? true : false); $entityManager->persist($config); diff --git a/src/Plugin/CloneProject/EventSubscriber.php b/src/Plugin/CloneProject/EventSubscriber.php index 2e8cce0..c2897b3 100644 --- a/src/Plugin/CloneProject/EventSubscriber.php +++ b/src/Plugin/CloneProject/EventSubscriber.php @@ -31,7 +31,7 @@ class EventSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param ResqueHelper $resqueHelper + * @param ResqueHelper $resqueHelper * @param EntityManager $entityManager */ public function __construct(ResqueHelper $resqueHelper, EntityManager $entityManager) @@ -40,20 +40,33 @@ public function __construct(ResqueHelper $resqueHelper, EntityManager $entityMan $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::PACKAGE_CREATE => ['onCreatePackage', 0], + Events::PACKAGE_UPDATE => ['onUpdatePackage', 0], + ]; + } + /** * @param PackageUpdateEvent $event */ public function onUpdatePackage(PackageUpdateEvent $event) { $package = $event->getPackage(); + /** @var \Terramar\Packages\Plugin\CloneProject\PackageConfiguration $config */ $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\CloneProject\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config || !$config->isEnabled() || !$package->isEnabled()) { return; } - $this->resqueHelper->enqueue('default', 'Terramar\Packages\Plugin\CloneProject\CloneProjectJob', array('id' => $event->getPackage()->getId())); + $this->resqueHelper->enqueue('default', 'Terramar\Packages\Plugin\CloneProject\CloneProjectJob', + ['id' => $event->getPackage()->getId()]); } /** @@ -63,7 +76,7 @@ public function onCreatePackage(PackageEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\CloneProject\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config) { $config = new PackageConfiguration(); @@ -72,15 +85,4 @@ public function onCreatePackage(PackageEvent $event) $this->entityManager->persist($config); } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::PACKAGE_CREATE => array('onCreatePackage', 0), - Events::PACKAGE_UPDATE => array('onUpdatePackage', 0), - ); - } } diff --git a/src/Plugin/CloneProject/PackageCloneEvent.php b/src/Plugin/CloneProject/PackageCloneEvent.php index 5aa0d5b..99025df 100644 --- a/src/Plugin/CloneProject/PackageCloneEvent.php +++ b/src/Plugin/CloneProject/PackageCloneEvent.php @@ -22,8 +22,8 @@ class PackageCloneEvent extends PackageEvent /** * Constructor. * - * @param Package $package The updated package - * @param string $repositoryPath The path to the cloned repository + * @param Package $package The updated package + * @param string $repositoryPath The path to the cloned repository */ public function __construct(Package $package, $repositoryPath) { diff --git a/src/Plugin/CloneProject/PackageConfiguration.php b/src/Plugin/CloneProject/PackageConfiguration.php index 2ed4c62..42c0af7 100644 --- a/src/Plugin/CloneProject/PackageConfiguration.php +++ b/src/Plugin/CloneProject/PackageConfiguration.php @@ -41,7 +41,7 @@ class PackageConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** diff --git a/src/Plugin/CloneProject/Plugin.php b/src/Plugin/CloneProject/Plugin.php index f64055d..d24efd3 100644 --- a/src/Plugin/CloneProject/Plugin.php +++ b/src/Plugin/CloneProject/Plugin.php @@ -31,14 +31,17 @@ class Plugin implements PluginInterface */ public function configure(ContainerBuilder $container) { - $container->register('packages.plugin.clone_project.subscriber', 'Terramar\Packages\Plugin\CloneProject\EventSubscriber') + $container->register('packages.plugin.clone_project.subscriber', + 'Terramar\Packages\Plugin\CloneProject\EventSubscriber') ->addArgument(new Reference('packages.helper.resque')) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('kernel.event_subscriber'); $container->getDefinition('packages.controller_manager') - ->addMethodCall('registerController', array(Actions::PACKAGE_EDIT, 'Terramar\Packages\Plugin\CloneProject\Controller::editAction')) - ->addMethodCall('registerController', array(Actions::PACKAGE_UPDATE, 'Terramar\Packages\Plugin\CloneProject\Controller::updateAction')); + ->addMethodCall('registerController', + [Actions::PACKAGE_EDIT, 'Terramar\Packages\Plugin\CloneProject\Controller::editAction']) + ->addMethodCall('registerController', + [Actions::PACKAGE_UPDATE, 'Terramar\Packages\Plugin\CloneProject\Controller::updateAction']); } /** @@ -57,7 +60,7 @@ public function getName() public function getVersion() { if (!$this->version) { - $matches = array(); + $matches = []; preg_match('/version (\d\.\d{1,2}\.\d{1,2})/', exec('git --version'), $matches); $this->version = $matches[1]; } diff --git a/src/Plugin/CompilerAwarePluginInterface.php b/src/Plugin/CompilerAwarePluginInterface.php new file mode 100644 index 0000000..9afee04 --- /dev/null +++ b/src/Plugin/CompilerAwarePluginInterface.php @@ -0,0 +1,28 @@ +controllers[$action])) { - $this->controllers[$action] = array(); + $this->controllers[$action] = []; } $this->controllers[$action][] = $controller; @@ -24,6 +24,6 @@ public function registerController($action, $controller) public function getControllers($action) { - return isset($this->controllers[$action]) ? $this->controllers[$action] : array(); + return isset($this->controllers[$action]) ? $this->controllers[$action] : []; } } diff --git a/src/Plugin/ControllerManagerInterface.php b/src/Plugin/ControllerManagerInterface.php index aa57544..bec7f5f 100644 --- a/src/Plugin/ControllerManagerInterface.php +++ b/src/Plugin/ControllerManagerInterface.php @@ -16,7 +16,7 @@ interface ControllerManagerInterface { /** * Register a controller for the given action. - * + * * @param string $action * @param string $controller */ diff --git a/src/Plugin/GitHub/Controller.php b/src/Plugin/GitHub/Controller.php index bdfe061..8e52e76 100644 --- a/src/Plugin/GitHub/Controller.php +++ b/src/Plugin/GitHub/Controller.php @@ -44,22 +44,22 @@ public function editAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration')->findOneBy(array( + $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration')->findOneBy([ 'remote' => $id, - )); + ]); - return new Response($app->get('twig')->render('Plugin/GitHub/edit.html.twig', array( + return new Response($app->get('twig')->render('Plugin/GitHub/edit.html.twig', [ 'config' => $config ?: new RemoteConfiguration(), - ))); + ])); } public function updateAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration')->findOneBy(array( + $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration')->findOneBy([ 'remote' => $id, - )); + ]); if (!$config) { return new Response(); diff --git a/src/Plugin/GitHub/PackageConfiguration.php b/src/Plugin/GitHub/PackageConfiguration.php index 3adc093..e170d4d 100644 --- a/src/Plugin/GitHub/PackageConfiguration.php +++ b/src/Plugin/GitHub/PackageConfiguration.php @@ -41,7 +41,7 @@ class PackageConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** diff --git a/src/Plugin/GitHub/PackageSubscriber.php b/src/Plugin/GitHub/PackageSubscriber.php index 5606b0a..a946021 100644 --- a/src/Plugin/GitHub/PackageSubscriber.php +++ b/src/Plugin/GitHub/PackageSubscriber.php @@ -29,7 +29,7 @@ class PackageSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param SyncAdapter $adapter + * @param SyncAdapter $adapter * @param EntityManager $entityManager */ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) @@ -38,6 +38,18 @@ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::PACKAGE_CREATE => ['onCreatePackage', 255], + Events::PACKAGE_ENABLE => ['onEnablePackage', 255], + Events::PACKAGE_DISABLE => ['onDisablePackage', 255], + ]; + } + /** * @param PackageEvent $event */ @@ -45,7 +57,7 @@ public function onCreatePackage(PackageEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\GitHub\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config) { $config = new PackageConfiguration(); @@ -80,16 +92,4 @@ public function onDisablePackage(PackageEvent $event) $this->adapter->disableHook($package); } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::PACKAGE_CREATE => array('onCreatePackage', 255), - Events::PACKAGE_ENABLE => array('onEnablePackage', 255), - Events::PACKAGE_DISABLE => array('onDisablePackage', 255), - ); - } } diff --git a/src/Plugin/GitHub/Plugin.php b/src/Plugin/GitHub/Plugin.php index 4127305..ff03b36 100644 --- a/src/Plugin/GitHub/Plugin.php +++ b/src/Plugin/GitHub/Plugin.php @@ -31,23 +31,29 @@ public function configure(ContainerBuilder $container) ->addArgument(new Reference('router.url_generator')); $container->getDefinition('packages.helper.sync') - ->addMethodCall('registerAdapter', array(new Reference('packages.plugin.github.adapter'))); + ->addMethodCall('registerAdapter', [new Reference('packages.plugin.github.adapter')]); - $container->register('packages.plugin.github.package_subscriber', 'Terramar\Packages\Plugin\GitHub\PackageSubscriber') + $container->register('packages.plugin.github.package_subscriber', + 'Terramar\Packages\Plugin\GitHub\PackageSubscriber') ->addArgument(new Reference('packages.plugin.github.adapter')) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('kernel.event_subscriber'); - $container->register('packages.plugin.github.remote_subscriber', 'Terramar\Packages\Plugin\GitHub\RemoteSubscriber') + $container->register('packages.plugin.github.remote_subscriber', + 'Terramar\Packages\Plugin\GitHub\RemoteSubscriber') ->addArgument(new Reference('packages.plugin.github.adapter')) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('kernel.event_subscriber'); $container->getDefinition('packages.controller_manager') - ->addMethodCall('registerController', array(Actions::REMOTE_NEW, 'Terramar\Packages\Plugin\GitHub\Controller::newAction')) - ->addMethodCall('registerController', array(Actions::REMOTE_CREATE, 'Terramar\Packages\Plugin\GitHub\Controller::createAction')) - ->addMethodCall('registerController', array(Actions::REMOTE_EDIT, 'Terramar\Packages\Plugin\GitHub\Controller::editAction')) - ->addMethodCall('registerController', array(Actions::REMOTE_UPDATE, 'Terramar\Packages\Plugin\GitHub\Controller::updateAction')); + ->addMethodCall('registerController', + [Actions::REMOTE_NEW, 'Terramar\Packages\Plugin\GitHub\Controller::newAction']) + ->addMethodCall('registerController', + [Actions::REMOTE_CREATE, 'Terramar\Packages\Plugin\GitHub\Controller::createAction']) + ->addMethodCall('registerController', + [Actions::REMOTE_EDIT, 'Terramar\Packages\Plugin\GitHub\Controller::editAction']) + ->addMethodCall('registerController', + [Actions::REMOTE_UPDATE, 'Terramar\Packages\Plugin\GitHub\Controller::updateAction']); } /** diff --git a/src/Plugin/GitHub/RemoteConfiguration.php b/src/Plugin/GitHub/RemoteConfiguration.php index 460f324..f0af334 100644 --- a/src/Plugin/GitHub/RemoteConfiguration.php +++ b/src/Plugin/GitHub/RemoteConfiguration.php @@ -51,7 +51,7 @@ class RemoteConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** @@ -99,7 +99,7 @@ public function getUsername() */ public function setUsername($username) { - $this->username = (string) $username; + $this->username = (string)$username; } /** @@ -115,6 +115,6 @@ public function getToken() */ public function setToken($token) { - $this->token = (string) $token; + $this->token = (string)$token; } } diff --git a/src/Plugin/GitHub/RemoteSubscriber.php b/src/Plugin/GitHub/RemoteSubscriber.php index 33c9054..92dff85 100644 --- a/src/Plugin/GitHub/RemoteSubscriber.php +++ b/src/Plugin/GitHub/RemoteSubscriber.php @@ -29,7 +29,7 @@ class RemoteSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param SyncAdapter $adapter + * @param SyncAdapter $adapter * @param EntityManager $entityManager */ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) @@ -38,6 +38,16 @@ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::REMOTE_DISABLE => ['onDisableRemote', 255], + ]; + } + /** * @param RemoteEvent $event */ @@ -49,21 +59,11 @@ public function onDisableRemote(RemoteEvent $event) } $packages = $this->entityManager->getRepository('Terramar\Packages\Entity\Package') - ->findBy(array('remote' => $remote)); + ->findBy(['remote' => $remote]); foreach ($packages as $package) { $this->adapter->disableHook($package); $package->setEnabled(false); } } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::REMOTE_DISABLE => array('onDisableRemote', 255), - ); - } } diff --git a/src/Plugin/GitHub/SyncAdapter.php b/src/Plugin/GitHub/SyncAdapter.php index 8dce6b4..cd32448 100644 --- a/src/Plugin/GitHub/SyncAdapter.php +++ b/src/Plugin/GitHub/SyncAdapter.php @@ -13,8 +13,8 @@ use Github\Client; use Github\HttpClient\Message\ResponseMediator; use Nice\Router\UrlGeneratorInterface; -use Terramar\Packages\Entity\Remote; use Terramar\Packages\Entity\Package; +use Terramar\Packages\Entity\Remote; use Terramar\Packages\Helper\SyncAdapterInterface; class SyncAdapter implements SyncAdapterInterface @@ -31,8 +31,8 @@ class SyncAdapter implements SyncAdapterInterface /** * Constructor. - * - * @param EntityManager $entityManager + * + * @param EntityManager $entityManager * @param UrlGeneratorInterface $urlGenerator */ public function __construct(EntityManager $entityManager, UrlGeneratorInterface $urlGenerator) @@ -51,6 +51,14 @@ public function supports(Remote $remote) return $remote->getAdapter() === $this->getName(); } + /** + * @return string + */ + public function getName() + { + return 'GitHub'; + } + /** * @param Remote $remote * @@ -58,40 +66,97 @@ public function supports(Remote $remote) */ public function synchronizePackages(Remote $remote) { - $existingPackages = $this->entityManager->getRepository('Terramar\Packages\Entity\Package')->findBy(array('remote' => $remote)); + $existingPackages = $this->entityManager->getRepository('Terramar\Packages\Entity\Package')->findBy(['remote' => $remote]); $projects = $this->getAllProjects($remote); - $packages = array(); + $packages = []; foreach ($projects as $project) { - if (!$this->packageExists($existingPackages, $project['id'])) { + $package = $this->getExistingPackage($existingPackages, $project['id']); + if ($package === null) { $package = new Package(); $package->setExternalId($project['id']); - $package->setName($project['name']); - $package->setDescription($project['description']); - $package->setFqn($project['full_name']); - $package->setWebUrl($project['clone_url']); - $package->setSshUrl($project['ssh_url']); - $package->setHookExternalId(''); $package->setRemote($remote); - $packages[] = $package; } + $package->setName($project['name']); + $package->setDescription($project['description']); + $package->setFqn($project['full_name']); + $package->setWebUrl($project['clone_url']); + $package->setSshUrl($project['ssh_url']); + $packages[] = $package; + } + + $removed = array_diff($existingPackages, $packages); + foreach ($removed as $package) { + $this->entityManager->remove($package); } return $packages; } + private function getAllProjects(Remote $remote) + { + $client = $this->getClient($remote); + + $projects = []; + $page = 1; + while (true) { + $response = $client->getHttpClient()->get('/user/repos', [ + 'page' => $page, + 'per_page' => 100, + ]); + $projects = array_merge($projects, ResponseMediator::getContent($response)); + $pageInfo = ResponseMediator::getPagination($response); + if (!isset($pageInfo['next'])) { + break; + } + + ++$page; + } + + return $projects; + } + + private function getClient(Remote $remote) + { + $config = $this->getRemoteConfig($remote); + + $client = new Client(); + $client->authenticate($config->getToken(), Client::AUTH_HTTP_TOKEN); + + return $client; + } + /** - * @return string + * @param Remote $remote + * + * @return RemoteConfiguration */ - public function getName() + private function getRemoteConfig(Remote $remote) { - return 'GitHub'; + return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration')->findOneBy(['remote' => $remote]); + } + + /** + * @param $existingPackages []Package + * @param $gitlabId + * @return Package|null + */ + private function getExistingPackage($existingPackages, $gitlabId) + { + $res = array_filter($existingPackages, function (Package $package) use ($gitlabId) { + return (string)$package->getExternalId() === (string)$gitlabId; + }); + if (count($res) === 0) { + return null; + } + return array_shift($res); } + /** * Enable a GitHub webhook for the given Package. - * + * * @param Package $package * * @return bool @@ -103,28 +168,40 @@ public function enableHook(Package $package) return true; } - $client = $this->getClient($package->getRemote()); - $url = 'repos/'.$package->getFqn().'/hooks'; - $response = $client->getHttpClient()->post($url, json_encode(array( - 'name' => 'web', - 'config' => array( - 'url' => $this->urlGenerator->generate('webhook_receive', array('id' => $package->getId()), true), - 'content_type' => 'json', - ), - 'events' => array('push', 'create'), - ))); + try { + $client = $this->getClient($package->getRemote()); + $url = 'repos/' . $package->getFqn() . '/hooks'; + $response = $client->getHttpClient()->post($url, json_encode([ + 'name' => 'web', + 'config' => [ + 'url' => $this->urlGenerator->generate('webhook_receive', ['id' => $package->getId()], + true), + 'content_type' => 'json', + ], + 'events' => ['push', 'create'], + ])); + + $hook = ResponseMediator::getContent($response); + + $package->setHookExternalId($hook['id']); + $config->setEnabled(true); - $hook = ResponseMediator::getContent($response); + return true; - $package->setHookExternalId($hook['id']); - $config->setEnabled(true); + } catch (\Exception $e) { + // TODO: Log the exception + return false; + } + } - return true; + private function getConfig(Package $package) + { + return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitHub\PackageConfiguration')->findOneBy(['package' => $package]); } /** * Disable a GitHub webhook for the given Package. - * + * * @param Package $package * * @return bool @@ -136,70 +213,24 @@ public function disableHook(Package $package) return true; } - if ($package->getHookExternalId()) { - $client = $this->getClient($package->getRemote()); - $url = 'repos/'.$package->getFqn().'/hooks/'.$package->getHookExternalId(); - $client->getHttpClient()->delete($url); - } - - $package->setHookExternalId(''); - $config->setEnabled(false); - - return true; - } - - private function getConfig(Package $package) - { - return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitHub\PackageConfiguration')->findOneBy(array('package' => $package)); - } - - /** - * @param Remote $remote - * - * @return RemoteConfiguration - */ - private function getRemoteConfig(Remote $remote) - { - return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration')->findOneBy(array('remote' => $remote)); - } - - private function getAllProjects(Remote $remote) - { - $client = $this->getClient($remote); - - $projects = array(); - $page = 1; - while (true) { - $response = $client->getHttpClient()->get('/user/repos', array( - 'page' => $page, - 'per_page' => 100, - )); - $projects = array_merge($projects, ResponseMediator::getContent($response)); - $pageInfo = ResponseMediator::getPagination($response); - if (!isset($pageInfo['next'])) { - break; + try { + if ($package->getHookExternalId()) { + $client = $this->getClient($package->getRemote()); + $url = 'repos/' . $package->getFqn() . '/hooks/' . $package->getHookExternalId(); + $client->getHttpClient()->delete($url); } - ++$page; - } + $package->setHookExternalId(''); + $config->setEnabled(false); - return $projects; - } - - private function getClient(Remote $remote) - { - $config = $this->getRemoteConfig($remote); - - $client = new Client(); - $client->authenticate($config->getToken(), Client::AUTH_HTTP_TOKEN); + return true; - return $client; - } + } catch (\Exception $e) { + // TODO: Log the exception + $package->setHookExternalId(''); + $config->setEnabled(false); - private function packageExists($existingPackages, $githubId) - { - return count(array_filter($existingPackages, function (Package $package) use ($githubId) { - return (string) $package->getExternalId() === (string) $githubId; - })) > 0; + return false; + } } } diff --git a/src/Plugin/GitLab/Controller.php b/src/Plugin/GitLab/Controller.php index 0e4b772..ce7d21c 100644 --- a/src/Plugin/GitLab/Controller.php +++ b/src/Plugin/GitLab/Controller.php @@ -44,22 +44,22 @@ public function editAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration')->findOneBy(array( + $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration')->findOneBy([ 'remote' => $id, - )); + ]); - return new Response($app->get('twig')->render('Plugin/GitLab/edit.html.twig', array( + return new Response($app->get('twig')->render('Plugin/GitLab/edit.html.twig', [ 'config' => $config ?: new RemoteConfiguration(), - ))); + ])); } public function updateAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration')->findOneBy(array( + $config = $entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration')->findOneBy([ 'remote' => $id, - )); + ]); if (!$config) { return new Response(); diff --git a/src/Plugin/GitLab/PackageConfiguration.php b/src/Plugin/GitLab/PackageConfiguration.php index d214f59..60ecfa1 100644 --- a/src/Plugin/GitLab/PackageConfiguration.php +++ b/src/Plugin/GitLab/PackageConfiguration.php @@ -41,7 +41,7 @@ class PackageConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** diff --git a/src/Plugin/GitLab/PackageSubscriber.php b/src/Plugin/GitLab/PackageSubscriber.php index ea5afe3..ec3a9a0 100644 --- a/src/Plugin/GitLab/PackageSubscriber.php +++ b/src/Plugin/GitLab/PackageSubscriber.php @@ -29,7 +29,7 @@ class PackageSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param SyncAdapter $adapter + * @param SyncAdapter $adapter * @param EntityManager $entityManager */ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) @@ -38,6 +38,18 @@ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::PACKAGE_CREATE => ['onCreatePackage', 255], + Events::PACKAGE_ENABLE => ['onEnablePackage', 255], + Events::PACKAGE_DISABLE => ['onDisablePackage', 255], + ]; + } + /** * @param PackageEvent $event */ @@ -45,7 +57,7 @@ public function onCreatePackage(PackageEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\GitLab\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config) { $config = new PackageConfiguration(); @@ -80,16 +92,4 @@ public function onDisablePackage(PackageEvent $event) $this->adapter->disableHook($package); } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::PACKAGE_CREATE => array('onCreatePackage', 255), - Events::PACKAGE_ENABLE => array('onEnablePackage', 255), - Events::PACKAGE_DISABLE => array('onDisablePackage', 255), - ); - } } diff --git a/src/Plugin/GitLab/Plugin.php b/src/Plugin/GitLab/Plugin.php index 5d7e166..1abb269 100644 --- a/src/Plugin/GitLab/Plugin.php +++ b/src/Plugin/GitLab/Plugin.php @@ -31,23 +31,29 @@ public function configure(ContainerBuilder $container) ->addArgument(new Reference('router.url_generator')); $container->getDefinition('packages.helper.sync') - ->addMethodCall('registerAdapter', array(new Reference('packages.plugin.gitlab.adapter'))); + ->addMethodCall('registerAdapter', [new Reference('packages.plugin.gitlab.adapter')]); - $container->register('packages.plugin.gitlab.package_subscriber', 'Terramar\Packages\Plugin\GitLab\PackageSubscriber') + $container->register('packages.plugin.gitlab.package_subscriber', + 'Terramar\Packages\Plugin\GitLab\PackageSubscriber') ->addArgument(new Reference('packages.plugin.gitlab.adapter')) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('kernel.event_subscriber'); - $container->register('packages.plugin.gitlab.remote_subscriber', 'Terramar\Packages\Plugin\GitLab\RemoteSubscriber') + $container->register('packages.plugin.gitlab.remote_subscriber', + 'Terramar\Packages\Plugin\GitLab\RemoteSubscriber') ->addArgument(new Reference('packages.plugin.gitlab.adapter')) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('kernel.event_subscriber'); $container->getDefinition('packages.controller_manager') - ->addMethodCall('registerController', array(Actions::REMOTE_NEW, 'Terramar\Packages\Plugin\GitLab\Controller::newAction')) - ->addMethodCall('registerController', array(Actions::REMOTE_CREATE, 'Terramar\Packages\Plugin\GitLab\Controller::createAction')) - ->addMethodCall('registerController', array(Actions::REMOTE_EDIT, 'Terramar\Packages\Plugin\GitLab\Controller::editAction')) - ->addMethodCall('registerController', array(Actions::REMOTE_UPDATE, 'Terramar\Packages\Plugin\GitLab\Controller::updateAction')); + ->addMethodCall('registerController', + [Actions::REMOTE_NEW, 'Terramar\Packages\Plugin\GitLab\Controller::newAction']) + ->addMethodCall('registerController', + [Actions::REMOTE_CREATE, 'Terramar\Packages\Plugin\GitLab\Controller::createAction']) + ->addMethodCall('registerController', + [Actions::REMOTE_EDIT, 'Terramar\Packages\Plugin\GitLab\Controller::editAction']) + ->addMethodCall('registerController', + [Actions::REMOTE_UPDATE, 'Terramar\Packages\Plugin\GitLab\Controller::updateAction']); } /** diff --git a/src/Plugin/GitLab/RemoteConfiguration.php b/src/Plugin/GitLab/RemoteConfiguration.php index 248f08a..b23edd5 100644 --- a/src/Plugin/GitLab/RemoteConfiguration.php +++ b/src/Plugin/GitLab/RemoteConfiguration.php @@ -51,7 +51,7 @@ class RemoteConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** @@ -99,7 +99,7 @@ public function getUrl() */ public function setUrl($url) { - $this->url = (string) $url; + $this->url = (string)$url; } /** @@ -115,6 +115,6 @@ public function getToken() */ public function setToken($token) { - $this->token = (string) $token; + $this->token = (string)$token; } } diff --git a/src/Plugin/GitLab/RemoteSubscriber.php b/src/Plugin/GitLab/RemoteSubscriber.php index 4edca54..5caefad 100644 --- a/src/Plugin/GitLab/RemoteSubscriber.php +++ b/src/Plugin/GitLab/RemoteSubscriber.php @@ -29,7 +29,7 @@ class RemoteSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param SyncAdapter $adapter + * @param SyncAdapter $adapter * @param EntityManager $entityManager */ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) @@ -38,6 +38,16 @@ public function __construct(SyncAdapter $adapter, EntityManager $entityManager) $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::REMOTE_DISABLE => ['onDisableRemote', 255], + ]; + } + /** * @param RemoteEvent $event */ @@ -49,21 +59,11 @@ public function onDisableRemote(RemoteEvent $event) } $packages = $this->entityManager->getRepository('Terramar\Packages\Entity\Package') - ->findBy(array('remote' => $remote)); + ->findBy(['remote' => $remote]); foreach ($packages as $package) { $this->adapter->disableHook($package); $package->setEnabled(false); } } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::REMOTE_DISABLE => array('onDisableRemote', 255), - ); - } } diff --git a/src/Plugin/GitLab/SyncAdapter.php b/src/Plugin/GitLab/SyncAdapter.php index 841f165..8e96f4e 100644 --- a/src/Plugin/GitLab/SyncAdapter.php +++ b/src/Plugin/GitLab/SyncAdapter.php @@ -13,8 +13,8 @@ use Gitlab\Client; use Gitlab\Model\Project; use Nice\Router\UrlGeneratorInterface; -use Terramar\Packages\Entity\Remote; use Terramar\Packages\Entity\Package; +use Terramar\Packages\Entity\Remote; use Terramar\Packages\Helper\SyncAdapterInterface; class SyncAdapter implements SyncAdapterInterface @@ -31,8 +31,8 @@ class SyncAdapter implements SyncAdapterInterface /** * Constructor. - * - * @param EntityManager $entityManager + * + * @param EntityManager $entityManager * @param UrlGeneratorInterface $urlGenerator */ public function __construct(EntityManager $entityManager, UrlGeneratorInterface $urlGenerator) @@ -51,6 +51,14 @@ public function supports(Remote $remote) return $remote->getAdapter() === $this->getName(); } + /** + * @return string + */ + public function getName() + { + return 'GitLab'; + } + /** * @param Remote $remote * @@ -58,102 +66,33 @@ public function supports(Remote $remote) */ public function synchronizePackages(Remote $remote) { - $existingPackages = $this->entityManager->getRepository('Terramar\Packages\Entity\Package')->findBy(array('remote' => $remote)); + /** @var []Package $existingPackages */ + $existingPackages = $this->entityManager->getRepository('Terramar\Packages\Entity\Package')->findBy(['remote' => $remote]); $projects = $this->getAllProjects($remote); - $packages = array(); + $packages = []; foreach ($projects as $project) { - if (!$this->packageExists($existingPackages, $project['id'])) { + $package = $this->getExistingPackage($existingPackages, $project['id']); + if ($package === null) { $package = new Package(); $package->setExternalId($project['id']); - $package->setName($project['name']); - $package->setDescription($project['description']); - $package->setFqn($project['path_with_namespace']); - $package->setWebUrl($project['web_url']); - $package->setSshUrl($project['ssh_url_to_repo']); - $package->setHookExternalId(''); $package->setRemote($remote); - $packages[] = $package; } + $package->setName($project['name']); + $package->setDescription($project['description']); + $package->setFqn($project['path_with_namespace']); + $package->setWebUrl($project['web_url']); + $package->setSshUrl($project['ssh_url_to_repo']); + $packages[] = $package; } - return $packages; - } - - /** - * @return string - */ - public function getName() - { - return 'GitLab'; - } - - /** - * Enable a GitLab webhook for the given Package. - * - * @param Package $package - * - * @return bool - */ - public function enableHook(Package $package) - { - $config = $this->getConfig($package); - if ($config->isEnabled()) { - return true; + $removed = array_diff($existingPackages, $packages); + foreach ($removed as $package) { + $this->entityManager->remove($package); } - $client = $this->getClient($package->getRemote()); - $project = Project::fromArray($client, (array) $client->api('projects')->show($package->getExternalId())); - $hook = $project->addHook( - $this->urlGenerator->generate('webhook_receive', array('id' => $package->getId()), true), - array('push_events' => true, 'tag_push_events' => true) - ); - $package->setHookExternalId($hook->id); - $config->setEnabled(true); - - return true; - } - - /** - * Disable a GitLab webhook for the given Package. - * - * @param Package $package - * - * @return bool - */ - public function disableHook(Package $package) - { - $config = $this->getConfig($package); - if (!$config->isEnabled()) { - return true; - } - - if ($package->getHookExternalId()) { - $client = $this->getClient($package->getRemote()); - $project = Project::fromArray($client, (array) $client->api('projects')->show($package->getExternalId())); - $project->removeHook($package->getHookExternalId()); - } - - $package->setHookExternalId(''); - $config->setEnabled(false); - - return true; - } - - private function getConfig(Package $package) - { - return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitLab\PackageConfiguration')->findOneBy(array('package' => $package)); - } - - /** - * @param Remote $remote - * - * @return RemoteConfiguration - */ - private function getRemoteConfig(Remote $remote) - { - return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration')->findOneBy(array('remote' => $remote)); + return $packages; } private function getAllProjects(Remote $remote) @@ -161,7 +100,7 @@ private function getAllProjects(Remote $remote) $client = $this->getClient($remote); $isAdmin = $client->api('users')->me()['is_admin']; - $projects = array(); + $projects = []; $page = 1; while (true) { @@ -191,16 +130,107 @@ private function getClient(Remote $remote) { $config = $this->getRemoteConfig($remote); - $client = new Client(rtrim($config->getUrl(), '/').'/api/v3/'); + $client = new Client(rtrim($config->getUrl(), '/') . '/api/v3/'); $client->authenticate($config->getToken(), Client::AUTH_HTTP_TOKEN); return $client; } - private function packageExists($existingPackages, $gitlabId) + /** + * @param Remote $remote + * + * @return RemoteConfiguration + */ + private function getRemoteConfig(Remote $remote) + { + return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration')->findOneBy(['remote' => $remote]); + } + + /** + * @param $existingPackages + * @param $gitlabId + * @return Package|null + */ + private function getExistingPackage($existingPackages, $gitlabId) + { + $res = array_filter($existingPackages, function (Package $package) use ($gitlabId) { + return (string)$package->getExternalId() === (string)$gitlabId; + }); + if (count($res) === 0) { + return null; + } + return array_shift($res); + } + + /** + * Enable a GitLab webhook for the given Package. + * + * @param Package $package + * + * @return bool + */ + public function enableHook(Package $package) { - return count(array_filter($existingPackages, function (Package $package) use ($gitlabId) { - return (string) $package->getExternalId() === (string) $gitlabId; - })) > 0; + $config = $this->getConfig($package); + if ($config->isEnabled()) { + return true; + } + try { + $client = $this->getClient($package->getRemote()); + $project = Project::fromArray($client, (array)$client->api('projects')->show($package->getExternalId())); + $hook = $project->addHook( + $this->urlGenerator->generate('webhook_receive', ['id' => $package->getId()], true), + ['push_events' => true, 'tag_push_events' => true] + ); + $package->setHookExternalId($hook->id); + $config->setEnabled(true); + + return true; + + } catch (\Exception $e) { + // TODO: Log the exception + return false; + } + } + + private function getConfig(Package $package) + { + return $this->entityManager->getRepository('Terramar\Packages\Plugin\GitLab\PackageConfiguration')->findOneBy(['package' => $package]); + } + + /** + * Disable a GitLab webhook for the given Package. + * + * @param Package $package + * + * @return bool + */ + public function disableHook(Package $package) + { + $config = $this->getConfig($package); + if (!$config->isEnabled()) { + return true; + } + + try { + if ($package->getHookExternalId()) { + $client = $this->getClient($package->getRemote()); + $project = Project::fromArray($client, + (array)$client->api('projects')->show($package->getExternalId())); + $project->removeHook($package->getHookExternalId()); + } + + $package->setHookExternalId(''); + $config->setEnabled(false); + + return true; + + } catch (\Exception $e) { + // TODO: Log the exception + $package->setHookExternalId(''); + $config->setEnabled(false); + + return false; + } } } diff --git a/src/Plugin/PluginInterface.php b/src/Plugin/PluginInterface.php index 3aaed32..c9f8022 100644 --- a/src/Plugin/PluginInterface.php +++ b/src/Plugin/PluginInterface.php @@ -14,7 +14,7 @@ /** * PluginInterface defines the implementation of a Packages plugin. * - * @see http://docs.terramarlabs.com/packages/3.1/plugins/creating-a-plugin + * @see http://docs.terramarlabs.com/packages/3.2/plugins/creating-a-plugin */ interface PluginInterface { @@ -30,14 +30,14 @@ public function configure(ContainerBuilder $container); /** * Get the plugin name. - * + * * @return string */ public function getName(); /** * Get a string identifying the plugin's (or underlying tool's) version. - * + * * @return string|null */ public function getVersion(); diff --git a/src/Plugin/RouterPluginInterface.php b/src/Plugin/RouterPluginInterface.php new file mode 100644 index 0000000..47b85b8 --- /dev/null +++ b/src/Plugin/RouterPluginInterface.php @@ -0,0 +1,32 @@ +get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration')->findOneBy(array( - 'package' => $id, - )); + $config = $entityManager->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration')->findOneBy([ + 'package' => $id, + ]); - return new Response($app->get('twig')->render('Plugin/Sami/edit.html.twig', array( - 'config' => $config, - ))); + return new Response($app->get('twig')->render('Plugin/Sami/edit.html.twig', [ + 'config' => $config, + ])); } public function updateAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration')->findOneBy(array( - 'package' => $id, - )); + $config = $entityManager->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration')->findOneBy([ + 'package' => $id, + ]); $config->setEnabled($request->get('sami_enabled') ? true : false); $config->setTitle($request->get('sami_title')); diff --git a/src/Plugin/Sami/EventSubscriber.php b/src/Plugin/Sami/EventSubscriber.php index 6ad7c4b..9c84f42 100644 --- a/src/Plugin/Sami/EventSubscriber.php +++ b/src/Plugin/Sami/EventSubscriber.php @@ -32,7 +32,7 @@ class EventSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param ResqueHelper $resqueHelper + * @param ResqueHelper $resqueHelper * @param EntityManager $entityManager */ public function __construct(ResqueHelper $resqueHelper, EntityManager $entityManager) @@ -41,6 +41,17 @@ public function __construct(ResqueHelper $resqueHelper, EntityManager $entityMan $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::PACKAGE_CREATE => ['onCreatePackage', 0], + CloneProjectEvents::PACKAGE_CLONED => ['onClonePackage', 0], + ]; + } + /** * @param PackageCloneEvent $event */ @@ -48,7 +59,7 @@ public function onClonePackage(PackageCloneEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config || !$config->isEnabled() || !$package->isEnabled()) { return; @@ -59,7 +70,7 @@ public function onClonePackage(PackageCloneEvent $event) $this->entityManager->persist($config); $this->entityManager->flush($config); - $this->resqueHelper->enqueue('default', 'Terramar\Packages\Plugin\Sami\UpdateJob', array('id' => $package->getId())); + $this->resqueHelper->enqueue('default', 'Terramar\Packages\Plugin\Sami\UpdateJob', ['id' => $package->getId()]); } /** @@ -69,7 +80,7 @@ public function onCreatePackage(PackageEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config) { $config = new PackageConfiguration(); @@ -78,15 +89,4 @@ public function onCreatePackage(PackageEvent $event) $this->entityManager->persist($config); } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::PACKAGE_CREATE => array('onCreatePackage', 0), - CloneProjectEvents::PACKAGE_CLONED => array('onClonePackage', 0), - ); - } } diff --git a/src/Plugin/Sami/PackageConfiguration.php b/src/Plugin/Sami/PackageConfiguration.php index f5dbcdf..5555366 100644 --- a/src/Plugin/Sami/PackageConfiguration.php +++ b/src/Plugin/Sami/PackageConfiguration.php @@ -81,7 +81,7 @@ class PackageConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** @@ -129,7 +129,7 @@ public function getRepositoryPath() */ public function setRepositoryPath($repositoryPath) { - $this->repositoryPath = (string) $repositoryPath; + $this->repositoryPath = (string)$repositoryPath; } /** @@ -145,7 +145,7 @@ public function getRemoteRepoPath() */ public function setRemoteRepoPath($remoteRepoPath) { - $this->remoteRepoPath = (string) $remoteRepoPath; + $this->remoteRepoPath = (string)$remoteRepoPath; } /** @@ -161,7 +161,7 @@ public function getTitle() */ public function setTitle($title) { - $this->title = (string) $title; + $this->title = (string)$title; } /** @@ -177,7 +177,7 @@ public function getTheme() */ public function setTheme($theme) { - $this->theme = (string) $theme; + $this->theme = (string)$theme; } /** @@ -193,7 +193,7 @@ public function getTags() */ public function setTags($tags) { - $this->tags = (string) $tags; + $this->tags = (string)$tags; } /** @@ -209,7 +209,7 @@ public function getRefs() */ public function setRefs($refs) { - $this->refs = (string) $refs; + $this->refs = (string)$refs; } /** @@ -225,7 +225,7 @@ public function getDocsPath() */ public function setDocsPath($docsPath) { - $this->docsPath = (string) $docsPath; + $this->docsPath = (string)$docsPath; } /** @@ -241,6 +241,6 @@ public function getTemplatesDir() */ public function setTemplatesDir($templatesDir) { - $this->templatesDir = (string) $templatesDir; + $this->templatesDir = (string)$templatesDir; } } diff --git a/src/Plugin/Sami/Plugin.php b/src/Plugin/Sami/Plugin.php index 4dcbdca..e8e8692 100644 --- a/src/Plugin/Sami/Plugin.php +++ b/src/Plugin/Sami/Plugin.php @@ -33,8 +33,10 @@ public function configure(ContainerBuilder $container) ->addTag('kernel.event_subscriber'); $container->getDefinition('packages.controller_manager') - ->addMethodCall('registerController', array(Actions::PACKAGE_EDIT, 'Terramar\Packages\Plugin\Sami\Controller::editAction')) - ->addMethodCall('registerController', array(Actions::PACKAGE_UPDATE, 'Terramar\Packages\Plugin\Sami\Controller::updateAction')); + ->addMethodCall('registerController', + [Actions::PACKAGE_EDIT, 'Terramar\Packages\Plugin\Sami\Controller::editAction']) + ->addMethodCall('registerController', + [Actions::PACKAGE_UPDATE, 'Terramar\Packages\Plugin\Sami\Controller::updateAction']); } /** diff --git a/src/Plugin/Sami/UpdateJob.php b/src/Plugin/Sami/UpdateJob.php index c84b8cb..429afd6 100644 --- a/src/Plugin/Sami/UpdateJob.php +++ b/src/Plugin/Sami/UpdateJob.php @@ -17,22 +17,6 @@ class UpdateJob extends ContainerAwareJob { - /** - * @return string - */ - private function getCacheDir(Package $package) - { - return $this->getContainer()->getParameter('app.cache_dir').'/sami/'.$package->getFqn(); - } - - /** - * @return EntityManager - */ - private function getEntityManager() - { - return $this->getContainer()->get('doctrine.orm.entity_manager'); - } - public function run($args) { $package = $this->getEntityManager()->find('Terramar\Packages\Entity\Package', $args['id']); @@ -41,7 +25,7 @@ public function run($args) } $config = $this->getEntityManager()->getRepository('Terramar\Packages\Plugin\Sami\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config) { throw new \RuntimeException('Invalid project configuration'); @@ -53,12 +37,12 @@ public function run($args) mkdir($cachePath, 0777, true); } - $configFilePath = $cachePath.'/config.php'; + $configFilePath = $cachePath . '/config.php'; $this->writeConfig($configFilePath, $package, $config); echo "Wrote config file to: $configFilePath\n"; $finder = new PhpExecutableFinder(); - $builder = new ProcessBuilder(array('vendor/bin/sami.php', 'update', $configFilePath)); + $builder = new ProcessBuilder(['vendor/bin/sami.php', 'update', $configFilePath]); $builder->setEnv('HOME', $this->getContainer()->getParameter('app.root_dir')); $builder->setPrefix($finder->find()); @@ -73,15 +57,15 @@ public function run($args) if ($config->getRemoteRepoPath()) { echo "Updating configured remote with doc changes...\n"; - $localRepoPath = $cachePath.'/remote'; + $localRepoPath = $cachePath . '/remote'; $this->emptyAndRemoveDirectory($localRepoPath); - $buildPath = $cachePath.'/build'; + $buildPath = $cachePath . '/build'; - $builder = new ProcessBuilder(array( + $builder = new ProcessBuilder([ 'clone', $config->getRemoteRepoPath(), $localRepoPath, - )); + ]); $builder->setPrefix('git'); $process = $builder->getProcess(); $process->run(function ($type, $message) { @@ -89,15 +73,15 @@ public function run($args) }); if (!$process->isSuccessful()) { - throw new \RuntimeException('Unable to clone remote repository "'.$config->getRemoteRepoPath()."\"\n"); + throw new \RuntimeException('Unable to clone remote repository "' . $config->getRemoteRepoPath() . "\"\n"); } echo "Copying generated files into repository...\n"; - $builder = new ProcessBuilder(array( + $builder = new ProcessBuilder([ '-R', - $buildPath.'/', + $buildPath . '/', '.', - )); + ]); $builder->setPrefix('cp'); $process = $builder->getProcess(); $process->setWorkingDirectory($localRepoPath); @@ -106,10 +90,10 @@ public function run($args) }); echo "Adding all files...\n"; - $builder = new ProcessBuilder(array( + $builder = new ProcessBuilder([ 'add', '.', - )); + ]); $builder->setPrefix('git'); $process = $builder->getProcess(); $process->setWorkingDirectory($localRepoPath); @@ -118,11 +102,11 @@ public function run($args) }); echo "Committing...\n"; - $builder = new ProcessBuilder(array( + $builder = new ProcessBuilder([ 'commit', '-m', 'Automated commit', - )); + ]); $builder->setPrefix('git'); $process = $builder->getProcess(); $process->setWorkingDirectory($localRepoPath); @@ -131,10 +115,10 @@ public function run($args) }); echo "Pushing...\n"; - $builder = new ProcessBuilder(array( + $builder = new ProcessBuilder([ 'push', 'origin', - )); + ]); $builder->setPrefix('git'); $process = $builder->getProcess(); $process->setWorkingDirectory($localRepoPath); @@ -144,25 +128,35 @@ public function run($args) } } - private function getRefs(PackageConfiguration $config) + /** + * @return EntityManager + */ + private function getEntityManager() { - return array_map(function ($value) { - return explode(':', $value); - }, explode(',', $config->getRefs())); + return $this->getContainer()->get('doctrine.orm.entity_manager'); + } + + /** + * @return string + */ + private function getCacheDir(Package $package) + { + return $this->getContainer()->getParameter('app.cache_dir') . '/sami/' . $package->getFqn(); } private function writeConfig($configFilePath, Package $package, PackageConfiguration $config) { $cachePath = $this->getCacheDir($package); - $tagsCode = $config->getTags() ? ' ->addFromTags(\''.implode('\',\'', explode(',', $config->getTags())).'\)'.PHP_EOL : ''; + $tagsCode = $config->getTags() ? ' ->addFromTags(\'' . implode('\',\'', + explode(',', $config->getTags())) . '\)' . PHP_EOL : ''; $refsCode = ''; foreach ($this->getRefs($config) as $ref) { - $refsCode .= ' ->add(\''.$ref[0].'\', \''.$ref[1].'\')'.PHP_EOL; + $refsCode .= ' ->add(\'' . $ref[0] . '\', \'' . $ref[1] . '\')' . PHP_EOL; } $templatesCode = ''; if ($templatesDir = $config->getTemplatesDir()) { - $templatesCode = ' \'templates_dir\' => array(\''.$templatesDir.'\'),'."\n"; + $templatesCode = ' \'templates_dir\' => array(\'' . $templatesDir . '\'),' . "\n"; } $code = <<getRefs())); + } + private function emptyAndRemoveDirectory($directory) { - $files = array_diff(scandir($directory), array('.', '..')); + $files = array_diff(scandir($directory), ['.', '..']); foreach ($files as $file) { (is_dir("$directory/$file")) ? $this->emptyAndRemoveDirectory("$directory/$file") : unlink("$directory/$file"); } diff --git a/src/Plugin/Satis/Command/BuildCommand.php b/src/Plugin/Satis/Command/BuildCommand.php index 21081ca..b9ef9a1 100644 --- a/src/Plugin/Satis/Command/BuildCommand.php +++ b/src/Plugin/Satis/Command/BuildCommand.php @@ -11,11 +11,12 @@ use Composer\Satis\Console\Command\BuildCommand as BaseCommand; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Terramar\Packages\Console\Application; /** * Wraps Satis build command. @@ -27,6 +28,16 @@ class BuildCommand extends BaseCommand implements ContainerAwareInterface */ protected $container; + /** + * Sets the container. + * + * @param ContainerInterface|null $container A ContainerInterface instance or null + */ + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + protected function configure() { parent::configure(); @@ -34,7 +45,8 @@ protected function configure() $this->setName('satis:build'); $def = $this->getDefinition(); $args = $def->getArguments(); - $args['file'] = new InputArgument('file', InputArgument::OPTIONAL, 'Satis configuration file. If left blank, one will be generated.'); + $args['file'] = new InputArgument('file', InputArgument::OPTIONAL, + 'Satis configuration file. If left blank, one will be generated.'); $def->setArguments($args); } @@ -50,16 +62,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $input->setArgument('file', $configFile); } - parent::execute($input, $output); - } + $input->setOption('skip-errors', true); - /** - * Sets the container. - * - * @param ContainerInterface|null $container A ContainerInterface instance or null - */ - public function setContainer(ContainerInterface $container = null) - { - $this->container = $container; + $app = $this->getApplication(); + if ($app instanceof Application) { + $this->setIO($app->getIO()); + } + + parent::execute($input, $output); } } diff --git a/src/Plugin/Satis/Command/UpdateCommand.php b/src/Plugin/Satis/Command/UpdateCommand.php index 5d05d0f..514ca95 100644 --- a/src/Plugin/Satis/Command/UpdateCommand.php +++ b/src/Plugin/Satis/Command/UpdateCommand.php @@ -10,15 +10,17 @@ namespace Terramar\Packages\Plugin\Satis\Command; use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; use Terramar\Packages\Console\Command\ContainerAwareCommand; -use Terramar\Packages\Entity\Package; /** * Updates the projects satis.json. + * + * @deprecated + * @see BuildCommand */ class UpdateCommand extends ContainerAwareCommand { @@ -27,24 +29,27 @@ protected function configure() $this ->setName('satis:update') ->setDescription('Updates the project\'s satis.json file') - ->setDefinition(array( + ->setDefinition([ new InputArgument('scan-dir', InputArgument::OPTIONAL, 'Directory to look for git repositories'), new InputOption('build', 'b', InputOption::VALUE_NONE, 'Build packages.json after update'), new InputOption('skip-errors', null, InputOption::VALUE_NONE, 'Skip Download or Archive errors'), - )); + ]); } /** - * @param InputInterface $input The input instance + * @param InputInterface $input The input instance * @param OutputInterface $output The output instance */ protected function execute(InputInterface $input, OutputInterface $output) { - $output->writeln(array('WARNING: The satis:update command is deprecated. It does not do anything functional but remains for backwards compatibility.', '')); + $output->writeln([ + 'WARNING: The satis:update command is deprecated. It does not do anything functional but remains for backwards compatibility.', + '', + ]); $configHelper = $this->container->get('packages.plugin.satis.config_helper'); $configFile = $configHelper->generateConfiguration(); - $skipErrors = (bool) $input->getOption('skip-errors'); + $skipErrors = (bool)$input->getOption('skip-errors'); $data = json_decode(file_get_contents($configFile), true); @@ -52,13 +57,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf('Found repository: %s', $repository['url'])); } - $output->writeln(array( + $output->writeln([ 'satis.json updated successfully.', - )); + ]); - $output->writeln(array( + $output->writeln([ sprintf('Found %s repositories.', count($data['repositories'])), - )); + ]); if ($input->getOption('build')) { $command = $this->getApplication()->find('satis:build'); diff --git a/src/Plugin/Satis/ConfigurationHelper.php b/src/Plugin/Satis/ConfigurationHelper.php index 7abc21a..99c49b5 100644 --- a/src/Plugin/Satis/ConfigurationHelper.php +++ b/src/Plugin/Satis/ConfigurationHelper.php @@ -10,8 +10,8 @@ namespace Terramar\Packages\Plugin\Satis; use Doctrine\ORM\EntityManager; +use Nice\Router\UrlGeneratorInterface; use Symfony\Component\Filesystem\Filesystem; -use Terramar\Packages\Entity\Package; class ConfigurationHelper { @@ -25,6 +25,11 @@ class ConfigurationHelper */ private $entityManager; + /** + * @var \Nice\Router\UrlGeneratorInterface + */ + private $urlGenerator; + /** * @var string */ @@ -42,29 +47,47 @@ class ConfigurationHelper /** * @param EntityManager $entityManager - * @param string $rootDir - * @param $cacheDir + * @param UrlGeneratorInterface $urlGenerator + * @paramstring $rootDir + * @param string $cacheDir + * @param array $config */ - public function __construct(EntityManager $entityManager, $rootDir, $cacheDir, array $config) - { + public function __construct( + EntityManager $entityManager, + UrlGeneratorInterface $urlGenerator, + $rootDir, + $cacheDir, + array $config + ) { $this->entityManager = $entityManager; + $this->urlGenerator = $urlGenerator; $this->filesystem = new Filesystem(); $this->rootDir = $rootDir; $this->cacheDir = $cacheDir; $this->config = $config; } - public function generateConfiguration(array $options = array()) + public function generateConfiguration(array $options = []) { - $data = array_merge($options, array( - 'name' => $this->config['name'], - 'homepage' => $this->config['homepage'], - 'output-dir' => realpath($this->config['output_dir']), - 'repositories' => array(), - 'output-html' => false, - 'require-dependencies' => true, + $data = array_merge($options, [ + 'name' => $this->config['name'], + 'homepage' => $this->config['homepage'], + 'output-dir' => realpath($this->config['output_dir']), + 'repositories' => [], + 'output-html' => false, + 'require-dependencies' => true, 'require-dev-dependencies' => true, - )); + 'config' => ['gitlab-domains' => []], + ]); + + if (isset($this->config['archive']) && $this->config['archive'] === true) { + $data['archive'] = [ + 'directory' => 'dist', + 'format' => 'tar', + 'prefix-url' => $this->config['base_path'], + 'skip-dev' => true, + ]; + } $packages = $this->entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration') ->createQueryBuilder('pc') @@ -74,23 +97,59 @@ public function generateConfiguration(array $options = array()) ->getQuery() ->getResult(); - $repositories = array_map(function (PackageConfiguration $config) { - return $config->getPackage()->getSshUrl(); - }, $packages); - foreach ($repositories as $repository) { - $data['repositories'][] = array( + $data['repositories'] = array_map(function (PackageConfiguration $config) use (&$data) { + $options = [ 'type' => 'vcs', - 'url' => $repository, - ); - } - - $this->filesystem->mkdir($this->cacheDir.'/satis'); - - $filename = tempnam($this->cacheDir.'/satis', 'satis_'); + 'url' => $config->getPackage()->getSshUrl(), + ]; + $remote = $config->getPackage()->getRemote(); + switch ($remote->getAdapter()) { + case 'GitHub': + /** @var \Terramar\Packages\Plugin\GitHub\RemoteConfiguration $remoteConfig */ + $remoteConfig = $this->entityManager->getRepository('Terramar\Packages\Plugin\GitHub\RemoteConfiguration') + ->findOneBy(['remote' => $remote]); + if (!$remoteConfig) { + throw new \RuntimeException('Unable to find RemoteConfiguration for ' . $remote->getAdapter() . ' ' . $remote->getName()); + } + + $options['github-token'] = $remoteConfig->getToken(); + + break; + + case 'GitLab': + /** @var \Terramar\Packages\Plugin\GitLab\RemoteConfiguration $remoteConfig */ + $remoteConfig = $this->entityManager->getRepository('Terramar\Packages\Plugin\GitLab\RemoteConfiguration') + ->findOneBy(['remote' => $remote]); + if (!$remoteConfig) { + throw new \RuntimeException('Unable to find RemoteConfiguration for ' . $remote->getAdapter() . ' ' . $remote->getName()); + } + + $url = parse_url($remoteConfig->getUrl(), PHP_URL_HOST); + if (!in_array($url, $data['config']['gitlab-domains'])) { + $data['config']['gitlab-domains'][] = $url; + } + + $options['gitlab-token'] = $remoteConfig->getToken(); + if (parse_url($remoteConfig->getUrl(), PHP_URL_SCHEME) === "http") { + $options['secure-http'] = false; + $data['config']['secure-http'] = false; + } + + break; + } + + return $options; + }, $packages); + + $this->filesystem->mkdir($this->cacheDir . '/satis'); + + $filename = tempnam($this->cacheDir . '/satis', 'satis_'); $this->filesystem->dumpFile($filename, json_encode($data, JSON_PRETTY_PRINT)); + echo json_encode($data, JSON_PRETTY_PRINT) . "\n"; + return $filename; } } diff --git a/src/Plugin/Satis/Controller.php b/src/Plugin/Satis/Controller.php index 397b75c..0a59dd7 100644 --- a/src/Plugin/Satis/Controller.php +++ b/src/Plugin/Satis/Controller.php @@ -19,22 +19,22 @@ public function editAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration')->findOneBy(array( - 'package' => $id, - )); + $config = $entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration')->findOneBy([ + 'package' => $id, + ]); - return new Response($app->get('twig')->render('Plugin/Satis/edit.html.twig', array( - 'config' => $config, - ))); + return new Response($app->get('twig')->render('Plugin/Satis/edit.html.twig', [ + 'config' => $config, + ])); } public function updateAction(Application $app, Request $request, $id) { /** @var \Doctrine\ORM\EntityManager $entityManager */ $entityManager = $app->get('doctrine.orm.entity_manager'); - $config = $entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration')->findOneBy(array( - 'package' => $id, - )); + $config = $entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration')->findOneBy([ + 'package' => $id, + ]); $config->setEnabled($request->get('satis_enabled') ? true : false); $entityManager->persist($config); diff --git a/src/Plugin/Satis/EventSubscriber.php b/src/Plugin/Satis/EventSubscriber.php index 0444f2b..5ed0ecb 100644 --- a/src/Plugin/Satis/EventSubscriber.php +++ b/src/Plugin/Satis/EventSubscriber.php @@ -31,7 +31,7 @@ class EventSubscriber implements EventSubscriberInterface /** * Constructor. * - * @param ResqueHelper $resqueHelper + * @param ResqueHelper $resqueHelper * @param EntityManager $entityManager */ public function __construct(ResqueHelper $resqueHelper, EntityManager $entityManager) @@ -40,6 +40,17 @@ public function __construct(ResqueHelper $resqueHelper, EntityManager $entityMan $this->entityManager = $entityManager; } + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + Events::PACKAGE_UPDATE => ['onUpdatePackage', 0], + Events::PACKAGE_CREATE => ['onCreatePackage', 0], + ]; + } + /** * @param PackageUpdateEvent $event */ @@ -47,13 +58,15 @@ public function onUpdatePackage(PackageUpdateEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config || !$config->isEnabled() || !$package->isEnabled()) { return; } - $this->resqueHelper->enqueueOnce('default', 'Terramar\Packages\Plugin\Satis\UpdateAndBuildJob'); + $this->resqueHelper->enqueueOnce('default', 'Terramar\Packages\Plugin\Satis\UpdateAndBuildJob', [ + 'package_id' => $package->getId(), + ]); } /** @@ -63,7 +76,7 @@ public function onCreatePackage(PackageEvent $event) { $package = $event->getPackage(); $config = $this->entityManager->getRepository('Terramar\Packages\Plugin\Satis\PackageConfiguration') - ->findOneBy(array('package' => $package)); + ->findOneBy(['package' => $package]); if (!$config) { $config = new PackageConfiguration(); @@ -72,15 +85,4 @@ public function onCreatePackage(PackageEvent $event) $this->entityManager->persist($config); } - - /** - * @return array - */ - public static function getSubscribedEvents() - { - return array( - Events::PACKAGE_UPDATE => array('onUpdatePackage', 0), - Events::PACKAGE_CREATE => array('onCreatePackage', 0), - ); - } } diff --git a/src/Plugin/Satis/FirewallCompilerPass.php b/src/Plugin/Satis/FirewallCompilerPass.php new file mode 100644 index 0000000..1bea410 --- /dev/null +++ b/src/Plugin/Satis/FirewallCompilerPass.php @@ -0,0 +1,30 @@ +getParameterBag()->resolveValue('%packages.configuration%'); + if (isset($config['secure_satis']) && $config['secure_satis'] === true) { + $container->getDefinition('security.firewall_matcher') + ->addMethodCall('matchPath', ['^/manage|^/packages(?!\.)']); + } + } +} \ No newline at end of file diff --git a/src/Plugin/Satis/FrontendController.php b/src/Plugin/Satis/FrontendController.php new file mode 100644 index 0000000..5fbb185 --- /dev/null +++ b/src/Plugin/Satis/FrontendController.php @@ -0,0 +1,112 @@ +secure = isset($config['secure_satis']) ? (bool)$config['secure_satis'] : true; + $this->outputDir = $config['output_dir']; + $this->basePath = $config['base_path']; + $this->authenticator = $authenticator; + } + + /** + * Handles packages.json and include/*.json requests. + * + * If secure_satis is enabled, HTTP Basic authentication will be required. + * The username and password required are those defined in config.yml. + * + * @param Request $request + * @return Response + */ + public function outputAction(Request $request) + { + if ($this->secure) { + $username = $request->getUser(); + $password = $request->getPassword(); + if ( + $this->authenticator->authenticate(new Request(['username' => $username, 'password' => $password])) !== true + ) { + return new Response('', 401, ['WWW-Authenticate' => 'Basic realm="'.$this->basePath.'"']); + } + } + + $path = $this->outputDir.urldecode($request->getPathInfo()); + if (!file_exists($path)) { + return new Response('Not Found', Response::HTTP_NOT_FOUND); + } + + return new Response( + file_get_contents($path), + Response::HTTP_OK, + ['Content-type' => 'application/json']); + } + + /** + * Handles dist/* requests when archive is enabled. + * + * If secure_satis is enabled, HTTP Basic authentication will be required. + * The username and password required are those defined in config.yml. + * + * @param Request $request + * @return Response + */ + public function distAction(Request $request) + { + if ($this->secure) { + $username = $request->getUser(); + $password = $request->getPassword(); + if ( + $this->authenticator->authenticate(new Request(['username' => $username, 'password' => $password])) !== true + ) { + return new Response('', 401, ['WWW-Authenticate' => 'Basic realm="'.$this->basePath.'"']); + } + } + + $path = $this->outputDir.urldecode($request->getPathInfo()); + if (!file_exists($path)) { + return new Response('Not Found', Response::HTTP_NOT_FOUND); + } + + return new Response( + file_get_contents($path), + Response::HTTP_OK, + ['Content-type' => 'application/x-tar', 'Content-disposition' => 'attachment']); + } +} diff --git a/src/Plugin/Satis/InventoryController.php b/src/Plugin/Satis/InventoryController.php new file mode 100644 index 0000000..0b89881 --- /dev/null +++ b/src/Plugin/Satis/InventoryController.php @@ -0,0 +1,104 @@ +getRepository(); + + $contents = []; + foreach ($repository->getPackages() as $package) { + /* @var \Composer\Package\CompletePackage $package */ + $contents[$package->getName()]['name'] = $package->getName(); + $contents[$package->getName()]['versions'][] = $package->getPrettyVersion(); + } + + return new Response($app->get('templating')->render('Plugin/Satis/Inventory/index.html.twig', [ + 'contents' => $contents, + ])); + } + + /** + * Displays the details for a given package. + */ + public function viewAction(Application $app, $id, $version = null) + { + $repository = $this->getRepository(); + + $id = str_replace('+', '/', $id); + if ($version) { + $version = str_replace('+', '/', $version); + } + + $packages = $repository->findPackages($id); + + usort($packages, function ($a, $b) { + if ($a->getReleaseDate() > $b->getReleaseDate()) { + return -1; + } + + return 1; + }); + + $package = null; + if ($version) { + foreach ($packages as $p) { + if ($p->getPrettyVersion() == $version) { + $package = $p; + } + } + } else { + /* @var \Composer\Package\CompletePackage $package */ + $package = $packages[0]; + } + + return new Response($app->get('templating')->render('Plugin/Satis/Inventory/view.html.twig', [ + 'packages' => $packages, + 'package' => $package, + ])); + } + + /** + * @return \Composer\Repository\ComposerRepository + */ + private function getRepository() + { + $configuration = $this->container->getParameter('packages.configuration'); + + $io = new ConsoleIO(new ArgvInput(), new ConsoleOutput(), new HelperSet([])); + $config = new Config(); + $config->merge([ + 'config' => [ + 'home' => $this->container->getParameter('app.root_dir'), + ], + ]); + $repository = new ComposerRepository([ + 'url' => 'file://' . $configuration['output_dir'] . '/packages.json', + ], $io, $config); + + return $repository; + } +} diff --git a/src/Plugin/Satis/PackageConfiguration.php b/src/Plugin/Satis/PackageConfiguration.php index 14c8ee8..84f2a7d 100644 --- a/src/Plugin/Satis/PackageConfiguration.php +++ b/src/Plugin/Satis/PackageConfiguration.php @@ -41,7 +41,7 @@ class PackageConfiguration */ public function setEnabled($enabled) { - $this->enabled = (bool) $enabled; + $this->enabled = (bool)$enabled; } /** diff --git a/src/Plugin/Satis/Plugin.php b/src/Plugin/Satis/Plugin.php index c9731d1..44f5305 100644 --- a/src/Plugin/Satis/Plugin.php +++ b/src/Plugin/Satis/Plugin.php @@ -10,12 +10,17 @@ namespace Terramar\Packages\Plugin\Satis; use Composer\Satis\Satis; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Terramar\Packages\Plugin\Actions; +use Terramar\Packages\Plugin\CompilerAwarePluginInterface; use Terramar\Packages\Plugin\PluginInterface; +use Terramar\Packages\Plugin\RouterPluginInterface; +use Terramar\Packages\Router\RouteCollector; -class Plugin implements PluginInterface +class Plugin implements PluginInterface, RouterPluginInterface, CompilerAwarePluginInterface { /** * Configure the given ContainerBuilder. @@ -32,19 +37,53 @@ public function configure(ContainerBuilder $container) ->addArgument(new Reference('doctrine.orm.entity_manager')) ->addTag('kernel.event_subscriber'); - $container->register('packages.plugin.satis.config_helper', 'Terramar\Packages\Plugin\Satis\ConfigurationHelper') + $container->register('packages.plugin.satis.config_helper', + 'Terramar\Packages\Plugin\Satis\ConfigurationHelper') ->addArgument(new Reference('doctrine.orm.entity_manager')) + ->addArgument(new Reference('router.url_generator')) ->addArgument('%app.root_dir%') ->addArgument('%app.cache_dir%') ->addArgument('%packages.configuration%'); + $container->register('packages.plugin.satis.frontend_controller', 'Terramar\Packages\Plugin\Satis\FrontendController') + ->addArgument('%packages.configuration%') + ->addArgument(new Reference('security.authenticator')); + + $container->register('packages.plugin.satis.inventory_controller', 'Terramar\Packages\Plugin\Satis\InventoryController') + ->addMethodCall('setContainer', [new Reference('service_container')]); + $container->getDefinition('packages.controller_manager') - ->addMethodCall('registerController', array(Actions::PACKAGE_EDIT, 'Terramar\Packages\Plugin\Satis\Controller::editAction')) - ->addMethodCall('registerController', array(Actions::PACKAGE_UPDATE, 'Terramar\Packages\Plugin\Satis\Controller::updateAction')); + ->addMethodCall('registerController', + [Actions::PACKAGE_EDIT, 'Terramar\Packages\Plugin\Satis\Controller::editAction']) + ->addMethodCall('registerController', + [Actions::PACKAGE_UPDATE, 'Terramar\Packages\Plugin\Satis\Controller::updateAction']); $container->getDefinition('packages.command_registry') - ->addMethodCall('addCommand', array('Terramar\Packages\Plugin\Satis\Command\BuildCommand')) - ->addMethodCall('addCommand', array('Terramar\Packages\Plugin\Satis\Command\UpdateCommand')); + ->addMethodCall('addCommand', ['Terramar\Packages\Plugin\Satis\Command\BuildCommand']) + ->addMethodCall('addCommand', ['Terramar\Packages\Plugin\Satis\Command\UpdateCommand']); + } + + /** + * Configure the given RouteCollector. + * + * This method allows a plugin to register additional HTTP routes with the + * RouteCollector. + * + * @param RouteCollector $collector + * @return void + */ + public function collect(RouteCollector $collector) + { + $collector->map('/packages.json', 'satis_packages', 'packages.plugin.satis.frontend_controller:outputAction'); + $collector->map('/include/{file}', null, 'packages.plugin.satis.frontend_controller:outputAction'); + $collector->map('/dist/{group}/{package}/{file}', null, 'packages.plugin.satis.frontend_controller:distAction'); + + $collector->map('/packages', 'packages_index', + 'packages.plugin.satis.inventory_controller:indexAction'); + $collector->map('/packages/{id}', 'packages_view', + 'packages.plugin.satis.inventory_controller:viewAction'); + $collector->map('/packages/{id}/{version}', 'packages_view_version', + 'packages.plugin.satis.inventory_controller:viewAction'); } /** @@ -64,4 +103,14 @@ public function getVersion() { return Satis::VERSION; } + + /** + * Gets the CompilerPasses this plugin requires. + * + * @return array|CompilerPassInterface[] + */ + public function getCompilerPasses() + { + return array(new FirewallCompilerPass()); + } } diff --git a/src/Plugin/Satis/UpdateAndBuildJob.php b/src/Plugin/Satis/UpdateAndBuildJob.php index b0f4f35..103de72 100644 --- a/src/Plugin/Satis/UpdateAndBuildJob.php +++ b/src/Plugin/Satis/UpdateAndBuildJob.php @@ -9,23 +9,44 @@ namespace Terramar\Packages\Plugin\Satis; +use Doctrine\ORM\EntityManager; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\ProcessBuilder; +use Terramar\Packages\Entity\Package; use Terramar\Packages\Job\ContainerAwareJob; class UpdateAndBuildJob extends ContainerAwareJob { public function run($args) { + /** @var Package $package */ + $package = $this->getEntityManager()->getRepository(Package::class)->find($args['package_id']); + $finder = new PhpExecutableFinder(); - $builder = new ProcessBuilder(array('bin/console', 'satis:update', '--build', '--skip-errors')); + $builder = new ProcessBuilder([ + 'bin/console', + 'satis:build', + '--repository-url', + $package->getSshUrl(), + ]); + $builder->setEnv('HOME', $this->getContainer()->getParameter('app.root_dir')); $builder->setPrefix($finder->find()); $builder->setTimeout(null); + echo $builder->getProcess()->getCommandLine() . "\n"; + $process = $builder->getProcess(); $process->run(function ($type, $message) { echo $message; }); } + + /** + * @return EntityManager + */ + private function getEntityManager() + { + return $this->getContainer()->get('doctrine.orm.entity_manager'); + } } diff --git a/src/Router/RouteCollector.php b/src/Router/RouteCollector.php index 201daee..bd48d55 100644 --- a/src/Router/RouteCollector.php +++ b/src/Router/RouteCollector.php @@ -10,9 +10,24 @@ namespace Terramar\Packages\Router; use Nice\Router\RouteCollector as BaseCollector; +use Terramar\Packages\Plugin\RouterPluginInterface; class RouteCollector extends BaseCollector { + /** + * @var RouterPluginInterface[] + */ + private $plugins = []; + + /** + * Register a router plugin with the collector. + * + * @param RouterPluginInterface $plugin + */ + public function registerPlugin(RouterPluginInterface $plugin) { + $this->plugins[$plugin->getName()] = $plugin; + } + /** * Perform any collection. */ @@ -22,20 +37,34 @@ protected function collectRoutes() $this->map('/login', 'login', 'Terramar\Packages\Controller\DefaultController::loginAction'); $this->map('/logout', 'logout', ''); - $this->map('/webhook/{id}/receive', 'webhook_receive', 'Terramar\Packages\Controller\WebHookController::receiveAction', ['POST']); + $this->map('/webhook/{id}/receive', 'webhook_receive', + 'Terramar\Packages\Controller\WebHookController::receiveAction', ['POST']); $this->map('/manage', 'manage', 'Terramar\Packages\Controller\ManageController::indexAction'); - $this->map('/manage/packages', 'manage_packages', 'Terramar\Packages\Controller\PackageController::indexAction'); - $this->map('/manage/package/{id}/edit', 'manage_package_edit', 'Terramar\Packages\Controller\PackageController::editAction'); - $this->map('/manage/package/{id}/update', 'manage_package_update', 'Terramar\Packages\Controller\PackageController::updateAction', ['POST']); - $this->map('/manage/package/{id}/toggle', 'manage_package_toggle', 'Terramar\Packages\Controller\PackageController::toggleAction'); + $this->map('/manage/packages', 'manage_packages', + 'Terramar\Packages\Controller\PackageController::indexAction'); + $this->map('/manage/package/{id}/edit', 'manage_package_edit', + 'Terramar\Packages\Controller\PackageController::editAction'); + $this->map('/manage/package/{id}/update', 'manage_package_update', + 'Terramar\Packages\Controller\PackageController::updateAction', ['POST']); + $this->map('/manage/package/{id}/toggle', 'manage_package_toggle', + 'Terramar\Packages\Controller\PackageController::toggleAction'); $this->map('/manage/remotes', 'manage_remotes', 'Terramar\Packages\Controller\RemoteController::indexAction'); - $this->map('/manage/remote/new', 'manage_remote_new', 'Terramar\Packages\Controller\RemoteController::newAction'); - $this->map('/manage/remote/create', 'manage_remote_create', 'Terramar\Packages\Controller\RemoteController::createAction', ['POST']); - $this->map('/manage/remote/{id}/edit', 'manage_remote_edit', 'Terramar\Packages\Controller\RemoteController::editAction'); - $this->map('/manage/remote/{id}/update', 'manage_remote_update', 'Terramar\Packages\Controller\RemoteController::updateAction', ['POST']); - $this->map('/manage/remote/{id}/sync', 'manage_remote_sync', 'Terramar\Packages\Controller\RemoteController::syncAction'); + $this->map('/manage/remote/new', 'manage_remote_new', + 'Terramar\Packages\Controller\RemoteController::newAction'); + $this->map('/manage/remote/create', 'manage_remote_create', + 'Terramar\Packages\Controller\RemoteController::createAction', ['POST']); + $this->map('/manage/remote/{id}/edit', 'manage_remote_edit', + 'Terramar\Packages\Controller\RemoteController::editAction'); + $this->map('/manage/remote/{id}/update', 'manage_remote_update', + 'Terramar\Packages\Controller\RemoteController::updateAction', ['POST']); + $this->map('/manage/remote/{id}/sync', 'manage_remote_sync', + 'Terramar\Packages\Controller\RemoteController::syncAction'); + + foreach ($this->plugins as $plugin) { + $plugin->collect($this); + } } } diff --git a/src/Security/FirewallSubscriber.php b/src/Security/FirewallSubscriber.php new file mode 100644 index 0000000..683e1f8 --- /dev/null +++ b/src/Security/FirewallSubscriber.php @@ -0,0 +1,200 @@ +firewallMatcher = $firewallMatcher; + $this->authMatcher = $authMatcher; + $this->logoutMatcher = $logoutMatcher; + $this->loginPath = $loginPath; + $this->successPath = $successPath; + $this->tokenKey = $tokenKey; + $this->authenticator = $authenticator; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + $request = $event->getRequest(); + if ($this->authMatcher->matches($request)) { + $this->handleAuthentication($event); + + return; + } + + if ($this->logoutMatcher->matches($request)) { + $this->handleLogout($event); + + return; + } + + if (!$this->firewallMatcher->matches($request)) { + return; + } + + if (!$request->hasSession()) { + $event->setResponse(new Response('', 403)); + + return; + } + + if (!$request->getSession()->has($this->tokenKey)) { + $this->redirectForAuthentication($event); + } + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 8), + ); + } + + private function handleAuthentication(GetResponseEvent $event) + { + $request = $event->getRequest(); + $session = $request->getSession(); + + if (!$session) { + $event->setResponse(new Response('', 403)); + + return; + } + + if ($this->authenticator->authenticate($request)) { + $session->set($this->tokenKey, true); + + $successEvent = new SecurityEvent($request); + $this->eventDispatcher->dispatch(Events::LOGIN_SUCCESS, $successEvent); + + $successPath = $request->getSession()->get(self::SUCCESS_REDIR_SESSION_KEY, $this->successPath); + if ($successPath === $this->loginPath || $successPath === '/logout') { + // TODO: The '/logout' check is a hack, should probably inject the configuration value. + $successPath = $this->successPath; + } + $request->getSession()->remove(self::SUCCESS_REDIR_SESSION_KEY); + $event->setResponse(new RedirectResponse($event->getRequest()->getBaseUrl().$successPath)); + } else { + $failEvent = new SecurityEvent($request); + $this->eventDispatcher->dispatch(Events::LOGIN_FAIL, $failEvent); + + $this->redirectForAuthentication($event); + } + } + + private function redirectForAuthentication(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->getSession()->set(self::SUCCESS_REDIR_SESSION_KEY, $request->getPathInfo()); + $event->setResponse(new RedirectResponse($event->getRequest()->getBaseUrl().$this->loginPath)); + } + + private function handleLogout(GetResponseEvent $event) + { + $request = $event->getRequest(); + $session = $request->getSession(); + $session->remove($this->tokenKey); + + $logoutEvent = new SecurityEvent($request); + $this->eventDispatcher->dispatch(Events::LOGOUT, $logoutEvent); + + $this->redirectForAuthentication($event); + } +} diff --git a/src/Twig/PackagesConfigExtension.php b/src/Twig/PackagesConfigExtension.php new file mode 100644 index 0000000..5bcc502 --- /dev/null +++ b/src/Twig/PackagesConfigExtension.php @@ -0,0 +1,54 @@ +config = $config; + } + + /** + * @return array + */ + public function getGlobals() + { + return [ + 'packages_conf' => [ + 'name' => $this->config['name'], + 'homepage' => $this->config['homepage'], + 'contact_email' => $this->config['contact_email'], + ], + ]; + } + + /** + * @return string + */ + public function getName() + { + return 'packages_conf'; + } +} diff --git a/src/Twig/PluginControllerExtension.php b/src/Twig/PluginControllerExtension.php index c168077..dd7fb73 100644 --- a/src/Twig/PluginControllerExtension.php +++ b/src/Twig/PluginControllerExtension.php @@ -10,8 +10,8 @@ namespace Terramar\Packages\Twig; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Terramar\Packages\Plugin\ControllerManagerInterface; /** @@ -38,7 +38,7 @@ class PluginControllerExtension extends \Twig_Extension * Constructor. * * @param ControllerManagerInterface $manager - * @param FragmentHandler $handler + * @param FragmentHandler $handler */ public function __construct(ControllerManagerInterface $manager, FragmentHandler $handler) { @@ -59,10 +59,11 @@ public function setRequest(Request $request = null) */ public function getFunctions() { - return array( - new \Twig_SimpleFunction('render', array($this, 'render'), array('is_safe' => array('html'))), - new \Twig_SimpleFunction('plugin_controllers', array($this, 'getControllers')), - ); + return [ + new \Twig_SimpleFunction('render', [$this, 'render'], ['is_safe' => ['html']]), + new \Twig_SimpleFunction('plugin_controllers', [$this, 'getControllers']), + new \Twig_SimpleFunction('md5', 'md5'), + ]; } /** @@ -83,13 +84,13 @@ public function render($uri) * @param array $params * @return array|ControllerReference */ - public function getControllers($action, $params = array()) + public function getControllers($action, $params = []) { $params['app'] = $this->request->get('app'); return array_map(function ($controller) use ($params) { - return new ControllerReference($controller, $params); - }, $this->manager->getControllers($action)); + return new ControllerReference($controller, $params); + }, $this->manager->getControllers($action)); } /** diff --git a/src/Twig/SecurityExtension.php b/src/Twig/SecurityExtension.php new file mode 100644 index 0000000..1fc6819 --- /dev/null +++ b/src/Twig/SecurityExtension.php @@ -0,0 +1,55 @@ +request !== null) { + return $this->request->getSession()->get('__nice.is_authenticated', false); + } + return false; + }) + ]; + } + + /** + * @return string + */ + public function getName() + { + return 'security'; + } + + /** + * @param Request|null $request + */ + public function setRequest(Request $request = null) + { + $this->request = $request; + } +} diff --git a/src/Version.php b/src/Version.php index ee81d62..51e54f9 100644 --- a/src/Version.php +++ b/src/Version.php @@ -11,5 +11,5 @@ class Version { - const VERSION = '3.1.5'; + const VERSION = '3.2.0'; } diff --git a/views/Default/base.html.twig b/views/Default/base.html.twig index fbd2400..65deb36 100644 --- a/views/Default/base.html.twig +++ b/views/Default/base.html.twig @@ -2,12 +2,11 @@ - Packages - Terramar Labs + Packages - {{ packages_conf.name }} - - - + + @@ -22,12 +21,19 @@

Composer Repository - for Terramar Labs + for {{ packages_conf.name }}

{% block login_link %} - + {% if logged_in() %} + + {% else %} + + {% endif %} {% endblock %}
@@ -35,14 +41,19 @@ {% block body %} {% endblock %} - + {% if packages_conf.homepage is not empty %} + {% endif %} + +
+

Proudly powered by Satis {{ constant('Composer\\Satis\\Satis::VERSION') }} + and Packages {{ constant('Terramar\\Packages\\Version::VERSION') }} + {% if updatedAt %}Last updated: {{ updatedAt|date('Y-m-d H:i:s') }}{% endif %} +

diff --git a/views/Default/index.html.twig b/views/Default/index.html.twig index 7efd567..77ac56a 100644 --- a/views/Default/index.html.twig +++ b/views/Default/index.html.twig @@ -2,7 +2,10 @@ {% block body %}

This is a private repository.

+ {% if packages_conf.contact_email is not empty %}

If you should have access, contact us. - + {{ packages_conf.contact_email }}

+ {% endif %} + Available Packages {% endblock %} diff --git a/views/Manage/index.html.twig b/views/Manage/index.html.twig index c219112..c19b5e1 100644 --- a/views/Manage/index.html.twig +++ b/views/Manage/index.html.twig @@ -18,7 +18,7 @@

- +
More info diff --git a/views/Package/edit.html.twig b/views/Package/edit.html.twig index c93d435..9ee246c 100644 --- a/views/Package/edit.html.twig +++ b/views/Package/edit.html.twig @@ -11,9 +11,17 @@

Package Information

+ What's this?  
+
@@ -27,6 +35,12 @@   Enabled
+ {% if package.enabled and package.hookExternalId is empty %} +
+ The Package is enabled, but no webhook is installed on the Remote.
+ Pushing code will not trigger updates within this application. +
+ {% endif %}
@@ -72,6 +86,12 @@ $plugins.fadeOut('fast'); } }); + $(document.getElementById('whats-this-link')).on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + $(document.getElementById('whats-this-text')).slideDown('fast'); + $(this).hide(); + }) }) {% endblock %} diff --git a/views/Plugin/GitLab/edit.html.twig b/views/Plugin/GitLab/edit.html.twig index 7abea22..17b2c68 100644 --- a/views/Plugin/GitLab/edit.html.twig +++ b/views/Plugin/GitLab/edit.html.twig @@ -9,7 +9,7 @@ Configure GitLab settings for this remote
- +
diff --git a/views/Plugin/Satis/Inventory/index.html.twig b/views/Plugin/Satis/Inventory/index.html.twig new file mode 100644 index 0000000..cbd8a53 --- /dev/null +++ b/views/Plugin/Satis/Inventory/index.html.twig @@ -0,0 +1,19 @@ +{% extends 'Default/base.html.twig' %} + +{% block body %} +

To add the following packages, you'll have to add the following code to the repositories key in composer.json. For more information see composer repositories:

+
{
+    "type": "composer",
+    "url": "{{ url('home') }}"
+},
+

Available Packages

+ + + + + + {% for package in contents %} + + {% endfor %} +
NameVersion
{{ package.name }}{{ package.versions|join(' ')|raw }}
+{% endblock %} diff --git a/views/Plugin/Satis/Inventory/view.html.twig b/views/Plugin/Satis/Inventory/view.html.twig new file mode 100644 index 0000000..05ce82d --- /dev/null +++ b/views/Plugin/Satis/Inventory/view.html.twig @@ -0,0 +1,117 @@ +{% extends 'Default/base.html.twig' %} + +{% block body %} + +

{{ package.name }}

+
+
+
+ composer require {{ package.name }} +
+ {{ package.description }} +
+
+
+

+ {% for author in package.authors %} + + {% endfor %} +

+ {% set url = 'https://' ~ package.sourceUrl|replace({ '.git': '', 'git@': '', ':': '/' }) %} + + {% if package.homepage %} + + {% endif %} +
+
+
+
+
+
{{ package.release_date|date('Y-m-d H:i:s') }}
+ {{ package.prettyVersion }} +
+
+
+

Requires

+ {% if package.requires|length > 0 %} + +
    {% for item in package.requires %}
  • {{ item.target }}: {{ item.prettyConstraint }}
  • {% endfor %}
+ {% else %} +
None
+ {% endif %} +
+
+

Requires (dev)

+ {% if package.devRequires|length > 0 %} +
    {% for item in package.devRequires %}
  • {{ item.target }}: {{ item.prettyConstraint }}
  • {% endfor %}
+ {% else %} +
None
+ {% endif %} +
+
+

Suggests

+ {% if package.suggests|length > 0 %} +
    {% for item in package.suggests %}
  • {{ item.target }}: {{ item.prettyConstraint }}
  • {% endfor %}
+ {% else %} +
None
+ {% endif %} +
+
+
+
+

Provides

+ {% if package.provides|length > 0 %} +
    {% for item in package.provides %}
  • {{ item.target }}: {{ item.prettyConstraint }}
  • {% endfor %}
+ {% else %} +
None
+ {% endif %} +
+
+

Conflicts

+ {% if package.conflicts|length > 0 %} +
    {% for item in package.conflicts %}
  • {{ item.target }}: {{ item.prettyConstraint }}
  • {% endfor %}
+ {% else %} +
None
+ {% endif %} +
+
+

Replaces

+ {% if package.replaces|length > 0 %} +
    {% for item in package.replaces %}
  • {{ item.target }}: {{ item.prettyConstraint }}
  • {% endfor %}
+ {% else %} +
None
+ {% endif %} +
+
+
+
+
+ {{ package.license|join }} +
+ {{ package.sourceReference }} +
+ {% for author in package.authors %} + {{ author.name }} <{{ author.email }}> + {% endfor %} +
+ {% for tag in package.tags %} + {{ tag }} + {% endfor %} +
+
+
+
+ +
+
+{% endblock %} diff --git a/views/Remote/edit.html.twig b/views/Remote/edit.html.twig index 74f2626..d2a33e9 100644 --- a/views/Remote/edit.html.twig +++ b/views/Remote/edit.html.twig @@ -11,8 +11,16 @@

Configure Remote

+ What's this?  
+
@@ -43,3 +51,16 @@
{% endblock %} + +{% block javascripts %} + +{% endblock %} \ No newline at end of file diff --git a/views/Remote/index.html.twig b/views/Remote/index.html.twig index 9ff5ff9..7de9d4d 100644 --- a/views/Remote/index.html.twig +++ b/views/Remote/index.html.twig @@ -7,9 +7,32 @@ {% block content %}
+ + {% if last_error %} + {# +
+
+
An error occurred while syncing.
+
+
+

Packages was unable to load information from the remote code host. Please try again.

+ Details + +
+
#} +
+

An error occurred while syncing.

+

Packages was unable to load information from the remote code host. Please try again.

+ Details + +
+ {% endif %}
-

Showing all configured remote hosts

+

Showing all configured remote code hosts

@@ -42,7 +65,7 @@ {% else %} -

No remotes configured. Add a new remote.

+

No remote code hosts configured. Add a new remote.

{% endif %}
@@ -61,5 +84,14 @@ "order": [[1, 'desc'], [0, 'asc']] }); }); + + {% if last_error %} + $('#last_error').on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + $('#last_error_msg').show(); + $(this).hide(); + }); + {% endif %} {% endblock %} diff --git a/views/Remote/new.html.twig b/views/Remote/new.html.twig index 7cf28a5..f57eed7 100644 --- a/views/Remote/new.html.twig +++ b/views/Remote/new.html.twig @@ -10,10 +10,18 @@
-

Configure Remote

+

Configure Remote

+ What's this?  
+
@@ -47,3 +55,16 @@
{% endblock %} + +{% block javascripts %} + +{% endblock %} \ No newline at end of file diff --git a/views/base.html.twig b/views/base.html.twig index a4928d6..e4b676e 100644 --- a/views/base.html.twig +++ b/views/base.html.twig @@ -2,21 +2,15 @@ - {% if block('title') is not empty %}{{ block('title') }} - {% endif %}Packages - Terramar Labs + {% if block('title') is not empty %}{{ block('title') }} - {% endif %}Packages - {{ packages_conf.name }} - - - + + - - - - + +
@@ -34,7 +28,7 @@
- - + + {% block javascripts %}{% endblock %} diff --git a/web/css/AdminLTE.css b/web/css/AdminLTE.css index fc577d9..2c35022 100755 --- a/web/css/AdminLTE.css +++ b/web/css/AdminLTE.css @@ -2976,4 +2976,11 @@ Component: timeline .plugin-list dt { font-weight: normal; -} \ No newline at end of file +} + + +.whats-this-link { + line-height: 24px; + margin-right: 10px; + margin-top: 5px; +} diff --git a/web/css/bootstrap.min.css b/web/css/bootstrap.min.css new file mode 120000 index 0000000..8be6e0f --- /dev/null +++ b/web/css/bootstrap.min.css @@ -0,0 +1 @@ +../../vendor/twbs/bootstrap/dist/css/bootstrap.min.css \ No newline at end of file diff --git a/web/css/font-awesome.min.css b/web/css/font-awesome.min.css new file mode 120000 index 0000000..6b64ecf --- /dev/null +++ b/web/css/font-awesome.min.css @@ -0,0 +1 @@ +../../vendor/fortawesome/font-awesome/css/font-awesome.min.css \ No newline at end of file diff --git a/web/css/jquery.dataTables.min.css b/web/css/jquery.dataTables.min.css new file mode 120000 index 0000000..c7f4c57 --- /dev/null +++ b/web/css/jquery.dataTables.min.css @@ -0,0 +1 @@ +../../vendor/datatables/datatables/media/css/jquery.dataTables.min.css \ No newline at end of file diff --git a/web/css/public.css b/web/css/public.css index 7029e44..5403969 100644 --- a/web/css/public.css +++ b/web/css/public.css @@ -55,20 +55,20 @@ h1 { .contact { color: #777; -} - -.contact span:before { - content: "contact@"; -} - -.contact span:after { - content: "terramarlabs.com" + user-select: none; + cursor: default; } .last-update, .contact { display: block; } +.powered-by { + margin-top: 5px; + text-align: center; + color: #777; +} + @media only screen and (min-width: 480px) { .last-update:before { content: "- "; @@ -85,4 +85,12 @@ h1 { margin: 15px; padding: 20px; } +} + +ul { + padding-left:20px; +} + +.label { + margin-right:3px; } \ No newline at end of file diff --git a/web/fonts b/web/fonts new file mode 120000 index 0000000..ac31117 --- /dev/null +++ b/web/fonts @@ -0,0 +1 @@ +../vendor/fortawesome/font-awesome/fonts \ No newline at end of file diff --git a/web/images b/web/images new file mode 120000 index 0000000..2c504a6 --- /dev/null +++ b/web/images @@ -0,0 +1 @@ +../vendor/datatables/datatables/media/images \ No newline at end of file diff --git a/web/js/AdminLTE/app.js b/web/js/AdminLTE/app.js index 80b1ea9..43351ed 100755 --- a/web/js/AdminLTE/app.js +++ b/web/js/AdminLTE/app.js @@ -79,7 +79,5 @@ $(function() { window.fixContainerHeights = _fix; }); -$(window).load(function(){ /*! pace 0.4.17 */ (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V=[].slice,W={}.hasOwnProperty,X=function(a,b){function c(){this.constructor=a}for(var d in b)W.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},Y=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};for(t={catchupTime:500,initialRate:.03,minTime:500,ghostTime:500,maxProgressPerFrame:10,easeFactor:1.25,startOnPageLoad:!0,restartOnPushState:!0,restartOnRequestAfter:500,target:"body",elements:{checkInterval:100,selectors:["body"]},eventLag:{minSamples:10,sampleCount:3,lagThreshold:3},ajax:{trackMethods:["GET"],trackWebSockets:!1}},B=function(){var a;return null!=(a="undefined"!=typeof performance&&null!==performance?"function"==typeof performance.now?performance.now():void 0:void 0)?a:+new Date},D=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,s=window.cancelAnimationFrame||window.mozCancelAnimationFrame,null==D&&(D=function(a){return setTimeout(a,50)},s=function(a){return clearTimeout(a)}),F=function(a){var b,c;return b=B(),(c=function(){var d;return d=B()-b,d>=33?(b=B(),a(d,function(){return D(c)})):setTimeout(c,33-d)})()},E=function(){var a,b,c;return c=arguments[0],b=arguments[1],a=3<=arguments.length?V.call(arguments,2):[],"function"==typeof c[b]?c[b].apply(c,a):c[b]},u=function(){var a,b,c,d,e,f,g;for(b=arguments[0],d=2<=arguments.length?V.call(arguments,1):[],f=0,g=d.length;g>f;f++)if(c=d[f])for(a in c)W.call(c,a)&&(e=c[a],null!=b[a]&&"object"==typeof b[a]&&null!=e&&"object"==typeof e?u(b[a],e):b[a]=e);return b},p=function(a){var b,c,d,e,f;for(c=b=0,e=0,f=a.length;f>e;e++)d=a[e],c+=Math.abs(d),b++;return c/b},w=function(a,b){var c,d,e;if(null==a&&(a="options"),null==b&&(b=!0),e=document.querySelector("[data-pace-"+a+"]")){if(c=e.getAttribute("data-pace-"+a),!b)return c;try{return JSON.parse(c)}catch(f){return d=f,"undefined"!=typeof console&&null!==console?console.error("Error parsing inline pace options",d):void 0}}},g=function(){function a(){}return a.prototype.on=function(a,b,c,d){var e;return null==d&&(d=!1),null==this.bindings&&(this.bindings={}),null==(e=this.bindings)[a]&&(e[a]=[]),this.bindings[a].push({handler:b,ctx:c,once:d})},a.prototype.once=function(a,b,c){return this.on(a,b,c,!0)},a.prototype.off=function(a,b){var c,d,e;if(null!=(null!=(d=this.bindings)?d[a]:void 0)){if(null==b)return delete this.bindings[a];for(c=0,e=[];cO;O++)I=S[O],C[I]===!0&&(C[I]=t[I]);i=function(a){function b(){return T=b.__super__.constructor.apply(this,arguments)}return X(b,a),b}(Error),b=function(){function a(){this.progress=0}return a.prototype.getElement=function(){var a;if(null==this.el){if(a=document.querySelector(C.target),!a)throw new i;this.el=document.createElement("div"),this.el.className="pace pace-active",document.body.className=document.body.className.replace("pace-done",""),document.body.className+=" pace-running",this.el.innerHTML='
\n
\n
\n
',null!=a.firstChild?a.insertBefore(this.el,a.firstChild):a.appendChild(this.el)}return this.el},a.prototype.finish=function(){var a;return a=this.getElement(),a.className=a.className.replace("pace-active",""),a.className+=" pace-inactive",document.body.className=document.body.className.replace("pace-running",""),document.body.className+=" pace-done"},a.prototype.update=function(a){return this.progress=a,this.render()},a.prototype.destroy=function(){try{this.getElement().parentNode.removeChild(this.getElement())}catch(a){i=a}return this.el=void 0},a.prototype.render=function(){var a,b;return null==document.querySelector(C.target)?!1:(a=this.getElement(),a.children[0].style.width=""+this.progress+"%",(!this.lastRenderedProgress||this.lastRenderedProgress|0!==this.progress|0)&&(a.children[0].setAttribute("data-progress-text",""+(0|this.progress)+"%"),this.progress>=100?b="99":(b=this.progress<10?"0":"",b+=0|this.progress),a.children[0].setAttribute("data-progress",""+b)),this.lastRenderedProgress=this.progress)},a.prototype.done=function(){return this.progress>=100},a}(),h=function(){function a(){this.bindings={}}return a.prototype.trigger=function(a,b){var c,d,e,f,g;if(null!=this.bindings[a]){for(f=this.bindings[a],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.call(this,b));return g}},a.prototype.on=function(a,b){var c;return null==(c=this.bindings)[a]&&(c[a]=[]),this.bindings[a].push(b)},a}(),N=window.XMLHttpRequest,M=window.XDomainRequest,L=window.WebSocket,v=function(a,b){var c,d,e,f;f=[];for(d in b.prototype)try{e=b.prototype[d],null==a[d]&&"function"!=typeof e?f.push(a[d]=e):f.push(void 0)}catch(g){c=g}return f},z=[],Pace.ignore=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?V.call(arguments,1):[],z.unshift("ignore"),c=b.apply(null,a),z.shift(),c},Pace.track=function(){var a,b,c;return b=arguments[0],a=2<=arguments.length?V.call(arguments,1):[],z.unshift("track"),c=b.apply(null,a),z.shift(),c},H=function(a){var b;if(null==a&&(a="GET"),"track"===z[0])return"force";if(!z.length&&C.ajax){if("socket"===a&&C.ajax.trackWebSockets)return!0;if(b=a.toUpperCase(),Y.call(C.ajax.trackMethods,b)>=0)return!0}return!1},j=function(a){function b(){var a,c=this;b.__super__.constructor.apply(this,arguments),a=function(a){var b;return b=a.open,a.open=function(d,e){return H(d)&&c.trigger("request",{type:d,url:e,request:a}),b.apply(a,arguments)}},window.XMLHttpRequest=function(b){var c;return c=new N(b),a(c),c},v(window.XMLHttpRequest,N),null!=M&&(window.XDomainRequest=function(){var b;return b=new M,a(b),b},v(window.XDomainRequest,M)),null!=L&&C.ajax.trackWebSockets&&(window.WebSocket=function(a,b){var d;return d=new L(a,b),H("socket")&&c.trigger("request",{type:"socket",url:a,protocols:b,request:d}),d},v(window.WebSocket,L))}return X(b,a),b}(h),P=null,x=function(){return null==P&&(P=new j),P},x().on("request",function(b){var c,d,e,f;return f=b.type,e=b.request,Pace.running||C.restartOnRequestAfter===!1&&"force"!==H(f)?void 0:(d=arguments,c=C.restartOnRequestAfter||0,"boolean"==typeof c&&(c=0),setTimeout(function(){var b,c,g,h,i,j;if(b="socket"===f?e.readyState<2:0<(h=e.readyState)&&4>h){for(Pace.restart(),i=Pace.sources,j=[],c=0,g=i.length;g>c;c++){if(I=i[c],I instanceof a){I.watch.apply(I,d);break}j.push(void 0)}return j}},c))}),a=function(){function a(){var a=this;this.elements=[],x().on("request",function(){return a.watch.apply(a,arguments)})}return a.prototype.watch=function(a){var b,c,d;return d=a.type,b=a.request,c="socket"===d?new m(b):new n(b),this.elements.push(c)},a}(),n=function(){function a(a){var b,c,d,e,f,g,h=this;if(this.progress=0,null!=window.ProgressEvent)for(c=null,a.addEventListener("progress",function(a){return h.progress=a.lengthComputable?100*a.loaded/a.total:h.progress+(100-h.progress)/2}),g=["load","abort","timeout","error"],d=0,e=g.length;e>d;d++)b=g[d],a.addEventListener(b,function(){return h.progress=100});else f=a.onreadystatechange,a.onreadystatechange=function(){var b;return 0===(b=a.readyState)||4===b?h.progress=100:3===a.readyState&&(h.progress=50),"function"==typeof f?f.apply(null,arguments):void 0}}return a}(),m=function(){function a(a){var b,c,d,e,f=this;for(this.progress=0,e=["error","open"],c=0,d=e.length;d>c;c++)b=e[c],a.addEventListener(b,function(){return f.progress=100})}return a}(),d=function(){function a(a){var b,c,d,f;for(null==a&&(a={}),this.elements=[],null==a.selectors&&(a.selectors=[]),f=a.selectors,c=0,d=f.length;d>c;c++)b=f[c],this.elements.push(new e(b))}return a}(),e=function(){function a(a){this.selector=a,this.progress=0,this.check()}return a.prototype.check=function(){var a=this;return document.querySelector(this.selector)?this.done():setTimeout(function(){return a.check()},C.elements.checkInterval)},a.prototype.done=function(){return this.progress=100},a}(),c=function(){function a(){var a,b,c=this;this.progress=null!=(b=this.states[document.readyState])?b:100,a=document.onreadystatechange,document.onreadystatechange=function(){return null!=c.states[document.readyState]&&(c.progress=c.states[document.readyState]),"function"==typeof a?a.apply(null,arguments):void 0}}return a.prototype.states={loading:0,interactive:50,complete:100},a}(),f=function(){function a(){var a,b,c,d,e,f=this;this.progress=0,a=0,e=[],d=0,c=B(),b=setInterval(function(){var g;return g=B()-c-50,c=B(),e.push(g),e.length>C.eventLag.sampleCount&&e.shift(),a=p(e),++d>=C.eventLag.minSamples&&a=100&&(this.done=!0),b===this.last?this.sinceLastUpdate+=a:(this.sinceLastUpdate&&(this.rate=(b-this.last)/this.sinceLastUpdate),this.catchup=(b-this.progress)/C.catchupTime,this.sinceLastUpdate=0,this.last=b),b>this.progress&&(this.progress+=this.catchup*a),c=1-Math.pow(this.progress/100,C.easeFactor),this.progress+=c*this.rate*a,this.progress=Math.min(this.lastProgress+C.maxProgressPerFrame,this.progress),this.progress=Math.max(0,this.progress),this.progress=Math.min(100,this.progress),this.lastProgress=this.progress,this.progress},a}(),J=null,G=null,q=null,K=null,o=null,r=null,Pace.running=!1,y=function(){return C.restartOnPushState?Pace.restart():void 0},null!=window.history.pushState&&(R=window.history.pushState,window.history.pushState=function(){return y(),R.apply(window.history,arguments)}),null!=window.history.replaceState&&(U=window.history.replaceState,window.history.replaceState=function(){return y(),U.apply(window.history,arguments)}),k={ajax:a,elements:d,document:c,eventLag:f},(A=function(){var a,c,d,e,f,g,h,i;for(Pace.sources=J=[],g=["ajax","elements","document","eventLag"],c=0,e=g.length;e>c;c++)a=g[c],C[a]!==!1&&J.push(new k[a](C[a]));for(i=null!=(h=C.extraSources)?h:[],d=0,f=i.length;f>d;d++)I=i[d],J.push(new I(C));return Pace.bar=q=new b,G=[],K=new l})(),Pace.stop=function(){return Pace.trigger("stop"),Pace.running=!1,q.destroy(),r=!0,null!=o&&("function"==typeof s&&s(o),o=null),A()},Pace.restart=function(){return Pace.trigger("restart"),Pace.stop(),Pace.start()},Pace.go=function(){return Pace.running=!0,q.render(),r=!1,o=F(function(a,b){var c,d,e,f,g,h,i,j,k,m,n,o,p,s,t,u,v;for(j=100-q.progress,d=o=0,e=!0,h=p=0,t=J.length;t>p;h=++p)for(I=J[h],m=null!=G[h]?G[h]:G[h]=[],g=null!=(v=I.elements)?v:[I],i=s=0,u=g.length;u>s;i=++s)f=g[i],k=null!=m[i]?m[i]:m[i]=new l(f),e&=k.done,k.done||(d++,o+=k.tick(a));return c=o/d,q.update(K.tick(a,c)),n=B(),q.done()||e||r?(q.update(100),Pace.trigger("done"),setTimeout(function(){return q.finish(),Pace.running=!1,Pace.trigger("hide")},Math.max(C.ghostTime,Math.min(C.minTime,B()-n)))):b()})},Pace.start=function(a){u(C,a),Pace.running=!0;try{q.render()}catch(b){i=b}return document.querySelector(".pace")?(Pace.trigger("start"),Pace.go()):setTimeout(Pace.start,50)},"function"==typeof define&&define.amd?define(function(){return Pace}):"object"==typeof exports?module.exports=Pace:C.startOnPageLoad&&Pace.start()}).call(this); -}); diff --git a/web/js/bootstrap.min.js b/web/js/bootstrap.min.js new file mode 120000 index 0000000..c288084 --- /dev/null +++ b/web/js/bootstrap.min.js @@ -0,0 +1 @@ +../../vendor/twbs/bootstrap/dist/js/bootstrap.min.js \ No newline at end of file diff --git a/web/js/jquery.dataTables.min.js b/web/js/jquery.dataTables.min.js new file mode 120000 index 0000000..252cd49 --- /dev/null +++ b/web/js/jquery.dataTables.min.js @@ -0,0 +1 @@ +../../vendor/datatables/datatables/media/js/jquery.dataTables.min.js \ No newline at end of file diff --git a/web/js/jquery.min.js b/web/js/jquery.min.js new file mode 120000 index 0000000..2380e19 --- /dev/null +++ b/web/js/jquery.min.js @@ -0,0 +1 @@ +../../vendor/components/jquery/jquery.min.js \ No newline at end of file