Location: PHPKode > projects > Zzap > zzap/app/plugins/blog/blogPlugin.php
<?php 
/**
 * zzap group messaging / microblogging
 * 
 * Copyright (C) 2008 Dirk Ollmetzer
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @package Zzap
 * @subpackage plugins
 * @author Dirk Ollmetzer <hide@address.com>
 * @copyright 2008 Dirk Ollmetzer
 * @version $Id: blogPlugin.php 148 2008-10-27 20:37:34Z dollmetzer $
 * @license GPLv3 http://www.gnu.org/copyleft/gpl.html
 */

require_once PATH_LIB.'Plugin.php';
require_once PATH_APP.'plugins/blog/blogModel.php';
require_once(PATH_APP.'models/shortlinkModel.php');
require_once(PATH_APP.'models/messageModel.php');
require_once(PATH_APP.'models/userDetailModel.php');
require_once 'Services/Blogging.php'; // PEAR module

/**
 * blog plugin
 * 
 * Extends zzap with functionallity for receiving and sending
 * messages to and from a blog
 * 
 * @package Zzap
 * @subpackage plugins
 * @author Dirk Ollmetzer <hide@address.com>
 * @copyright 2008 Dirk Ollmetzer
 * @version $Id: blogPlugin.php 148 2008-10-27 20:37:34Z dollmetzer $
 * @license GPLv3 http://www.gnu.org/copyleft/gpl.html
 */
class blogPlugin extends Plugin
{

	/**
	 * Hook function
	 * Called from pluginController to display and process the user settings
	 * for this plugin.
	 */
	public function userSettings() {
		
		//echo "blogPlugin::userSettings()<br />\n";
		$language = $GLOBALS['application']->getLanguage();
		
		// get settings from DB
		$blogModel = new blogModel();
		$blog = $blogModel->getBlogByUser($GLOBALS['session']->get('user_id'));
		
		// fetch and check form data
		$error = array();
		if(isset($_POST['f_submit'])) {
			$f_blogurl      = substr(strip_tags($_POST['f_blogurl']), 0, 128);
			$f_blogtype     = $_POST['f_blogtype'];
			$f_announce     = $_POST['f_announce'];
			$f_replycomment = $_POST['f_replycomment'];
			$f_sendarticle  = $_POST['f_sendarticle'];
			$f_blogcategory = substr(strip_tags($_POST['f_blogcategory']), 0, 64);
			$f_bloguser     = substr(strip_tags($_POST['f_bloguser']), 0, 64);
			$f_blogpassword = substr(strip_tags($_POST['f_blogpassword']), 0, 64);
			
			if($f_blogurl != '') {
				$f_blogurl = preg_replace('/^https:\/\//', '', $f_blogurl);
				$f_blogurl = preg_replace('/^http:\/\//', '', $f_blogurl);
				$f_blogurl = preg_replace('/\/$/', '', $f_blogurl);
			}
			
			if(!in_array($f_blogtype, array_keys($GLOBALS['application']->setup['external']['blogsystems']))) {
				$f_blogtype = '';
			}
			
			if($f_announce == 'on') {
				$f_announce = 1;
			} else {
				$f_announce = 0;
			}
			
			if($f_replycomment == 'on') {
				$f_replycomment = 1;
			} else {
				$f_replycomment = 0;
			}

			if($f_sendarticle == 'on') {
				$f_sendarticle = 1;
			} else {
				$f_sendarticle = 0;
			}
			
			if(sizeof($error) == 0) {
				$blogModel->saveBlog($GLOBALS['session']->get('user_id'), $f_blogtype, $f_blogurl, $f_announce, $f_replycomment, $f_sendarticle, 0, $f_blogcategory, $f_bloguser, $f_blogpassword);
			}
			
			$blog['blogurl']      = $f_blogurl;
			$blog['blogtype']     = $f_blogtype;
			$blog['announce']     = $f_announce;
			$blog['replycomment'] = $f_replycomment;
			$blog['sendarticle']  = $f_sendarticle;
			$blog['blogcategory'] = $f_blogcategory;
			$blog['bloguser']     = $f_bloguser;
			$blog['blogpassword'] = $f_blogpassword;	
		}
		$GLOBALS['application']->content['formError'] = $error;
		
		
		// set plugin view partial
		$GLOBALS['application']->content['piView']  = 'blog/views/'.$GLOBALS['session']->get('ua_markup').'_settings.php';
		$GLOBALS['application']->content['piName']  = 'blog';
		$GLOBALS['application']->content['piTitle'] = $language['headline'];


		
		$GLOBALS['application']->content['blog'] = $blog;
		
	} /* userSettings() */



