<?php
/**
 * Pimcore
 *
 * This source file is subject to the GNU General Public License version 3 (GPLv3)
 * For the full copyright and license information, please view the LICENSE.md and gpl-3.0.txt
 * files that are distributed with this source code.
 *
 * @category   Pimcore
 * @package    Element
 * @copyright  Copyright (c) 2009-2016 pimcore GmbH (http://www.pimcore.org)
 * @license    http://www.pimcore.org/license     GNU General Public License version 3 (GPLv3)
 */

namespace Pimcore\Model\Element;

use Pimcore\Model;
use Pimcore\Model\Document;
use Pimcore\Model\Asset;
use Pimcore\Model\Object;
use Pimcore\Model\Dependency;
use Pimcore\File;

class Service extends Model\AbstractModel {

    /**
     * @static
     * @param  $element
     * @return string
     */
    public static function getIdPath($element) {

        $path = "";

        if ($element instanceof ElementInterface) {
            $elementType = self::getElementType($element);
            $nid = $element->getParentId();
            $ne = self::getElementById($elementType, $nid);
        }

        if ($ne) {
            $path = self::getIdPath($ne, $path);
        }

        if ($element) {
            $path = $path . "/" . $element->getId();
        }

        return $path;
    }

    /**
     * @static
     * @param  $list array | \Pimcore\Model\Listing\AbstractListing
     * @return array
     */
    public static function getIdList($list,$idGetter = 'getId'){
       $ids = array();
       if(is_array($list)){
           foreach($list as $entry){
               if(is_object($entry) && method_exists($entry,$idGetter)){
                   $ids[] = $entry->$idGetter();
               }elseif(is_scalar($entry)){
                   $ids[] = $entry;
               }
           }
       }

       if($list instanceof \Pimcore\Model\Listing\AbstractListing){
               $ids = $list->loadIdList();
       }
        $ids = array_unique($ids);
       return $ids;
    }

    /**
     * @param Dependency $d
     * @return array
     */
    public static function getRequiredByDependenciesForFrontend(Dependency $d)
    {
        $dependencies["hasHidden"] = false;
        $dependencies["requiredBy"] = array();

        // requiredBy
        foreach ($d->getRequiredBy() as $r) {
            if ($e = self::getDependedElement($r)) {
                if ($e->isAllowed("list")) {
                    $dependencies["requiredBy"][] = self::getDependencyForFrontend($e);
                } else {
                    $dependencies["hasHidden"] = true;
                }
            }
        }
        return $dependencies;
    }

    /**
     * @param Dependency $d
     * @return array
     */
    public static function getRequiresDependenciesForFrontend(Dependency $d)
    {
        $dependencies["hasHidden"] = false;
        $dependencies["requires"] = array();

        // requires
        foreach ($d->getRequires() as $r) {
            if ($e = self::getDependedElement($r)) {
                if ($e->isAllowed("list")) {
                    $dependencies["requires"][] = self::getDependencyForFrontend($e);
                } else {
                    $dependencies["hasHidden"] = true;
                }

            }
        }

        return $dependencies;
    }

    /**
     * @param Document|Asset|Object\AbstractObject $element
     * @return array
     */
    public static function getDependencyForFrontend($element)
    {
        if ($element instanceof ElementInterface) {
            return array(
                "id" => $element->getId(),
                "path" => $element->getFullPath(),
                "type" => self::getElementType($element),
                "subtype" => $element->getType()
            );
        }
    }

    /**
     * @param array $config
     * @return Object\AbstractObject|Document|Asset
     */
    public static function getDependedElement($config)
    {

        if ($config["type"] == "object") {
            return Object::getById($config["id"]);
        }
        else if ($config["type"] == "asset") {
            return Asset::getById($config["id"]);
        }
        else if ($config["type"] == "document") {
            return Document::getById($config["id"]);
        }

        return false;
    }


    /**
     * determines whether an element is published
     *
     * @static
     * @param  ElementInterface $element
     * @return bool
     */
    public static function isPublished($element = null)
    {

        if ($element instanceof ElementInterface) {
            if (method_exists($element, "isPublished")) {
                return $element->isPublished();
            }
            else {
                return true;
            }
        }
        return false;
    }

