<?php
/**
 * Article model.
 *
 * @package koubel homepage
 * @author Miroslav Kubelik (koubel@volny.cz)
 * @copyright Copyright (c) 2007-2009 Miroslav Kubelik
 * @license http://opensource.org/licenses/gpl-license.php GNU GPL
 */

/**
 * Article model class
 *
 * @package koubel homepage
 * @author Miroslav Kubelik (koubel@volny.cz)
 * @copyright Copyright (c) 2007-2009 Miroslav Kubelik
 * @license http://opensource.org/licenses/gpl-license.php GNU GPL
 */
class Blog_Model_Article extends Blog_Model_Model
{
    /** articles per page */
    const ARTICLES_PERPAGE = 5;

    /**
     * article html cache element base identifier
     * @see HomeWebApp_View_Helper_TextRenderer
     */
    const HTML_CACHE = "articleHTML";

    /** identifier
     * @var int
     */
    private $_id;

    /** title for an article
     * @var string
     */
    private $_title;

    /** "niced" url identifier which is derived format of the title
     * @var string
     */
    private $_titleUrl;

    /** article text
     * @var string
     */
    private $_text;

    /** article plain text, without all tags
     * @var string
     */
    private $_plainText;

    /**
     * format for the text
     * @var string one of the Dao_Article::FORMAT constants
     */
    private $_format;

    /** creation datetime for the article
     * @var string the format is mostly Zend_Date::ISO_8601
     */
    private $_posted;

    /** author identifier
     * @var int
     */
    private $_authorId;

    /** count of comments for the article
     * @var int
     */
    private $_commentCount;

    /** count of trackbacks for the article
     * @var int
     */
    private $_trackBackCount;

    /** article author name
     * @var string
     */
    private $_authorName;

    /** article rating
     * @var Blog_Model_ArticleRatig
     */
    private $_rating;

    /** count of views
     * @var int
     */
    private $_viewCount;

    protected function _setDefaultDao()
    {
        $this->setDao(new Blog_Model_Dao_Article());
    }

    public function getId()
    {
        return $this->_id;
    }

    public function setId($id)
    {
        $this->_id=$id;
    }

    /**
     * gets identifier for the xhmtl cache entry
     * @return string|null identifier, is null when the article hasn't database
     * identifier
     */
    public function getHTMLCacheId()
    {
        return $this->_id ? self::HTML_CACHE.$this->_id : null;
    }

    public function getTitle()
    {
        return $this->_title;
    }

    public function setTitle($title)
    {
        $this->_title=$title;
    }

    public function getTitleUrl()
    {
        return $this->_titleUrl;
    }

    public function setTitleUrl($titleUrl)
    {
        $this->_titleUrl=$titleUrl;
    }

    public function getText()
    {
        return $this->_text;
    }

    public function setText($text)
    {
        $this->_text=$text;
    }

    public function getPlainText()
    {
        return $this->_plainText;
    }

    public function setPlainText($text)
    {
        $this->_plainText=$text;
    }

    public function getPosted()
    {
        return $this->_posted;
    }

    public function setPosted($posted)
    {
        $this->_posted=$posted;
    }

    public function getAuthorId()
    {
        return $this->_authorId;
    }

    public function setAuthorId($id)
    {
        $this->_authorId=$id;
    }

    public function getFormat()
    {
        return $this->_format;
    }

    public function setFormat($format)
    {
        $this->_format=$format;
    }

    public function getViewCount()
    {
        return $this->_viewCount;
    }

    /**
     * Gets all available formats
     * @return array
     */
    public static function getAvailFormats()
    {
        return array(Blog_Model_Dao_Article::FORMAT_HTML, Blog_Model_Dao_Article::FORMAT_WIKI);
    }

    public function getCommentCount()
    {
        if (!isset($this->_commentCount)) {
            $comment = new Blog_Model_Comment();
            $this->_commentCount = $comment->getCountByArticle($this);
        }
        return $this->_commentCount;
    }

