├── .gitignore ├── README.md ├── _cli_stub.php ├── build.xml ├── composer.json ├── composer.lock ├── example-config.php ├── magento-tar-to-connect.phar ├── magento-tar-to-connect.php ├── src └── magento │ └── downloader │ └── lib │ └── Mage │ ├── Archive │ ├── Abstract.php │ ├── Helper │ │ └── File.php │ ├── Interface.php │ └── Tar.php │ └── Exception.php └── tests ├── AuthorTest.php ├── ExampleTest.php ├── MagentoTarToConnectBaseTest.php ├── WhoTestsTheTestsTest.php └── fixtures ├── Pulsestorm_Better404.tar ├── first.tar └── first.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /tests/fixtures/build 3 | /tests/fixtures/build-artifacts -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MagentoTarToConnect 2 | =================== 3 | 4 | A small shell script to automatically package tar archives into Magento's Connect 2.0 format. 5 | 6 | ### Description 7 | 8 | Under the hood Magento Connect 2.0 packages (Magento Connect 2.0 was introduced around the time of Magento CE 1.5) are actually tar'ed and gziped files with a specially formatted package manifest. Well, they're almost `tar` and `gzip` files. Magento implemented their own archiving and unarchiving code in PHP, and this code occasionally has problems with tar archives created via standard OS tools. 9 | 10 | This shell script will take a standard tar archive, untar it, build the Connect `package.xml` manifest, and then re-tar and gzip the files **using Magento's code** (included in the `vendor` library here, but you can substitute your own). This decreases the likelihood your package will be incompatible with Magento Connect. 11 | 12 | ## Usage 13 | 14 | The syntax for using this script is as following 15 | 16 | $ ./magento-tar-to-connect.php example-config.php 17 | 18 | Where `example-config.php` is a PHP file which returns a set of configuration key/value pairs. These key/value pairs provide the script with the location of an archive file, the output location, as well as the bare minimum Magento Connect fields needed to create a valid extension. An example file might look something like this 19 | 20 | '/fakehome/Documents/github/Pulsestorm/var/build', 26 | 'archive_files' => 'Pulstorm_Example.tar', 27 | 28 | //The Magento Connect extension name. Must be unique on Magento Connect 29 | //Has no relation to your code module name. Will be the Connect extension name 30 | 'extension_name' => 'Pulstorm_Example', 31 | 32 | //Your extension version. By default, if you're creating an extension from a 33 | //single Magento module, the tar-to-connect script will look to make sure this 34 | //matches the module version. You can skip this check by setting the 35 | //skip_version_compare value to true 36 | 'extension_version' => '1.0.3', 37 | 'skip_version_compare' => false, 38 | 39 | //You can also have the package script use the version in the module you 40 | //are packaging with. 41 | 'auto_detect_version' => false, 42 | 43 | //Where on your local system you'd like to build the files to 44 | 'path_output' => '/fakehome/Pulsestorm/var/build-connect', 45 | 46 | //Magento Connect license value. 47 | 'stability' => 'stable', 48 | 49 | //Magento Connect license value 50 | 'license' => 'MIT', 51 | 52 | //Magento Connect channel value. This should almost always (always?) be community 53 | 'channel' => 'community', 54 | 55 | //Magento Connect information fields. 56 | 'summary' => 'Provides navigation shortcuts for the admin console\'s navigation and global search', 57 | 'description' => 'This extension provides Magento admin console users with an "application launcher". This application launcher provides instant access to the admin console\'s navigation, every system configuration search section, as well as the Magento global search. The Pulse Storm launcher is a free, open source, must have extension for anyone working with Magento. ', 58 | 'notes' => 'Typo fixes, properly aborts ajax requests.', 59 | 60 | //Magento Connect author information. If author_email is foo@example.com, script will 61 | //prompt you for the correct name. Should match your http://www.magentocommerce.com/ 62 | //login email address 63 | 'author_name' => 'Alan Storm', 64 | 'author_user' => 'alanstorm', 65 | 'author_email' => 'foo@example.com', 66 | 67 | //PHP min/max fields for Connect. I don't know if anyone uses these, but you should 68 | //probably check that they're accurate 69 | 'php_min' => '5.2.0', 70 | 'php_max' => '6.0.0' 71 | ); 72 | 73 | ## Building a `phar` with Phing 74 | 75 | The project also includes a `phing` build.xml file. You can use this to create an executable `phar` of the script. If you're not familiar, a `phar` is sort of like a stand alone PHP executable. You can [read more here](http://php.net/phar). 76 | 77 | $ phing create_phar 78 | 79 | This will create a new `magento-tar-to-connect.phar` file in the main project directory. 80 | 81 | ## Running the script stand alone 82 | 83 | If you don't know how to use `phing` or `phar` files, you can run the scripts by copying 84 | 85 | 1. `magento-tar-to-connect.php` 86 | 2. And the `src/magento` folder 87 | 88 | To wherever you run your scripts from. 89 | 90 | ## Composer and Unit Tests 91 | 92 | [PHP Composer](https://getcomposer.org/) is not required to run this project. However, we've included `composer.json` for installing `phpunit`. After running `composer install` or `composer update`, you'll be able to run the unit tests with the following 93 | 94 | vendor/bin/phpunit tests 95 | 96 | Tests should be placed in 97 | 98 | tests/ 99 | 100 | See the test file 101 | 102 | tests/ExampleTest.php 103 | 104 | for instructions on how to load a test fixture (i.e. archive), and automatically build a `tgz` Magento connect extension from it. -------------------------------------------------------------------------------- /_cli_stub.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setPharMode(true); 8 | // $application->run(); 9 | 10 | __HALT_COMPILER(); -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pulsestorm/magento-tar-to-connect", 3 | "description": "A duct-tape shell script for packaging Magento Extenstions", 4 | "authors": [ 5 | { 6 | "name": "Alan Storm", 7 | "email": "astorm@alanstorm.com" 8 | } 9 | ], 10 | "require": { 11 | 12 | }, 13 | "require-dev": { 14 | "phpunit/phpunit": "4.2.*", 15 | "pear/archive_tar": "1.3.*" 16 | }, 17 | "autoload": { 18 | "classmap": [ 19 | "tests/MagentoTarToConnectBaseTest.php" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "ce2901b464a4811d5fc585a394507f85", 8 | "packages": [ 9 | 10 | ], 11 | "packages-dev": [ 12 | { 13 | "name": "ocramius/instantiator", 14 | "version": "1.1.3", 15 | "source": { 16 | "type": "git", 17 | "url": "https://github.com/Ocramius/Instantiator.git", 18 | "reference": "e24a12178906ff2e7471b8aaf3a0eb789b59f881" 19 | }, 20 | "dist": { 21 | "type": "zip", 22 | "url": "https://api.github.com/repos/Ocramius/Instantiator/zipball/e24a12178906ff2e7471b8aaf3a0eb789b59f881", 23 | "reference": "e24a12178906ff2e7471b8aaf3a0eb789b59f881", 24 | "shasum": "" 25 | }, 26 | "require": { 27 | "ocramius/lazy-map": "1.0.*", 28 | "php": "~5.3" 29 | }, 30 | "require-dev": { 31 | "athletic/athletic": "~0.1.8", 32 | "ext-pdo": "*", 33 | "ext-phar": "*", 34 | "phpunit/phpunit": "~4.0", 35 | "squizlabs/php_codesniffer": "2.0.*@ALPHA" 36 | }, 37 | "type": "library", 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "2.0.x-dev" 41 | } 42 | }, 43 | "autoload": { 44 | "psr-0": { 45 | "Instantiator\\": "src" 46 | } 47 | }, 48 | "notification-url": "https://packagist.org/downloads/", 49 | "license": [ 50 | "MIT" 51 | ], 52 | "authors": [ 53 | { 54 | "name": "Marco Pivetta", 55 | "email": "ocramius@gmail.com", 56 | "homepage": "http://ocramius.github.com/" 57 | } 58 | ], 59 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 60 | "homepage": "https://github.com/Ocramius/Instantiator", 61 | "keywords": [ 62 | "constructor", 63 | "instantiate" 64 | ], 65 | "time": "2014-08-25 14:48:16" 66 | }, 67 | { 68 | "name": "ocramius/lazy-map", 69 | "version": "1.0.0", 70 | "source": { 71 | "type": "git", 72 | "url": "https://github.com/Ocramius/LazyMap.git", 73 | "reference": "7fe3d347f5e618bcea7d39345ff83f3651d8b752" 74 | }, 75 | "dist": { 76 | "type": "zip", 77 | "url": "https://api.github.com/repos/Ocramius/LazyMap/zipball/7fe3d347f5e618bcea7d39345ff83f3651d8b752", 78 | "reference": "7fe3d347f5e618bcea7d39345ff83f3651d8b752", 79 | "shasum": "" 80 | }, 81 | "require": { 82 | "php": ">=5.3.3" 83 | }, 84 | "require-dev": { 85 | "athletic/athletic": "~0.1.6", 86 | "phpmd/phpmd": "1.5.*", 87 | "phpunit/phpunit": ">=3.7", 88 | "satooshi/php-coveralls": "~0.6", 89 | "squizlabs/php_codesniffer": "1.4.*" 90 | }, 91 | "type": "library", 92 | "extra": { 93 | "branch-alias": { 94 | "dev-master": "1.0.x-dev" 95 | } 96 | }, 97 | "autoload": { 98 | "psr-0": { 99 | "LazyMap\\": "src" 100 | } 101 | }, 102 | "notification-url": "https://packagist.org/downloads/", 103 | "license": [ 104 | "MIT" 105 | ], 106 | "authors": [ 107 | { 108 | "name": "Marco Pivetta", 109 | "email": "ocramius@gmail.com", 110 | "homepage": "http://ocramius.github.com/", 111 | "role": "Developer" 112 | } 113 | ], 114 | "description": "A library that provides lazy instantiation logic for a map of objects", 115 | "homepage": "https://github.com/Ocramius/LazyMap", 116 | "keywords": [ 117 | "lazy", 118 | "lazy instantiation", 119 | "lazy loading", 120 | "map", 121 | "service location" 122 | ], 123 | "time": "2013-11-09 22:30:54" 124 | }, 125 | { 126 | "name": "pear/archive_tar", 127 | "version": "1.3.11", 128 | "source": { 129 | "type": "git", 130 | "url": "https://github.com/pear/Archive_Tar.git", 131 | "reference": "23341344e19bbab1056cf2d2773f28cfccf787a3" 132 | }, 133 | "dist": { 134 | "type": "zip", 135 | "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/23341344e19bbab1056cf2d2773f28cfccf787a3", 136 | "reference": "23341344e19bbab1056cf2d2773f28cfccf787a3", 137 | "shasum": "" 138 | }, 139 | "require": { 140 | "php": ">=4.3.0" 141 | }, 142 | "type": "library", 143 | "autoload": { 144 | "psr-0": { 145 | "Archive_Tar": "" 146 | } 147 | }, 148 | "notification-url": "https://packagist.org/downloads/", 149 | "license": [ 150 | "BSD-3-Clause" 151 | ], 152 | "authors": [ 153 | { 154 | "name": "Michiel Rook", 155 | "email": "mrook@php.net", 156 | "role": "Lead" 157 | }, 158 | { 159 | "name": "Vincent Blavet", 160 | "email": "vincent@phpconcept.net" 161 | }, 162 | { 163 | "name": "Greg Beaver", 164 | "email": "greg@chiaraquartet.net" 165 | } 166 | ], 167 | "description": "Tar file management class", 168 | "homepage": "https://github.com/pear/Archive_Tar", 169 | "keywords": [ 170 | "archive", 171 | "tar" 172 | ], 173 | "time": "2013-02-09 11:44:32" 174 | }, 175 | { 176 | "name": "phpunit/php-code-coverage", 177 | "version": "2.0.11", 178 | "source": { 179 | "type": "git", 180 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 181 | "reference": "53603b3c995f5aab6b59c8e08c3a663d2cc810b7" 182 | }, 183 | "dist": { 184 | "type": "zip", 185 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/53603b3c995f5aab6b59c8e08c3a663d2cc810b7", 186 | "reference": "53603b3c995f5aab6b59c8e08c3a663d2cc810b7", 187 | "shasum": "" 188 | }, 189 | "require": { 190 | "php": ">=5.3.3", 191 | "phpunit/php-file-iterator": "~1.3", 192 | "phpunit/php-text-template": "~1.2", 193 | "phpunit/php-token-stream": "~1.3", 194 | "sebastian/environment": "~1.0", 195 | "sebastian/version": "~1.0" 196 | }, 197 | "require-dev": { 198 | "ext-xdebug": ">=2.1.4", 199 | "phpunit/phpunit": "~4.1" 200 | }, 201 | "suggest": { 202 | "ext-dom": "*", 203 | "ext-xdebug": ">=2.2.1", 204 | "ext-xmlwriter": "*" 205 | }, 206 | "type": "library", 207 | "extra": { 208 | "branch-alias": { 209 | "dev-master": "2.0.x-dev" 210 | } 211 | }, 212 | "autoload": { 213 | "classmap": [ 214 | "src/" 215 | ] 216 | }, 217 | "notification-url": "https://packagist.org/downloads/", 218 | "include-path": [ 219 | "" 220 | ], 221 | "license": [ 222 | "BSD-3-Clause" 223 | ], 224 | "authors": [ 225 | { 226 | "name": "Sebastian Bergmann", 227 | "email": "sb@sebastian-bergmann.de", 228 | "role": "lead" 229 | } 230 | ], 231 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 232 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 233 | "keywords": [ 234 | "coverage", 235 | "testing", 236 | "xunit" 237 | ], 238 | "time": "2014-08-31 06:33:04" 239 | }, 240 | { 241 | "name": "phpunit/php-file-iterator", 242 | "version": "1.3.4", 243 | "source": { 244 | "type": "git", 245 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 246 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 247 | }, 248 | "dist": { 249 | "type": "zip", 250 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 251 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 252 | "shasum": "" 253 | }, 254 | "require": { 255 | "php": ">=5.3.3" 256 | }, 257 | "type": "library", 258 | "autoload": { 259 | "classmap": [ 260 | "File/" 261 | ] 262 | }, 263 | "notification-url": "https://packagist.org/downloads/", 264 | "include-path": [ 265 | "" 266 | ], 267 | "license": [ 268 | "BSD-3-Clause" 269 | ], 270 | "authors": [ 271 | { 272 | "name": "Sebastian Bergmann", 273 | "email": "sb@sebastian-bergmann.de", 274 | "role": "lead" 275 | } 276 | ], 277 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 278 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 279 | "keywords": [ 280 | "filesystem", 281 | "iterator" 282 | ], 283 | "time": "2013-10-10 15:34:57" 284 | }, 285 | { 286 | "name": "phpunit/php-text-template", 287 | "version": "1.2.0", 288 | "source": { 289 | "type": "git", 290 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 291 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a" 292 | }, 293 | "dist": { 294 | "type": "zip", 295 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 296 | "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a", 297 | "shasum": "" 298 | }, 299 | "require": { 300 | "php": ">=5.3.3" 301 | }, 302 | "type": "library", 303 | "autoload": { 304 | "classmap": [ 305 | "Text/" 306 | ] 307 | }, 308 | "notification-url": "https://packagist.org/downloads/", 309 | "include-path": [ 310 | "" 311 | ], 312 | "license": [ 313 | "BSD-3-Clause" 314 | ], 315 | "authors": [ 316 | { 317 | "name": "Sebastian Bergmann", 318 | "email": "sb@sebastian-bergmann.de", 319 | "role": "lead" 320 | } 321 | ], 322 | "description": "Simple template engine.", 323 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 324 | "keywords": [ 325 | "template" 326 | ], 327 | "time": "2014-01-30 17:20:04" 328 | }, 329 | { 330 | "name": "phpunit/php-timer", 331 | "version": "1.0.5", 332 | "source": { 333 | "type": "git", 334 | "url": "https://github.com/sebastianbergmann/php-timer.git", 335 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" 336 | }, 337 | "dist": { 338 | "type": "zip", 339 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 340 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 341 | "shasum": "" 342 | }, 343 | "require": { 344 | "php": ">=5.3.3" 345 | }, 346 | "type": "library", 347 | "autoload": { 348 | "classmap": [ 349 | "PHP/" 350 | ] 351 | }, 352 | "notification-url": "https://packagist.org/downloads/", 353 | "include-path": [ 354 | "" 355 | ], 356 | "license": [ 357 | "BSD-3-Clause" 358 | ], 359 | "authors": [ 360 | { 361 | "name": "Sebastian Bergmann", 362 | "email": "sb@sebastian-bergmann.de", 363 | "role": "lead" 364 | } 365 | ], 366 | "description": "Utility class for timing", 367 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 368 | "keywords": [ 369 | "timer" 370 | ], 371 | "time": "2013-08-02 07:42:54" 372 | }, 373 | { 374 | "name": "phpunit/php-token-stream", 375 | "version": "1.3.0", 376 | "source": { 377 | "type": "git", 378 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 379 | "reference": "f8d5d08c56de5cfd592b3340424a81733259a876" 380 | }, 381 | "dist": { 382 | "type": "zip", 383 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/f8d5d08c56de5cfd592b3340424a81733259a876", 384 | "reference": "f8d5d08c56de5cfd592b3340424a81733259a876", 385 | "shasum": "" 386 | }, 387 | "require": { 388 | "ext-tokenizer": "*", 389 | "php": ">=5.3.3" 390 | }, 391 | "require-dev": { 392 | "phpunit/phpunit": "~4.2" 393 | }, 394 | "type": "library", 395 | "extra": { 396 | "branch-alias": { 397 | "dev-master": "1.3-dev" 398 | } 399 | }, 400 | "autoload": { 401 | "classmap": [ 402 | "src/" 403 | ] 404 | }, 405 | "notification-url": "https://packagist.org/downloads/", 406 | "license": [ 407 | "BSD-3-Clause" 408 | ], 409 | "authors": [ 410 | { 411 | "name": "Sebastian Bergmann", 412 | "email": "sebastian@phpunit.de" 413 | } 414 | ], 415 | "description": "Wrapper around PHP's tokenizer extension.", 416 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 417 | "keywords": [ 418 | "tokenizer" 419 | ], 420 | "time": "2014-08-31 06:12:13" 421 | }, 422 | { 423 | "name": "phpunit/phpunit", 424 | "version": "4.2.6", 425 | "source": { 426 | "type": "git", 427 | "url": "https://github.com/sebastianbergmann/phpunit.git", 428 | "reference": "c28a790620fe30b049bb693be1ef9cd4e0fe906c" 429 | }, 430 | "dist": { 431 | "type": "zip", 432 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c28a790620fe30b049bb693be1ef9cd4e0fe906c", 433 | "reference": "c28a790620fe30b049bb693be1ef9cd4e0fe906c", 434 | "shasum": "" 435 | }, 436 | "require": { 437 | "ext-dom": "*", 438 | "ext-json": "*", 439 | "ext-pcre": "*", 440 | "ext-reflection": "*", 441 | "ext-spl": "*", 442 | "php": ">=5.3.3", 443 | "phpunit/php-code-coverage": "~2.0", 444 | "phpunit/php-file-iterator": "~1.3.1", 445 | "phpunit/php-text-template": "~1.2", 446 | "phpunit/php-timer": "~1.0.2", 447 | "phpunit/phpunit-mock-objects": "~2.2", 448 | "sebastian/comparator": "~1.0", 449 | "sebastian/diff": "~1.1", 450 | "sebastian/environment": "~1.0", 451 | "sebastian/exporter": "~1.0", 452 | "sebastian/version": "~1.0", 453 | "symfony/yaml": "~2.0" 454 | }, 455 | "suggest": { 456 | "phpunit/php-invoker": "~1.1" 457 | }, 458 | "bin": [ 459 | "phpunit" 460 | ], 461 | "type": "library", 462 | "extra": { 463 | "branch-alias": { 464 | "dev-master": "4.2.x-dev" 465 | } 466 | }, 467 | "autoload": { 468 | "classmap": [ 469 | "src/" 470 | ] 471 | }, 472 | "notification-url": "https://packagist.org/downloads/", 473 | "include-path": [ 474 | "", 475 | "../../symfony/yaml/" 476 | ], 477 | "license": [ 478 | "BSD-3-Clause" 479 | ], 480 | "authors": [ 481 | { 482 | "name": "Sebastian Bergmann", 483 | "email": "sebastian@phpunit.de", 484 | "role": "lead" 485 | } 486 | ], 487 | "description": "The PHP Unit Testing framework.", 488 | "homepage": "http://www.phpunit.de/", 489 | "keywords": [ 490 | "phpunit", 491 | "testing", 492 | "xunit" 493 | ], 494 | "time": "2014-09-14 09:31:24" 495 | }, 496 | { 497 | "name": "phpunit/phpunit-mock-objects", 498 | "version": "2.2.1", 499 | "source": { 500 | "type": "git", 501 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 502 | "reference": "b241b18d87a47093f20fae8b0ba40379b00bd53a" 503 | }, 504 | "dist": { 505 | "type": "zip", 506 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/b241b18d87a47093f20fae8b0ba40379b00bd53a", 507 | "reference": "b241b18d87a47093f20fae8b0ba40379b00bd53a", 508 | "shasum": "" 509 | }, 510 | "require": { 511 | "ocramius/instantiator": "~1.0", 512 | "php": ">=5.3.3", 513 | "phpunit/php-text-template": "~1.2" 514 | }, 515 | "require-dev": { 516 | "phpunit/phpunit": "~4.2" 517 | }, 518 | "suggest": { 519 | "ext-soap": "*" 520 | }, 521 | "type": "library", 522 | "extra": { 523 | "branch-alias": { 524 | "dev-master": "2.2.x-dev" 525 | } 526 | }, 527 | "autoload": { 528 | "classmap": [ 529 | "src/" 530 | ] 531 | }, 532 | "notification-url": "https://packagist.org/downloads/", 533 | "license": [ 534 | "BSD-3-Clause" 535 | ], 536 | "authors": [ 537 | { 538 | "name": "Sebastian Bergmann", 539 | "email": "sb@sebastian-bergmann.de", 540 | "role": "lead" 541 | } 542 | ], 543 | "description": "Mock Object library for PHPUnit", 544 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 545 | "keywords": [ 546 | "mock", 547 | "xunit" 548 | ], 549 | "time": "2014-09-06 17:32:37" 550 | }, 551 | { 552 | "name": "sebastian/comparator", 553 | "version": "1.0.0", 554 | "source": { 555 | "type": "git", 556 | "url": "https://github.com/sebastianbergmann/comparator.git", 557 | "reference": "f7069ee51fa9fb6c038e16a9d0e3439f5449dcf2" 558 | }, 559 | "dist": { 560 | "type": "zip", 561 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/f7069ee51fa9fb6c038e16a9d0e3439f5449dcf2", 562 | "reference": "f7069ee51fa9fb6c038e16a9d0e3439f5449dcf2", 563 | "shasum": "" 564 | }, 565 | "require": { 566 | "php": ">=5.3.3", 567 | "sebastian/diff": "~1.1", 568 | "sebastian/exporter": "~1.0" 569 | }, 570 | "require-dev": { 571 | "phpunit/phpunit": "~4.1" 572 | }, 573 | "type": "library", 574 | "extra": { 575 | "branch-alias": { 576 | "dev-master": "1.0.x-dev" 577 | } 578 | }, 579 | "autoload": { 580 | "classmap": [ 581 | "src/" 582 | ] 583 | }, 584 | "notification-url": "https://packagist.org/downloads/", 585 | "license": [ 586 | "BSD-3-Clause" 587 | ], 588 | "authors": [ 589 | { 590 | "name": "Sebastian Bergmann", 591 | "email": "sebastian@phpunit.de", 592 | "role": "lead" 593 | }, 594 | { 595 | "name": "Jeff Welch", 596 | "email": "whatthejeff@gmail.com" 597 | }, 598 | { 599 | "name": "Volker Dusch", 600 | "email": "github@wallbash.com" 601 | }, 602 | { 603 | "name": "Bernhard Schussek", 604 | "email": "bschussek@2bepublished.at" 605 | } 606 | ], 607 | "description": "Provides the functionality to compare PHP values for equality", 608 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 609 | "keywords": [ 610 | "comparator", 611 | "compare", 612 | "equality" 613 | ], 614 | "time": "2014-05-02 07:05:58" 615 | }, 616 | { 617 | "name": "sebastian/diff", 618 | "version": "1.1.0", 619 | "source": { 620 | "type": "git", 621 | "url": "https://github.com/sebastianbergmann/diff.git", 622 | "reference": "1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d" 623 | }, 624 | "dist": { 625 | "type": "zip", 626 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d", 627 | "reference": "1e091702a5a38e6b4c1ba9ca816e3dd343df2e2d", 628 | "shasum": "" 629 | }, 630 | "require": { 631 | "php": ">=5.3.3" 632 | }, 633 | "type": "library", 634 | "extra": { 635 | "branch-alias": { 636 | "dev-master": "1.1-dev" 637 | } 638 | }, 639 | "autoload": { 640 | "classmap": [ 641 | "src/" 642 | ] 643 | }, 644 | "notification-url": "https://packagist.org/downloads/", 645 | "license": [ 646 | "BSD-3-Clause" 647 | ], 648 | "authors": [ 649 | { 650 | "name": "Sebastian Bergmann", 651 | "email": "sebastian@phpunit.de", 652 | "role": "lead" 653 | }, 654 | { 655 | "name": "Kore Nordmann", 656 | "email": "mail@kore-nordmann.de" 657 | } 658 | ], 659 | "description": "Diff implementation", 660 | "homepage": "http://www.github.com/sebastianbergmann/diff", 661 | "keywords": [ 662 | "diff" 663 | ], 664 | "time": "2013-08-03 16:46:33" 665 | }, 666 | { 667 | "name": "sebastian/environment", 668 | "version": "1.0.0", 669 | "source": { 670 | "type": "git", 671 | "url": "https://github.com/sebastianbergmann/environment.git", 672 | "reference": "79517609ec01139cd7e9fded0dd7ce08c952ef6a" 673 | }, 674 | "dist": { 675 | "type": "zip", 676 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/79517609ec01139cd7e9fded0dd7ce08c952ef6a", 677 | "reference": "79517609ec01139cd7e9fded0dd7ce08c952ef6a", 678 | "shasum": "" 679 | }, 680 | "require": { 681 | "php": ">=5.3.3" 682 | }, 683 | "require-dev": { 684 | "phpunit/phpunit": "4.0.*@dev" 685 | }, 686 | "type": "library", 687 | "extra": { 688 | "branch-alias": { 689 | "dev-master": "1.0.x-dev" 690 | } 691 | }, 692 | "autoload": { 693 | "classmap": [ 694 | "src/" 695 | ] 696 | }, 697 | "notification-url": "https://packagist.org/downloads/", 698 | "license": [ 699 | "BSD-3-Clause" 700 | ], 701 | "authors": [ 702 | { 703 | "name": "Sebastian Bergmann", 704 | "email": "sebastian@phpunit.de", 705 | "role": "lead" 706 | } 707 | ], 708 | "description": "Provides functionality to handle HHVM/PHP environments", 709 | "homepage": "http://www.github.com/sebastianbergmann/environment", 710 | "keywords": [ 711 | "Xdebug", 712 | "environment", 713 | "hhvm" 714 | ], 715 | "time": "2014-02-18 16:17:19" 716 | }, 717 | { 718 | "name": "sebastian/exporter", 719 | "version": "1.0.1", 720 | "source": { 721 | "type": "git", 722 | "url": "https://github.com/sebastianbergmann/exporter.git", 723 | "reference": "1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529" 724 | }, 725 | "dist": { 726 | "type": "zip", 727 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529", 728 | "reference": "1f9a98e6f5dfe0524cb8c6166f7c82f3e9ae1529", 729 | "shasum": "" 730 | }, 731 | "require": { 732 | "php": ">=5.3.3" 733 | }, 734 | "require-dev": { 735 | "phpunit/phpunit": "4.0.*@dev" 736 | }, 737 | "type": "library", 738 | "extra": { 739 | "branch-alias": { 740 | "dev-master": "1.0.x-dev" 741 | } 742 | }, 743 | "autoload": { 744 | "classmap": [ 745 | "src/" 746 | ] 747 | }, 748 | "notification-url": "https://packagist.org/downloads/", 749 | "license": [ 750 | "BSD-3-Clause" 751 | ], 752 | "authors": [ 753 | { 754 | "name": "Sebastian Bergmann", 755 | "email": "sebastian@phpunit.de", 756 | "role": "lead" 757 | }, 758 | { 759 | "name": "Jeff Welch", 760 | "email": "whatthejeff@gmail.com" 761 | }, 762 | { 763 | "name": "Volker Dusch", 764 | "email": "github@wallbash.com" 765 | }, 766 | { 767 | "name": "Adam Harvey", 768 | "email": "aharvey@php.net", 769 | "role": "Lead" 770 | }, 771 | { 772 | "name": "Bernhard Schussek", 773 | "email": "bschussek@2bepublished.at" 774 | } 775 | ], 776 | "description": "Provides the functionality to export PHP variables for visualization", 777 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 778 | "keywords": [ 779 | "export", 780 | "exporter" 781 | ], 782 | "time": "2014-02-16 08:26:31" 783 | }, 784 | { 785 | "name": "sebastian/version", 786 | "version": "1.0.3", 787 | "source": { 788 | "type": "git", 789 | "url": "https://github.com/sebastianbergmann/version.git", 790 | "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43" 791 | }, 792 | "dist": { 793 | "type": "zip", 794 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", 795 | "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43", 796 | "shasum": "" 797 | }, 798 | "type": "library", 799 | "autoload": { 800 | "classmap": [ 801 | "src/" 802 | ] 803 | }, 804 | "notification-url": "https://packagist.org/downloads/", 805 | "license": [ 806 | "BSD-3-Clause" 807 | ], 808 | "authors": [ 809 | { 810 | "name": "Sebastian Bergmann", 811 | "email": "sebastian@phpunit.de", 812 | "role": "lead" 813 | } 814 | ], 815 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 816 | "homepage": "https://github.com/sebastianbergmann/version", 817 | "time": "2014-03-07 15:35:33" 818 | }, 819 | { 820 | "name": "symfony/yaml", 821 | "version": "v2.5.4", 822 | "target-dir": "Symfony/Component/Yaml", 823 | "source": { 824 | "type": "git", 825 | "url": "https://github.com/symfony/Yaml.git", 826 | "reference": "01a7695bcfb013d0a15c6757e15aae120342986f" 827 | }, 828 | "dist": { 829 | "type": "zip", 830 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/01a7695bcfb013d0a15c6757e15aae120342986f", 831 | "reference": "01a7695bcfb013d0a15c6757e15aae120342986f", 832 | "shasum": "" 833 | }, 834 | "require": { 835 | "php": ">=5.3.3" 836 | }, 837 | "type": "library", 838 | "extra": { 839 | "branch-alias": { 840 | "dev-master": "2.5-dev" 841 | } 842 | }, 843 | "autoload": { 844 | "psr-0": { 845 | "Symfony\\Component\\Yaml\\": "" 846 | } 847 | }, 848 | "notification-url": "https://packagist.org/downloads/", 849 | "license": [ 850 | "MIT" 851 | ], 852 | "authors": [ 853 | { 854 | "name": "Symfony Community", 855 | "homepage": "http://symfony.com/contributors" 856 | }, 857 | { 858 | "name": "Fabien Potencier", 859 | "email": "fabien@symfony.com" 860 | } 861 | ], 862 | "description": "Symfony Yaml Component", 863 | "homepage": "http://symfony.com", 864 | "time": "2014-08-31 03:22:04" 865 | } 866 | ], 867 | "aliases": [ 868 | 869 | ], 870 | "minimum-stability": "stable", 871 | "stability-flags": [ 872 | 873 | ], 874 | "prefer-stable": false, 875 | "platform": [ 876 | 877 | ], 878 | "platform-dev": [ 879 | 880 | ] 881 | } 882 | -------------------------------------------------------------------------------- /example-config.php: -------------------------------------------------------------------------------- 1 | '/fakehome/Documents/github/Pulsestorm/var/build', 7 | 'archive_files' => 'Pulstorm_Example.tar', 8 | 9 | //The Magento Connect extension name. Must be unique on Magento Connect 10 | //Has no relation to your code module name. Will be the Connect extension name 11 | 'extension_name' => 'Pulstorm_Example', 12 | 13 | //Your extension version. By default, if you're creating an extension from a 14 | //single Magento module, the tar-to-connect script will look to make sure this 15 | //matches the module version. You can skip this check by setting the 16 | //skip_version_compare value to true 17 | 'extension_version' => '1.0.3', 18 | 'skip_version_compare' => false, 19 | 20 | //You can also have the package script use the version in the module you 21 | //are packaging with. 22 | 'auto_detect_version' => false, 23 | 24 | //Where on your local system you'd like to build the files to 25 | 'path_output' => '/fakehome/Pulsestorm/var/build-connect', 26 | 27 | //Magento Connect license value. 28 | 'stability' => 'stable', 29 | 30 | //Magento Connect license value 31 | 'license' => 'MIT', 32 | 33 | //Magento Connect channel value. This should almost always (always?) be community 34 | 'channel' => 'community', 35 | 36 | //Magento Connect information fields. 37 | 'summary' => 'Provides navigation shortcuts for the admin console\'s navigation and gloal search', 38 | 'description' => 'This extension provides Magento admin console users with an "application launcher". This application launcher provides instant access to the admin console\'s navigation, every system configuration search section, as well as the Magento global search. The Pulse Storm launcher is a free, open source, must have extension for anyone working with Magento. ', 39 | 'notes' => 'Typo fixes, properly aborts ajax requests.', 40 | 41 | //Magento Connect author information. If author_email is foo@example.com, script will 42 | //prompt you for the correct name. Should match your http://www.magentocommerce.com/ 43 | //login email address 44 | 'author_name' => 'Alan Storm', 45 | 'author_user' => 'alanstorm', 46 | 'author_email' => 'foo@example.com', 47 | 48 | // Optional: adds additional author nodes to package.xml 49 | 'additional_authors' => array( 50 | array( 51 | 'author_name' => 'Mike West', 52 | 'author_user' => 'micwest', 53 | 'author_email' => 'foo2@example.com', 54 | ), 55 | array( 56 | 'author_name' => 'Reggie Gabriel', 57 | 'author_user' => 'rgabriel', 58 | 'author_email' => 'foo3@example.com', 59 | ), 60 | ), 61 | 62 | //PHP min/max fields for Connect. I don't know if anyone uses these, but you should 63 | //probably check that they're accurate 64 | 'php_min' => '5.2.0', 65 | 'php_max' => '6.0.0', 66 | 67 | //PHP extension dependencies. An array containing one or more of either: 68 | // - a single string (the name of the extension dependency); use this if the 69 | // extension version does not matter 70 | // - an associative array with 'name', 'min', and 'max' keys which correspond 71 | // to the extension's name and min/max required versions 72 | //Example: 73 | // array('json', array('name' => 'mongo', 'min' => '1.3.0', 'max' => '1.4.0')) 74 | 'extensions' => array() 75 | ); 76 | -------------------------------------------------------------------------------- /magento-tar-to-connect.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astorm/MagentoTarToConnect/4533f0aa683d58622783d3a4d614ff97e5345c7a/magento-tar-to-connect.phar -------------------------------------------------------------------------------- /magento-tar-to-connect.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | xpath("dir[@name='".$part."']"); 78 | if(count($nodes) > 0) 79 | { 80 | $node = array_pop($nodes); 81 | } 82 | else 83 | { 84 | $node = $node->addChild('dir'); 85 | $node->addAttribute('name', $part); 86 | } 87 | } 88 | 89 | $node = $node->addChild('file'); 90 | $node->addAttribute('name',$single_file); 91 | $node->addAttribute('hash',md5_file($full_dir)); 92 | } 93 | 94 | static public function createPackageXml($files, $base_dir, $config) 95 | { 96 | $xml = simplexml_load_string(''); 97 | $xml->name = $config['extension_name']; 98 | $xml->version = $config['extension_version']; 99 | $xml->stability = $config['stability']; 100 | $xml->license = $config['license']; 101 | $xml->channel = $config['channel']; 102 | $xml->extends = ''; 103 | $xml->summary = $config['summary']; 104 | $xml->description = $config['description']; 105 | $xml->notes = $config['notes']; 106 | 107 | $authors = $xml->addChild('authors'); 108 | foreach (self::getAuthorData($config) as $oneAuthor) { 109 | $author = $authors->addChild('author'); 110 | $author->name = $oneAuthor['author_name']; 111 | $author->user = $oneAuthor['author_user']; 112 | $author->email = $oneAuthor['author_email']; 113 | } 114 | 115 | $xml->date = date('Y-m-d'); 116 | $xml->time = date('G:i:s'); 117 | $xml->compatible = ''; 118 | $dependencies = $xml->addChild('dependencies'); 119 | $required = $dependencies->addChild('required'); 120 | $php = $required->addChild('php'); 121 | $php->min = $config['php_min']; //'5.2.0'; 122 | $php->max = $config['php_max']; //'6.0.0'; 123 | 124 | // add php extension dependencies 125 | if (is_array($config['extensions'])) { 126 | foreach ($config['extensions'] as $extinfo) { 127 | $extension = $required->addChild('extension'); 128 | if (is_array($extinfo)) { 129 | $extension->name = $extinfo['name']; 130 | $extension->min = isset($extinfo['min']) ? $extinfo['min'] : ""; 131 | $extension->max = isset($extinfo['max']) ? $extinfo['max'] : ""; 132 | } else { 133 | $extension->name = $extinfo; 134 | $extension->min = ""; 135 | $extension->max = ""; 136 | } 137 | } 138 | } 139 | 140 | $node = $xml->addChild('contents'); 141 | $node = $node->addChild('target'); 142 | $node->addAttribute('name', 'mage'); 143 | 144 | // $files = $this->recursiveGlob($temp_dir); 145 | // $files = array_unique($files); 146 | $temp_dir = false; 147 | foreach($files as $file) 148 | { 149 | //$this->addFileNode($node,$temp_dir,$file); 150 | self::createPackageXmlAddNode($node, $file, $base_dir); 151 | } 152 | //file_put_contents($temp_dir . '/package.xml', $xml->asXml()); 153 | 154 | return $xml->asXml(); 155 | } 156 | 157 | static public function getTempDir() 158 | { 159 | $name = tempnam(sys_get_temp_dir(),'tmp'); 160 | unlink($name); 161 | $name = $name; 162 | mkdir($name,0777,true); 163 | return $name; 164 | } 165 | 166 | static public function validateConfig($config) 167 | { 168 | $keys = array('base_dir','archive_files','path_output', 169 | ); 170 | foreach($keys as $key) 171 | { 172 | if(!array_key_exists($key, $config)) 173 | { 174 | self::error("Config file missing key [$key]"); 175 | } 176 | } 177 | 178 | if($config['author_email'] == 'foo@example.com') 179 | { 180 | $email = self::input("Email Address is configured with foo@example.com. Enter a new address"); 181 | if(trim($email) != '') 182 | { 183 | $config['author_email'] = trim($email); 184 | } 185 | } 186 | 187 | if(!array_key_exists('extensions', $config)) 188 | { 189 | $config['extensions'] = null; 190 | } 191 | return $config; 192 | 193 | 194 | } 195 | 196 | static public function loadConfig($config_name=false) 197 | { 198 | if(!$config_name) 199 | { 200 | $config_name = basename(__FILE__,'php') . 'config.php'; 201 | } 202 | if(!file_exists($config_name)) 203 | { 204 | self::error("Could not find $config_name. Create this file, or pass in an alternate"); 205 | } 206 | $config = include $config_name; 207 | 208 | $config = self::validateConfig($config); 209 | return $config; 210 | } 211 | 212 | static public function getModuleVersion($files) 213 | { 214 | $configs = array(); 215 | foreach($files as $file) 216 | { 217 | if(basename($file) == 'config.xml') 218 | { 219 | $configs[] = $file; 220 | } 221 | } 222 | 223 | foreach($configs as $file) 224 | { 225 | $xml = simplexml_load_file($file); 226 | $version_strings = $xml->xpath('//version'); 227 | foreach($version_strings as $version) 228 | { 229 | $version = (string) $version; 230 | if(!empty($version)) 231 | { 232 | return (string)$version; 233 | } 234 | } 235 | } 236 | 237 | foreach($configs as $file) 238 | { 239 | $xml = simplexml_load_file($file); 240 | $modules = $xml->xpath('//modules'); 241 | foreach($modules[0] as $module) 242 | { 243 | $version = (string)$module->version; 244 | if(!empty($version)) 245 | { 246 | return $version; 247 | } 248 | } 249 | } 250 | } 251 | 252 | static public function checkModuleVersionVsPackageVersion($files, $extension_version) 253 | { 254 | $configs = array(); 255 | foreach($files as $file) 256 | { 257 | if(basename($file) == 'config.xml') 258 | { 259 | $configs[] = $file; 260 | } 261 | } 262 | 263 | foreach($configs as $file) 264 | { 265 | $xml = simplexml_load_file($file); 266 | $version_strings = $xml->xpath('//version'); 267 | foreach($version_strings as $version) 268 | { 269 | if($version != $extension_version) 270 | { 271 | self::error( 272 | "Extension Version [$extension_version] does not match " . 273 | "module version [$version] found in a config.xml file. Add " . 274 | "'skip_version_compare' => true to configuration to skip this check." 275 | ); 276 | } 277 | } 278 | } 279 | } 280 | 281 | static public function buildExtensionFromConfig($config) 282 | { 283 | ob_start(); 284 | 285 | # extract and validate config values 286 | $base_dir = $config['base_dir']; //'/Users/alanstorm/Documents/github/Pulsestorm/var/build'; 287 | if($base_dir['0'] !== '/') 288 | { 289 | $base_dir = getcwd() . '/' . $base_dir; 290 | } 291 | $archive_files = $config['archive_files']; //'Pulsestorm_Modulelist.tar'; 292 | $path_output = $config['path_output']; //'/Users/alanstorm/Desktop/working'; 293 | $archive_connect = $config['extension_name'] . '-' . $config['extension_version'] . '.tgz'; 294 | ###-------------------------------------------------- 295 | 296 | # make sure the archive we're creating exists 297 | if(!file_exists($base_dir . '/' . $archive_files)) 298 | { 299 | self::error('Can\'t find specified archive, bailing' . "\n[" . $base_dir . '/' . $archive_files.']'); 300 | exit; 301 | } 302 | ###-------------------------------------------------- 303 | 304 | # create a temporary directory, move to temporary 305 | $temp_dir = self::getTempDir(); 306 | chdir($temp_dir); 307 | ###-------------------------------------------------- 308 | 309 | # copy and extract archive 310 | shell_exec("cp '" . $base_dir . "/" . $archive_files . "' " . $temp_dir); 311 | if (preg_match('/\.zip$/', $archive_files)) { 312 | shell_exec("unzip -o '" . $temp_dir . "/" . $archive_files . "'"); 313 | } else { 314 | shell_exec("tar -xvf '" . $temp_dir . "/" . $archive_files . "'"); 315 | } 316 | shell_exec("rm '" . $temp_dir . "/" . $archive_files . "'"); 317 | ###-------------------------------------------------- 318 | 319 | # get a lsit of all the files without directories 320 | $all = self::globRecursive($temp_dir . '/*'); 321 | $dirs = self::globRecursive($temp_dir .'/*',GLOB_ONLYDIR); 322 | $files = array_diff($all, $dirs); 323 | ###-------------------------------------------------- 324 | 325 | # now that we've extracted the files, yoink the version number from the config 326 | # this only works is auto_detect_version is true. Also, may not return what 327 | # you expect if your connect extension includes multiple Magento modules 328 | if(isset($config['auto_detect_version']) && $config['auto_detect_version'] == true) 329 | { 330 | $config['extension_version'] = self::getModuleVersion($files); 331 | $archive_connect = $config['extension_name'] . '-' . $config['extension_version'] . '.tgz'; 332 | } 333 | ###-------------------------------------------------- 334 | 335 | # checks that your Magento Connect extension version matches the version of your 336 | # modules file. Probably redundant if auto_detect_version is true 337 | if(!$config['skip_version_compare']) 338 | { 339 | self::checkModuleVersionVsPackageVersion($files, $config['extension_version']); 340 | } 341 | ###-------------------------------------------------- 342 | 343 | # creates the base extension package.xml file 344 | $xml = self::createPackageXml($files,$temp_dir,$config); 345 | file_put_contents($temp_dir . '/package.xml',$xml); 346 | self::output($temp_dir); 347 | ###-------------------------------------------------- 348 | 349 | # create the base output folder if it doesn't exist 350 | if(!is_dir($path_output)) 351 | { 352 | mkdir($path_output, 0777, true); 353 | } 354 | ###-------------------------------------------------- 355 | 356 | # use Magento architve to tar up the files 357 | $archiver = new Mage_Archive_Tar; 358 | $archiver->pack($temp_dir,$path_output.'/'.$archive_files,true); 359 | ###-------------------------------------------------- 360 | 361 | # zip up the archive 362 | shell_exec("gzip '" . $path_output . "/" . $archive_files . "'"); 363 | shell_exec("mv '" . $path_output . "/" . $archive_files . ".gz' '" . $path_output . "/" . $archive_connect . "'"); 364 | ###-------------------------------------------------- 365 | 366 | # Creating extension xml for connect using the extension name 367 | self::createExtensionXml($files, $config, $temp_dir, $path_output); 368 | ###-------------------------------------------------- 369 | 370 | # Report on what we did 371 | self::output(''); 372 | self::output('Build Complete'); 373 | self::output('--------------------------------------------------'); 374 | self::output( "Built tgz in $path_output\n"); 375 | 376 | self::output( 377 | "Built XML for Connect Manager in" . "\n\n" . 378 | 379 | " $path_output/var/connect " . "\n\n" . 380 | 381 | "place in `/path/to/magento/var/connect to load extension in Connect Manager"); 382 | 383 | ###-------------------------------------------------- 384 | 385 | return ob_get_clean(); 386 | } 387 | 388 | static public function main($argv) 389 | { 390 | $this_script = array_shift($argv); 391 | $config_file = array_shift($argv); 392 | $config = self::loadConfig($config_file); 393 | 394 | self::output( 395 | self::buildExtensionFromConfig($config) 396 | ); 397 | 398 | } 399 | /** 400 | * extrapolate the target module using the file absolute path 401 | * @param string $filePath 402 | * @return string 403 | */ 404 | static public function extractTarget($filePath) 405 | { 406 | foreach (self::getTargetMap() as $tMap) { 407 | $pattern = '#' . $tMap['path'] . '#'; 408 | if (preg_match($pattern, $filePath)) { 409 | return $tMap['target']; 410 | } 411 | } 412 | return 'mage'; 413 | } 414 | /** 415 | * get target map 416 | * @return array 417 | */ 418 | static public function getTargetMap() 419 | { 420 | return array( 421 | array('path' => 'app/etc', 'target' => 'mageetc'), 422 | array('path' => 'app/code/local', 'target' => 'magelocal'), 423 | array('path' => 'app/code/community', 'target' => 'magecommunity'), 424 | array('path' => 'app/code/core', 'target' => 'magecore'), 425 | array('path' => 'app/design', 'target' => 'magedesign'), 426 | array('path' => 'lib', 'target' => 'magelib'), 427 | array('path' => 'app/locale', 'target' => 'magelocale'), 428 | array('path' => 'media/', 'target' => 'magemedia'), 429 | array('path' => 'skin/', 'target' => 'mageskin'), 430 | array('path' => 'http://', 'target' => 'mageweb'), 431 | array('path' => 'https://', 'target' => 'mageweb'), 432 | array('path' => 'Test/', 'target' => 'magetest'), 433 | ); 434 | } 435 | static public function createExtensionXml($files, $config, $tempDir, $path_output) 436 | { 437 | $extensionPath = $tempDir . DIRECTORY_SEPARATOR . 'var/connect/'; 438 | if (!is_dir($extensionPath)) { 439 | mkdir($extensionPath, 0777, true); 440 | } 441 | $extensionFileName = $extensionPath . $config['extension_name'] . '.xml'; 442 | file_put_contents($extensionFileName, self::buildExtensionXml($files, $config)); 443 | 444 | shell_exec("cp -Rf '" . $tempDir . DIRECTORY_SEPARATOR . "var' '" . $path_output . "'"); 445 | } 446 | static public function buildExtensionXml($files, $config) 447 | { 448 | $xml = simplexml_load_string('<_/>'); 449 | $build_data = self::getBuildData($xml, $files, $config); 450 | 451 | foreach ($build_data as $key => $value) { 452 | if (is_array($value) && is_callable($key)) { 453 | call_user_func_array($key, $value); 454 | } else { 455 | self::addChildNode($xml, $key, $value); 456 | } 457 | } 458 | 459 | return $xml->asXml(); 460 | } 461 | /** 462 | * Get an array of data to build the extension xml. The array of data will contains the key necessary 463 | * to build each node and key that are actual callback functions to be called to build sub-section of the xml. 464 | * @param SimpleXMLElement $xml 465 | * @param array $files 466 | * @param array $config 467 | * @return array 468 | */ 469 | static public function getBuildData(SimpleXMLElement $xml, array $files, array $config) 470 | { 471 | return array( 472 | 'form_key' => isset($config['form_key']) ? $config['form_key'] : uniqid(), 473 | '_create' => isset($config['_create']) ? $config['_create'] : '', 474 | 'name' => $config['extension_name'], 475 | 'channel'=> $config['channel'], 476 | 'Pulsestorm_MagentoTarToConnect::buildVersionIdsNode' => array($xml), 477 | 'summary' => $config['summary'], 478 | 'description' => $config['description'], 479 | 'license' => $config['license'], 480 | 'license_uri' => isset($config['license_uri']) ? $config['license_uri'] : '', 481 | 'version' => $config['extension_version'], 482 | 'stability' => $config['stability'], 483 | 'notes' => $config['notes'], 484 | 'Pulsestorm_MagentoTarToConnect::buildAuthorsNode' => array($xml, $config), 485 | 'Pulsestorm_MagentoTarToConnect::buildPhpDependsNode' => array($xml, $config), 486 | 'Pulsestorm_MagentoTarToConnect::buildContentsNode' => array($xml, $files) 487 | ); 488 | } 489 | /** 490 | * Remove a passed in file absolute path and return the relative path to the Magento application file context. 491 | * @param string $file 492 | * @return string 493 | */ 494 | static public function extractRelativePath($file) 495 | { 496 | $pattern = '/app\/etc\/|app\/code\/community\/|app\/code\/local\/|app\/design\/|lib\/|app\/locale\/|skin\/|js\//'; 497 | $relativePath = self::splitFilePath($file, $pattern); 498 | if ($file !== $relativePath) { 499 | return $relativePath; 500 | } 501 | $shellDir = 'shell'; 502 | $relativePath = self::splitFilePath($file, '/' . $shellDir . '\//'); 503 | return ($file !== $relativePath) ? $shellDir . DIRECTORY_SEPARATOR . $relativePath : $file; 504 | } 505 | /** 506 | * Split a file path using the passed in pattern and file absolute path and return 507 | * the relative path to the file. 508 | * @param string $file 509 | * @param string $pattern 510 | * @return string The relative path to file 511 | */ 512 | static public function splitFilePath($file, $pattern) 513 | { 514 | $splitPath = preg_split($pattern, $file, -1); 515 | return (count($splitPath) > 1) ? $splitPath[1] : $file; 516 | } 517 | /** 518 | * Build 'contents' node including all its child nodes. 519 | * @param SimpleXMLElement $xml 520 | * @param array $files 521 | * @return void 522 | */ 523 | static public function buildContentsNode(SimpleXMLElement $xml, array $files) 524 | { 525 | $node = self::addChildNode($xml, 'contents', ''); 526 | $call_backs = array( 527 | 'target' => 'Pulsestorm_MagentoTarToConnect::extractTarget', 528 | 'path' => 'Pulsestorm_MagentoTarToConnect::extractRelativePath', 529 | 'type' => 'file', 530 | 'include'=> '', 531 | 'ignore' => '' 532 | ); 533 | 534 | $parent_nodes = array_reduce(array_keys($call_backs), function ($item, $key) use ($node) { 535 | $item[$key] = Pulsestorm_MagentoTarToConnect::addChildNode($node, $key, ''); 536 | return $item; 537 | }); 538 | 539 | // Adding empty node, this is a workaround for the Magento connect bug. 540 | // When no empty nodes are added the first file is removed from the package extension. 541 | foreach ($parent_nodes as $child_key => $child_node) { 542 | self::addChildNode($child_node, $child_key, ''); 543 | } 544 | 545 | foreach ($files as $file) { 546 | foreach ($parent_nodes as $key => $child_node) { 547 | $call_back = $call_backs[$key]; 548 | $value = ($call_back === 'file') ? $call_back : (is_callable($call_back) ? call_user_func_array($call_back, array($file)) : $call_back); 549 | self::addChildNode($child_node, $key, $value); 550 | } 551 | } 552 | } 553 | /** 554 | * Add a 'depends_php_min' node and a 'depends_php_max' to the passed in SimpleXMLElement class instance object. 555 | * @param SimpleXMLElement $xml 556 | * @param array $config 557 | * @return void 558 | */ 559 | static public function buildPhpDependsNode(SimpleXMLElement $xml, array $config) 560 | { 561 | $data = array('depends_php_min' => 'php_min', 'depends_php_max' => 'php_max'); 562 | foreach ($data as $key => $cfg_key) { 563 | self::addChildNode($xml, $key, $config[$cfg_key]); 564 | } 565 | } 566 | /** 567 | * Get author data, which is a combination of author data and additional authors data from the configuration. 568 | * @param array $config 569 | * @return array 570 | */ 571 | static public function getAuthorData(array $config) 572 | { 573 | $authorList[0] = array( 574 | 'author_name' => $config['author_name'], 575 | 'author_user' => $config['author_user'], 576 | 'author_email' => $config['author_email'], 577 | ); 578 | if (array_key_exists('additional_authors', $config)) { 579 | $authorList = array_merge($authorList, $config['additional_authors']); 580 | } 581 | return $authorList; 582 | } 583 | /** 584 | * Get a specific author information by key. 585 | * @param array $authorList 586 | * @param string $key 587 | * @return array 588 | */ 589 | static public function getAuthorInfoByKey(array $authorList, $key) 590 | { 591 | return array_map(function($author) use ($key) { return $author[$key]; }, $authorList); 592 | } 593 | /** 594 | * Build 'authors' node including all its child nodes. 595 | * @param SimpleXMLElement $xml 596 | * @param array $config 597 | * @return void 598 | */ 599 | static public function buildAuthorsNode(SimpleXMLElement $xml, array $config) 600 | { 601 | $meta = array('name' => 'author_name', 'user' => 'author_user', 'email' => 'author_email'); 602 | $authorList = self::getAuthorData($config); 603 | $authors = self::addChildNode($xml, 'authors', ''); 604 | foreach ($meta as $key => $cfg_key) { 605 | $parentNode = self::addChildNode($authors, $key, ''); 606 | foreach (self::getAuthorInfoByKey($authorList, $cfg_key) as $value) { 607 | self::addChildNode($parentNode, $key, $value); 608 | } 609 | } 610 | } 611 | /** 612 | * Build 'version_ids' node including all its child nodes. 613 | * @param SimpleXMLElement $xml 614 | * @return void 615 | */ 616 | static public function buildVersionIdsNode(SimpleXMLElement $xml) 617 | { 618 | $key = 'version_ids'; 619 | $parentNode = self::addChildNode($xml, $key, ''); 620 | foreach (array(2, 1) as $version) { 621 | self::addChildNode($parentNode, $key, $version); 622 | } 623 | } 624 | /** 625 | * Add child node to a passed in SimpleXMLElement class instance object. 626 | * @param SimpleXMLElement $context 627 | * @param string $name 628 | * @param string $value 629 | * @return SimpleXMLElement 630 | */ 631 | static public function addChildNode(SimpleXMLElement $context, $name, $value='') 632 | { 633 | $child = $context->addChild($name); 634 | if (trim($value)) { 635 | $child->{0} = $value; 636 | } 637 | return $child; 638 | } 639 | } 640 | if(isset($argv)) 641 | { 642 | Pulsestorm_MagentoTarToConnect::main($argv); 643 | } 644 | -------------------------------------------------------------------------------- /src/magento/downloader/lib/Mage/Archive/Abstract.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | class Mage_Archive_Abstract 35 | { 36 | /** 37 | * Write data to file. If file can't be opened - throw exception 38 | * 39 | * @param string $destination 40 | * @param string $data 41 | * @return boolean 42 | * @throws Mage_Exception 43 | */ 44 | protected function _writeFile($destination, $data) 45 | { 46 | $destination = trim($destination); 47 | if(false === file_put_contents($destination, $data)) { 48 | throw new Mage_Exception("Can't write to file: " . $destination); 49 | } 50 | return true; 51 | } 52 | 53 | /** 54 | * Read data from file. If file can't be opened, throw to exception. 55 | * 56 | * @param string $source 57 | * @return string 58 | * @throws Mage_Exception 59 | */ 60 | protected function _readFile($source) 61 | { 62 | $data = ''; 63 | if (is_file($source) && is_readable($source)) { 64 | $data = @file_get_contents($source); 65 | if ($data === false) { 66 | throw new Mage_Exception("Can't get contents from: " . $source); 67 | } 68 | } 69 | return $data; 70 | } 71 | 72 | /** 73 | * Get file name from source (URI) without last extension. 74 | * 75 | * @param string $source 76 | * @param bool $withExtension 77 | * @return mixed|string 78 | */ 79 | public function getFilename($source, $withExtension=false) 80 | { 81 | $file = str_replace(dirname($source) . DS, '', $source); 82 | if (!$withExtension) { 83 | $file = substr($file, 0, strrpos($file, '.')); 84 | } 85 | return $file; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/magento/downloader/lib/Mage/Archive/Helper/File.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | class Mage_Archive_Helper_File 35 | { 36 | /** 37 | * Full path to directory where file located 38 | * 39 | * @var string 40 | */ 41 | protected $_fileLocation; 42 | 43 | /** 44 | * File name 45 | * 46 | * @var string 47 | */ 48 | protected $_fileName; 49 | 50 | /** 51 | * Full path (directory + filename) to file 52 | * 53 | * @var string 54 | */ 55 | protected $_filePath; 56 | 57 | /** 58 | * File permissions that will be set if file opened in write mode 59 | * 60 | * @var int 61 | */ 62 | protected $_chmod; 63 | 64 | /** 65 | * File handler 66 | * 67 | * @var pointer 68 | */ 69 | protected $_fileHandler; 70 | 71 | /** 72 | * Set file path via constructor 73 | * 74 | * @param string $filePath 75 | */ 76 | public function __construct($filePath) 77 | { 78 | $pathInfo = pathinfo($filePath); 79 | 80 | $this->_filePath = $filePath; 81 | $this->_fileLocation = isset($pathInfo['dirname']) ? $pathInfo['dirname'] : ''; 82 | $this->_fileName = isset($pathInfo['basename']) ? $pathInfo['basename'] : ''; 83 | } 84 | 85 | /** 86 | * Close file if it's not closed before object destruction 87 | */ 88 | public function __destruct() 89 | { 90 | if ($this->_fileHandler) { 91 | $this->_close(); 92 | } 93 | } 94 | 95 | /** 96 | * Open file 97 | * 98 | * @param string $mode 99 | * @param int $chmod 100 | * @throws Mage_Exception 101 | */ 102 | public function open($mode = 'w+', $chmod = 0666) 103 | { 104 | if ($this->_isWritableMode($mode)) { 105 | if (!is_writable($this->_fileLocation)) { 106 | throw new Mage_Exception('Permission denied to write to ' . $this->_fileLocation); 107 | } 108 | 109 | if (is_file($this->_filePath) && !is_writable($this->_filePath)) { 110 | throw new Mage_Exception("Can't open file " . $this->_fileName . " for writing. Permission denied."); 111 | } 112 | } 113 | 114 | if ($this->_isReadableMode($mode) && (!is_file($this->_filePath) || !is_readable($this->_filePath))) { 115 | if (!is_file($this->_filePath)) { 116 | throw new Mage_Exception('File ' . $this->_filePath . ' does not exist'); 117 | } 118 | 119 | if (!is_readable($this->_filePath)) { 120 | throw new Mage_Exception('Permission denied to read file ' . $this->_filePath); 121 | } 122 | } 123 | 124 | $this->_open($mode); 125 | 126 | $this->_chmod = $chmod; 127 | } 128 | 129 | /** 130 | * Write data to file 131 | * 132 | * @param string $data 133 | */ 134 | public function write($data) 135 | { 136 | $this->_checkFileOpened(); 137 | $this->_write($data); 138 | } 139 | 140 | /** 141 | * Read data from file 142 | * 143 | * @param int $length 144 | * @return string|boolean 145 | */ 146 | public function read($length = 4096) 147 | { 148 | $data = false; 149 | $this->_checkFileOpened(); 150 | if ($length > 0) { 151 | $data = $this->_read($length); 152 | } 153 | 154 | return $data; 155 | } 156 | 157 | /** 158 | * Check whether end of file reached 159 | * 160 | * @return boolean 161 | */ 162 | public function eof() 163 | { 164 | $this->_checkFileOpened(); 165 | return $this->_eof(); 166 | } 167 | 168 | /** 169 | * Close file 170 | */ 171 | public function close() 172 | { 173 | $this->_checkFileOpened(); 174 | $this->_close(); 175 | $this->_fileHandler = false; 176 | @chmod($this->_filePath, $this->_chmod); 177 | } 178 | 179 | /** 180 | * Implementation of file opening 181 | * 182 | * @param string $mode 183 | * @throws Mage_Exception 184 | */ 185 | protected function _open($mode) 186 | { 187 | $this->_fileHandler = @fopen($this->_filePath, $mode); 188 | 189 | if (false === $this->_fileHandler) { 190 | throw new Mage_Exception('Failed to open file ' . $this->_filePath); 191 | } 192 | } 193 | 194 | /** 195 | * Implementation of writing data to file 196 | * 197 | * @param string $data 198 | * @throws Mage_Exception 199 | */ 200 | protected function _write($data) 201 | { 202 | $result = @fwrite($this->_fileHandler, $data); 203 | 204 | if (false === $result) { 205 | throw new Mage_Exception('Failed to write data to ' . $this->_filePath); 206 | } 207 | } 208 | 209 | /** 210 | * Implementation of file reading 211 | * 212 | * @param int $length 213 | * @throws Mage_Exception 214 | */ 215 | protected function _read($length) 216 | { 217 | $result = fread($this->_fileHandler, $length); 218 | 219 | if (false === $result) { 220 | throw new Mage_Exception('Failed to read data from ' . $this->_filePath); 221 | } 222 | 223 | return $result; 224 | } 225 | 226 | /** 227 | * Implementation of EOF indicator 228 | * 229 | * @return boolean 230 | */ 231 | protected function _eof() 232 | { 233 | return feof($this->_fileHandler); 234 | } 235 | 236 | /** 237 | * Implementation of file closing 238 | */ 239 | protected function _close() 240 | { 241 | fclose($this->_fileHandler); 242 | } 243 | 244 | /** 245 | * Check whether requested mode is writable mode 246 | * 247 | * @param string $mode 248 | */ 249 | protected function _isWritableMode($mode) 250 | { 251 | return preg_match('/(^[waxc])|(\+$)/', $mode); 252 | } 253 | 254 | /** 255 | * Check whether requested mode is readable mode 256 | * 257 | * @param string $mode 258 | */ 259 | protected function _isReadableMode($mode) { 260 | return !$this->_isWritableMode($mode); 261 | } 262 | 263 | /** 264 | * Check whether file is opened 265 | * 266 | * @throws Mage_Exception 267 | */ 268 | protected function _checkFileOpened() 269 | { 270 | if (!$this->_fileHandler) { 271 | throw new Mage_Exception('File not opened'); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/magento/downloader/lib/Mage/Archive/Interface.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | interface Mage_Archive_Interface 35 | { 36 | /** 37 | * Pack file or directory. 38 | * 39 | * @param string $source 40 | * @param string $destination 41 | * @return string 42 | */ 43 | public function pack($source, $destination); 44 | 45 | /** 46 | * Unpack file or directory. 47 | * 48 | * @param string $source 49 | * @param string $destination 50 | * @return string 51 | */ 52 | public function unpack($source, $destination); 53 | } 54 | -------------------------------------------------------------------------------- /src/magento/downloader/lib/Mage/Archive/Tar.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | class Mage_Archive_Tar extends Mage_Archive_Abstract implements Mage_Archive_Interface 35 | { 36 | /** 37 | * Tar block size 38 | * 39 | * @const int 40 | */ 41 | const TAR_BLOCK_SIZE = 512; 42 | 43 | /** 44 | * Keep file or directory for packing. 45 | * 46 | * @var string 47 | */ 48 | protected $_currentFile; 49 | 50 | /** 51 | * Keep path to file or directory for packing. 52 | * 53 | * @var mixed 54 | */ 55 | protected $_currentPath; 56 | 57 | /** 58 | * Skip first level parent directory. Example: 59 | * use test/fip.php instead test/test/fip.php; 60 | * 61 | * @var mixed 62 | */ 63 | protected $_skipRoot; 64 | 65 | /** 66 | * Tarball data writer 67 | * 68 | * @var Mage_Archive_Helper_File 69 | */ 70 | protected $_writer; 71 | 72 | /** 73 | * Tarball data reader 74 | * 75 | * @var Mage_Archive_Helper_File 76 | */ 77 | protected $_reader; 78 | 79 | /** 80 | * Path to file where tarball should be placed 81 | * 82 | * @var string 83 | */ 84 | protected $_destinationFilePath; 85 | 86 | /** 87 | * Initialize tarball writer 88 | * 89 | * @return Mage_Archive_Tar 90 | */ 91 | protected function _initWriter() 92 | { 93 | $this->_writer = new Mage_Archive_Helper_File($this->_destinationFilePath); 94 | $this->_writer->open('w'); 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Returns string that is used for tar's header parsing 101 | * 102 | * @return string 103 | */ 104 | protected static final function _getFormatParseHeader() 105 | { 106 | return 'a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/' 107 | . 'a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12closer'; 108 | } 109 | 110 | /** 111 | * Destroy tarball writer 112 | * 113 | * @return Mage_Archive_Tar 114 | */ 115 | protected function _destroyWriter() 116 | { 117 | if ($this->_writer instanceof Mage_Archive_Helper_File) { 118 | $this->_writer->close(); 119 | $this->_writer = null; 120 | } 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * Get tarball writer 127 | * 128 | * @return Mage_Archive_Helper_File 129 | */ 130 | protected function _getWriter() 131 | { 132 | if (!$this->_writer) { 133 | $this->_initWriter(); 134 | } 135 | 136 | return $this->_writer; 137 | } 138 | 139 | /** 140 | * Initialize tarball reader 141 | * 142 | * @return Mage_Archive_Tar 143 | */ 144 | protected function _initReader() 145 | { 146 | $this->_reader = new Mage_Archive_Helper_File($this->_getCurrentFile()); 147 | $this->_reader->open('r'); 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * Destroy tarball reader 154 | * 155 | * @return Mage_Archive_Tar 156 | */ 157 | protected function _destroyReader() 158 | { 159 | if ($this->_reader instanceof Mage_Archive_Helper_File) { 160 | $this->_reader->close(); 161 | $this->_reader = null; 162 | } 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * Get tarball reader 169 | * 170 | * @return Mage_Archive_Helper_File 171 | */ 172 | protected function _getReader() 173 | { 174 | if (!$this->_reader) { 175 | $this->_initReader(); 176 | } 177 | 178 | return $this->_reader; 179 | } 180 | 181 | /** 182 | * Set option that define ability skip first catalog level. 183 | * 184 | * @param mixed $skipRoot 185 | * @return Mage_Archive_Tar 186 | */ 187 | protected function _setSkipRoot($skipRoot) 188 | { 189 | $this->_skipRoot = $skipRoot; 190 | return $this; 191 | } 192 | 193 | /** 194 | * Set file which is packing. 195 | * 196 | * @param string $file 197 | * @return Mage_Archive_Tar 198 | */ 199 | protected function _setCurrentFile($file) 200 | { 201 | $this->_currentFile = $file .((!is_link($file) && is_dir($file) && substr($file, -1) != DS) ? DS : ''); 202 | return $this; 203 | } 204 | 205 | /** 206 | * Set path to file where tarball should be placed 207 | * 208 | * @param string $destinationFilePath 209 | * @return Mage_Archive_Tar 210 | */ 211 | protected function _setDestinationFilePath($destinationFilePath) 212 | { 213 | $this->_destinationFilePath = $destinationFilePath; 214 | return $this; 215 | } 216 | 217 | /** 218 | * Retrieve file which is packing. 219 | * 220 | * @return string 221 | */ 222 | protected function _getCurrentFile() 223 | { 224 | return $this->_currentFile; 225 | } 226 | 227 | /** 228 | * Set path to file which is packing. 229 | * 230 | * @param string $path 231 | * @return Mage_Archive_Tar 232 | */ 233 | protected function _setCurrentPath($path) 234 | { 235 | if ($this->_skipRoot && is_dir($path)) { 236 | $this->_currentPath = $path.(substr($path, -1)!=DS?DS:''); 237 | } else { 238 | $this->_currentPath = dirname($path) . DS; 239 | } 240 | return $this; 241 | } 242 | 243 | /** 244 | * Retrieve path to file which is packing. 245 | * 246 | * @return string 247 | */ 248 | protected function _getCurrentPath() 249 | { 250 | return $this->_currentPath; 251 | } 252 | 253 | /** 254 | * Walk through directory and add to tar file or directory. 255 | * Result is packed string on TAR format. 256 | * 257 | * @deprecated after 1.7.0.0 258 | * @param boolean $skipRoot 259 | * @return string 260 | */ 261 | protected function _packToTar($skipRoot=false) 262 | { 263 | $file = $this->_getCurrentFile(); 264 | $header = ''; 265 | $data = ''; 266 | if (!$skipRoot) { 267 | $header = $this->_composeHeader(); 268 | $data = $this->_readFile($file); 269 | $data = str_pad($data, floor(((is_dir($file) ? 0 : filesize($file)) + 512 - 1) / 512) * 512, "\0"); 270 | } 271 | $sub = ''; 272 | if (is_dir($file)) { 273 | $treeDir = scandir($file); 274 | if (empty($treeDir)) { 275 | throw new Mage_Exception('Can\'t scan dir: ' . $file); 276 | } 277 | array_shift($treeDir); /* remove './'*/ 278 | array_shift($treeDir); /* remove '../'*/ 279 | foreach ($treeDir as $item) { 280 | $sub .= $this->_setCurrentFile($file.$item)->_packToTar(false); 281 | } 282 | } 283 | $tarData = $header . $data . $sub; 284 | $tarData = str_pad($tarData, floor((strlen($tarData) - 1) / 1536) * 1536, "\0"); 285 | return $tarData; 286 | } 287 | 288 | /** 289 | * Recursively walk through file tree and create tarball 290 | * 291 | * @param boolean $skipRoot 292 | * @param boolean $finalize 293 | * @throws Mage_Exception 294 | */ 295 | protected function _createTar($skipRoot = false, $finalize = false) 296 | { 297 | if (!$skipRoot) { 298 | $this->_packAndWriteCurrentFile(); 299 | } 300 | 301 | $file = $this->_getCurrentFile(); 302 | 303 | if (is_dir($file)) { 304 | $dirFiles = scandir($file); 305 | 306 | if (false === $dirFiles) { 307 | throw new Mage_Exception('Can\'t scan dir: ' . $file); 308 | } 309 | 310 | array_shift($dirFiles); /* remove './'*/ 311 | array_shift($dirFiles); /* remove '../'*/ 312 | 313 | foreach ($dirFiles as $item) { 314 | $this->_setCurrentFile($file . $item)->_createTar(); 315 | } 316 | } 317 | 318 | if ($finalize) { 319 | $this->_getWriter()->write(str_repeat("\0", self::TAR_BLOCK_SIZE * 12)); 320 | } 321 | } 322 | 323 | /** 324 | * Write current file to tarball 325 | */ 326 | protected function _packAndWriteCurrentFile() 327 | { 328 | $archiveWriter = $this->_getWriter(); 329 | $archiveWriter->write($this->_composeHeader()); 330 | 331 | $currentFile = $this->_getCurrentFile(); 332 | 333 | $fileSize = 0; 334 | 335 | if (is_file($currentFile) && !is_link($currentFile)) { 336 | $fileReader = new Mage_Archive_Helper_File($currentFile); 337 | $fileReader->open('r'); 338 | 339 | while (!$fileReader->eof()) { 340 | $archiveWriter->write($fileReader->read()); 341 | } 342 | 343 | $fileReader->close(); 344 | 345 | $fileSize = filesize($currentFile); 346 | } 347 | 348 | $appendZerosCount = (self::TAR_BLOCK_SIZE - $fileSize % self::TAR_BLOCK_SIZE) % self::TAR_BLOCK_SIZE; 349 | $archiveWriter->write(str_repeat("\0", $appendZerosCount)); 350 | } 351 | 352 | /** 353 | * Compose header for current file in TAR format. 354 | * If length of file's name greater 100 characters, 355 | * method breaks header into two pieces. First contains 356 | * header and data with long name. Second contain only header. 357 | * 358 | * @param boolean $long 359 | * @return string 360 | */ 361 | protected function _composeHeader($long = false) 362 | { 363 | $file = $this->_getCurrentFile(); 364 | $path = $this->_getCurrentPath(); 365 | $infoFile = stat($file); 366 | $nameFile = str_replace($path, '', $file); 367 | $nameFile = str_replace('\\', '/', $nameFile); 368 | $packedHeader = ''; 369 | $longHeader = ''; 370 | if (!$long && strlen($nameFile)>100) { 371 | $longHeader = $this->_composeHeader(true); 372 | $longHeader .= str_pad($nameFile, floor((strlen($nameFile) + 512 - 1) / 512) * 512, "\0"); 373 | } 374 | $header = array(); 375 | $header['100-name'] = $long?'././@LongLink':substr($nameFile, 0, 100); 376 | $header['8-mode'] = $long ? ' ' 377 | : str_pad(substr(sprintf("%07o", $infoFile['mode']),-4), 6, '0', STR_PAD_LEFT); 378 | $header['8-uid'] = $long || $infoFile['uid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['uid']); 379 | $header['8-gid'] = $long || $infoFile['gid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['gid']); 380 | $header['12-size'] = $long ? sprintf("%011o", strlen($nameFile)) : sprintf("%011o", is_dir($file) 381 | ? 0 : filesize($file)); 382 | $header['12-mtime'] = $long?'00000000000':sprintf("%011o", $infoFile['mtime']); 383 | $header['8-check'] = sprintf('% 8s', ''); 384 | $header['1-type'] = $long ? 'L' : (is_link($file) ? 2 : (is_dir($file) ? 5 : 0)); 385 | $header['100-symlink'] = is_link($file) ? readlink($file) : ''; 386 | $header['6-magic'] = 'ustar '; 387 | $header['2-version'] = ' '; 388 | $a=function_exists('posix_getpwuid')?posix_getpwuid (fileowner($file)):array('name'=>''); 389 | $header['32-uname'] = $a['name']; 390 | $a=function_exists('posix_getgrgid')?posix_getgrgid (filegroup($file)):array('name'=>''); 391 | $header['32-gname'] = $a['name']; 392 | $header['8-devmajor'] = ''; 393 | $header['8-devminor'] = ''; 394 | $header['155-prefix'] = ''; 395 | $header['12-closer'] = ''; 396 | 397 | $packedHeader = ''; 398 | foreach ($header as $key=>$element) { 399 | $length = explode('-', $key); 400 | $packedHeader .= pack('a' . $length[0], $element); 401 | } 402 | 403 | $checksum = 0; 404 | for ($i = 0; $i < 512; $i++) { 405 | $checksum += ord(substr($packedHeader, $i, 1)); 406 | } 407 | $packedHeader = substr_replace($packedHeader, sprintf("%07o", $checksum)."\0", 148, 8); 408 | 409 | return $longHeader . $packedHeader; 410 | } 411 | 412 | /** 413 | * Read TAR string from file, and unpacked it. 414 | * Create files and directories information about discribed 415 | * in the string. 416 | * 417 | * @param string $destination path to file is unpacked 418 | * @return array list of files 419 | * @throws Mage_Exception 420 | */ 421 | protected function _unpackCurrentTar($destination) 422 | { 423 | $archiveReader = $this->_getReader(); 424 | $list = array(); 425 | 426 | while (!$archiveReader->eof()) { 427 | $header = $this->_extractFileHeader(); 428 | 429 | if (!$header) { 430 | continue; 431 | } 432 | 433 | $currentFile = $destination . $header['name']; 434 | $dirname = dirname($currentFile); 435 | 436 | if (in_array($header['type'], array("0",chr(0), ''))) { 437 | 438 | if(!file_exists($dirname)) { 439 | $mkdirResult = @mkdir($dirname, 0777, true); 440 | 441 | if (false === $mkdirResult) { 442 | throw new Mage_Exception('Failed to create directory ' . $dirname); 443 | } 444 | } 445 | 446 | $this->_extractAndWriteFile($header, $currentFile); 447 | $list[] = $currentFile; 448 | 449 | } elseif ($header['type'] == '5') { 450 | 451 | if(!file_exists($dirname)) { 452 | $mkdirResult = @mkdir($currentFile, $header['mode'], true); 453 | 454 | if (false === $mkdirResult) { 455 | throw new Mage_Exception('Failed to create directory ' . $currentFile); 456 | } 457 | } 458 | $list[] = $currentFile . DS; 459 | } elseif ($header['type'] == '2') { 460 | 461 | $symlinkResult = @symlink($header['symlink'], $currentFile); 462 | 463 | if (false === $symlinkResult) { 464 | throw new Mage_Exception('Failed to create symlink ' . $currentFile . ' to ' . $header['symlink']); 465 | } 466 | } 467 | } 468 | 469 | return $list; 470 | } 471 | 472 | /** 473 | * Get header from TAR string and unpacked it by format. 474 | * 475 | * @deprecated after 1.7.0.0 476 | * @param resource $pointer 477 | * @return string 478 | */ 479 | protected function _parseHeader(&$pointer) 480 | { 481 | $firstLine = fread($pointer, 512); 482 | 483 | if (strlen($firstLine)<512){ 484 | return false; 485 | } 486 | 487 | $fmt = self::_getFormatParseHeader(); 488 | $header = unpack ($fmt, $firstLine); 489 | 490 | $header['mode']=$header['mode']+0; 491 | $header['uid']=octdec($header['uid']); 492 | $header['gid']=octdec($header['gid']); 493 | $header['size']=octdec($header['size']); 494 | $header['mtime']=octdec($header['mtime']); 495 | $header['checksum']=octdec($header['checksum']); 496 | 497 | if ($header['type'] == "5") { 498 | $header['size'] = 0; 499 | } 500 | 501 | $checksum = 0; 502 | $firstLine = substr_replace($firstLine, ' ', 148, 8); 503 | for ($i = 0; $i < 512; $i++) { 504 | $checksum += ord(substr($firstLine, $i, 1)); 505 | } 506 | 507 | $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5)); 508 | 509 | $checksumOk = $header['checksum'] == $checksum; 510 | if (isset($header['name']) && $checksumOk) { 511 | if ($header['name'] == '././@LongLink' && $header['type'] == 'L') { 512 | $realName = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); 513 | $headerMain = $this->_parseHeader($pointer); 514 | $headerMain['name'] = $realName; 515 | return $headerMain; 516 | } else { 517 | if ($header['size']>0) { 518 | $header['data'] = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); 519 | } else { 520 | $header['data'] = ''; 521 | } 522 | return $header; 523 | } 524 | } 525 | return false; 526 | } 527 | 528 | /** 529 | * Read and decode file header information from tarball 530 | * 531 | * @return array|boolean 532 | */ 533 | protected function _extractFileHeader() 534 | { 535 | $archiveReader = $this->_getReader(); 536 | 537 | $headerBlock = $archiveReader->read(self::TAR_BLOCK_SIZE); 538 | 539 | if (strlen($headerBlock) < self::TAR_BLOCK_SIZE) { 540 | return false; 541 | } 542 | 543 | $header = unpack(self::_getFormatParseHeader(), $headerBlock); 544 | 545 | $header['mode'] = octdec($header['mode']); 546 | $header['uid'] = octdec($header['uid']); 547 | $header['gid'] = octdec($header['gid']); 548 | $header['size'] = octdec($header['size']); 549 | $header['mtime'] = octdec($header['mtime']); 550 | $header['checksum'] = octdec($header['checksum']); 551 | 552 | if ($header['type'] == "5") { 553 | $header['size'] = 0; 554 | } 555 | 556 | $checksum = 0; 557 | $headerBlock = substr_replace($headerBlock, ' ', 148, 8); 558 | 559 | for ($i = 0; $i < 512; $i++) { 560 | $checksum += ord(substr($headerBlock, $i, 1)); 561 | } 562 | 563 | $checksumOk = $header['checksum'] == $checksum; 564 | if (isset($header['name']) && $checksumOk) { 565 | 566 | if (!($header['name'] == '././@LongLink' && $header['type'] == 'L')) { 567 | $header['name'] = trim($header['name']); 568 | return $header; 569 | } 570 | 571 | $realNameBlockSize = floor(($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE) 572 | * self::TAR_BLOCK_SIZE; 573 | $realNameBlock = $archiveReader->read($realNameBlockSize); 574 | $realName = substr($realNameBlock, 0, $header['size']); 575 | 576 | $headerMain = $this->_extractFileHeader(); 577 | $headerMain['name'] = trim($realName); 578 | return $headerMain; 579 | } 580 | 581 | return false; 582 | } 583 | 584 | /** 585 | * Extract next file from tarball by its $header information and save it to $destination 586 | * 587 | * @param array $fileHeader 588 | * @param string $destination 589 | */ 590 | protected function _extractAndWriteFile($fileHeader, $destination) 591 | { 592 | $fileWriter = new Mage_Archive_Helper_File($destination); 593 | $fileWriter->open('w', $fileHeader['mode']); 594 | 595 | $archiveReader = $this->_getReader(); 596 | 597 | $filesize = $fileHeader['size']; 598 | $bytesExtracted = 0; 599 | 600 | while ($filesize > $bytesExtracted && !$archiveReader->eof()) { 601 | $block = $archiveReader->read(self::TAR_BLOCK_SIZE); 602 | $nonExtractedBytesCount = $filesize - $bytesExtracted; 603 | 604 | $data = substr($block, 0, $nonExtractedBytesCount); 605 | $fileWriter->write($data); 606 | 607 | $bytesExtracted += strlen($block); 608 | } 609 | } 610 | 611 | /** 612 | * Pack file to TAR (Tape Archiver). 613 | * 614 | * @param string $source 615 | * @param string $destination 616 | * @param boolean $skipRoot 617 | * @return string 618 | */ 619 | public function pack($source, $destination, $skipRoot = false) 620 | { 621 | $this->_setSkipRoot($skipRoot); 622 | $source = realpath($source); 623 | $tarData = $this->_setCurrentPath($source) 624 | ->_setDestinationFilePath($destination) 625 | ->_setCurrentFile($source); 626 | 627 | $this->_initWriter(); 628 | $this->_createTar($skipRoot, true); 629 | $this->_destroyWriter(); 630 | 631 | return $destination; 632 | } 633 | 634 | /** 635 | * Unpack file from TAR (Tape Archiver). 636 | * 637 | * @param string $source 638 | * @param string $destination 639 | * @return string 640 | */ 641 | public function unpack($source, $destination) 642 | { 643 | $this->_setCurrentFile($source) 644 | ->_setCurrentPath($source); 645 | 646 | $this->_initReader(); 647 | $this->_unpackCurrentTar($destination); 648 | $this->_destroyReader(); 649 | 650 | return $destination; 651 | } 652 | 653 | /** 654 | * Extract one file from TAR (Tape Archiver). 655 | * 656 | * @param string $file 657 | * @param string $source 658 | * @param string $destination 659 | * @return string 660 | */ 661 | public function extract($file, $source, $destination) 662 | { 663 | $this->_setCurrentFile($source); 664 | $this->_initReader(); 665 | 666 | $archiveReader = $this->_getReader(); 667 | $extractedFile = ''; 668 | 669 | while (!$archiveReader->eof()) { 670 | $header = $this->_extractFileHeader(); 671 | if ($header['name'] == $file) { 672 | $extractedFile = $destination . basename($header['name']); 673 | $this->_extractAndWriteFile($header, $extractedFile); 674 | break; 675 | } 676 | 677 | if ($header['type'] != 5){ 678 | $skipBytes = floor(($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE) 679 | * self::TAR_BLOCK_SIZE; 680 | $archiveReader->read($skipBytes); 681 | } 682 | } 683 | 684 | $this->_destroyReader(); 685 | return $extractedFile; 686 | } 687 | } 688 | -------------------------------------------------------------------------------- /src/magento/downloader/lib/Mage/Exception.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | class Mage_Exception extends Exception 35 | {} 36 | -------------------------------------------------------------------------------- /tests/AuthorTest.php: -------------------------------------------------------------------------------- 1 | _buildExtensionFromFixture('Pulsestorm_Better404.tar'); 15 | $xml_gui = simplexml_load_file($results['connect_xml']); 16 | $xml_package = simplexml_load_file($results['extracted'] . '/package.xml'); 17 | 18 | $names_package = array(); 19 | foreach($xml_package->authors->children() as $author) 20 | { 21 | $names_package[] = (string) $author->name; 22 | } 23 | 24 | foreach($xml_gui->authors->name->children() as $author) 25 | { 26 | $names_gui[] = (string) $author; 27 | } 28 | 29 | $this->assertEquals($names_package, $names_gui); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | _buildExtensionFromFixture('first.tar'); 17 | 18 | //if you don't like the default configuration we used, specify your own 19 | $config = $this->_getBaseExtensionConfig('Pulsestorm_Better404.tar'); 20 | $config['extension_name'] = 'Pulsestorm_Better404'; 21 | $results_second = $this->_buildExtensionFromFixture('Pulsestorm_Better404.tar', $config); 22 | 23 | //The results array has three keys 24 | $results_first['extension']; //path to the built extension 25 | $results_first['connect_xml']; //path to the built adminhtml connect XML file 26 | $results_first['extracted']; //path to a tmp folder with an already extracted tgz 27 | //that is, the build extension extracted to a folder 28 | 29 | //With the information in the three tests above, you're free to test anything 30 | //about the built extension, and with the fixtures folder being a simple drop 31 | //drop in, you're free to create clear reproducable bug reports 32 | 33 | $this->assertTrue(file_exists($results_first['extracted'] . '/package.xml')); 34 | $this->assertTrue(file_exists($results_second['extracted'] . '/package.xml')); 35 | } 36 | } -------------------------------------------------------------------------------- /tests/MagentoTarToConnectBaseTest.php: -------------------------------------------------------------------------------- 1 | _getBaseBuildArtifactsPath(); 14 | if(!file_exists($path_build_artifacts)) 15 | { 16 | mkdir($path_build_artifacts); 17 | } 18 | 19 | $path_build = $this->_getBaseBuildPath(); 20 | //if there's a build folder, move it to artifacts 21 | if(file_exists($path_build)) 22 | { 23 | rename($path_build, $path_build_artifacts . '/' . uniqid()); 24 | } 25 | 26 | //create a build folder 27 | mkdir($path_build); 28 | } 29 | 30 | public function testSetup() 31 | { 32 | $this->assertTrue(true); 33 | } 34 | 35 | protected function _getBaseExtensionConfig($fixture, $extension_name='Pulsestorm_Unittest', $extension_version='1.0.0', 36 | $author_email='testing@example.com') 37 | { 38 | $base_repo_path = $this->_getBaseRespoitoryPath(); 39 | $config = include $base_repo_path . '/' . self::EXAMPLE_CONFIG; 40 | 41 | $config['base_dir'] = $this->_getBaseBuildPath(); 42 | $config['archive_files'] = $fixture; 43 | $config['extension_name'] = $extension_name; 44 | $config['extension_version'] = $extension_version; 45 | $config['path_output'] = $this->_getBaseBuildPath(); 46 | $config['author_email'] = $author_email; 47 | $config['skip_version_compare'] = true; 48 | return $config; 49 | } 50 | 51 | protected function _buildExtensionFromFixture($fixture,$config=false) 52 | { 53 | $path_fixture = $this->_getFixturePath($fixture); 54 | $this->_copyFixtureToBuild($path_fixture); 55 | 56 | if(!$config) 57 | { 58 | $config = $this->_getBaseExtensionConfig($fixture); 59 | } 60 | 61 | 62 | Pulsestorm_MagentoTarToConnect::buildExtensionFromConfig($config); 63 | 64 | $path_extension = $this->_getBaseBuildPath() . '/' . 65 | $config['extension_name'] . '-' . 66 | $config['extension_version'] . '.tgz'; 67 | 68 | $path_connectxml = $this->_getBaseBuildPath() . '/var/connect/' . 69 | $config['extension_name'] . '.xml'; 70 | 71 | $untared = $this->_untarIntoTemp($path_extension); 72 | 73 | $results = array(); 74 | $results['extension'] = $path_extension; 75 | $results['extracted'] = $untared; 76 | $results['connect_xml'] = $path_connectxml; 77 | return $results; 78 | 79 | } 80 | 81 | protected function _getBaseRespoitoryPath() 82 | { 83 | return realpath((__DIR__ . '/../')); 84 | } 85 | 86 | protected function _untarIntoTemp($path) 87 | { 88 | $original_dir = getcwd(); 89 | 90 | //create a temp file, turn it into a directory 91 | $dir = tempnam('/tmp','mt2c');; 92 | unlink($dir); 93 | mkdir($dir); 94 | chdir($dir); 95 | 96 | $tar = new Archive_Tar($path); 97 | $tar->extract('.'); 98 | chdir($original_dir); 99 | return $dir; 100 | } 101 | 102 | protected function _getFixturePath($name) 103 | { 104 | return realpath(__DIR__) . '/fixtures/' . $name; 105 | } 106 | 107 | protected function _copyFixtureToBuild($path) 108 | { 109 | $path_new = dirname($path) . '/build/' . basename($path); 110 | copy($path, $path_new); 111 | return $path_new; 112 | } 113 | 114 | protected function _getBaseBuildArtifactsPath() 115 | { 116 | return $this->_getBaseBuildPath() . self::PATH_BUILD_ARTIFACTS_SUFFIX; 117 | } 118 | protected function _getBaseBuildPath() 119 | { 120 | return $this->_getBaseRespoitoryPath() . self::PATH_BUILD; 121 | } 122 | } -------------------------------------------------------------------------------- /tests/WhoTestsTheTestsTest.php: -------------------------------------------------------------------------------- 1 | _getFixturePath('first.txt'); 7 | $this->assertTrue(file_exists($fixture)); 8 | } 9 | 10 | public function testGetTarFixture() 11 | { 12 | $fixture = $this->_getFixturePath('first.tar'); 13 | $path = $this->_untarIntoTemp($fixture); 14 | 15 | // echo "\n",$path . '/first.txt',"\n"; 16 | $this->assertTrue(file_exists($path . '/first.txt')); 17 | } 18 | 19 | public function testCanReadExampleConfig() 20 | { 21 | return $this->assertTrue( 22 | file_exists($this->_getBaseRespoitoryPath() . '/' . self::EXAMPLE_CONFIG) 23 | ); 24 | } 25 | 26 | public function testCopyFixtureToBuild() 27 | { 28 | $fixture = $this->_getFixturePath('first.tar'); 29 | $this->_copyFixtureToBuild($fixture); 30 | 31 | $this->assertTrue(file_exists( 32 | $this->_getBaseBuildPath() . '/first.tar' 33 | )); 34 | } 35 | 36 | public function testRunTarToConnect() 37 | { 38 | $results = $this->_buildExtensionFromFixture('first.tar'); 39 | $this->assertTrue(file_exists($results['extension'])); 40 | } 41 | 42 | public function testRunTarToConnectNamedNonDefault() 43 | { 44 | $fixture = 'first.tar'; 45 | $name = 'Pulsestorm_Unittestdifferent'; 46 | $config = $this->_getBaseExtensionConfig($fixture); 47 | $config['extension_name'] = $name; 48 | 49 | $results = $this->_buildExtensionFromFixture('first.tar', $config); 50 | $this->assertTrue((boolean)strpos($results['extension'], $name)); 51 | $this->assertTrue(file_exists($results['extension'])); 52 | } 53 | 54 | public function testRunTarToConnectAndTmpExtraction() 55 | { 56 | $results = $this->_buildExtensionFromFixture('first.tar'); 57 | 58 | $this->assertTrue(file_exists($results['extracted'] . '/package.xml')); 59 | $this->assertTrue(file_exists($results['extracted'] . '/first.txt')); 60 | } 61 | 62 | public function testBetter404() 63 | { 64 | $results = $this->_buildExtensionFromFixture('Pulsestorm_Better404.tar'); 65 | $this->assertTrue(file_exists($results['extension'])); 66 | $this->assertTrue(file_exists($results['extension'])); 67 | $this->assertTrue(file_exists($results['connect_xml'])); 68 | } 69 | } -------------------------------------------------------------------------------- /tests/fixtures/Pulsestorm_Better404.tar: -------------------------------------------------------------------------------- 1 | app/code/community/Pulsestorm/Better404/000755 000765 000000 00000000000 12232106127 021560 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/Block/000755 000765 000000 00000000000 12255012032 022606 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/controllers/000755 000765 000000 00000000000 12255004711 024127 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/etc/000755 000765 000000 00000000000 12255004723 022337 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/Helper/000755 000765 000000 00000000000 12232106127 022777 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/layouts/000755 000765 000000 00000000000 12232106127 023260 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/Model/000755 000765 000000 00000000000 12255012121 022613 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/sql/000755 000765 000000 00000000000 12232106127 022357 5ustar00alanstormwheel000000 000000 app/code/community/Pulsestorm/Better404/Model/Lint.php000644 000765 000024 00000013206 12255012121 024241 0ustar00alanstormstaff000000 000000 _initRouters(); 18 | $this->_initClaimed(); 19 | } 20 | 21 | protected function _getConfigNodesWithRouters() 22 | { 23 | return array('frontend', 'admin'); 24 | } 25 | 26 | protected function _initClaimed() 27 | { 28 | $module = $this->getUrlModuleName(); 29 | $this->_claimed = array(); 30 | 31 | $nodes = $this->_getConfigNodesWithRouters(); 32 | foreach($nodes as $node) 33 | { 34 | //front name 35 | foreach($this->_routers[$node] as $router) 36 | { 37 | $args = $router->args; 38 | if(!$args) 39 | { 40 | continue; 41 | } 42 | if((string)$args->frontName == $module) 43 | { 44 | $this->_claimed[] = $router; 45 | } 46 | } 47 | } 48 | return $this->_claimed; 49 | } 50 | 51 | public function getClaimed() 52 | { 53 | return $this->_claimed; 54 | } 55 | 56 | protected function _initRouters() 57 | { 58 | $config = Mage::getConfig(); 59 | $nodes = $this->_getConfigNodesWithRouters(); 60 | foreach($nodes as $node) 61 | { 62 | $frontend = $config->getNode($node); 63 | foreach($frontend->routers->children() as $router) 64 | { 65 | $this->_routers[$node][$router->getName()] = $router; 66 | } 67 | } 68 | } 69 | 70 | public function getUrlOriginalPath() 71 | { 72 | if(!$this->_originalPath) 73 | { 74 | $this->_originalPath = Mage::app()->getRequest()->getOriginalPathInfo(); 75 | } 76 | return $this->_originalPath; 77 | } 78 | 79 | public function getUrlModuleName() 80 | { 81 | if(!$this->_inferedModule) 82 | { 83 | $path = $this->getUrlOriginalPath(); 84 | $path = trim($path, '/'); 85 | $path = explode('/', $path); 86 | $this->_inferedModule = array_shift($path); 87 | } 88 | return $this->_inferedModule; 89 | } 90 | 91 | public function getUrlControllerName() 92 | { 93 | if(!$this->_inferedController) 94 | { 95 | $path = $this->getUrlOriginalPath(); 96 | $path = trim($path, '/'); 97 | $path = explode('/', $path); 98 | $this->_inferedController = array_key_exists(1,$path) ? $path[1] : 'index'; 99 | } 100 | return $this->_inferedController; 101 | } 102 | 103 | public function getUrlActionName() 104 | { 105 | if(!$this->_inferedAction) 106 | { 107 | $path = $this->getUrlOriginalPath(); 108 | $path = trim($path, '/'); 109 | $path = explode('/', $path); 110 | $this->_inferedAction = array_key_exists(2,$path) ? $path[2] : 'index'; 111 | } 112 | return $this->_inferedAction; 113 | } 114 | 115 | public function getControllerInformation($module_name) 116 | { 117 | if(!$this->_controllerInformation) 118 | { 119 | $router_object = new Mage_Core_Controller_Varien_Router_Standard; 120 | $this->_controllerInformation = array(); 121 | $this->_controllerInformation['class_file'] = $router_object->getControllerFileName($module_name, $this->getUrlControllerName()); 122 | $this->_controllerInformation['class_name'] = $router_object->getControllerClassName($module_name, $this->getUrlControllerName()); 123 | } 124 | return $this->_controllerInformation; 125 | } 126 | 127 | public function getActionMethodExists($controller_name,$action) 128 | { 129 | $info = $this->getControllerInformation($this->getUrlModuleName()); 130 | require_once $info['class_file']; 131 | $r = new ReflectionClass($controller_name); 132 | return $r->hasMethod($action . 'Action'); 133 | } 134 | 135 | public function getClaimedByName() 136 | { 137 | $claimed = $this->getClaimed(); 138 | return (string) $claimed[0]->args->module; 139 | } 140 | 141 | public function getControllerClassExists($module_name) 142 | { 143 | $info = $this->getControllerInformation($module_name); 144 | $tokens = token_get_all(file_get_contents($info['class_file'])); 145 | 146 | $state = self::STATE_NEUTRAL; 147 | foreach($tokens as $token) 148 | { 149 | if(!is_array($token)) //skip single character tokens 150 | { 151 | continue; 152 | } 153 | $constant_value = $token[0]; 154 | $real_value = $token[1]; 155 | $token_name = token_name($constant_value); 156 | if($token_name == 'T_CLASS') 157 | { 158 | $state = self::STATE_CLASS_DEF; 159 | } 160 | if($state != self::STATE_CLASS_DEF) 161 | { 162 | continue; 163 | } 164 | //first T_STRING after 165 | if($token_name == 'T_STRING') 166 | { 167 | return $info['class_name'] == $real_value; 168 | } 169 | } 170 | return false; 171 | } 172 | 173 | public function getExtraModules() 174 | { 175 | 176 | $claimed = $this->getClaimed(); 177 | $router = array_shift($claimed); 178 | $args = $router->args; 179 | if(!$args) 180 | { 181 | return array(); 182 | } 183 | 184 | $modules = $args->modules; 185 | if(!$modules) 186 | { 187 | return array(); 188 | } 189 | 190 | $return = array(); 191 | foreach($modules->children() as $module) 192 | { 193 | $return[] = (string) $module; 194 | } 195 | return $return; 196 | } 197 | }app/code/community/Pulsestorm/Better404/Model/Observer.php000644 000765 000024 00000001640 12254733752 025144 0ustar00alanstormstaff000000 000000 _is404()) 203 | { 204 | return; 205 | } 206 | 207 | $layout = $observer->getLayout(); 208 | $block = $layout->getBlock('cms.wrapper'); 209 | if(!$block) 210 | { 211 | return; 212 | } 213 | $layout->unsetBlock('cms.wrapper'); 214 | $block = $layout->createBlock('pulsestorm_better404/404','cms.wrapper'); 215 | $block->setBlock('cms.wrapper', $block); 216 | } 217 | 218 | protected function _is404() 219 | { 220 | $headers = Mage::app()->getResponse()->getHeaders(); 221 | foreach($headers as $header) 222 | { 223 | if(strToLower($header['name']) != 'status') 224 | { 225 | continue; 226 | } 227 | 228 | if(strpos($header['value'],'404') !== false) 229 | { 230 | return true; 231 | } 232 | } 233 | return false; 234 | } 235 | }app/code/community/Pulsestorm/Better404/etc/config.xml000644 000765 000024 00000001655 12255004722 024341 0ustar00alanstormstaff000000 000000 236 | 237 | 238 | 239 | 0.1.0 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | Pulsestorm_Better404_Model 248 | 249 | 250 | 251 | 252 | 253 | Pulsestorm_Better404_Block 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | singleton 262 | pulsestorm_better404/observer 263 | addExtraBlocks 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | app/code/community/Pulsestorm/Better404/Block/404.php000644 000765 000024 00000004221 12255012032 023632 0ustar00alanstormstaff000000 000000 _initLint(); 278 | $this->setTemplate('pulsestorm_better404/404.phtml'); 279 | } 280 | 281 | public function getClaimedByName() 282 | { 283 | return $this->_lint->getClaimedByName(); 284 | } 285 | 286 | public function getUrlOriginalPath() 287 | { 288 | return $this->_lint->getUrlOriginalPath(); 289 | } 290 | 291 | public function getClaimed() 292 | { 293 | return $this->_lint->getClaimed(); 294 | } 295 | 296 | public function getUrlModuleName() 297 | { 298 | return $this->_lint->getUrlModuleName(); 299 | } 300 | 301 | public function getUrlControllerName() 302 | { 303 | return $this->_lint->getUrlControllerName(); 304 | } 305 | 306 | public function getUrlActionName() 307 | { 308 | return $this->_lint->getUrlActionName(); 309 | } 310 | 311 | public function getControllerFilePath($module_name) 312 | { 313 | $info = $this->_lint->getControllerInformation($module_name); 314 | return $info['class_file']; 315 | } 316 | 317 | /** 318 | * Crude state machine to check if the class exists 319 | */ 320 | public function getControllerClassExists($module_name) 321 | { 322 | return $this->_lint->getControllerClassExists($module_name); 323 | } 324 | 325 | public function getControllerInformation($module_name) 326 | { 327 | return $this->_lint->getControllerInformation($module_name); 328 | } 329 | 330 | public function getControllerClassName($module_name) 331 | { 332 | $info = $this->_lint->getControllerInformation($module_name); 333 | return $info['class_name']; 334 | } 335 | 336 | public function getControllerFileExists($module_name) 337 | { 338 | $info = $this->_lint->getControllerInformation($module_name); 339 | return file_exists($info['class_file']); 340 | } 341 | 342 | public function getActionMethodExists($controller_name, $action) 343 | { 344 | return $this->_lint->getActionMethodExists($controller_name, $action); 345 | } 346 | 347 | public function getExtraModules() 348 | { 349 | return $this->_lint->getExtraModules(); 350 | } 351 | protected function _initLint() 352 | { 353 | $this->_lint = Mage::getModel('pulsestorm_better404/lint'); 354 | return $this->_lint; 355 | } 356 | }app/etc/modules/Pulsestorm_Better404.xml000644 000765 000024 00000000252 12232106127 021653 0ustar00alanstormstaff000000 000000 truecommunityapp/design/frontend/base/default/template/pulsestorm_better404/000755 000765 000024 00000000000 12255023544 026256 5ustar00alanstormstaff000000 000000 app/design/frontend/base/default/template/pulsestorm_better404/404.phtml000644 000765 000024 00000007461 12255023544 027643 0ustar00alanstormstaff000000 000000