	/**
	 * Hook function
	 * Called from the asynchronous messageProcess script after
	 * the publishing of a message.
	 * Possible use: Sending messages to external services
	 * 
	 * @param array $user    Sender. Associative array of record set from DB table 'user'
	 * @param array $message Message. Associative array of record set from DB table 'message'
	 *
	 * @return integer       Entry ID of the Blog or 0, if no entry was made
	 */
	public function messageProcess($user, $message) {
		
		// Don't echo Article back to blog. NOPE: too easy. It means, that no
		// Blogentry would ever processed and sent to twitter.
		// Instead: check double entry before processing
		if($message['source'] == 'blog') {
			$GLOBALS['application']->writeLog('Message comes from Blog. Processing aborted' ,'messageProcess');
			return $result;
		}
		
		$result = 0;
		$userDetailModel = new userDetailModel();
		$userDetail = $userDetailModel->getUserDetail($user['id']); 

		$GLOBALS['application']->importLanguageFile(PATH_APP.'plugins/blog/lang_'.$userDetail['language'].'.ini');
		$language = $GLOBALS['application']->getLanguage();
		
		if($message['replyto'] > 0) {

			$this->blogComment($message, $language);
			
		} else {
	
			$blogModel = new blogModel();
			$blogdata = $blogModel->getBlogByUser($message['userid']);
				
			if(sizeof($blogdata) > 0) {
				
				if($blogdata['sendarticle'] == 1) {
					if(($blogdata['blogtype'] != '') AND ($blogdata['blogurl'] != '') AND ($blogdata['bloguser'] != '') AND ($blogdata['blogpassword'] != '')) {
						
						// prepare blog connection
						$driver = $GLOBALS['application']->setup['external']['blogsystems'][$blogdata['blogtype']]['interface'];
						$apiscript = $GLOBALS['application']->setup['external']['blogsystems'][$blogdata['blogtype']]['apiscript'];
						
						// to ensure proper execution in the PEAR module, we must divide
						// the host from the path to the blog 
						$temp = explode('/', $blogdata['blogurl']);
						$host = array_shift($temp);
						$path = join('/', $temp);
						
						
						// Build headline
						$msg_headline = $message['message'];
						$msg_headline = preg_replace('/https?:\/\/[\w\d:#@%\/;$()~_?\+-=\\\.&]*/', '', $msg_headline);
						
						
						// Build message body
						$msg_body = $message['message'];
						$msg_body = preg_replace('/https?:\/\/[\w\d:#@%\/;$()~_?\+-=\\\.&]*/', '<a href="$0" target="external">$0</a> ', $msg_body);
						// include picture, if there's one
						// ... (watch documentation of PEAR modules)
						
						// include footer to messagelist of user
						$msg_body .= '<p><a href="'.$GLOBALS['application']->buildLink('messages/user', $user['handle']).'" target="zzap">';
						$msg_body .= sprintf($language['msg_blogentry_zzap'], $user['handle']);
						$msg_body .= "</a></p>\n";
						
						$blog = Services_Blogging::factory(
							$driver,
							$blogdata['bloguser'],
							$blogdata['blogpassword'],
							'http://'.$host,
							'/'.$path.'/'.$apiscript
						);
						$post = $blog->createNewPost();
						$post->title = $msg_headline;
						$post->content = $msg_body;
						$post->categories = array($blogdata['blogcategory']);
						
						$blog->savePost($post);
						$result = $post->id;
						
						$blogModel->saveBlogEntry($message['id'], $result);
						
					} else {
						$GLOBALS['application']->writeLog('Blogdata (type, url, ...) missing.', 'messageProcess');
					}
					
				}
			}
			return $result;
		}

	} /* messageProcess($user, $message) */