    public function getTrackBackCount()
    {
        if (!isset($this->_trackBackCount)) {
            $trackBack = new Blog_Model_TrackBackEntry();
            $this->_trackBackCount = $trackBack->getCountByArticle($this);
        }
        return $this->_trackBackCount;
    }

    public function getAuthorName()
    {
        if (!isset($this->_authorName)) {
            $this->getAuthor();
        }
        return $this->_authorName;
    }

    public function getAuthor()
    {
        $author = new Blog_Model_Author();
        $author->setByArticle($this);

        $this->_authorName = $author->getUsername();

        return $author;
    }

    /**
     * Gets rating for an article
     * @return Blog_Model_ArticleRating
     */
    public function getRating()
    {
        if (!isset($this->_rating)) {
            $this->_rating = new Blog_Model_ArticleRating();
            $this->_rating->setArticleId($this->getId());
        }

        return $this->_rating;
    }

    public function setByComment(Blog_Model_Comment $comment)
    {
        $currentComment = $comment->getDaoRow();
        $articleRow = $currentComment->findParentRow("Blog_Model_Dao_Article");
        $this->_setFromDaoRow($articleRow->toArray());
    }

    public function getAll($pageNo=null,$perPageItems=self::ARTICLES_PERPAGE,
        $extendedInfo=null)
    {
        $rows=$this->getDao()->fetchAllItems($pageNo,$perPageItems,$extendedInfo);

        return $this->_getSet($rows);
    }

    /**
     * Return paginator from {@link Model::$dao}
     * @return Zend_Paginator
     */
    public function getPaginator() {
        return $this->getDao()->getPaginator();
    }

    protected function _setPropFromArray(&$arr)
    {
        foreach($arr as $key=>$val) {
            $this->{"_".$key}=$val;
        }
    }

    public function findByTitleUrl($titleUrl)
    {
        $row = $this->getDao()->findByTitleUrl($titleUrl);

        if (is_null($row)) {
            return false;
        } else {
            $this->_setFromDaoRow($row->toArray());
            return true;
        }
    }

    public function findById($id)
    {
        $row = $this->getDao()->find($id)->current();

        if (is_null($row)) {
            return false;
        } else {
            $this->_setFromDaoRow($row->toArray());
            return true;
        }
    }

    public function insert()
    {
        $this->_id = $this->getDao()->insert(array(
            "title"=>$this->_title,
            "titleURL"=>$this->_titleUrl,
            "author_id"=>$this->_authorId,
            "posted"=>$this->_posted,
            "text"=>$this->_text,
            "plainText"=>$this->_plainText,
            "format"=>$this->_format
        ));
    }

    public function update()
    {
        $updArr = array(
            "author_id"=>$this->_authorId,
            "title"=>$this->_title,
            "titleUrl"=>$this->_titleUrl,
            "posted"=>$this->_posted,
            "text"=>$this->_text,
            "plainText"=>$this->_plainText,
            "format"=>$this->_format,
        );

        if (isset($this->_viewCount)) {
            $updArr['viewCount']=$this->_viewCount;
        }

        $this->getDao()->update($updArr,"id={$this->_id}");
    }

    public function delete()
    {
        $this->getDao()->delete("id=$this->_id");
    }

    public function addToCategoriesbyArrId($categoriesArrId)
    {
        $categoryArticleDao = new Blog_Model_Dao_CategoryArticle();
        foreach($categoriesArrId as $categoryId) {
            $categoryArticleDao->insert(array("article_id"=>$this->_id,"category_id"=>$categoryId));
        }
    }

