<?php
/**
 * Magento
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@magentocommerce.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Magento to newer
 * versions in the future. If you wish to customize Magento for your
 * needs please refer to http://www.magentocommerce.com for more information.
 *
 * @category    Mage
 * @package     Mage_Connect
 * @copyright   Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com)
 * @license     http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */


final class Mage_Connect_Command_Install
extends Mage_Connect_Command
{

    /**
     * Install action callback
     * @param string $command
     * @param array $options
     * @param array $params
     * @return void
     */
    public function doInstall($command, $options, $params, $objects = array())
    {
        $this->cleanupParams($params);

        $installFileMode = $command === 'install-file';


        $cache=null;
        /**
         * @var $cache Mage_Connect_Singleconfig
         */
        $ftpObj=null;


        try {
            $packager = $this->getPackager();
            $forceMode = isset($options['force']);
            $upgradeAllMode = $command == 'upgrade-all';
            $upgradeMode = $command == 'upgrade' || $command == 'upgrade-all';
            $noFilesInstall = isset($options['nofiles']);
            $withDepsMode = !isset($options['nodeps']);
            $ignoreModifiedMode = true || !isset($options['ignorelocalmodification']);
            $clearInstallMode = $command == 'install' && !$forceMode;
            $installAll = isset($options['install_all']);
            $channelAuth = isset($options['auth'])?$options['auth']:array();

            $rest = $this->rest();
            $ftp = empty($options['ftp']) ? false : $options['ftp'];
            if($ftp) {
                list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp);
            } else {
                $config = $this->config();
                $cache = $this->getSconfig();
            }
            if(empty($config->magento_root)){
                $config->magento_root=dirname(dirname($_SERVER['SCRIPT_FILENAME']));
            }
            chdir($config->magento_root);
            $dirCache = DIRECTORY_SEPARATOR . $config->downloader_path . DIRECTORY_SEPARATOR . Mage_Connect_Config::DEFAULT_CACHE_PATH;
            $dirTmp = DIRECTORY_SEPARATOR . Mage_Connect_Package_Reader::PATH_TO_TEMPORARY_DIRECTORY;
            $dirMedia = DIRECTORY_SEPARATOR . 'media';
            $isWritable = true;
            if($ftp) {
                $cwd=$ftpObj->getcwd();
                $ftpObj->mkdirRecursive($cwd . $dirCache,0777);
                $ftpObj->chdir($cwd);
                $ftpObj->mkdirRecursive($cwd . $dirTmp,0777);
                $ftpObj->chdir($cwd);
                $ftpObj->mkdirRecursive($cwd . $dirMedia,0777);
                $ftpObj->chdir($cwd);
                $err = "Please check for sufficient ftp write file permissions.";
            } else {
                @mkdir($config->magento_root . $dirCache,0777,true);
                @mkdir($config->magento_root . $dirTmp,0777,true);
                @mkdir($config->magento_root . $dirMedia,0777,true);
                $isWritable = is_writable($config->magento_root)
                              && is_writable($config->magento_root . DIRECTORY_SEPARATOR . $config->downloader_path)
                              && is_writable($config->magento_root . $dirCache)
                              && is_writable($config->magento_root . $dirTmp)
                              && is_writable($config->magento_root . $dirMedia);
                $err = "Please check for sufficient write file permissions.";
            }
            $isWritable = $isWritable && is_writable($config->magento_root . $dirMedia)
                          && is_writable($config->magento_root . $dirCache)
                          && is_writable($config->magento_root . $dirTmp);
            if(!$isWritable){
                $this->doError($command, $err);
                throw new Exception('Your Magento folder does not have sufficient write permissions, which downloader requires.');
            }
            if(!empty($channelAuth)){
                $rest->getLoader()->setCredentials($channelAuth['username'], $channelAuth['password']);
            }

            if($installFileMode) {
                if(count($params) < 1) {
                    throw new Exception("Argument should be: filename");
                }
                $filename = $params[0];
                if(!@file_exists($filename)) {
                    throw new Exception("File '{$filename}' not found");
                }
                if(!@is_readable($filename)) {
                    throw new Exception("File '{$filename}' is not readable");
                }

                $package = new Mage_Connect_Package($filename);
                $package->setConfig($config);
                $package->validate();
                $errors = $package->getErrors();
                if(count($errors)) {
                    throw new Exception("Package file is invalid\n".implode("\n", $errors));
                }

                $pChan = $package->getChannel();
                $pName = $package->getName();
                $pVer = $package->getVersion();


                if (!($cache->isChannelName($pChan) || $cache->isChannelAlias($pChan))) {
                    throw new Exception("The '{$pChan}' channel is not installed. Please use the MAGE shell script to install the '{$pChan}' channel.");
                }

                $conflicts = $cache->hasConflicts($pChan, $pName, $pVer);

                if(false !== $conflicts) {
                    $conflicts = implode(", ",$conflicts);
                    if($forceMode) {
                        $this->doError($command, "Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts);
                    } else {
                        throw new Exception("Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts);
                    }
                }

                $conflicts = $package->checkPhpDependencies();
                if(true !== $conflicts) {
                    $confilcts = implode(",",$conflicts);
                    $err = "Package {$pChan}/{$pName} {$pVer} depends on PHP extensions: ".$conflicts;
                    if($forceMode) {
                        $this->doError($command, $err);
                    } else {
                        throw new Exception($err);
                    }
                }

                $conflicts = $package->checkPhpVersion();
                if(true !== $conflicts) {
                    $err = "Package {$pChan}/{$pName} {$pVer}: ".$conflicts;
                    if($forceMode) {
                        $this->doError($command, $err);
                    } else {
                        throw new Exception($err);
                    }
                }


                if(!$noFilesInstall) {
                    if($ftp) {
                        $packager->processInstallPackageFtp($package, $filename, $config, $ftpObj);
                    } else {
                        $packager->processInstallPackage($package, $filename, $config);
                    }
                }
                $cache->addPackage($package);
                $installedDeps = array();
                $installedDepsAssoc = array();
                $installedDepsAssoc[] = array('channel'=>$pChan, 'name'=>$pName, 'version'=>$pVer);
                $installedDeps[] = array($pChan, $pName, $pVer);


                $title = isset($options['title']) ? $options['title'] : "Package installed: ";
                $out = array($command => array('data'=>$installedDeps, 'assoc'=>$installedDepsAssoc,  'title'=>$title));

                if($ftp) {
                    $packager->writeToRemoteCache($cache, $ftpObj);
                    @unlink($config->getFilename());
                }

                $this->ui()->output($out);
                return $out[$command]['data'];
            }

            if(!$upgradeAllMode) {

                if(count($params) < 2) {
                    throw new Exception("Argument should be: channelName packageName");
                }
                $channel = $params[0];
                $package = $params[1];
                $argVersionMax = isset($params[2]) ? $params[2]: false;
                $argVersionMin = false;
                
                $cache->checkChannel($channel, $config, $rest);
                $channelName = $cache->chanName($channel);
                $this->ui()->output("Checking dependencies of packages");
                $packagesToInstall = $packager->getDependenciesList( $channelName, $package, $cache, $config, $argVersionMax, $argVersionMin, $withDepsMode, false, $rest);
                /*
                 * @TODO: process 'failed' results
                 */
                if(count($packagesToInstall['failed'])) {
                    $showError=!count($packagesToInstall['result']);
                    foreach($packagesToInstall['failed'] as $failed){
                        //$failed = array('name'=>$package, 'channel'=>$chanName, 'max'=>$versionMax, 'min'=>$versionMin, 'reason'=>$e->getMessage());
                        $msg="Package {$failed['channel']}/{$failed['name']} failed: ".$failed['reason'];
                        if($showError){
                            $this->doError($command, $msg);
                        }else{
                            $this->ui()->output($msg);
                        }
                    }
                }
                $packagesToInstall = $packagesToInstall['result'];
            } else {
                if(empty($params[0])) {
                    $channels = $cache->getChannelNames();
                } else {
                    $channel = $params[0];
                    if(!$cache->isChannel($channel)) {
                        throw new Exception("'{$channel}' is not existant channel name / valid uri");
                    }
                    $channels = $cache->chanName($channel);
                }
                $packagesToInstall = array();
                $neededToUpgrade = $packager->getUpgradesList($channels, $cache, $config, $rest);
                foreach($neededToUpgrade as $chan=>$packages) {
                    foreach($packages as $name=>$data) {
                        $versionTo = $data['to'];
                        $tmp = $packager->getDependenciesList( $chan, $name, $cache, $config, $versionTo, $versionTo, $withDepsMode, false, $rest);
                        if(count($tmp['result'])) {
                            $packagesToInstall = array_merge($packagesToInstall, $tmp['result']);
                        }
                    }
                }
            }

            /**
             * Make installation
             */
            $installedDeps = array();
            $installedDepsAssoc = array();
            $keys = array();

            foreach($packagesToInstall as $package) {
                try {
                    $pName = $package['name'];
                    $pChan = $package['channel'];
                    $pVer = $package['downloaded_version'];
                    $pInstallState = $package['install_state'];
                    $rest->setChannel($cache->chanUrl($pChan));

                    /**
                     * Skip existing packages
                     */
                    if ($upgradeMode && $cache->hasPackage($pChan, $pName, $pVer, $pVer) || ('already_installed' == $pInstallState && !$forceMode)) {
                        $this->ui()->output("Already installed: {$pChan}/{$pName} {$pVer}, skipping");
                        continue;
                    }

                    if('incompartible' == $pInstallState) {
                        $this->ui()->output("Package incompartible with installed Magento: {$pChan}/{$pName} {$pVer}, skipping");
                        continue;
                    }

                    $conflicts = $cache->hasConflicts($pChan, $pName, $pVer);

                    if(false !== $conflicts) {
                        $conflicts = implode(", ",$conflicts);
                        if($forceMode) {
                            $this->doError($command, "Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts);
                        } else {
                            throw new Exception("Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts);
                        }
                    }

                    /**
                     * Modifications
                     */
                    if (($upgradeMode || ($pInstallState == 'upgrade')) && !$ignoreModifiedMode) {
                        if($ftp) {
                            $modifications = $packager->getRemoteModifiedFiles($pChan, $pName, $cache, $config, $ftp);
                        } else {
                            $modifications = $packager->getLocalModifiedFiles($pChan, $pName, $cache, $config);
                        }
                        if (count($modifications) > 0) {
                            $this->ui()->output('Changed locally: ');
                            foreach ($modifications as $row) {
                                if(!$ftp) {
                                    $this->ui()->output($config->magento_root.DS.$row);
                                } else {
                                    $this->ui()->output($row);
                                }
                            }
                            /*$this->ui()->confirm('Do you want rewrite all files?');
                             continue;*/
                        }
                    }

                    if($ftp) {
                        $cwd=$ftpObj->getcwd();
                        $dir=$cwd . DIRECTORY_SEPARATOR .$config->downloader_path . DIRECTORY_SEPARATOR . Mage_Connect_Config::DEFAULT_CACHE_PATH . DIRECTORY_SEPARATOR . trim( $pChan, "\\/");
                        $ftpObj->mkdirRecursive($dir,0777);
                        $ftpObj->chdir($cwd);
                    } else {
                        $dir = $config->getChannelCacheDir($pChan);
                        @mkdir($dir, 0777, true);
                    }
                    $dir = $config->getChannelCacheDir($pChan);
                    $packageFileName = $pName."-".$pVer.".tgz";
                    $file = $dir.DIRECTORY_SEPARATOR.$packageFileName;
                    if(!@file_exists($file)) {
                        $this->ui()->output("Starting to download $packageFileName ...");
                        $rest->downloadPackageFileOfRelease($pName, $pVer, $file);
                        $this->ui()->output(sprintf("...done: %s bytes", number_format(filesize($file))));
                    }

                    /**
                     * Remove old version package before install new
                     */
                    if ($cache->hasPackage($pChan, $pName)) {
                        if ($ftp) {
                            $packager->processUninstallPackageFtp($pChan, $pName, $cache, $ftpObj);
                        } else {
                            $packager->processUninstallPackage($pChan, $pName, $cache, $config);
                        }
                        $cache->deletePackage($pChan, $pName);
                    }

                    $package = new Mage_Connect_Package($file);
                    if ($clearInstallMode && $pInstallState != 'upgrade' && !$installAll) {
                        $this->validator()->validateContents($package->getContents(), $config);
                        $errors = $this->validator()->getErrors();
                        if (count($errors)) {
                            throw new Exception("Package '{$pName}' is invalid\n" . implode("\n", $errors));
                        }
                    }

                    $conflicts = $package->checkPhpDependencies();
                    if(true !== $conflicts) {
                        $confilcts = implode(",",$conflicts);
                        $err = "Package {$pChan}/{$pName} {$pVer} depends on PHP extensions: ".$conflicts;
                        if($forceMode) {
                            $this->doError($command, $err);
                        } else {
                            throw new Exception($err);
                        }
                    }

                    $conflicts = $package->checkPhpVersion();
                    if(true !== $conflicts) {
                        $err = "Package {$pChan}/{$pName} {$pVer}: ".$conflicts;
                        if($forceMode) {
                            $this->doError($command, $err);
                        } else {
                            throw new Exception($err);
                        }
                    }
                    
                    /**
                     * @todo: make "Use custom permissions" functionality working
                     */
                    if(!$noFilesInstall) {
                        $this->ui()->output("Installing package {$pChan}/{$pName} {$pVer}");
                        if($ftp) {
                            $packager->processInstallPackageFtp($package, $file, $config, $ftpObj);
                        } else {
                            $packager->processInstallPackage($package, $file, $config);
                        }
                        $this->ui()->output("Package {$pChan}/{$pName} {$pVer} installed successfully");
                    }
                    $cache->addPackage($package);

                    $installedDepsAssoc[] = array('channel'=>$pChan, 'name'=>$pName, 'version'=>$pVer);
                    $installedDeps[] = array($pChan, $pName, $pVer);

                } catch(Exception $e) {
                    $this->doError($command, $e->getMessage());
                }
            }



            $title = isset($options['title']) ? $options['title'] : "Package installed: ";
            $out = array($command => array('data'=>$installedDeps, 'assoc'=>$installedDepsAssoc,  'title'=>$title));

            if($ftp) {
                $packager->writeToRemoteCache($cache, $ftpObj);
                @unlink($config->getFilename());
            }

            $this->ui()->output($out);
            return $out[$command]['data'];

        } catch (Exception $e) {
            if($ftp) {
                $packager->writeToRemoteCache($cache, $ftpObj);
                @unlink($config->getFilename());
            }
            return $this->doError($command, $e->getMessage());
        }
    }

    /**
     * Upgrade action callback
     * @param string $command
     * @param array $options
     * @param array $params
     * @return void
     */
    public function doUpgrade($command, $options, $params)
    {
        $options['title'] = "Package upgraded: ";
        return $this->doInstall($command, $options, $params);
    }

    /**
     * Updgrade action callback
     * @param string $command
     * @param array $options
     * @param array $params
     * @return void
     */
    public function doUpgradeAll($command, $options, $params)
    {
        $options['title'] = "Package upgraded: ";
        return $this->doInstall($command, $options, $params);
    }

    /**
     * Uninstall package callback
     * @param string $command
     * @param array $options
     * @param array $params
     * @return unknown_type
     */
    public function doUninstall($command, $options, $params)
    {
        $this->cleanupParams($params);
        //$this->splitPackageArgs($params);

        try {
            if(count($params) != 2) {
                throw new Exception("Argument count should be = 2");
            }

            $channel = $params[0];
            $package = $params[1];
            $packager = $this->getPackager();
            $withDepsMode = !isset($options['nodeps']);
            $forceMode = isset($options['force']);

            $ftp = empty($options['ftp']) ? false : $options['ftp'];
            if($ftp) {
                list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp);
            } else {
                $cache = $this->getSconfig();
                $config = $this->config();
            }

            $chan = $cache->getChannel($channel);
            $channel = $cache->chanName($channel);
            if(!$cache->hasPackage($channel, $package)) {
                throw new Exception("Package is not installed");
            }

            $deletedPackages = array();
            $list = $packager->getUninstallList($channel, $package, $cache, $config, $withDepsMode);
            foreach($list['list'] as $packageData) {
                try {
                    $reqd = $cache->requiredByOtherPackages($packageData['channel'], $packageData['name'], $list['list']);
                    if(count($reqd)) {
                        $errMessage = "{$packageData['channel']}/{$packageData['name']} {$packageData['version']} is required by: ";
                        $t = array();
                        foreach($reqd as $r) {
                            $t[] = $r['channel']."/".$r['name']. " ".$r['version'];
                        }
                        $errMessage .= implode(", ", $t);
                        if($forceMode) {
                            $this->ui()->output("Warning: ".$errMessage);
                        } else {
                            throw new Exception($errMessage);
                        }
                    }
                } catch(Exception $e) {
                    if($forceMode) {
                        $this->doError($command, $e->getMessage());
                    } else {
                        throw new Exception($e->getMessage());
                    }
                }
            }
            foreach($list['list'] as $packageData) {
                try {
                    list($chan, $pack) = array($packageData['channel'], $packageData['name']);
                    $packageName = $packageData['channel'] . "/" . $packageData['name'];
                    $this->ui()->output("Starting to uninstall $packageName ");
                    if($ftp) {
                        $packager->processUninstallPackageFtp($chan, $pack, $cache, $ftpObj);
                    } else {
                        $packager->processUninstallPackage($chan, $pack, $cache, $config);
                    }
                    $cache->deletePackage($chan, $pack);
                    $deletedPackages[] = array($chan, $pack);
                    $this->ui()->output("Package {$packageName} uninstalled");
                } catch(Exception $e) {
                    if($forceMode) {
                        $this->doError($command, $e->getMessage());
                    } else {
                        throw new Exception($e->getMessage());
                    }
                }
            }
            if($ftp) {
                $packager->writeToRemoteCache($cache, $ftpObj);
                @unlink($config->getFilename());
            }
            $out = array($command=>array('data'=>$deletedPackages, 'title'=>'Package deleted: '));
            $this->ui()->output($out);

        } catch (Exception $e) {
            return $this->doError($command, $e->getMessage());
        }

    }

}

