<?php
/*
 *  $Id: Lib.php 7490 2010-03-29 19:53:27Z jwage $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */

/**
 * Doctrine_Lib has not commonly used static functions, mostly for debugging purposes
 *
 * @package     Doctrine
 * @subpackage  Lib
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.org
 * @since       1.0
 * @version     $Revision: 7490 $
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
 */
class Doctrine_Lib
{
    /**
     * Generates a human readable representation of a record's state.
     *
     * This method translates a Doctrine_Record state (integer constant)
     * in an english string.
     * @see Doctrine_Record::STATE_* constants
     *
     * @param integer $state    the state of record
     * @return string           description of given state
     */
    public static function getRecordStateAsString($state)
    {
        switch ($state) {
            case Doctrine_Record::STATE_PROXY:
                return "proxy";
                break;
            case Doctrine_Record::STATE_CLEAN:
                return "persistent clean";
                break;
            case Doctrine_Record::STATE_DIRTY:
                return "persistent dirty";
                break;
            case Doctrine_Record::STATE_TDIRTY:
                return "transient dirty";
                break;
            case Doctrine_Record::STATE_TCLEAN:
                return "transient clean";
                break;
        }
    }

    /**
     * Dumps a record.
     *
     * This method returns an html representation of a given
     * record, containing keys, state and data.
     *
     * @param Doctrine_Record $record
     * @return string
     */
    public static function getRecordAsString(Doctrine_Record $record)
    {
        $r[] = '<pre>';
        $r[] = 'Component  : ' . $record->getTable()->getComponentName();
        $r[] = 'ID         : ' . Doctrine_Core::dump($record->identifier());
        $r[] = 'References : ' . count($record->getReferences());
        $r[] = 'State      : ' . Doctrine_Lib::getRecordStateAsString($record->state());
        $r[] = 'OID        : ' . $record->getOID();
        $r[] = 'data       : ' . Doctrine_Core::dump($record->getData(), false);
        $r[] = '</pre>';

        return implode("\n",$r)."<br />";
    }

    /**
     * Generates a human readable representation of a connection's state.
     *
     * This method translates a Doctrine_Connection state (integer constant)
     * in a english description.
     * @see Doctrine_Transaction::STATE_* constants
     * @param integer $state    state of the connection as a string
     * @return string
     */
    public static function getConnectionStateAsString($state)
    {
        switch ($state) {
            case Doctrine_Transaction::STATE_SLEEP:
                return "open";
                break;
            case Doctrine_Transaction::STATE_BUSY:
                return "busy";
                break;
            case Doctrine_Transaction::STATE_ACTIVE:
                return "active";
                break;
        }
    }

    /**
     * Generates a string representation of a connection.
     *
     * This method returns an html dump of a connection, containing state, open
     * transactions and loaded tables.
     *
     * @param Doctrine_Connection $connection
     * @return string
     */
    public static function getConnectionAsString(Doctrine_Connection $connection)
    {
        $r[] = '<pre>';
        $r[] = 'Doctrine_Connection object';
        $r[] = 'State               : ' . Doctrine_Lib::getConnectionStateAsString($connection->transaction->getState());
        $r[] = 'Open Transactions   : ' . $connection->transaction->getTransactionLevel();
        $r[] = 'Table in memory     : ' . $connection->count();
        $r[] = 'Driver name         : ' . $connection->getAttribute(Doctrine_Core::ATTR_DRIVER_NAME);
        $r[] = "</pre>";

        return implode("\n",$r)."<br>";
    }

    /**
     * Generates a string representation of a table.
     *
     * This method returns an html dump of a table, containing component name
     * and table physical name.
     * @param Doctrine_Table $table
     * @return string
     */
    public static function getTableAsString(Doctrine_Table $table)
    {
        $r[] = "<pre>";
        $r[] = "Component   : ".$table->getComponentName();
        $r[] = "Table       : ".$table->getTableName();
        $r[] = "</pre>";

        return implode("\n",$r)."<br>";
    }

