<?php

/**
 * Used to make ORM objects interact efficiently with the CMS
 */
interface ICatalyzContentProvider {
    public function updateFromRequest($node, $request, $previewMode);
    public function getValidSubClasses();
    public static function getNodeClassCaption();
    public static function getNodeClassDescription();
    static public function getLayoutAreas();
}

/**
 * Used to add more customization capabilities to the CMS from objects
 */
interface ICatalyzAdvancedContentProvider extends ICatalyzContentProvider
{
    public function needsLayout($node, $request);
    public function getTemplateName($node, $request);
}

/**
 * Used to define that a node is always hidden from menu.
 *
 * Example:
 *                         - News
 */
interface ICatalyzHiddenNode extends ICatalyzContentProvider
{
}

interface ICatalyzSubNode extends ICatalyzContentProvider {
}

interface ICatalyzSlotProvider extends ICatalyzContentProvider {
    public function getSlot($name);
}

interface ICatalyzTranslatable extends ICatalyzContentProvider
{
    public function removeTranslation($language);
    public function createTranslation($newLanguage);
}

interface ICatalyzRedirectToParentNodeOnceCreated extends ICatalyzContentProvider
{
}
interface ICatalyzInsertOnTop extends ICatalyzContentProvider {
}

/**
 * Allow nodes to control action permissions
 */
interface ICatalyzToolbarActionManager extends ICatalyzContentProvider {
    public function canAddChild($default);
    public function canEdit();
    public function canReorderChildren($default);
    public function canDelete();
}

/**
 * Allow nodes to add new actions in Catalyz Toolbar
 */
interface ICatalyzToolbarActionProvider extends ICatalyzContentProvider
{
    /**
     * Return all actions to include in Catalyz Toolbar
     *
     * @return array With the following keys:
     * - key: unique action identifier (used to add another action near it)
     * - url: when to redirect user to
     * - caption: caption to display in the toolbar
     * - before/after: name of action where to insert the action
     *
     * Example: array(array(
     *                   'key' => 'foo',
     *                   'caption' => 'Foo',
     *                   'url' => 'http:///www.foobar.com',
     *                   'after' => 'edit'
     *
     * Standard actions keys are:
     * - add
     * - edit
     * - translation
     * - reorder
     * - delete
     */
    public function getToolbarActions();
}

/**
 * Allow nodes to tweak action names
 *
 * Actions can return false to use default value
 */
interface ICatalyzToolbarActionStringManager extends ICatalyzContentProvider
{
    public function getCatalyzToolbarAddCaption();
    public function getCatalyzToolbarReorderCaption();
}

/**
 * Allow nodes to define new actions in the Edit page
 */
interface ICatalyzEditActionManager extends ICatalyzContentProvider
{
    public function getEditActions();
    public function handleEditActionIfAny($page, $node, $request);
}
interface ICatalyzLayoutProvider extends ICatalyzContentProvider
{
    public function getAvailableLayouts();
}
interface ICatalyzContentEditStringManager extends ICatalyzContentProvider
{
    public function getCatalyzContentTitleCaption();
}
interface ICatalyzDefaultContentProvider extends ICatalyzContentProvider
{
    public function getDefaultContent($field);
}
interface ICatalyzContentSearchable extends ICatalyzContentProvider
{
    public function getNodeTitle();
    public static function createSearchService();
}

/**
 * Used to define that node can handle different contexts based on request
 * content, using the initializeContext method object will be able to
 * initialize itself and display the right content.
 *
 * Exemple:
 * - NewsList can offer last X news by default and achive of a specific month on request
 */
interface ICatalyzContextualNode extends ICatalyzContentProvider
{
    const VIEW = 1;
    const EDIT = 2;
    public function initializeContext($request, $mode);
}
interface ICatalyzUrlProvider
{
    static public function getUrl($ContentTreeNode, $defaultUrl);
}
interface ICatalyzOpenTargetController
{
    static public function getOpenTarget($ContentTreeNode);
}
class catalyzPageActions extends sfActions
{
    public function executeIndex($request)
    {
        $this->forward404();
    }
    public function executeView($request)
    {
        $this->page = sfContext::getInstance()->get('page');
        $this->setLayout($this->page->getNode()->getCurrentLayoutNameForAction());
        $this->getResponse()->setTitle($this->page->getNode()->getBrowserTitle());
        return sfView::SUCCESS;
    }
}