	/**
	 * Hook function
	 * Called from the ping.php script, when an external service
	 * sends a pingback to zzap.
	 * possible use: grab new messages from a blog just in time as
	 * they are published
	 * 
	 * @return boolean processing completed without error?
	 */
	public function pingIncomming() {
		
		// get caller URL from Ping
		$callerURL = $this->getCallerURL();
		if($callerURL == '') {
			$GLOBALS['application']->writeLog('Found no caller URL in Ping. Processing aborted', 'ping');
			return false;
		} else {
			$GLOBALS['application']->writeLog('Caller URL: '.$callerURL, 'ping');
		}
		
		// get matching bloginfo from DB
		$bloginfo = $this->getBlogInfo($callerURL);
		if(!isset($bloginfo['userid'])) {
			$GLOBALS['application']->writeLog("Found no user with blogurl '$callerURL'. Processing aborted", 'ping');
			return false;
		} else {
			$GLOBALS['application']->writeLog('Bloginfo: '.print_r($bloginfo, true), 'ping');
		}
		
		// exit, if blogentries shouldn't be announced
		if($bloginfo['announce'] != 1) {
			$GLOBALS['application']->writeLog("No processing of incmming announcements. Processing aborted", 'ping');
			return false;
		}
		
		// fetch RSS feed from Blog
		$rss = $this->fetchRSS($bloginfo);
		// $GLOBALS['application']->writeLog('found RSS Feed: '.$rss, 'ping');
		$recentArticle = $this->getRecentArticle($rss);
		if(sizeof($recentArticle) == 0) {
			$GLOBALS['application']->writeLog('No recent article found in RSS', 'ping');
			return false;
		} else {
			$GLOBALS['application']->writeLog('recent article : '.print_r($recentArticle, true), 'ping');
		}

		// exit, if blogentry already exists 
		$messageModel = new messageModel();
		$message = $recentArticle['title'];
		if($messageModel->messageExists($message, $bloginfo['userid'])) {
			$GLOBALS['application']->writeLog('blogentry "'.$message.'" already exists', 'ping');
			return false;
		}

		// create shortlink
		$shortlinkModel = new shortlinkModel();
		// check, if link ist not already registered
		if($shortlinkModel->exists($recentArticle['link'], $bloginfo['userid'])) {
			$GLOBALS['application']->writeLog('link for "'.$recentArticle['link'].'", user '.$bloginfo['userid'].' already exists', 'ping');
			return false;
		}
		$slid = $shortlinkModel->create($recentArticle['link'], $bloginfo['userid']);
		$link = $GLOBALS['application']->buildLink('sl/'.$slid);
		$GLOBALS['application']->writeLog('Shortlink is : '.$link, 'ping');
		
		// assemble message with link
		$maxlen = 140 - strlen(' '.$link);
		if(strlen($message) > $maxlen) {
			$message = substr($message, 0, ($maxlen - 3)).'...';
		}
		$message = $message.' '.$link;
		$GLOBALS['application']->writeLog("User ".$bloginfo['userid'].", Message '$message'", 'ping');

		// put user message in the DB
		$mid = $messageModel->setMessage($message, $bloginfo['userid']);
		
		// update Shortlink with message id
		$shortlinkModel->updateLink($slid, $mid);
		
		
		// Save id of blog entry with message id. But where from????
		
		// start asynchronous message processing
		$request = "http://".URL_BASE."/mp.php?mid=$mid&source=blog";
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $request);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
		curl_setopt($ch, CURLOPT_TIMEOUT, 1);
		curl_exec($ch);
		curl_close($ch);
		
