<?php
/**
 * Shopware 4.0
 * Copyright © 2012 shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 *
 * @category   Shopware
 * @package    Shopware_Controllers
 * @subpackage Article
 * @copyright  Copyright (c) 2012, shopware AG (http://www.shopware.de)
 * @version    $Id$
 * @author     Heiner Lohaus
 * @author     $Author$
 */

/**
 * Shopware Backend Controller
 *
 * todo@all: Documentation
 */
class Shopware_Controllers_Backend_Article extends Shopware_Controllers_Backend_ExtJs
{
    /**
     * Repository for the article model.
     * @var \Shopware\Models\Article\Repository
     */
    protected $repository = null;
    /**
     * Repository for the shop model
     * @var \Shopware\Models\Shop\Repository
     */
    protected $shopRepository = null;
    /**
     * Repository for the customer model
     * @var \Shopware\Models\Customer\Repository
     */
    protected $customerRepository = null;
    /**
     * Repository for the category model
     * @var \Shopware\Models\Category\Repository
     */
    protected $categoryRepository = null;
    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $articleDetailRepository = null;
    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $customerGroupRepository = null;
    /**
     * Entity Manager
     * @var null
     */
    protected $manager = null;

    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $configuratorDependencyRepository = null;

    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $configuratorPriceSurchargeRepository = null;

    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $configuratorGroupRepository = null;

    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $configuratorOptionRepository = null;


    /**
     * @var \Shopware\Components\Model\ModelRepository
     */
    protected $configuratorSetRepository = null;

	public function initAcl(){
		$this->addAclPermission("loadStores","read","Insufficient Permissions");
		$this->addAclPermission("duplicateArticle","save","Insufficient Permissions");
		$this->addAclPermission("save","save","Insufficient Permissions");
		$this->addAclPermission("delete","delete","Insufficient Permissions");
	}
    /**
     * Disable template engine for all actions
     */
    public function preDispatch()
    {
        if(!in_array($this->Request()->getActionName(), array('index', 'load', 'validateNumber'))) {
            $this->Front()->Plugins()->Json()->setRenderer();
        } else {
            //$this->View()->setCaching();
        }

        //if the shopware cache was cleared, the browser cache will be cleared too.
        //if((($noCache = $this->Request()->getParam('no-cache'))
        //  && $this->View()->Template()->cached->timestamp < $noCache)
        //  || (($cacheControl = $this->Request()->getHeader('Cache-Control')) !== null
        //  && strpos($cacheControl, 'no-cache') !== false)) {
        //    $this->View()->Template()->cached->timestamp = $noCache;
        //    $this->View()->Template()->cached->valid = false;
        //}
    }

    /**
     * Internal helper function to get access to the entity manager.
     * @return Shopware\Components\Model\ModelManager
     */
    protected function getManager() {
        if ($this->manager === null) {
            $this->manager= Shopware()->Models();
        }
        return $this->manager;
    }

    /**
     * Internal helper function to get access to the article repository.
     *
     * @return Shopware\Models\Article\Repository
     */
    protected function getRepository()
    {
        if ($this->repository === null) {
            $this->repository = Shopware()->Models()->getRepository('Shopware\Models\Article\Article');
        }
        return $this->repository;
    }

    /**
     * Helper function to get access to the customerGroup repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getCustomerGroupRepository() {
    	if ($this->customerGroupRepository === null) {
    		$this->customerGroupRepository = Shopware()->Models()->getRepository('Shopware\Models\Customer\Group');
    	}
    	return $this->customerGroupRepository;
    }

    /**
     * Helper function to get access to the articleDetail repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getArticleDetailRepository() {
    	if ($this->articleDetailRepository === null) {
    		$this->articleDetailRepository = Shopware()->Models()->getRepository('Shopware\Models\Article\Detail');
    	}
    	return $this->articleDetailRepository;
    }

    /**
     * Internal helper function to get access to the article repository.
     *
     * @return Shopware\Models\Article\Repository
     */
    protected function getCustomerRepository()
    {
        if ($this->customerRepository === null) {
            $this->customerRepository = Shopware()->Models()->getRepository('Shopware\Models\Customer\Customer');
        }
        return $this->customerRepository;
    }

    /**
     * Internal helper function to get access on the shop repository.
     * @return null|Shopware\Models\Shop\Repository
     */
    protected function getShopRepository() {
        if ($this->shopRepository === null) {
            $this->shopRepository = Shopware()->Models()->getRepository('Shopware\Models\Shop\Shop');
        }

        return $this->shopRepository;
    }

    /**
     * Internal helper function to get access on the category repository.
     * @return null|Shopware\Models\Category\Repository
     */
    protected function getCategoryRepository() {
        if ($this->categoryRepository === null) {
            $this->categoryRepository = Shopware()->Models()->getRepository('Shopware\Models\Category\Category');
        }
        return $this->categoryRepository;
    }

    /**
     * Helper function to get access to the configuratorPriceSurcharge repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getConfiguratorPriceSurchargeRepository() {
    	if ($this->configuratorPriceSurchargeRepository === null) {
    		$this->configuratorPriceSurchargeRepository = Shopware()->Models()->getRepository('Shopware\Models\Article\Configurator\PriceSurcharge');
    	}
    	return $this->configuratorPriceSurchargeRepository;
    }

    /**
     * Helper function to get access to the ConfiguratorDependency repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getConfiguratorDependencyRepository() {
    	if ($this->configuratorDependencyRepository === null) {
    		$this->configuratorDependencyRepository = Shopware()->Models()->getRepository('Shopware\Models\Article\Configurator\Dependency');
    	}
    	return $this->configuratorDependencyRepository;
    }

    /**
     * Helper function to get access to the configuratorGroup repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getConfiguratorGroupRepository() {
    	if ($this->configuratorGroupRepository === null) {
    		$this->configuratorGroupRepository = Shopware()->Models()->getRepository('Shopware\Models\Article\Configurator\Group');
    	}
    	return $this->configuratorGroupRepository;
    }
    /**
     * Helper function to get access to the configuratorOption repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getConfiguratorOptionRepository() {
    	if ($this->configuratorOptionRepository === null) {
    		$this->configuratorOptionRepository = Shopware()->Models()->getRepository('Shopware\Models\Article\Configurator\Option');
    	}
    	return $this->configuratorOptionRepository;
    }

    /**
     * Helper function to get access to the configuratorSet repository.
     * @return \Shopware\Components\Model\ModelRepository
     */
    protected function getConfiguratorSetRepository() {
    	if ($this->configuratorSetRepository === null) {
    		$this->configuratorSetRepository = Shopware()->Models()->getRepository('Shopware\Models\Article\Configurator\Set');
    	}
    	return $this->configuratorSetRepository;
    }

    /**
     * Event listener function of the article backend module. Fired when the user
     * edit or create an article and clicks the save button which displayed on bottom of the article
     * detail window.
     */
    public function saveAction()
    {
        $data = $this->Request()->getParams();
        if ($this->Request()->has('id')) {
            $article = $this->getRepository()->find((int) $this->Request()->getParam('id'));
        } else {
            $article = new \Shopware\Models\Article\Article();
        }
        $this->saveArticle($data, $article);
    }

    /**
     * Event listener function of the configurator set model in the article backend module.
     */
    public function saveConfiguratorSetAction()
    {
        try {
            $data = $this->Request()->getParams();
            $id = (int) $data['id'];
            $articleId = (int) $data['articleId'];

            if (!empty($articleId)) {
                $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $articleId);
                if ($article->getConfiguratorSet()->getId() !== $id) {
                    Shopware()->Models()->remove($article->getConfiguratorSet());
                    Shopware()->Models()->flush();
                }
            }

            if (!empty($id) && $id > 0) {
                $configuratorSet = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Set', $id);
            } else {
                $configuratorSet = new \Shopware\Models\Article\Configurator\Set();
            }
            if (!$configuratorSet) {
                $this->View()->assign(array(
                    'success' => false,
                    'noId' => true
                ));
                return;
            }

            $groups = array();
            foreach($data['groups'] as $groupData) {
                if (!empty($groupData['id']) && $groupData['active']) {
                    $group = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Group', $groupData['id']);
                    $group->setPosition($groupData['position']);
                    $groups[] = $group;
                }
            }
            $data['groups'] = $groups;

            $options = array();
            foreach($data['options'] as $optionData) {
                if (!empty($optionData['id']) && $optionData['active']) {
                    $option = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Option', $optionData['id']);
                    $option->setPosition($optionData['position']);
                    $options[] = $option;
                }
            }
            $data['options'] = $options;
            if ($configuratorSet->getOptions()) {
                $configuratorSet->getOptions()->clear();
            }
            if ($configuratorSet->getGroups()) {
                $configuratorSet->getGroups()->clear();
            }
            $configuratorSet->fromArray($data);
            Shopware()->Models()->persist($configuratorSet);
            Shopware()->Models()->flush();

            if (!empty($articleId)) {
                /**@var $article \Shopware\Models\Article\Article*/
                $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $articleId);
                $article->setConfiguratorSet($configuratorSet);
                Shopware()->Models()->persist($article);
                Shopware()->Models()->flush();
            }