class ContentTreeNodeIterator {
    /**
     *
     * @var ContentTree
     */
    var $ContentTree;
    /**
     *
     * @var ContentTreeNode
     */
    var $current;

	var $started = false;
    function __construct($ContentTree)
    {
        $this->ContentTree = $ContentTree;
        $this->rewind();
    }
    public function key()
    {
        return $this->current->getId();
    }
    public function valid()
    {
        return !empty($this->current);
    }
    protected function getNextNode($node)
    {
        if (!$node) {
			return null;
        }
        if (count($node->children) > 0) {
			return $node->children[0];
        }
        if ($node->nextSibling) {
			return $node->nextSibling;
        }
        $parentNode = $node->parent;
        while ($parentNode) {
            if ($parentNode->nextSibling) {
				return $parentNode->nextSibling;
            }
            $parentNode = $parentNode->parent;
        }
		return null;
    }

    public function next()
    {
		if($this->started){
			$this->current = $this->getNextNode($this->current);
		}else{
			$this->started = true;
		}
        return $this->current;
    }
    public function rewind()
    {
    	$this->started = false;
        $this->current = $this->ContentTree->getRoot();
    }
    public function current()
    {
        return $this->current;
    }
}

class FilteredContentTreeNodeIterator extends ContentTreeNodeIterator {
    function __construct($ContentTree, $validClasses = array())
    {
		$this->validClasses = $validClasses;
		parent::__construct($ContentTree);
    }

	var $validClasses;

    protected function getNextNode($node)
    {
    	$result = $node;
        do {
            $result = parent::getNextNode($result);
        } while ($result && !$this->isNodeValid($result));
        return $result;
    }
	protected function isNodeValid($node){
		//var_dump($node->getNodeClass());
		return $node && in_array($node->getNodeClass(), $this->validClasses);
	}
	public function rewind()
	{
		$this->current = $this->ContentTree->getRoot();
		if(!$this->isNodeValid($this->current)){
			$this->current = $this->getNextNode($this->current);
		}
		$this->started = false;
	}
}

class Catalyz {
    static protected $pageCulture;

    static function getPageCulture($long_format = false)
    {
        if ($long_format) {
            return sprintf('%s_%s', self::$pageCulture, strtoupper(self::$pageCulture));
        }
        return self::$pageCulture;
    }

    static function setPageCulture($value)
    {
        self::$pageCulture = $value;
    }

    static public function getPageByNodeId($nodeId)
    {
        $ContentTree = ContentTree::instance();
        assert($ContentTree);
        $peerName = sprintf('Node%sPeer', $ContentTree->getContentTreeNodeById($nodeId)->getNodeClass());
        $peer = new $peerName();
        $criteria = new Criteria();
        $fieldName = eval(sprintf('return %s::NODE_ID;', $peerName));
        $criteria->add($fieldName, $nodeId);
        $criteria->setLimit(1);
        // $page = $peer->doSelectOne($criteria);
        $pages = $peer->doSelect($criteria); //WithI18N
        $page = array_shift($pages);
        // var_dump($peer);exit;
        return $page;
    }

