<?php
/*
 * This file is part of MODX Revolution.
 *
 * Copyright (c) MODX, LLC. All Rights Reserved.
 *
 * For complete copyright and license information, see the COPYRIGHT and LICENSE
 * files found in the top-level directory of this distribution.
 */

/**
 * Abstracts the package building process
 *
 * @package modx
 * @subpackage transport
 */
class modPackageBuilder {
    /**
    * @var string The directory in which the package file is located.
    * @access public
    */
    public $directory;
    /**
    * @var string The unique signature for the package.
    * @access public
    */
    public $signature;
    /**
    * @var string The filename of the actual package.
    * @access public
    */
    public $filename;
    /**
    * @var xPDOTransport The xPDOTransport package object.
    * @access public
    */
    public $package;
    /**
     * @var modNamespace The modNamespace that the package is associated with.
     * @access public
     */
    public $namespace;
    /**
     * @var array An array of classnames to automatically select by namespace
     * @access public
     */
    public $autoselects;

    /**
     * Creates an instance of the modPackageBuilder class.
     *
     * @param modX &$modx A reference to a modX instance.
     * @return modPackageBuilder
     */
    function __construct(modX &$modx) {
        $this->modx = & $modx;
        $this->modx->loadClass('transport.modTransportVehicle', '', false, true);
        $this->modx->loadClass('transport.xPDOTransport', XPDO_CORE_PATH, true, true);

        if (!$workspace = $this->modx->getObject('modWorkspace', array (
                'active' => 1
            ))) {
            $this->modx->log(modX::LOG_LEVEL_FATAL,$this->modx->lexicon('core_err_invalid'));
            exit ();
        }
        $this->directory = $workspace->get('path') . 'packages/';
        $this->modx->lexicon->load('workspace');
        $this->autoselects = array ();
    }

    /**
    * Allows for customization of the package workspace.
    *
    * @access public
    * @param integer $workspace_id The ID of the workspace to select.
    * @return modWorkspace The workspace set, false if invalid.
    */
    public function setWorkspace($workspace_id) {
        if (!is_numeric($workspace_id)) {
            return false;
        }
        $workspace = $this->modx->getObject('modWorkspace', $workspace_id);
        if ($workspace == null) {
            return false;
        }

        $this->directory = $workspace->get('path') . 'packages/';
        return $workspace;
    }

    /**
     * @deprecated To be removed in 2.2
     * @param string $name
     * @param string $version
     * @param string $release
     * @see modPackageBuilder::createPackage
     */
    public function create($name, $version, $release = '') {
        $this->modx->deprecated('2.1.2', 'Use modPackageBuilder::createPackage instead.');
        $this->createPackage($name, $version, $release);
    }

    /**
    * Creates a new xPDOTransport package.
    *
    * @access public
    * @param string $name The name of the component the package represents.
    * @param string $version A string representing the version of the package.
    * @param string $release A string describing the specific release of this version of the
    * package.
    * @return xPDOTransport The xPDOTransport package object.
    */
    public function createPackage($name, $version, $release = '') {
        /* setup the signature and filename */
        $s['name'] = strtolower($name);
        $s['version'] = $version;
        $s['release'] = $release;
        $this->signature = $s['name'];
        if (!empty ($s['version'])) {
            $this->signature .= '-' . $s['version'];
        }
        if (!empty ($s['release'])) {
            $this->signature .= '-' . $s['release'];
        }
        $this->filename = $this->signature . '.transport.zip';

        /* remove the package if it's already been made */
        if (file_exists($this->directory . $this->filename)) {
            unlink($this->directory . $this->filename);
        }
        if (file_exists($this->directory . $this->signature) && is_dir($this->directory . $this->signature)) {
            $cacheManager = $this->modx->getCacheManager();
            if ($cacheManager) {
                $cacheManager->deleteTree($this->directory . $this->signature, true, false, array ());
            }
        }

        /* create the transport package */
        $this->package = new xPDOTransport($this->modx, $this->signature, $this->directory);
        $this->modx->log(modX::LOG_LEVEL_INFO, $this->modx->lexicon('package_created',array(
            'signature' => $this->signature,
        )));

        return $this->package;
    }

    /**
     * Sets the classes that are to automatically be included and built into the
     * package.
     *
     * @access public
     * @param array $classes An array of class names to build in
     * @return void
     */
    public function setAutoSelects(array $classes = array ()) {
        $this->autoselects = $classes;
    }

    /**
    * Creates the modTransportVehicle for the specified object.
    *
    * @access public
    * @param mixed $obj The payload being abstracted as a vehicle.
    * @param array $attr Attributes for the vehicle.
    * @return modTransportVehicle The created modTransportVehicle instance.
    */
    public function createVehicle($obj, $attr) {
        if ($this->{'namespace'}) {
            $attr['namespace'] = $this->{'namespace'}; /* package the namespace into the metadata */
        }
        $vehicle = new modTransportVehicle($obj, $attr);

        return $vehicle;
    }