    /**
     * Generates a colored sql query.
     *
     * This methods parses a plain text query and generates the html needed
     * for visual formatting.
     *
     * @todo: What about creating a config varialbe for the color?
     * @param string $sql   plain text query
     * @return string       the formatted sql code
     */
    public static function formatSql($sql)
    {
        $e = explode("\n",$sql);
        $color = "367FAC";
        $l = $sql;
        $l = str_replace("SELECT ", "<font color='$color'><b>SELECT </b></font><br \>  ",$l);
        $l = str_replace("FROM ", "<font color='$color'><b>FROM </b></font><br \>",$l);
        $l = str_replace(" LEFT JOIN ", "<br \><font color='$color'><b> LEFT JOIN </b></font>",$l);
        $l = str_replace(" INNER JOIN ", "<br \><font color='$color'><b> INNER JOIN </b></font>",$l);
        $l = str_replace(" WHERE ", "<br \><font color='$color'><b> WHERE </b></font>",$l);
        $l = str_replace(" GROUP BY ", "<br \><font color='$color'><b> GROUP BY </b></font>",$l);
        $l = str_replace(" HAVING ", "<br \><font color='$color'><b> HAVING </b></font>",$l);
        $l = str_replace(" AS ", "<font color='$color'><b> AS </b></font><br \>  ",$l);
        $l = str_replace(" ON ", "<font color='$color'><b> ON </b></font>",$l);
        $l = str_replace(" ORDER BY ", "<font color='$color'><b> ORDER BY </b></font><br \>",$l);
        $l = str_replace(" LIMIT ", "<font color='$color'><b> LIMIT </b></font><br \>",$l);
        $l = str_replace(" OFFSET ", "<font color='$color'><b> OFFSET </b></font><br \>",$l);
        $l = str_replace("  ", "<dd>",$l);

        return $l;
    }

    /**
     * Generates a string representation of a collection.
     *
     * This method returns an html dump of a collection of records, containing
     * all data.
     *
     * @param Doctrine_Collection $collection
     * @return string
     */
    public static function getCollectionAsString(Doctrine_Collection $collection)
    {
        $r[] = "<pre>";
        $r[] = get_class($collection);
        $r[] = 'data : ' . Doctrine_Core::dump($collection->getData(), false);
        //$r[] = 'snapshot : ' . Doctrine_Core::dump($collection->getSnapshot());
        $r[] = "</pre>";

        return implode("\n",$r);
    }

    // Code from symfony sfToolkit class. See LICENSE
    // code from php at moechofe dot com (array_merge comment on php.net)
    /*
     * arrayDeepMerge
     *
     * array arrayDeepMerge ( array array1 [, array array2 [, array ...]] )
     *
     * Like array_merge
     *
     *  arrayDeepMerge() merges the elements of one or more arrays together so
     * that the values of one are appended to the end of the previous one. It
     * returns the resulting array.
     *  If the input arrays have the same string keys, then the later value for
     * that key will overwrite the previous one. If, however, the arrays contain
     * numeric keys, the later value will not overwrite the original value, but
     * will be appended.
     *  If only one array is given and the array is numerically indexed, the keys
     * get reindexed in a continuous way.
     *
     * Different from array_merge
     *  If string keys have arrays for values, these arrays will merge recursively.
     */
     public static function arrayDeepMerge()
     {
         switch (func_num_args()) {
             case 0:
                return false;
             case 1:
                return func_get_arg(0);
             case 2:
                $args = func_get_args();
                $args[2] = array();

                if (is_array($args[0]) && is_array($args[1]))
                {
                    foreach (array_unique(array_merge(array_keys($args[0]),array_keys($args[1]))) as $key)
                    {
                        $isKey0 = array_key_exists($key, $args[0]);
                        $isKey1 = array_key_exists($key, $args[1]);

                        if ($isKey0 && $isKey1 && is_array($args[0][$key]) && is_array($args[1][$key]))
                        {
                            $args[2][$key] = self::arrayDeepMerge($args[0][$key], $args[1][$key]);
                        } else if ($isKey0 && $isKey1) {
                            $args[2][$key] = $args[1][$key];
                        } else if ( ! $isKey1) {
                            $args[2][$key] = $args[0][$key];
                        } else if ( ! $isKey0) {
                            $args[2][$key] = $args[1][$key];
                        }
                    }

                    return $args[2];
                } else {
                    return $args[1];
                }
            default:
                $args = func_get_args();
                $args[1] = self::arrayDeepMerge($args[0], $args[1]);
                array_shift($args);

                return call_user_func_array(array('Doctrine_Lib', 'arrayDeepMerge'), $args);
            break;
        }
    }