    /**
     * @static
     * @param  string $type
     * @param  string $path
     * @return ElementInterface
     */
    public static function getElementByPath($type, $path)
    {
        if ($type == "asset") {
            $element = Asset::getByPath($path);
        } else if ($type == "object") {
            $element = Object::getByPath($path);
        } else if ($type == "document") {
            $element = Document::getByPath($path);
        }
        return $element;
    }


    /**
     * Returns a uniqe key for the element in the $target-Path (recursive)
     * @static
     * @return ElementInterface|string
     * @param string $type
     * @param string $sourceKey
     * @param ElementInterface $target
     */
    public static function getSaveCopyName($type, $sourceKey, $target)
    {
        if (self::pathExists($target->getFullPath() . "/" . $sourceKey, $type)) {
            // only for assets: add the prefix _copy before the file extension (if exist) not after to that source.jpg will be source_copy.jpg and not source.jpg_copy
            if($type == "asset" && $fileExtension = File::getFileExtension($sourceKey)) {
                $sourceKey = str_replace("." . $fileExtension, "_copy." . $fileExtension, $sourceKey);
            } else {
                $sourceKey .= "_copy";
            }

            return self::getSaveCopyName($type, $sourceKey, $target);
        }
        return $sourceKey;
    }

    /**
     * @static
     * @param $type
     * @param $path
     * @return bool
     */
    public static function pathExists ($path, $type = null) {
        if($type == "asset") {
            return Asset\Service::pathExists($path);
        } else if ($type == "document") {
            return Document\Service::pathExists($path);
        } else if ($type == "object") {
            return Object\Service::pathExists($path);
        }

        return;
    }


    /**
     * @static
     * @param  string $type
     * @param  int $id
     * @return ElementInterface
     */
    public static function getElementById($type, $id)
    {
        $element = null;
        if ($type == "asset") {
            $element = Asset::getById($id);
        } else if ($type == "object") {
            $element = Object::getById($id);
        } else if ($type == "document") {
            $element = Document::getById($id);
        }
        return $element;
    }

    /**
     * @static
     * @param  ElementInterface $element $element
     * @return string
     */
    public static function getElementType($element)
    {
        $type = null;
        if ($element instanceof Object\AbstractObject) {
            $type = "object";
        } else if ($element instanceof Document) {
            $type = "document";
        } else if ($element instanceof Asset) {
            $type = "asset";
        }
        return $type;
    }

    /**
     * determines the type of an element (object,asset,document)
     *
     * @static
     * @param  ElementInterface $element
     * @return string
     */
    public static function getType($element)
    {
        return self::getElementType($element);
    }

    /**
     * Schedules element with this id for sanity check to be cleaned of broken relations
     *
     * @static
     * @param  ElementInterface $element
     * @return void
     */
    public static function scheduleForSanityCheck($element)
    {

        $type = self::getElementType($element);
        $sanityCheck = new Sanitycheck($element->getId(), $type);
        $sanityCheck->save();


    }

    /**
     *
     */
    public static function runSanityCheck() {

        $sanityCheck = Sanitycheck::getNext();
        while ($sanityCheck) {

            $element = self::getElementById($sanityCheck->getType(), $sanityCheck->getId());
            if ($element) {
                try {
                    self::performSanityCheck($element);
                } catch (\Exception $e) {
                    \Logger::error("Element\\Service: sanity check for element with id [ " . $element->getId() . " ] and type [ " . self::getType($element) . " ] failed");
                }
                $sanityCheck->delete();
            } else {
                $sanityCheck->delete();
            }
            $sanityCheck = Sanitycheck::getNext();

            // reduce load on server
            \Logger::debug("Now timeout for 3 seconds");
            sleep(3);
        }
    }

    /**
     * @static
     * @param  ElementInterface $element
     * @return void
     */
    protected static function performSanityCheck($element)
    {
        if($latestVersion = $element->getLatestVersion()) {
            if($latestVersion->getDate() > $element->getModificationDate()) {
                return;
            }
        }

        $element->setUserModification(0);
        $element->save();

        if($version = $element->getLatestVersion(true)) {
            $version->setNote("Sanitycheck");
            $version->save();
        }
    }


