<?php
/**
 * Implementation of the trackback class
 * Mostly code is cut-n-paste from the trackback class
 * Copyright (c) 2004 Ran Aroussi (http://www.blogish.org)
 *
 * @package MKLib
 * @author Miroslav Kubelik (koubel@volny.cz)
 * @copyright Copyright (c) 2007-2009 Miroslav Kubelik
 * @license http://opensource.org/licenses/gpl-license.php GNU GPL
 */
 
/**
 * Class with the trackback implementation, in some things (logger) depends
 * on some Zend_Framework clases.
 *
 * @package MKLib
 * @author Miroslav Kubelik (koubel@volny.cz)
 * @copyright Copyright (c) 2007-2009 Miroslav Kubelik
 * @license http://opensource.org/licenses/gpl-license.php GNU GPL
 */
class MKLib_TrackBack
{
    /**
     * default error message for trackback response
     */
    const DEFAULT_RESPONSE_ERROR_MESSAGE = "An error occured while trying to log your trackback...";
    
    /**
     * encoding in the request
     * @var string - default is UTF-8
     */
    private $_encoding;

    /** url, which is pinged
     * @var string
     */
    private $_pingUrl;

    /** title of the trackbacker's blog entry
     * @var string
     */
    private $_title;

    /** little description of the trackbacker's blog entry
     * @var string
     */
    private $_excerpt;

    /**
     * url of the trackbacker's blog entry
     * @var string
     */
    private $_url;

    /**
     * name of the blog
     * @var string
     */
    private $_blogName;

    /**
     * error message which is received from the response
     * @var string
     */
    private $_errorMessage;

    /**
     * loggging functionality
     * @var Zend_Log
     */
    private $_log;

    /**
     * Creates trackback instance
     * @param string $url url for trackack sender
     * @param string $encoding request encoding
     * @return MKLib_Trackack
     * @throws InvalidArgumentException if url isn't correctly set
     */
    public function __construct($url, $encoding = "UTF-8")
    {
        if (!$url) {
            throw new InvalidArgumentException("url of the site which sends trackback must be presented");
        }
        $this->_url = $url;
        $this->_encoding = strtoupper($encoding);
    }

    /**
     * Sets logger
     * @param Zend_Log $log
     */
    public function setLog(Zend_Log $log)
    {
        $this->_log = $log;
    }

    /**
     * Logs message if logger is available
     * @param string $msg logged message
     * @param int $level log level - see Zend_Log constants
     */
    private function _logMsg($msg, $level = Zend_Log::INFO)
    {
        if (!$this->_log) return;
        $this->_log->log($msg, $level);
    }

    /**
     * Convert string into utf-8 encoding if needed.
     * @param string $str text for conversion
     * @return string repaired text
     */
    private function _repairEncoding($str)
    {
        return ($this->_encoding == "UTF-8") ? $str : iconv($this->_encoding, "UTF-8", $str);
    }

    /**
     * Sets trackback title
     * @param strin $title
     */
    public function setTitle($title)
    {
        $this->_title = $this->_repairEncoding($title);
    }

    /**
     * Sets trackback excerpt
     * @param string $excerpt
     */
    public function setExcerpt($excerpt)
    {
        $this->_excerpt = $this->_repairEncoding($excerpt);
    }

    /**
     * Sets ping url
     * @param string $url
     */
    public function setPingUrl($url)
    {
        if (!preg_match("|^http(s)?://|", $url)) {
            $this->_pingUrl = "http://" . $url;
        } else $this->_pingUrl = $url;
    }

    /**
     * Sets blog name
     * @param string $name
     */
    public function setBlogName($name)
    {
        $this->_blogName = $this->_repairEncoding($name);
    }

    /**
     * Gets current error message
     * @return string
     */
    public function getErrorMessage()
    {
        return $this->_errorMessage;
    }