Page not Found

357 |

358 | We couldn't find a page at the URL you specified. The information below will help 359 | a Magento programmer figure out why. 360 |

361 | 362 |

Original Path

363 |

364 | Original Path Information getUrlOriginalPath();?>. 365 |

366 | 367 |

Module/Front Name

368 |

369 | Module/Front Name: getUrlModuleName(); ?>. 370 |

371 | 372 | getClaimed(); ?> 373 | 374 |

375 | No modules claim [getUrlModuleName(); ?>] as a 376 | <frontName/>`. 377 |

378 | 379 | 380 | 381 | 1){ ?> 382 |

383 | getUrlModuleName(); ?> is claimed by multiple modules, which only leads to tears. 384 |

385 | 386 | 387 | 388 | 389 |

390 | The Module/Front Name [getUrlModuleName(); ?>] is claimed by 391 | the Magento module getClaimedByName(); ?>. 392 |

393 | 394 | 395 | getExtraModules()){ ?> 396 | If Magento can't find a controller in getClaimedByName(); ?>, 397 | the system will used the following controller class name prefixes to search for a controller. 398 | 399 | <modules/> node in the <routers/> node) 400 | 405 | 406 | 407 | getClaimedByName();?> 408 |

Controller Name

409 |

410 | Controller Name: getUrlControllerName(); ?> 411 |

412 | 413 | getControllerFileExists($claimed_by)){ ?> 414 |

415 | The controller name getUrlControllerName(); ?> matches the following controller file, but this file does not exist. 416 |

417 |

418 | 419 | 420 | 421 | getControllerClassExists($claimed_by)){ ?> 422 |

423 | We found the following controller file, but this file did not contain 424 | a definition for the controller class getControllerClassName($claimed_by)?>. 425 |

426 |

427 | 428 | 429 | 430 | 431 | getControllerInformation($claimed_by); ?> 432 |

433 | The controller name getUrlControllerName(); ?> uses the 434 | controller class , defined in the following file. 435 |

436 |

437 | 438 | 439 |

Action Name

440 |

441 | Action Name: getUrlActionName(); ?> 442 |

443 | 444 | getActionMethodExists($class_name, $this->getUrlActionName())){ ?> 445 |

446 | The action method getUrlActionName(); ?>Action is not present in the controller file. 447 |

448 | 449 | -------------------------------------------------------------------------------- /tests/fixtures/first.tar: -------------------------------------------------------------------------------- 1 | first.txt000644 000765 000024 00000000035 12406420675 014115 0ustar00alanstormstaff000000 000000 This is a brain dead fixture. -------------------------------------------------------------------------------- /tests/fixtures/first.txt: -------------------------------------------------------------------------------- 1 | This is a brain dead fixture. --------------------------------------------------------------------------------