    public function updateCategories($newCategoriesIds)
    {
        $currentArticleRow = $this->getDaoRow();

        //get current article categories IDs
        $currentArticleCategories = $currentArticleRow->findManyToManyRowset(
            'Blog_Model_Dao_Category', 'Blog_Model_Dao_CategoryArticle');
        $currentIds = array();
        foreach($currentArticleCategories as $category) {
            $currentIds[]=$category->id;
        }

        $categoryArticleDao = new Blog_Model_Dao_CategoryArticle();

        //new categories
        $diff = array_diff($newCategoriesIds,$currentIds);
        foreach($diff as $catId) {
            $categoryArticleDao->insert(
                array("category_id" => $catId, "article_id" => $this->_id)
            );
        }

        //old caregories - categories which were assigned and now aren't, these
        //will be removed
        $diff = array_diff($currentIds,$newCategoriesIds);
        foreach($diff as $catId) {
            $categoryArticleDao->delete("category_id=$catId AND article_id=$this->_id");
        }
    }

    /**
     * Gets database row object which corresponds our current properties.
     * It is used mostly for the operation with the relationship. Hence we need
     * to set only keys
     * @param bool $keyOnly true - sets only key properties (primary and foreign) it is
     * enough in most situations, false - sets all properties
     * @return Zend_Db_Table_Row_Abstract
     */
    public function getDaoRow($keyOnly=true)
    {
        $data = array(
            "id"=>$this->_id,
            "author_id"=>$this->_authorId
        );

        if (!$keyOnly) {
            $data["title"]=$this->_title;
            $data["titleURL"]=$this->_titleUrl;
            $data["posted"]=$this->_posted;
            $data["text"]=$this->_text;
            $data["format"]=$this->_format;
        }

        $row = $this->getDao()->createRow($data);
        return $row;
    }

    public function getCategories()
    {
        $category = new Blog_Model_Category();
        return $category->getByArticle($this);
    }

    /**
     * Gets articles in the specific category.
     *
     * findManyToManyRowset method cannot be used, because it doesn't support
     * the pagination
     *
     * @return array array of Blog_Model_Article objects
     */
    public function getByCategory(Blog_Model_Category $category,$pageNo=null,$perPageItems=null)
    {
        $categoryArticlesDao = new Blog_Model_Dao_CategoryArticle();
        $catoryArticlesRows = $categoryArticlesDao->getArticlesByCategory(
            $category->getId(),$pageNo,$perPageItems
        );

        //sets article paginator for a category only if we need some pages
        if ($pageNo && $perPageItems) {
            $category->setArticlePaginator($categoryArticlesDao->getPaginator());
        }
        
        return $this->_getSet($catoryArticlesRows);
    }

    /**
     * Gets all comments for the article
     * @return array array of Comment objects
     */
    public function getComments()
    {
        $comment = new Blog_Model_Comment();
        return $comment->getByArticle($this);
    }

    /**
     * Gets all trackbacks for the article
     * @return array array of Blog_Model_TrackBackEntry objects
     */
    public function getTrackBacks()
    {
        $trackBack = new Blog_Model_TrackBackEntry();
        return $trackBack->getByArticle($this);
    }

    /**
     * Gets average rating for the article
     * @return int
     */
    public function getAverageRating()
    {
        return $this->getRating()->getAverageRatingForArticle();
    }

    /**
     * Gets a voting count for the article
     * @return int
     */
    public function getVotingCount()
    {
        return $this->getRating()->getArticleVotingCount();
    }

    /**
     * Gets a rating info for the article
     * @return array
     * @see Blog_Model_Dao_ArticleRating::getRatingInfo()
     */
    public function getRatingInfo()
    {
        return $this->getRating()->getRatingInfo();
    }

    /**
     * Gets count of articles for the specific date.
     * Just proxy for the dao method call.
     * @param int $year (1970-1038 :-))
     * @param int $month (1-12)
     * @param int|null $day (1-31)
     * @returns int
     */
    public function getArchiveItemsCount($year,$month,$day)
    {
        return $this->getDao()->getArchiveItemsCount($year,$month,$day);
    }