    public function ping()
    {
        if (!$this->_pingUrl) throw new LogicException("pingUrl isn't set");

        if (!$this->_title) $title = "Trackbacking your entry...";
        else $title = $this->_title;

        if (!$this->_excerpt) $excerpt = "I found your entry interesting so"
            . "I've added a link to it on my site.)";
        else $excerpt = $this->_excerpt;

        $requestText = "url=" . rawurlencode($this->_url) . "&title=" 
            . rawurlencode($title) . (empty($this->_blogName) ? "" : "&blog_name=" 
            . rawurlencode($this->_blogName)) . "&excerpt=" 
            . rawurlencode($excerpt);
        
        $connParams = $this->_getConnectionParams();
        $tbSock = fsockopen($connParams["host"], $connParams["port"]);
        if (!is_resource($tbSock)) {
            $this->_errorMessage = "can't connect to: $this->_pingUrl "
                . "connection to {$connParams["host"]}:{$connParams["port"]} failed";
            $this->_logMsg($this->_errorMessage);
            return false;
        }
        $this->_logMsg("connection to {$connParams["host"]}:{$connParams["port"]} Ok");
       
        //send data
        fputs($tbSock, "POST " . $connParams["path"] . $connParams["query"] . " HTTP/1.1\r\n");
        fputs($tbSock, "Host: " . $connParams["host"] . "\r\n");
        fputs($tbSock, "Content-type: application/x-www-form-urlencoded; charset=utf-8\r\n");
        fputs($tbSock, "Content-length: " . strlen($requestText) . "\r\n");
        fputs($tbSock, "Connection: close\r\n\r\n");
        fputs($tbSock, $requestText);
        $this->_logMsg("POST requets $requestText send");
      
        //gather result
        $response = "";
        while (!feof($tbSock)) {
            $response.= fgets($tbSock, 128);
        }
        //close socket
        fclose($tbSock);
        $this->_logMsg("response $response recieved");
     
        //check response status
        //echo "response is ".$response."<br>";
        strpos($response, '<error>0</error>') ? $retVal = true : $retVal = false;
        if (preg_match("/<message>(.*)<\/message>/", $response, $matches)) {
            $this->_errorMessage = $matches[1];
        } else $this->_errorMessage = "";
    
        return $retVal;
    }
    
    public static function makeResponse($success, $errorMessage = self::DEFAULT_RESPONSE_ERROR_MESSAGE)
    {
        // Start response to trackbacker...
        $response = '<' . '?xml version="1.0" encoding="utf-8" ?' . '>' . "\n";
        $response.= "<response> \n";
        if ($success) {
            // Trackback received successfully...
            $response.= " <error>0</error> \n";
        } else {
            $response.= " <error>1</error> \n";
            $response.= " <message>" . $errorMessage . "</message>\n";
        }
  
        //End response to trackbacker...
        $response.= "</response>";
 
        return $response;
    }

    private function _getConnectionParams()
    {
        $params = parse_url($this->_pingUrl);
        if ((isset($params["query"])) && ($params["query"] != "")) {
            $params["query"] = "?" . $params["query"];
        } else $params["query"] = "";

        if ((isset($params["port"]) && !is_numeric($params["port"])) || (!isset($params["port"]))) {
            $params["port"] = 80;
        }

        return $params;
    }

    /**
     * Checks if received trackback is spam or not. Try to find the trackbacked url,
     * in page, from which the trackback is send, this url is specified as the
     * url parameter in the trackback message. It uses Zend_Http_Client for a retrieving
     * a content of the $trackBackerUrl page.
     *
     * @param string $urlEntry url, which will be searched
     * @param string $trackBackerUrl url, where should have be $urlEntry
     * @return bool true if the url wasn't found in the page, it seems likes spam
     * false, if the url wasn't found in the page, it seems like valid tracback
     */
    public static function isTrackBackSpam($urlEntry, $trackBackerUrl)
    {
        $client = new Zend_Http_Client($trackBackerUrl);
        $response = $client->request();
        if ($response->isError()) return true;
        $body = $response->getBody();

        //prepare characters for the pattern matching
        $urlEntry = preg_quote($urlEntry, "/");
        $pattern = "/<\s*a.*href\s*=[\"'\s]*" . $urlEntry . "[\"'\s]*.*>.*<\s*\/\s*a\s*>/i";
        $found = preg_match($pattern, $body);

        return !$found;
    }