    /**
     * @static
     * @param  $props
     * @return array
     */
    public static function minimizePropertiesForEditmode($props)
    {

        $properties = array();
        foreach ($props as $key => $p) {

            //$p = object2array($p);
            $allowedProperties = array(
                "key",
                "o_key",
                "filename",
                "path",
                "o_path",
                "id",
                "o_id",
                "o_type",
                "type"
            );

            if ($p->getData() instanceof Document || $p->getData() instanceof Asset || $p->getData() instanceof Object\AbstractObject) {

                $pa = array();

                $vars = get_object_vars($p->getData());

                foreach ($vars as $k => $value) {
                    if (in_array($k, $allowedProperties)) {
                        $pa[$k] = $p->getData()->$k;
                    }
                }

                // clone it because of caching
                $tmp = clone $p;
                $tmp->setData($pa);
                $properties[$key] = object2array($tmp);
            }
            else {
                $properties[$key] = object2array($p);
            }

            // add config from predefined properties
            if ($p->getName() && $p->getType()) {
                $predefined = Model\Property\Predefined::getByKey($p->getName());

                if ($predefined && $predefined->getType() == $p->getType()) {
                    $properties[$key]["config"] = $predefined->getConfig();
                }
            }
        }

        return $properties;
    }


    /**
     * @param  ElementInterface $target the parent element
     * @param  ElementInterface $new the newly inserted child
     * @return void
     */
    protected function updateChilds($target, $new)
    {

        if (is_array($target->getChilds())) {
            //check in case of recursion
            $found = false;
            foreach ($target->getChilds() as $child) {
                if ($child->getId() == $new->getId()) {
                    $found = true;
                }
            }
            if (!$found) {
                $target->setChilds(array_merge($target->getChilds(), array($new)));
            }
        } else {
            $target->setChilds(array($new));
        }


    }

    /**
     * @param  ElementInterface $element
     * @return array
     */
    public static function gridElementData(ElementInterface $element)
    {
        $data = array(
            "id" => $element->getId(),
            "fullpath" => $element->getFullPath(),
            "type" => self::getType($element),
            "subtype" => $element->getType(),
            "filename" => self::getFilename($element),
            "creationDate" => $element->getCreationDate(),
            "modificationDate" => $element->getModificationDate()
        );

        if (method_exists($element, "isPublished")) {
            $data["published"] = $element->isPublished();
        } else {
            $data["published"] = true;
        }
        return $data;
    }


    /**
     * @param ElementInterface $element
     * @return string
     */
    public static function getFilename(ElementInterface $element)
    {
        if ($element instanceof Document || $element instanceof Object\AbstractObject) {
            return $element->getKey();
        } else if ($element instanceof Asset) {
            return $element->getFilename();
        }
    }

    /**
     * find all elements which the user may not list and therefore may never be shown to the user
     * @param  string $type asset|object|document
     * @return array
     */
    public static function findForbiddenPaths($type, $user)
    {
        if($user->isAdmin()) {
            return array();
        }

        // get workspaces
        $workspaces = $user->{"getWorkspaces".ucfirst($type)}();
        foreach ($user->getRoles() as $roleId) {
            $role = Model\User\Role::getById($roleId);
            $workspaces = array_merge($workspaces, $role->{"getWorkspaces".ucfirst($type)}());
        }

        $forbidden = array();
        if(count($workspaces) > 0) {
            foreach ($workspaces as $workspace) {
                if(!$workspace->getList()) {
                    $forbidden[] = $workspace->getCpath();
                }
            }
        } else {
            $forbidden[] = "/";
        }

        return $forbidden;
    }

    /**
     * renews all references, for example after unserializing an ElementInterface
     * @param  Document|Asset|Object\AbstractObject $data
     * @return mixed
     */
    public static function renewReferences($data, $initial = true)
    {
        if (is_array($data)) {
            foreach ($data as &$value) {
                $value = self::renewReferences($value, false);
            }
            return $data;
        } else if (is_object($data)) {
            if ($data instanceof ElementInterface && !$initial) {
                return self::getElementById(self::getElementType($data), $data->getId());
            } else {

                // if this is the initial element set the correct path and key
                if ($data instanceof ElementInterface && $initial) {

                    $originalElement = self::getElementById(self::getElementType($data), $data->getId());

                    if ($originalElement) {
                        if ($data instanceof Asset) {
                            $data->setFilename($originalElement->getFilename());
                        } else if ($data instanceof Document) {
                            $data->setKey($originalElement->getKey());
                        } else if ($data instanceof Object\AbstractObject) {
                            $data->setKey($originalElement->getKey());
                        }

                        if (!Object\AbstractObject::doNotRestoreKeyAndPath()) {
                            $data->setPath($originalElement->getPath());
                        }
                    }
                }

                $properties = get_object_vars($data);
                foreach ($properties as $name => $value) {
                    $data->$name = self::renewReferences($value, false);
                }
                return $data;
            }
        }
        return $data;
    }