    static $cache = array();
    static public function getPageByNodeIdAndClass($nodeId, $nodeClass, $culture = null)
    {
        if (!isset(self::$cache[$nodeId])) {
            $peerName = sprintf('Node%sPeer', $nodeClass);
            $peer = new $peerName();
            $criteria = new Criteria();
            $fieldName = eval(sprintf('return %s::NODE_ID;', $peerName));
            // var_dump($fieldName);exit;
            $criteria->add($fieldName, $nodeId);
            if (method_exists($peer, 'doSelectWithI18N')) {
                $criteria->setLimit(1);
                if (null == $culture) {
                    $culture = sfConfig::get('app_translations_default_language');
                }
                $pages = $peer->doSelectWithI18N($criteria, $culture);
                $page = array_shift($pages);
            } else {
                $page = $peer->doSelectOne($criteria);
            }
            self::$cache[$nodeId] = $page;
        }
        // var_dump(method_exists($peer, 'doSelectWithI18N'));exit;
        return self::$cache[$nodeId];
    }
    static public function getPagesByClasses($nodeClasses)
    {
        $criteria = new Criteria();
        $criteria->add(NodePeer::NODE_CLASS, $nodeClasses, Criteria::IN);
        return NodePeer::doSelectWithI18n($criteria);
    }
    static public function cachePageByNodeIdsAndClass($nodeIds, $nodeClass)
    {
        $nodeIdsToFetch = array();
        foreach ($nodeIds as $nodeId) {
            if (!isset(self::$cache[$nodeId])) {
                $nodeIdsToFetch[] = $nodeId;
            }
        }

        if (0 < count($nodeIds)) {
            $peerName = sprintf('Node%sPeer', $nodeClass);
            $peer = new $peerName();
            $criteria = new Criteria();
            $fieldName = eval(sprintf('return %s::NODE_ID;', $peerName));
            $criteria->add($fieldName, $nodeIds, Criteria::IN);
            if (method_exists($peer, 'doSelectWithI18N')) {
                $pages = $peer->doSelectWithI18N($criteria, sfConfig::get('app_translations_default_language'));
            } else {
                $pages = $peer->doSelect($criteria);
            }

            foreach ($pages as $page) {
                self::$cache[$page->getNodeId()] = $page;
            }
        }
    }

    static public function mysqlQuery($sql, $dbHandler = null)
    {
        $elapsedTime = 0;
        if (sfConfig::get('sf_debug') && sfConfig::get('sf_logging_enabled')) {
            $sqlTimer = sfTimerManager::getTimer('Database');
            $timer = new sfTimer();
        }

        $retval = mysql_query($sql, $dbHandler);

        if (sfConfig::get('sf_debug') && sfConfig::get('sf_logging_enabled')) {
            $sqlTimer->addTime();
            $elapsedTime = $timer->getElapsedTime();
        }
        $msg = sprintf("executeQuery(): [%.2f ms] %s", $elapsedTime * 1000, $sql);

        $dispatcher = sfProjectConfiguration::getActive()->getEventDispatcher();
        if ($dispatcher && sfConfig::get('sf_logging_enabled')) {
            // message on one line
            $msg = preg_replace("/\r?\n/", ' ', $msg);
            $dispatcher->notify(new sfEvent(null, 'application.log', array($msg)));
        }
        return $retval;
    }

    public static function getAllTranslations()
    {
        sfLoader::loadHelpers(array('I18N'), sfContext::getInstance()->getModuleName());
        $current_culture = sfConfig::get('app_translations_default_language');
        $result = array();
        $allLanguages = sfConfig::get('app_translations_available_languages');
        if (empty($allLanguages)) {
            trigger_error('Unable to find supported languauges for this site in app_translations_available_languages');
        } else {
            foreach($allLanguages as $iso) {
                $result[$iso] = format_language($iso, $current_culture);
            }
            asort($result);
        }
        return $result;
    }