    /**
     * Prepares all necessary $_POST data for next processing in this class.
     * - converts data from the detected encoding into $destCharset encoding.
     * - filters only trackback parametres (from specification)
     * @param string $destCharset destination encoding for input data
     * @return array prepared trackback data for the next processing
     */
    public static function prepareRequestData($destCharset = "utf-8")
    {
        //detect encoding from the request
        if (isset($_SERVER["CONTENT_TYPE"])) {
            if (preg_match("/charset=([a-z0-9-]+)/i", $_SERVER["CONTENT_TYPE"], $matches)) {
                $inputCharset = strtolower($matches[1]);
            }
        } else {
            $inputCharset = "utf-8";
        }

        $data = array();
        foreach($_POST as $key => $val) {
            if ($key == "url" || $key == "title" || $key == "blog_name" || $key == "excerpt") {
                if ($inputCharset != $destCharset) $val = iconv($inputCharset, $destCharset, $val);
                $data[$key] = $val;
            }
        }
        
        return $data;
    }

    /**
     * Produces embedded RDF representing metadata about the entry,
     * allowing clients to auto-discover the TrackBack Ping URL.
     *
     * @param string $pingUrl url which will be pinged
     * @param string $RFC822_date a publishing day of the entry
     * @param string $title an entry title
     * @param string $descr an short entry description
     * @param string $permalink an entry permalink
     * @param string $author an author of an entry
     * @return string
     *
     * @throws LogicException when ping url property isn't set
     */
    public static function getRDfAutodiscover($pingUrl, $RFC822_date,
        $permalink, $descr, $title, $author)
    {
        if (!$pingUrl) throw new LogicException("pingUrl isn't set");
        
        if (strlen($descr) > 50) $descr = MKLib_Utils::niceTruncate($descr, 50, "...");
        if (strlen($title) > 50) $title = MKLib_Utils::niceTruncate($title, 50, "...");
       
        $return = "<!-- RDF FOR THE ENTRY\n";
        $return.= "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" \n";
        $return.= "    xmlns:dc=\"http://purl.org/dc/elements/1.1/\" \n";
        $return.= "    xmlns:trackback=\"http://madskills.com/public/xml/rss/module/trackback/\"> \n";
        $return.= "<rdf:Description \n";
        $return.= "    rdf:about=\"" . self::_xmlSafe($permalink) . "\" \n";
        $return.= "    dc:identifier=\"" . self::_xmlSafe($permalink) . "\" \n";
        $return.= "    trackback:ping=\"" . self::_xmlSafe($pingUrl) . "\" \n";
        $return.= "    dc:title=\"" . self::_xmlSafe($title) . "\" \n";
        $return.= "    dc:subject=\"TrackBack\" \n";
        $return.= "    dc:description=\"" . self::_xmlSafe($descr) . "\" \n";
        $return.= "    dc:creator=\"" . self::_xmlSafe($author) . "\" \n";
        $return.= "    dc:date=\"" . $RFC822_date . "\" /> \n";
        $return.= "</rdf:RDF> \n";
        $return.= "END OF RDF FOR THE ENTRY -->  \n";
        
        return $return;
    }

    /**
     * Converts a string into an XML-safe string (replaces &, <, >, " and ')
     * @param string $string
     * @return string
     */
    private static function _xmlSafe($string)
    {
        return htmlspecialchars($string, ENT_QUOTES);
    }
}