		return true;

	} /* pingIncomming() */
	
	
	
	/**
	 * Sends a reply as a comment to the blog
	 *
	 * @param array $message  Message data
	 * @param array $language Array of language snippet from *.ini files
	 *
	 */
	protected function blogComment($message, $language) {
		
		$messageModel = new messageModel();
		$originalMessage = $messageModel->getMessage($message['replyto']);
		
		$GLOBALS['application']->writeLog("Reply to this message :".print_r($originalMessage, true), 'messageProcess');
		
		// Lookup, if author of originalmessage enabled replyto for his blog
		$blogModel = new blogModel();
		$blogData = $blogModel->getBlogByUser($originalMessage['userid']);
		$GLOBALS['application']->writeLog("Blog data :".print_r($blogData, true), 'messageProcess');
		if($blogData['replycomment'] != 1) {
			$GLOBALS['application']->writeLog("Reply not permitted.", 'messageProcess');
			return;
		}
				
		// Is it an blog entry?
		$blogEntry = $blogModel->getBlogEntry($message['replyto']);
		$GLOBALS['application']->writeLog("Blog entry: ".print_r($blogEntry, true), 'messageProcess');
		if(sizeof($blogEntry) == 0) {
			$GLOBALS['application']->writeLog("Seems no blog entry for message ".$message['replyto'].". No trackback sent.", 'messageProcess');
			return;
		}
		
		if($blogEntry['entryid'] > 1) {
			
			// reply to entry, that was initially blogged by zzap.
			
			// separate host and path
			//$temp = explode('/', $blogData['blogurl']);
			//$host = array_shift($temp);
			//$path = join('/', $temp);
			
			// build trackback link
			$TBLink = $GLOBALS['application']->setup['external']['blogsystems'][$blogData['blogtype']]['trackback_link'];
			$TBLink = str_replace('###ENTRY_ID###', $blogEntry['entryid'], $TBLink);
			if($path != '') {
				$TBLink = $path.'/'.$TBLink;
			}
			$TBLink = '/'.$TBLink;
			$GLOBALS['application']->writeLog("reply to entry, that was initially blogged by zzap : ".$TBLink.' to host '.$host, 'messageProcess');
			/*
			$data = "title=".urlencode(sprintf($language['msg_trackback_head'], $message['handle']));
			$data .= "&url=".$GLOBALS['application']->buildLink('state/show',$message['id']);
			$data .= "&blog_name=zzap";
			$data .= "&excerpt=".urlencode($message['message']);
			
			$GLOBALS['application']->writeLog(", Data: ".$data, 'messageProcess');
			
			$fp = @fsockopen($host, 80);
			@fputs($fp, "POST ".$TBLink." HTTP/1.1\r\n");
			@fputs($fp, "Host: ".$host."\r\n");
			@fputs($fp, "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n");
			@fputs($fp, "Content-length: ".strlen($data)."\r\n");
			@fputs($fp, "Connection: close\r\n\r\n");
			@fputs($fp, $data);
			@fclose($fp);
			*/
			// start synchronous message processing
			$request = "http://".$blogData['blogurl'].$TBLink;
			$GLOBALS['application']->writeLog("CURL request: $request", 'messageProcess');
			
			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, $request);
			//curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
			curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
			curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
			curl_setopt($ch, CURLOPT_POST, true);
			curl_setopt($ch, CURLOPT_POSTFIELDS, array(
				'title' => sprintf($language['msg_trackback_head'], $message['handle']),
				'url' => $GLOBALS['application']->buildLink('state/show',$message['id']),
				'blog_name' => 'zzap',
				'excerpt' => $message['message']
			));
			curl_setopt($ch, CURLOPT_TIMEOUT, 60);
			curl_exec($ch);
			curl_close($ch);
			
			if(curl_errno($ch) != 0) {
				$GLOBALS['application']->writeLog("CURL error: ".curl_error($ch), 'messageProcess');
			}
			
		} else {
			// reply to entry, that was announced to zzap
			$GLOBALS['application']->writeLog("reply to entry, that was announced to zzap. Not implemented yet", 'messageProcess');
		}
		
	} /* blogComment($message, $language) */



	/**
	 * Returns Infos about the most recent article in the RSS Feed
	 *
	 * @param string RSS Feed
	 *
	 * @return array Information about the Article
	 */
	protected function getRecentArticle($rss) {
		
		$result = array();
		
		try {
			$feed = new SimpleXMLElement($rss);
			$result['title']       = (string) $feed->channel->item[0]->title;
			$result['link']        = (string) $feed->channel->item[0]->link;
			$result['category']    = (string) $feed->channel->item[0]->category;
			$result['commentlink'] = (string) $feed->channel->item[0]->comments;
		} catch(Exception $e) {
			$GLOBALS['application']->writeLog("RSS ressource wasn't a well formed XML", 'ping');
		} 
		
		return $result;
	
	} /* getRecentArticle($rss) */
	
	
	
	/**
	 * Fetches the RSS feed from a blog, considering the blogtype
	 *
	 * @param array $bloginfo Typically a user contact record set, but at least 'blogurl' and 'blogtype'
	 *
	 * @return string RSS Feed
	 */
	protected function fetchRSS($bloginfo) {
		
		// prepare feed url
		$rssURL = $bloginfo['blogurl'];
		if(substr($rssURL, strlen($rssURL)-1, 1) != '/') {
			$rssURL .= '/'; 
		}
		// fetch standard RSS URL from users blogsystem 
		$rssURL .= $GLOBALS['application']->setup['external']['blogsystems'][$bloginfo['blogtype']]['rss_link'];
		
		$rss = '';
		$fp = fopen('http://'.$rssURL, 'r');
		while($part = fread($fp, 1024)) {
			$rss .= $part;
		}
		fclose($fp);
		
		return $rss;
		
	} /* fetchRSS($bloginfo) */
	
	
	
	/**
	 * Returns stored information about a blog by its URL
	 * 
	 * @param string $blogURL URL as it comes from a pingback call
	 *
	 * @return array Bloginformations
	 */
	protected function getBlogInfo($blogURL) {
		
		// first replace leading protocol and trailing slash from URL 
		$blogURL = preg_replace("/^https:\/\//", '', $blogURL);
		$blogURL = preg_replace("/^http:\/\//", '', $blogURL);
		$blogURL = preg_replace('/\/$/', '', $blogURL);
		
		$blogModel = new blogModel();
		return $blogModel->getBlogByURL($blogURL);
		
	} /* getBlogInfo($blogURL)  */
	
	
	
	/**
	 * Processes a Pingback call from a blog.
	 * Expected is a XML-RPC call with the method 'weblogUpdates.ping' or 'weblogUpdates.extendedPing'
	 * 
	 * @return string URL of the blog without leading 'http://' and trailing '/'
	 */
	protected function getCallerURL() {

		// Workaround for bug in PHP < 5.2.2 
		if ( !isset( $HTTP_RAW_POST_DATA ) ) {
			$rawData = file_get_contents( 'php://input' );
		} else {
			$rawData = $HTTP_RAW_POST_DATA;
		}
		
		try {
			$xml = new SimpleXMLElement($rawData);
			
			switch($xml->methodName) {
				case "weblogUpdates.ping":
					$pingparams = array();
					foreach($xml->params->param as $param) {
						$pingparams[] = $param->value->string;
					}
					$url = $pingparams[1];
					$url = preg_replace('/^https:\/\//', '', $url);
					$url = preg_replace('/^http:\/\//', '', $url);
					$url = preg_replace('/\/$/', '', $url);
					return $url;
					break;
			
				case "weblogUpdates.extendedPing";
					$pingparams = array();
					foreach($xml->params->param as $param) {
					$pingparams[] = $param->value->string;
					}
					// third parameter is the feed URL
					$url = $pingparams[1];
					$url = preg_replace('/^https:\/\//', '', $url);
					$url = preg_replace('/^http:\/\//', '', $url);
					$url = preg_replace('/\/$/', '', $url);
					return $url;
					break;
	
				default:
					$GLOBALS['application']->writeLog("Unknown Format:\n".$rawData."\n", 'ping');
					return '';
					break;
			}
			
		} catch(Exception $e) {
			$GLOBALS['application']->writeLog("No well formed XML", 'ping');
			return '';
		}
		
	} /* getCallerURL() */
	
}
?>
Return current item: Zzap