    public static function registerRoutes(sfEvent $event)
    {
        $routing = $event->getSubject();
//        $routing->appendRoute('login', '/catalyz/login', array('module' => 'catalyz', 'action' => 'login'));
//        $routing->appendRoute('logout', '/catalyz/logout', array('module' => 'catalyz', 'action' => 'logout'));

        $routing->appendRoute('node-add', '/catalyz/node-add/:parent_id', array('module' => 'catalyz', 'action' => 'nodeAdd'), array('parent_id' => '^\d+$'));
        $routing->appendRoute('node-add-class', '/catalyz/node-add/:parent_id/:page_type', array('module' => 'catalyz', 'action' => 'nodeEdit'), array('parent_id' => '^\d+$'));
        $routing->appendRoute('node-edit-new', '/catalyz/node-edit', array('module' => 'catalyz', 'action' => 'nodeEdit'));
        $routing->appendRoute('node-edit', '/catalyz/node-edit/:node_id', array('module' => 'catalyz', 'action' => 'nodeEdit'), array('node_id' => '^\d+$'));
        $routing->appendRoute('node-reorder', '/catalyz/node-reorder/:node_id', array('module' => 'catalyz', 'action' => 'nodeReorder'), array('node_id' => '^\d+$'));
        $routing->appendRoute('node-reorganize', '/catalyz/node-reorganize/:node_id', array('module' => 'catalyz', 'action' => 'nodeReorganize'), array('node_id' => '^\d+$'));
        $routing->appendRoute('node-reorganize-update', '/catalyz/node-reorganize-update', array('module' => 'catalyz', 'action' => 'nodeReorganizeUpdate'), array());
        $routing->appendRoute('node-delete', '/catalyz/node-delete/:node_id', array('module' => 'catalyz', 'action' => 'nodeDelete'), array('node_id' => '^\d+$'));

        $routing->appendRoute('node-translations', '/catalyz/node-translations/:node_id', array('module' => 'catalyz', 'action' => 'nodeManageLanguageVersions'), array('node_id' => '^\d+$'));
        $routing->appendRoute('node-translation-add', '/catalyz/node-translation-add/:node_id', array('module' => 'catalyz', 'action' => 'nodeAddLanguageVersion'), array('node_id' => '^\d+$'));
        $routing->appendRoute('node-translation-add-and-edit', '/catalyz/node-translation-add/:node_id/:language/:edit', array('module' => 'catalyz', 'action' => 'nodeAddLanguageVersion', 'edit' => 1), array('node_id' => '^\d+$'));
        $routing->appendRoute('node-translation-edit', '/catalyz/node-translation-edit/:node_id/:culture', array('module' => 'catalyz', 'action' => 'nodeEdit'), array('node_id' => '^\d+$', 'culture' => '^[a-z_]+$'));
        $routing->appendRoute('node-translation-delete', '/catalyz/node-translation-delete/:node_id/:culture', array('module' => 'catalyz', 'action' => 'nodeDeleteLanguageVersion'), array('node_id' => '^\d+$', 'culture' => '^[a-z_]+$'));
        $routing->appendRoute('node-translation-overview', '/catalyz/node-translation-overview/:node_id', array('module' => 'catalyz', 'action' => 'nodeTranslationOverview'), array('node_id' => '^\d+$'));

        $routing->appendRoute('cache-status', '/catalyz/cache-status', array('module' => 'catalyz', 'action' => 'cacheStatus'));
        $routing->appendRoute('cache-clear', '/catalyz/cache-clear', array('module' => 'catalyz', 'action' => 'cacheClear'));
        $routing->appendRoute('cache-refresh', '/catalyz/cache-refresh', array('module' => 'catalyz', 'action' => 'cacheRefresh'));
        $routing->appendRoute('cache-create', '/catalyz/cache-create', array('module' => 'catalyz', 'action' => 'cacheCreate'));
        $routing->appendRoute('node-cache-clear', '/catalyz/node-cache-clear/:node_id/:culture', array('module' => 'catalyz', 'action' => 'cacheClear'), array('node_id' => '^\d+$', 'culture' => '^[a-z_]+$'));
        $routing->appendRoute('node-cache-create', '/catalyz/node-cache-create/:node_id/:culture', array('module' => 'catalyz', 'action' => 'cacheCreate'), array('node_id' => '^\d+$', 'culture' => '^[a-z_]+$'));

		$routing->appendRoute('admin', '/catalyz/admin/:node_id', array('module' => 'catalyz', 'action' => 'admin'), array('node_id' => '^\d+$'));

		$routing->appendRoute('sitemap-xml', '/sitemap.xml', array('module' => 'catalyz', 'action' => 'xmlSitemap'));

		$routing->appendRoute('catalyz', '/catalyz/:action/*', array('module' => 'catalyz'));
		$routing->appendRoute('homepage', '/*', array('module' => 'catalyz', 'action' => 'index'));

//        $routing->appendRoute('catalyz', '/catalyz/:action', array('module' => 'catalyz'));
        // $routing->prependRoute('default_symfony', '/symfony/:action/*', array('module' => 'default'));
    }