    /**
     * Registers a namespace to the transport package. If no namespace is found,
     * will create a namespace.
     *
     * @access public
     * @param string|modNamespace $ns The modNamespace object or the
     * string name of the namespace
     * @param boolean|array $autoincludes If true, will automatically select
     * relative resources to the namespace.
     * @param boolean $packageNamespace If false, will not package the namespace
     * as a vehicle.
     * @param string $path The path for the namespace to be created.
     * @param string $assetsPath An optional custom assets_path for the namespace.
     * @return boolean True if successful.
     */
    public function registerNamespace($ns = 'core', $autoincludes = true, $packageNamespace = true, $path = '', $assetsPath = '') {
        if (!($ns instanceof modNamespace)) {
            $namespace = $this->modx->getObject('modNamespace', $ns);
            if (!$namespace) {
                $namespace = $this->modx->newObject('modNamespace');
                $namespace->set('name', $ns);
                $namespace->set('path',$path);
                $namespace->set('assets_path',$assetsPath);
            }
            if (!empty($path)) {
                $namespace->set('path',$path);
            }
            if (!empty($assetsPath)) {
                $namespace->set('assets_path',$assetsPath);
            }
        } else {
            $namespace = $ns;
        }
        $this->{'namespace'} = $namespace;

        $this->modx->log(modX::LOG_LEVEL_INFO, $this->modx->lexicon('namespace_registered',array(
            'namespace' => $this->{'namespace'}->get('name'),
        )));

        /* define some basic attributes */
        $attributes = array (
            xPDOTransport::UNIQUE_KEY => 'name',
            xPDOTransport::PRESERVE_KEYS => true,
            xPDOTransport::UPDATE_OBJECT => true,
            xPDOTransport::RESOLVE_FILES => true,
            xPDOTransport::RESOLVE_PHP => true,
        );
        if ($packageNamespace) {
            /* create the namespace vehicle */
            $v = $this->createVehicle($namespace, $attributes);

            /* put it into the package */
            if (!$this->putVehicle($v)) {
                return false;
            }
            $this->modx->log(modX::LOG_LEVEL_INFO, $this->modx->lexicon('namespace_packaged',array(
                'namespace' => $this->{'namespace'}->get('name'),
            )));
        }

        /* Can automatically package in certain classes based upon their namespace values */
        if ($autoincludes == true || (is_array($autoincludes) && !empty ($autoincludes))) {
            $this->modx->log(modx::LOG_LEVEL_INFO, $this->modx->lexicon('autoincludes_packaging',array(
                'autoincludes' => print_r($autoincludes, true),
            )));
            if (is_array($autoincludes)) {
                /* set automatically included packages */
                $this->setAutoSelects($autoincludes);
            }

            /* grab all related classes that can be auto-packaged and package them in */
            foreach ($this->autoselects as $classname) {
                $objs = $this->modx->getCollection($classname, array (
                    'namespace' => $namespace->get('name'),

                ));
                foreach ($objs as $obj) {
                    $v = $this->createVehicle($obj, $attributes);
                    if (!$this->putVehicle($v)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
    * Puts the vehicle into the package.
    *
    * @access public
    * @param modTransportVehicle $vehicle The vehicle to insert into the package.
    * @return boolean True if successful.
    */
    public function putVehicle($vehicle) {
        $attr = $vehicle->compile();
        $obj = $vehicle->fetch();
        return $this->package->put($obj, $attr);
    }

    /**
    * Packs the package.
    *
    * @see xPDOTransport::pack
    *
    * @access public
    * @return boolean True if successful.
    */
    public function pack() {
        return $this->package->pack();
    }

    /**
     * Retrieves the package signature.
     *
     * @access public
     * @return string The signature of the included package.
     */
    public function getSignature() {
        return $this->package->signature;
    }

    /**
     * Generates the model from a schema.
     *
     * @access public
     * @param string $model The directory path of the model to generate to.
     * @param string $schema The schema file to generate from.
     * @return boolean true if successful
     */
    public function buildSchema($model, $schema) {
        $manager = $this->modx->getManager();
        $generator = $manager->getGenerator();
        $generator->parseSchema($schema, $model);
        return true;
    }

    /**
     * Set an array of attributes into the xPDOTransport manifest.
     *
     * @access public
     * @param array $attributes An array of attributes to set in the
     * manifest of the package being built.
     * @return null
     */
    public function setPackageAttributes(array $attributes = array ()) {
        if ($this->package !== null) {
            foreach ($attributes as $k => $v)
                $this->package->setAttribute($k, $v);
        } else {
            $this->modx->log(modX::LOG_LEVEL_ERROR, $this->modx->lexicon('package_err_spa'));
        }
    }
}