    /**
     * @static
     * @param string $path
     * @return string
     */
    public static function correctPath ($path) {
        // remove trailing slash
        if($path != "/") {
            $path = rtrim($path,"/ ");
        }

        // correct wrong path (root-node problem)
        $path = str_replace("//", "/", $path);

        return $path;
    }

    /**
     * @static
     * @param ElementInterface $element
     * @return ElementInterface
     */
    public static function loadAllFields (ElementInterface $element) {

        if($element instanceof Document) {
            Document\Service::loadAllDocumentFields($element);
        } else if ($element instanceof Object\Concrete) {
            Object\Service::loadAllObjectFields($element);
        } else if ($element instanceof Asset) {
            Asset\Service::loadAllFields($element);
        }

        return $element;
    }

    /**
     * clean up broken views which were generated by localized fields, ....
     * when removing a language the view isn't valid anymore
     */
    public function cleanupBrokenViews () {

        $this->getDao()->cleanupBrokenViews();
    }

    /** Callback for array_filter function.
     * @param $var value
     * @return bool true if value is accepted
     */
    private static function filterNullValues($var) {
        return strlen($var) > 0;
    }

    /**
     * @param $path
     * @param array $options
     * @return null
     * @throws \Exception
     */
    public static function createFolderByPath($path,$options = array()) {
        $calledClass = get_called_class();
        if($calledClass == __CLASS__){
            throw new \Exception("This method must be called from a extended class. e.g Asset\\Service, Object\\Service, Document\\Service");
        }

        $type = str_replace('\Service','',$calledClass);
        $type = "\\" . ltrim($type, "\\");
        $folderType = $type . '\Folder';

        $lastFolder = null;
        $pathsArray = array();
        $parts = explode('/', $path);
        $parts = array_filter($parts, "\\Pimcore\\Model\\Element\\Service::filterNullValues");

        $sanitizedPath = "/";
        foreach($parts as $part) {
            $sanitizedPath = $sanitizedPath . File::getValidFilename($part) . "/";
        }

        if (!($foundElement = $type::getByPath($sanitizedPath))) {

            foreach ($parts as $part) {
                $pathsArray[] = $pathsArray[count($pathsArray) - 1] . '/' . File::getValidFilename($part);
            }

            for ($i = 0; $i < count($pathsArray); $i++) {
                $currentPath = $pathsArray[$i];
                if (!($type::getByPath($currentPath) instanceof $type)) {
                    $parentFolderPath = ($i ==0) ? '/' : $pathsArray[$i - 1];

                    $parentFolder = $type::getByPath($parentFolderPath);

                    $folder = new $folderType();
                    $folder->setParent($parentFolder);
                    if ($parentFolder) {
                        $folder->setParentId($parentFolder->getId());
                    } else {
                        $folder->setParentId(1);
                    }

                    $key = substr($currentPath, strrpos($currentPath, '/') + 1, strlen($currentPath));

                    if (method_exists($folder, 'setKey')) {
                        $folder->setKey($key);
                    }

                    if (method_exists($folder, 'setFilename')) {
                        $folder->setFilename($key);
                    }

                    if (method_exists($folder, 'setType')) {
                        $folder->setType('folder');
                    }

                    $folder->setPath($currentPath);
                    $folder->setUserModification(0);
                    $folder->setUserOwner(1);
                    $folder->setCreationDate(time());
                    $folder->setModificationDate(time());
                    $folder->setValues($options);
                    $folder->save();
                    $lastFolder = $folder;
                }
            }
        } else {
            return $foundElement;
        }
        return $lastFolder;
    }
}