├── .scrutinizer.yml
├── .travis.yml
├── README.md
├── appveyor.yml
├── composer.json
├── res
└── target.xml
└── src
└── MagentoHackathon
└── Composer
└── Magento
├── Event
├── EventManager.php
├── InstallEvent.php
├── PackagePostInstallEvent.php
├── PackagePreInstallEvent.php
└── PackageUnInstallEvent.php
├── Factory
├── InstallStrategyFactory.php
├── InstallerFactory.php
├── ModuleManagerFactory.php
├── ParserFactory.php
├── ParserFactoryInterface.php
└── PathTranslationParserFactory.php
├── GitIgnore.php
├── InstallStrategy
├── Copy.php
├── Exception
│ ├── SourceNotExistsException.php
│ └── TargetExistsException.php
├── InstallStrategyInterface.php
├── Link.php
├── None.php
└── Symlink.php
├── InstalledPackage.php
├── InstalledPackageDumper.php
├── Installer
├── GlobResolver.php
├── Installer.php
├── InstallerInterface.php
└── TargetFilter.php
├── Listener
├── CheckAndCreateMagentoRootDirListener.php
├── GitIgnoreListener.php
└── PackagePrioritySortListener.php
├── Map
├── Map.php
└── MapCollection.php
├── ModuleManager.php
├── Parser
├── MapParser.php
├── ModmanParser.php
├── PackageXmlParser.php
├── Parser.php
├── ParserInterface.php
└── PathTranslationParser.php
├── Plugin.php
├── ProjectConfig.php
├── Repository
├── InstalledPackageFileSystemRepository.php
└── InstalledPackageRepositoryInterface.php
├── UnInstallStrategy
├── UnInstallStrategy.php
└── UnInstallStrategyInterface.php
└── Util
└── FileSystem.php
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | external_code_coverage: true
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.6
5 | - 5.5
6 | - 5.4
7 | - 5.3
8 | - hhvm
9 |
10 | matrix:
11 | allow_failures:
12 | - php: 5.3
13 | - php: hhvm
14 |
15 | install:
16 | - composer self-update
17 | - composer install --dev --prefer-source
18 |
19 | before_script:
20 | - mkdir -p build/logs
21 |
22 | script:
23 | - ./vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml
24 | - ./vendor/bin/phpcs --standard=PSR2 ./src/
25 | - ./vendor/bin/phpcs --standard=PSR2 ./tests/MagentoHackathon
26 |
27 | after_script:
28 | - wget https://scrutinizer-ci.com/ocular.phar
29 | - php ocular.phar code-coverage:upload --format=php-clover ./build/logs/clover.xml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/AydinHassan/magento-module-composer-installer)
2 | [](https://scrutinizer-ci.com/g/AydinHassan/magento-module-composer-installer/?branch=master)
3 | [](https://scrutinizer-ci.com/g/AydinHassan/magento-module-composer-installer/?branch=master)
4 |
5 | # Magento Composer Installer
6 | [](https://gitter.im/magento-hackathon/magento-composer-installer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 |
8 |
9 |
10 | The purpose of this project is to
11 | enable [composer](https://github.com/composer/composer) to install Magento modules,
12 | and automatically integrate them into a Magento installation.
13 |
14 | We strongly recommend you to also read the general composer documentations on [getcomposer.org](http://getcomposer.org)
15 |
16 | Also you should see [Using composer correctly (confoo) by Igor Wiedler](https://speakerdeck.com/igorw/using-composer-correctly-confoo)
17 |
18 |
19 | ## Project Details
20 |
21 | This project only covers the custom installer for composer. If you have problems with outdated versions,
22 | need to install magento connect modules or similar, you need to look for [packages.firegento.com](http://packages.firegento.com/)
23 |
24 |
25 | ### support contacts
26 |
27 | If you have problems please have patience, as normal support is done during free time.
28 | If you are willing to pay to get your problem fixed, communicate this from the start to get faster responses.
29 |
30 |
31 | If you need consulting, support, training or help regarding Magento and Composer,
32 | you have the chance to hire one of the following people/companies.
33 |
34 | * Daniel Fahlke aka Flyingmana (Maintainer): flyingmana@googlemail.com [@Flyingmana](https://twitter.com/Flyingmana)
35 | * brandung - Magento Team: magento-team@brandung.de (http://brandung.de)
36 | * Your Name here
37 | * Your Name here
38 | * Your Company Name here
39 | * Your Company Name here
40 |
41 | other support contacts
42 |
43 | * irc: freenode the channels #magento-composer #magento-reddit and for german speaking people #magento-de
44 | * twitter: [@firegento](https://twitter.com/firegento)
45 |
46 | ## Known issue
47 | - Error message: `Fatal error: Call to undefined method MagentoHackathon\Composer\Magento\Installer::setDeployManager()` happens when you update from 1.x to 2.x, as we switched from pure installer to plugin.
48 |
49 | Solution: remove the `vendor` directory and the `composer.lock` and do a fresh install.
50 | =======
51 | ## Known issues
52 |
53 | ### When upgrading from 1.x to 2.x
54 |
55 | The update from 1.x to 2.x has to be done with no plugins as otherwise a fatal error will be triggered (which does not hurt, just run the update again and it runs through).
56 |
57 | - Error message: `Fatal error: Call to undefined method MagentoHackathon\Composer\Magento\Installer::setDeployManager()`
58 |
59 | To prevent this error, upgrade only *magento-composer-installer* first:
60 |
61 | ```composer update --no-plugins --no-dev "magento-hackathon/magento-composer-installer"```
62 |
63 | Fallback Solutions:
64 |
65 | 1. execute `composer install` two times.
66 | 2. remove the `vendor` directory and `composer.lock` and do a fresh install.
67 |
68 | ### Timeouts and slow downloading.
69 |
70 | Mostly caused by outtages of Github, Repositories or the Internet. This is a common problem with having all
71 | packges remote.
72 |
73 | For all of this Issues you can make use of the commercial [Toran Proxy](https://toranproxy.com/).
74 | It also allows hosting of private packages and speeds up the whole downloading process.
75 |
76 | Another alternative is to look into [Satis](https://github.com/composer/satis), bare git mirrors and repository aliasing.
77 |
78 | Another way to speedup downloads over ssh(also interesting for satis users) is to improve your ssh configs.
79 | At least for newer versions of openSSH you can add the following to your ```.ssh/config``` to reuse previous connections.
80 | ```
81 | Host *
82 | ControlPath ~/.ssh/controlmasters/%r@%h:%p
83 | ControlMaster auto
84 | ControlPersist 10m
85 | ```
86 |
87 | also you need to create the ```controlmasters``` directory:
88 | ```sh
89 | mkdir ~/.ssh/controlmasters
90 | chmod go-xr ~/.ssh/controlmasters
91 | ```
92 |
93 | further information can be found on [wikibooks](http://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing)
94 |
95 | ## Usage
96 |
97 | See below for a [generic instruction on how to install composer](#installation-of-composer) if you aren't familiar with it.
98 |
99 |
100 | ### Install a module in your project
101 |
102 | If you want to use [the public Magento module repository](http://packages.firegento.com),
103 | set up your root ```composer.json``` in your project like this:
104 |
105 | ```json
106 | {
107 | "require": {
108 | "your-vendor-name/module-name": "*",
109 | "magento-hackathon/magento-composer-installer": "*"
110 | },
111 | "repositories": [
112 | {
113 | "type": "composer",
114 | "url": "http://packages.firegento.com"
115 | }
116 | ],
117 | "extra":{
118 | "magento-root-dir": "htdocs/"
119 | }
120 | }
121 | ```
122 |
123 | If you want to use a github/git/svn/etc repository,
124 | set up your root ```composer.json``` in your project like this:
125 |
126 | ```json
127 | {
128 | "require": {
129 | "magento-hackathon/magento-composer-installer":"*",
130 | "the-vendor-name/the-module-name": "*"
131 | },
132 | "repositories": [
133 | {
134 | "type": "vcs",
135 | "url": "https://github.com/magento-hackathon/magento-composer-installer"
136 | },
137 | {
138 | "type": "vcs",
139 | "url": "the/github/or/git/or/svn/etc/repository/uri-of-the-module"
140 | }
141 | ],
142 | "extra":{
143 | "magento-root-dir": "htdocs/"
144 | }
145 | }
146 | ```
147 | Notes:
148 |
149 | 1. More information about VCS repositories can be found
150 | at [getcomposer.org](http://getcomposer.org/doc/05-repositories.md#vcs)
151 |
152 |
153 |
154 | ### Change the Vendor/Name of your Module
155 |
156 | sometimes it will happen, that you change the name or the vendor of a package.
157 | For example you developed a module in your own namespace and later moved it to an organization, or you moved it
158 | from one to another organization.
159 | In this cases you should change your ```composer.json``` a bit to make it for users easier.
160 | Look for the ```replace``` statement
161 |
162 |
163 | ```json
164 | {
165 | "name": "your-new-vendor-name/module-name",
166 | "type": "magento-module",
167 | "license":"OSL-3.0",
168 | "description":"A short one line description of your module",
169 | "authors":[
170 | {
171 | "name":"Author Name",
172 | "email":"author@example.com"
173 | }
174 | ],
175 | "replace": {
176 | "your-vendor-name/module-name":"*"
177 | }
178 | }
179 | ```
180 |
181 |
182 |
183 | ### Auto add files to .gitignore
184 |
185 | If you want to have the deployed files automatically added to your .gitignore file, then you can just set the `auto-append-gitignore` key to true:
186 |
187 | ```json
188 | {
189 | ...
190 | "extra":{
191 | "magento-root-dir": "htdocs/",
192 | "auto-append-gitignore": true
193 | }
194 | ...
195 | }
196 | ```
197 |
198 | The `.gitignore` file will be loaded from the current directory, and if it does not exist, it will be created. Every set of module files, will have a comment above them
199 | describing the module name for clarity.
200 |
201 | Multiple deploys will not add additional lines to your .gitignore, they will only ever be added once.
202 |
203 |
204 | ### Testing
205 |
206 | First clone the magento-composer-installer, then install the dev-stuff:
207 |
208 | ```
209 | ./bin/composer.phar install --dev
210 | ```
211 |
212 | then run ```vendor/bin/phpunit``` in project-root directory.
213 |
214 | Note: Windows users please run ```phpunit``` with Administrator permissions.
215 |
216 |
217 | ### How to overwrite dependencies
218 |
219 | We don't want to use always the official repositories for specific dependencies.
220 | For example for development purpose or use versions with custom patches.
221 |
222 | For this case you have the _repositories_ section inside your project composer.json
223 | Here you can define own package composer.json for specific dependencies by the package name.
224 |
225 | This example shows how to use a local git projects local-master branch which even works without a composer.json inside
226 | and a project in VCS with existing composer.json, which is not yet on packagist.
227 |
228 | ```json
229 | {
230 | ...
231 | "repositories": [
232 | {
233 | "type": "package",
234 | "package": {
235 | "name": "magento-hackathon/magento-composer-installer",
236 | "version": "dev-master",
237 | "type": "composer-installer",
238 | "source": {
239 | "type": "git",
240 | "url": "/public_projects/magento/magento-composer-installer/",
241 | "reference": "local-master"
242 | },
243 | "autoload": {
244 | "psr-0": {"MagentoHackathon\\Composer\\Magento": "src/"}
245 | },
246 | "extra": {
247 | "class": "\\MagentoHackathon\\Composer\\Magento\\Installer"
248 | }
249 | }
250 | }
251 | ]
252 | }
253 | ```
254 |
255 | ## Installation of composer
256 |
257 | ### 1. Install PHP-Composer
258 |
259 | #### On Linux/Mac
260 |
261 | Go to your project root directory and run:
262 |
263 | ```
264 | mkdir bin
265 | curl -s https://getcomposer.org/installer | php -- --install-dir=bin
266 | ```
267 |
268 | #### On Windows
269 | Please take a look at http://getcomposer.org/doc/00-intro.md#installation-windows
270 |
271 | Creation of symbolic links requires the SeCreateSymbolicLinkPrivilege (“Create symbolic links”), which is granted only
272 | to administrators by default (but you can change that using security policy).
273 |
274 | To change the policies:
275 | - Launch secpol.msc via Start or Start → Run.
276 | - Open Security Settings → Local Policies → User Rights Assignment.
277 | - In the list, find the "Create symbolic links" item, which represents SeCreateSymbolicLinkPrivilege.
278 | Double-click on the item and add yourself (or the whole Users group) to the list.
279 |
280 | (Seen at http://superuser.com/questions/124679/how-do-i-create-an-mklink-in-windows-7-home-premium-as-a-regular-user#125981)
281 |
282 |
283 | ### 2. Download composer.json template
284 |
285 | See [Usage](#usage).
286 |
287 |
288 | ### 3. Install Magento modules via composer
289 |
290 | ```
291 | php bin/composer.phar install
292 | ```
293 |
294 |
295 |
296 |
297 |
298 | ## Further Information
299 |
300 | * [FAQ](doc/FAQ.md)
301 | * [Make a Magento module installable with composer](doc/MakeAModuleInstallableWithComposer.md)
302 | * [About File Mapping like for example modman](doc/Mapping.md)
303 | * [About Deploying files into your Magento root and possible configs](doc/Deploy.md)
304 |
305 | ### External Links
306 |
307 | * [Composer How to Screencast](http://www.youtube.com/watch?v=m_yprtQiFgk)
308 | * [Introducing Composer Blog on Magebase.com](http://magebase.com/magento-tutorials/composer-with-magento/)
309 | * [Magento, Composer and Symfonys Dependency Injection](http://www.piotrbelina.com/magento-composer-and-dependency-injection/)
310 | * [Using Composer for Magento(at engineyard)](https://blog.engineyard.com/2014/composer-for-magento)
311 |
312 | ### Core Contributors
313 |
314 | * Daniel Fahlke aka Flyingmana (Maintainer)
315 | * Jörg Weller
316 | * Karl Spies
317 | * Tobias Vogt
318 | * David Fuhr
319 | * Amir Tchavoshinia
320 | * Vinai Kopp (Maintainer)
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | build: false
2 | shallow_clone: true
3 | platform: 'x86'
4 | clone_folder: C:\projects\magento-composer-installer
5 | branches:
6 | except:
7 | - gh-pages
8 |
9 | install:
10 | - SET PATH=C:\Program Files\OpenSSL;%PATH%
11 | - cinst php
12 | - cd c:\tools\php
13 | - copy php.ini-production php.ini
14 | - echo date.timezone="UTC" >> php.ini
15 | - echo extension_dir=ext >> php.ini
16 | - echo extension=php_openssl.dll >> php.ini
17 | - SET PATH=C:\tools\php;%PATH%
18 | - cd C:\projects\magento-composer-installer
19 | - php -r "readfile('http://getcomposer.org/installer');" | php
20 | - php composer.phar install --prefer-dist --no-interaction
21 |
22 | test_script:
23 | - cd C:\projects\magento-composer-installer
24 | - vendor\bin\phpunit.bat tests
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"magento-hackathon/magento-composer-installer",
3 | "description":"Composer installer for Magento modules",
4 | "keywords":[ "composer-installer", "magento"],
5 | "minimum-stability":"stable",
6 | "type":"composer-plugin",
7 | "license":"OSL-3.0",
8 | "homepage":"https://github.com/magento-hackathon/magento-composer-installer",
9 | "repositories": [
10 | {
11 | "type": "composer",
12 | "url": "http://packages.firegento.com"
13 | }
14 | ],
15 | "authors":[
16 | {
17 | "name":"Aydin Hassan",
18 | "email":"aydin@hotmail.co.uk"
19 | },
20 | {
21 | "name":"Daniel Fahlke aka Flyingmana",
22 | "email":"flyingmana@googlemail.com"
23 | },
24 | {
25 | "name":"Jörg Weller",
26 | "email":"weller@flagbit.de"
27 | },
28 | {
29 | "name":"Karl Spies",
30 | "email":"karl.spies@gmx.net"
31 | },
32 | {
33 | "name":"Tobias Vogt",
34 | "email":"tobi@webguys.de"
35 | },
36 | {
37 | "name":"David Fuhr",
38 | "email":"fuhr@flagbit.de"
39 | },
40 | {
41 | "name":"Vinai Kopp",
42 | "email":"vinai@netzarbeiter.com"
43 | }
44 | ],
45 | "require":{
46 | "php": ">=5.3.2",
47 | "symfony/console": "~2.5",
48 | "composer-plugin-api": "1.0.0"
49 | },
50 | "require-dev":{
51 | "phpunit/phpunit":"~4.4",
52 | "squizlabs/php_codesniffer": "~2.1",
53 | "composer/composer":"1.0.*@dev",
54 | "mikey179/vfsStream":"~1.4"
55 | },
56 | "autoload":{
57 | "psr-0":{
58 | "MagentoHackathon\\Composer":"src/"
59 | }
60 | },
61 | "autoload-dev":{
62 | "psr-0":{
63 | "MagentoHackathon\\Composer\\Magento":"tests/"
64 | }
65 | },
66 | "archive": {
67 | "exclude": [
68 | "vendor"
69 | ]
70 | },
71 | "extra":{
72 | "class":"MagentoHackathon\\Composer\\Magento\\Plugin"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/res/target.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Event/EventManager.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class EventManager
13 | {
14 | /**
15 | * @var array
16 | */
17 | private $listeners = [];
18 |
19 | /**
20 | * @param string $event
21 | * @param callable $callback
22 | */
23 | public function listen($event, $callback)
24 | {
25 | if (!is_callable($callback)) {
26 | throw new \InvalidArgumentException(sprintf(
27 | 'Second argument should be a callable. Got: "%s"',
28 | is_object($callback) ? get_class($callback) : gettype($callback)
29 | ));
30 | }
31 |
32 | if (!isset($this->listeners[$event])) {
33 | $this->listeners[$event] = [$callback];
34 | } else {
35 | $this->listeners[$event][] = $callback;
36 | }
37 | }
38 |
39 | /**
40 | * @param Event $event
41 | */
42 | public function dispatch(Event $event)
43 | {
44 | if (!isset($this->listeners[$event->getName()])) {
45 | return;
46 | }
47 |
48 | foreach ($this->listeners[$event->getName()] as $listener) {
49 | call_user_func_array($listener, [$event]);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Event/InstallEvent.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class InstallEvent extends Event
14 | {
15 | /**
16 | * @var ArrayObject
17 | */
18 | protected $packages;
19 |
20 | /**
21 | * @param string $name
22 | * @param ArrayObject $packages
23 | */
24 | public function __construct($name, ArrayObject $packages)
25 | {
26 | parent::__construct($name);
27 | $this->packages = $packages;
28 | }
29 |
30 | /**
31 | * @return ArrayObject
32 | */
33 | public function getPackages()
34 | {
35 | return $this->packages;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Event/PackagePostInstallEvent.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class PackagePostInstallEvent extends Event
16 | {
17 | /**
18 | * @var PackageInterface
19 | */
20 | protected $composerPackage;
21 |
22 | /**
23 | * @var InstalledPackage
24 | */
25 | protected $installedPackage;
26 |
27 | /**
28 | * @param PackageInterface $composerPackage
29 | * @param InstalledPackage $installedPackage
30 | */
31 | public function __construct(PackageInterface $composerPackage, InstalledPackage $installedPackage)
32 | {
33 | parent::__construct('package-post-install');
34 | $this->composerPackage = $composerPackage;
35 | $this->installedPackage = $installedPackage;
36 | }
37 |
38 | /**
39 | * @return PackageInterface
40 | */
41 | public function getPackage()
42 | {
43 | return $this->composerPackage;
44 | }
45 |
46 | /**
47 | * @return MapCollection
48 | */
49 | public function getMappings()
50 | {
51 | return $this->installedPackage->getMappings();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Event/PackagePreInstallEvent.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class PackagePreInstallEvent extends Event
14 | {
15 | /**
16 | * @var PackageInterface
17 | */
18 | protected $package;
19 |
20 | /**
21 | * @param PackageInterface $package
22 | */
23 | public function __construct(PackageInterface $package)
24 | {
25 | parent::__construct('package-pre-install');
26 | $this->package = $package;
27 | }
28 |
29 | /**
30 | * @return PackageInterface
31 | */
32 | public function getPackage()
33 | {
34 | return $this->package;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Event/PackageUnInstallEvent.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class PackageUnInstallEvent extends Event
15 | {
16 | /**
17 | * @var InstalledPackage
18 | */
19 | protected $package;
20 |
21 | /**
22 | * @param string $name
23 | * @param InstalledPackage $package
24 | */
25 | public function __construct($name, InstalledPackage $package)
26 | {
27 | parent::__construct($name);
28 | $this->package = $package;
29 | }
30 |
31 | /**
32 | * @return InstalledPackage
33 | */
34 | public function getPackage()
35 | {
36 | return $this->package;
37 | }
38 |
39 | /**
40 | * @return MapCollection
41 | */
42 | public function getMappings()
43 | {
44 | return $this->package->getMappings();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Factory/InstallStrategyFactory.php:
--------------------------------------------------------------------------------
1 | 100,
33 | 'link' => 100,
34 | 'copy' => 101,
35 | ];
36 |
37 | /**
38 | * @var array
39 | */
40 | protected static $strategies = [
41 | 'copy' => '\MagentoHackathon\Composer\Magento\InstallStrategy\Copy',
42 | 'symlink' => '\MagentoHackathon\Composer\Magento\InstallStrategy\Symlink',
43 | 'link' => '\MagentoHackathon\Composer\Magento\InstallStrategy\Link',
44 | 'none' => '\MagentoHackathon\Composer\Magento\InstallStrategy\None',
45 | ];
46 |
47 | /**
48 | * @param ProjectConfig $config
49 | */
50 | public function __construct(ProjectConfig $config)
51 | {
52 | $this->config = $config;
53 | }
54 |
55 | /**
56 | * @param PackageInterface $package
57 | * @return InstallStrategyInterface
58 | */
59 | public function make(PackageInterface $package)
60 | {
61 | $strategyName = $this->determineStrategy($package);
62 | if (!isset(static::$strategies[$strategyName])) {
63 | $className = static::$strategies['symlink'];
64 | } else {
65 | $className = static::$strategies[$strategyName];
66 | }
67 |
68 | if ($className === static::$strategies['none']) {
69 | $instance = new $className;
70 | } else {
71 | $instance = new $className(new FileSystem);
72 | }
73 |
74 | return $instance;
75 | }
76 |
77 | /**
78 | * @param PackageInterface $package
79 | * @return string
80 | */
81 | public function determineStrategy(PackageInterface $package)
82 | {
83 | $strategyName = $this->config->getDeployStrategy();
84 | if ($this->config->hasDeployStrategyOverwrite()) {
85 | $moduleSpecificDeployStrategies = $this->config->getDeployStrategyOverwrite();
86 |
87 | if (isset($moduleSpecificDeployStrategies[$package->getName()])) {
88 | $strategyName = $moduleSpecificDeployStrategies[$package->getName()];
89 | }
90 | }
91 |
92 | return $strategyName;
93 | }
94 |
95 | /**
96 | * @param PackageInterface $package
97 | * @return int
98 | */
99 | public function getDefaultPriority(PackageInterface $package)
100 | {
101 | if (isset(static::$priorityMap[$this->determineStrategy($package)])) {
102 | return static::$priorityMap[$this->determineStrategy($package)];
103 | }
104 |
105 | return 100;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Factory/InstallerFactory.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class InstallerFactory
19 | {
20 | /**
21 | * @param ProjectConfig $config
22 | * @param EventManager $eventManager
23 | *
24 | * @return Installer
25 | */
26 | public function make(ProjectConfig $config, EventManager $eventManager)
27 | {
28 | $installStrategyFactory = new InstallStrategyFactory($config);
29 | $fileSystem = new FileSystem;
30 | $globResolver = new GlobResolver;
31 | $targetFilter = new TargetFilter($config->getMagentoDeployIgnore());
32 | $parser = new Parser(new ParserFactory($config));
33 |
34 | return new Installer(
35 | $installStrategyFactory,
36 | $fileSystem,
37 | $config,
38 | $globResolver,
39 | $targetFilter,
40 | $parser,
41 | $eventManager
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Factory/ModuleManagerFactory.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | class ModuleManagerFactory
25 | {
26 | /**
27 | * @param ProjectConfig $config
28 | * @param EventManager $eventManager
29 | * @param IOInterface $io
30 | *
31 | * @return ModuleManager
32 | */
33 | public function make(ProjectConfig $config, EventManager $eventManager, IOInterface $io)
34 | {
35 | $installStrategyFactory = new InstallStrategyFactory($config);
36 |
37 | if ($config->hasAutoAppendGitignore()) {
38 | $this->addGitIgnoreListener($eventManager, $config);
39 | }
40 |
41 | if ($io->isDebug()) {
42 | $this->addDebugListener($eventManager, $io);
43 | }
44 |
45 | $eventManager->listen(
46 | 'pre-install',
47 | new CheckAndCreateMagentoRootDirListener($config->getMagentoRootDir(false))
48 | );
49 |
50 | $eventManager->listen(
51 | 'pre-install',
52 | new PackagePrioritySortListener($installStrategyFactory, $config)
53 | );
54 |
55 | $installerFactory = new InstallerFactory;
56 | return new ModuleManager(
57 | new InstalledPackageFileSystemRepository(
58 | $config->getModuleRepositoryLocation(),
59 | new InstalledPackageDumper
60 | ),
61 | $eventManager,
62 | $config,
63 | new UnInstallStrategy(new FileSystem, $config->getMagentoRootDir()),
64 | $installerFactory->make($config, $eventManager),
65 | $installStrategyFactory
66 | );
67 | }
68 |
69 | /**
70 | * @param EventManager $eventManager
71 | * @param ProjectConfig $config
72 | */
73 | protected function addGitIgnoreListener(EventManager $eventManager, ProjectConfig $config)
74 | {
75 | $gitIgnoreLocation = sprintf('%s/.gitignore', $config->getMagentoRootDir());
76 | $gitIgnore = new GitIgnoreListener(new GitIgnore($gitIgnoreLocation));
77 |
78 | $eventManager->listen('package-post-install', [$gitIgnore, 'addNewInstalledFiles']);
79 | $eventManager->listen('package-post-uninstall', [$gitIgnore, 'removeUnInstalledFiles']);
80 | }
81 |
82 | /**
83 | * @param EventManager $eventManager
84 | * @param IOInterface $io
85 | */
86 | protected function addDebugListener(EventManager $eventManager, IOInterface $io)
87 | {
88 | $eventManager->listen('package-pre-install', function (PackagePreInstallEvent $event) use ($io) {
89 | $io->write('Start magento deploy for ' . $event->getPackage()->getName());
90 | });
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Factory/ParserFactory.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class ParserFactory implements ParserFactoryInterface
18 | {
19 |
20 | /**
21 | * @var ProjectConfig
22 | */
23 | protected $config;
24 |
25 | /**
26 | */
27 | public function __construct(ProjectConfig $config)
28 | {
29 | $this->config = $config;
30 | }
31 |
32 | /**
33 | * @param PackageInterface $package
34 | * @param string $sourceDir
35 | * @return ParserInterface
36 | * @throws \ErrorException
37 | */
38 | public function make(PackageInterface $package, $sourceDir)
39 | {
40 | $moduleSpecificMap = $this->config->getMagentoMapOverwrite();
41 | if (isset($moduleSpecificMap[$package->getName()])) {
42 | $map = $moduleSpecificMap[$package->getName()];
43 | return new MapParser($map);
44 | }
45 |
46 | $extra = $package->getExtra();
47 | if (isset($extra['map'])) {
48 | return new MapParser($extra['map']);
49 | }
50 |
51 | if (isset($extra['package-xml'])) {
52 | return new PackageXmlParser(sprintf('%s/%s', $sourceDir, $extra['package-xml']));
53 | }
54 |
55 | $modmanFile = sprintf('%s/modman', $sourceDir);
56 | if (file_exists($modmanFile)) {
57 | return new ModmanParser($modmanFile);
58 | }
59 |
60 | throw new \ErrorException(
61 | sprintf('Unable to find deploy strategy for module: "%s" no known mapping', $package->getName())
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Factory/ParserFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface ParserFactoryInterface
14 | {
15 |
16 | /**
17 | * @param PackageInterface $package
18 | * @param string $sourceDir
19 | * @return ParserInterface
20 | */
21 | public function make(PackageInterface $package, $sourceDir);
22 | }
23 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Factory/PathTranslationParserFactory.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class PathTranslationParserFactory implements ParserFactoryInterface
16 | {
17 | /**
18 | * @var ParserFactoryInterface
19 | */
20 | protected $parserFactory;
21 |
22 | /**
23 | * @var ProjectConfig
24 | */
25 | protected $config;
26 |
27 | /**
28 | * @param ParserFactoryInterface $parserFactory
29 | */
30 | public function __construct(ParserFactoryInterface $parserFactory, ProjectConfig $config)
31 | {
32 | $this->parserFactory = $parserFactory;
33 | $this->config = $config;
34 | }
35 |
36 | /**
37 | * @param PackageInterface $package
38 | * @param string $sourceDir
39 | * @return ParserInterface
40 | * @throws \ErrorException
41 | */
42 | public function make(PackageInterface $package, $sourceDir)
43 | {
44 | $parser = $this->parserFactory->make($package, $sourceDir);
45 |
46 | if ($this->config->hasPathMappingTranslations()) {
47 | $translations = $this->config->getPathMappingTranslations();
48 | return new PathTranslationParser($parser, $translations);
49 | }
50 |
51 | return $parser;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/GitIgnore.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class GitIgnore
11 | {
12 | /**
13 | * @var array
14 | */
15 | protected $lines = [];
16 |
17 | /**
18 | * @var string|null
19 | */
20 | protected $gitIgnoreLocation;
21 |
22 | /**
23 | * @var bool
24 | */
25 | protected $hasChanges = false;
26 |
27 | /**
28 | * @param string $fileLocation
29 | */
30 | public function __construct($fileLocation)
31 | {
32 | $this->gitIgnoreLocation = $fileLocation;
33 | if (file_exists($fileLocation)) {
34 | $this->lines = array_flip(file($fileLocation, FILE_IGNORE_NEW_LINES));
35 | }
36 | }
37 |
38 | /**
39 | * @param string $file
40 | */
41 | public function addEntry($file)
42 | {
43 | if (!isset($this->lines[$file])) {
44 | $this->lines[$file] = $file;
45 | }
46 | $this->hasChanges = true;
47 | }
48 |
49 | /**
50 | * @param array $files
51 | */
52 | public function addMultipleEntries(array $files)
53 | {
54 | foreach ($files as $file) {
55 | $this->addEntry($file);
56 | }
57 | }
58 |
59 | /**
60 | * @param string $file
61 | */
62 | public function removeEntry($file)
63 | {
64 | if (isset($this->lines[$file])) {
65 | unset($this->lines[$file]);
66 | $this->hasChanges = true;
67 | }
68 | }
69 |
70 | /**
71 | * @param array $files
72 | */
73 | public function removeMultipleEntries(array $files)
74 | {
75 | foreach ($files as $file) {
76 | $this->removeEntry($file);
77 | }
78 | }
79 |
80 | /**
81 | * @return array
82 | */
83 | public function getEntries()
84 | {
85 | return array_values(array_flip($this->lines));
86 | }
87 |
88 | /**
89 | * Write the file
90 | */
91 | public function write()
92 | {
93 | if ($this->hasChanges) {
94 | file_put_contents($this->gitIgnoreLocation, implode("\n", array_flip($this->lines)));
95 | }
96 | $this->hasChanges = false;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstallStrategy/Copy.php:
--------------------------------------------------------------------------------
1 | fileSystem = $fileSystem;
27 | }
28 |
29 | /**
30 | * Resolve the mappings. If source is a folder, create mappings for every file inside it.
31 | * Also if destination dir is an existing folder and its base does not match the source base,
32 | * source should be placed inside destination.
33 | *
34 | * @param string $source
35 | * @param string $destination
36 | * @param string $absoluteSource
37 | * @param string $absoluteDestination
38 | *
39 | * @return array Resolved Mappings
40 | */
41 | public function resolve($source, $destination, $absoluteSource, $absoluteDestination)
42 | {
43 | if (is_dir($absoluteDestination)
44 | && !$this->fileSystem->sourceAndDestinationBaseMatch($source, $destination)
45 | ) {
46 | // If the destination exists and is a directory
47 | // and basename of source and destination are not equal that means we want to copy
48 | // source into destination, not to destination
49 | // eg. src: code/Some_Module.xml dest: app/etc/modules
50 | // would result in Some_Module.xml being placed inside: app/etc/modules
51 | // - so: app/etc/modules/Some_Module.xml
52 | //
53 | $destination = sprintf('%s/%s', $destination, basename($source));
54 | }
55 |
56 | //dir - dir
57 | if (is_dir($absoluteSource)) {
58 | return $this->resolveDirectory($source, $destination, $absoluteSource, $absoluteDestination);
59 | }
60 |
61 | //file - to - file
62 | return [[$source, $destination]];
63 | }
64 |
65 | /**
66 | * Build an array of mappings which should be created
67 | * eg. Every file in the directory
68 | *
69 | * @param string $source
70 | * @param string $destination
71 | * @param string $absoluteSource
72 | * @param string $absoluteDestination
73 | *
74 | * @return array Array of resolved mappings
75 | */
76 | protected function resolveDirectory($source, $destination, $absoluteSource, $absoluteDestination)
77 | {
78 | $iterator = new \RecursiveIteratorIterator(
79 | new \RecursiveDirectoryIterator($absoluteSource, \RecursiveDirectoryIterator::SKIP_DOTS),
80 | \RecursiveIteratorIterator::SELF_FIRST
81 | );
82 |
83 | $resolvedMappings = [];
84 | foreach ($iterator as $item) {
85 | /** @var SplFileinfo $item */
86 |
87 | if ($item->isFile()) {
88 | $resolvedMappings[] = [
89 | sprintf('%s/%s', $source, $iterator->getSubPathname()),
90 | sprintf('%s/%s', $destination, $iterator->getSubPathname()),
91 | ];
92 | }
93 | }
94 | return $resolvedMappings;
95 | }
96 |
97 | /**
98 | * @param Map $map
99 | * @param bool $force
100 | * @throws TargetExistsException
101 | */
102 | public function create(Map $map, $force)
103 | {
104 | // If file exists and force is not specified, throw exception
105 | if (file_exists($map->getAbsoluteDestination())) {
106 | if (!$force) {
107 | throw new TargetExistsException($map->getAbsoluteDestination());
108 | }
109 | $this->fileSystem->remove($map->getAbsoluteDestination());
110 | }
111 |
112 | copy($map->getAbsoluteSource(), $map->getAbsoluteDestination());
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstallStrategy/Exception/SourceNotExistsException.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class SourceNotExistsException extends RuntimeException
13 | {
14 | /**
15 | * @var string
16 | */
17 | protected $sourceFilePath;
18 |
19 | /**
20 | * @param string $source
21 | */
22 | public function __construct($source)
23 | {
24 | $this->sourceFilePath = $source;
25 | $message = sprintf('Source "%s" does not exist', $source);
26 | parent::__construct($message);
27 | }
28 |
29 | /**
30 | * @return string
31 | */
32 | public function getSourceFilePath()
33 | {
34 | return $this->sourceFilePath;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstallStrategy/Exception/TargetExistsException.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class TargetExistsException extends RuntimeException
13 | {
14 | /**
15 | * @var string
16 | */
17 | protected $targetFilePath;
18 |
19 | /**
20 | * @param string $target
21 | */
22 | public function __construct($target)
23 | {
24 | $this->targetFilePath = $target;
25 | $message = sprintf('Target "%s" already exists (set extra.magento-force to override)', $target);
26 | parent::__construct($message);
27 | }
28 |
29 | /**
30 | * @return string
31 | */
32 | public function getTargetFilePath()
33 | {
34 | return $this->targetFilePath;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstallStrategy/InstallStrategyInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface InstallStrategyInterface
14 | {
15 |
16 | /**
17 | * Resolve the mappings. Should return an array of mappings which
18 | * should be created. Eg in the case of copy, if source is a directory, then each file inside that directory
19 | * should be returned as a mapping.
20 | *
21 | * @param string $source
22 | * @param string $destination
23 | * @param string $absoluteSource
24 | * @param string $absoluteDestination
25 | *
26 | * @return array Resolved Mappings
27 | */
28 | public function resolve($source, $destination, $absoluteSource, $absoluteDestination);
29 |
30 | /**
31 | * @param Map $map Map contains the relative and absolute source and destination
32 | * @param bool $force Whether the creation should be forced (eg if it exists already)
33 | * @throws TargetExistsException If a
34 | *
35 | */
36 | public function create(Map $map, $force);
37 | }
38 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstallStrategy/Link.php:
--------------------------------------------------------------------------------
1 | fileSystem = $fileSystem;
27 | }
28 |
29 | /**
30 | * Resolve the mappings. If source is a folder, create mappings for every file inside it.
31 | * Also if destination dir is an existing folder and its base does not match the source base,
32 | * source should be placed inside destination.
33 | *
34 | * @param string $source
35 | * @param string $destination
36 | * @param string $absoluteSource
37 | * @param string $absoluteDestination
38 | *
39 | * @return array Resolved Mappings
40 | */
41 | public function resolve($source, $destination, $absoluteSource, $absoluteDestination)
42 | {
43 | if (is_dir($absoluteDestination)
44 | && !$this->fileSystem->sourceAndDestinationBaseMatch($source, $destination)
45 | ) {
46 | // If the destination exists and is a directory
47 | // and basename of source and destination are not equal that means we want to copy
48 | // source into destination, not to destination
49 | // eg. src: code/Some_Module.xml dest: app/etc/modules
50 | // would result in Some_Module.xml being placed inside: app/etc/modules
51 | // - so: app/etc/modules/Some_Module.xml
52 | //
53 |
54 | $destination = sprintf('%s/%s', $destination, basename($source));
55 | }
56 |
57 | //dir - dir
58 | if (is_dir($absoluteSource)) {
59 | return $this->resolveDirectory($source, $destination, $absoluteSource, $absoluteDestination);
60 | }
61 |
62 | //file - to - file
63 | return [[$source, $destination]];
64 | }
65 |
66 | /**
67 | * Build an array of mappings which should be created
68 | * eg. Every file in the directory
69 | *
70 | * @param string $source
71 | * @param string $destination
72 | * @param string $absoluteSource
73 | * @param string $absoluteDestination
74 | *
75 | * @return array Array of resolved mappings
76 | */
77 | protected function resolveDirectory($source, $destination, $absoluteSource, $absoluteDestination)
78 | {
79 | $iterator = new \RecursiveIteratorIterator(
80 | new \RecursiveDirectoryIterator($absoluteSource, \RecursiveDirectoryIterator::SKIP_DOTS),
81 | \RecursiveIteratorIterator::SELF_FIRST
82 | );
83 |
84 | $resolvedMappings = [];
85 | foreach ($iterator as $item) {
86 | /** @var SplFileinfo $item */
87 | if ($item->isFile()) {
88 | $resolvedMappings[] = [
89 | sprintf('%s/%s', $source, $iterator->getSubPathname()),
90 | sprintf('%s/%s', $destination, $iterator->getSubPathname()),
91 | ];
92 | }
93 | }
94 | return $resolvedMappings;
95 | }
96 |
97 | /**
98 | * @param Map $map
99 | * @param bool $force
100 | * @throws TargetExistsException
101 | */
102 | public function create(Map $map, $force)
103 | {
104 | // If file exists and force is not specified, throw exception
105 | if (file_exists($map->getAbsoluteDestination())) {
106 | if (!$force) {
107 | throw new TargetExistsException($map->getAbsoluteDestination());
108 | }
109 | $this->fileSystem->remove($map->getAbsoluteDestination());
110 | }
111 |
112 | link($map->getAbsoluteSource(), $map->getAbsoluteDestination());
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstallStrategy/None.php:
--------------------------------------------------------------------------------
1 | fileSystem = $fileSystem;
28 | }
29 |
30 | /**
31 | * Resolve the mappings.
32 | *
33 | * For symlinks, if the destination exists and is a directory, the symlink
34 | * should be created *inside* destination
35 | *
36 | * @param string $source
37 | * @param string $destination
38 | * @param string $absoluteSource
39 | * @param string $absoluteDestination
40 | *
41 | * @return array Resolved Mappings
42 | */
43 | public function resolve($source, $destination, $absoluteSource, $absoluteDestination)
44 | {
45 | if (is_dir($absoluteDestination)) {
46 | $destination = sprintf('%s/%s', $destination, basename($source));
47 | }
48 |
49 | return [[$source, $destination]];
50 | }
51 |
52 | /**
53 | * @param Map $map
54 | * @param bool $force
55 | * @throws TargetExistsException
56 | */
57 | public function create(Map $map, $force)
58 | {
59 | //if destination exists should we overwrite it?
60 | if (is_dir($map->getAbsoluteDestination())
61 | && $this->fileSystem->sourceAndDestinationBaseMatch($map->getSource(), $map->getDestination())
62 | ) {
63 | if (!$force) {
64 | throw new TargetExistsException($map->getAbsoluteDestination());
65 | }
66 | $this->fileSystem->remove($map->getAbsoluteDestination());
67 | }
68 |
69 | return $this->symlink($map, $force);
70 | }
71 |
72 | /**
73 | * @param Map $map
74 | * @param bool $force
75 | * @throws TargetExistsException
76 | */
77 | protected function symlink(Map $map, $force)
78 | {
79 | if (is_link($map->getAbsoluteDestination())) {
80 | $symLinkCorrect = $this->fileSystem->symLinkPointsToCorrectLocation(
81 | $map->getAbsoluteDestination(),
82 | $map->getAbsoluteSource()
83 | );
84 |
85 | if ($symLinkCorrect) {
86 | return;
87 | }
88 | $this->fileSystem->remove($map->getAbsoluteDestination());
89 | }
90 |
91 | // If file exists and force is not specified, throw exception unless FORCE is set
92 | // existing symlinks are already handled
93 | if (file_exists($map->getAbsoluteDestination())) {
94 | if (!$force) {
95 | throw new TargetExistsException($map->getAbsoluteDestination());
96 | }
97 | $this->fileSystem->remove($map->getAbsoluteDestination());
98 | }
99 |
100 | $this->fileSystem->createSymlink($map->getAbsoluteSource(), $map->getAbsoluteDestination());
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstalledPackage.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class InstalledPackage
13 | {
14 | /**
15 | * @var string
16 | */
17 | protected $name;
18 |
19 | /**
20 | * @var string
21 | */
22 | protected $version;
23 |
24 | /**
25 | * @var MapCollection
26 | */
27 | protected $mappings;
28 |
29 | /**
30 | * @param string $name
31 | * @param string $version
32 | * @param MapCollection $mappings
33 | */
34 | public function __construct($name, $version, MapCollection $mappings)
35 | {
36 | $this->name = $name;
37 | $this->version = $version;
38 | $this->mappings = $mappings;
39 | }
40 |
41 | /**
42 | * @return string
43 | */
44 | public function getName()
45 | {
46 | return $this->name;
47 | }
48 |
49 | /**
50 | * @return string
51 | */
52 | public function getVersion()
53 | {
54 | return $this->version;
55 | }
56 |
57 | /**
58 | * @return string
59 | */
60 | public function getUniqueName()
61 | {
62 | return sprintf('%s-%s', $this->getName(), $this->getVersion());
63 | }
64 |
65 | /**
66 | * @return MapCollection
67 | */
68 | public function getMappings()
69 | {
70 | return $this->mappings;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/InstalledPackageDumper.php:
--------------------------------------------------------------------------------
1 | $installedPackage->getName(),
22 | 'version' => $installedPackage->getVersion(),
23 | 'mappings' => $this->dumpMappings($installedPackage->getMappings()),
24 | ];
25 | }
26 |
27 | /**
28 | * @param array $data
29 | * @return InstalledPackage
30 | */
31 | public function restore(array $data)
32 | {
33 | return new InstalledPackage($data['packageName'], $data['version'], $this->restoreMappings($data['mappings']));
34 | }
35 |
36 | /**
37 | * @param MapCollection $mappings
38 | *
39 | * @return array
40 | */
41 | private function dumpMappings(MapCollection $mappings)
42 | {
43 | return array_map(
44 | function (Map $map) {
45 | return [
46 | 'source' => $map->getSource(),
47 | 'destination' => $map->getDestination(),
48 | 'source_root' => $map->getSourceRoot(),
49 | 'destination_root' => $map->getDestinationRoot()
50 | ];
51 | },
52 | $mappings->all()
53 | );
54 | }
55 |
56 | /**
57 | * @param array $mappings
58 | *
59 | * @return MapCollection
60 | */
61 | private function restoreMappings(array $mappings)
62 | {
63 | return new MapCollection(array_map(
64 | function (array $row) {
65 | return new Map(
66 | $row['source'],
67 | $row['destination'],
68 | $row['source_root'],
69 | $row['destination_root']
70 | );
71 | },
72 | $mappings
73 | ));
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Installer/GlobResolver.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | final class GlobResolver
19 | {
20 |
21 | /**
22 | * Resolve glob mappings to file -> file mappings
23 | *
24 | * @param MapCollection $maps
25 | *
26 | * @return MapCollection
27 | */
28 | public function resolve(MapCollection $maps)
29 | {
30 |
31 | $updatedMappings = [];
32 | foreach ($maps as $mapping) {
33 | /** @var Map $mapping */
34 | if (file_exists($mapping->getAbsoluteSource())) {
35 | //file is a file, we don't care about this
36 | $updatedMappings[] = $mapping;
37 | continue;
38 | }
39 |
40 | //not a file, is it a glob?
41 | $iterator = new GlobIterator($mapping->getAbsoluteSource());
42 |
43 | try {
44 | if (!$iterator->count()) {
45 | //we just skip this if there are no results
46 | //if there are no results should we just remove this mapping?
47 | //it kind of makes sense to as it is not necessarily an error
48 | //if a glob produces no results
49 | $updatedMappings[] = $mapping;
50 | continue;
51 | }
52 | } catch (LogicException $e) {
53 | /**
54 | * This a PHP bug where a LogicException is thrown if no files exist
55 | * @link https://bugs.php.net/bug.php?id=55701
56 | */
57 | $updatedMappings[] = $mapping;
58 | continue;
59 | }
60 |
61 | //add each glob as a separate mapping
62 | foreach ($iterator as $globResult) {
63 | $updatedMappings[] = $this->processMapping($globResult, $mapping);
64 | }
65 | }
66 |
67 | return new MapCollection($updatedMappings);
68 | }
69 |
70 | /**
71 | * @param \SplFileInfo $globMatch
72 | * @param Map $map
73 | *
74 | * @return Map
75 | */
76 | protected function processMapping(\SplFileInfo $globMatch, Map $map)
77 | {
78 | $absolutePath = $globMatch->getPathname();
79 |
80 | //get the relative path to this file/dir - strip of the source path
81 | //+1 to strip leading slash
82 | $source = substr($absolutePath, strlen($map->getSourceRoot()) + 1);
83 | $destination = sprintf('%s/%s', $map->getDestination(), $globMatch->getFilename());
84 |
85 | return new Map($source, $destination, $map->getSourceRoot(), $map->getDestinationRoot());
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Installer/Installer.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class Installer implements InstallerInterface
22 | {
23 |
24 | /**
25 | * @var InstallStrategyFactory
26 | */
27 | protected $installStrategyFactory;
28 |
29 | /**
30 | * @var FileSystem
31 | */
32 | protected $fileSystem;
33 |
34 | /**
35 | * @var ProjectConfig
36 | */
37 | protected $config;
38 |
39 | /**
40 | * @var GlobResolver
41 | */
42 | protected $globResolver;
43 |
44 | /**
45 | * @var TargetFilter
46 | */
47 | protected $targetFilter;
48 |
49 | /**
50 | * @var Parser
51 | */
52 | protected $parser;
53 |
54 | /**
55 | * @var EventManager
56 | */
57 | protected $eventManager;
58 |
59 | /**
60 | * @param InstallStrategyFactory $installStrategyFactory
61 | * @param FileSystem $fileSystem
62 | * @param ProjectConfig $config
63 | * @param GlobResolver $globResolver
64 | * @param TargetFilter $targetFilter
65 | * @param Parser $parser
66 | * @param EventManager $eventManager
67 | */
68 | public function __construct(
69 | InstallStrategyFactory $installStrategyFactory,
70 | FileSystem $fileSystem,
71 | ProjectConfig $config,
72 | GlobResolver $globResolver,
73 | TargetFilter $targetFilter,
74 | Parser $parser,
75 | EventManager $eventManager
76 | ) {
77 | $this->installStrategyFactory = $installStrategyFactory;
78 | $this->fileSystem = $fileSystem;
79 | $this->config = $config;
80 | $this->globResolver = $globResolver;
81 | $this->targetFilter = $targetFilter;
82 | $this->parser = $parser;
83 | $this->eventManager = $eventManager;
84 | }
85 |
86 | /**
87 | * Delegate installation to the particular strategy
88 | *
89 | * @param PackageInterface $package
90 | * @param string $packageSourceDirectory
91 | * @return MapCollection
92 | * @throws \ErrorException
93 | */
94 | public function install(PackageInterface $package, $packageSourceDirectory)
95 | {
96 | $installStrategy = $this->installStrategyFactory->make($package);
97 | $force = $this->config->getMagentoForceByPackageName($package->getName());
98 |
99 | $mappings = $this->parser->getMappings(
100 | $package,
101 | $packageSourceDirectory,
102 | $this->config->getMagentoRootDir()
103 | );
104 |
105 | //lets expand glob mappings first
106 | $mappings = $this->globResolver->resolve($mappings);
107 |
108 | $this->createMissingDirectories($mappings);
109 |
110 | $mappings = $this->resolveMappings($mappings, $installStrategy);
111 | $mappings = $this->removeIgnoredMappings($package, $mappings);
112 | $mappings = $this->removeNonExistingSourceMappings($mappings);
113 |
114 | foreach ($mappings as $map) {
115 | /** @var Map $map */
116 | $this->fileSystem->ensureDirectoryExists(dirname($map->getAbsoluteDestination()));
117 | try {
118 | $installStrategy->create($map, $force);
119 | } catch (TargetExistsException $e) {
120 | //dispath event so console can log?
121 | //re-throw for now
122 | throw $e;
123 | }
124 | }
125 |
126 | return $mappings;
127 | }
128 |
129 | /**
130 | * If raw Map destination ends with a directory separator,
131 | * source is not a directory and the destination file does not exist
132 | * create destination s a directory
133 | *
134 | * @param MapCollection $mappings
135 | */
136 | protected function createMissingDirectories(MapCollection $mappings)
137 | {
138 | foreach ($mappings as $map) {
139 | /** @var Map $map */
140 |
141 | // Create target directory if it ends with a directory separator
142 | if ($this->fileSystem->endsWithDirectorySeparator($map->getRawDestination())
143 | && !is_dir($map->getAbsoluteSource())
144 | && !file_exists($map->getAbsoluteDestination())
145 | ) {
146 | $this->fileSystem->ensureDirectoryExists($map->getAbsoluteDestination());
147 | }
148 | }
149 | }
150 |
151 | /**
152 | * @param MapCollection $mappings
153 | * @param InstallStrategyInterface $installStrategy
154 | * @return MapCollection
155 | */
156 | protected function resolveMappings(MapCollection $mappings, InstallStrategyInterface $installStrategy)
157 | {
158 | $replacementItems = [];
159 | foreach ($mappings as $map) {
160 | /** @var Map $map */
161 | $resolvedMappings = $installStrategy->resolve(
162 | $map->getSource(),
163 | $map->getDestination(),
164 | $map->getAbsoluteSource(),
165 | $map->getAbsoluteDestination()
166 | );
167 |
168 | $maps = array_map(
169 | function (array $mapping) use ($map) {
170 | return new Map($mapping[0], $mapping[1], $map->getSourceRoot(), $map->getDestinationRoot());
171 | },
172 | $resolvedMappings
173 | );
174 |
175 | $replacementItems = array_merge($replacementItems, $maps);
176 | }
177 |
178 | return new MapCollection($replacementItems);
179 | }
180 |
181 | /**
182 | * @param MapCollection $mappings
183 | * @return MapCollection
184 | */
185 | protected function removeNonExistingSourceMappings(MapCollection $mappings)
186 | {
187 | //remove mappings where source doesn't exist
188 | return $mappings->filter(function (Map $map) {
189 | //throw exceptions for missing source?
190 | //trigger event and log?
191 | return file_exists($map->getAbsoluteSource());
192 | });
193 | }
194 |
195 | /**
196 | * @param PackageInterface $package
197 | * @param MapCollection $mappings
198 | * @return MapCollection
199 | */
200 | protected function removeIgnoredMappings(PackageInterface $package, MapCollection $mappings)
201 | {
202 | $targetFilter = $this->targetFilter;
203 | return $mappings->filter(function (Map $map) use ($package, $targetFilter) {
204 | return !$targetFilter->isTargetIgnored($package, $map->getDestination());
205 | });
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Installer/InstallerInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface InstallerInterface
14 | {
15 | /**
16 | * @param PackageInterface $package
17 | * @param string $packageSourceDirectory
18 | *
19 | * @return MapCollection
20 | */
21 | public function install(PackageInterface $package, $packageSourceDirectory);
22 | }
23 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Installer/TargetFilter.php:
--------------------------------------------------------------------------------
1 | ignores = $ignores;
24 | }
25 |
26 | /**
27 | * @param PackageInterface $package
28 | * @param string $target
29 | * @return bool
30 | */
31 | public function isTargetIgnored(PackageInterface $package, $target)
32 | {
33 | $moduleSpecificDeployIgnores = [];
34 |
35 | if (isset($this->ignores['*'])) {
36 | $moduleSpecificDeployIgnores = $this->ignores['*'];
37 | }
38 |
39 | if (isset($this->ignores[$package->getName()])) {
40 | $moduleSpecificDeployIgnores = array_merge(
41 | $moduleSpecificDeployIgnores,
42 | $this->ignores[$package->getName()]
43 | );
44 | }
45 |
46 | //prepend all ignores with '/' if they do not have it already
47 | $moduleSpecificDeployIgnores = array_map(
48 | function ($ignore) {
49 | return sprintf('/%s', ltrim($ignore, '\\/'));
50 | },
51 | $moduleSpecificDeployIgnores
52 | );
53 |
54 | $target = sprintf('/%s', ltrim($target, '\\/'));
55 | $target = str_replace('/./', '/', $target);
56 | $target = str_replace('//', '/', $target);
57 |
58 | return count(
59 | array_filter(
60 | $moduleSpecificDeployIgnores,
61 | function ($ignore) use ($target) {
62 | return $target === $ignore;
63 | }
64 | )
65 | ) > 0;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Listener/CheckAndCreateMagentoRootDirListener.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class CheckAndCreateMagentoRootDirListener
13 | {
14 |
15 | /**
16 | * @var string
17 | */
18 | protected $magentoRootDir;
19 |
20 | /**
21 | * @param string $magentoRootDir
22 | */
23 | public function __construct($magentoRootDir)
24 | {
25 | $this->magentoRootDir = $magentoRootDir;
26 | }
27 |
28 | /**
29 | * @param InstallEvent $event
30 | */
31 | public function __invoke(InstallEvent $event)
32 | {
33 | if (!file_exists($this->magentoRootDir)) {
34 | mkdir($this->magentoRootDir);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Listener/GitIgnoreListener.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class GitIgnoreListener
18 | {
19 |
20 | /**
21 | * @var GitIgnore
22 | */
23 | protected $gitIgnore;
24 |
25 | /**
26 | * @param GitIgnore $gitIgnore
27 | */
28 | public function __construct(GitIgnore $gitIgnore)
29 | {
30 | $this->gitIgnore = $gitIgnore;
31 | }
32 |
33 | /**
34 | * Add any files which were installed to the .gitignore
35 | *
36 | * @param PackagePostInstallEvent $e
37 | */
38 | public function addNewInstalledFiles(PackagePostInstallEvent $e)
39 | {
40 | $files = $this->processMappings($e->getMappings());
41 | $this->gitIgnore->addMultipleEntries($files);
42 | $this->gitIgnore->write();
43 | }
44 |
45 | /**
46 | * Remove any files which were removed to the .gitignore
47 | *
48 | * @param PackageUnInstallEvent $e
49 | */
50 | public function removeUnInstalledFiles(PackageUnInstallEvent $e)
51 | {
52 | $files = $this->processMappings($e->getMappings());
53 | $this->gitIgnore->removeMultipleEntries($files);
54 | $this->gitIgnore->write();
55 | }
56 |
57 | /**
58 | * @param MapCollection $maps
59 | * @return array
60 | */
61 | private function processMappings(MapCollection $maps)
62 | {
63 | return array_map(
64 | function (Map $map) {
65 | return sprintf('/%s', $map->getDestination());
66 | },
67 | $maps->all()
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Listener/PackagePrioritySortListener.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class PackagePrioritySortListener
16 | {
17 | /**
18 | * @var InstallStrategyFactory
19 | */
20 | protected $installStrategyFactory;
21 |
22 | /**
23 | * @var ProjectConfig
24 | */
25 | protected $config;
26 |
27 | /**
28 | * @param InstallStrategyFactory $installStrategyFactory
29 | * @param ProjectConfig $config
30 | */
31 | public function __construct(
32 | InstallStrategyFactory $installStrategyFactory,
33 | ProjectConfig $config
34 | ) {
35 |
36 | $this->installStrategyFactory = $installStrategyFactory;
37 | $this->config = $config;
38 | }
39 |
40 | /**
41 | * @param InstallEvent $event
42 | */
43 | public function __invoke(InstallEvent $event)
44 | {
45 | $packagesToInstall = $event->getPackages();
46 | $userPriorities = $this->config->getSortPriorities();
47 | $priorities = [];
48 | foreach ($packagesToInstall as $package) {
49 | /** @var PackageInterface $package */
50 | if (isset($userPriorities[$package->getName()])) {
51 | $priority = $userPriorities[$package->getName()];
52 | } else {
53 | $priority = $this->installStrategyFactory->getDefaultPriority($package);
54 | }
55 |
56 | $priorities[$package->getName()] = $priority;
57 | }
58 |
59 | $packagesToInstall->uasort(
60 | function (PackageInterface $a, PackageInterface $b) use ($priorities) {
61 | $aVal = $priorities[$a->getName()];
62 | $bVal = $priorities[$b->getName()];
63 |
64 | if ($aVal === $bVal) {
65 | return 0;
66 | }
67 | return ($aVal > $bVal) ? -1 : 1;
68 | }
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Map/Map.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | final class Map
11 | {
12 | /**
13 | * @var string
14 | */
15 | protected $source;
16 |
17 | /**
18 | * @var string
19 | */
20 | protected $destination;
21 |
22 | /**
23 | * @var string
24 | */
25 | protected $sourceRoot;
26 |
27 | /**
28 | * @var string
29 | */
30 | protected $destinationRoot;
31 |
32 | /**
33 | * @param string $source
34 | * @param string $destination
35 | * @param string $sourceRoot
36 | * @param string $destinationRoot
37 | */
38 | public function __construct($source, $destination, $sourceRoot, $destinationRoot)
39 | {
40 | $this->source = $source;
41 | $this->destination = $destination;
42 | $this->sourceRoot = $sourceRoot;
43 | $this->destinationRoot = $destinationRoot;
44 | }
45 |
46 | /**
47 | * @return string
48 | */
49 | public function getRawSource()
50 | {
51 | return $this->source;
52 | }
53 |
54 | /**
55 | * @return string
56 | */
57 | public function getRawDestination()
58 | {
59 | return $this->destination;
60 | }
61 |
62 | /**
63 | * @return string
64 | */
65 | public function getSource()
66 | {
67 | return trim($this->source, '\\/');
68 | }
69 |
70 | /**
71 | * @return string
72 | */
73 | public function getDestination()
74 | {
75 | return trim($this->destination, '\\/');
76 | }
77 |
78 | /**
79 | * @return string
80 | */
81 | public function getAbsoluteSource()
82 | {
83 | return sprintf('%s/%s', rtrim($this->sourceRoot, '\\/'), $this->getSource());
84 | }
85 |
86 | /**
87 | * @return string
88 | */
89 | public function getAbsoluteDestination()
90 | {
91 | return sprintf('%s/%s', rtrim($this->destinationRoot, '\\/'), $this->getDestination());
92 | }
93 |
94 | /**
95 | * @return string
96 | */
97 | public function getSourceRoot()
98 | {
99 | return rtrim($this->sourceRoot, '\\/');
100 | }
101 |
102 | /**
103 | * @return string
104 | */
105 | public function getDestinationRoot()
106 | {
107 | return rtrim($this->destinationRoot, '\\/');
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Map/MapCollection.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class MapCollection implements IteratorAggregate, Countable
15 | {
16 |
17 | /**
18 | * @var Map[]
19 | */
20 | protected $maps;
21 |
22 | /**
23 | * @param array $maps
24 | */
25 | public function __construct(array $maps)
26 | {
27 | //enforce type safety
28 | array_map(function ($map) {
29 | if (!$map instanceof Map) {
30 | throw new \InvalidArgumentException('Input must be an array of "Map"');
31 | }
32 | }, $maps);
33 | $this->maps = $maps;
34 | }
35 |
36 | /**
37 | * @return ArrayIterator
38 | */
39 | public function getIterator()
40 | {
41 | return new ArrayIterator($this->maps);
42 | }
43 |
44 | /**
45 | * @return Map[]
46 | */
47 | public function all()
48 | {
49 | return $this->maps;
50 | }
51 |
52 | /**
53 | * @return int
54 | */
55 | public function count()
56 | {
57 | return count($this->maps);
58 | }
59 |
60 | /**
61 | * @param Map $mapToRemove
62 | *
63 | * @return static
64 | */
65 | public function remove(Map $mapToRemove)
66 | {
67 | return new static(array_values(array_filter(
68 | $this->maps,
69 | function (Map $map) use ($mapToRemove) {
70 | return $mapToRemove !== $map;
71 | }
72 | )));
73 | }
74 |
75 | /**
76 | * @param Map $mapToReplace
77 | * @param Map[] $replacementMaps
78 | *
79 | * @return static
80 | */
81 | public function replace(Map $mapToReplace, array $replacementMaps)
82 | {
83 | //enforce type safety
84 | array_map(function ($map) {
85 | if (!$map instanceof Map) {
86 | throw new \InvalidArgumentException('Input must be an array of "Map"');
87 | }
88 | }, $replacementMaps);
89 |
90 | $key = array_search($mapToReplace, $this->maps);
91 | if (false === $key) {
92 | throw new \InvalidArgumentException('Map does not belong to this collection');
93 | }
94 |
95 | $maps = $this->maps;
96 | array_splice($maps, $key, 1, $replacementMaps);
97 | return new static($maps);
98 | }
99 |
100 | /**
101 | * @param $callback
102 | * @return static
103 | */
104 | public function filter($callback)
105 | {
106 | if (!is_callable($callback)) {
107 | throw new \InvalidArgumentException(
108 | sprintf('Expected callable, got "%s"', is_object($callback) ? get_class($callback) : gettype($callback))
109 | );
110 | }
111 |
112 | return new static(array_filter($this->maps, $callback));
113 | }
114 |
115 | /**
116 | * @return array
117 | */
118 | public function getAllDestinations()
119 | {
120 | return array_map(
121 | function (Map $map) {
122 | return $map->getAbsoluteDestination();
123 | },
124 | $this->maps
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/ModuleManager.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class ModuleManager
23 | {
24 | /**
25 | * @var InstalledPackageRepositoryInterface
26 | */
27 | protected $installedPackageRepository;
28 |
29 | /**
30 | * @var EventManager
31 | */
32 | protected $eventManager;
33 |
34 | /**
35 | * @var ProjectConfig
36 | */
37 | protected $config;
38 |
39 | /**
40 | * @var UnInstallStrategyInterface
41 | */
42 | protected $unInstallStrategy;
43 |
44 | /**
45 | * @var InstallerInterface
46 | */
47 | protected $installer;
48 |
49 | /**
50 | * @var InstallStrategyFactory
51 | */
52 | protected $installStrategyFactory;
53 |
54 | /**
55 | * @param InstalledPackageRepositoryInterface $installedRepository
56 | * @param EventManager $eventManager
57 | * @param ProjectConfig $config
58 | * @param UnInstallStrategyInterface $unInstallStrategy
59 | * @param InstallerInterface $installer
60 | * @param InstallStrategyFactory $installStrategyFactory
61 | */
62 | public function __construct(
63 | InstalledPackageRepositoryInterface $installedRepository,
64 | EventManager $eventManager,
65 | ProjectConfig $config,
66 | UnInstallStrategyInterface $unInstallStrategy,
67 | InstallerInterface $installer,
68 | InstallStrategyFactory $installStrategyFactory
69 | ) {
70 | $this->installedPackageRepository = $installedRepository;
71 | $this->eventManager = $eventManager;
72 | $this->config = $config;
73 | $this->unInstallStrategy = $unInstallStrategy;
74 | $this->installer = $installer;
75 | $this->installStrategyFactory = $installStrategyFactory;
76 | }
77 |
78 | /**
79 | * @param array $currentComposerInstalledPackages
80 | * @return array
81 | */
82 | public function updateInstalledPackages(array $currentComposerInstalledPackages)
83 | {
84 | $packagesToRemove = $this->getRemoves(
85 | $currentComposerInstalledPackages,
86 | $this->installedPackageRepository->findAll()
87 | );
88 |
89 | $packagesToInstall = $this->getInstalls(
90 | $currentComposerInstalledPackages
91 | );
92 |
93 | $this->doRemoves($packagesToRemove);
94 | $this->doInstalls($packagesToInstall);
95 | }
96 |
97 | /**
98 | * @param ArrayObject $packagesToRemove
99 | */
100 | public function doRemoves(ArrayObject $packagesToRemove)
101 | {
102 | foreach ($packagesToRemove as $package) {
103 | $mappings = $package->getMappings();
104 |
105 | $this->eventManager->dispatch(
106 | new PackageUnInstallEvent('package-pre-uninstall', $package, $mappings)
107 | );
108 |
109 | $this->unInstallStrategy->unInstall($mappings);
110 | $this->installedPackageRepository->remove($package);
111 |
112 | $this->eventManager->dispatch(
113 | new PackageUnInstallEvent('package-post-uninstall', $package, $mappings)
114 | );
115 | }
116 | }
117 |
118 | /**
119 | * @param ArrayObject $packagesToInstall
120 | */
121 | protected function doInstalls(ArrayObject $packagesToInstall)
122 | {
123 | $this->eventManager->dispatch(new InstallEvent('pre-install', $packagesToInstall));
124 |
125 | foreach ($packagesToInstall as $package) {
126 | $this->eventManager->dispatch(new PackagePreInstallEvent($package));
127 |
128 | $mappings = $this->installer->install($package, $this->getPackageSourceDirectory($package));
129 |
130 | $installedPackaged = new InstalledPackage(
131 | $package->getName(),
132 | $package->getVersion(),
133 | $mappings
134 | );
135 |
136 | $this->installedPackageRepository->add($installedPackaged);
137 |
138 | $this->eventManager->dispatch(new PackagePostInstallEvent($package, $installedPackaged));
139 | }
140 |
141 | $this->eventManager->dispatch(new InstallEvent('post-install', $packagesToInstall));
142 | }
143 |
144 | /**
145 | * @param PackageInterface[] $currentComposerInstalledPackages
146 | * @param InstalledPackage[] $magentoInstalledPackages
147 | * @return ArrayObject
148 | */
149 | protected function getRemoves(array $currentComposerInstalledPackages, array $magentoInstalledPackages)
150 | {
151 | //make the package names as the array keys
152 | $currentComposerInstalledPackages = array_combine(
153 | array_map(
154 | function (PackageInterface $package) {
155 | return $package->getName();
156 | },
157 | $currentComposerInstalledPackages
158 | ),
159 | $currentComposerInstalledPackages
160 | );
161 |
162 | return new ArrayObject(
163 | array_filter(
164 | $magentoInstalledPackages,
165 | function (InstalledPackage $package) use ($currentComposerInstalledPackages) {
166 | if (!isset($currentComposerInstalledPackages[$package->getName()])) {
167 | return true;
168 | }
169 |
170 | $composerPackage = $currentComposerInstalledPackages[$package->getName()];
171 | return $package->getUniqueName() !== $composerPackage->getUniqueName();
172 | }
173 | )
174 | );
175 | }
176 |
177 | /**
178 | * @param PackageInterface[] $currentComposerInstalledPackages
179 | * @return ArrayObject
180 | */
181 | protected function getInstalls(array $currentComposerInstalledPackages)
182 | {
183 | $repo = $this->installedPackageRepository;
184 | return new ArrayObject(
185 | array_filter($currentComposerInstalledPackages, function (PackageInterface $package) use ($repo) {
186 | return !$repo->has($package->getName(), $package->getVersion());
187 | })
188 | );
189 | }
190 |
191 | /**
192 | * @param PackageInterface $package
193 | * @return string
194 | */
195 | private function getPackageSourceDirectory(PackageInterface $package)
196 | {
197 | $path = sprintf("%s/%s", realpath($this->config->getVendorDir()), $package->getPrettyName());
198 | $targetDir = $package->getTargetDir();
199 |
200 | if ($targetDir) {
201 | $path = sprintf("%s/%s", $path, $targetDir);
202 | }
203 |
204 | return $path;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Parser/MapParser.php:
--------------------------------------------------------------------------------
1 | mappings = $mappings;
26 | }
27 |
28 | /**
29 | * @return array
30 | */
31 | public function getMappings()
32 | {
33 | return $this->mappings;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Parser/ModmanParser.php:
--------------------------------------------------------------------------------
1 | file = new \SplFileObject($modManFile);
26 | }
27 |
28 | /**
29 | * @return array
30 | * @throws \ErrorException
31 | */
32 | public function getMappings()
33 | {
34 | if (!$this->file->isReadable()) {
35 | throw new \ErrorException(sprintf('modman file "%s" not readable', $this->file->getPathname()));
36 | }
37 |
38 | $map = $this->parseMappings();
39 | return $map;
40 | }
41 |
42 | /**
43 | * @throws \ErrorException
44 | * @return array
45 | */
46 | protected function parseMappings()
47 | {
48 | $map = [];
49 | foreach ($this->file as $line => $row) {
50 | $row = trim($row);
51 | if ('' === $row || in_array($row[0], ['#', '@'])) {
52 | continue;
53 | }
54 | $parts = preg_split('/\s+/', $row, 2, PREG_SPLIT_NO_EMPTY);
55 | if (count($parts) != 2) {
56 | throw new \ErrorException(
57 | sprintf('Invalid row on line %d has %d parts, expected 2', $line, count($row))
58 | );
59 | }
60 | $map[] = $parts;
61 | }
62 | return $map;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Parser/PackageXmlParser.php:
--------------------------------------------------------------------------------
1 | file = new \SplFileObject($packageXmlFile);
33 | }
34 |
35 | /**
36 | * @return array
37 | * @throws \ErrorException
38 | */
39 | public function getMappings()
40 | {
41 | if (!$this->file->isReadable()) {
42 | throw new \ErrorException(sprintf('Package file "%s" not readable', $this->file->getPathname()));
43 | }
44 |
45 | $map = $this->parseMappings();
46 | return $map;
47 | }
48 |
49 | /**
50 | * @throws \RuntimeException
51 | * @return array
52 | */
53 | protected function parseMappings()
54 | {
55 | $map = [];
56 |
57 | /** @var $package SimpleXMLElement */
58 | $package = simplexml_load_file($this->file->getPathname());
59 | if (isset($package)) {
60 | foreach ($package->xpath('//contents/target') as $target) {
61 | try {
62 | $basePath = $this->getTargetPath($target);
63 |
64 | foreach ($target->children() as $child) {
65 | foreach ($this->getElementPaths($child) as $elementPath) {
66 | $relativePath = $basePath . '/' . $elementPath;
67 | $map[] = [$relativePath, $relativePath];
68 | }
69 | }
70 |
71 | } catch (\RuntimeException $e) {
72 | // Skip invalid targets
73 | continue;
74 | }
75 | }
76 | }
77 | return $map;
78 | }
79 |
80 | /**
81 | * @param \SimpleXMLElement $target
82 | * @return string
83 | * @throws \RuntimeException
84 | */
85 | protected function getTargetPath(\SimpleXMLElement $target)
86 | {
87 | $name = (string) $target->attributes()->name;
88 | $targets = $this->getTargetsDefinitions();
89 | if (! isset($targets[$name])) {
90 | throw new \RuntimeException('Invalid target type ' . $name);
91 | }
92 | return $targets[$name];
93 | }
94 |
95 | /**
96 | * @return array
97 | */
98 | protected function getTargetsDefinitions()
99 | {
100 | if (!$this->targets) {
101 | $targets = simplexml_load_file(__DIR__ . '/../../../../../res/target.xml');
102 | foreach ($targets as $target) {
103 | $attributes = $target->attributes();
104 | $this->targets["{$attributes->name}"] = "{$attributes->uri}";
105 | }
106 | }
107 | return $this->targets;
108 | }
109 |
110 | /**
111 | * @param \SimpleXMLElement $element
112 | * @return array
113 | * @throws \RuntimeException
114 | */
115 | protected function getElementPaths(\SimpleXMLElement $element)
116 | {
117 | $type = $element->getName();
118 | $name = (string) $element->attributes()->name;
119 | $elementPaths = [];
120 |
121 | switch ($type) {
122 | case 'dir':
123 | if ($element->count()) {
124 | foreach ($element->children() as $child) {
125 | foreach ($this->getElementPaths($child) as $elementPath) {
126 | $elementPaths[] = $name == '.' ? $elementPath : $name . '/' . $elementPath;
127 | }
128 | }
129 | } else {
130 | $elementPaths[] = $name;
131 | }
132 | break;
133 |
134 | case 'file':
135 | $elementPaths[] = $name;
136 | break;
137 |
138 | default:
139 | throw new \RuntimeException('Unknown path type: ' . $type);
140 | }
141 |
142 | return $elementPaths;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Parser/Parser.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class Parser
16 | {
17 |
18 | /**
19 | * @var ParserFactoryInterface
20 | */
21 | protected $parserFactory;
22 |
23 | /**
24 | * @param ParserFactoryInterface $parserFactory
25 | */
26 | public function __construct(ParserFactoryInterface $parserFactory)
27 | {
28 | $this->parserFactory = $parserFactory;
29 | }
30 |
31 | /**
32 | * @param PackageInterface $package
33 | * @param string $packageSourceDirectory
34 | * @param string $installDirectory
35 | *
36 | * @return MapCollection
37 | */
38 | public function getMappings(PackageInterface $package, $packageSourceDirectory, $installDirectory)
39 | {
40 | $mapParser = $this->parserFactory->make($package, $packageSourceDirectory);
41 |
42 | $maps = array_map(
43 | function (array $map) use ($packageSourceDirectory, $installDirectory) {
44 | return new Map($map[0], $map[1], $packageSourceDirectory, $installDirectory);
45 | },
46 | $mapParser->getMappings()
47 | );
48 |
49 | return new MapCollection($maps);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Parser/ParserInterface.php:
--------------------------------------------------------------------------------
1 | pathPrefixTranslations = $this->createPrefixVariants($translations);
40 | $this->parser = $parser;
41 | }
42 |
43 | /**
44 | * Given an array of path mapping translations, combine them with a list
45 | * of starting variations. This is so that a translation for 'js' will
46 | * also match path mappings beginning with './js'.
47 | *
48 | * @param $translations
49 | * @return array
50 | */
51 | protected function createPrefixVariants($translations)
52 | {
53 | $newTranslations = [];
54 | foreach ($translations as $key => $value) {
55 | foreach ($this->pathPrefixVariants as $variant) {
56 | $newTranslations[$variant . $key] = $value;
57 | }
58 | }
59 |
60 | return $newTranslations;
61 | }
62 |
63 | /**
64 | * loop the mappings for the wrapped parser, check if any of the targets are for
65 | * directories that have been moved under the public directory. If so,
66 | * update the target paths to include 'public/'. As no standard Magento
67 | * path mappings should ever start with 'public/', and path mappings
68 | * that already include the public directory should always have
69 | * js/skin/media paths starting with 'public/', it should be safe to call
70 | * multiple times on either.
71 | *
72 | * @return array Updated path mappings
73 | */
74 | public function getMappings()
75 | {
76 | $translatedMappings = [];
77 | foreach ($this->parser->getMappings() as $index => $mapping) {
78 | $translatedMappings[$index] = $mapping;
79 | foreach ($this->pathPrefixTranslations as $prefix => $translate) {
80 | if (strpos($mapping[1], $prefix) === 0) {
81 | // replace the old prefix with the translated version
82 | $translatedMappings[$index][1] = $translate . substr($mapping[1], strlen($prefix));
83 | // should never need to translate a prefix more than once
84 | // per path mapping
85 | break;
86 | }
87 | }
88 | }
89 |
90 | return $translatedMappings;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Plugin.php:
--------------------------------------------------------------------------------
1 | io = $io;
76 | $this->composer = $composer;
77 | $composerConfig = $composer->getConfig()->all();
78 | $this->config = new ProjectConfig($composer->getPackage()->getExtra(), $composerConfig['config']);
79 | $this->eventManager = new EventManager;
80 | $moduleManagerFactory = new ModuleManagerFactory;
81 | $this->moduleManager = $moduleManagerFactory->make($this->config, $this->eventManager, $io);
82 |
83 | if ($io->isDebug()) {
84 | $io->write('Activate Magento plugin');
85 | }
86 | }
87 |
88 | /**
89 | * @return array
90 | */
91 | public static function getSubscribedEvents()
92 | {
93 | return [
94 | ScriptEvents::POST_INSTALL_CMD => [
95 | ['onNewCodeEvent', 0],
96 | ],
97 | ScriptEvents::POST_UPDATE_CMD => [
98 | ['onNewCodeEvent', 0],
99 | ],
100 | ];
101 | }
102 |
103 | /**
104 | * This event is triggered after installing or updating composer
105 | *
106 | * @param CommandEvent $event
107 | */
108 | public function onNewCodeEvent(CommandEvent $event)
109 | {
110 | $magentoModules = array_filter(
111 | $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(),
112 | function (PackageInterface $package) {
113 | return $package->getType() === static::PACKAGE_TYPE;
114 | }
115 | );
116 |
117 | $this->moduleManager->updateInstalledPackages($magentoModules);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/ProjectConfig.php:
--------------------------------------------------------------------------------
1 | extra = $extra;
55 | $this->composerConfig = $composerConfig;
56 | }
57 |
58 | /**
59 | * @param array $array
60 | * @param string $key
61 | * @param mixed $default
62 | *
63 | * @return mixed
64 | */
65 | protected function fetchVarFromConfigArray(array $array, $key, $default = null)
66 | {
67 | $result = $default;
68 | if (isset($array[$key])) {
69 | $result = $array[$key];
70 | }
71 |
72 | return $result;
73 | }
74 |
75 | /**
76 | * @param string $key
77 | * @param mixed $default
78 | *
79 | * @return mixed
80 | */
81 | protected function fetchVarFromExtraConfig($key, $default = null)
82 | {
83 | return $this->fetchVarFromConfigArray($this->extra, $key, $default);
84 | }
85 |
86 | /**
87 | * @param bool $realPath
88 | * @return string
89 | */
90 | public function getMagentoRootDir($realPath = true)
91 | {
92 | $rootDir = rtrim(
93 | trim(
94 | $this->fetchVarFromExtraConfig(
95 | self::MAGENTO_ROOT_DIR_KEY,
96 | self::DEFAULT_MAGENTO_ROOT_DIR
97 | )
98 | ),
99 | DIRECTORY_SEPARATOR
100 | );
101 |
102 | if ($realPath) {
103 | return realpath($rootDir);
104 | }
105 |
106 | return $rootDir;
107 | }
108 |
109 | /**
110 | * @return string
111 | */
112 | public function getDeployStrategy()
113 | {
114 | return trim((string) $this->fetchVarFromExtraConfig(self::MAGENTO_DEPLOY_STRATEGY_KEY));
115 | }
116 |
117 | /**
118 | * @return bool
119 | */
120 | public function hasDeployStrategy()
121 | {
122 | return $this->hasExtraField(self::MAGENTO_DEPLOY_STRATEGY_KEY);
123 | }
124 |
125 | /**
126 | * @return array
127 | */
128 | public function getDeployStrategyOverwrite()
129 | {
130 | return $this->transformArrayKeysToLowerCase(
131 | $this->fetchVarFromExtraConfig(self::MAGENTO_DEPLOY_STRATEGY_OVERWRITE_KEY, [])
132 | );
133 | }
134 |
135 | /**
136 | * @return bool
137 | */
138 | public function hasDeployStrategyOverwrite()
139 | {
140 | return $this->hasExtraField(self::MAGENTO_DEPLOY_STRATEGY_OVERWRITE_KEY);
141 | }
142 |
143 | /**
144 | * @return array
145 | */
146 | public function getMagentoDeployIgnore()
147 | {
148 | return $this->transformArrayKeysToLowerCase(
149 | $this->fetchVarFromExtraConfig(self::MAGENTO_DEPLOY_IGNORE_KEY, [])
150 | );
151 | }
152 |
153 | /**
154 | * @return bool
155 | */
156 | public function hasMagentoDeployIgnore()
157 | {
158 | return $this->hasExtraField(self::MAGENTO_DEPLOY_IGNORE_KEY);
159 | }
160 |
161 | /**
162 | * @return bool
163 | */
164 | public function getMagentoForce()
165 | {
166 | return (bool) $this->fetchVarFromExtraConfig(self::MAGENTO_FORCE_KEY);
167 | }
168 |
169 | /**
170 | * @param string $packagename
171 | * @return string
172 | */
173 | public function getMagentoForceByPackageName($packagename)
174 | {
175 | return $this->getMagentoForce();
176 | }
177 |
178 | /**
179 | * @return bool
180 | */
181 | public function hasAutoAppendGitignore()
182 | {
183 | return $this->hasExtraField(self::AUTO_APPEND_GITIGNORE_KEY);
184 | }
185 |
186 | /**
187 | * @return array
188 | */
189 | public function getPathMappingTranslations()
190 | {
191 | return $this->fetchVarFromExtraConfig(self::PATH_MAPPINGS_TRANSLATIONS_KEY, []);
192 | }
193 |
194 | /**
195 | * @return bool
196 | */
197 | public function hasPathMappingTranslations()
198 | {
199 | return $this->hasExtraField(self::PATH_MAPPINGS_TRANSLATIONS_KEY);
200 | }
201 |
202 | /**
203 | * @return array
204 | */
205 | public function getMagentoMapOverwrite()
206 | {
207 | return $this->transformArrayKeysToLowerCase(
208 | $this->fetchVarFromExtraConfig(self::MAGENTO_MAP_OVERWRITE_KEY, [])
209 | );
210 | }
211 |
212 | /**
213 | * @param string $key
214 | * @return bool
215 | */
216 | protected function hasExtraField($key)
217 | {
218 | return !is_null($this->fetchVarFromExtraConfig($key));
219 | }
220 |
221 | /**
222 | * @param array $array
223 | *
224 | * @return array
225 | */
226 | public function transformArrayKeysToLowerCase(array $array)
227 | {
228 | return array_change_key_case($array, CASE_LOWER);
229 | }
230 |
231 | /**
232 | * Get Composer vendor directory
233 | *
234 | * @return string
235 | */
236 | public function getVendorDir()
237 | {
238 | return $this->fetchVarFromConfigArray($this->composerConfig, 'vendor-dir');
239 | }
240 |
241 | /**
242 | * Get Package Sort Order
243 | *
244 | * @return array
245 | */
246 | public function getSortPriorities()
247 | {
248 | return $this->fetchVarFromConfigArray($this->extra, self::SORT_PRIORITY_KEY, []);
249 | }
250 |
251 | /**
252 | * @return string
253 | */
254 | public function getModuleRepositoryLocation()
255 | {
256 | $moduleRepoDir = $this->fetchVarFromExtraConfig(
257 | self::MODULE_REPOSITORY_LOCATION_KEY,
258 | $this->fetchVarFromConfigArray(
259 | $this->composerConfig,
260 | 'vendor-dir'
261 | )
262 | );
263 |
264 | return sprintf('%s/magento-installed.json', $moduleRepoDir);
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Repository/InstalledPackageFileSystemRepository.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class InstalledPackageFileSystemRepository implements InstalledPackageRepositoryInterface
14 | {
15 |
16 | /**
17 | * @var string Path to state file
18 | */
19 | protected $filePath;
20 |
21 | /**
22 | * @var array
23 | */
24 | protected $packages = [];
25 |
26 | /**
27 | * @var bool Flag to indicate if we have read the existing file
28 | */
29 | protected $isLoaded = false;
30 |
31 | /**
32 | * @var bool Flag to indicate if we need to write once we are finished
33 | */
34 | protected $hasChanges = false;
35 |
36 | /**
37 | * @var InstalledPackageDumper
38 | */
39 | protected $dumper;
40 |
41 | /**
42 | * If file exists, check its readable
43 | * Check in any case that it's writeable
44 | *
45 | * @param string $filePath
46 | * @param InstalledPackageDumper $dumper
47 | */
48 | public function __construct($filePath, InstalledPackageDumper $dumper)
49 | {
50 | if (file_exists($filePath) && !is_writable($filePath)) {
51 | throw new \InvalidArgumentException(sprintf('File "%s" is not writable', $filePath));
52 | }
53 |
54 | if (file_exists($filePath) && !is_readable($filePath)) {
55 | throw new \InvalidArgumentException(sprintf('File "%s" is not readable', $filePath));
56 | }
57 |
58 | if (!file_exists($filePath) && !is_writable(dirname($filePath))) {
59 | throw new \InvalidArgumentException(sprintf('Directory "%s" is not writable', dirname($filePath)));
60 | }
61 |
62 | $this->filePath = $filePath;
63 | $this->dumper = $dumper;
64 | }
65 |
66 | /**
67 | * @return array
68 | */
69 | public function findAll()
70 | {
71 | $this->load();
72 | return $this->packages;
73 | }
74 |
75 | /**
76 | * @param string $packageName
77 | * @return InstalledPackage
78 | * @throws \Exception
79 | */
80 | public function findByPackageName($packageName)
81 | {
82 | $this->load();
83 | foreach ($this->packages as $package) {
84 | if ($package->getName() === $packageName) {
85 | return $package;
86 | }
87 | }
88 |
89 | throw new \Exception(sprintf('Package Installed Files for: "%s" not found', $packageName));
90 | }
91 |
92 | /**
93 | * If version specified, perform a strict check,
94 | * which only returns true if repository has the package in the specified version
95 | *
96 | * @param string $packageName
97 | * @param string $version
98 | * @return bool
99 | */
100 | public function has($packageName, $version = null)
101 | {
102 | $this->load();
103 | try {
104 | $package = $this->findByPackageName($packageName);
105 |
106 | if (null === $version) {
107 | return true;
108 | }
109 |
110 | return $package->getVersion() === $version;
111 | } catch (\Exception $e) {
112 | return false;
113 | }
114 | }
115 |
116 | /**
117 | * @param InstalledPackage $package
118 | * @throws \Exception
119 | */
120 | public function add(InstalledPackage $package)
121 | {
122 | $this->load();
123 |
124 | try {
125 | $this->findByPackageName($package->getName());
126 | } catch (\Exception $e) {
127 | $this->packages[] = $package;
128 | $this->hasChanges = true;
129 | return;
130 | }
131 |
132 | throw new \Exception(sprintf('Package: "%s" is already installed', $package->getName()));
133 | }
134 |
135 | /**
136 | * @param InstalledPackage $package
137 | * @throws \Exception
138 | */
139 | public function remove(InstalledPackage $package)
140 | {
141 | $this->load();
142 |
143 | foreach ($this->packages as $key => $installedPackage) {
144 | if ($installedPackage->getName() === $package->getName()) {
145 | array_splice($this->packages, $key, 1);
146 | $this->hasChanges = true;
147 | return;
148 | }
149 | }
150 |
151 | throw new \Exception(sprintf('Package: "%s" not found', $package->getName()));
152 | }
153 |
154 | /**
155 | * Load the Mappings File
156 | *
157 | * @return array
158 | */
159 | private function load()
160 | {
161 | if (!$this->isLoaded && file_exists($this->filePath)) {
162 | $data = json_decode(file_get_contents($this->filePath), true);
163 |
164 | foreach ($data as $installedPackageData) {
165 | $this->packages[] = $this->dumper->restore($installedPackageData);
166 | }
167 | }
168 |
169 | $this->isLoaded = true;
170 | }
171 |
172 | /**
173 | * Do the write on destruct, we shouldn't have to do this manually
174 | * - you don't call save after adding an entry to the database
175 | * and at the same time, do want to perform IO for each package addition/removal.
176 | *
177 | * Also I don't like enforcing the consumer to call save and load.
178 | */
179 | public function __destruct()
180 | {
181 | if ($this->hasChanges) {
182 | $data = [];
183 | foreach ($this->packages as $installedPackage) {
184 | $data[] = $this->dumper->dump($installedPackage);
185 | }
186 |
187 | $flags = JSON_UNESCAPED_SLASHES;
188 | if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
189 | $flags |= JSON_PRETTY_PRINT;
190 | }
191 |
192 | file_put_contents($this->filePath, json_encode($data, $flags));
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Repository/InstalledPackageRepositoryInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface InstalledPackageRepositoryInterface
13 | {
14 | /**
15 | * Get all installed packages
16 | *
17 | * @return InstalledPackage[]
18 | */
19 | public function findAll();
20 |
21 | /**
22 | * @param string $packageName
23 | * @return InstalledPackage|null
24 | */
25 | public function findByPackageName($packageName);
26 |
27 | /**
28 | * @param InstalledPackage $package
29 | */
30 | public function remove(InstalledPackage $package);
31 |
32 | /**
33 | * @param InstalledPackage $package
34 | */
35 | public function add(InstalledPackage $package);
36 |
37 | /**
38 | * @param string $packageName
39 | * @param string $version
40 | * @return bool
41 | */
42 | public function has($packageName, $version = null);
43 | }
44 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/UnInstallStrategy/UnInstallStrategy.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class UnInstallStrategy implements UnInstallStrategyInterface
15 | {
16 |
17 | /**
18 | * @var FileSystem
19 | */
20 | protected $fileSystem;
21 |
22 | /**
23 | * The root dir for uninstalling from. Should be project root.
24 | *
25 | * @var string
26 | */
27 | protected $rootDir;
28 |
29 | /**
30 | * @param FileSystem $fileSystem
31 | * @param string $rootDir
32 | */
33 | public function __construct(FileSystem $fileSystem, $rootDir)
34 | {
35 | $this->fileSystem = $fileSystem;
36 | $this->rootDir = $rootDir;
37 | }
38 |
39 | /**
40 | * UnInstall the extension given the list of install files
41 | *
42 | * @param MapCollection $collection
43 | */
44 | public function unInstall(MapCollection $collection)
45 | {
46 | foreach ($collection as $map) {
47 | /** @var Map $map */
48 | $this->fileSystem->unlink($map->getAbsoluteDestination());
49 |
50 | if ($this->fileSystem->isDirEmpty(dirname($map->getAbsoluteDestination()))) {
51 | $this->fileSystem->removeEmptyDirectoriesUpToRoot(
52 | dirname($map->getAbsoluteDestination()),
53 | $this->rootDir
54 | );
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/UnInstallStrategy/UnInstallStrategyInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface UnInstallStrategyInterface
13 | {
14 | /**
15 | * UnInstall the extension given the list of install files
16 | * @param MapCollection $collection
17 | */
18 | public function unInstall(MapCollection $collection);
19 | }
20 |
--------------------------------------------------------------------------------
/src/MagentoHackathon/Composer/Magento/Util/FileSystem.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class FileSystem extends ComposerFs
13 | {
14 | /**
15 | * Returns the relative path from $from to $to
16 | *
17 | * This is utility method for symlink creation.
18 | * Orig Source: http://stackoverflow.com/a/2638272/485589
19 | *
20 | * @param string $from
21 | * @param string $to
22 | *
23 | * @return string
24 | */
25 | public function getRelativePath($from, $to)
26 | {
27 | $from = str_replace('\\', '/', $from);
28 | $to = str_replace('\\', '/', $to);
29 |
30 | $from = str_replace(['/./', '//'], '/', $from);
31 | $to = str_replace(['/./', '//'], '/', $to);
32 |
33 | $from = explode('/', $from);
34 | $to = explode('/', $to);
35 |
36 | $relPath = $to;
37 |
38 | foreach ($from as $depth => $dir) {
39 | // find first non-matching dir
40 | if ($dir === $to[$depth]) {
41 | // ignore this directory
42 | array_shift($relPath);
43 | } else {
44 | // get number of remaining dirs to $from
45 | $remaining = count($from) - $depth;
46 | if ($remaining > 1) {
47 | // add traversals up to first matching dir
48 | $padLength = (count($relPath) + $remaining - 1) * -1;
49 | $relPath = array_pad($relPath, $padLength, '..');
50 | break;
51 | } else {
52 | $relPath[0] = './' . $relPath[0];
53 | }
54 | }
55 | }
56 | return implode('/', $relPath);
57 | }
58 |
59 | /**
60 | * @param string $absoluteSource
61 | * @param string $rootDirectory
62 | *
63 | * @return string
64 | */
65 | public function makePathRelative($absoluteSource, $rootDirectory)
66 | {
67 | return substr($absoluteSource, strlen($rootDirectory) + 1);
68 | }
69 |
70 | /**
71 | * Check if the basename of source and directory match
72 | *
73 | * @param string $source
74 | * @param string $destination
75 | *
76 | * @return bool
77 | */
78 | public function sourceAndDestinationBaseMatch($source, $destination)
79 | {
80 | $sourceBase = basename($source);
81 | $destinationBase = basename($destination);
82 |
83 | return $sourceBase === $destinationBase;
84 | }
85 |
86 | /**
87 | * If destination to correct location
88 | * return true
89 | *
90 | * @param string $destination
91 | * @param string $source
92 | *
93 | * @return bool
94 | */
95 | public function symLinkPointsToCorrectLocation($destination, $source)
96 | {
97 | $destinationAbsolutePath = realpath(readlink($destination));
98 | $sourceAbsolutePath = realpath($source);
99 |
100 | return $destinationAbsolutePath === $sourceAbsolutePath;
101 | }
102 |
103 | /**
104 | * Create a symlink fixes various quirks on different OS's
105 | *
106 | * @param string $source
107 | * @param string $destination
108 | *
109 | * @throws \ErrorException
110 | */
111 | public function createSymlink($source, $destination)
112 | {
113 | $relativeSourcePath = $this->getRelativePath($destination, $source);
114 | if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
115 | $relativeSourcePath = str_replace('/', '\\', $relativeSourcePath);
116 | $param = is_dir($source) ? '/D' : '';
117 | exec(sprintf('mklink %s %s %s', $param, $destination, $relativeSourcePath), $output, $return);
118 | } else {
119 | $result = @symlink($relativeSourcePath, $destination);
120 |
121 | if (false === $result) {
122 | throw new \ErrorException(sprintf('An error occurred while creating symlink: %s', $relativeSourcePath));
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * @param string $path
129 | * @return bool
130 | */
131 | public function endsWithDirectorySeparator($path)
132 | {
133 | return in_array(substr($path, -1), ['/', '\\']);
134 | }
135 |
136 | /**
137 | * Extend to unlink symlinks incase they are
138 | * directories.
139 | *
140 | * @param $file
141 | * @return bool
142 | */
143 | public function remove($file)
144 | {
145 | if (is_link($file)) {
146 | return $this->unlink($file);
147 | }
148 |
149 | if (is_dir($file)) {
150 | return $this->removeDirectory($file);
151 | }
152 |
153 | if (file_exists($file)) {
154 | return $this->unlink($file);
155 | }
156 |
157 | return false;
158 | }
159 |
160 | /**
161 | * @param string $dir
162 | * @param string $root
163 | */
164 | public function removeEmptyDirectoriesUpToRoot($dir, $root)
165 | {
166 | if (!$this->isDirEmpty($dir)) {
167 | return;
168 | }
169 |
170 | if ($dir === $root) {
171 | return;
172 | }
173 |
174 | $this->removeDirectory($dir);
175 | $this->removeEmptyDirectoriesUpToRoot(dirname($dir), $root);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------