    /**
     * arrayDiffSimple
     *
     * array arrayDiffSimple ( array array1 , array array2 )
     *
     * Like array_diff
     *
     * arrayDiffSimple() has exactly the same behavior than array_diff, but can handle
     * only 2 arrays. PHP versions > 5.4.0 generate some NOTICE if you use array_diff
     * sometimes because of array_diff internal behavior with (string) casts.
     * This method solves the problem.
     *
     * Code from symfony sfToolkit class. See LICENSE
     * code from cto at verylastroom dot com
     *
     * @param array $array1
     * @param array $array2
     * @static
     * @access public
     * @return array
     */
    public static function arrayDiffSimple($array1, $array2)
    {
        $diff = array();

        foreach($array1 as $key => $val) {
            if(!isset($array2[$key])) {
                $diff[$key] = $val;
            } else {
                if(is_array($array2[$key]) && !is_array($val)) {
                    $diff[$key] = $val;
                }
            }
        }

        return $diff;
    }

    /**
     * Makes the directories for a path recursively.
     *
     * This method creates a given path issuing mkdir commands for all folders
     * that do not exist yet. Equivalent to 'mkdir -p'.
     *
     * @param string $path
     * @param integer $mode     an integer (octal) chmod parameter for the
     *                          created directories
     * @return boolean  true if succeeded
     */
    public static function makeDirectories($path, $mode = 0777)
    {
        if ( ! $path) {
          return false;
        }

        if (is_dir($path) || is_file($path)) {
          return true;
        }

        return mkdir(trim($path), $mode, true);
    }

    /**
     * Removes a non empty directory.
     *
     * This method recursively removes a directory and all its descendants.
     * Equivalent to 'rm -rf'.
     *
     * @param string $folderPath
     * @return boolean  success of the operation
     */
    public static function removeDirectories($folderPath)
    {
        if (is_dir($folderPath))
        {
            foreach (scandir($folderPath) as $value)
            {
                if ($value != '.' && $value != '..')
                {
                    $value = $folderPath . "/" . $value;

                    if (is_dir($value)) {
                        self::removeDirectories($value);
                    } else if (is_file($value)) {
                        unlink($value);
                    }
                }
            }

            return rmdir($folderPath);
        } else {
            return false;
        }
    }

    /**
     * Copy all directory content in another one.
     *
     * This method recursively copies all $source files and subdirs in $dest.
     * If $source is a file, only it will be copied in $dest.
     *
     * @param string $source    a directory path
     * @param string $dest      a directory path
     * @return
     */
    public static function copyDirectory($source, $dest)
    {
        // Simple copy for a file
        if (is_file($source)) {
            return copy($source, $dest);
        }

        // Make destination directory
        if ( ! is_dir($dest)) {
            mkdir($dest);
        }

        // Loop through the folder
        $dir = dir($source);
        while (false !== $entry = $dir->read()) {
            // Skip pointers
            if ($entry == '.' || $entry == '..') {
                continue;
            }

            // Deep copy directories
            if ($dest !== "$source/$entry") {
                self::copyDirectory("$source/$entry", "$dest/$entry");
            }
        }

        // Clean up
        $dir->close();

        return true;
    }

    /**
     * Checks for a valid class name for Doctrine coding standards.
     *
     * This methods tests if $className is a valid class name for php syntax
     * and for Doctrine coding standards. $className must use camel case naming
     * and underscores for directory separation.
     *
     * @param string $classname
     * @return boolean
     */
    public static function isValidClassName($className)
    {
        if (preg_match('~(^[a-z])|(_[a-z])|([\W])|(_{2})~', $className)) {
            return false;
        }

        return true;
    }
}
