├── README.md ├── composer.json ├── modman.bat ├── modman.php └── modman.sh /README.md: -------------------------------------------------------------------------------- 1 | modman-php 2 | ========== 3 | 4 | PHP implementation for modman, to use it on every operating system with PHP support (also Windows). 5 | 6 | Currently implemented: 7 | - init (creates .modman directory) 8 | - link (creates symlinks) 9 | - deploy (update symlinks) 10 | - deploy-all (updates all modules) 11 | - repair (repairs all symlinks) 12 | - clean (removes all dead symlinks) 13 | - create (creates a modman file for an existing module) 14 | - clone (clones a git repository) 15 | 16 | 17 | --force is available for link, deploy, deploy-all and clone, if not set script aborts when conflicts are found 18 | --copy is available for deploy and deploy-all, will copy the files and folders instead of symlinking them 19 | 20 | Usage examples: 21 | 22 | php modman.php init 23 | php modman.php link ..\B2BProfessional 24 | php modman.php deploy B2BProfessional 25 | 26 | Or directly clone which does also init and deploy: 27 | 28 | php modman.php clone https://github.com/sitewards/B2BProfessional 29 | 30 | Currently supported in modman files: 31 | - symlinks (incl. wildcards) 32 | - @import 33 | - @shell 34 | 35 | For Windows users there's also a batch file available, so instead of typing php and directory to modman.php you could just use modman.bat everywhere if you add it to your %PATH%-variable: 36 | 37 | modman link c:\B2BProfessional 38 | 39 | 40 | Started at Magento Hackathon in Zürich 2013-03-09 41 | 42 | 43 | Influenced by the original modman at https://github.com/colinmollenhour/modman/ 44 | 45 | 46 | init 47 | ==== 48 | 49 | Creates the .modman directory, which is used for all other operations. 50 | 51 | cd $PROJECT 52 | modman init 53 | 54 | or 55 | 56 | cd $PROJECT 57 | modman init 58 | 59 | If you don't specify a basedir (aka magento directory) the current working directory will be used. 60 | The basedir functionality is supposed to be used to move the .modman directory outside of the magento main directory. 61 | - That first of all helps to structure your projects better 62 | - But is also a security feature as modman might link sensitive data like docs into your magento magento main directory. 63 | 64 | link 65 | ==== 66 | 67 | Creates symlink from a modman file 68 | 69 | cd $PROJOECT 70 | modman link /path/to/myMageModule 71 | 72 | Optional parameter --force to automatically remove conflicted files 73 | 74 | deploy 75 | ====== 76 | 77 | Updates the symlinks of a linked module 78 | 79 | cd $PROJECT 80 | modman deploy myMageModule 81 | 82 | Optional parameter --force to automatically remove conflicted files 83 | 84 | Optional parameter --copy to copy files instead of creating symlinks 85 | 86 | deploy-all 87 | ========== 88 | 89 | Updates all symlinks of linked modules 90 | 91 | cd $PROJECT 92 | modman deploy-all 93 | 94 | Optional parameter --force to automatically remove conflicted files 95 | 96 | Optional parameter --copy to copy files instead of creating symlinks 97 | 98 | repair 99 | ====== 100 | 101 | Repairs all symlinks of all linked modules 102 | 103 | cd $PROJECT 104 | modman repair 105 | 106 | clean 107 | ===== 108 | 109 | Scans directory for dead symlinks and deletes them. Useful if a module was deleted and not removed in the project 110 | 111 | cd $PROJECT 112 | modman clean 113 | 114 | remove 115 | ====== 116 | 117 | Removes links of a project 118 | 119 | cd $PROJECT 120 | modman remove myMageModule 121 | 122 | create 123 | ====== 124 | 125 | Scans through the current directory and creates a modman file containing all files and folders 126 | 127 | cd $MODULE 128 | modman create 129 | 130 | 131 | Optional parameter --force to automatically overwrite existing modman-file. 132 | Optional parameter --include-hidden to list hidden files and directories in modman-file. 133 | Optional parameter --include to include a template file at the end of the new modman-file. 134 | If the current directory is recognized as a magento module, only the path to the module's code directory is added to the modman file. 135 | 136 | clone 137 | ===== 138 | 139 | Clones a git repository 140 | 141 | cd $PROJECT 142 | modman clone https://git.url 143 | 144 | Optional parameter --force to overwrite existing folder. 145 | Optional parameter --create-modman to create a new modman file in the cloned folder if there is no modman file yet. 146 | 147 | Feature ideas 148 | ============= 149 | 150 | - Check if "allow symlinks" is activated in Magento when linking template files 151 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sitewards/modman-php", 3 | "license": "MIT", 4 | "description": "PHP implementation for modman, to use it on every operating system with PHP support (also Windows).", 5 | "homepage": "https://github.com/sitewards/modman-php", 6 | "require": { 7 | "php": ">=5.3.0" 8 | }, 9 | "bin": ["modman.php", "modman.sh", "modman.bat"] 10 | } 11 | -------------------------------------------------------------------------------- /modman.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | php %~dp0modman.php %* -------------------------------------------------------------------------------- /modman.php: -------------------------------------------------------------------------------- 1 | printHelp(); 24 | exit; 25 | } 26 | 27 | $bForce = in_array('--force', $aParameters, true); 28 | $bCopy = in_array('--copy', $aParameters, true); 29 | 30 | switch ($aParameters[1]) { 31 | case 'link': 32 | if (!isset($aParameters[2])) { 33 | throw new Exception('please specify target directory'); 34 | } 35 | $sLinkPath = realpath($aParameters[2]); 36 | if (!$sLinkPath){ 37 | throw new Exception('Link path is invalid!'); 38 | } 39 | $oLink = new Modman_Command_Link($sLinkPath); 40 | $oLink->createSymlinks($bForce); 41 | echo 'Successfully create symlink new module \'' . basename($sLinkPath) . '\'' . PHP_EOL; 42 | break; 43 | case 'init': 44 | $sCwd = getcwd(); 45 | $sBaseDir = null; 46 | if (isset($aParameters[2])) { 47 | $sBaseDir = $aParameters[2]; 48 | } 49 | $sInitPath = realpath($sCwd); 50 | $oInit = new Modman_Command_Init(); 51 | $oInit->doInit($sInitPath, $sBaseDir); 52 | break; 53 | case 'deploy': 54 | if (!isset($aParameters[2])) { 55 | throw new Exception('please specify module name'); 56 | } 57 | $oDeploy = new Modman_Command_Deploy($aParameters[2]); 58 | $oDeploy->doDeploy($bForce, $bCopy); 59 | echo $aParameters[2] . ' has been deployed under ' . getcwd() . PHP_EOL; 60 | break; 61 | case 'repair': 62 | $bForce = true; 63 | case 'deploy-all': 64 | $oDeployAll = new Modman_Command_All('Modman_Command_Deploy'); 65 | $oDeployAll->doDeploy($bForce, $bCopy); 66 | break; 67 | case 'clean': 68 | $oClean = new Modman_Command_Clean(); 69 | $oClean->doClean(); 70 | break; 71 | case 'remove': 72 | if (!isset($aParameters[2])) { 73 | throw new Exception('please specify module name'); 74 | } 75 | $oRemove = new Modman_Command_Remove($aParameters[2]); 76 | $oRemove->doRemove($bForce); 77 | break; 78 | case 'create': 79 | $oCreate = new Modman_Command_Create(); 80 | $iIncludeOffset = array_search('--include', $aParameters); 81 | $bListHidden = array_search('--include-hidden', $aParameters); 82 | if ($iIncludeOffset){ 83 | $oCreate->setIncludeFile($aParameters[$iIncludeOffset + 1]); 84 | } 85 | $oCreate->doCreate($bForce, $bListHidden); 86 | break; 87 | case 'clone': 88 | if (!isset($aParameters[2])){ 89 | throw new Exception('Please specify git repository URL'); 90 | } 91 | $bCreateModman = array_search('--create-modman', $aParameters); 92 | $oClone = new Modman_Command_Clone($aParameters[2], new Modman_Command_Create()); 93 | $oClone->doClone($bForce, $bCreateModman); 94 | break; 95 | default: 96 | throw new Exception('command does not exist'); 97 | } 98 | } catch (Exception $oException) { 99 | // set small timeout, no big delays for a funny feature 100 | $rCtx = stream_context_create(array('http'=> 101 | array( 102 | 'timeout' => 1, 103 | ) 104 | )); 105 | $sMessage = $oException->getMessage(); 106 | $sCowsay = @file_get_contents('http://cowsay.morecode.org/say?message=' . urlencode($sMessage) . '&format=text', false, $rCtx); 107 | if ($sCowsay) { 108 | echo $sCowsay; 109 | } else { 110 | echo '-----' . PHP_EOL; 111 | echo 'An error occured:' . PHP_EOL; 112 | echo $sMessage . PHP_EOL; 113 | echo '-----'; 114 | } 115 | echo PHP_EOL . PHP_EOL; 116 | $this->printHelp(); 117 | } 118 | } 119 | 120 | /** 121 | * prints the help 122 | */ 123 | public function printHelp(){ 124 | $sHelp = <<< EOH 125 | PHP-based module manager, originally implemented as bash-script 126 | (for original implementation see https://github.com/colinmollenhour/modman) 127 | 128 | Following general commands are currently supported: 129 | - link (optional --force) 130 | - init (optional ) 131 | - repair 132 | - deploy (optional --force) 133 | - deploy-all (optional --force) 134 | - clean 135 | - create (optional --force, --include and --include-hidden) 136 | - clone (optional --force, --create-modman) 137 | 138 | Currently supported in modman-files: 139 | - symlinks (with wildcards) 140 | - @import and @shell command 141 | EOH; 142 | 143 | echo $sHelp . PHP_EOL; 144 | } 145 | 146 | } 147 | 148 | class Modman_Command_All { 149 | private $sClassName; 150 | 151 | /** 152 | * constructor for a command 153 | * 154 | * @param string $sClassName 155 | */ 156 | public function __construct($sClassName) { 157 | $this->sClassName = $sClassName; 158 | } 159 | 160 | /** 161 | * returns all linked modules 162 | * 163 | * @return array 164 | * @throws Exception if modman directory does not exist 165 | */ 166 | private function getAllModules() { 167 | if (!file_exists(Modman_Command_Init::MODMAN_DIRECTORY_NAME)) { 168 | throw new Exception ('No modman directory found. You need to call "modman init" to create it.' . PHP_EOL 169 | . 'Please consider the documentation below.', Modman::ERR_NOT_INITIALIZED); 170 | } 171 | $aDirEntries = scandir(Modman_Command_Init::MODMAN_DIRECTORY_NAME); 172 | unset($aDirEntries[array_search('.', $aDirEntries)]); 173 | unset($aDirEntries[array_search('..', $aDirEntries)]); 174 | $iBaseDir = array_search(Modman_Command_Init::getBaseDirFile(), $aDirEntries); 175 | if ($iBaseDir !== false) { 176 | unset($aDirEntries[$iBaseDir]); 177 | } 178 | return $aDirEntries; 179 | } 180 | 181 | /** 182 | * calls a method on all modules 183 | * 184 | * @param string $sMethodName the method name to call 185 | * @param array $aArguments the parameters to give that method 186 | */ 187 | public function __call($sMethodName, $aArguments) { 188 | foreach ($this->getAllModules() as $sModuleName) { 189 | $oClass = new $this->sClassName($sModuleName); 190 | call_user_func_array(array($oClass, $sMethodName), $aArguments); 191 | } 192 | } 193 | } 194 | 195 | class Modman_Command_Init { 196 | 197 | // directory name 198 | const MODMAN_DIRECTORY_NAME = '.modman'; 199 | const MODMAN_BASEDIR_FILE = '.basedir'; 200 | 201 | public static function getBaseDirFile() { 202 | return self::MODMAN_DIRECTORY_NAME . DIRECTORY_SEPARATOR . self::MODMAN_BASEDIR_FILE; 203 | } 204 | 205 | /** 206 | * Creates directory ".modman" if it doesn't exist 207 | * 208 | * @param string 209 | * @param string 210 | */ 211 | public function doInit($sDirectory, $sBaseDir = null) { 212 | $sModmanDirectory = $sDirectory . DIRECTORY_SEPARATOR . self::MODMAN_DIRECTORY_NAME; 213 | if (!is_dir($sModmanDirectory)){ 214 | mkdir($sModmanDirectory); 215 | } 216 | if (!is_null($sBaseDir)) { 217 | file_put_contents(self::getBaseDirFile(), $sBaseDir); 218 | } 219 | } 220 | } 221 | 222 | class Modman_Command_Link { 223 | private $sTarget; 224 | 225 | /** 226 | * constructor 227 | * 228 | * @param string $sTarget target to link 229 | */ 230 | public function __construct($sTarget) { 231 | if (empty($sTarget)) { 232 | throw new Exception('no source defined'); 233 | } 234 | $this->sTarget = $sTarget; 235 | } 236 | 237 | /** 238 | * creates the symlinks 239 | * 240 | * @param bool $bForce if true errors will be ignored 241 | * @throws Exception if module is already linked 242 | */ 243 | public function createSymlinks($bForce = false) { 244 | $sModuleName = basename($this->sTarget); 245 | $sModuleSymlink = Modman_Command_Init::MODMAN_DIRECTORY_NAME . DIRECTORY_SEPARATOR . $sModuleName; 246 | if (is_link($sModuleSymlink)) { 247 | throw new Exception($sModuleName . ' is already linked'); 248 | } 249 | symlink($this->sTarget, $sModuleSymlink); 250 | 251 | $oDeploy = new Modman_Command_Deploy($sModuleName); 252 | $oDeploy->doDeploy($bForce); 253 | } 254 | } 255 | 256 | class Modman_Command_Link_Line { 257 | private $sTarget, $sSymlink; 258 | 259 | /** 260 | * constructor 261 | * 262 | * @param array $aDirectories - key 0 = source; key 1 = target 263 | */ 264 | public function __construct($aDirectories) { 265 | $this->sTarget = $aDirectories[0]; 266 | if (empty($aDirectories[1])) { 267 | $this->sSymlink = $this->sTarget; 268 | } else { 269 | $this->sSymlink = $aDirectories[1]; 270 | } 271 | } 272 | 273 | /** 274 | * returns the target 275 | * 276 | * @return string 277 | */ 278 | public function getTarget() { 279 | return $this->rtrimDS($this->sTarget); 280 | } 281 | 282 | /** 283 | * returns the symlink 284 | * 285 | * @return string 286 | */ 287 | public function getSymlink() { 288 | $sBaseDir = getcwd(); 289 | $sBaseDirFile = Modman_Command_Init::getBaseDirFile(); 290 | if (file_exists($sBaseDirFile)) { 291 | $sBaseDir = rtrim(array_shift(file($sBaseDirFile, FILE_IGNORE_NEW_LINES))); 292 | } 293 | return $sBaseDir . DIRECTORY_SEPARATOR . $this->rtrimDS($this->sSymlink); 294 | } 295 | 296 | /** 297 | * fixes trailing slashes on *nix systems 298 | * 299 | * @param $sDir 300 | * @return string 301 | */ 302 | private function rtrimDS($sDir) { 303 | return rtrim($sDir, DIRECTORY_SEPARATOR); 304 | } 305 | 306 | /** 307 | * returns the symlink base dir 308 | * 309 | * @return string 310 | */ 311 | public function getSymlinkBaseDir() { 312 | return dirname($this->getSymlink()); 313 | } 314 | } 315 | 316 | class Modman_Reader { 317 | 318 | const MODMAN_FILE_NAME = 'modman'; 319 | 320 | private $aFileContent = array(); 321 | private $aObjects = array(); 322 | private $sClassName; 323 | private $aShells = array(); 324 | private $sModuleDirectory; 325 | 326 | /** 327 | * constructor 328 | * 329 | * @param string $sDirectory - where to read 330 | */ 331 | public function __construct($sDirectory) { 332 | $this->sModuleDirectory = $sDirectory; 333 | $this->aFileContent = file($sDirectory . DIRECTORY_SEPARATOR . self::MODMAN_FILE_NAME); 334 | $sFileName = $sDirectory . DIRECTORY_SEPARATOR . self::MODMAN_FILE_NAME; 335 | if (!file_exists($sFileName)) { 336 | throw new Exception ('The directory you would like to link has no modman file.' . PHP_EOL 337 | . 'Cannot link to this directory.', Modman::ERR_NO_MODMAN_FILE); 338 | } 339 | $this->aFileContent = file($sFileName); 340 | } 341 | 342 | /** 343 | * returns the params of a line as array 344 | * 345 | * @param string $sRow line separated by spaces 346 | * @return array 347 | */ 348 | private function getParamsArray($sRow){ 349 | return explode(' ', preg_replace('/\s+/', ' ', $sRow)); 350 | } 351 | 352 | /** 353 | * returns an array of objects per row 354 | * 355 | * @param string $sClassName class which should be used to initialize each row 356 | * @return array 357 | */ 358 | public function getObjectsPerRow($sClassName) { 359 | $this->sClassName = $sClassName; 360 | foreach ($this->aFileContent as $sLine) { 361 | if (substr($sLine, 0, 1) == '#') { 362 | // skip comments 363 | continue; 364 | } 365 | $aParameters = $this->getParamsArray($sLine); 366 | if (substr($sLine, 0, 7) == '@import') { 367 | $this->doImport($aParameters); 368 | continue; 369 | } elseif (substr($sLine, 0, 6) == '@shell') { 370 | unset($aParameters[0]); 371 | $this->aShells[] = implode(' ', $aParameters); 372 | continue; 373 | } elseif (substr($sLine, 0, 1) == '@'){ 374 | echo 'Do not understand: ' . $sLine . PHP_EOL; 375 | continue; 376 | } 377 | if (strstr($sLine, '*')) { 378 | foreach (glob($this->sModuleDirectory . DIRECTORY_SEPARATOR . $aParameters[0]) as $sFilename) { 379 | $sRelativeFilename = substr($sFilename, strlen($this->sModuleDirectory . DIRECTORY_SEPARATOR)); 380 | $sRelativeTarget = str_replace(str_replace('*', '', $aParameters[0]), $aParameters[1], $sRelativeFilename); 381 | $this->aObjects[] = new $sClassName(array($sRelativeFilename, $sRelativeTarget)); 382 | } 383 | } else { 384 | $this->aObjects[] = new $sClassName($aParameters); 385 | } 386 | } 387 | return $this->aObjects; 388 | } 389 | 390 | /** 391 | * imports another file 392 | * 393 | * @param array $aCommandParams params submitted to import 394 | * @throws Exception if the path could not be parsed 395 | */ 396 | private function doImport($aCommandParams){ 397 | $sDirectoryName = realpath($this->sModuleDirectory . DIRECTORY_SEPARATOR . $aCommandParams[1]); 398 | if (!$sDirectoryName){ 399 | throw new Exception('The import path could not be parsed!'); 400 | } 401 | 402 | $oModmanReader = new Modman_Reader($sDirectoryName); 403 | $aObjects = $oModmanReader->getObjectsPerRow($this->sClassName); 404 | 405 | // Hack to make paths relative to $this->sModuleDirectory 406 | // Fixes the case when the paths are relative to nested folder, eg "../../file" 407 | $sBaseDir = getcwd(); 408 | $aObjectsFixed = array(); 409 | foreach ($aObjects as $iLine => $oLine) { 410 | $sTarget = $oLine->getTarget(); 411 | $sTarget = realpath($sDirectoryName . DIRECTORY_SEPARATOR . $sTarget); 412 | $sTarget = str_replace($this->sModuleDirectory, '', $sTarget); 413 | $sTarget = trim($sTarget, DIRECTORY_SEPARATOR); 414 | 415 | $sSymlink = $oLine->getSymlink(); 416 | $sSymlink = str_replace($sBaseDir, '', $sSymlink); 417 | $sSymlink = trim($sSymlink, DIRECTORY_SEPARATOR); 418 | 419 | $aObjectsFixed[] = new $this->sClassName(array($sTarget, $sSymlink)); 420 | } 421 | 422 | $this->aObjects = array_merge($this->aObjects, $aObjectsFixed); 423 | } 424 | 425 | /** 426 | * returns all collected shell commands 427 | * 428 | * @return array 429 | */ 430 | public function getShells() { 431 | return $this->aShells; 432 | } 433 | 434 | } 435 | 436 | class Modman_Reader_Conflicts { 437 | private $aConflicts = array(); 438 | 439 | /** 440 | * checks for conflicts in file 441 | * 442 | * @param string $sSymlink symlink name 443 | * @param string $sType type (either dir or file) 444 | * @param string $sTarget = false target where the symlink should link to 445 | */ 446 | public function checkForConflict($sSymlink, $sType, $sTarget = false) { 447 | if (is_link($sSymlink)) { 448 | if ( 449 | !( 450 | $sType == 'link' 451 | AND realpath($sSymlink) == realpath($sTarget) 452 | ) 453 | ) { 454 | $this->aConflicts[$sSymlink] = 'link'; 455 | } 456 | } elseif (file_exists($sSymlink)) { 457 | if (is_dir($sSymlink)) { 458 | if ($sType == 'dir') { 459 | return; 460 | } 461 | $this->aConflicts[$sSymlink] = 'dir'; 462 | } else { 463 | $this->aConflicts[$sSymlink] = 'file'; 464 | } 465 | } 466 | } 467 | 468 | /** 469 | * returns if there are any conflicts 470 | * 471 | * @return bool true if conflicts exist 472 | */ 473 | public function hasConflicts() { 474 | return (count($this->aConflicts) > 0); 475 | } 476 | 477 | /** 478 | * returns conflicts as human readable string 479 | * 480 | * @return string 481 | */ 482 | public function getConflictsString() { 483 | $sString = ''; 484 | foreach ($this->aConflicts as $sFilename => $sType) { 485 | switch ($sType) { 486 | case 'dir': 487 | $sString .= $sFilename . ' is an existing directory.' . PHP_EOL; 488 | break; 489 | case 'file': 490 | $sString .= $sFilename . ' is an existing file.' . PHP_EOL; 491 | break; 492 | case 'link': 493 | $sString .= $sFilename . ' is an existing link pointing to ' . realpath($sFilename) . '.' . PHP_EOL; 494 | break; 495 | } 496 | } 497 | return $sString; 498 | } 499 | 500 | /** 501 | * removes a linked module 502 | */ 503 | public function cleanup() { 504 | $oResourceRemover = new Modman_Resource_Remover(); 505 | foreach ($this->aConflicts as $sFilename => $sType) { 506 | switch ($sType) { 507 | case 'dir': 508 | $oResourceRemover->doRemoveFolderRecursively($sFilename); 509 | break; 510 | case 'file': 511 | case 'link': 512 | $oResourceRemover->doRemoveResource($sFilename); 513 | break; 514 | } 515 | } 516 | } 517 | } 518 | 519 | class Modman_Command_Deploy { 520 | private $sModuleName; 521 | 522 | /** 523 | * constructor 524 | * 525 | * @param string $sModuleName which module to deploy 526 | * @throws Exception 527 | */ 528 | public function __construct($sModuleName) { 529 | if (empty($sModuleName)) { 530 | throw new Exception('please provide a module name to deploy'); 531 | } 532 | $this->sModuleName = $sModuleName; 533 | } 534 | 535 | /** 536 | * executes the deploy 537 | * 538 | * @param bool $bForce=false true if errors should be ignored 539 | * @param bool $bCopy=false true if files and folders should be copied instead of symlinked 540 | * @throws Exception on error 541 | */ 542 | public function doDeploy($bForce = false, $bCopy = false) { 543 | if ($this->sModuleName === Modman_Command_Init::MODMAN_BASEDIR_FILE) { 544 | return; 545 | } 546 | 547 | $oModmanModuleSymlink = new Modman_Module_Symlink($this->sModuleName); 548 | $sTarget = $oModmanModuleSymlink->getModmanModuleSymlinkPath(); 549 | 550 | $this->oReader = new Modman_Reader($sTarget); 551 | $aLines = $this->oReader->getObjectsPerRow('Modman_Command_Link_Line'); 552 | $oConflicts = new Modman_Reader_Conflicts(); 553 | foreach ($aLines as $iLine => $oLine) { 554 | /* @var $oLine Modman_Command_Link_Line */ 555 | if ($oLine->getTarget() AND $oLine->getSymlink()) { 556 | $sDirectoryName = $oLine->getSymlinkBaseDir(); 557 | if (!is_dir($sDirectoryName)) { 558 | $oConflicts->checkForConflict($sDirectoryName, 'dir'); 559 | } 560 | $oConflicts->checkForConflict($oLine->getSymlink(), 'link', $oLine->getTarget()); 561 | } else { 562 | unset($aLines[$iLine]); 563 | } 564 | } 565 | if ($oConflicts->hasConflicts()) { 566 | $sConflictsString = 'conflicts detected: ' . PHP_EOL . 567 | $oConflicts->getConflictsString() . PHP_EOL; 568 | if ($bForce) { 569 | echo $sConflictsString; 570 | echo 'Doing cleanup ... ' . PHP_EOL; 571 | $oConflicts->cleanup(); 572 | } else { 573 | throw new Exception($sConflictsString . 574 | 'use --force' 575 | ); 576 | } 577 | } 578 | foreach ($aLines as $oLine) { 579 | /* @var $oLine Modman_Command_Link_Line */ 580 | $sFullTarget = $sTarget . DIRECTORY_SEPARATOR . $oLine->getTarget(); 581 | if (!file_exists($sFullTarget)) { 582 | throw new Exception('can not link to non-existing file ' . $sFullTarget); 583 | } 584 | // create directories if path does not exist 585 | $sDirectoryName = $oLine->getSymlinkBaseDir(); 586 | if (!is_dir($sDirectoryName)) { 587 | echo 'Create directory ' . $sDirectoryName . PHP_EOL; 588 | mkdir($sDirectoryName, 0777, true); 589 | } 590 | if (!is_link($oLine->getSymlink())) { 591 | echo ' Applied: ' . $oLine->getSymlink() . ' ' . $sFullTarget . PHP_EOL; 592 | 593 | if ($bCopy) { 594 | if (is_dir($sFullTarget)) { 595 | mkdir($oLine->getSymlink()); 596 | $oIterator = new \RecursiveIteratorIterator( 597 | new \RecursiveDirectoryIterator( 598 | $sFullTarget, 599 | \RecursiveDirectoryIterator::SKIP_DOTS 600 | ), 601 | \RecursiveIteratorIterator::SELF_FIRST 602 | ); 603 | foreach ($oIterator as $oItem) { 604 | if ($oItem->isDir()) { 605 | mkdir($oLine->getSymlink() . DIRECTORY_SEPARATOR . $oIterator->getSubPathName()); 606 | } else { 607 | copy($oItem, $oLine->getSymlink() . DIRECTORY_SEPARATOR . $oIterator->getSubPathName()); 608 | } 609 | } 610 | } else { 611 | copy($sFullTarget, $oLine->getSymlink()); 612 | } 613 | } else { 614 | symlink( 615 | $sFullTarget, 616 | $oLine->getSymlink() 617 | ); 618 | } 619 | 620 | } 621 | } 622 | 623 | foreach ($this->oReader->getShells() as $sShell) { 624 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 625 | $sShell = str_replace('rm -rf', 'deltree', $sShell); 626 | } 627 | $sShell = str_replace('$MODULE', $sTarget, $sShell); 628 | $sShell = str_replace('$PROJECT', getcwd(), $sShell); 629 | system($sShell); 630 | } 631 | } 632 | } 633 | 634 | 635 | class Modman_Module_Symlink { 636 | private $sModuleName; 637 | 638 | /** 639 | * constructor 640 | * 641 | * @param string $sModuleName module name 642 | * @throws Exception 643 | */ 644 | public function __construct($sModuleName){ 645 | if (empty($sModuleName)) { 646 | throw new Exception('please provide a module name to deploy'); 647 | } 648 | $this->sModuleName = $sModuleName; 649 | } 650 | 651 | /** 652 | * returns module symlink 653 | * 654 | * @return string 655 | */ 656 | public function getModmanModuleSymlink(){ 657 | $sModmanModuleSymlink = Modman_Command_Init::MODMAN_DIRECTORY_NAME . DIRECTORY_SEPARATOR . $this->sModuleName; 658 | return $sModmanModuleSymlink; 659 | } 660 | 661 | /** 662 | * returns module symlink path 663 | * 664 | * @return string 665 | * @throws Exception if symlink is not linked 666 | */ 667 | public function getModmanModuleSymlinkPath(){ 668 | $sModmanModuleSymlink = $this->getModmanModuleSymlink(); 669 | if (!is_link($sModmanModuleSymlink) AND !is_dir($sModmanModuleSymlink)) { 670 | throw new Exception($this->sModuleName . ' is not initialized, please clone or link it'); 671 | } 672 | $sTarget = realpath($sModmanModuleSymlink); 673 | return $sTarget; 674 | } 675 | } 676 | 677 | class Modman_Command_Clean { 678 | private $aDeadSymlinks = array(); 679 | 680 | /** 681 | * executes the clean command 682 | */ 683 | public function doClean() { 684 | $oResourceRemover = new Modman_Resource_Remover(); 685 | foreach ($this->getDeadSymlinks() as $sSymlink) { 686 | echo 'Remove ' . $sSymlink . '.' . PHP_EOL; 687 | $oResourceRemover->doRemoveResource($sSymlink); 688 | } 689 | } 690 | 691 | /** 692 | * returns dead symlinks 693 | * 694 | * @param string $sDirectory=NULL define directory to work on, if not defined uses getcwd() 695 | * @return array list of dead symlinks 696 | */ 697 | private function getDeadSymlinks($sDirectory = NULL) { 698 | if (is_null($sDirectory)) { 699 | $sDirectory = getcwd(); 700 | } 701 | $this->scanForDeadSymlinks($sDirectory); 702 | return $this->aDeadSymlinks; 703 | } 704 | 705 | /** 706 | * recursive scan for dead symlinks 707 | * 708 | * @param string $sDirectory 709 | */ 710 | private function scanForDeadSymlinks($sDirectory) { 711 | foreach (scandir($sDirectory) as $sFilename) { 712 | if ($sFilename == '.' OR $sFilename == '..') { 713 | continue; 714 | } 715 | $sFullFilename = $sDirectory . DIRECTORY_SEPARATOR . $sFilename; 716 | if (is_dir($sFullFilename) AND !is_link($sFullFilename)) { 717 | $this->scanForDeadSymlinks($sFullFilename); 718 | } elseif (is_link($sFullFilename) AND !file_exists(realpath($sFullFilename))) { 719 | $this->aDeadSymlinks[] = $sFullFilename; 720 | } 721 | } 722 | } 723 | } 724 | 725 | class Modman_Command_Remove { 726 | /** 727 | * constructor 728 | * 729 | * @param string $sModuleName define module name 730 | * @throws Exception 731 | */ 732 | public function __construct($sModuleName) { 733 | if (empty($sModuleName)) { 734 | throw new Exception('please provide a module name to deploy'); 735 | } 736 | $this->sModuleName = $sModuleName; 737 | } 738 | 739 | /** 740 | * executres remove 741 | * 742 | * @param bool $bForce = false, true ignores errors 743 | * @throws Exception on error 744 | */ 745 | public function doRemove($bForce = false){ 746 | $oModmanModuleSymlink = new Modman_Module_Symlink($this->sModuleName); 747 | $sTarget = $oModmanModuleSymlink->getModmanModuleSymlinkPath(); 748 | 749 | $this->oReader = new Modman_Reader($sTarget); 750 | $aLines = $this->oReader->getObjectsPerRow('Modman_Command_Link_Line'); 751 | 752 | $oResourceRemover = new Modman_Resource_Remover(); 753 | 754 | foreach ($aLines as $oLine) { 755 | $sOriginalPath = $oLine->getTarget(); 756 | $sSymlinkPath = $oLine->getSymlink(); 757 | if (is_link($sSymlinkPath) 758 | AND file_exists($sTarget . DIRECTORY_SEPARATOR . $sOriginalPath)){ 759 | 760 | if (is_link($sSymlinkPath)){ 761 | $oResourceRemover->doRemoveResource($sSymlinkPath); 762 | } elseif ($bForce){ 763 | $oResourceRemover->doRemoveResource($sSymlinkPath); 764 | } else { 765 | throw new Exception('Problem with removing ' . $sSymlinkPath . ' - use --force'); 766 | } 767 | } 768 | } 769 | 770 | $oResourceRemover->doRemoveResource($oModmanModuleSymlink->getModmanModuleSymlink()); 771 | } 772 | } 773 | 774 | class Modman_Command_Create { 775 | 776 | private $aLinks = array(); 777 | 778 | private $sIncludeFilePath; 779 | 780 | private $bListHidden = false; 781 | 782 | const MAGENTO_MODULE_CODE_RELATIVE_PATH_DEPTH = 4; 783 | const MAGENTO_MODULE_DESIGN_RELATIVE_PATH_DEPTH = 7; 784 | 785 | /** 786 | * sets the include file 787 | * 788 | * @param string $sFilename 789 | * @throws Exception if $sFilename does not exist 790 | */ 791 | public function setIncludeFile($sFilename){ 792 | $sFilePath = realpath($sFilename); 793 | if (!$sFilePath){ 794 | throw new Exception("please provide a valid include file"); 795 | } else { 796 | $this->sIncludeFilePath = $sFilePath; 797 | } 798 | } 799 | 800 | /** 801 | * checks if a directory is empty 802 | * 803 | * @param string $sDirectoryPath 804 | * @return bool true if directory is empty (broken symlinks count as empty) 805 | */ 806 | private function isDirectoryEmpty($sDirectoryPath){ 807 | if (false === @readlink($sDirectoryPath)) { 808 | return true; 809 | } 810 | $aCurrentDirectoryListing = scandir($sDirectoryPath); 811 | return count($aCurrentDirectoryListing) <= 2; 812 | } 813 | 814 | /** 815 | * checks if node is a hidden once 816 | * 817 | * @param string $sNode 818 | * @return bool true for hidden files 819 | */ 820 | private function isHiddenNode($sNode){ 821 | return strlen($sNode) > 2 AND substr($sNode, 0, 1) == '.'; 822 | } 823 | 824 | /** 825 | * checks if directory is a magento module 826 | * 827 | * @param string $sDirectoryPathToCheck 828 | * @return bool true if directory is a magento module 829 | */ 830 | private function isMagentoModuleDirectory($sDirectoryPathToCheck){ 831 | $aPathParts = explode(DIRECTORY_SEPARATOR, $sDirectoryPathToCheck); 832 | 833 | $iAppPosition = array_search('app', $aPathParts); 834 | if (!$iAppPosition){ 835 | return false; 836 | } 837 | return ( 838 | $this->isMagentoModuleCodeDirectory($aPathParts, $iAppPosition) 839 | OR $this->isMagentoModuleDesignDirectory($aPathParts, $iAppPosition) 840 | ); 841 | } 842 | 843 | /** 844 | * checks if directory is the magento code directory 845 | * 846 | * @param array $aPathParts - all path parts from this directory 847 | * @param integer $iAppPosition - position of app directory in $aPathParts 848 | * @return bool true if directory is the magento code directory 849 | */ 850 | private function isMagentoModuleCodeDirectory($aPathParts, $iAppPosition) { 851 | if (!isset($aPathParts[$iAppPosition + self::MAGENTO_MODULE_CODE_RELATIVE_PATH_DEPTH])){ 852 | return false; 853 | } 854 | 855 | if ($aPathParts[$iAppPosition + 1] == 'code' 856 | AND in_array($aPathParts[$iAppPosition + 2], array('community', 'local'))){ 857 | return true; 858 | } 859 | } 860 | 861 | /** 862 | * checks if directory is the magento design directory 863 | * 864 | * @param array $aPathParts - all path parts from this directory 865 | * @param integer $iAppPosition - position of app directory in $aPathParts 866 | * @return bool true if directory is the magento design directory 867 | */ 868 | private function isMagentoModuleDesignDirectory($aPathParts, $iAppPosition) { 869 | if (!isset($aPathParts[$iAppPosition + self::MAGENTO_MODULE_DESIGN_RELATIVE_PATH_DEPTH])){ 870 | return false; 871 | } 872 | 873 | if ($aPathParts[$iAppPosition + 1] == 'design' 874 | AND ( 875 | $aPathParts[$iAppPosition + 2] == 'frontend' 876 | OR $aPathParts[$iAppPosition + 2] == 'adminhtml' 877 | OR $aPathParts[$iAppPosition + 2] == 'install' 878 | ) 879 | AND $aPathParts[$iAppPosition + 3] == 'base' 880 | AND $aPathParts[$iAppPosition + 4] == 'default' 881 | AND $aPathParts[$iAppPosition + 5] == 'template' 882 | ){ 883 | return true; 884 | } 885 | } 886 | 887 | /** 888 | * returns directory structure 889 | * 890 | * @param string $sDirectoryPath 891 | * @return array with directory structure 892 | */ 893 | private function getDirectoryStructure($sDirectoryPath) { 894 | $aResult = array(); 895 | 896 | $aCurrentDirectoryListing = scandir($sDirectoryPath); 897 | foreach ($aCurrentDirectoryListing as $sNode){ 898 | $sDirectoryPathToCheck = $sDirectoryPath . DIRECTORY_SEPARATOR . $sNode; 899 | if ((!$this->isHiddenNode($sNode) OR $this->bListHidden) 900 | AND !in_array($sNode, array('.', '..', 'modman', 'README', 'README.md', 'composer.json', 'atlassian-ide-plugin.xml'))){ 901 | if (is_dir($sDirectoryPathToCheck) 902 | AND !$this->isDirectoryEmpty($sDirectoryPathToCheck) 903 | AND !$this->isMagentoModuleDirectory($sDirectoryPathToCheck)){ 904 | $aResult[$sNode] = $this->getDirectoryStructure($sDirectoryPathToCheck); 905 | } else { 906 | $aResult[] = $sNode; 907 | } 908 | } 909 | } 910 | return $aResult; 911 | } 912 | 913 | /** 914 | * generates link list from directory structure 915 | * 916 | * @param array $aDirectoryStructure created by $this->getDirectoryStructure(string) 917 | * @param array $aPathElements = array() 918 | */ 919 | private function generateLinkListFromDirectoryStructure($aDirectoryStructure, $aPathElements = array()){ 920 | foreach ($aDirectoryStructure as $sDirectory => $mElements){ 921 | if (!is_array($mElements)){ 922 | $this->aLinks[] = 923 | (count($aPathElements) > 0 ? implode(DIRECTORY_SEPARATOR, $aPathElements) . DIRECTORY_SEPARATOR : '') . 924 | $mElements; 925 | } else { 926 | $this->generateLinkListFromDirectoryStructure($mElements, array_merge($aPathElements, array($sDirectory))); 927 | } 928 | } 929 | } 930 | 931 | /** 932 | * returns modman file path 933 | * 934 | * @return string 935 | */ 936 | private function getModmanFilePath(){ 937 | return getcwd() . DIRECTORY_SEPARATOR . Modman_Reader::MODMAN_FILE_NAME; 938 | } 939 | 940 | /** 941 | * checks if modman file exists 942 | * 943 | * @return bool true if modman file exists 944 | */ 945 | private function existsModmanFile(){ 946 | return file_exists($this->getModmanFilePath()); 947 | } 948 | 949 | /** 950 | * generates modman file 951 | */ 952 | private function generateModmanFile(){ 953 | if (file_exists($this->getModmanFilePath())){ 954 | unlink($this->getModmanFilePath()); 955 | } 956 | 957 | $sOutput = ''; 958 | foreach ($this->aLinks as $sLink){ 959 | $sLink = '/' . $sLink; 960 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 961 | $sLink = str_replace('\\', '/', $sLink); 962 | } 963 | $sOutput .= $sLink . ' ' . $sLink . PHP_EOL; 964 | } 965 | 966 | $rModmanFile = fopen($this->getModmanFilePath(), 'w'); 967 | fputs($rModmanFile,$sOutput); 968 | 969 | // if include file defined, include it to the modman 970 | if ($this->sIncludeFilePath){ 971 | $sIncludeFileContent = file_get_contents($this->sIncludeFilePath); 972 | fputs($rModmanFile, "\n" . $sIncludeFileContent); 973 | } 974 | 975 | fclose($rModmanFile); 976 | 977 | } 978 | 979 | /** 980 | * executes create command 981 | * 982 | * @param bool $bForce - if true errors will be ignored 983 | * @param bool $bListHidden = false, if true hidden files will be listed 984 | * @throws Exception on errors 985 | */ 986 | public function doCreate($bForce, $bListHidden = false){ 987 | $this->bListHidden = $bListHidden; 988 | 989 | $aDirectoryStructure = $this->getDirectoryStructure(getcwd()); 990 | $this->generateLinkListFromDirectoryStructure($aDirectoryStructure); 991 | 992 | if ($this->existsModmanFile() AND !$bForce){ 993 | throw new Exception('modman file ' . $this->getModmanFilePath() . ' already exists. Use --force'); 994 | } else { 995 | $this->generateModmanFile(); 996 | } 997 | } 998 | } 999 | 1000 | class Modman_Command_Clone { 1001 | 1002 | /** 1003 | * Url if git repo to be cloned 1004 | * 1005 | * @var string 1006 | */ 1007 | private $sGitUrl; 1008 | /** 1009 | * name of the folder for the git repo to be cloned into 1010 | * 1011 | * @var string 1012 | */ 1013 | private $sFolderName; 1014 | /** 1015 | * command to create modman file 1016 | * 1017 | * @var Modman_Command_Create 1018 | */ 1019 | private $oCreate; 1020 | 1021 | /** 1022 | * saves the git url and pre-calculates local folder name 1023 | * 1024 | * @param string $sGitUrl 1025 | * @param Modman_Command_Create $oCreate 1026 | */ 1027 | public function __construct($sGitUrl, Modman_Command_Create $oCreate){ 1028 | $this->sGitUrl = $sGitUrl; 1029 | $this->sFolderName = $this->getFolderNameFromParam($sGitUrl); 1030 | $this->oCreate = $oCreate; 1031 | } 1032 | 1033 | /** 1034 | * calculates local folder name from git url or directory 1035 | * 1036 | * @param string $sGitUrl 1037 | * @return string 1038 | */ 1039 | private function getFolderNameFromParam($sGitUrl){ 1040 | // is this a url 1041 | if (strstr($sGitUrl, '/')) { 1042 | $aSlashParts = explode('/', $sGitUrl); 1043 | if (strpos($aSlashParts[count($aSlashParts) - 1], '.git') !== false){ 1044 | $aDotParts = explode('.', $aSlashParts[count($aSlashParts) - 1]); 1045 | $sFolderName = $aDotParts[0]; 1046 | } else { 1047 | $sFolderName = $aSlashParts[count($aSlashParts) - 1]; 1048 | } 1049 | // or a directory? 1050 | } else { 1051 | $sFolderName = basename($sGitUrl); 1052 | } 1053 | 1054 | return $sFolderName; 1055 | } 1056 | 1057 | /** 1058 | * returns path to module folder 1059 | * 1060 | * @return string 1061 | */ 1062 | private function getModuleFolderPath(){ 1063 | return getcwd() . DIRECTORY_SEPARATOR 1064 | . Modman_Command_Init::MODMAN_DIRECTORY_NAME . DIRECTORY_SEPARATOR 1065 | . $this->sFolderName; 1066 | } 1067 | 1068 | /** 1069 | * checks if module folder exists 1070 | * 1071 | * @return bool 1072 | */ 1073 | private function existsModuleFolder(){ 1074 | return is_dir($this->getModuleFolderPath()); 1075 | } 1076 | 1077 | /** 1078 | * executes git clone command 1079 | */ 1080 | private function executeClone(){ 1081 | shell_exec( 1082 | 'git clone ' . escapeshellarg($this->sGitUrl) . ' ' 1083 | . escapeshellarg($this->getModuleFolderPath()) 1084 | ); 1085 | } 1086 | 1087 | /** 1088 | * checks if modman file exists in module folder 1089 | * 1090 | * @return bool 1091 | */ 1092 | private function existsModmanFile(){ 1093 | return is_file( 1094 | $this->getModuleFolderPath() . DIRECTORY_SEPARATOR 1095 | . Modman_Reader::MODMAN_FILE_NAME 1096 | ); 1097 | } 1098 | 1099 | /** 1100 | * creates modman file in the module folder 1101 | */ 1102 | private function doCreateModmanFile(){ 1103 | $sCurrentDirectory = getcwd(); 1104 | chdir($this->getModuleFolderPath()); 1105 | $this->oCreate->doCreate(false); 1106 | chdir($sCurrentDirectory); 1107 | } 1108 | 1109 | /** 1110 | * deletes module folder 1111 | * 1112 | * @param string $sFolderName 1113 | * @return bool 1114 | */ 1115 | private function deleteModuleFolder($sFolderName){ 1116 | $oRemover = new Modman_Resource_Remover(); 1117 | $oRemover->doRemoveFolderRecursively($sFolderName); 1118 | } 1119 | 1120 | /** 1121 | * main method to create a clone of a git repo 1122 | * 1123 | * @param bool $bForce 1124 | * @param bool $bCreateModman 1125 | * @throws Exception 1126 | */ 1127 | public function doClone($bForce = false, $bCreateModman = false){ 1128 | $sCwd = getcwd(); 1129 | $sInitPath = realpath($sCwd); 1130 | $oInit = new Modman_Command_Init(); 1131 | $oInit->doInit($sInitPath); 1132 | 1133 | if ($this->existsModuleFolder()){ 1134 | if (!$bForce){ 1135 | throw new Exception('Module already exists. Please use --force to overwrite existing folder'); 1136 | } else { 1137 | $this->deleteModuleFolder($this->getModuleFolderPath()); 1138 | } 1139 | } 1140 | $this->executeClone(); 1141 | 1142 | if (!$this->existsModmanFile() AND $bCreateModman){ 1143 | $this->doCreateModmanFile(); 1144 | } 1145 | 1146 | $oDeploy = new Modman_Command_Deploy($this->sFolderName); 1147 | $oDeploy->doDeploy($bForce); 1148 | 1149 | } 1150 | } 1151 | 1152 | class Modman_Resource_Remover{ 1153 | 1154 | /** 1155 | * checks if the folder is empty 1156 | * 1157 | * @param string $sDirectoryPath 1158 | * @return bool 1159 | */ 1160 | private function isFolderEmpty($sDirectoryPath){ 1161 | return count(scandir($sDirectoryPath)) == 2; 1162 | } 1163 | 1164 | /** 1165 | * checks if it's windows environment 1166 | * 1167 | * @return bool 1168 | */ 1169 | private function isWin() 1170 | { 1171 | $sPhpOs = strtolower(PHP_OS); 1172 | return strpos($sPhpOs, 'win') !== false; 1173 | } 1174 | 1175 | /** 1176 | * fixes permissions on windows to be 1177 | * able to delete files/links 1178 | * 1179 | * @param string $sElementPath 1180 | */ 1181 | private function fixWindowsPermissions($sElementPath) 1182 | { 1183 | if ($this->isWin()) { 1184 | // workaround for windows to delete read-only flag 1185 | // which prevents link/file from being deleted properly 1186 | chmod($sElementPath, 0777); 1187 | } 1188 | } 1189 | 1190 | /** 1191 | * removes a resource 1192 | * 1193 | * @param string $sElementPath resource to remove 1194 | * @throws Exception 1195 | */ 1196 | public function doRemoveResource($sElementPath){ 1197 | $this->fixWindowsPermissions($sElementPath); 1198 | if (is_dir($sElementPath)){ 1199 | if ($this->isFolderEmpty($sElementPath)){ 1200 | rmdir($sElementPath); 1201 | } elseif (is_link($sElementPath)) { 1202 | if ($this->isWin()) { 1203 | rmdir($sElementPath); 1204 | } else { 1205 | unlink($sElementPath); 1206 | } 1207 | } else { 1208 | throw new InvalidArgumentException('A resource must be a file, an empty folder or a symlink.'); 1209 | } 1210 | } elseif (is_file($sElementPath)){ 1211 | unlink($sElementPath); 1212 | } elseif (is_link($sElementPath)){ 1213 | unlink($sElementPath); 1214 | } else { 1215 | throw new InvalidArgumentException('A resource must be a file, an empty folder or a symlink.'); 1216 | } 1217 | } 1218 | 1219 | /** 1220 | * deletes folder recursively 1221 | * 1222 | * @param string $sFolderName 1223 | * @return bool 1224 | */ 1225 | public function doRemoveFolderRecursively($sFolderName){ 1226 | 1227 | $oDirectoryIterator = new RecursiveDirectoryIterator($sFolderName); 1228 | /** @var SplFileInfo $oElement */ 1229 | foreach (new RecursiveIteratorIterator($oDirectoryIterator, RecursiveIteratorIterator::CHILD_FIRST) as $oElement){ 1230 | $this->doRemoveResource($oElement->getPathname()); 1231 | } 1232 | $this->doRemoveResource($sFolderName); 1233 | } 1234 | 1235 | } 1236 | 1237 | $oModman = new Modman(); 1238 | $oModman->run($argv); 1239 | -------------------------------------------------------------------------------- /modman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sScript="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/modman.php"; 4 | php "$sScript" "$@"; 5 | --------------------------------------------------------------------------------