    /**
     * Returns article list for the specific year, or month or day.
     * Just proxy for the dao method call. A pagination is supported.
     * @param int $year
     * @param int $month
     * @param int|null $day (1-31)
     * @param int $pageNo current page number, firts page has no. 1
     * @param int $perPageItems items per page
     * @return array result table as array
     */
    public function getArchiveItems($year,$month,$day,$pageNo=null,$perPageItems=null)
    {
        $rows = $this->getDao()->getArchiveItems($year,$month,$day,$pageNo,$perPageItems);

        return $this->_getSet($rows);
    }

    protected function _inflectDaoRowIndexes(&$arr)
    {
        $notAllowedIndexes = array("dao");

        $keys = array_keys($arr);
        foreach($keys as $key) {
            //unset not allowed indexes
            if (in_array($key,$notAllowedIndexes))
                throw new Exception("you cannot use $key index as the column index");

            switch ($key) {
            case "author_id":
                MKLib_Utils::renameKey($arr,$key,"authorId");
                break;
            case "titleURL":
                MKLib_Utils::renameKey($arr,$key,"titleUrl");
                break;
            default:
                break;
            }
        }
    }

    /**
     * Gets next article by posted date, one younger article.
     * @return null|Article null is returned if no article is younger then current,
     * current article is first article
     */
    public function getNext()
    {
        $daoRow = $this->getDao()->getNext($this->getPosted());
        if (is_null($daoRow)) return null;
        $nextItem = new Blog_Model_Article();
        $nextItem->_setFromDaoRow($daoRow->toArray());
        return $nextItem;
    }

    /**
     * Gets previous article by posted date, one older article.
     * @return null|Blog_Model_Article null is returned if no article is older then current,
     * current article is last article
     */
    public function getPrevious()
    {
        $daoRow = $this->getDao()->getPrevious($this->getPosted());
        if (is_null($daoRow)) return null;
        $previousItem = new Blog_Model_Article();
        $previousItem->_setFromDaoRow($daoRow->toArray());
        return $previousItem;
    }

    /**
     * Returns first paragraph from the article html text.
     * If none find returns whole article.
     * @param sting $articleText article text as the html
     * @return string
     */
    public static function getFirstParagraph($articleText)
    {
        preg_match("/<p>.*<\/p>/sU",$articleText,$matches);
        return empty($matches) ? $articleText : $matches[0] ;
    }


    /**
     * invalidate all cache entries for the current article
     */
    public function invalidateCache()
    {
        $cache = Zend_Controller_Front::getInstance()
            ->getParam('bootstrap')
            ->getResource("cache");

        if (!($cacheId=$this->getHTMLCacheId())) {
            throw new Exception("Cannot invalidate html cache entry for the article, cannot get
                html cache id");
        }

        $cache->remove($cacheId);
    }

    /**
     * Increments view count for the article
     * @param bool $saveDao if is true incremented view count is immediately updated
     * in the database
     */
    public function incViewCount($saveDao=false)
    {
        $this->_viewCount++;
        if ($saveDao) $this->getDao()->updateViewCount($this->_id,$this->_viewCount);
    }


    /**
     * Makes html special chars conversion for all text in the <code> tag for a whole
     * article text.
     * @param string $articleText article text as the html
     * @return string
     */
    public static function escapeCode($articleText)
    {
        $pattern = "/<code>((\\n|.)*)<\/code>/U";
        $text = preg_replace_callback($pattern,array('Blog_Model_Article', 'escapeCodeCallback'),$articleText);
        return $text;

        //alernative, but with non-transparent $replacement, stripslashes in a replacement
        //needed because /e regexp modifier slashes all ' "
        //$pattern = "/<code>((\\n|.)*)<\/code>/eU";
        //$replacement="'<code>'.htmlspecialchars(stripslashes('\\1'),ENT_NOQUOTES).'</code>'";
        //$text=preg_replace($pattern,$replacement,$articleText);
    }

    /**
     * Callback for the {@link escapeCode()}
     * @param array $matches array with regexp matches
     * @return string
     */
    public static function escapeCodeCallback($matches)
    {
        return "<code>".htmlspecialchars($matches[1])."</code>";
    }
}