            $data = $this->getRepository()->getConfiguratorSetQuery($configuratorSet->getId())->getArrayResult();
            $this->View()->assign(array(
                'success' => true,
                'data' => $data
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the backend article module. Fired when the user want to accept the
     * variant data of the main detail to the selected variant(s).
     */
    public function acceptMainDataAction() {
        try {
            $data = $this->Request()->getParams();
            $articleId = (int) $data['articleId'];
            if (empty($articleId)) {
                $this->View()->assign(array('success' => false,'noId' => true));
                return;
            }

            /**@var $article \Shopware\Models\Article\Article*/
            $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $articleId);
            $mainDetail = $article->getMainDetail();
            $mainData = $this->getMappingData($mainDetail, $data);
            $variants = $this->getVariantsForMapping($articleId, $mainDetail, $data);
            if (!empty($variants)) {
                /**@var $variant \Shopware\Models\Article\Detail*/
                foreach($variants as $variant) {
                    $variant->fromArray($mainData);
                    Shopware()->Models()->persist($variant);
                }
                Shopware()->Models()->flush();
            }
            $this->View()->assign(array('success' => true));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Internal helper function to which returns all article variants which are not the main detail
     * or the backend variant.
     * @param $articleId
     * @param $mainDetail
     * @param $mapping
     * @return array
     */
    protected function getVariantsForMapping($articleId, $mainDetail, $mapping)
    {
        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->select(array('details'))
                ->from('Shopware\Models\Article\Detail', 'details')
                ->where('details.id != ?1')
                ->andWhere('details.articleId = ?2')
                ->setParameter(1, $mainDetail->getId())
                ->setParameter(2, $articleId);

        if (!empty($mapping['variants'])) {
            $ids = array();
            foreach($mapping['variants'] as $variant) {
                $ids[] = $variant['id'];
            }
            if (!empty($ids)) {
                $builder->andWhere('details.id IN (?3)')
                        ->setParameter(3, $ids);
            }
        }
        $variants =$builder->getQuery()->getResult();
        return $variants;
    }

    /**
     * Returns the main detail data for the variant mapping action.
     * @param $mainDetail \Shopware\Models\Article\Detail
     * @param $mapping
     * @return array
     */
    protected function getMappingData($mainDetail, $mapping)
    {
        $mainData = array();
        if ($mapping['settings']) {
            $mainData['supplierNumber'] = $mainDetail->getSupplierNumber();
            $mainData['weight'] = $mainDetail->getWeight();
            $mainData['inStock'] = $mainDetail->getInStock();
            $mainData['stockMin'] = $mainDetail->getStockMin();
            $mainData['ean'] = $mainDetail->getEan();
            $mainData['minPurchase'] = $mainDetail->getMinPurchase();
            $mainData['purchaseSteps'] = $mainDetail->getPurchaseSteps();
            $mainData['maxPurchase'] = $mainDetail->getMaxPurchase();
            $mainData['releaseDate'] = $mainDetail->getReleaseDate();
            $mainData['shippingTime'] = $mainDetail->getShippingTime();
            $mainData['shippingFree'] = $mainDetail->getShippingFree();
        }
        if ($mapping['attributes']) {
            $builder = Shopware()->Models()->createQueryBuilder();
            $mainData['attribute'] = $builder->select(array('attributes'))
                    ->from('Shopware\Models\Attribute\Article', 'attributes')
                    ->where('attributes.articleId = ?1')
                    ->andHaving('attributes.articleDetailId = :detailId')
                    ->setParameter(1, $mainDetail->getArticle()->getId())
                    ->setParameter('detailId', $mainDetail->getId())
                    ->setFirstResult(0)
                    ->setMaxResults(1)
                    ->getQuery()
                    ->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

            unset($mainData['attribute']['id']);
            unset($mainData['attribute']['articleId']);
            unset($mainData['attribute']['articleDetailId']);
            unset($mainData['attribute']['articleDetail']);
            $mainData['attribute']['article'] = $mainDetail->getArticle();
        }
        if ($mapping['prices']) {
            $builder = Shopware()->Models()->createQueryBuilder();
            $prices = $builder->select(array('prices', 'customerGroup'))
                              ->from('Shopware\Models\Article\Price', 'prices')
                              ->innerJoin('prices.customerGroup', 'customerGroup')
                              ->where('prices.articleDetailsId = ?1')
                              ->setParameter(1, $mainDetail->getId())
                              ->getQuery()
                              ->getArrayResult();

            foreach($prices as $key => $price) {
                unset($price['id']);
                $price['customerGroup'] = $this->getCustomerGroupRepository()->find($price['customerGroup']['id']);
                $price['article'] = $mainDetail->getArticle();
                $prices[$key] = $price;
            }
            $mainData['prices'] = $prices;
        }
        if ($mapping['basePrice']) {
            $mainData['unit'] = $mainDetail->getUnit();
            $mainData['purchaseUnit'] = $mainDetail->getPurchaseUnit();
            $mainData['referenceUnit'] = $mainDetail->getReferenceUnit();
            $mainData['packUnit'] = $mainDetail->getPackUnit();
        }
        return $mainData;
    }

    /**
     * Event listener function of the article backend module. Fired when the user clicks the "duplicate article" button
     * on the detail page to duplicate the whole article configuration for a new article.
     */
    public function duplicateArticleAction() {
        try {
            $articleId = $this->Request()->getParam('articleId', null);

            if (empty($articleId)) {
                $this->View()->assign(array(
                    'success' => false,
                    'noId' => true
                ));
            }

            /**
             * @var $article Shopware\Models\Article\Article
             */
            $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $articleId);
            if($article->getConfiguratorSet() !== null){
                $isConfigurator = true;
                $mailDetailId = $article->getMainDetail()->getId();
            }else{
                $isConfigurator = false;
                $mailDetailId = null;
            }


            $this->duplicateArticleData($articleId);
            $newArticleId = Shopware()->Db()->lastInsertId('s_articles');
            $this->duplicateArticleCategories($articleId, $newArticleId);
            $this->duplicateArticleCustomerGroups($articleId, $newArticleId);
            $this->duplicateArticleRelated($articleId, $newArticleId);
            $this->duplicateArticleSimilar($articleId, $newArticleId);
            $this->duplicateArticleDetails($articleId, $newArticleId, $mailDetailId);
            $this->duplicateArticleLinks($articleId, $newArticleId);
            $this->duplicateArticleImages($articleId, $newArticleId);
            $this->duplicateArticleProperties($articleId, $newArticleId);
            $this->duplicateArticleDownloads($articleId, $newArticleId);
            $setId = $this->duplicateArticleConfigurator($articleId, $newArticleId);

            $sql= "UPDATE s_articles, s_articles_details SET main_detail_id = s_articles_details.id
                    WHERE s_articles_details.articleID = s_articles.id
                    AND s_articles.id = ?
                    AND s_articles_details.kind = 1";
            Shopware()->Db()->query($sql, array($newArticleId));

            if ($setId !== null) {
                $sql= "UPDATE s_articles SET configurator_set_id = ?
                        WHERE s_articles.id = ?";
                Shopware()->Db()->query($sql, array($setId, $newArticleId));
            }


            $this->View()->assign(array(
                'success' => true,
                'articleId' => $newArticleId,
                'isConfigurator' => $isConfigurator
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Internal helper function which duplicates the article data of the s_articles.
     * @param $articleId
     */
    protected function duplicateArticleData($articleId)
    {
        $sql= "INSERT INTO s_articles
               SELECT NULL,
                   supplierID, CONCAT(name, '-', 'Copy'), description, description_long, shippingtime, datum, active, taxID, pseudosales, topseller, keywords, changetime, pricegroupID, pricegroupActive, filtergroupID, laststock, crossbundlelook, notification, template, mode, NULL, available_from, available_to, NULL
               FROM s_articles as source
               WHERE source.id = ?";
        Shopware()->Db()->query($sql, array($articleId));
    }

    /**
     * Internal helper function which duplicates the assigned categories of the article to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleCategories($articleId, $newArticleId)
    {
        $sql= "INSERT INTO s_articles_categories
               SELECT NULL, ?, categoryID
               FROM s_articles_categories as source
               WHERE source.articleID = ?
        ";
        Shopware()->Db()->query($sql, array($newArticleId, $articleId));
    }

    /**
     * Internal helper function to duplicate the avoid customer group configuration from the passed article
     * id to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleCustomerGroups($articleId, $newArticleId)
    {
        $sql= "INSERT INTO s_articles_avoid_customergroups
               SELECT ?, customergroupID
               FROM s_articles_avoid_customergroups as source
               WHERE source.articleID = ?
        ";
        Shopware()->Db()->query($sql, array($newArticleId, $articleId));
    }

    /**
     * Internal helper function to duplicate the related article configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleRelated($articleId, $newArticleId)
    {
        $sql= "INSERT INTO s_articles_relationships
               SELECT NULL, ?, relatedarticle
               FROM s_articles_relationships as source
               WHERE source.articleID = ?
        ";
        Shopware()->Db()->query($sql, array($newArticleId, $articleId));
    }

    /**
     * Internal helper function to duplicate the similar article configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleSimilar($articleId, $newArticleId)
    {
        $sql= "INSERT INTO s_articles_similar
               SELECT NULL, ?, relatedarticle
               FROM s_articles_similar as source
               WHERE source.articleID = ?
        ";
        Shopware()->Db()->query($sql, array($newArticleId, $articleId));
    }

    /**
     * Internal helper function to duplicate the article link configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleLinks($articleId, $newArticleId)
    {
        $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $newArticleId);

        $builder = Shopware()->Models()->createQueryBuilder();
        $links = $builder->select(array('links', 'attribute'))
                ->from('Shopware\Models\Article\Link', 'links')
                ->leftJoin('links.attribute', 'attribute')
                ->where('links.articleId = ?1')
                ->setParameter(1, $articleId)
                ->getQuery()
                ->getArrayResult();

        foreach($links as $data) {
            $link = new \Shopware\Models\Article\Link();
            $link->fromArray($data);
            $link->setArticle($article);
            Shopware()->Models()->persist($link);
        }
        Shopware()->Models()->flush();
    }

    /**
     * Internal helper function to duplicate the download configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleDownloads($articleId, $newArticleId)
    {
        $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $newArticleId);

        $builder = Shopware()->Models()->createQueryBuilder();
        $downloads = $builder->select(array('downloads', 'attribute'))
                ->from('Shopware\Models\Article\Download', 'downloads')
                ->leftJoin('downloads.attribute', 'attribute')
                ->where('downloads.articleId = ?1')
                ->setParameter(1, $articleId)
                ->getQuery()
                ->getArrayResult();

        foreach($downloads as $data) {
            $download = new \Shopware\Models\Article\Download();
            $download->fromArray($data);
            $download->setArticle($article);
            Shopware()->Models()->persist($download);
        }
        Shopware()->Models()->flush();
    }

    /**
     * Internal helper function to duplicate the image configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleImages($articleId, $newArticleId)
    {
        $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $newArticleId);

        $builder = Shopware()->Models()->createQueryBuilder();
        $images = $builder->select(array('images', 'media', 'attribute', 'mappings', 'rules', 'option'))
                ->from('Shopware\Models\Article\Image', 'images')
                ->leftJoin('images.attribute', 'attribute')
                ->leftJoin('images.mappings', 'mappings')
                ->leftJoin('images.media', 'media')
                ->leftJoin('mappings.rules', 'rules')
                ->leftJoin('rules.option', 'option')
                ->where('images.articleId = ?1')
                ->andWhere('images.parentId IS NULL')
                ->setParameter(1, $articleId)
                ->getQuery()
                ->getArrayResult();


        foreach ($images as &$data) {
            if (!empty($data['mappings'])) {
                foreach($data['mappings'] as $mappingKey => $mapping) {
                    foreach ($mapping['rules'] as $ruleKey => $rule) {
                        $option = Shopware()->Models()->find('\Shopware\Models\Article\Configurator\Option', $rule['optionId']);
                        if ($option) {
                            $rule['option'] = $option;
                            $data['mappings'][$mappingKey]['rules'][$ruleKey]['option'] = $option;
                        }
                    }
                }
            }

            if (!empty($data['mediaId'])) {
                $data['media'] = Shopware()->Models()->find('\Shopware\Models\Media\Media', $data['mediaId']);
                if (!$data['media']) {
                     unset($data['media']);
                }
            }

            $image = new \Shopware\Models\Article\Image();
            $image->fromArray($data);
            $image->setArticle($article);
            $image->setArticleDetail(null);

            Shopware()->Models()->persist($image);
        }

        Shopware()->Models()->flush();
    }

    /**
     * Internal helper function to duplicate the property configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleProperties($articleId, $newArticleId)
    {
        $sql= "INSERT INTO s_filter_articles
               SELECT ?, valueID
               FROM s_filter_articles as source
               WHERE source.articleID = ?
        ";
        Shopware()->Db()->query($sql, array($newArticleId, $articleId));
    }

    /**
     * Internal helper function to duplicate the variant configuration from the passed article
     * to the new article.
     * @param $articleId
     * @param $newArticleId
     */
    protected function duplicateArticleDetails($articleId, $newArticleId, $mailDetailId = null)
    {
        $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $newArticleId);
        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->select(array('details', 'prices', 'attribute', 'images'))
                ->from('Shopware\Models\Article\Detail', 'details')
                ->leftJoin('details.prices', 'prices')
                ->leftJoin('details.attribute', 'attribute')
                ->leftJoin('details.images', 'images')
                ->where('details.articleId = ?1');

        if($mailDetailId !== null){
            $builder->andWhere('details.id = ?2');
            $details = $builder->setParameter(1, $articleId)
                               ->setParameter(2, $mailDetailId)
                               ->getQuery()
                               ->getArrayResult();
        } else {
            $details = $builder->setParameter(1, $articleId)
                               ->getQuery()
                               ->getArrayResult();
        }

        foreach ($details as $data) {
            $prices = array();
            $data['number'] = $data['number'] . uniqid();
            $detail = new \Shopware\Models\Article\Detail();

            foreach($data['prices'] as $priceData) {
                if (empty($priceData['customerGroupKey'])) {
                    continue;
                }
                $customerGroup = $this->getCustomerGroupRepository()->findOneBy(array('key' => $priceData['customerGroupKey']));
                if ($customerGroup instanceof \Shopware\Models\Customer\Group) {
                    $priceData['customerGroup'] = $customerGroup;
                    $priceData['article'] = $article;
                    $prices[] = $priceData;
                }
            }
            $data['prices'] = $prices;

            // unset configuratorOptions and images. These are variantspecific and are going to be recreated later
            unset($data['images']);
            unset($data['configuratorOptions']);

            if (!empty($data['unitId'])) {
                $data['unit'] = Shopware()->Models()->find('Shopware\Models\Article\Unit', $data['unitId']);
            } else {
                $data['unit'] = null;
            }

            if (!empty($data['attribute'])) {
                $data['attribute']['article'] = $article;
            }

            $data['article'] = $article;

            $detail->fromArray($data);
            Shopware()->Models()->persist($detail);
        }
        Shopware()->Models()->flush();
    }

    /**
     * @param $articleId
     * @return null|string
     */
    protected function duplicateArticleConfigurator($articleId)
    {
        $unique = uniqid();

        /**@var $oldArticle \Shopware\Models\Article\Article*/
        $oldArticle = Shopware()->Models()->find('Shopware\Models\Article\Article', $articleId);
        if (!$oldArticle->getConfiguratorSet()) {
            return null;
        }

        $oldSetId = $oldArticle->getConfiguratorSet()->getId();

        $sql= "INSERT INTO s_article_configurator_sets
                SELECT NULL, CONCAT(name, '-', '". $unique ."'), public, type
                FROM s_article_configurator_sets as source
                WHERE source.id = ?";

        Shopware()->Db()->query($sql, array($oldSetId));
        $newSetId = Shopware()->Db()->lastInsertId('s_article_configurator_sets');

        $sql= "INSERT INTO s_article_configurator_set_group_relations
                SELECT NULL, ?, group_id
                FROM s_article_configurator_set_group_relations as source
                WHERE source.set_id = ?";
        Shopware()->Db()->query($sql, array($newSetId, $oldSetId));


        $sql= "INSERT INTO s_article_configurator_set_option_relations
                SELECT NULL, ?, option_id
                FROM s_article_configurator_set_option_relations as source
                WHERE source.set_id = ?";
        Shopware()->Db()->query($sql, array($newSetId, $oldSetId));

        $sql= "INSERT INTO s_article_configurator_dependencies
                SELECT NULL, ?, parent_id, child_id
                FROM s_article_configurator_dependencies as source
                WHERE source.configurator_set_id = ?";
        Shopware()->Db()->query($sql, array($newSetId, $oldSetId));

        $sql= "INSERT INTO s_article_configurator_price_surcharges
                SELECT NULL, ?, parent_id, child_id, surcharge
                FROM s_article_configurator_price_surcharges as source
                WHERE source.configurator_set_id = ?";
        Shopware()->Db()->query($sql, array($newSetId, $oldSetId));

        return $newSetId;
    }


    /**
     *
     */
    public function deleteAllVariantsAction()
    {
        try {
            $articleId = (int) $this->Request()->getParam('articleId');
            if (empty($articleId)) {
                $this->View()->assign(array(
                    'success' => false
                ));
                return;
            }
            $this->removeAllConfiguratorVariants($articleId);
            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Internal helper function to remove all article variants.
     * @param $articleId
     */
    protected function removeAllConfiguratorVariants($articleId) {
        $builder = Shopware()->Models()->createQueryBuilder();
        $details = $builder->select(array('details', 'configuratorOptions'))
                ->from('Shopware\Models\Article\Detail', 'details')
                ->innerJoin('details.configuratorOptions', 'configuratorOptions')
                ->where('details.articleId = ?1')
                ->setParameter(1, $articleId)
                ->getQuery()
                ->getArrayResult();

        /**@var $article \Shopware\Models\Article\Article*/
        $article = $this->getRepository()->find($articleId);
        $mainDetailId = $article->getMainDetail()->getId();

        if (empty($details)) {
            return;
        }

        $detailIds = array();
        foreach($details as $detail) {
            if (empty($detail['configuratorOptions'])) {
                continue;
            }
            if($mainDetailId == $detail['id']) {
                continue;
            }
            $detailIds[] = $detail['id'];
        }

        if (count($detailIds) == 0) {
            return;
        }

        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->delete('Shopware\Models\Attribute\Article', 'details')
                ->andWhere('details.articleDetailId IN (?1)')
                ->setParameter(1, $detailIds)
                ->getQuery()
                ->execute();

        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->delete('Shopware\Models\Article\Detail', 'details')
                ->andWhere('details.id IN (?1)')
                ->setParameter(1, $detailIds)
                ->getQuery()
                ->execute();

        $sql= "DELETE FROM s_article_configurator_option_relations WHERE article_id IN (?)";
        Shopware()->Db()->query($sql, array(implode(',', $detailIds)));

        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->delete('Shopware\Models\Article\Price', 'prices')
                ->andWhere('prices.articleDetailsId IN (?1)')
                ->setParameter(1, $detailIds)
                ->getQuery()
                ->execute();
    }


    public function saveMediaMappingAction()
    {
        try {
            $imageId = (int) $this->Request()->getParam('id', null);
            $mappings = $this->Request()->getParam('mappings');

            if (empty($imageId) || $imageId <= 0) {
                $this->View()->assign(array('success' => false, 'noId' => true));
                return;
            }

            $query = $this->getRepository()->getArticleImageDataQuery($imageId);
            $image = $query->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_OBJECT);
            $imageData = $query->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
            $this->getRepository()->getDeleteImageChildrenQuery($imageId)->execute();

            $mappingModels = array();
            foreach($mappings as $mappingData) {
                if (empty($mappingData['rules'])) {
                    continue;
                }
                if (empty($mappingData['id'])) {
                    $mapping = new \Shopware\Models\Article\Image\Mapping();
                } else {
                    $mapping = Shopware()->Models()->find('Shopware\Models\Article\Image\Mapping', $mappingData['id']);
                }

                $mapping->getRules()->clear();
                $options = array();
                foreach($mappingData['rules'] as $ruleData) {
                    $rule = new \Shopware\Models\Article\Image\Rule();
                    $option = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Option', $ruleData['optionId']);
                    $rule->setMapping($mapping);
                    $rule->setOption($option);
                    $mapping->getRules()->add($rule);
                    $options[] = $option;
                }
                $mapping->setImage($image);
                Shopware()->Models()->persist($mapping);
                $this->createImagesForOptions($options, $imageData, $image);
                $mappingModels[] = $mapping;
            }
            $image->setMappings($mappingModels);
            Shopware()->Models()->persist($image);
            Shopware()->Models()->flush();

            $result = $this->getRepository()->getArticleImageQuery($imageId)->getArrayResult();

            $this->View()->assign(array('success' => true, 'data' => $result));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * @param $options
     * @param $imageData
     * @param $parent \Shopware\Models\Article\Image
     */
    protected function createImagesForOptions($options, $imageData, $parent)
    {
        $articleId = $parent->getArticle()->getId();
        $imageData['path'] = null;
        $imageData['parent'] = $parent;

        $details = $this->getRepository()->getDetailsForOptionIdsQuery($articleId, $options)->getResult();

        foreach($details as $detail) {
            $image = new \Shopware\Models\Article\Image();
            $image->fromArray($imageData);
            $image->setArticleDetail($detail);
            Shopware()->Models()->persist($image);
        }
        Shopware()->Models()->flush();
    }

    /**
     * Event listener function of the article backend module. Fired when the user
     * edit or create an article variant and clicks the save button which displayed on bottom of the article
     * variant detail window.
     */
    public function saveDetailAction()
    {
        try {
            $data = $this->Request()->getParams();
            $id = (int) $this->Request()->getParam('id');

            if ($id > 0) {
                $detail = $this->getArticleDetailRepository()->find($id);
            } else {
                $detail = new \Shopware\Models\Article\Detail();
            }
            $detail = $this->saveDetail($data, $detail);
            $data['id'] = $detail->getId();
            $this->View()->assign(array(
                'success' => true,
                'data' => $data
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * @param $data array
     * @param $detail \Shopware\Models\Article\Detail
     * @return \Shopware\Models\Article\Detail
     */
    protected function saveDetail($data, $detail)
    {
        $article = $detail->getArticle();
        $data['prices'] = $this->preparePricesAssociatedData($data['prices'], $article, $article->getTax());
        $data['attribute'] = $data['attribute'][0];
        $data['article'] = $article;
        unset($data['images']);
        if (!empty($data['unitId'])) {
            $data['unit'] = Shopware()->Models()->find('Shopware\Models\Article\Unit', $data['unitId']);
        } else {
            $data['unit'] = null;
        }

        unset($data['configuratorOptions']);
        $detail->fromArray($data);
        Shopware()->Models()->persist($detail);
        Shopware()->Models()->flush();
        if ($data['standard']) {
              //todo@hl Fix standard detail change
//            $mainDetail = $article->getMainDetail();
//            $mainDetail->setKind(2);
//            $article->setMainDetail($detail);
//            Shopware()->Models()->persist($mainDetail);
//            Shopware()->Models()->persist($article);
//            Shopware()->Models()->flush();
        }
        return $detail;
    }

    /**
     * Event listener function of the article backend module. Fired when the user saves or updates
     * an article configurator dependency in the dependency window.
     */
    public function saveConfiguratorDependencyAction()
    {
        try {
            $data = $this->Request()->getParams();
            $id = (int) $this->Request()->getParam('id');
            if ($id > 0) {
                $dependency = $this->getConfiguratorDependencyRepository()->find($id);
            } else {
                $dependency = new \Shopware\Models\Article\Configurator\Dependency();
            }

            $data['childOption'] = $this->getConfiguratorOptionRepository()->find($data['childId']);
            $data['parentOption'] = $this->getConfiguratorOptionRepository()->find($data['parentId']);
            $data['configuratorSet'] = $this->getConfiguratorSetRepository()->find($data['configuratorSetId']);
            $dependency->fromArray($data);
            Shopware()->Models()->persist($dependency);
            Shopware()->Models()->flush();

            $builder = Shopware()->Models()->createQueryBuilder();
            $data = $builder->select(array('dependency', 'dependencyParent', 'dependencyChild'))
                    ->from('Shopware\Models\Article\Configurator\Dependency', 'dependency')
                    ->leftJoin('dependency.parentOption', 'dependencyParent')
                    ->leftJoin('dependency.childOption', 'dependencyChild')
                    ->where('dependency.id = ?1')
                    ->setParameter(1, $dependency->getId())
                    ->getQuery()
                    ->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

            $this->View()->assign(array(
                'success' => true,
                'data' => $data
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module.
     * Fired when the user want to load a configurator set in the configurator tab.
     * The function returns all public defined configurator sets without the passed ids.
     */
    public function getConfiguratorSetsAction()
    {
        $id = $this->Request()->getParam('setId');
        $sets = $this->getRepository()->getConfiguratorSetsWithExcludedIdsQuery($id)->getArrayResult();
        $this->View()->assign(array(
            'success' => true,
            'data' => $sets
        ));
    }

    /**
     * Event listener function of the article backend module. Fired when the user saves or updates
     * an article configurator price surcharge in the dependency window.
     */
    public function saveConfiguratorPriceSurchargeAction()
    {
        try {
            $data = $this->Request()->getParams();
            if (!empty($data['id']) && $data['id'] > 0) {
                $priceSurcharge = $this->getConfiguratorPriceSurchargeRepository()->find($data['id']);
            } else {
                $priceSurcharge = new \Shopware\Models\Article\Configurator\PriceSurcharge();
            }

            $data['parentOption'] = $this->getConfiguratorOptionRepository()->find($data['parentId']);

            if (!empty($data['childId']) && $data['childId'] > 0) {
                $data['childOption'] = $this->getConfiguratorOptionRepository()->find($data['childId']);
            } else {
                $data['childOption'] = null;
            }
            $data['configuratorSet'] = $this->getConfiguratorSetRepository()->find($data['configuratorSetId']);

            $priceSurcharge->fromArray($data);
            Shopware()->Models()->persist($priceSurcharge);
            Shopware()->Models()->flush();

            $builder = Shopware()->Models()->createQueryBuilder();
            $data = $builder->select(array('priceSurcharge', 'priceSurchargesParent', 'priceSurchargesChild'))
                    ->from('Shopware\Models\Article\Configurator\PriceSurcharge', 'priceSurcharge')
                    ->leftJoin('priceSurcharge.parentOption', 'priceSurchargesParent')
                    ->leftJoin('priceSurcharge.childOption', 'priceSurchargesChild')
                    ->where('priceSurcharge.id = ?1')
                    ->setParameter(1, $priceSurcharge->getId())
                    ->getQuery()
                    ->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

            $this->View()->assign(array(
                'success' => true,
                'data' => $data
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module. Fired when the user clicks the delete
     * button in the dependency window to delete a dependency.
     */
    public function deleteConfiguratorDependencyAction()
    {
        try {
            $id = (int) $this->Request()->getParam('id');
            if (empty($id)) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => 'No valid dependency id passed'
                ));
                return;
            }
            $model = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Dependency', $id);
            Shopware()->Models()->remove($model);
            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    public function deleteConfiguratorPriceSurchargeAction()
    {
        try {
            $id = (int) $this->Request()->getParam('id');
            if (empty($id)) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => 'No valid surcharge id passed'
                ));
                return;
            }
            $model = Shopware()->Models()->find('Shopware\Models\Article\Configurator\PriceSurcharge', $id);
            Shopware()->Models()->remove($model);
            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Internal helper function to save the article data.
     * @param $data
     * @param $article \Shopware\Models\Article\Article
     * @return mixed
     */
    protected function saveArticle($data, $article) {
        try {
            $data = $this->prepareAssociatedData($data, $article);
            $article->fromArray($data);

            Shopware()->Models()->persist($article);
            Shopware()->Models()->flush();
            if (empty($data['id']) && !empty($data['autoNumber'])) {
                $this->increaseAutoNumber($data['autoNumber'], $article->getMainDetail()->getNumber());
            }

            $savedArticle = $this->getArticle($article->getId());
            $this->View()->assign(array(
                'success' => true,
                'data' => $savedArticle
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
            return;
        }
    }

    /**
     * The loadStoresAction function is an ExtJs event listener method of the article backend module.
     * The function is used to load all required stores for the article detail page in one request.
     * @return array
     */
    public function loadStoresAction()
    {
        $id = $this->Request()->getParam('articleId');
        $priceGroups = $this->getRepository()->getPriceGroupQuery()->getArrayResult();
        $suppliers = $this->getRepository()->getSuppliersQuery()->getArrayResult();
        $shops = $this->getShopRepository()->createQueryBuilder('shops')->getQuery()->getArrayResult();
        $taxes = $this->getRepository()->getTaxesQuery()->getArrayResult();
        $templates = $this->getTemplates();
        $units = $this->getRepository()->getUnitsQuery()->getArrayResult();
        $customerGroups = $this->getCustomerRepository()->getCustomerGroupsQuery()->getArrayResult();
        $properties = $this->getRepository()->getPropertiesQuery()->getArrayResult();
        $configuratorGroups = $this->getRepository()->getConfiguratorGroupsQuery()->getArrayResult();
        $attributeFields = $this->getAttributeFields();

        $priceSurcharges = null;
        $dependencies = null;
        $configuratorSet = null;
        if (!empty($id)) {
            $article = $this->getArticle($id);
        } else {
            $article = $this->getNewArticleData();
        }

        $this->View()->assign(array(
            'success' => true,
            'data' => array(
                'shops' => $shops,
                'customerGroups' => $customerGroups,
                'taxes' => $taxes,
                'suppliers' => $suppliers,
                'attributeFields' => $attributeFields,
                'templates' => $templates,
                'units' => $units,
                'properties' => $properties,
                'priceGroups' => $priceGroups,
                'article' => $article,
                'configuratorSet' => $configuratorSet,
                'configuratorGroups' => $configuratorGroups,
                'priceSurcharges' => $priceSurcharges,
                'dependencies' => $dependencies,
                'settings' => array()
            )
        ));
    }


    /**
     *
     */
    public function getPropertyListAction()
    {
        $articleId = $this->Request()->getParam('articleId');
        $propertyGroupId =  $this->Request()->getParam('propertyGroupId');

        $builder = Shopware()->Models()->createQueryBuilder()
            ->from('Shopware\Models\Property\Option', 'po')
            ->join('po.groups', 'pg', 'with', 'pg.id = :propertyGroupId')
            ->setParameter('propertyGroupId', $propertyGroupId)
            ->select(array( 'PARTIAL po.{id,name}' ));

        $query = $builder->getQuery();
        $options = array();
        foreach($query->getArrayResult() as $option) {
            $options[$option['id']] = $option;
        }

        $builder = Shopware()->Models()->createQueryBuilder()
            ->from('Shopware\Models\Property\Value', 'pv')
            ->join('pv.articles', 'pa', 'with', 'pa.id = :articleId')
            ->setParameter('articleId', $articleId)
            ->join('pv.option', 'po')
            ->select(array('po.id as optionId', 'pv.id as value'));

        $query = $builder->getQuery();
        foreach($query->getArrayResult() as $value) {
            if(!isset($options[$value['optionId']])) {
                continue;
            }
            if(!isset($options[$value['optionId']]['name'])) {
                $options[$value['optionId']]['value'] = array($value['value']);
            } else {
                $options[$value['optionId']]['value'][] = $value['value'];
            }
        }

        $this->View()->assign(array(
            'data' =>  array_values($options),
            'total' =>  count($options),
            'success' => true
        ));
    }

    /**
     * Returns the available property values
     */
    public function getPropertyValuesAction()
    {
        $propertyGroupId =  $this->Request()->getParam('propertyGroupId');

        $builder = Shopware()->Models()->createQueryBuilder()
            ->from('Shopware\Models\Property\Value', 'pv')
            ->join('pv.option', 'po')
            ->join('po.groups', 'pg', 'with', 'pg.id = :propertyGroupId')
            ->setParameter('propertyGroupId', $propertyGroupId)
            ->select(array( 'pv.id', 'pv.value', 'po.id as optionId' ));

        $query = $builder->getQuery();
        $data = $query->getArrayResult();

        $this->View()->assign(array(
            'data' =>  $data,
            'total' =>  count($data),
            'success' => true
        ));
    }

    public function setPropertyListAction()
    {
        $models = Shopware()->Models();
        $articleId = $this->Request()->getParam('articleId');
        /** @var $article Shopware\Models\Article\Article */
        $article = $models->find('Shopware\Models\Article\Article', $articleId);
        $properties = $this->Request()->getParam('properties', array());

        if (empty($properties[0])){
            $properties[0] = array(
                "id" => $this->Request()->getParam('id'),
                "name" => $this->Request()->getParam('name'),
                "value" => $this->Request()->getParam('value'),

            );
        }

        $propertyValues = $article->getPropertyValues();
        $propertyValues->clear();
        $models->flush();

        foreach($properties as $property) {
            if(empty($property['value'])) {
                continue;
            }
            /** @var $article Shopware\Models\Property\Option */
            $option = $models->find('Shopware\Models\Property\Option', $property['id']);
            foreach((array) $property['value'] as $value) {
                if(is_int($value)) {
                    $value = $models->find('Shopware\Models\Property\Value', $value);
                } else {
                    $value = new Shopware\Models\Property\Value(
                        $option,
                        $value
                    );
                    $models->persist($value);
                }
                if($value !== null) {
                    $propertyValues->add($value);
                }
            }
        }
        $models->flush();

        $this->View()->assign(array(
            'success' => true
        ));
    }

    /**
     * Selects the dynamic attribute fields
     * @return array
     */
    protected function getAttributeFields()
    {
        $builder = Shopware()->Models()->createQueryBuilder();
        return $builder->select(array('elements'))
                ->from('Shopware\Models\Article\Element', 'elements')
                ->getQuery()
                ->getArrayResult();
    }

    /**
     * Internal helper function to get the article data of the passed id.
     * @param $id
     * @return array
     */
    protected function getArticle($id)
    {
        $data = $this->getRepository()->getArticleQuery($id)->getArrayResult();

        $data[0]['mainPrices'] = $this->getPrices($data[0]['mainDetail']['id'], $data[0]['tax']);
        $data[0]['attribute'] = $this->getRepository()->getAttributesQuery($data[0]['mainDetail']['id'])->getArrayResult();

        $configuratorSet = $this->getRepository()
                                ->getArticleConfiguratorSetByArticleIdQuery($id)
                                ->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

        if (!empty($configuratorSet)) {
            $data[0]['configuratorSet'] = array($configuratorSet['configuratorSet']);
        } else {
            $data[0]['configuratorSet'] = null;
        }

        $sql= "SELECT categoryID FROM s_articles_categories WHERE articleID = ? ORDER BY id ASC";
        $categorySort = Shopware()->Db()->fetchCol($sql, array($id));

        $categories = array();
        foreach($categorySort as $sort) {
            $category = $data[0]['categories'][$sort];
            $category['name'] = $this->getCategoryRepository()->getPathById($sort, 'name', '>');
            $categories[] = $category;
        }
        $data[0]['categories'] = $categories;

        $data[0]['dependencies'] = $this->getRepository()->getConfiguratorDependenciesQuery($data[0]['configuratorSet'][0]['id'])->getArrayResult();
        $data[0]['priceSurcharges'] = $this->getRepository()->getConfiguratorPriceSurchargesQuery($data[0]['configuratorSet'][0]['id'])->getArrayResult();
        if ($data[0]['added'] && $data[0]['added'] instanceof \DateTime) {
            $added = $data[0]['added'];
            $data[0]['added'] = $added->format('d.m.Y');
        }
        return $data;
    }

    /**
     * Loads the variant listing for the article backend module.
     */
    public function detailListAction()
    {
        if (!$this->Request()->has('articleId')) {
            $this->View()->assign(array(
                'success' => false,
                'message' => 'No valid article id passed'
            ));
            return;
        }
        $articleId = $this->Request()->getParam('articleId');

        /**@var $article \Shopware\Models\Article\Article*/
        $article = Shopware()->Models()->find('Shopware\Models\Article\Article', $articleId);
        $tax = array(
            'tax' => $article->getTax()->getTax()
        );

        $idQuery = $this->getRepository()->getConfiguratorListIdsQuery(
            $articleId,
            $this->Request()->getParam('filter'),
            $this->Request()->getParam('sort'),
            $this->Request()->getParam('start'),
            $this->Request()->getParam('limit', 20)
        );

        $total = Shopware()->Models()->getQueryCount($idQuery);
        $ids = $idQuery->getArrayResult();

        foreach($ids as $key => $id) {
            $ids[$key] = $id['id'];
        }
        if (empty($ids)) {
            $this->View()->assign(array(
                'success' => true,
                'data' => array(),
                'total' => 0
            ));
            return;
        }

        $query = $this->getRepository()->getDetailsByIdsQuery($ids, $this->Request()->getParam('sort'));
        $details = $query->getArrayResult();

        $return = array();
        foreach($details as $key => $detail) {
            if (empty($detail['prices']) || empty($detail['configuratorOptions'])) {
                continue;
            }
            $detail['prices'] = $this->formatPricesFromNetToGross($detail['prices'], $tax);
            if ($detail['releaseDate']) {
                $releaseDate = $detail['releaseDate'];
                if ($releaseDate instanceof \DateTime) {
                    $detail['releaseDate'] = $releaseDate->format('d.m.Y');
                }
            }
            $return[] = $detail;
        }

        $this->View()->assign(array(
            'success' => true,
            'data' => $return,
            'total' => $total
        ));
    }

    /**
     * Internal helper function to convert gross prices to net prices.
     * @param $prices
     * @param $tax
     * @return array
     */
    protected function formatPricesFromNetToGross($prices, $tax)
    {
        foreach ($prices as $key => $price) {
            $customerGroup = $price['customerGroup'];
            if ($customerGroup['taxInput']) {
                $price['price'] = $price['price'] / 100 * (100 + $tax['tax']) ;
                $price['pseudoPrice'] = $price['pseudoPrice'] / 100 * (100 + $tax['tax']) ;
            }
            $prices[$key] = $price;
        }
        return $prices;
    }


    /**
     * Internal helper function to load the article main detail prices into the backend module.
     * @param $id
     * @param $tax
     * @return array
     */
    protected function getPrices($id, $tax)
    {
        $prices = $this->getRepository()
                       ->getPricesQuery($id)
                       ->getArrayResult();

        return $this->formatPricesFromNetToGross($prices, $tax);
    }

    /**
     * Saves the passed configurator group data. If an id passed, the function updates the existing group, otherwise
     * a new group will be created.
     */
    public function saveConfiguratorGroupAction()
    {
        try {
            $id = (int)$this->Request()->getParam('id');
            if (!empty($id)) {
                $group = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Group', $id);
            } else {
                $group = new \Shopware\Models\Article\Configurator\Group();
            }
            $data = $this->Request()->getParams();
            unset($data['options']);
            $group->fromArray($data);
            Shopware()->Models()->persist($group);
            Shopware()->Models()->flush();
            $data['id'] = $group->getId();

            $this->View()->assign(array(
                'success' => true,
                'data' => $data
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Saves the passed configurator option data. If an id passed, the function updates the existing options, otherwise
     * a new option will be created.
     */
    public function saveConfiguratorOptionAction()
    {
        try {
            $id = (int) $this->Request()->getParam('id');
            if (!empty($id)) {
                $option = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Option', $id);
            } else {
                $option = new \Shopware\Models\Article\Configurator\Option();
            }
            $data = $this->Request()->getParams();
            if (empty($data['groupId'])) {
                return;
            }
            $data['group'] = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Group', $data['groupId']);

            $option->fromArray($data);
            Shopware()->Models()->persist($option);
            Shopware()->Models()->flush();
            $data['id'] = $option->getId();

            $this->View()->assign(array(
                'success' => true,
                'data' => $data
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Helper function which creates for the passed configurator groups
     * the cross join sql for all possible variants.
     * Returns an array with the sql and all used group ids
     * @param $groups
     * @param $offset
     * @param $limit
     * @return array
     */
    protected function prepareGeneratorData($groups, $offset, $limit)
    {

        //we have to iterate all passed groups to check the activated options.
        $activeGroups = array();
        //we need a second array with all group ids to iterate them easily in the sql generation
        $originals = array();
        foreach($groups as $group) {
            if (!$group['active']) {
                continue;
            }
            $options = array();
            //we iterate the options to get the option ids in a one dimensional array.
            foreach($group['options'] as $option) {
                if ($option['active']) {
                    $options[] = $option['id'];
                }
            }

            //if some options active, we save the group and the options in an internal array
            if (!empty($options)) {
                $activeGroups[] = array('id' => $group['id'], 'options' => $options);
                $originals[] = $group['id'];
            }
        }

        if (empty($activeGroups)) {
            return array();
        }

        //the first groups serves as the sql from path, so we have to remove the first id from the array
        $first = array_shift($activeGroups);
        $firstId = $first['id'];

        //now we create plain sql templates to parse the ids over the sprintf function
        $selectTemplate = "o%s.id as o%sId, o%s.name as o%sName, g%s.id as g%sId, g%s.name as g%sName";

        $fromTemplate = "FROM s_article_configurator_options o%s
                            LEFT JOIN s_article_configurator_groups g%s ON g%s.id = o%s.group_id";

        $joinTemplate = "CROSS JOIN s_article_configurator_options o%s ON o%s.group_id = %s AND o%s.id IN (%s)
                            LEFT JOIN s_article_configurator_groups g%s ON g%s.id = o%s.group_id";

        $whereTemplate = "WHERE o%s.group_id = %s
                          AND o%s.id IN (%s)";

        $groupSql = array();
        $selectSql = array();

        //we have remove the first group id, but we need the first id in the select, from and where path.
        $selectSql[] = sprintf($selectTemplate, $firstId,$firstId,$firstId,$firstId,$firstId,$firstId,$firstId,$firstId);
        $groupSql[] = sprintf($fromTemplate, $firstId,$firstId,$firstId,$firstId);
        $whereSql = sprintf($whereTemplate, $firstId,$firstId,$firstId, implode(',', $first['options']));

        //now we iterate all other groups, and create a select sql path and a cross join sql path.
        foreach($activeGroups as $group) {
            $groupId = $group['id'];
            $selectSql[] = sprintf($selectTemplate, $groupId,$groupId,$groupId,$groupId,$groupId,$groupId,$groupId,$groupId);
            $groupSql[] = sprintf($joinTemplate, $groupId,$groupId,$groupId,$groupId,implode(',', $group['options']),$groupId,$groupId,$groupId);
        }

        //concat the sql statement
        $sql= 'SELECT ' . implode(",\n", $selectSql) . ' ' . implode("\n", $groupSql) . ' ' . $whereSql . ' LIMIT ' . $offset . ',' . $limit;

        return array(
            'sql' => $sql,
            'originals' => $originals
        );
    }

    /**
     * Called when the user clicks the "generateVariants" button in the article backend module.
     * The function expects that an article id passed and an array with active groups passed.
     */
    public function createConfiguratorVariantsAction()
    {
        try {
            //first get the id parameter of the request object
            $articleId = $this->Request()->getParam('articleId', 1);
            $groups = $this->Request()->getParam('groups');
            $offset = $this->Request()->getParam('offset', 0);
            $limit = $this->Request()->getParam('limit', 50);

            if ($offset === 0) {
                $this->removeAllConfiguratorVariants($articleId);
            }

            /**@var $article \Shopware\Models\Article\Article*/
            $article = $this->getRepository()->find($articleId);

            $detailData = $this->getDetailDataForVariantGeneration($article);

            $configuratorSet = $article->getConfiguratorSet();

            $dependencies = $this->getRepository()->getConfiguratorDependenciesQuery($configuratorSet->getId())->getArrayResult();
            $priceSurcharges = $this->getRepository()->getConfiguratorPriceSurchargesQuery($configuratorSet->getId())->getArrayResult();

            $generatorData = $this->prepareGeneratorData($groups, $offset, $limit);
            if (empty($generatorData)) {
                return;
            }
            $sql = $generatorData['sql'];
            $originals = $generatorData['originals'];

            $variants = Shopware()->Db()->fetchAll($sql);

            $counter = $offset;
            $allOptions = $this->getRepository()->getAllConfiguratorOptionsIndexedByIdQuery()->getResult();

            //iterate all selected variants to insert them into the database
            foreach($variants as $variant) {
                $variantData = $this->prepareVariantData($variant, $detailData, $counter, $dependencies, $priceSurcharges, $allOptions, $originals, $article);
                if ($variantData === false) {
                    continue;
                }
                //merge the data with the original main detail data
                $data = array_merge($detailData, $variantData);
                if(!$counter) {
                    $detail = $article->getMainDetail();
                } else {
                    $detail = new \Shopware\Models\Article\Detail();
                }
                $detail->fromArray($data);
                $detail->setArticle($article);
                Shopware()->Models()->persist($detail);
                $counter++;
            }
            Shopware()->Models()->flush();

            $article = $this->getArticle($articleId);
            $this->View()->assign(array(
                'success' => true,
                'data' => $article
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => true,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Helper function to prepare the variant data for a new article detail.
     * Iterates all passed price surcharges and dependencies to check if the current variant
     * has configurator options which defined in the dependencies or in the price surcharges.
     * The used price surcharge options will be added to each variant price row.
     * If the variant has configurator options which defined as dependency row,
     * the variant won't be created. The function will return false and
     * the foreach queue in the "createConfiguratorVariantsAction" will be continue.
     *
     * @param $variant
     * @param $detailData
     * @param $counter
     * @param $dependencies
     * @param $priceSurcharges
     * @param $allOptions
     * @param $originals
     * @param $article \Shopware\Models\Article\Article
     * @return array|bool
     */
    protected function prepareVariantData($variant, $detailData, $counter, $dependencies, $priceSurcharges, $allOptions, $originals, $article) {
        $name = '';
        $optionsModels= array();
        $tax = $article->getTax();
        $optionIds = array();


        //iterate the original ids to get the new variant name
        foreach($originals as $id) {
            $optionId = $variant['o' . $id . 'Id'];

            //first we push the option ids in an one dimensional array to check
            $optionIds[] = $optionId;

            $optionsModels[] = $allOptions[$optionId];
            $name[] = $variant['o' . $id . 'Name'];
        }

        $optionPriceSurcharges = array();
        foreach($priceSurcharges as $surcharge) {
            if (in_array($surcharge['parentId'], $optionIds) && empty($surcharge['childId'])) {
                $optionPriceSurcharges[] = $surcharge;
            } elseif (in_array($surcharge['parentId'], $optionIds) && !empty($surcharge['childId']) && in_array($surcharge['childId'], $optionIds)) {
                $optionPriceSurcharges[] = $surcharge;
            }
        }

        $abortVariant = false;
        foreach($dependencies as $dependency) {
            if (in_array($dependency['parentId'], $optionIds) && in_array($dependency['childId'], $optionIds)) {
                $abortVariant = true;
            }
        }

        if ($abortVariant) {
            return false;
        }

        //create the new variant data
        $variantData = array(
            'additionalText' => implode(' / ', $name),
            'active' => 1,
            'configuratorOptions' => $optionsModels
        );

        if($counter) {
            $variantData['kind'] = 2;
            $variantData['number'] = $detailData['number'] . '.' . $counter;
        }

        //we have to check the defined price surcharges for the article configurator set,
        //to add the defined surcharges to the variant prices with the corresponding configurator options
        if (!empty($optionPriceSurcharges)) {
            $fullPriceSurcharge = 0;
            foreach($optionPriceSurcharges as $priceSurcharge) {
                $fullPriceSurcharge += $priceSurcharge['surcharge'];
            }

            $prices = $detailData['prices'];
            $newPrices = array();

            foreach($prices as $priceData) {
                $customerGroup = $priceData['customerGroup'];

                $surcharge = $fullPriceSurcharge;
                if ($customerGroup->getTaxInput()) {
                    $surcharge = $fullPriceSurcharge / (100 + $tax->getTax()) * 100;
                }

                $priceData['price'] += $surcharge;
                $newPrices[] = $priceData;
            }
            $variantData['prices'] = $newPrices;
        }

        return $variantData;
    }

    protected function getDependencyByOptionId($optionId, $dependencies) {
        $returnValue = array();
        foreach($dependencies as $dependency) {
            if ($dependency['parentId'] == $optionId) {
                $returnValue = $dependency;
                break;
            }
        }
        return $returnValue;
    }

    protected function getSurchargeByOptionId($optionId, $priceSurcharges) {
        $returnValue = array();
        foreach($priceSurcharges as $priceSurcharge) {
            if ($priceSurcharge['parentId'] == $optionId) {
                $returnValue = $priceSurcharge;
                break;
            }
        }
        return $returnValue;
    }


    /**
     * Helper function for the variant generation. Returns the article main detail data which used as base configuration for
     * the generated article variants.
     * @param $article
     * @return mixed
     */
    protected function getDetailDataForVariantGeneration($article) {
        $builder = Shopware()->Models()->createQueryBuilder();
        $detailData = $builder->select(array('detail', 'prices', 'attribute', 'customerGroup'))
                              ->from('Shopware\Models\Article\Detail', 'detail')
                              ->leftJoin('detail.prices', 'prices')
                              ->leftJoin('prices.customerGroup', 'customerGroup')
                              ->leftJoin('detail.attribute', 'attribute')
                              ->where('detail.id = ?1')
                              ->setParameter(1, $article->getMainDetail()->getId())
                              ->getQuery()
                              ->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

        foreach($detailData['prices'] as &$price) {
            $price['article'] = $article;
            unset($price['id']);
            $price['customerGroup'] = Shopware()->Models()->find('Shopware\Models\Customer\Group', $price['customerGroup']['id']);
        }
        if ($detailData['unitId']) {
            $detailData['unit'] = Shopware()->Models()->find('Shopware\Models\Article\Unit', $detailData['unitId']);
        }

        $detailData['attribute']['article'] = $article;
        return $detailData;
    }

    /**
     * This function prepares the posted extJs data. First all ids resolved to the assigned shopware models.
     * After the ids resolved, the function removes the two dimensional arrays of oneToOne associations.
     * @param $data
     * @param $article \Shopware\Models\Article\Article
     * @return array
     */
    protected function prepareAssociatedData($data, $article)
    {
        //format the posted extJs article data
        $data = $this->prepareArticleAssociatedData($data);

        //format the posted extJs article main detail data
        $data = $this->prepareMainDetailAssociatedData($data);

        //format the posted extJs article main prices data
        $data = $this->prepareMainPricesAssociatedData($data, $article);

        $data = $this->prepareAvoidCustomerGroups($data);

        //format the posted extJs article configurator association.
        $data = $this->prepareConfiguratorAssociatedData($data, $article);

        //format the posted extJs article attribute data
        $data = $this->prepareAttributeAssociatedData($data, $article);

        //format the posted extJs article categories associations
        $data = $this->prepareCategoryAssociatedData($data);

        //format the posted extJs related article association
        $data = $this->prepareRelatedAssociatedData($data, $article);

        //format the posted extJs similar article association
        $data = $this->prepareSimilarAssociatedData($data, $article);

        //format the posted extJs article image data
        $data = $this->prepareImageAssociatedData($data);

        //format the posted extJs article link data
        $data = $this->prepareLinkAssociatedData($data);

        //format the posted extJs article download data
        $data = $this->prepareDownloadAssociatedData($data);

        return $data;
    }

    /**
     * Internal helper function which resolves the passed customer group ids
     * with Shopware\Models\Customer\Group models.
     * The configured customer groups are not allowed to set the article in the store front.
     * @param $data
     * @return mixed
     */
    protected function prepareAvoidCustomerGroups($data) {
        if (!empty($data['customerGroups'])) {
            $customerGroups = array();
            foreach($data['customerGroups'] as $customerGroup) {
                if (!empty($customerGroup['id'])) {
                    $customerGroups[] = Shopware()->Models()->find('Shopware\Models\Customer\Group', $customerGroup['id']);
                }
            }
            $data['customerGroups'] = $customerGroups;
        } else {
            $data['customerGroups'] = null;
        }

        return $data;
    }

    /**
     * Internal helper function to check if the article is configured as
     * multiple dimensional article (Configurator activated).
     * The following scenarios are possible:
     * <code>
     *  - New Article
     *    --> Checkbox activated
     *    --> "isConfigurator" = true  / configuratorSetId = null
     *    --> A new configurator set will be created with the name "Set-ArticleNumber"
     *
     *  - Existing Article
     *    --> Checkbox wasn't activated before, now the user activated the checkbox
     *    --> "isConfigurator" = true  / configuratorSetId = null
     *    --> A new configurator set will be created with the name "Set-ArticleNumber"
     *
     *  - Existing Article
     *    --> Checkbox was activated before, now the user deactivated the checkbox
     *    --> "isConfigurator" = false / configuratorSetId = Some Numeric value
     *    --> The old configurator set will be deleted.
     *
     * </code>
     * @param $data
     * @param $article \Shopware\Models\Article\Article
     * @return array
     */
    protected function prepareConfiguratorAssociatedData($data, $article)
    {
        if (!empty($data['configuratorSetId'])) {
            $data['configuratorSet'] = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Set', $data['configuratorSetId']);
        } else if ($data['isConfigurator']) {
            $set = new \Shopware\Models\Article\Configurator\Set();
            $set->setName('Set-' . $data['mainDetail']['number']);
            $set->setPublic(false);
            $data['configuratorSet'] = $set;
        } else {
            //if the article has an configurator set, we have to remove this set if it isn't used for other articles
            if ($article->getConfiguratorSet() && $article->getConfiguratorSet()->getId()) {
                $builder = Shopware()->Models()->createQueryBuilder();
                $articles = $builder->select(array('articles'))
                        ->from('Shopware\Models\Article\Article', 'articles')
                        ->where('articles.configuratorSetId = ?1')
                        ->setParameter(1, $article->getConfiguratorSet()->getId())
                        ->getQuery()
                        ->getArrayResult();

                if (count($articles) <= 1) {
                    $set = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Set', $article->getConfiguratorSet()->getId());
                    Shopware()->Models()->remove($set);
                }
            }
            $data['configuratorSet'] = null;
        }
        return $data;
    }

    /**
     * This function prepares the posted extJs data of the article model.
     * @param $data
     * @return array
     */
    protected function prepareArticleAssociatedData($data)
    {
        //check if a tax id is passed and load the tax model or set the tax parameter to null.
        if (!empty($data['taxId'])) {
            $data['tax'] = Shopware()->Models()->find('Shopware\Models\Tax\Tax', $data['taxId']);
        } else {
            $data['tax'] = null;
        }

        //check if a supplier id is passed and load the supplier model or set the supplier parameter to null.
        if (!empty($data['supplierId'])) {
            $data['supplier'] = Shopware()->Models()->find('Shopware\Models\Article\Supplier', $data['supplierId']);
        } elseif (!empty($data['supplierName'])) {
            $supplier = $this->getManager()->getRepository('Shopware\Models\Article\Supplier')->findOneBy(array('name' => trim($data['supplierName'])));
            if (!$supplier) {
                $supplier = new \Shopware\Models\Article\Supplier();
                $supplier->setName($data['supplierName']);
            }
            $data['supplier'] = $supplier;
        } else {
            $data['supplier'] = null;
        }

        //check if a supplier id is passed and load the supplier model or set the supplier parameter to null.
        if (!empty($data['priceGroupId'])) {
            $data['priceGroup'] = Shopware()->Models()->find('Shopware\Models\Price\Group', $data['priceGroupId']);
        } else {
            $data['priceGroup'] = null;
        }

        if (!empty($data['filterGroupId'])) {
            $data['propertyGroup'] = Shopware()->Models()->find('Shopware\Models\Property\Group', $data['filterGroupId']);
        } else {
            $data['propertyGroup'] = null;
        }

        $data['changed'] = new \DateTime();
        return $data;
    }

    /**
     * Prepares the data for the article main detail object.
     * @param $data
     * @return array
     */
    protected function prepareMainDetailAssociatedData($data)
    {
        $data['mainDetail'] = $data['mainDetail'][0];
        $data['mainDetail']['active'] = $data['active'];
        if (!empty($data['mainDetail']['unitId'])) {
            $data['mainDetail']['unit'] = Shopware()->Models()->find('Shopware\Models\Article\Unit', $data['mainDetail']['unitId']);
        } else {
            $data['mainDetail']['unit'] = null;
        }
        unset($data['mainDetail']['configuratorOptions']);
        return $data;
    }

    /**
     * This function loads the category models for the passed ids in the "categories" parameter.
     * @param $data
     * @return array
     */
    protected function prepareCategoryAssociatedData($data)
    {
        $categories = array();
        foreach($data['categories'] as $categoryData) {
            if (!empty($categoryData['id'])) {
                $model = Shopware()->Models()->find('Shopware\Models\Category\Category', $categoryData['id']);
                $categories[] = $model;
            }
        }
        $data['categories'] = $categories;
        return $data;
    }

    /**
     * This function loads the related article models for the passed ids in the "related" parameter.
     * @param $data
     * @param $article \Shopware\Models\Article\Article
     * @return array
     */
    protected function prepareRelatedAssociatedData($data, $article)
    {
        $related = array();
        foreach($data['related'] as $relatedData) {
            if (empty($relatedData['id'])) {
                continue;
            }
            /**@var $relatedArticle \Shopware\Models\Article\Article*/
            $relatedArticle = $this->getRepository()->find($relatedData['id']);

            //if the user select the cross
            if ($relatedData['cross']) {
                $relatedArticle->getRelated()->add($article);
                Shopware()->Models()->persist($relatedArticle);
            }
            $related[] = $relatedArticle;
        }
        $data['related'] = $related;
        return $data;
    }


    /**
     * This function loads the similar models for the passed ids in the "similar" parameter.
     * @param $data
     * @param $article \Shopware\Models\Article\Article
     * @return array
     */
    protected function prepareSimilarAssociatedData($data, $article)
    {
        $similar = array();
        foreach($data['similar'] as $similarData) {
            if (empty($similarData['id'])) {
                continue;
            }
            /**@var $similarArticle \Shopware\Models\Article\Article*/
            $similarArticle = $this->getRepository()->find($similarData['id']);

            //if the user select the cross
            if ($similarData['cross']) {
                $similarArticle->getSimilar()->add($article);
                Shopware()->Models()->persist($similarArticle);
            }
            $similar[] = $similarArticle;
        }
        $data['similar'] = $similar;
        return $data;
    }

    /**
     * This function loads the category models for the passed ids in the "categories" parameter.
     * @param $data
     * @return array
     */
    protected function prepareImageAssociatedData($data)
    {
        $position = 1;
        foreach($data['images'] as &$imageData) {
            $imageData['position'] = $position;
            $imageData['attribute'] = $imageData['attribute'][0];
            if (!empty($imageData['mediaId'])) {
                $media = Shopware()->Models()->find('Shopware\Models\Media\Media', $imageData['mediaId']);
                if ($media instanceof \Shopware\Models\Media\Media) {
                    $imageData['media'] = $media;
                } else {
                    $imageData['media'] = null;
                }
            } else {
                $imageData['media'] = null;
            }
            unset($imageData['mappings']);
            unset($imageData['children']);
            unset($imageData['parent']);
            $position++;
        }
        return $data;
    }

    /**
     * This function prepares the attribute data for the article.
     * @param $data
     * @param $article
     * @return array
     */
    protected function prepareAttributeAssociatedData($data, $article)
    {
        $data['attribute'][0]['article'] = $article;
        $data['mainDetail']['attribute'] = $data['attribute'][0];
        unset($data['attribute']);
        return $data;
    }

    /**
     * This function prepares the prices for the article main detail object.
     * @param $data
     * @param $article \Shopware\Models\Article\Article
     * @return mixed
     */
    protected function prepareMainPricesAssociatedData($data, $article)
    {
        $data['mainDetail']['prices'] = $this->preparePricesAssociatedData($data['mainPrices'], $article, $data['tax']);
        return $data;
    }

    /**
     * @param $prices array
     * @param $article \Shopware\Models\Article\Article
     * @param $tax \Shopware\Models\Tax\Tax
     * @return array
     */
    protected function preparePricesAssociatedData($prices, $article, $tax)
    {
        foreach($prices as &$priceData) {
            //load the customer group of the price definition
            $customerGroup = $this->getCustomerGroupRepository()->findOneBy(array('key' => $priceData['customerGroupKey']));

            //if no customer group founded, remove price and continue
            if (!$customerGroup instanceof \Shopware\Models\Customer\Group) {
                continue;
            }

            $priceData['to'] = intval($priceData['to']);

            //if the "to" value isn't numeric, set the place holder "beliebig"
            if ($priceData['to'] <= 0) {
                $priceData['to'] = 'beliebig';
            }

            if ($customerGroup->getTaxInput()) {
                $priceData['price'] = $priceData['price'] / (100 + $tax->getTax()) * 100;
                $priceData['pseudoPrice'] = $priceData['pseudoPrice'] / (100 + $tax->getTax()) * 100;
            }

            //resolve the oneToMany association of ExtJs to an oneToOne association for doctrine.
            $priceData['attribute'] = $priceData['attribute'][0];
            $priceData['customerGroup'] = $customerGroup;
            $priceData['article'] = $article;
            $priceData['articleDetail'] = $article->getMainDetail();
        }

        return $prices;
    }

    /**
     * Prepares the link data of the article.
     * @param $data
     * @return array
     */
    protected function prepareLinkAssociatedData($data)
    {
        foreach($data['links'] as &$linkData) {
            $linkData['attribute'] = $linkData['attribute'][0];
        }
        return $data;
    }

    /**
     * Prepares the download data of the article.
     * @param $data
     * @return array
     */
    protected function prepareDownloadAssociatedData($data)
    {
        foreach($data['downloads'] as &$downloadData) {
            $downloadData['attribute'] = $downloadData['attribute'][0];
        }
        return $data;
    }

    /**
     * Returns a list of all article detail templates as array.
     * @return array
     */
    protected function getTemplates()
    {
        $config = Shopware()->Config()->detailTemplates;
        $data = array();
        foreach(explode(';', $config) as $path) {
            list($id, $name) = explode(':', $path);
            $data[] = array('id' => $id, 'name' => $name);
        }
        return $data;
    }

    /**
     * Internal helper function which returns default data for a new article.
     * @return array
     */
    protected function getNewArticleData()
    {
        $sql= "SELECT number FROM s_order_number WHERE name = 'articleordernumber'";
        $number = Shopware()->Db()->fetchOne($sql);
        $number++;

        return array(
            'number' => Shopware()->Config()->backendAutoOrderNumberPrefix . $number,
            'autoNumber' => $number
        );
    }

    /**
     * Event listener function of the article store of the backend module.
     * @return mixed
     */
    public function deleteAction()
    {
        try {
            if (!$this->Request()->has('id')) {
                return;
            }
            $id = (int) $this->Request()->getParam('id');
            $article = $this->getRepository()->find($id);
            if (!$article instanceof \Shopware\Models\Article\Article) {
                return;
            }
            $this->removePrices($article->getId());
            $this->removeArticleEsd($article->getId());
            $this->removeAttributes($article->getId());
            $this->removeArticleDetails($article);

            Shopware()->Models()->remove($article);
            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'data' => $this->Request()->getParams(),
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Internal helper function to remove all article prices quickly.
     * @param $articleId
     */
    protected function removePrices($articleId)
    {
        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->delete('Shopware\Models\Article\Price', 'prices')
                ->where('prices.articleId = :id')
                ->setParameter('id',$articleId)
                ->getQuery()
                ->execute();
    }

    /**
     * Internal helper function to remove the article attributes quickly.
     * @param $articleId
     */
    protected function removeAttributes($articleId)
    {
        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->delete('Shopware\Models\Attribute\Article', 'attribute')
                ->where('attribute.articleId = :id')
                ->setParameter('id',$articleId)
                ->getQuery()
                ->execute();
    }

    /**
     * Internal helper function to remove the detail esd configuration quickly.
     * @param $articleId
     */
    protected function removeArticleEsd($articleId)
    {
        $builder = Shopware()->Models()->createQueryBuilder();
        $builder->delete('Shopware\Models\Article\Esd', 'esd')
                ->where('esd.articleId = :id')
                ->setParameter('id',$articleId)
                ->getQuery()
                ->execute();
    }

    /**
     * @param $article \Shopware\Models\Article\Article
     */
    protected function removeArticleDetails($article)
    {
        $sql= "SELECT id FROM s_articles_details WHERE articleID = ? AND kind != 1";
        $details = Shopware()->Db()->fetchAll($sql, array($article->getId()));

        foreach($details as $detail) {
            $builder = Shopware()->Models()->createQueryBuilder();
            $builder->delete('Shopware\Models\Article\Image', 'image')
                    ->where('image.articleDetailId = :id')
                    ->setParameter('id', $detail['id'])
                    ->getQuery()
                    ->execute();

            $sql= "DELETE FROM s_article_configurator_option_relations WHERE article_id = ?";
            Shopware()->Db()->query($sql, array($detail['id']));

            $builder = Shopware()->Models()->createQueryBuilder();
            $builder->delete('Shopware\Models\Article\Detail', 'detail')
                    ->where('detail.id = :id')
                    ->setParameter('id', $detail['id'])
                    ->getQuery()
                    ->execute();
        }
    }

    /**
     * Event listener function of the configurator group model of the article backend module.
     * Fired when the user want to remove a configurator group.
     * The function requires a passed id to load the shopware model an remove it over the model manager.
     */
    public function deleteConfiguratorGroupAction()
    {
        try {
            if (!$this->Request()->has('id')) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => 'No valid id passed'
                ));
            }
            $model = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Group', (int) $this->Request()->getParam('id'));
            if (!$model instanceof \Shopware\Models\Article\Configurator\Group) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => 'No valid id passed'
                ));
            }
            $builder = Shopware()->Models()->createQueryBuilder();
            $boundedArticles = $builder->select(array('articles'))
                                       ->from('Shopware\Models\Article\Detail', 'articles')
                                       ->innerJoin('articles.configuratorOptions', 'options')
                                       ->where('options.groupId = ?1')
                                       ->setParameter(1, (int) $this->Request()->getParam('id'))
                                       ->getQuery()
                                       ->getArrayResult();

            if (count($boundedArticles) > 0) {
                $articles = array();
                foreach($boundedArticles as $article) {
                    $articles[] = $article['number'] . ' - ' . $article['additionalText'];
                }

                $this->View()->assign(array(
                    'success' => false,
                    'articles' => $articles,
                    'message' => 'Articles bounded on this group!'
                ));
                return;
            }

            Shopware()->Models()->remove($model);
            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the configurator listing. Fired when the user
     * selects one or many rows in the configurator listing and clicks the delete button.
     */
    public function deleteDetailAction()
    {
        $details = $this->Request()->getParam('details', array(array('id' => (int) $this->Request()->getParam('id'))));

        try {
            $article = null;
            foreach($details as $detail) {
                if (empty($detail['id'])) {
                    continue;
                }
                /**@var $model \Shopware\Models\Article\Detail*/
                $model = Shopware()->Models()->find('Shopware\Models\Article\Detail', $detail['id']);
                if (!$model instanceof \Shopware\Models\Article\Detail) {
                    continue;
                }
                if ($article === null) {
                    $article = $model->getArticle();
                }
                if ($model->getId() !== $article->getMainDetail()->getId()) {
                    Shopware()->Models()->remove($model);
                }
            }
            Shopware()->Models()->flush();

            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'error' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the configurator group model of the article backend module.
     * Fired when the user want to remove a configurator group.
     * The function requires a passed id to load the shopware model an remove it over the model manager.
     */
    public function deleteConfiguratorOptionAction()
    {
        try {
            $id = (int) $this->Request()->getParam('id');

            if (empty($id) || $id < 0) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => 'No valid id passed'
                ));
            }
            $model = Shopware()->Models()->find('Shopware\Models\Article\Configurator\Option', $id);
            if (!$model instanceof \Shopware\Models\Article\Configurator\Option) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => 'No valid id passed'
                ));
            }
            $builder = Shopware()->Models()->createQueryBuilder();
            $boundedArticles = $builder->select(array('articles'))
                                       ->from('Shopware\Models\Article\Detail', 'articles')
                                       ->innerJoin('articles.configuratorOptions', 'options')
                                       ->where('options.id = ?1')
                                       ->setParameter(1, $id)
                                       ->getQuery()
                                       ->getArrayResult();

            if (count($boundedArticles) > 0) {
                $articles = array();
                foreach($boundedArticles as $article) {
                    $articles[] = $article['number'] . ' - ' . $article['additionalText'];
                }

                $this->View()->assign(array(
                    'success' => false,
                    'articles' => $articles,
                    'message' => 'Articles bounded on this option!'
                ));
                return;
            }

            Shopware()->Models()->remove($model);
            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Increase the number of the s_order_number
     * @param $autoNumber
     * @param $number
     * @return void
     * @internal param $data
     * @internal param $mainDetail
     */
    protected function increaseAutoNumber($autoNumber, $number)
    {
        if (strlen($number) > 2) {
            $number = substr($number, strlen(Shopware()->Config()->backendAutoOrderNumberPrefix));
        }
        if ($number == $autoNumber) {
            $sql= "UPDATE s_order_number SET number = ? WHERE name = 'articleordernumber'";
            Shopware()->Db()->query($sql, array($autoNumber));
        }
    }


    /**
     * Event listener function of the article backend module.
     * Will be fired when the user changes to the ESD-Tab.
     */
    public function getEsdAction()
    {
        if ($this->Request()->getParam('filterCandidates', false)) {
            $articleId = $this->Request()->getParam('articleId');

            $builder = $this->getManager()->createQueryBuilder();

            $builder->select(array(
                        'articleDetail.id as id',
                        'article.name as name',
                        'articleDetail.id as articleDetailId',
                        'articleDetail.additionalText as additionalText',
                        'article.id as articleId',
                    ))
                    ->from('Shopware\Models\Article\Detail', 'articleDetail')
                    ->leftJoin('articleDetail.esd', 'esd')
                    ->leftJoin('articleDetail.article', 'article')
                    ->where('articleDetail.articleId = :articleId')
                    ->andWhere('esd.id IS NULL')
                    ->setParameter('articleId', $articleId);

            $query = $builder->getQuery();
            $query->setHydrationMode(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
            $paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);

            //returns the total count of the query
            $totalResult = $paginator->count();

            //returns the customer data
            $result = $paginator->getIterator()->getArrayCopy();

            foreach($result as &$item) {
                if (!empty($item['additionalText'])) {
                    $item['name'] .= ' - ' . $item['additionalText'];
                }
            }

            $this->View()->assign(array(
                'data' =>  $result,
                'total' => $totalResult,
                'success' => true
            ));

            return;
        }

        $articleId = $this->Request()->getParam('articleId');

        $filter = $this->Request()->getParam('filter');
        $sort   = $this->Request()->getParam('sort');
        $start = $this->Request()->getParam('start', 0);
        $limit = $this->Request()->getParam('limit', 20);

        $query = $this->getRepository()->getEsdByArticleQuery($articleId, $filter, $limit, $start, $sort);
        $query->setHydrationMode(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

        $paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);

        //returns the total count of the query
        $totalResult = $paginator->count();

        //returns the customer data
        $result = $paginator->getIterator()->getArrayCopy();

        $this->View()->assign(array(
            'data' => $result,
            'total' => $totalResult,
            'success' => true
        ));
    }

    /**
     * Event listener function of the article backend module.
     * Will be fired when the user clicks the edit esd-button.
     */
    public function getSerialsAction()
    {
        $esdId = $this->Request()->getParam('esdId');

        $filter = $this->Request()->getParam('filter');
        $sort   = $this->Request()->getParam('sort');
        $start = $this->Request()->getParam('start', 0);
        $limit = $this->Request()->getParam('limit', 20);

        $query = $this->getRepository()->getSerialsByEsdQuery($esdId, $filter, $start, $limit, $sort);
        $query->setHydrationMode(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);

        $paginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query);

        //returns the total count of the query
        $totalResult = $paginator->count();

        //returns the customer data
        $result = $paginator->getIterator()->getArrayCopy();

        $this->View()->assign(array(
            'data' =>  $result,
            'total' => $totalResult,
            'success' => true
        ));
    }

    public function createEsdAction()
    {
        try {
            $articleDetailId = $this->Request()->getPost('articleDetailId');

            /** @var $articleDetail \Shopware\Models\Article\Detail */
            $articleDetail = Shopware()->Models()->getRepository('Shopware\Models\Article\Detail')->find($articleDetailId);
            if (!$articleDetail) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => sprintf('ArticleDetail by id %s not found', $articleDetailId)
                ));
                return;
            }

            $esd = new \Shopware\Models\Article\Esd();
            $esd->setArticleDetail($articleDetail);

            $this->getManager()->persist($esd);
            $this->getManager()->flush();

            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module.
     * Will be fired when the user saves ESD
     */
    public function saveEsdAction()
    {
        try {
            $esdId = $this->Request()->getPost('id');

            /** @var $esd \Shopware\Models\Article\Esd */
            $esd = Shopware()->Models()->getRepository('Shopware\Models\Article\Esd')->find($esdId);
            if (!$esd) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => sprintf('ESD by id %s not found', $esdId)
                ));
                return;
            }

            $freeSerialsCount = $this->getFreeSerialCount($esdId);
            $articleDetail = $esd->getArticleDetail();
            $articleDetail->setInStock($freeSerialsCount);

            $esd->fromArray($this->Request()->getPost());
            $this->getManager()->flush();

            $this->View()->assign(array(
                'data' =>  $this->Request()->getPost(),
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module.
     * Will be fired when the user deletes ESD
     */
    public function deleteEsdAction()
    {
        $details = $this->Request()->getParam('details', array(array('id' => $this->Request()->getParam('id'))));
        try {
            foreach($details as $detail) {
                if (empty($detail['id'])) {
                    continue;
                }

                $model = Shopware()->Models()->find('Shopware\Models\Article\Esd', $detail['id']);
                if (!$model) {
                    continue;
                }
                Shopware()->Models()->remove($model);
            }

            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));
        } catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'error' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module.
     * Will be fired when the user deletes serials
     */
    public function deleteSerialsAction()
    {
        $esdId = $this->Request()->getParam('esdId');

        $details = $this->Request()->getParam('details', array(array('id' => $this->Request()->getParam('id'))));

        try {
            foreach($details as $detail) {
                if (empty($detail['id'])) {
                    continue;
                }

                $model = Shopware()->Models()->find('Shopware\Models\Article\EsdSerial', $detail['id']);
                if (!$model) {
                    continue;
                }
                Shopware()->Models()->remove($model);
            }

            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));

            // Update stock
            /** @var $esd \Shopware\Models\Article\Esd */
            $esd = Shopware()->Models()->getRepository('Shopware\Models\Article\Esd')->find($esdId);
            $freeSerialsCount = $this->getFreeSerialCount($esdId);
            $articleDetail = $esd->getArticleDetail();
            $articleDetail->setInStock($freeSerialsCount);
            Shopware()->Models()->flush();
        } catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'error' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module.
     * Deletes unused serialsnumbers
     */
    public function deleteUnusedSerialsAction()
    {
        $esdId = $this->Request()->getParam('esdId');

        $query = $this->getRepository()->getUnusedSerialsByEsdQuery($esdId);
        $serials = $query->execute();
        $totalCount = count($serials);

        foreach ($serials as $serial) {
            $this->getManager()->remove($serial);
        }

        $this->getManager()->flush();

        $this->View()->assign(array(
            'total'   => $totalCount,
            'success' => true
        ));

        // Update stock
        /** @var $esd \Shopware\Models\Article\Esd */
        $esd = Shopware()->Models()->getRepository('Shopware\Models\Article\Esd')->find($esdId);
        $freeSerialsCount = $this->getFreeSerialCount($esdId);
        $articleDetail = $esd->getArticleDetail();
        $articleDetail->setInStock($freeSerialsCount);

        $this->getManager()->flush();
    }

    /**
     * Return number of free serials for given esdId
     *
     * @param int $esdId
     * @return int
     */
    public function getFreeSerialCount($esdId)
    {
        $query = $this->getRepository()->getFreeSerialsCountByEsdQuery($esdId);
        $result = $query->getSingleScalarResult();

        return $result;
    }

    /**
     * Event listener function of the article backend module.
     * Creates new serial numbers
     */
    public function saveSerialsAction()
    {
        try {
            $esdId = $this->Request()->getParam('esdId');

            /** @var $esd \Shopware\Models\Article\Esd */
            $esd = Shopware()->Models()->getRepository('Shopware\Models\Article\Esd')->find($esdId);

            if (!$esd) {
                $this->View()->assign(array(
                    'success' => false,
                    'message' => sprintf('ESD by id %s not found', $esdId)
                ));
                return;
            }

            $serials = $this->Request()->getParam('serials');

            // split string at newlines (WIN, Linux, OSX)
            $serials = preg_split('/$\R?^/m', $serials);

            // trim every serialnumber
            array_walk($serials, 'trim');

            // remove empty serialnumbers
            $serials = array_filter($serials);

            // remove duplicates
            $serials = array_unique($serials);

            $newSerials = 0;

            foreach ($serials as $serialnumber) {
                $serialnumber = trim($serialnumber);
                $serial = Shopware()->Models()->getRepository('Shopware\Models\Article\EsdSerial')->findOneBy(array('serialnumber' => $serialnumber));
                if ($serial) {
                    continue;
                }

                $serial = new \Shopware\Models\Article\EsdSerial();
                $serial->setSerialnumber($serialnumber);
                $serial->setEsd($esd);
                $this->getManager()->persist($serial);
                $newSerials++;
            }
            $this->getManager()->flush();

            // Update stock
            $freeSerialsCount = $this->getFreeSerialCount($esdId);
            $articleDetail = $esd->getArticleDetail();
            $articleDetail->setInStock($freeSerialsCount);

            $this->getManager()->flush();

            $this->View()->assign(array(
                'success' => true,
                'total' => $newSerials
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Event listener function of the article backend module.
     * Returns list of ESD-Files
     */
    public function getEsdFilesAction()
    {
        $filePath = Shopware()->DocPath('files_' . Shopware()->Config()->get('sESDKEY'));

        $result = array();
        foreach (new DirectoryIterator($filePath) as $file) {
            if ($file->isDot() || strpos($file->getFilename(), '.') === 0) {
                continue;
            }
            $result[] = array(
                'filename' => $file->getFilename()
            );
        }

        $this->View()->assign(array(
            'data' =>  $result,
            'total' =>  count($result),
            'success' => true
        ));
    }

    /**
     * Event listener function of the article backend module.
     * Uploads ESD-File
     */
    public function uploadEsdFileAction()
    {
        $destinationDir = Shopware()->DocPath('files_' . Shopware()->Config()->get('sESDKEY'));

        try {
            $fileBag = new \Symfony\Component\HttpFoundation\FileBag($_FILES);
            /** @var $file Symfony\Component\HttpFoundation\File\UploadedFile */
            $file = $fileBag->get('fileId');
            $file->move($destinationDir, $file->getClientOriginalName());
        } catch (Exception $e) {
            $this->View()->assign(array('success' => false, 'message' => $e->getMessage()));
            return;
        }
        if ($file === null) {
            $this->View()->assign(array('success' => false));
            return;
        }

        $this->View()->assign(array('success' => true));
    }

    /**
     * Event listener function of the article backend module.
     * Downloads ESD-File
     */
    public function getEsdDownloadAction()
    {
        $filename = $this->Request()->getParam('filename');
        $file = 'files/' . Shopware()->Config()->get('sESDKEY') . '/' . $filename;

        if (!file_exists(Shopware()->OldPath() . $file)) {
            $this->View()->assign(array(
                'message' => 'File not found',
                'success' => false
            ));

            return;
        }
        $this->redirect($file);
    }

    /**
     * Event listener function of the article backend module.
     * Returns statistical data
     */
    public function getChartData()
    {
        $articleId = $this->Request()->getParam('articleId');
        $format = 'month';

        if ($format == 'month') {
            $dateFormat = '%Y%m';
            $limit = 12;
        } else {
            $dateFormat = '%Y%m%d';
            $limit = 32;
        }

        $sql = sprintf("
            SELECT
                SUM(price*quantity) AS revenue,
                SUM(quantity) AS orders,
                DATE_FORMAT(ordertime, '%s') as groupdate,
                WEEK(ordertime) as week,
                MONTH(ordertime) as month,
                ordertime as date
            FROM s_order_details, s_order
            WHERE articleID = ?
                AND s_order.id = s_order_details.orderID
                AND s_order.status != 4
                AND s_order.status != -1
            GROUP BY groupdate
            ORDER BY groupdate ASC
            LIMIT %d
        ", $dateFormat, $limit);

        $stmt = Shopware()->Db()->query($sql, $articleId);
        $result = $stmt->fetchAll();

        $this->View()->assign(array(
            'data' =>  $result,
            'total' =>  count($result),
            'success' => true
        ));
    }

    /**
     * The "regenerateVariantOrderNumbersAction" allows the user to recreate
     * the article variant order number with an own number syntax.
     * Called from the article backend module.
     */
    public function regenerateVariantOrderNumbersAction()
    {
        try {
            $data = $this->Request()->getParams();
            $articleId = $data['articleId'];
            $syntax = $data['syntax'];
            if (!$articleId > 0 || strlen($syntax) === 0) {
                return;
            }

            $article = $this->getRepository()
                            ->getArticleWithVariantsAndOptionsQuery($articleId)
                            ->getOneOrNullResult(\Doctrine\ORM\AbstractQuery::HYDRATE_OBJECT);

            $abortId = $article->getMainDetail()->getId();
            $commands = $this->prepareNumberSyntax($syntax);
            $details = $article->getDetails();

            $counter = 1;
            /** @var $detail \Shopware\Models\Article\Detail */
            foreach ($details as $detail) {
                if ($detail->getId() === $abortId) {
                    continue;
                }
                $number = $this->interpretNumberSyntax($article, $detail, $commands, $counter);
                $counter++;
                if (strlen($number) === 0) {
                    continue;
                }
                $detail->setNumber($number);
                Shopware()->Models()->persist($detail);
            }
            Shopware()->Models()->flush();
            $this->View()->assign(array(
                'success' => true
            ));
        }
        catch (Exception $e) {
            $this->View()->assign(array(
                'success' => false,
                'message' => $e->getMessage()
            ));
        }
    }

    /**
     * Start function for number generation. Iterates the different commands,
     * resolves the cursor or counter and starts the recursive
     *
     * @param $article
     * @param $detail
     * @param $commands
     * @param $counter
     * @return string
     */
    protected function interpretNumberSyntax($article, $detail, $commands, $counter)
    {
        $name = array();
        foreach($commands as $command) {
            //if the command isn't equals "n" we have to execute commands
            if ($command !== 'n') {
                //first we have to resolve the cursor object which used for the first command
                //the cursor object is set over the "prepareNumberSyntax" function.
                if ($command['cursor'] === 'detail') {
                    $cursor = $detail;
                } else {
                    $cursor = $article;
                }
                //call the recursive interpreter to resolve all commands
                $name[] = $this->recursiveInterpreter($cursor, 0, $command['commands']);
            } else {
                $name[] = $counter;
            }
        }
        //return all command results, concat with a dot
        return implode('.', $name);
    }

    /**
     * This function executes the different commands for the number regeneration function.
     * First the function executes the current command on the passed cursor object.
     * If the result is traversable
     *
     * @param $cursor
     * @param $index
     * @param $commands
     * @return string
     */
    protected function recursiveInterpreter($cursor, $index, $commands)
    {
        if (!is_object($cursor)) {
            return '';
        }

        //first we execute the current command on the cursor object
        $result = $cursor->$commands[$index]();
        //now we increment the command index
        $index++;

        //if the result of the current command on the cursor is an array
        if ($result instanceof \Traversable) {
            //we have to execute the following command on each array element.
            $results = array();
            foreach($result as $object) {
                $results[] = $this->recursiveInterpreter($object, $index, $commands);
            }
            return implode('.', $results);

        //if the result of the current command on the cursor is an object
        } elseif (is_object($result)) {
            //we have to execute the next command on the result
            return $this->recursiveInterpreter($result, $index, $commands);

        //otherwise we can return directly.
        } else {
            return $result;
        }

    }

    /**
     * Prepares the passed number syntax. Executes a regular expression
     * to get all syntax commands and maps this commands to the rout
     * object.
     * @param $syntax
     * @return array
     */
    protected function prepareNumberSyntax($syntax)
    {
        preg_match_all('#\{(.*?)\}#msi', $syntax, $result);
        $syntax = $result[1];

        $properties = array();
        foreach($syntax as $path) {
            if ($path !== 'n') {
                $properties[] = $this->getCommandMapping($path);
            } else {
                $properties[] = $path;
            }
        }
        return $properties;
    }

    /**
     * Internal helper function which helps the get the cursor object for the passed syntax command.
     */
    protected function getCommandMapping($syntax)
    {
        //we have to explode the current command to resolve the multiple properties.
        $paths = explode('.', $syntax);

        //we have to map the different properties to define the start cursor object.
        switch($paths[0]) {
            //options are only available for the different article variants
            case "options":
                $cursor = 'detail';
                $paths[0] = "configuratorOptions";
                break;
            //all other commands will rout to the article
            default:
                $cursor = 'article';
        }

        $commands = array();

        //now we convert the property names to the getter functions.
        foreach($paths as $path) {
            $commands[] = 'get' . ucfirst($path);
        }

        return array(
            'cursor' => $cursor,
            'commands' => $commands
        );
    }

    /**
     * Event listener function of the article backend module.
     * Returns statistical data
     */
    public function getStatisticAction()
    {
        $articleId = $this->Request()->getParam('articleId');

        if ($this->Request()->getParam('chart', false)) {
            return $this->getChartData();
        }

        $startDate = $this->Request()->getParam('fromDate', date("Y-m-d", mktime(0, 0, 0, date("m"), 1, date("Y"))));
        $endDate   = $this->Request()->getParam('toDate', date("Y-m-d"));

        $sql = "
            SELECT
            SUM(price*quantity) AS revenue,
            SUM(quantity) AS orders,
            MONTH(ordertime) as month,
            DATE_FORMAT(ordertime, '%Y-%m-%d') as date
            FROM s_order_details, s_order
            WHERE articleID = :articleId
            AND s_order.id = s_order_details.orderID
            AND s_order.status != 4
            AND s_order.status != -1
            AND TO_DAYS(ordertime) <= TO_DAYS(:endDate)
            AND TO_DAYS(ordertime) >= TO_DAYS(:startDate)
            GROUP BY TO_DAYS(ordertime)
            ORDER BY ordertime DESC
        ";

        $stmt = Shopware()->Db()->query($sql, array(
            'endDate'   => $endDate,
            'startDate' => $startDate,
            'articleId' => $articleId,
        ));
        $result = $stmt->fetchAll();

        $this->View()->assign(array(
            'data' => $result,
            'total' => count($result),
            'success' => true
        ));
    }

    /**
     * Remote validator for the article order number field.
     * The passed value must be set and the number must be unique
     *
     * @return string|void returns the string "true" if valid, nothing otherwise
     */
    public function validateNumberAction()
    {
        Shopware()->Plugins()->Controller()->ViewRenderer()->setNoRender();

        $exist = $this->getRepository()
                      ->getValidateNumberQuery($this->Request()->value, $this->Request()->param)
                      ->getArrayResult();

        if (empty($exist) && strlen($this->Request()->value) > 0) {
            echo 'true';
        } else {
            return;
        }
    }

    /**
     * Event listener function of the backend module. Fired when the user select a shop in the shop combo in the option
     * panel of the sidebar and clicks on the "preview" button to display the article details in the store front.
     */
    public function previewDetailAction()
    {
   		$shopId = (int)$this->Request()->getParam('shopId');
   		$articleId = (int)$this->Request()->getParam('articleId');

        $repository = Shopware()->Models()->getRepository('Shopware\Models\Shop\Shop');
        $shop = $repository->getActiveById($shopId);
        $shop->registerResources(Shopware()->Bootstrap());

   		Shopware()->Session()->Admin = true;

   		$url = $this->Front()->Router()->assemble(array(
           'module' => 'frontend',
           'controller' => 'detail',
           'sArticle' => $articleId,
           'appendSession' => true
        ));

   		$this->redirect($url);
   	}

    /**
     * Internal helper function to get the field names of the passed violation array.
     * @param $violations \Symfony\Component\Validator\ConstraintViolationList
     * @return string
     */
    protected function getViolationFields($violations)
    {
        $fields = array();
        /**@var $violation Symfony\Component\Validator\ConstraintViolation*/
        foreach($violations as $violation) {
            $fields[] = $violation->getPropertyPath();
        }
        return $fields;
    }
}