<?php
/**
 * Implementation of blog Index controller actions
 *
 * @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
 */

/**
 * Serve blog index actions
 *
 * @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_IndexController extends Zend_Controller_Action
{
    /**
     * cookie variable name for the serialized comment form
     * data
     */  
    const COMMENTCOOKIE = "commentInfo";

    /**
     * renders right panel
     */
    public function rightBlock()
    {
        //right block
        $category = new Blog_Model_Category();
        $this->view->allCategories = $category->getAll(null,null,array("article"));

        //search form
        $searchForm = new Blog_Form_Search();
        $searchForm->setAction($this->_helper->url->url())
            ->setMethod("get")
            ->setAttrib('id', 'searchForm');

        $searchText = $searchForm->getElement("searchText");
        if ($this->_hasParam($searchText->getName())) {
            if ($searchForm->isValid(array($searchText->getName() => $this->_getParam($searchText->getName())))) {
                //search object
                $search = Blog_Model_Search::factorySearch("SearchBlogArticle");
                //set the pagination
                $search->setPageParams(1,Blog_Model_Article::ARTICLES_PERPAGE);
                $search->setParams(array("searchText" => $this->_getParam("searchText")));
                $search->saveToSession();
                $this->_helper->redirector->gotoUrlAndExit($this->_helper->url->url(
                    array(),"blogSearchResult"), array("prependBase" => false));
                exit;
            }
        }

        $this->view->searchForm = $searchForm;

        //Renders rightblock into 'rightBlock' segment
        $response = $this->getResponse();
        $response->insert('rightBlock', $this->view->render('blogMenu.phtml'));

        //unset, because $this->view is used in other controller actions and must be
        //without any variables in these actions
        unset($this->view->allCategories);

        //workaround for the bug?? in the Zend_View_Helper_Action
        //this is necessary because blogMenu.phtml calls action view helper, which
        //calls the viewRenderer and its state (_noRender member variable) is changed
        //in this calls
        $this->_helper->viewRenderer->setNoRender(false);
    }

    /**
     * Action handler for index page.
     */
    public function indexAction()
    {
        $this->rightBlock();

        if ($this->_hasParam("entry")) {
            $this->_forward("view-entry");
            return;
        }

        $article = new Blog_Model_Article();

        $this->view->articles = $article->getAll(
            $this->_helper->Paginator->getPageNumber(),
            Blog_Model_Article::ARTICLES_PERPAGE,
            array("comment","author")
        );

        //paginator
        $this->view->paginator = $article->getPaginator();

        //page title
        $this->view->pageTitle="Blog";
    }

    /**
     * Action handler for one blog entry.
     * Serves add action for comments and view comments for the current article also.   
     */
    public function viewEntryAction()
    {
        $articleTitleUrl=$this->_getParam("entry");
        $currentArticle = new Blog_Model_Article();

        $found = $currentArticle->findByTitleUrl($articleTitleUrl);
        //false - article not found
        if (!$found) {
            throw new Zend_Controller_Action_Exception("No article found",404);
        }

        //right block
        $this->rightBlock();

        $this->view->article = $currentArticle;
        $this->view->author = $currentArticle->getAuthor();
        $this->view->comments = $currentArticle->getComments();
        $this->view->trackbacks = $currentArticle->getTrackBacks();
        $this->view->ratingInfo = $currentArticle->getRatingInfo();
        $this->view->trackbackRDF = $this->trackbackArticleRDF($currentArticle);
        
        //next and previous articles
        $this->view->nextArticle = $currentArticle->getNext();
        $this->view->previousArticle = $currentArticle->getPrevious();

        //check if already voted
        $articleRating = $currentArticle->getRating();
        $articleRating->setIpAddr($this->getRequest()->getServer("REMOTE_ADDR"));
        if ($this->getRequest()->has($articleRating->getCookieName())
            || $articleRating->wasVoted() )
        {
            $this->view->voted=true;
        } else {
            $this->view->voted=false;
        }

        //page title
        $this->view->pageTitle = $currentArticle->getTitle();

        $currentEntryUrl = $this->_helper->url->url(
            array("entry" => $currentArticle->getTitleUrl()),
            "blogArticle"
        );

        $this->view->trackBackUrl = $this->_helper->url->url(
            array("id"=>$currentArticle->getId()),"blogTrackBack"
        );

        //just alias for $this->_helper->flashMessenger
        $messanger=$this->_helper->getHelper('FlashMessenger');

        //comment form
        $commentForm = new Blog_Form_Comment();
        $commentForm->setAction($currentEntryUrl)
            ->setMethod('post')
            ->setAttrib('id', 'commentForm');

        $this->view->commentForm = $commentForm;

        //form was send, try validate and save result
        if ($this->_request->isPost()) {
            if ($commentForm->isValid($this->_request->getPost())) {
                $values = $commentForm->getValues();

                $username = htmlspecialchars($values["username"]);
                $email = $values['email'];
                $commentMessage = $values['message'];
                $commentMessage = Blog_Model_Comment::prepareCommentText($commentMessage);

                $www = $values['www'];
                if (!empty($www) && !preg_match("|^http(s)?://|i",$www)) {
                    $www = "http://".$www;
                }

                //try to save result into Db
                try {
                    $comment = new Blog_Model_Comment();
                    $comment->setArticleId($currentArticle->getId());
                    $comment->setMessage($commentMessage);
                    $comment->setAuthorName($username);
                    $comment->setAuthorEmail($email);
                    $comment->setAuthorWWW($www);

                    //insert new item into the data source
                    $comment->insert();

                    //sets cookie with comment dialog info, will be used as source for the
                    //comment form predefined variables
                    self::setCommentCookie($username,$email,$www);
                } catch (Zend_Db_Exception $e) {

                    $messages[]="Sorry, cannot save record comment, database error was occured"
                        . ($this->getInvokeArg("bootstrap")->getEnvironment() == "development"
                            ? ":". $e->getMessage() 
                            : "."
                           );
                    $this->view->messages = $messages;
                    return;
                }

                //some OK message, actually not necessary
                $this->_helper->redirector->setGotoUrl($currentEntryUrl,array("prependBase"=>false));
                $this->_helper->redirector->redirectAndExit();
            }
        } else {
            //form wasn't submited it means normal article view, we can add pageview
            //for the article
            $currentArticle->incViewCount(true);

            //sets form values from the cookies if these are presented
            if ($this->getRequest()->has(self::COMMENTCOOKIE)) {
                $values = unserialize($this->getRequest()->getCookie(self::COMMENTCOOKIE));
                $commentForm->username->setValue($values['username']);
                $commentForm->email->setValue($values['mainEmail']);
                $commentForm->www->setValue($values['www']);
            }
        }

        if ($messanger->hasMessages()) {
            $this->view->messages = $messanger->getMessages();
        }
    }

    /**
     * Action hadler for a view of articles category.
     */
    public function viewCategoryAction()
    {
        $category = new Blog_Model_Category();

        //try to find requested category
        if (! $category->findByTitleUrl($this->_getParam("name"))) {
            throw new Zend_Controller_Action_Exception("No category found",404);
        }

        //right block
        $this->rightBlock();

        $this->view->category = $category;

        //page title
        $this->view->pageTitle = $this->view->trans("Articles in the category")." "
            .$this->view->escape($category->getName());

        //category articles in the current page
        $this->view->articles = $category->getArticles(
            $this->_helper->Paginator->getPageNumber(),
            Blog_Model_Article::ARTICLES_PERPAGE
        );

        $this->view->paginator = $category->getArticlePaginator();
    }

    /**
     * Builds and prints RSS feed for some count if recent articles.
     * Sets "isFeed" parameter, which says to controller plugin that this
     * response isn't part of the normal site template.
     */
    public function feedAction()
    {
        // Don't auto render
        $this->_helper->viewRenderer->setNoRender();
        // Disable layout
        $this->_helper->layout->disableLayout();
        $article = new Blog_Model_Article();
        $recentArticles = $article->getAll(1,10);

        $lastUpdate = current($recentArticles)->getPosted();
        
        $baseUri = MKLib_AppBaseUrl::getUri();

        //prepare an array for a feed
        $feedArray = array(
            "title" => "Mirin's blog",
            "link" => $baseUri . $this->_helper->url->url(array(),"blogFeed"),
            "lastUpdate" => strtotime($lastUpdate),
            "charset" => "utf-8",
            "description" => "Blog about coding, and other stuff",
            "author" => "Mirek Kubelik",
            "email" => "koubel@volny.cz",
            "copyright" => "Koubel, all rights reserved",
            "generator" => 'Zend Framework Zend_Feed',
            "language" => "cs",
            'entries' => array()
        );

        foreach ($recentArticles as $article) {
            //get article text in the html 
            $this->view->article = $article;
            $articleHtml = $this->view->render("pureHtml.phtml");
            unset($this->view->article);

            $articleUrlUnique = $baseUri . $this->_helper->url->url(
                array("entry" => $article->getTitleUrl()),"blogArticle"
            );

            //resolves local links like <a href="/homepage/blog">link</a> into
            //<a href="http://mysite/homepage/blog">link</a>
            $articleHtml=preg_replace("/\s(href|src)=\"\//"," $1=\"$baseUri/",$articleHtml);

            //resolves local links for the footnoteslike <a href="#fnt">link</a> into
            //<a href="http://mysite/homepage/blog#fnt">link</a>
            $articleHtml=preg_replace("/<a href=\"#/","<a href=\"$articleUrlUnique#",
                $articleHtml);

            $feedArray['entries'][] = array(
                "guid" => $articleUrlUnique,
                "title" => $article->getTitle(),
                "link" => $articleUrlUnique,
                "description" => strip_tags(Blog_Model_Article::getFirstParagraph($articleHtml)),
                "lastUpdate" => strtotime($article->getPosted()),
                "content" => Blog_Model_Article::escapeCode($articleHtml)
            );
        }

        $feed = Zend_Feed::importArray($feedArray, 'rss');
        $feed->send();

        //response is feed
        $this->_setParam("isFeed",true);
    }

    /**
     * Builds and print RSS feed for some recent comments.
     * Sets "isFeed" parameter, which says to controller plugin that this
     * response isn't part of the normal template.
     */
    public function commentFeedAction()
    {
        // Don't auto render
        $this->_helper->viewRenderer->setNoRender();
        // Disable layout
        $this->_helper->layout->disableLayout();
        $comment = new Blog_Model_Comment();
        //last 20 recent comments
        $recentComments = $comment->getAll(1,20);

        $lastUpdate = current($recentComments)->getPosted();

        $baseUri = MKLib_AppBaseUrl::getUri();

        //prepare an array for a feed
        $feedArray = array(
            "title" => "Mirin's blog - recent comments",
            "link" => $baseUri . $this->_helper->url->url(array(),"blogCommentFeed"),
            "lastUpdate" => strtotime($lastUpdate),
            "charset" => "utf-8",
            "description" => "recent comments in Mirin's blog",
            "author" => "Mirek Kubelik",
            "email" => "koubel@volny.cz",
            "copyright" => "Koubel, all rights reserved",
            "generator" => 'Zend Framework Zend_Feed',
            "language" => "cs",
            'entries' => array()
        );

        foreach ($recentComments as $comment) {
            $relatedArticle = $comment->getArticle();
            //comment order in all article comments
            $order = $comment->getOrderforArticle();
            $message = Blog_Model_Comment::commentBody( $comment->getMessage() );

            //unique comment URL, used for the guid and link
            $commentUrlUnique = $baseUri . $this->_helper->url->url(
                array("entry" => $relatedArticle->getTitleUrl()),"blogArticle"
            )."#comment$order";

            $feedArray['entries'][] = array(
                "guid" => $commentUrlUnique,
                "title" => $comment->getAuthorName()
                . " ("
                . $relatedArticle->getTitle()
                . ")",
                "link" => $commentUrlUnique,
                "description" => $message,
                "lastUpdate" => strtotime($comment->getPosted()),
                "content" => $message
            );
        }

        $feed = Zend_Feed::importArray($feedArray, 'rss');
        $feed->send();

        //response is feed
        $this->_setParam("isFeed",true);
    }

    /**
     * Views result from the main search form, it searches for an atricles
     * via simple regexp for article body.
     *
     * Parameters for searching are saved in a session - see
     * {@link Blog_IndexController::rightBlock()} code after successful
     * search form submit.
     */
    public function searchResultAction()
    {
        $this->rightBlock();
        
        //page title
        $this->view->pageTitle = $this->view->trans("Found articles");

        //search object
        $search = Blog_Model_Search::factorySearch("SearchBlogArticle");

        //set the pagination
        $search->setPageParams(
            $this->_helper->Paginator->getPageNumber(),
            Blog_Model_Article::ARTICLES_PERPAGE
        );

        $article = new Blog_Model_Article();
        //found items
        $this->view->foundItems = $article->getFound($search);
        $this->view->paginator = $search->getPaginator();

        //save search params to session if we have more then one page
        if (count($this->view->paginator)>1) {
            $search->saveToSession();
        }
    }

    /**
     * Actions for a view of articles for one month.
     */
    public function viewArchiveAction()
    {
        //right block
        $this->rightBlock();

        $year = $this->_getParam("year");
        $month = $this->_getParam("month");
        $day = $this->_getParam("day");

        $locale = Zend_Registry::get("Zend_Locale");
        $date = new Zend_Date(array('year' => $year,
            'month' => $month,
            'day'=>is_null($day) ? 1 : $day,
            $locale
        ));

        $this->view->month = MKLib_Utils::getMonthFirstCaseName($month,$locale);
        $this->view->year = $date->get(Zend_Date::YEAR);

        //page title
        $this->view->pageTitle = $this->view->trans("Articles archive for")." ";
        if (!is_null($day)) {
            $this->view->pageTitle.=$date->get(Zend_Date::DATES);
        } else {
            $this->view->pageTitle.=$this->view->month." ".$this->view->year;
        }

        $article = new Blog_Model_Article();

        //articles in the current page for current archive year and month
        $this->view->articles = $article->getArchiveItems($year,$month,$day,
            $this->_helper->Paginator->getPageNumber(),
            Blog_Model_Article::ARTICLES_PERPAGE
        );

        $this->view->paginator = $article->getPaginator();
    }

    /**
     * View archive.
     * Response is composed from two action view helpers, one article-calendar-archive
     * and second article-month-stat. Both are called from this view template.
     * This action is used for response of ajax calls.
     */
    public function viewArchiveWidgetsAction()
    {
        // Disable layout
        $this->_helper->layout->disableLayout();
    }

    /**
     * Sets cokie for the comment form data.
     * All data variables are saved in cookie as serialized array
     * @param string $username the username of a commentator
     * @param string $email the commentator email
     * @param string $www the commentator homepage
     */  
    public static function setCommentCookie($username,$email,$www)
    {
        $arr = array("username"=>$username,"mainEmail"=>$email,"www"=>$www);

 /*
 $cookieDomain=MKLib_AppBaseUrl::getCookieDomain();
 $this->getResponse()->addCookie(new Zend_Http_Cookie(
    self::COMMENTCOOKIE
   ,serialize($arr)
   ,!$cookieDomain ? MKLib_HttpResponse::FALSE_DOMAIN : $cookieDomain
   ,strtotime("+1 year")
   ,$this->_helper->url->url(array(),"blogIndex")
   ,false
   )
  );
  */
        setCookie(self::COMMENTCOOKIE,serialize($arr),strtotime("+1 year"),
            Zend_Controller_Action_HelperBroker::getStaticHelper('url')->url(array(),"blogIndex"),
            MKLib_AppBaseUrl::getCookieDomain()
        );
    }

    public function articleRatingAction()
    {
        // Don't auto render
        $this->_helper->viewRenderer->setNoRender();
        //this is ajax request with json response, hence disable layout
        $this->_helper->layout->disableLayout();

        $data=array(
            "articleId"=>$this->_getParam("articleId"),
            "rating"=>$this->_getParam("rating")
        );

        $filters = array(
            'articleId' => 'StringTrim',
            'rating' => 'StringTrim'
        );

        $validators = array(
            'articleId'   => array('Int', 'presence' => 'required'),
            'rating'   =>  array('Int', 'presence' => 'required')
        );

        $inputFilter = new Zend_Filter_Input($filters,$validators);
        $inputFilter->setData($data);

        $message="";

        if ($inputFilter->isValid()) {
            $articleId=$inputFilter->articleId;
            $rating=$inputFilter->rating;
            $remIP=$this->getRequest()->REMOTE_ADDR;

            $translator = $this->getInvokeArg("bootstrap")->getResource("translate");

            $articleRating = new Blog_Model_ArticleRating();

            try {
                $articleRating->setRating($rating);
                $articleRating->setArticleId($articleId);
                $articleRating->setIpaddr($remIP);

                if ($this->getRequest()->has($articleRating->getCookieName()) 
                    || $articleRating->wasVoted())
                {
                    $message=$translator->_("You already voted").".";
                } else {
                    $articleRating->insert();

                    //set cookie
                    setCookie($articleRating->getCookieName(),1,strtotime("+1 month"),
                        $this->_helper->url->url(array(),"blogIndex"),
                        MKLib_AppBaseUrl::getCookieDomain()
                    );

                    $message=$translator->_("Thank you").".";
                }

            } catch (Zend_Db_Exception $e) {
                //$message="Cannot save record, database error: ".$e->getMessage();
                $message="database error".$e->getMessage();
            }
        } else {
            $message="invalid input data";
        }

        $retArr = $articleRating->getRatingInfo();
        $retArr["message"] = $message;
        //adjust average
        $retArr["average"] = sprintf("%.2f",$retArr["average"]);

        echo Zend_Json::encode($retArr);
    }

    /** The action for an article preview.
     * This action is invoked from ajax call, so we need to disable a layout.
     */
    public function articlePreviewAction()
    {
        //disable layouts
        $this->_helper->layout->disableLayout();

        //temporary article object
        $article = new Blog_Model_Article();
        //set properties from the request values
        $article->setFormat($this->_getParam("formatType"));
        $article->setText($this->_getParam("articleText"));

        $this->view->article = $article;
    }

    /** The action for an comment preview.
     * This action is invoked from ajax call, so we need to disable a layout.
     */
    public function commentPreviewAction()
    {
        //disable layouts
        $this->_helper->layout->disableLayout();

        $commentText = Blog_Model_Comment::prepareCommentText($this->_getParam("message"));

        $this->view->commentText = $commentText;
    }

    /**
     * Renders the list of links for the months and article count for every month
     * in the list. Is called from the Zend_View_Helper action as the widget.
     */
    public function articleMonthStatAction()
    {
        $monthArchive=$this->_getMonthArchive();

        //remove current month statistic, because
        //this month stat is in the calendar archive head
        $monthStats=$monthArchive->getMonthStats();
        foreach ($monthStats as $key=>$monthSt) {
            if ($monthSt->getMonth()==$monthArchive->getMonth()
                && $monthSt->getYear()==$monthArchive->getYear()) {
                    unset($monthStats[$key]);
                }
        }

        $this->view->stat=$monthStats;
    }

    /**
     * Renders the calendar for the article archive.
     * Is called from the Zend_View_Helper action as the widget.
     */
    public function articleCalendarArchiveAction()
    {
        $monthArchive=$this->_getMonthArchive();

        $this->view->articleArchive=$monthArchive;
    }

    /**
     * Creates month archive object and sets its properties from requests values
     * if they are available in the request.
     * @return ArticleMonthArchive
     */
    private function _getMonthArchive()
    {
        $year = $this->_hasParam("year") ? $this->_getParam("year") : date('Y');
        $month = $this->_hasParam("month") ? $this->_getParam("month") : date('n');

        $monthArchive = new Blog_Model_ArticleMonthArchive();
        $monthArchive->setMonth($month);
        $monthArchive->setYear($year);

        return $monthArchive;
    }

    /**
     * Returs request parameters which are related to archive.
     * These parameters are: month, year, day
     * @return array
     */
    public static function getArchiveParams()
    {
        $archiveParams = array();
        $request = Zend_Controller_Front::getInstance()->getRequest();

        $paramsKeys = array("year","month","day");
        foreach($paramsKeys as $paramKey) {
            if ($param = $request->getUserParam($paramKey)) $archiveParams[$paramKey]=$param;
        }

        return $archiveParams;
    }

    /**
     * Handles the trackbacks.
     * Doesn't use viewRenderer and exits after the XML response message is sent.
     * Saves trackback into the database. TrackBack is checked if is spam or not.
     * @return string trackback response XML message - see {@link MKLib_TrackBack::makeResponse()}
     */
    public function trackbackAction()
    {
        $article = new Blog_Model_Article();
        $found = $article->findById($this->_getParam("id"));

        //entry for given id wasn't find
        if (!$found) {
            echo MKLib_TrackBack::makeResponse(false,"unknown entry id");
            exit;
        }

        if ($_SERVER["REQUEST_METHOD"]=="GET") {
            $this->_setParam("entry",$article->getTitleUrl());
            $this->_forward("view-entry");
            return;
        }

        //Don't auto render
        $this->_helper->viewRenderer->setNoRender();

        $filters = array(
            'url' =>'StringTrim',
            'title' => 'StringTrim',
            'blog_name' => 'StringTrim',
            'excerpt' => 'StringTrim'
        );

        $validators = array(
            'id' => array('Digits','presence' => 'required'),
            'url'   => array(new MKLib_UrlValid(),'presence' => 'required'),
            'title' => array(),
            'blog_name' => array(),
            'excerpt' => array()
        );

        $inputFilter = new Zend_Filter_Input($filters, $validators);
        //sets a default escaper into the stripTags filter
        $inputFilter->setDefaultEscapeFilter(new Zend_Filter_StripTags());

        $data = MKLib_TrackBack::prepareRequestData();
        $data["id"] = $this->_getParam("id");
        $inputFilter->setData($data);

        if (!$inputFilter->isValid()) {
            $message = "Data are invalid:";
            $message .= implode("" , MKLib_Utils::getMessagesOneArray($inputFilter));
            echo MKLib_TrackBack::makeResponse(false,$message);
            exit;
        }

        //get full article Url
        $baseUri = MKLib_AppBaseUrl::getUri();
        $articleUrlUnique = $baseUri . $this->_helper->url->url(
            array("entry" => $article->getTitleUrl()),
            "blogArticle"
        );

        //url of the trackbacker
        $sourceUrl = $inputFilter->url;

        //trackback domain object
        $trackBack = new Blog_Model_TrackBackEntry();
        $trackBack->setArticleId($inputFilter->id);
        $trackBack->setIpAddr($this->getRequest()->REMOTE_ADDR);
        $trackBack->setUrl(substr($inputFilter->url,0,500));
        $trackBack->setTitle(substr($inputFilter->title,0,50));
        $trackBack->setBlogName(substr($inputFilter->blog_name,0,100));
        $excerpt = substr($inputFilter->excerpt,0,200);
        //remove dots (dots are added in some trackbacks excerpts from the wordpress blogs
        $excerpt = trim($excerpt,".");
        $trackBack->setExcerpt($excerpt);

        if ($trackBack->isExist()) {
            //sent trackback response
            echo MKLib_TrackBack::makeResponse(false,"your trackbact already exists");
            exit;
        }

        if (MKLib_TrackBack::isTrackBackSpam($articleUrlUnique,$sourceUrl)) {
            $trackBack->setSpam();
            $trackBack->insert();
            echo MKLib_TrackBack::makeResponse(false,"entry url isn't found in the trackbacker page");
            exit;
        }

        //all OK save trackback and send successfull trackback response
        $trackBack->setSpam(false);
        $trackBack->insert();
        echo MKLib_TrackBack::makeResponse(true);
        exit;
    }

    /**
     * Gets RDF for article trackback autodiscovery
     * @return string
     */
    public function trackbackArticleRDF(Blog_Model_Article $article)
    {
        //get full article Url
        $baseUri = MKLib_AppBaseUrl::getUri();

        $articleUrl = $this->_helper->url->url(
            array("entry" => $article->getTitleUrl()),
            "blogArticle");

        $datePosted = new Zend_Date($article->getPosted(),Zend_Date::ISO_8601,"en_US");

        return MKLib_TrackBack::getRDfAutodiscover($baseUri . $articleUrl,
            $datePosted->get(Zend_Date::RFC_822),
            $baseUri . $articleUrl,
            substr(trim($article->getPlainText()),0,100),
            $article->getTitle(),
            "Mirin"
        );
    }
}