    public static function createTranslation($node, $newLanguage, $clearCache = true)
    {
        $default_locale = sfConfig::get('app_translations_default_language');
		$node->setUrlType($node->getUrlType($default_locale), $newLanguage);
		$node->setTitle($node->getTitle($default_locale), $newLanguage);
        $node->setSubTitle($node->getSubTitle($default_locale), $newLanguage);
        //$node->setUrlIdentifier($node->getUrlIdentifier($default_locale), $newLanguage);
		$node->setBrowserTitleContent($node->getBrowserTitleContent($default_locale), $newLanguage);
		$node->setSeoKeywordsContent($node->getSeoKeywordsContent($default_locale), $newLanguage);
		$node->setSeoDescriptionContent($node->getSeoDescriptionContent($default_locale), $newLanguage);

        $node->save();
        $page = $node->getPage();
        $page->createTranslation($newLanguage);
        $page->save();
        if ($clearCache) {
            Catalyz::clearTranslationCache($node->getId());
        }
    }

    public static function clearTranslationCache($nodeId)
    {
        $cacheManager = sfContext::getInstance()->getViewCacheManager();
        if ($cacheManager) {
            $cacheManager->remove('@sf_cache_partial?module=englishuk&action=_sidenav_translations&sf_cache_key=' . $nodeId);
        }
    }

    public static function normalizeUrlIdentifier($text)
    {
		// convert special characters
		$text = str_replace('ç', 'c', $text);
        $text = utf8_decode($text);
        $text = htmlentities($text);
        $text = preg_replace('/&([a-zA-Z])(uml|acute|grave|circ|tilde);/', '$1', $text);
        $text = html_entity_decode($text);
        // lower characters
        $text = strtolower($text);
        // strip all non word chars
        $text = preg_replace('/[^-\w\.]/', ' ', $text);
        // replace all white space sections with a separator
        $text = preg_replace('/\ +/', '-', $text);
        // trim separators
        $text = preg_replace('/(\-|\.)+/', '\1', $text);
        $text = trim($text, '-.');
        return $text;
    }

    public static function cleanText($text)
    {
        $text = utf8_decode($text);
        $text = htmlentities($text);
        $text = preg_replace('/&([a-zA-Z])(uml|acute|grave|circ|tilde);/', '$1', $text);
        $text = html_entity_decode($text);
        return $text;
    }

    public static function isTabEnabled($name)
    {
        return !in_array($name, sfConfig::get('app_catalyz_disabled_tabs', array()));
    }

    /**
     * Catalyz::renderText()
     *
     * @param mixed $page
     * @return
     */
    public static function renderText($content)
    {
        $content = CatalyzTextFilter::renderLinksToNodes($content);
        $content = CatalyzTextFilter::renderLightBoxImages($content);
        return $content;
    }

    /**
     * Catalyz::renderCollapseExpand()
     *
     * @param string $content
     * @return string
     */
    public static function renderCollapseExpand($content, $ContentTree)
    {
        return CatalyzTextFilter::renderCollapseExpand($content, $ContentTree);
    }

    public static function publishNode(sfEvent $event)
    {
        $node =/*(Node)*/ $event->getSubject();
        CatalyzCache::regenerate($node->getId(), $node->getCulture());
    }

	public static function initializeAdminActions(sfEvent $event){
		$page = $event->getSubject();
        CatalyzToolbar::instance()->register(array(
                'key' => 'cache',
                'caption' => __('Cache', null, 'catalyz'),
                'url' => sfContext::getInstance()->getController()->genUrl('@cache-status?node_id=' . $page->getNodeId())
                ), true);
		CatalyzToolbar::instance()->register(array(
		    'key' => 'move',
		    'caption' => __('Reorganize', null, 'catalyz'),
		    'url' => sfContext::getInstance()->getController()->genUrl('@node-reorganize?node_id=' . $page->getNodeId())
		    ), true);

	}

	function configureWebDebugToolbar(sfEvent $event){
		$webDebugToolbar = $event->getSubject();
		$webDebugToolbar->setPanel('CatalyzCMS', new sfWebDebugPanelCatalyzCMS($webDebugToolbar));

	}


	public static function getLoginHandler(){
		static $login_handler = null;
		if (is_null($login_handler)) {
			$className = sfConfig::get('app_login_handler');
        	$login_handler = new $className();
		}
		return $login_handler;
	}
}

?>