├── inc ├── Validate.php ├── r2t │ └── shortener │ │ ├── liipto.php │ │ ├── liipto140.php │ │ └── splanetphp.php ├── ProwlPHP │ ├── example.php │ ├── README │ └── ProwlPHP.php ├── XML │ └── Feed │ │ ├── Parser │ │ ├── Exception.php │ │ ├── RSS09Element.php │ │ ├── RSS1Element.php │ │ ├── RSS11Element.php │ │ ├── RSS2Element.php │ │ ├── RSS09.php │ │ ├── AtomElement.php │ │ ├── RSS11.php │ │ ├── RSS1.php │ │ ├── RSS2.php │ │ └── Atom.php │ │ └── Parser.php ├── sfYaml │ ├── sfYamlDumper.class.php │ ├── sfYaml.class.php │ ├── sfYamlInline.class.php │ └── sfYamlParser.class.php ├── Services │ ├── Twitter │ │ ├── Favorites.php │ │ ├── Notifications.php │ │ ├── Friendships.php │ │ ├── Users.php │ │ ├── Exception.php │ │ ├── Search.php │ │ ├── Account.php │ │ ├── DirectMessages.php │ │ ├── Statuses.php │ │ └── Common.php │ └── Twitter.php ├── HTTP │ └── Request │ │ └── Listener.php └── r2t.php ├── conf ├── defaults.yml └── feeds.yml-dist ├── IDEAS ├── rss2twitter.php ├── LICENSE └── getOauthToken.php /inc/Validate.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torne/rss2twi.php/master/inc/Validate.php -------------------------------------------------------------------------------- /conf/defaults.yml: -------------------------------------------------------------------------------- 1 | defaults: 2 | shortener: liipto 3 | maxurllength: 30 4 | prefix: '' 5 | maxposts: 5 6 | -------------------------------------------------------------------------------- /IDEAS: -------------------------------------------------------------------------------- 1 | - SUP (Simple Update Protocol) Support for faster and less ressource intensive update checks -> 2 | http://blog.friendfeed.com/2008/08/simple-update-protocol-fetch-updates.html 3 | 4 | -------------------------------------------------------------------------------- /rss2twitter.php: -------------------------------------------------------------------------------- 1 | process(); 9 | 10 | 11 | -------------------------------------------------------------------------------- /inc/r2t/shortener/liipto.php: -------------------------------------------------------------------------------- 1 | apiurl . urlencode($url)); 14 | if (!PEAR::isError($req->sendRequest())) { 15 | return $req->getResponseBody(); 16 | } 17 | return ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /conf/feeds.yml-dist: -------------------------------------------------------------------------------- 1 | feeds: 2 | example: 3 | url: http://example.org/rss.xml 4 | twitter: 5 | # for oauth access (preferred) 6 | # Run getOauthToken.php on the commandline and do what it tells 7 | # You need the pecl oauth extension for this. 8 | token: tokenALongStringForThetoken 9 | secret: secretUsuallyLongAndAll 10 | # for old school username/password access 11 | user: user 12 | pass: pass 13 | prefix: '[blog]' 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 14 rue de Plaisance, 75014 Paris, France 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | 16 | -------------------------------------------------------------------------------- /inc/r2t/shortener/liipto140.php: -------------------------------------------------------------------------------- 1 | apiurl .'?url='. urlencode($url) ."&text=" . urlencode($text). "&maxchars=120"); 14 | if (!PEAR::isError($req->sendRequest())) { 15 | return json_decode($req->getResponseBody(),true); 16 | } 17 | return ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /inc/r2t/shortener/splanetphp.php: -------------------------------------------------------------------------------- 1 | apiurl .'?url='. urlencode($url) ."&text=" . urlencode($text). "&maxchars=120"); 15 | if (!PEAR::isError($req->sendRequest())) { 16 | return json_decode($req->getResponseBody(),true); 17 | } 18 | return ""; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /inc/ProwlPHP/example.php: -------------------------------------------------------------------------------- 1 | push(array( 7 | 'application'=>'rss2twi.php', 8 | 'event'=>'New Post', 9 | 'description'=>'Test message! \n Sent at ' . date('H:i:s'), 10 | 'priority'=>0, 11 | //'apikey'=>'APIKEY' // Not required if already set during object construction. 12 | //'providerkey'=>"PROVIDERKEY' 13 | ),true); 14 | 15 | var_dump($prowl->getError()); // Optional 16 | var_dump($prowl->getRemaining()); // Optional 17 | var_dump(date('d m Y h:i:s', $prowl->getResetdate())); // Optional 18 | 19 | ?> 20 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/Exception.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 20 | * @version CVS: $Id: Exception.php,v 1.3 2005/11/07 01:52:35 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * We are extending PEAR_Exception 26 | */ 27 | require_once 'PEAR/Exception.php'; 28 | 29 | /** 30 | * XML_Feed_Parser_Exception is a simple extension of PEAR_Exception, existing 31 | * to help with identification of the source of exceptions. 32 | * 33 | * @author James Stewart 34 | * @version Release: 1.0.3 35 | * @package XML_Feed_Parser 36 | */ 37 | class XML_Feed_Parser_Exception extends PEAR_Exception 38 | { 39 | 40 | } 41 | 42 | ?> -------------------------------------------------------------------------------- /getOauthToken.php: -------------------------------------------------------------------------------- 1 | disableSSLChecks(); 17 | $oauth->enableDebug(); 18 | $request_token_info = $oauth->getRequestToken($req_url); 19 | 20 | 21 | $token = $request_token_info["oauth_token"]; 22 | $secret = $request_token_info["oauth_token_secret"]; 23 | $oauth->setToken($request_token_info["oauth_token"],$request_token_info["oauth_token_secret"]); 24 | 25 | printf("I think I got a valid request token, navigate your www client to:\n\n%s?oauth_token=%s\n\nOnce you finish authorizing, insert the PIN and hit ENTER to continue\n\n", $authurl, $request_token_info["oauth_token"]); 26 | 27 | $in = fopen("php://stdin", "r"); 28 | $foo = fgets($in, 255); 29 | 30 | $access_token_info = $oauth->getAccessToken($acc_url,null,trim($foo)); 31 | 32 | print "Add the following to your conf/feeds.yml\n"; 33 | print "*****\n"; 34 | print " twitter:\n"; 35 | print " token: ".$access_token_info['oauth_token'] ."\n"; 36 | print " secret: ". $access_token_info['oauth_token_secret'] ."\n"; 37 | print "*****\n"; 38 | 39 | 40 | 41 | } catch(OAuthException $E) { 42 | print_r($E); 43 | } 44 | ?> 45 | -------------------------------------------------------------------------------- /inc/sfYaml/sfYamlDumper.class.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | require_once(dirname(__FILE__).'/sfYamlInline.class.php'); 12 | 13 | /** 14 | * sfYamlDumper class. 15 | * 16 | * @package symfony 17 | * @subpackage util 18 | * @author Fabien Potencier 19 | * @version SVN: $Id: sfYamlDumper.class.php 10594 2008-08-01 16:50:33Z dwhittle $ 20 | */ 21 | class sfYamlDumper 22 | { 23 | /** 24 | * Dumps a PHP value to YAML. 25 | * 26 | * @param mixed The PHP value 27 | * @param integer The level where you switch to inline YAML 28 | * @param integer The level o indentation indentation (used internally) 29 | * 30 | * @return string The YAML representation of the PHP value 31 | */ 32 | public function dump($input, $inline = 0, $indent = 0) 33 | { 34 | $output = ''; 35 | $prefix = $indent ? str_repeat(' ', $indent) : ''; 36 | 37 | if ($inline <= 0 || !is_array($input) || empty($input)) 38 | { 39 | $output .= $prefix.sfYamlInline::dump($input); 40 | } 41 | else 42 | { 43 | $isAHash = array_keys($input) !== range(0, count($input) - 1); 44 | 45 | foreach ($input as $key => $value) 46 | { 47 | $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); 48 | 49 | $output .= sprintf('%s%s%s%s', 50 | $prefix, 51 | $isAHash ? sfYamlInline::dump($key).':' : '-', 52 | $willBeInlined ? ' ' : "\n", 53 | $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + 2) 54 | ).($willBeInlined ? "\n" : ''); 55 | } 56 | } 57 | 58 | return $output; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /inc/ProwlPHP/README: -------------------------------------------------------------------------------- 1 | ProwlPHP 2 | Written by Fenric (sandbox@fenric.co.uk), 9 July 2009 3 | 4 | - Current Version: 0.3.1 5 | - Requirements: PHP 5 (or higher), cURL with SSL support. 6 | 7 | Hello there! This is a PHP class for Prowls iPhone notificaton service with API key authentication. More information on Prowl can be found at http://prowl.weks.net/. For more information on the class and the Prowl API take a look at the Wiki page (http://wiki.github.com/Fenric/ProwlPHP). 8 | 9 | I'd love to hear your ideas, suggestions and even your uses for ProwlPHP. Just drop me (Fenric) a message in the CocoaForge forums (http://forums.cocoaforge.com/memberlist.php?mode=viewprofile&u=41743) or right here on Github! Please post any issues you find on the issues section of GitHub (http://github.com/Fenric/ProwlPHP/issues). 10 | 11 | ------ 12 | Release Notes 13 | ------ 14 | 15 | 0.3.1 16 | * Minor revision to include checks at construction to ensure curl_exec and cURL SSL are available. 17 | * Error codes can now be manually passed through getError(int $code) to return the error message. 18 | 19 | 0.3 20 | * HTTP proxy support added. 21 | * Keys no longer verified be default at construction and as such saves API requests. 22 | * Provider keys now supported. 23 | * Error codes were not being saved correctly. Bug fixed. 24 | * API Key verification can now be called manually using 'verify' function. 25 | 26 | 0.2.2 27 | 28 | * Line breaks were not being honoured when ProwlPHP class was using with CLI. Commit fixed this issue. Thank you Fr3d for resolving this issue. 29 | 30 | 0.2.1 31 | 32 | * Addition of getRemaining function. Retrieve how many requests your IP has left. 33 | * Addition of getResetdate function. Retrieve reset date of remaining push requests. UNIX timestamp format. 34 | * Error code and messages added for when IP exceeds API limit. 35 | * Fixed push functions ability to request API using POST method. 36 | * Prep for provider keys, need to find out what they do first though! 37 | 38 | ------ 39 | Planned 40 | ------ 41 | 42 | * API Key chaining checks. 43 | * Support for PHP 4 -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS09Element.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS09Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /* 25 | * This class provides support for RSS 0.9 entries. It will usually be called by 26 | * XML_Feed_Parser_RSS09 with which it shares many methods. 27 | * 28 | * @author James Stewart 29 | * @version Release: 1.0.3 30 | * @package XML_Feed_Parser 31 | */ 32 | class XML_Feed_Parser_RSS09Element extends XML_Feed_Parser_RSS09 33 | { 34 | /** 35 | * This will be a reference to the parent object for when we want 36 | * to use a 'fallback' rule 37 | * @var XML_Feed_Parser_RSS09 38 | */ 39 | protected $parent; 40 | 41 | /** 42 | * Our specific element map 43 | * @var array 44 | */ 45 | protected $map = array( 46 | 'title' => array('Text'), 47 | 'link' => array('Link')); 48 | 49 | /** 50 | * Store useful information for later. 51 | * 52 | * @param DOMElement $element - this item as a DOM element 53 | * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member 54 | */ 55 | function __construct(DOMElement $element, $parent, $xmlBase = '') 56 | { 57 | $this->model = $element; 58 | $this->parent = $parent; 59 | } 60 | } 61 | 62 | ?> -------------------------------------------------------------------------------- /inc/sfYaml/sfYaml.class.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | /** 12 | * sfYaml class. 13 | * 14 | * @package symfony 15 | * @subpackage util 16 | * @author Fabien Potencier 17 | * @version SVN: $Id: sfYaml.class.php 8991 2008-05-16 00:14:34Z dwhittle $ 18 | */ 19 | class sfYaml 20 | { 21 | /** 22 | * Load YAML into a PHP array statically 23 | * 24 | * The load method, when supplied with a YAML stream (string or file), 25 | * will do its best to convert YAML in a file into a PHP array. 26 | * 27 | * Usage: 28 | * 29 | * $array = sfYAML::Load('config.yml'); 30 | * print_r($array); 31 | * 32 | * 33 | * @param string $input Path of YAML file or string containing YAML 34 | * 35 | * @return array 36 | */ 37 | public static function load($input) 38 | { 39 | $file = ''; 40 | 41 | // if input is a file, process it 42 | if (strpos($input, "\n") === false && is_file($input)) 43 | { 44 | $file = $input; 45 | 46 | ob_start(); 47 | $retval = include($input); 48 | $content = ob_get_clean(); 49 | 50 | // if an array is returned by the config file assume it's in plain php form else in yaml 51 | $input = is_array($retval) ? $retval : $content; 52 | } 53 | 54 | // if an array is returned by the config file assume it's in plain php form else in yaml 55 | if (is_array($input)) 56 | { 57 | return $input; 58 | } 59 | 60 | require_once dirname(__FILE__).'/sfYamlParser.class.php'; 61 | 62 | $yaml = new sfYamlParser(); 63 | 64 | try 65 | { 66 | $ret = $yaml->parse($input); 67 | } 68 | catch (Exception $e) 69 | { 70 | throw new InvalidArgumentException(sprintf('Unable to parse %s: %s', $file ? sprintf('file "%s"', $file) : 'string', $e->getMessage())); 71 | } 72 | 73 | return $ret; 74 | } 75 | 76 | /** 77 | * Dump YAML from PHP array statically 78 | * 79 | * The dump method, when supplied with an array, will do its best 80 | * to convert the array into friendly YAML. 81 | * 82 | * @param array $array PHP array 83 | * 84 | * @return string 85 | */ 86 | public static function dump($array, $inline = 2) 87 | { 88 | require_once dirname(__FILE__).'/sfYamlDumper.class.php'; 89 | 90 | $yaml = new sfYamlDumper(); 91 | 92 | return $yaml->dump($array, $inline); 93 | } 94 | } 95 | 96 | /** 97 | * Wraps echo to automatically provide a newline. 98 | * 99 | * @param string The string to echo with new line 100 | */ 101 | function echoln($string) 102 | { 103 | echo $string."\n"; 104 | } 105 | -------------------------------------------------------------------------------- /inc/Services/Twitter/Favorites.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Favorites 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_Favorites extends Services_Twitter_Common 56 | { 57 | /** 58 | * Favorite a status ID 59 | * 60 | * @param integer $id The status ID to favorite 61 | * 62 | * @throws Services_Twitter_Exception 63 | * @return object Instance of SimpleXMLElement 64 | */ 65 | public function create($id) 66 | { 67 | return $this->sendRequest( 68 | '/favorites/create/' . intval($id), array(), 'POST' 69 | ); 70 | } 71 | 72 | /** 73 | * Unfavorite a status ID 74 | * 75 | * @param integer $id The status ID to unfavorite 76 | * 77 | * @throws Services_Twitter_Exception 78 | * @return object Instance of SimpleXMLElement 79 | */ 80 | public function destroy($id) 81 | { 82 | return $this->sendRequest( 83 | '/favorites/destroy/' . intval($id), array(), 'POST' 84 | ); 85 | } 86 | } 87 | 88 | ?> 89 | -------------------------------------------------------------------------------- /inc/Services/Twitter/Notifications.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Exception 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_Notifications extends Services_Twitter_Common 56 | { 57 | /** 58 | * Quit getting status updates from a person 59 | * 60 | * @param integer $id The username/ID of the person to stop statuses of 61 | * 62 | * @throws Services_Twitter_Exception 63 | * @return object Instance of SimpleXMLElement 64 | */ 65 | public function leave($id) 66 | { 67 | return $this->sendRequest( 68 | '/notifications/leave/' . $id, array(), 'POST' 69 | ); 70 | } 71 | 72 | /** 73 | * Get status updates from a person 74 | * 75 | * @param integer $id The username/ID of the person to get statuses of 76 | * 77 | * @throws Services_Twitter_Exception 78 | * @return object Instance of SimpleXMLElement 79 | */ 80 | public function follow($id) 81 | { 82 | return $this->sendRequest( 83 | '/notifications/follow/' . $id, array(), 'POST' 84 | ); 85 | } 86 | } 87 | 88 | ?> 89 | -------------------------------------------------------------------------------- /inc/HTTP/Request/Listener.php: -------------------------------------------------------------------------------- 1 | 40 | * @copyright 2002-2007 Richard Heyes 41 | * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 | * @version CVS: $Id: Listener.php,v 1.3 2007/05/18 10:33:31 avb Exp $ 43 | * @link http://pear.php.net/package/HTTP_Request/ 44 | */ 45 | 46 | /** 47 | * Listener for HTTP_Request and HTTP_Response objects 48 | * 49 | * This class implements the Observer part of a Subject-Observer 50 | * design pattern. 51 | * 52 | * @category HTTP 53 | * @package HTTP_Request 54 | * @author Alexey Borzov 55 | * @version Release: 1.4.4 56 | */ 57 | class HTTP_Request_Listener 58 | { 59 | /** 60 | * A listener's identifier 61 | * @var string 62 | */ 63 | var $_id; 64 | 65 | /** 66 | * Constructor, sets the object's identifier 67 | * 68 | * @access public 69 | */ 70 | function HTTP_Request_Listener() 71 | { 72 | $this->_id = md5(uniqid('http_request_', 1)); 73 | } 74 | 75 | 76 | /** 77 | * Returns the listener's identifier 78 | * 79 | * @access public 80 | * @return string 81 | */ 82 | function getId() 83 | { 84 | return $this->_id; 85 | } 86 | 87 | 88 | /** 89 | * This method is called when Listener is notified of an event 90 | * 91 | * @access public 92 | * @param object an object the listener is attached to 93 | * @param string Event name 94 | * @param mixed Additional data 95 | * @abstract 96 | */ 97 | function update(&$subject, $event, $data = null) 98 | { 99 | echo "Notified of event: '$event'\n"; 100 | if (null !== $data) { 101 | echo "Additional data: "; 102 | var_dump($data); 103 | } 104 | } 105 | } 106 | ?> 107 | -------------------------------------------------------------------------------- /inc/Services/Twitter/Friendships.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Friendships 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_Friendships extends Services_Twitter_Common 56 | { 57 | /** 58 | * __call 59 | * 60 | * Implements: 61 | * 1. Services_Twitter_Friendships::create() 62 | * 2. Services_Twitter_Friendships::destroy() 63 | * 64 | * @param string $function Name of function being called (create/destroy) 65 | * @param array $args The arguments passed to the function call 66 | * 67 | * @return object Instance of SimpleXMLElement 68 | * @throws Services_Twitter_Exception 69 | */ 70 | public function __call($function, array $args = array()) 71 | { 72 | switch ($function) { 73 | case 'create': 74 | case 'destroy': 75 | if (!isset($args[0]) || 76 | preg_match('/[^a-z0-9_]+/i', $args[0])) { 77 | throw new Services_Twitter_Exception( 78 | 'A valid username or user id is required' 79 | ); 80 | } 81 | 82 | return $this->sendRequest( 83 | '/friendships/' . $function . '/' . $args[0], array(), 'POST' 84 | ); 85 | 86 | break; 87 | default: 88 | throw new Services_Twitter_Exception( 89 | get_class($this) . '::' . $function . '() is not a supported method' 90 | ); 91 | } 92 | } 93 | } 94 | 95 | ?> 96 | -------------------------------------------------------------------------------- /inc/Services/Twitter/Users.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Exception 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_Users extends Services_Twitter_Common 56 | { 57 | /** 58 | * Show extended information for a given user 59 | * 60 | * This will return the extended information of a given user. This 61 | * information includes design settings, so third party developers can 62 | * theme their widgets according to a given user's preferences. 63 | * 64 | * The parameter passed can be the user ID (integer) of the user, their 65 | * username (string) or the user's email address (string). NOTE: Strict 66 | * validation on email addresses is NOT done in this package. 67 | * 68 | * @param mixed $id_or_email Integer user ID, username or email address 69 | * 70 | * @return object Instance of SimpleXMLElement 71 | * @throws Services_Twitter_Exception 72 | * @see Services_Twitter_Common::sendRequest() 73 | */ 74 | public function show($id_or_email) 75 | { 76 | $params = array(); 77 | 78 | /** 79 | * This is by far not the most robust email checker here, but the 80 | * API will return an error if it's invalid and I don't want to base 81 | * this package on Validate, which isn't PHP5. 82 | */ 83 | if (strpos($id_or_email, '@')) { 84 | $endPoint = '/users/show'; 85 | $params['email'] = $id_or_email; 86 | } else { 87 | $endPoint = '/users/show/' . $id_or_email; 88 | } 89 | 90 | return $this->sendRequest($endPoint, $params); 91 | } 92 | } 93 | 94 | ?> 95 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS1Element.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS1Element.php,v 1.6 2006/06/30 17:41:56 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /* 25 | * This class provides support for RSS 1.0 entries. It will usually be called by 26 | * XML_Feed_Parser_RSS1 with which it shares many methods. 27 | * 28 | * @author James Stewart 29 | * @version Release: 1.0.3 30 | * @package XML_Feed_Parser 31 | */ 32 | class XML_Feed_Parser_RSS1Element extends XML_Feed_Parser_RSS1 33 | { 34 | /** 35 | * This will be a reference to the parent object for when we want 36 | * to use a 'fallback' rule 37 | * @var XML_Feed_Parser_RSS1 38 | */ 39 | protected $parent; 40 | 41 | /** 42 | * Our specific element map 43 | * @var array 44 | */ 45 | protected $map = array( 46 | 'id' => array('Id'), 47 | 'title' => array('Text'), 48 | 'link' => array('Link'), 49 | 'description' => array('Text'), # or dc:description 50 | 'category' => array('Category'), 51 | 'rights' => array('Text'), # dc:rights 52 | 'creator' => array('Text'), # dc:creator 53 | 'publisher' => array('Text'), # dc:publisher 54 | 'contributor' => array('Text'), # dc:contributor 55 | 'date' => array('Date'), # dc:date 56 | 'content' => array('Content') 57 | ); 58 | 59 | /** 60 | * Here we map some elements to their atom equivalents. This is going to be 61 | * quite tricky to pull off effectively (and some users' methods may vary) 62 | * but is worth trying. The key is the atom version, the value is RSS1. 63 | * @var array 64 | */ 65 | protected $compatMap = array( 66 | 'content' => array('content'), 67 | 'updated' => array('lastBuildDate'), 68 | 'published' => array('pubdate'), 69 | 'subtitle' => array('description'), 70 | 'updated' => array('date'), 71 | 'author' => array('creator'), 72 | 'contributor' => array('contributor') 73 | ); 74 | 75 | /** 76 | * Store useful information for later. 77 | * 78 | * @param DOMElement $element - this item as a DOM element 79 | * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member 80 | */ 81 | function __construct(DOMElement $element, $parent, $xmlBase = '') 82 | { 83 | $this->model = $element; 84 | $this->parent = $parent; 85 | } 86 | 87 | /** 88 | * If an rdf:about attribute is specified, return it as an ID 89 | * 90 | * There is no established way of showing an ID for an RSS1 entry. We will 91 | * simulate it using the rdf:about attribute of the entry element. This cannot 92 | * be relied upon for unique IDs but may prove useful. 93 | * 94 | * @return string|false 95 | */ 96 | function getId() 97 | { 98 | if ($this->model->attributes->getNamedItem('about')) { 99 | return $this->model->attributes->getNamedItem('about')->nodeValue; 100 | } 101 | return false; 102 | } 103 | 104 | /** 105 | * How RSS1 should support for enclosures is not clear. For now we will return 106 | * false. 107 | * 108 | * @return false 109 | */ 110 | function getEnclosure() 111 | { 112 | return false; 113 | } 114 | } 115 | 116 | ?> -------------------------------------------------------------------------------- /inc/Services/Twitter/Exception.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | require_once 'PEAR/Exception.php'; 47 | 48 | /** 49 | * Services_Twitter_Exception 50 | * 51 | * @category Services 52 | * @package Services_Twitter 53 | * @author Joe Stump 54 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 55 | * @link http://twitter.com 56 | */ 57 | class Services_Twitter_Exception extends PEAR_Exception 58 | { 59 | /** 60 | * Call to the API that created the error 61 | * 62 | * @var string $call 63 | */ 64 | protected $call = ''; 65 | 66 | /** 67 | * The raw response returned by the API 68 | * 69 | * @var string $response 70 | */ 71 | protected $response = ''; 72 | 73 | /** 74 | * Constructor 75 | * 76 | * @param string $message Error message 77 | * @param integer $code Error code 78 | * @param string $call API call that generated error 79 | * @param string $response The raw response that produced the erorr 80 | * 81 | * @see Services_Twitter_Exception::$call 82 | * @link http://php.net/exceptions 83 | */ 84 | public function __construct($message = null, 85 | $code = 0, 86 | $call = '', 87 | $response = '') 88 | { 89 | parent::__construct($message, $code); 90 | $this->call = $call; 91 | $this->response = $response; 92 | } 93 | 94 | /** 95 | * Return API call 96 | * 97 | * @return string 98 | * @see Services_Twitter_Exception::$call 99 | */ 100 | public function getCall() 101 | { 102 | return $this->call; 103 | } 104 | 105 | /** 106 | * Get the raw API response that died 107 | * 108 | * @return string 109 | * @see Services_Twitter_Exception::$response 110 | */ 111 | public function getResponse() 112 | { 113 | return $this->response; 114 | } 115 | 116 | /** 117 | * __toString 118 | * 119 | * Overload PEAR_Exception's horrible __toString implementation. 120 | * 121 | * @return string 122 | */ 123 | public function __toString() 124 | { 125 | return $this->message . ' (Code: ' . $this->code . ', Call: ' . 126 | $this->call . ')'; 127 | } 128 | } 129 | 130 | ?> 131 | -------------------------------------------------------------------------------- /inc/Services/Twitter/Search.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Search 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | * @link http://apiwiki.twitter.com/Search+API+Documentation 55 | */ 56 | class Services_Twitter_Search extends Services_Twitter_Common 57 | { 58 | /** 59 | * The search API's URI 60 | * 61 | * @var string $uri The search API's URI 62 | * @link http://apiwiki.twitter.com/Search+API+Documentation 63 | */ 64 | static private $uri = 'http://search.twitter.com'; 65 | 66 | /** 67 | * Constructor 68 | * 69 | * @see Services_Twitter_Common::sendRequest() 70 | * @see Services_Twitter_Common::__construct() 71 | * @return void 72 | */ 73 | public function __construct() 74 | { 75 | // Disables authentication in sendRequest() 76 | $this->user = $this->pass = null; 77 | } 78 | 79 | /** 80 | * Get search trends 81 | * 82 | * @return object The top ten trends on Twitter 83 | * @see Services_Twitter_Search::sendRequest() 84 | * @throws {@link Services_Twitter_Exception} on API/HTTP errors 85 | */ 86 | public function trends() 87 | { 88 | return $this->sendRequest('/trends'); 89 | } 90 | 91 | /** 92 | * Query the search API 93 | * 94 | * @param string $query The full query to send 95 | * 96 | * @throws {@link Services_Twitter_Exception} on API/HTTP errors 97 | * @return object The results of the query according to the API 98 | * @see Services_Twitter_Search::sendRequest() 99 | */ 100 | public function query($query) 101 | { 102 | return $this->sendRequest('/search', array('q' => $query)); 103 | } 104 | 105 | /** 106 | * Send a special request to Search API 107 | * 108 | * @param string $endPoint The search API endpoint minus extension 109 | * @param array $params An array of parameters 110 | * 111 | * @throws {@link Services_Twitter_Exception} on API/HTTP errors 112 | * @return object Instance of stdClass of JSON response 113 | * @see Services_Common::sendRequest() 114 | */ 115 | protected function sendRequest($endPoint, array $params = array()) 116 | { 117 | $endPoint = self::$uri . $endPoint . '.' . 118 | Services_Twitter::OUTPUT_JSON; 119 | 120 | return parent::sendRequest( 121 | $endPoint, $params, 'GET', Services_Twitter::OUTPUT_JSON 122 | ); 123 | } 124 | } 125 | 126 | ?> 127 | -------------------------------------------------------------------------------- /inc/Services/Twitter/Account.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Account 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_Account extends Services_Twitter_Common 56 | { 57 | /** 58 | * Verify a user's credentials 59 | * 60 | * @return boolean 61 | * @see Services_Twitter_Common::sendRequest() 62 | */ 63 | public function verify_credentials() 64 | { 65 | $res = $this->sendRequest('/account/verify_credentials'); 66 | return ((string)$res === 'true'); 67 | } 68 | 69 | /** 70 | * End a person's session 71 | * 72 | * Insert long rant about Twitter's API being inconsistent. This endpoint 73 | * returns 'Logged out.' in plain text so we overload it and check the 74 | * response string from the exception inevitably thrown when it can't 75 | * parse the XML. 76 | * 77 | * @return boolean 78 | * @see Services_Twitter_Common::sendRequest() 79 | * @see Services_Twitter_Exception::getResponse() 80 | */ 81 | public function end_session() 82 | { 83 | try { 84 | $res = $this->sendRequest('/account/end_session', array(), 'POST'); 85 | return (trim(strval($res->error)) == 'Logged out.'); 86 | } catch (Services_Twitter_Exception $e) { 87 | return false; 88 | } 89 | } 90 | 91 | /** 92 | * Update a user's location 93 | * 94 | * @param string $location The user's new location 95 | * 96 | * @return boolean True if the location was updated successfully 97 | * @see Services_Twitter_Common::sendRequest() 98 | * @link http://apiwiki.twitter.com/REST+API+Documentation#updatelocationnbsp 99 | */ 100 | public function update_location($location) 101 | { 102 | try { 103 | $res = $this->sendRequest( 104 | '/account/update_location', array( 105 | 'location' => $location 106 | ), 'POST' 107 | ); 108 | 109 | return (strval($res->location) == $location); 110 | } catch (Services_Twitter_Exception $e) { 111 | return false; 112 | } 113 | } 114 | 115 | /** 116 | * Update a user's delivery device 117 | * 118 | * @param string $device The new device (im, sms or none) 119 | * 120 | * @return object Instance of SimpleXmlElement returned from API 121 | * @see Services_Twitter_Common::sendRequest() 122 | * @link http://apiwiki.twitter.com/REST+API+Documentation#updatedeliverydevice 123 | */ 124 | public function update_delivery_device($device) 125 | { 126 | if (!in_array($device, array('im', 'sms', 'none'))) { 127 | throw new Services_Twitter_Exception('Invalid device: ' . $device); 128 | } 129 | 130 | return $this->sendRequest( 131 | '/account/update_delivery_device', array( 132 | 'device' => $device 133 | ), 'POST' 134 | ); 135 | } 136 | } 137 | 138 | ?> 139 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS11Element.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS11Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /* 25 | * This class provides support for RSS 1.1 entries. It will usually be called by 26 | * XML_Feed_Parser_RSS11 with which it shares many methods. 27 | * 28 | * @author James Stewart 29 | * @version Release: 1.0.3 30 | * @package XML_Feed_Parser 31 | */ 32 | class XML_Feed_Parser_RSS11Element extends XML_Feed_Parser_RSS11 33 | { 34 | /** 35 | * This will be a reference to the parent object for when we want 36 | * to use a 'fallback' rule 37 | * @var XML_Feed_Parser_RSS1 38 | */ 39 | protected $parent; 40 | 41 | /** 42 | * Our specific element map 43 | * @var array 44 | */ 45 | protected $map = array( 46 | 'id' => array('Id'), 47 | 'title' => array('Text'), 48 | 'link' => array('Link'), 49 | 'description' => array('Text'), # or dc:description 50 | 'category' => array('Category'), 51 | 'rights' => array('Text'), # dc:rights 52 | 'creator' => array('Text'), # dc:creator 53 | 'publisher' => array('Text'), # dc:publisher 54 | 'contributor' => array('Text'), # dc:contributor 55 | 'date' => array('Date'), # dc:date 56 | 'content' => array('Content') 57 | ); 58 | 59 | /** 60 | * Here we map some elements to their atom equivalents. This is going to be 61 | * quite tricky to pull off effectively (and some users' methods may vary) 62 | * but is worth trying. The key is the atom version, the value is RSS1. 63 | * @var array 64 | */ 65 | protected $compatMap = array( 66 | 'content' => array('content'), 67 | 'updated' => array('lastBuildDate'), 68 | 'published' => array('pubdate'), 69 | 'subtitle' => array('description'), 70 | 'updated' => array('date'), 71 | 'author' => array('creator'), 72 | 'contributor' => array('contributor') 73 | ); 74 | 75 | /** 76 | * Store useful information for later. 77 | * 78 | * @param DOMElement $element - this item as a DOM element 79 | * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member 80 | */ 81 | function __construct(DOMElement $element, $parent, $xmlBase = '') 82 | { 83 | $this->model = $element; 84 | $this->parent = $parent; 85 | } 86 | 87 | /** 88 | * If an rdf:about attribute is specified, return that as an ID 89 | * 90 | * There is no established way of showing an ID for an RSS1 entry. We will 91 | * simulate it using the rdf:about attribute of the entry element. This cannot 92 | * be relied upon for unique IDs but may prove useful. 93 | * 94 | * @return string|false 95 | */ 96 | function getId() 97 | { 98 | if ($this->model->attributes->getNamedItem('about')) { 99 | return $this->model->attributes->getNamedItem('about')->nodeValue; 100 | } 101 | return false; 102 | } 103 | 104 | /** 105 | * Return the entry's content 106 | * 107 | * The official way to include full content in an RSS1 entry is to use 108 | * the content module's element 'encoded'. Often, however, the 'description' 109 | * element is used instead. We will offer that as a fallback. 110 | * 111 | * @return string|false 112 | */ 113 | function getContent() 114 | { 115 | $options = array('encoded', 'description'); 116 | foreach ($options as $element) { 117 | $test = $this->model->getElementsByTagName($element); 118 | if ($test->length == 0) { 119 | continue; 120 | } 121 | if ($test->item(0)->hasChildNodes()) { 122 | $value = ''; 123 | foreach ($test->item(0)->childNodes as $child) { 124 | if ($child instanceof DOMText) { 125 | $value .= $child->nodeValue; 126 | } else { 127 | $simple = simplexml_import_dom($child); 128 | $value .= $simple->asXML(); 129 | } 130 | } 131 | return $value; 132 | } else if ($test->length > 0) { 133 | return $test->item(0)->nodeValue; 134 | } 135 | } 136 | return false; 137 | } 138 | 139 | /** 140 | * How RSS1.1 should support for enclosures is not clear. For now we will return 141 | * false. 142 | * 143 | * @return false 144 | */ 145 | function getEnclosure() 146 | { 147 | return false; 148 | } 149 | } 150 | 151 | ?> -------------------------------------------------------------------------------- /inc/Services/Twitter/DirectMessages.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_DirectMessages 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_DirectMessages extends Services_Twitter_Common 56 | { 57 | /** 58 | * Group name 59 | * 60 | * @var string $name Overload name of API group 61 | * @see Services_Twitter_Common::$name 62 | */ 63 | protected $name = 'direct_messages'; 64 | 65 | /** 66 | * Delete/Destroy a direct message 67 | * 68 | * @param integer $id The direct message id to delete 69 | * 70 | * @return object Instance of SimpleXMLElement 71 | * @throws Services_Twitter_Exception 72 | * @see Services_Twitter_Common::sendRequest() 73 | */ 74 | public function destroy($id) 75 | { 76 | if (!is_numeric($id)) { 77 | throw new Services_Twitter_Exception( 78 | $id . ' is not a valid numeric id' 79 | ); 80 | } 81 | 82 | return $this->sendRequest( 83 | '/direct_messages/destroy/' . $id, array(), 'POST' 84 | ); 85 | } 86 | 87 | /** 88 | * __call 89 | * 90 | * Evidently, PHP considers 'new' a reserved keyword. I really, really 91 | * wanted to keep the interface the same across the board and Twitter uses 92 | * /direct_messages/new so Services_Twitter should use 93 | * $foo->direct_messages->new(), which was causing parse errors. This is 94 | * an ugly hack to work around this issue. 95 | * 96 | * @param string $function The API endpoint to call 97 | * @param array $args The arguments for the API endpoint 98 | * 99 | * @throws Services_Twitter_Exception 100 | * @return object Instance of SimpleXMLElement 101 | * @see Services_Twitter_DirectMessages::dmNew() 102 | * @see Services_Twitter_Common::__call() 103 | */ 104 | public function __call($function, array $args = array()) 105 | { 106 | switch ($function) { 107 | case 'new': 108 | if (!isset($args[0]) || !isset($args[1])) { 109 | throw new Services_Twitter_Exception( 110 | 'id and text are required when sending a direct message', 111 | Services_Twitter::ERROR_UNKNOWN 112 | ); 113 | } 114 | 115 | return $this->dmNew($args[0], $args[1]); 116 | default: 117 | return parent::__call($function, $args); 118 | } 119 | } 120 | 121 | /** 122 | * Send a direct message 123 | * 124 | * @param string $user The id or username to send to 125 | * @param string $text The direct text to send 126 | * 127 | * @return object Instance of SimpleXMLElement of new status 128 | * @throws Services_Twitter_Exception 129 | * @see Services_Twitter_Common::sendRequest() 130 | */ 131 | protected function dmNew($user, $text) 132 | { 133 | if (preg_match('/[^a-z0-9_]/i', $user)) { 134 | throw new Services_Twitter_Exception( 135 | 'The user (' . $user . ') provided appears to be invalid', 136 | Services_Twitter::ERROR_UNKNOWN 137 | ); 138 | } 139 | 140 | if (!strlen($text)) { 141 | throw new Services_Twitter_Exception( 142 | 'Statuses cannot be empty strings', 143 | Services_Twitter::ERROR_UNKNOWN 144 | ); 145 | } 146 | 147 | return $this->sendRequest('/direct_messages/new', array( 148 | 'user' => $user, 149 | 'text' => $text 150 | ), 'POST'); 151 | } 152 | } 153 | 154 | ?> 155 | -------------------------------------------------------------------------------- /inc/ProwlPHP/ProwlPHP.php: -------------------------------------------------------------------------------- 1 | maxsize] 22 | 'apikey' => 204, // User API Key. 23 | 'providerkey' => 40, // Provider key. 24 | 'priority' => 2, // Range from -2 to 2. 25 | 'application' => 254, // Name of the app. 26 | 'event' => 1024, // Name of the event. 27 | 'description' => 10000, // Description of the event. 28 | ); 29 | 30 | public function __construct($apikey=null, $verify=false, $provkey=null, $proxy=null, $userpwd=null) 31 | { 32 | $curl_info = curl_version(); // Checks for cURL function and SSL version. Thanks Adrian Rollett! 33 | if(!function_exists('curl_exec') || empty($curl_info['ssl_version'])) 34 | { 35 | die($this->getError(10000)); 36 | } 37 | 38 | if(isset($proxy)) 39 | $this->_setProxy($proxy, $userpwd); 40 | 41 | if(isset($apikey) && $verify) 42 | $this->verify($apikey, $provkey); 43 | 44 | $this->_api_key = $apikey; 45 | } 46 | 47 | public function verify($apikey, $provkey) 48 | { 49 | $return = $this->_execute(sprintf($this->_url_verify, $apikey, $provkey)); 50 | return $this->_response($return); 51 | } 52 | 53 | public function push($params, $is_post=false) 54 | { 55 | if($is_post) 56 | $post_params = ''; 57 | 58 | $url = $is_post ? $this->_url_push : $this->_url_push . '?'; 59 | $params = func_get_args(); 60 | 61 | if(isset($this->_api_key) && !isset($params[0]['apikey'])) 62 | $params[0]['apikey'] = $this->_api_key; 63 | 64 | if(isset($this->_prov_key) && !isset($params[0]['providerkey'])) 65 | $params[0]['providerkey'] = $this->_prov_key; 66 | 67 | foreach($params[0] as $k => $v) 68 | { 69 | $v = str_replace("\\n","\n",$v); // Fixes line break issue! Cheers Fr3d! 70 | if(!isset($this->_params[$k])) 71 | { 72 | $this->_return_code = 400; 73 | return false; 74 | } 75 | if(strlen($v) > $this->_params[$k]) 76 | { 77 | $this->_return_code = 10001; 78 | return false; 79 | } 80 | 81 | if($is_post) 82 | $post_params .= $k . '=' . urlencode($v) . '&'; 83 | else 84 | $url .= $k . '=' . urlencode($v) . '&'; 85 | } 86 | 87 | if($is_post) 88 | $params = substr($post_params, 0, strlen($post_params)-1); 89 | else 90 | $url = substr($url, 0, strlen($url)-1); 91 | 92 | $return = $this->_execute($url, $is_post ? true : false, $params); 93 | 94 | return $this->_response($return); 95 | } 96 | 97 | public function getError($code=null) 98 | { 99 | $code = (empty($code)) ? $this->_return_code : $code; 100 | switch($code) 101 | { 102 | case 200: return 'Request Successful.'; break; 103 | case 400: return 'Bad request, the parameters you provided did not validate.'; break; 104 | case 401: return 'The API key given is not valid, and does not correspond to a user.'; break; 105 | case 405: return 'Method not allowed, you attempted to use a non-SSL connection to Prowl.'; break; 106 | case 406: return 'Your IP address has exceeded the API limit.'; break; 107 | case 500: return 'Internal server error, something failed to execute properly on the Prowl side.'; break; 108 | case 10000: return 'cURL library missing vital functions or does not support SSL. cURL w/SSL is required to execute ProwlPHP.'; break; 109 | case 10001: return 'Parameter value exceeds the maximum byte size.'; break; 110 | default: return false; break; 111 | } 112 | } 113 | 114 | public function getRemaining() 115 | { 116 | if(!isset($this->_remaining)) 117 | return false; 118 | 119 | return $this->_remaining; 120 | } 121 | 122 | public function getResetDate() 123 | { 124 | if(!isset($this->_resetdate)) 125 | return false; 126 | 127 | return $this->_resetdate; 128 | } 129 | 130 | private function _execute($url, $is_post=false, $params=null) 131 | { 132 | $this->_obj_curl = curl_init($this->_api_domain . $url); 133 | curl_setopt($this->_obj_curl, CURLOPT_HEADER, 0); 134 | curl_setopt($this->_obj_curl, CURLOPT_USERAGENT, "ProwlPHP/" . $this->_version); 135 | curl_setopt($this->_obj_curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 136 | curl_setopt($this->_obj_curl, CURLOPT_SSL_VERIFYPEER, false); 137 | curl_setopt($this->_obj_curl, CURLOPT_RETURNTRANSFER, 1); 138 | 139 | if($is_post) 140 | { 141 | curl_setopt($this->_obj_curl, CURLOPT_POST, 1); 142 | curl_setopt($this->_obj_curl, CURLOPT_POSTFIELDS, $params); 143 | } 144 | 145 | if($this->_use_proxy) 146 | { 147 | curl_setopt($this->_obj_curl, CURLOPT_HTTPPROXYTUNNEL, 1); 148 | curl_setopt($this->_obj_curl, CURLOPT_PROXY, $this->_proxy); 149 | curl_setopt($this->_obj_curl, CURLOPT_PROXYUSERPWD, $this->_proxy_userpwd); 150 | } 151 | 152 | $return = curl_exec($this->_obj_curl); 153 | curl_close($this->_obj_curl); 154 | return $return; 155 | } 156 | 157 | private function _response($return) 158 | { 159 | if($return===false) 160 | { 161 | $this->_return_code = 500; 162 | return false; 163 | } 164 | 165 | $response = new SimpleXMLElement($return); 166 | 167 | if(isset($response->success)) 168 | { 169 | $this->_return_code = (int)$response->success['code']; 170 | $this->_remaining = (int)$response->success['remaining']; 171 | $this->_resetdate = (int)$response->success['resetdate']; 172 | } 173 | else 174 | { 175 | $this->_return_code = $response->error['code']; 176 | } 177 | 178 | switch($this->_return_code) 179 | { 180 | case 200: return true; break; 181 | default: return false; break; 182 | } 183 | 184 | unset($response); 185 | } 186 | 187 | private function _setProxy($proxy, $userpwd=null) 188 | { 189 | if(strlen($proxy) > 0) 190 | { 191 | $this->_use_proxy = true; 192 | $this->_proxy = $proxy; 193 | $this->_proxy_userpwd = $userpwd; 194 | } 195 | } 196 | } 197 | 198 | ?> -------------------------------------------------------------------------------- /inc/Services/Twitter.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | require_once 'Services/Twitter/Exception.php'; 47 | require_once 'Services/Twitter/Common.php'; 48 | 49 | /** 50 | * Services_Twitter 51 | * 52 | * 53 | * statuses->update("I'm coding with PEAR right now!"); 62 | * print_r($msg); // Should be a SimpleXMLElement structure 63 | * } catch (Services_Twitter_Exception $e) { 64 | * echo $e->getMessage(); 65 | * } 66 | * ?> 67 | * 68 | * 69 | * @category Services 70 | * @package Services_Twitter 71 | * @author Joe Stump 72 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 73 | * @link http://twitter.com 74 | */ 75 | class Services_Twitter extends Services_Twitter_Common 76 | { 77 | /** 78 | * Twitter API error codes 79 | * 80 | * @global integer ERROR_UNKNOWN An unknown error occurred 81 | * @global integer ERROR_REQUEST Bad request sent 82 | * @global integer ERROR_AUTH Not authorized to do action 83 | * @global integer ERROR_FORBIDDEN Forbidden from doing action 84 | * @global integer ERROR_NOT_FOUND Item requested not found 85 | * @global integer ERROR_INTERNAL Internal Twitter error 86 | * @global integer ERROR_DOWN Twitter is down 87 | * @global integer ERROR_UNAVAILABLE API is overloaded 88 | */ 89 | const ERROR_UNKNOWN = 1; 90 | const ERROR_REQUEST = 400; 91 | const ERROR_AUTH = 401; 92 | const ERROR_FORBIDDEN = 403; 93 | const ERROR_NOT_FOUND = 404; 94 | const ERROR_INTERNAL = 500; 95 | const ERROR_DOWN = 502; 96 | const ERROR_UNAVAILABLE = 503; 97 | 98 | /** 99 | * Twitter API output parsing options 100 | * 101 | * @global string OUTPUT_XML The response is expected to be XML 102 | * @global string OUTPUT_JSON The response is expected to be JSON 103 | */ 104 | const OUTPUT_XML = 'xml'; 105 | const OUTPUT_JSON = 'json'; 106 | 107 | /** 108 | * Public URI of Twitter's API 109 | * 110 | * @var string $uri URI of Twitter API 111 | * @see Services_Twitter_Common::sendRequest() 112 | */ 113 | static public $uri = 'http://twitter.com'; 114 | 115 | /** 116 | * Supported areas / methods of Twitter's API 117 | * 118 | * @var array $methods 119 | * @see Services_Twitter::__get() 120 | */ 121 | static protected $methods = array( 122 | 'account' => 'Account', 123 | 'direct_messages' => 'DirectMessages', 124 | 'favorites' => 'Favorites', 125 | 'friendships' => 'Friendships', 126 | 'notifications' => 'Notifications', 127 | 'statuses' => 'Statuses', 128 | 'users' => 'Users', 129 | 'search' => 'Search' 130 | ); 131 | 132 | /** 133 | * Instances of Twitter methods 134 | * 135 | * @var array $instances 136 | * @see Services_Twitter::__get(), Services_Twitter::$methods 137 | */ 138 | protected $instances = array(); 139 | 140 | /** 141 | * Lazily load Twitter API drivers 142 | * 143 | * @param string $var Method to load 144 | * 145 | * @throws Services_Twitter_Exception 146 | * @return object Instance of API driver 147 | * @see Services_Twitter::factory() 148 | */ 149 | public function __get($var) 150 | { 151 | if (!isset(self::$methods[$var])) { 152 | throw new Services_Twitter_Exception( 153 | 'Method (' . $var . ') is not implemented' 154 | ); 155 | } 156 | 157 | return $this->factory(self::$methods[$var]); 158 | } 159 | 160 | /** 161 | * Instantiate API driver 162 | * 163 | * @param string $method API driver to load 164 | * 165 | * @return object Instance of API driver 166 | */ 167 | protected function factory($method) 168 | { 169 | if (isset($this->instances[$method])) { 170 | return $this->instances[$method]; 171 | } 172 | 173 | $file = 'Services/Twitter/' . $method . '.php'; 174 | include_once $file; 175 | 176 | $class = 'Services_Twitter_' . $method; 177 | $this->instances[$method] = new $class($this->user, $this->pass); 178 | return $this->instances[$method]; 179 | } 180 | } 181 | 182 | ?> 183 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS2Element.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS2Element.php,v 1.11 2006/07/26 21:18:47 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This class provides support for RSS 2.0 entries. It will usually be 26 | * called by XML_Feed_Parser_RSS2 with which it shares many methods. 27 | * 28 | * @author James Stewart 29 | * @version Release: 1.0.3 30 | * @package XML_Feed_Parser 31 | */ 32 | class XML_Feed_Parser_RSS2Element extends XML_Feed_Parser_RSS2 33 | { 34 | /** 35 | * This will be a reference to the parent object for when we want 36 | * to use a 'fallback' rule 37 | * @var XML_Feed_Parser_RSS2 38 | */ 39 | protected $parent; 40 | 41 | /** 42 | * Our specific element map 43 | * @var array 44 | */ 45 | protected $map = array( 46 | 'title' => array('Text'), 47 | 'guid' => array('Guid'), 48 | 'description' => array('Text'), 49 | 'author' => array('Text'), 50 | 'comments' => array('Text'), 51 | 'enclosure' => array('Enclosure'), 52 | 'pubDate' => array('Date'), 53 | 'source' => array('Source'), 54 | 'link' => array('Text'), 55 | 'lat' => array('Text'), 56 | 'long' => array('Text'), 57 | 'content' => array('Content')); 58 | 59 | /** 60 | * Here we map some elements to their atom equivalents. This is going to be 61 | * quite tricky to pull off effectively (and some users' methods may vary) 62 | * but is worth trying. The key is the atom version, the value is RSS2. 63 | * @var array 64 | */ 65 | protected $compatMap = array( 66 | 'id' => array('guid'), 67 | 'updated' => array('lastBuildDate'), 68 | 'published' => array('pubdate'), 69 | 'guidislink' => array('guid', 'ispermalink'), 70 | 'summary' => array('description')); 71 | 72 | /** 73 | * Store useful information for later. 74 | * 75 | * @param DOMElement $element - this item as a DOM element 76 | * @param XML_Feed_Parser_RSS2 $parent - the feed of which this is a member 77 | */ 78 | function __construct(DOMElement $element, $parent, $xmlBase = '') 79 | { 80 | $this->model = $element; 81 | $this->parent = $parent; 82 | } 83 | 84 | /** 85 | * Get the value of the guid element, if specified 86 | * 87 | * guid is the closest RSS2 has to atom's ID. It is usually but not always a 88 | * URI. The one attribute that RSS2 can posess is 'ispermalink' which specifies 89 | * whether the guid is itself dereferencable. Use of guid is not obligatory, 90 | * but is advisable. To get the guid you would call $item->id() (for atom 91 | * compatibility) or $item->guid(). To check if this guid is a permalink call 92 | * $item->guid("ispermalink"). 93 | * 94 | * @param string $method - the method name being called 95 | * @param array $params - parameters required 96 | * @return string the guid or value of ispermalink 97 | */ 98 | protected function getGuid($method, $params) 99 | { 100 | $attribute = (isset($params[0]) and $params[0] == 'ispermalink') ? 101 | true : false; 102 | $tag = $this->model->getElementsByTagName('guid'); 103 | if ($tag->length > 0) { 104 | if ($attribute) { 105 | if ($tag->hasAttribute("ispermalink")) { 106 | return $tag->getAttribute("ispermalink"); 107 | } 108 | } 109 | return $tag->item(0)->nodeValue; 110 | } 111 | return false; 112 | } 113 | 114 | /** 115 | * Access details of file enclosures 116 | * 117 | * The RSS2 spec is ambiguous as to whether an enclosure element must be 118 | * unique in a given entry. For now we will assume it needn't, and allow 119 | * for an offset. 120 | * 121 | * @param string $method - the method being called 122 | * @param array $parameters - we expect the first of these to be our offset 123 | * @return array|false 124 | */ 125 | protected function getEnclosure($method, $parameters) 126 | { 127 | $encs = $this->model->getElementsByTagName('enclosure'); 128 | $offset = isset($parameters[0]) ? $parameters[0] : 0; 129 | if ($encs->length > $offset) { 130 | try { 131 | if (! $encs->item($offset)->hasAttribute('url')) { 132 | return false; 133 | } 134 | $attrs = $encs->item($offset)->attributes; 135 | return array( 136 | 'url' => $attrs->getNamedItem('url')->value, 137 | 'length' => $attrs->getNamedItem('length')->value, 138 | 'type' => $attrs->getNamedItem('type')->value); 139 | } catch (Exception $e) { 140 | return false; 141 | } 142 | } 143 | return false; 144 | } 145 | 146 | /** 147 | * Get the entry source if specified 148 | * 149 | * source is an optional sub-element of item. Like atom:source it tells 150 | * us about where the entry came from (eg. if it's been copied from another 151 | * feed). It is not a rich source of metadata in the same way as atom:source 152 | * and while it would be good to maintain compatibility by returning an 153 | * XML_Feed_Parser_RSS2 element, it makes a lot more sense to return an array. 154 | * 155 | * @return array|false 156 | */ 157 | protected function getSource() 158 | { 159 | $get = $this->model->getElementsByTagName('source'); 160 | if ($get->length) { 161 | $source = $get->item(0); 162 | $array = array( 163 | 'content' => $source->nodeValue); 164 | foreach ($source->attributes as $attribute) { 165 | $array[$attribute->name] = $attribute->value; 166 | } 167 | return $array; 168 | } 169 | return false; 170 | } 171 | } 172 | 173 | ?> -------------------------------------------------------------------------------- /inc/Services/Twitter/Statuses.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | /** 47 | * Services_Twitter_Statuses 48 | * 49 | * @category Services 50 | * @package Services_Twitter 51 | * @author Joe Stump 52 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 53 | * @link http://twitter.com 54 | */ 55 | class Services_Twitter_Statuses extends Services_Twitter_Common 56 | { 57 | /** 58 | * Fetch a specific status message 59 | * 60 | * @param integer $id The unique numeric status ID 61 | * 62 | * @return object Instance of SimpleXMLElement of new status 63 | * @throws Services_Twitter_Exception 64 | * @see Services_Twitter_Common::sendRequest() 65 | */ 66 | public function show($id) 67 | { 68 | return $this->sendRequest('/statuses/show/' . (int)$id); 69 | } 70 | 71 | /** 72 | * Destroy a specific status message 73 | * 74 | * @param integer $id The unique numeric status ID 75 | * 76 | * @return object Instance of SimpleXMLElement of new status 77 | * @throws Services_Twitter_Exception 78 | * @see Services_Twitter_Common::sendRequest() 79 | */ 80 | public function destroy($id) 81 | { 82 | return $this->sendRequest( 83 | '/statuses/destroy/' . (int)$id, array(), 'POST' 84 | ); 85 | } 86 | 87 | /** 88 | * Update the Twitter status 89 | * 90 | * @param string $status New Twitter status 91 | * @param integer $inReplyTo Status ID being replied to 92 | * 93 | * @return object Instance of SimpleXMLElement of new status 94 | * @throws Services_Twitter_Exception 95 | * @see Services_Twitter_Common::sendRequest() 96 | */ 97 | public function update($status, $inReplyTo = 0) 98 | { 99 | if (!strlen($status)) { 100 | throw new Services_Twitter_Exception( 101 | 'Statuses cannot be empty strings' 102 | ); 103 | } 104 | 105 | $params = array( 106 | 'status' => $status 107 | ); 108 | 109 | if ((int)$inReplyTo > 0) { 110 | $params['in_reply_to_status_id'] = (int)$inReplyTo; 111 | } 112 | 113 | return $this->sendRequest('/statuses/update', $params, 'POST'); 114 | } 115 | 116 | /** 117 | * Get the user's timeline 118 | * 119 | * This method was overridden from the normal 120 | * Services_Twitter_Common::__call() because it can optionally take an $id 121 | * of another user. If the key 'id' exists in the $params array then it 122 | * alters the behavior of what's returned. Also, any argument other than 123 | * 'id', 'since' and 'page' are currently ignored. 124 | * 125 | * @param array $params Parameters array 126 | * 127 | * @return object Instance of SimpleXMLElement of new status 128 | * @throws Services_Twitter_Exception 129 | * @see Services_Twitter_Common::sendRequest() 130 | */ 131 | public function user_timeline(array $params = array()) 132 | { 133 | $allowed = array('id', 'since', 'since_id', 'page'); 134 | $tmp = array(); 135 | foreach ($params as $key => $val) { 136 | if (in_array($key, $allowed)) { 137 | $tmp[$key] = $val; 138 | } 139 | } 140 | 141 | $endPoint = '/statuses/user_timeline'; 142 | if (isset($tmp['id'])) { 143 | $endPoint .= '/' . $tmp['id']; 144 | unset($tmp['id']); 145 | } 146 | 147 | $res = $this->sendRequest($endPoint, $tmp); 148 | if (!isset($res->status) || 149 | (is_array($res->status) && !count($res->status))) { 150 | throw new Services_Twitter_Exception( 151 | $this->user . " has no status updates" 152 | ); 153 | } 154 | 155 | return $res; 156 | } 157 | 158 | /** 159 | * Returns up to 100 of the user's friends 160 | * 161 | * @param array $params Parameters array 162 | * 163 | * @return object Instance of SimpleXMLElement of new status 164 | * @throws Services_Twitter_Exception 165 | * @see Services_Twitter_Common::sendRequest() 166 | */ 167 | public function friends(array $params = array()) 168 | { 169 | $allowed = array('id', 'lite', 'page'); 170 | $tmp = array(); 171 | foreach ($params as $key => $val) { 172 | if (in_array($key, $allowed)) { 173 | $tmp[$key] = $val; 174 | } 175 | } 176 | 177 | $tmp['lite'] = (isset($tmp['lite']) && 178 | $tmp['lite'] === true) ? 'true' : 'false'; 179 | 180 | $endPoint = '/statuses/friends'; 181 | if (isset($tmp['id']) && strlen($tmp['id'])) { 182 | $endPoint .= '/' . $tmp['id']; 183 | unset($tmp['id']); 184 | } 185 | 186 | return $this->sendRequest($endPoint, $tmp); 187 | } 188 | 189 | /** 190 | * Returns up to 100 of the user's followers 191 | * 192 | * @param array $params Parameters array 193 | * 194 | * @return object Instance of SimpleXMLElement of new status 195 | * @throws {@link Services_Twitter_Exception} on request problems 196 | * @see Services_Twitter_Common::sendRequest() 197 | */ 198 | public function followers(array $params = array(), $screenName = null) 199 | { 200 | $allowed = array('lite', 'page'); 201 | $tmp = array(); 202 | foreach ($params as $key => $val) { 203 | if (in_array($key, $allowed)) { 204 | $tmp[$key] = $val; 205 | } 206 | } 207 | 208 | $tmp['lite'] = (isset($tmp['lite']) && 209 | $tmp['lite'] === true) ? 'true' : 'false'; 210 | 211 | if ($screenName !== null) { 212 | return $this->sendRequest('/statuses/followers/' . $screenName, $tmp); 213 | } 214 | 215 | return $this->sendRequest('/statuses/followers', $tmp); 216 | } 217 | } 218 | 219 | ?> 220 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS09.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS09.php,v 1.5 2006/07/26 21:18:46 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This class handles RSS0.9 feeds. 26 | * 27 | * @author James Stewart 28 | * @version Release: 1.0.3 29 | * @package XML_Feed_Parser 30 | * @todo Find a Relax NG URI we can use 31 | */ 32 | class XML_Feed_Parser_RSS09 extends XML_Feed_Parser_Type 33 | { 34 | /** 35 | * The URI of the RelaxNG schema used to (optionally) validate the feed 36 | * @var string 37 | */ 38 | private $relax = ''; 39 | 40 | /** 41 | * We're likely to use XPath, so let's keep it global 42 | * @var DOMXPath 43 | */ 44 | protected $xpath; 45 | 46 | /** 47 | * The feed type we are parsing 48 | * @var string 49 | */ 50 | public $version = 'RSS 0.9'; 51 | 52 | /** 53 | * The class used to represent individual items 54 | * @var string 55 | */ 56 | protected $itemClass = 'XML_Feed_Parser_RSS09Element'; 57 | 58 | /** 59 | * The element containing entries 60 | * @var string 61 | */ 62 | protected $itemElement = 'item'; 63 | 64 | /** 65 | * Here we map those elements we're not going to handle individually 66 | * to the constructs they are. The optional second parameter in the array 67 | * tells the parser whether to 'fall back' (not apt. at the feed level) or 68 | * fail if the element is missing. If the parameter is not set, the function 69 | * will simply return false and leave it to the client to decide what to do. 70 | * @var array 71 | */ 72 | protected $map = array( 73 | 'title' => array('Text'), 74 | 'link' => array('Text'), 75 | 'description' => array('Text'), 76 | 'image' => array('Image'), 77 | 'textinput' => array('TextInput')); 78 | 79 | /** 80 | * Here we map some elements to their atom equivalents. This is going to be 81 | * quite tricky to pull off effectively (and some users' methods may vary) 82 | * but is worth trying. The key is the atom version, the value is RSS2. 83 | * @var array 84 | */ 85 | protected $compatMap = array( 86 | 'title' => array('title'), 87 | 'link' => array('link'), 88 | 'subtitle' => array('description')); 89 | 90 | /** 91 | * We will be working with multiple namespaces and it is useful to 92 | * keep them together 93 | * @var array 94 | */ 95 | protected $namespaces = array( 96 | 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); 97 | 98 | /** 99 | * Our constructor does nothing more than its parent. 100 | * 101 | * @todo RelaxNG validation 102 | * @param DOMDocument $xml A DOM object representing the feed 103 | * @param bool (optional) $string Whether or not to validate this feed 104 | */ 105 | function __construct(DOMDocument $model, $strict = false) 106 | { 107 | $this->model = $model; 108 | 109 | $this->xpath = new DOMXPath($model); 110 | foreach ($this->namespaces as $key => $value) { 111 | $this->xpath->registerNamespace($key, $value); 112 | } 113 | $this->numberEntries = $this->count('item'); 114 | } 115 | 116 | /** 117 | * Included for compatibility -- will not work with RSS 0.9 118 | * 119 | * This is not something that will work with RSS0.9 as it does not have 120 | * clear restrictions on the global uniqueness of IDs. 121 | * 122 | * @param string $id any valid ID. 123 | * @return false 124 | */ 125 | function getEntryById($id) 126 | { 127 | return false; 128 | } 129 | 130 | /** 131 | * Get details of the image associated with the feed. 132 | * 133 | * @return array|false an array simply containing the child elements 134 | */ 135 | protected function getImage() 136 | { 137 | $images = $this->model->getElementsByTagName('image'); 138 | if ($images->length > 0) { 139 | $image = $images->item(0); 140 | $details = array(); 141 | if ($image->hasChildNodes()) { 142 | $details = array( 143 | 'title' => $image->getElementsByTagName('title')->item(0)->value, 144 | 'link' => $image->getElementsByTagName('link')->item(0)->value, 145 | 'url' => $image->getElementsByTagName('url')->item(0)->value); 146 | } else { 147 | $details = array('title' => false, 148 | 'link' => false, 149 | 'url' => $image->attributes->getNamedItem('resource')->nodeValue); 150 | } 151 | $details = array_merge($details, 152 | array('description' => false, 'height' => false, 'width' => false)); 153 | if (! empty($details)) { 154 | return $details; 155 | } 156 | } 157 | return false; 158 | } 159 | 160 | /** 161 | * The textinput element is little used, but in the interests of 162 | * completeness we will support it. 163 | * 164 | * @return array|false 165 | */ 166 | protected function getTextInput() 167 | { 168 | $inputs = $this->model->getElementsByTagName('textinput'); 169 | if ($inputs->length > 0) { 170 | $input = $inputs->item(0); 171 | $results = array(); 172 | $results['title'] = isset( 173 | $input->getElementsByTagName('title')->item(0)->value) ? 174 | $input->getElementsByTagName('title')->item(0)->value : null; 175 | $results['description'] = isset( 176 | $input->getElementsByTagName('description')->item(0)->value) ? 177 | $input->getElementsByTagName('description')->item(0)->value : null; 178 | $results['name'] = isset( 179 | $input->getElementsByTagName('name')->item(0)->value) ? 180 | $input->getElementsByTagName('name')->item(0)->value : null; 181 | $results['link'] = isset( 182 | $input->getElementsByTagName('link')->item(0)->value) ? 183 | $input->getElementsByTagName('link')->item(0)->value : null; 184 | if (empty($results['link']) && 185 | $input->attributes->getNamedItem('resource')) { 186 | $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue; 187 | } 188 | if (! empty($results)) { 189 | return $results; 190 | } 191 | } 192 | return false; 193 | } 194 | 195 | /** 196 | * Get details of a link from the feed. 197 | * 198 | * In RSS1 a link is a text element but in order to ensure that we resolve 199 | * URLs properly we have a special function for them. 200 | * 201 | * @return string 202 | */ 203 | function getLink($offset = 0, $attribute = 'href', $params = false) 204 | { 205 | $links = $this->model->getElementsByTagName('link'); 206 | if ($links->length <= $offset) { 207 | return false; 208 | } 209 | $link = $links->item($offset); 210 | return $this->addBase($link->nodeValue, $link); 211 | } 212 | } 213 | 214 | ?> -------------------------------------------------------------------------------- /inc/r2t.php: -------------------------------------------------------------------------------- 1 | init(); 12 | } 13 | 14 | protected function init() { 15 | include_once ("sfYaml/sfYaml.class.php"); 16 | define('R2T_TEMP_DIR', R2T_PROJECT_DIR . "/tmp/"); 17 | if (!file_Exists(R2T_TEMP_DIR)) { 18 | if (!mkdir(R2T_TEMP_DIR)) { 19 | die("Could not create " . R2T_TEMP_DIR); 20 | } 21 | } 22 | $yaml = file_get_contents(R2T_PROJECT_DIR . '/conf/defaults.yml'); 23 | $yaml .= file_get_contents(R2T_PROJECT_DIR . '/conf/feeds.yml'); 24 | $f = sfYAML::Load($yaml); 25 | if ($f['feeds']) { 26 | $this->feeds = $f['feeds']; 27 | } 28 | $this->defaults = $f['defaults']; 29 | } 30 | 31 | public function process() { 32 | 33 | foreach ($this->feeds as $feedname => $options) { 34 | $options = $this->mergeOptionsWithDefaults($options); 35 | $newentries = $this->getNewEntries($feedname, $options['url']); 36 | $cnt = 1; 37 | foreach ($newentries as $guid => $e) { 38 | try { 39 | $options = $this->twit($e, $options); 40 | } catch (Exception $e) { 41 | $entries = sfYAML::Load(R2T_TEMP_DIR . "/$feedname"); 42 | 43 | print "Couldn't post " . $entry['title'] . " " . $entry['link'] . " due to " . $e->getMessage(); 44 | unset ($entries[$guid]); 45 | unset ($newentries[$guid]); 46 | file_put_contents(R2T_TEMP_DIR . "/$feedname", sfYaml::dump($entries)); 47 | chmod(R2T_TEMP_DIR . "/$feedname",0666); 48 | continue; 49 | } 50 | $cnt++; 51 | if ($cnt > $options['maxposts']) { 52 | break; 53 | } 54 | } 55 | } 56 | return $newentries; 57 | } 58 | 59 | protected function mergeOptionsWithDefaults($options) { 60 | foreach ($this->defaults as $name => $value) { 61 | if (!isset($options[$name])) { 62 | $options[$name] = $value; 63 | } 64 | } 65 | return $options; 66 | 67 | } 68 | protected function twit($entry, $options) { 69 | 70 | if (isset($options['shortener']) && $options['shortener'] && strlen($entry['link']) > $options['maxurllength']) { 71 | if (!isset($options['shortenerObject'])) { 72 | $this->debug("create " . $options['shortener'] . " class"); 73 | include_once ("r2t/shortener/" . $options['shortener'] . ".php"); 74 | $classname = "r2t_shortener_" . $options['shortener']; 75 | $options['shortenerObject'] = new $classname(); 76 | } 77 | $this->debug("shorten " . $entry['link'] . " to "); 78 | $res = $options['shortenerObject']->shorten($entry['link'],$entry['title']); 79 | if (is_array($res)) { 80 | $entry['link'] = $res['url']; 81 | $entry['title'] = $res['text']; 82 | } else { 83 | $entry['link'] = $res; 84 | } 85 | 86 | $this->debug(" " . $entry['link']); 87 | } 88 | 89 | $msg = $entry['title'] . " " . $entry['link']; 90 | if (isset($options['prefix'])) { 91 | $msg = $options['prefix'] . " " . $msg; 92 | } 93 | $msg = trim($msg); 94 | $this->debug("twit " . $msg); 95 | /* oauth */ 96 | 97 | if ($options['twitter']['token'] && class_exists("OAuth")) { 98 | $req_url = 'http://twitter.com/oauth/request_token'; 99 | $acc_url = 'http://twitter.com/oauth/access_token'; 100 | $authurl = 'http://twitter.com/oauth/authorize'; 101 | $api_url = 'http://twitter.com/statuses/update.json'; 102 | $conskey = 'DyhAb4DLlFmc5Wn29QvL9g'; 103 | $conssec = 'wgaBiC9YJx38sqBLklUqpkWB1Cq1ztAemp5lkfwQ'; 104 | 105 | $oauth = new OAuth($conskey,$conssec,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI); 106 | $oauth->debug = 1; 107 | $oauth->setToken($options['twitter']['token'], $options['twitter']['secret']); 108 | 109 | $api_args = array("status" => $msg, "empty_param" => NULL); 110 | if (isset($entry['lat'])) { 111 | $api_args['lat'] = $entry['lat']; 112 | $api_args['long'] = $entry['long']; 113 | } 114 | $oauth->fetch($api_url, $api_args, OAUTH_HTTP_METHOD_POST, array("User-Agent" => "pecl/oauth")); 115 | /* end oauth */ 116 | } else { 117 | include_once 'Services/Twitter.php'; 118 | $service = new Services_Twitter($options['twitter']['user'], $options['twitter']['pass']); 119 | $service->statuses->update($msg); 120 | } 121 | $this->debug("prowlApiKey: " . $options['prowlApiKey']); 122 | if (!empty($options['prowlApiKey'])) { 123 | include_once('ProwlPHP/ProwlPHP.php'); 124 | $prowl = new Prowl($options['prowlApiKey']); 125 | $prowl->push(array( 126 | 'application'=>'rss2twi.php', 127 | 'event'=>'New Post', 128 | 'description'=> $msg, 129 | 'priority'=>2, 130 | //'apikey'=>'APIKEY' // Not required if already set during object construction. 131 | //'providerkey'=>"PROVIDERKEY' 132 | ),true); 133 | } 134 | return $options; 135 | 136 | } 137 | 138 | protected function getNewEntries($feedname, $url) { 139 | $oldentries = $this->getOldEntries($feedname); 140 | $onlineentries = $this->getOnlineEntries($feedname,$url); 141 | if (count($onlineentries) > 0) { 142 | //keep some old entries, so that they don't get repostet if the show up later 143 | $z = 0; 144 | $max = count($onlineentries); 145 | 146 | foreach($oldentries as $k => $v) { 147 | if(!isset($onlineentries[$k])) { 148 | $onlineentries[$k] = $v; 149 | $z++; 150 | if ($z > $max) { 151 | break; 152 | } 153 | } 154 | } 155 | 156 | file_put_contents(R2T_TEMP_DIR . "/$feedname", sfYaml::dump($onlineentries)); 157 | chmod(R2T_TEMP_DIR . "/$feedname",0666); 158 | } 159 | $newentries = $onlineentries; 160 | foreach ($onlineentries as $guid => $a) { 161 | if (isset($oldentries[$guid])) { 162 | unset($newentries[$guid]); 163 | } else { 164 | $this->debug(" New Entry: " . $a['link'] . " " . $a['title']); 165 | } 166 | 167 | } 168 | return $newentries; 169 | } 170 | 171 | protected function getOldEntries($feed) { 172 | $file = R2T_TEMP_DIR . "/$feed"; 173 | $oldentries = array(); 174 | if (file_exists($file)) { 175 | $oldentries = sfYAML::Load($file); 176 | } 177 | return $oldentries; 178 | 179 | } 180 | 181 | protected function getOnlineEntries($feedname,$url) { 182 | $feed = $this->readFeed($feedname,$url); 183 | $this->debug("Loop through entries"); 184 | $entries = array(); 185 | foreach ($feed as $entry) { 186 | //$this->debug(" " . $entry->link . ": " . $entry->guid . " " . $entry->title); 187 | if (isset($entry->guid)) { 188 | $entry->guid = $entry->link; 189 | } 190 | $e = array( 191 | "link" => $entry->link, 192 | "title" => $entry->title 193 | ); 194 | if(!$entry->guid) { 195 | $entry->guid = $entry->link; 196 | } 197 | 198 | if ($entry->lat) { 199 | $e['lat'] = $entry->lat; 200 | $e['long'] = $entry->long; 201 | } 202 | $entries[$entry->guid] = $e; 203 | } 204 | return $entries; 205 | } 206 | 207 | protected function readFeed($feedname,$url) { 208 | require_once ("XML/Feed/Parser.php"); 209 | 210 | $this->debug("readFeed for $url"); 211 | $body = $this->httpRequest($feedname,$url); 212 | if ($body) { 213 | $this->debug("parse Feed"); 214 | return new XML_Feed_Parser($body); 215 | 216 | } else { 217 | $this->debug("Feed for $url was empty"); 218 | return array(); 219 | } 220 | 221 | } 222 | 223 | protected function httpRequest($feedname,$url) { 224 | require_once ("HTTP/Request.php"); 225 | $this->debug("httpRequest for $url"); 226 | 227 | $req = new HTTP_Request($url); 228 | if (!PEAR::isError($req->sendRequest())) { 229 | return $req->getResponseBody(); 230 | } 231 | return null; 232 | 233 | } 234 | protected function debug($msg) { 235 | 236 | if ($this->debug) { 237 | if (is_string($msg)) { 238 | print $msg . "\n"; 239 | } else { 240 | var_dump($msg); 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/AtomElement.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: AtomElement.php,v 1.19 2007/03/26 12:43:11 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This class provides support for atom entries. It will usually be called by 26 | * XML_Feed_Parser_Atom with which it shares many methods. 27 | * 28 | * @author James Stewart 29 | * @version Release: 1.0.3 30 | * @package XML_Feed_Parser 31 | */ 32 | class XML_Feed_Parser_AtomElement extends XML_Feed_Parser_Atom 33 | { 34 | /** 35 | * This will be a reference to the parent object for when we want 36 | * to use a 'fallback' rule 37 | * @var XML_Feed_Parser_Atom 38 | */ 39 | protected $parent; 40 | 41 | /** 42 | * When performing XPath queries we will use this prefix 43 | * @var string 44 | */ 45 | private $xpathPrefix = ''; 46 | 47 | /** 48 | * xml:base values inherited by the element 49 | * @var string 50 | */ 51 | protected $xmlBase; 52 | 53 | /** 54 | * Here we provide a few mappings for those very special circumstances in 55 | * which it makes sense to map back to the RSS2 spec or to manage other 56 | * compatibilities (eg. with the Univeral Feed Parser). Key is the other version's 57 | * name for the command, value is an array consisting of the equivalent in our atom 58 | * api and any attributes needed to make the mapping. 59 | * @var array 60 | */ 61 | protected $compatMap = array( 62 | 'guid' => array('id'), 63 | 'links' => array('link'), 64 | 'tags' => array('category'), 65 | 'contributors' => array('contributor')); 66 | 67 | /** 68 | * Our specific element map 69 | * @var array 70 | */ 71 | protected $map = array( 72 | 'author' => array('Person', 'fallback'), 73 | 'contributor' => array('Person'), 74 | 'id' => array('Text', 'fail'), 75 | 'published' => array('Date'), 76 | 'updated' => array('Date', 'fail'), 77 | 'title' => array('Text', 'fail'), 78 | 'rights' => array('Text', 'fallback'), 79 | 'summary' => array('Text'), 80 | 'content' => array('Content'), 81 | 'link' => array('Link'), 82 | 'enclosure' => array('Enclosure'), 83 | 'category' => array('Category')); 84 | 85 | /** 86 | * Store useful information for later. 87 | * 88 | * @param DOMElement $element - this item as a DOM element 89 | * @param XML_Feed_Parser_Atom $parent - the feed of which this is a member 90 | */ 91 | function __construct(DOMElement $element, $parent, $xmlBase = '') 92 | { 93 | $this->model = $element; 94 | $this->parent = $parent; 95 | $this->xmlBase = $xmlBase; 96 | $this->xpathPrefix = "//atom:entry[atom:id='" . $this->id . "']/"; 97 | $this->xpath = $this->parent->xpath; 98 | } 99 | 100 | /** 101 | * Provides access to specific aspects of the author data for an atom entry 102 | * 103 | * Author data at the entry level is more complex than at the feed level. 104 | * If atom:author is not present for the entry we need to look for it in 105 | * an atom:source child of the atom:entry. If it's not there either, then 106 | * we look to the parent for data. 107 | * 108 | * @param array 109 | * @return string 110 | */ 111 | function getAuthor($arguments) 112 | { 113 | /* Find out which part of the author data we're looking for */ 114 | if (isset($arguments['param'])) { 115 | $parameter = $arguments['param']; 116 | } else { 117 | $parameter = 'name'; 118 | } 119 | 120 | $test = $this->model->getElementsByTagName('author'); 121 | if ($test->length > 0) { 122 | $item = $test->item(0); 123 | return $item->getElementsByTagName($parameter)->item(0)->nodeValue; 124 | } 125 | 126 | $source = $this->model->getElementsByTagName('source'); 127 | if ($source->length > 0) { 128 | $test = $this->model->getElementsByTagName('author'); 129 | if ($test->length > 0) { 130 | $item = $test->item(0); 131 | return $item->getElementsByTagName($parameter)->item(0)->nodeValue; 132 | } 133 | } 134 | return $this->parent->getAuthor($arguments); 135 | } 136 | 137 | /** 138 | * Returns the content of the content element or info on a specific attribute 139 | * 140 | * This element may or may not be present. It cannot be present more than 141 | * once. It may have a 'src' attribute, in which case there's no content 142 | * If not present, then the entry must have link with rel="alternate". 143 | * If there is content we return it, if not and there's a 'src' attribute 144 | * we return the value of that instead. The method can take an 'attribute' 145 | * argument, in which case we return the value of that attribute if present. 146 | * eg. $item->content("type") will return the type of the content. It is 147 | * recommended that all users check the type before getting the content to 148 | * ensure that their script is capable of handling the type of returned data. 149 | * (data carried in the content element can be either 'text', 'html', 'xhtml', 150 | * or any standard MIME type). 151 | * 152 | * @return string|false 153 | */ 154 | protected function getContent($method, $arguments = array()) 155 | { 156 | $attribute = empty($arguments[0]) ? false : $arguments[0]; 157 | $tags = $this->model->getElementsByTagName('content'); 158 | 159 | if ($tags->length == 0) { 160 | return false; 161 | } 162 | 163 | $content = $tags->item(0); 164 | 165 | if (! $content->hasAttribute('type')) { 166 | $content->setAttribute('type', 'text'); 167 | } 168 | if (! empty($attribute)) { 169 | return $content->getAttribute($attribute); 170 | } 171 | 172 | $type = $content->getAttribute('type'); 173 | 174 | if (! empty($attribute)) { 175 | if ($content->hasAttribute($attribute)) 176 | { 177 | return $content->getAttribute($attribute); 178 | } 179 | return false; 180 | } 181 | 182 | if ($content->hasAttribute('src')) { 183 | return $content->getAttribute('src'); 184 | } 185 | 186 | return $this->parseTextConstruct($content); 187 | } 188 | 189 | /** 190 | * For compatibility, this method provides a mapping to access enclosures. 191 | * 192 | * The Atom spec doesn't provide for an enclosure element, but it is 193 | * generally supported using the link element with rel='enclosure'. 194 | * 195 | * @param string $method - for compatibility with our __call usage 196 | * @param array $arguments - for compatibility with our __call usage 197 | * @return array|false 198 | */ 199 | function getEnclosure($method, $arguments = array()) 200 | { 201 | $offset = isset($arguments[0]) ? $arguments[0] : 0; 202 | $query = "//atom:entry[atom:id='" . $this->getText('id', false) . 203 | "']/atom:link[@rel='enclosure']"; 204 | 205 | $encs = $this->parent->xpath->query($query); 206 | if ($encs->length > $offset) { 207 | try { 208 | if (! $encs->item($offset)->hasAttribute('href')) { 209 | return false; 210 | } 211 | $attrs = $encs->item($offset)->attributes; 212 | $length = $encs->item($offset)->hasAttribute('length') ? 213 | $encs->item($offset)->getAttribute('length') : false; 214 | return array( 215 | 'url' => $attrs->getNamedItem('href')->value, 216 | 'type' => $attrs->getNamedItem('type')->value, 217 | 'length' => $length); 218 | } catch (Exception $e) { 219 | return false; 220 | } 221 | } 222 | return false; 223 | } 224 | 225 | /** 226 | * Get details of this entry's source, if available/relevant 227 | * 228 | * Where an atom:entry is taken from another feed then the aggregator 229 | * is supposed to include an atom:source element which replicates at least 230 | * the atom:id, atom:title, and atom:updated metadata from the original 231 | * feed. Atom:source therefore has a very similar structure to atom:feed 232 | * and if we find it we will return it as an XML_Feed_Parser_Atom object. 233 | * 234 | * @return XML_Feed_Parser_Atom|false 235 | */ 236 | function getSource() 237 | { 238 | $test = $this->model->getElementsByTagName('source'); 239 | if ($test->length == 0) { 240 | return false; 241 | } 242 | $source = new XML_Feed_Parser_Atom($test->item(0)); 243 | } 244 | 245 | /** 246 | * Get the entry as an XML string 247 | * 248 | * Return an XML serialization of the feed, should it be required. Most 249 | * users however, will already have a serialization that they used when 250 | * instantiating the object. 251 | * 252 | * @return string XML serialization of element 253 | */ 254 | function __toString() 255 | { 256 | $simple = simplexml_import_dom($this->model); 257 | return $simple->asXML(); 258 | } 259 | } 260 | 261 | ?> -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS11.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS11.php,v 1.6 2006/07/27 13:52:05 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This class handles RSS1.1 feeds. RSS1.1 is documented at: 26 | * http://inamidst.com/rss1.1/ 27 | * 28 | * @author James Stewart 29 | * @version Release: 1.0.3 30 | * @package XML_Feed_Parser 31 | * @todo Support for RDF:List 32 | * @todo Ensure xml:lang is accessible to users 33 | */ 34 | class XML_Feed_Parser_RSS11 extends XML_Feed_Parser_Type 35 | { 36 | /** 37 | * The URI of the RelaxNG schema used to (optionally) validate the feed 38 | * @var string 39 | */ 40 | private $relax = 'rss11.rnc'; 41 | 42 | /** 43 | * We're likely to use XPath, so let's keep it global 44 | * @var DOMXPath 45 | */ 46 | protected $xpath; 47 | 48 | /** 49 | * The feed type we are parsing 50 | * @var string 51 | */ 52 | public $version = 'RSS 1.0'; 53 | 54 | /** 55 | * The class used to represent individual items 56 | * @var string 57 | */ 58 | protected $itemClass = 'XML_Feed_Parser_RSS1Element'; 59 | 60 | /** 61 | * The element containing entries 62 | * @var string 63 | */ 64 | protected $itemElement = 'item'; 65 | 66 | /** 67 | * Here we map those elements we're not going to handle individually 68 | * to the constructs they are. The optional second parameter in the array 69 | * tells the parser whether to 'fall back' (not apt. at the feed level) or 70 | * fail if the element is missing. If the parameter is not set, the function 71 | * will simply return false and leave it to the client to decide what to do. 72 | * @var array 73 | */ 74 | protected $map = array( 75 | 'title' => array('Text'), 76 | 'link' => array('Text'), 77 | 'description' => array('Text'), 78 | 'image' => array('Image'), 79 | 'updatePeriod' => array('Text'), 80 | 'updateFrequency' => array('Text'), 81 | 'updateBase' => array('Date'), 82 | 'rights' => array('Text'), # dc:rights 83 | 'description' => array('Text'), # dc:description 84 | 'creator' => array('Text'), # dc:creator 85 | 'publisher' => array('Text'), # dc:publisher 86 | 'contributor' => array('Text'), # dc:contributor 87 | 'date' => array('Date') # dc:contributor 88 | ); 89 | 90 | /** 91 | * Here we map some elements to their atom equivalents. This is going to be 92 | * quite tricky to pull off effectively (and some users' methods may vary) 93 | * but is worth trying. The key is the atom version, the value is RSS2. 94 | * @var array 95 | */ 96 | protected $compatMap = array( 97 | 'title' => array('title'), 98 | 'link' => array('link'), 99 | 'subtitle' => array('description'), 100 | 'author' => array('creator'), 101 | 'updated' => array('date')); 102 | 103 | /** 104 | * We will be working with multiple namespaces and it is useful to 105 | * keep them together. We will retain support for some common RSS1.0 modules 106 | * @var array 107 | */ 108 | protected $namespaces = array( 109 | 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 110 | 'rss' => 'http://purl.org/net/rss1.1#', 111 | 'dc' => 'http://purl.org/rss/1.0/modules/dc/', 112 | 'content' => 'http://purl.org/rss/1.0/modules/content/', 113 | 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/'); 114 | 115 | /** 116 | * Our constructor does nothing more than its parent. 117 | * 118 | * @param DOMDocument $xml A DOM object representing the feed 119 | * @param bool (optional) $string Whether or not to validate this feed 120 | */ 121 | function __construct(DOMDocument $model, $strict = false) 122 | { 123 | $this->model = $model; 124 | 125 | if ($strict) { 126 | $validate = $this->model->relaxNGValidate(self::getSchemaDir . 127 | DIRECTORY_SEPARATOR . $this->relax); 128 | if (! $validate) { 129 | throw new XML_Feed_Parser_Exception('Failed required validation'); 130 | } 131 | } 132 | 133 | $this->xpath = new DOMXPath($model); 134 | foreach ($this->namespaces as $key => $value) { 135 | $this->xpath->registerNamespace($key, $value); 136 | } 137 | $this->numberEntries = $this->count('item'); 138 | } 139 | 140 | /** 141 | * Attempts to identify an element by ID given by the rdf:about attribute 142 | * 143 | * This is not really something that will work with RSS1.1 as it does not have 144 | * clear restrictions on the global uniqueness of IDs. We will employ the 145 | * _very_ hit and miss method of selecting entries based on the rdf:about 146 | * attribute. Please note that this is even more hit and miss with RSS1.1 than 147 | * with RSS1.0 since RSS1.1 does not require the rdf:about attribute for items. 148 | * 149 | * @param string $id any valid ID. 150 | * @return XML_Feed_Parser_RSS1Element 151 | */ 152 | function getEntryById($id) 153 | { 154 | if (isset($this->idMappings[$id])) { 155 | return $this->entries[$this->idMappings[$id]]; 156 | } 157 | 158 | $entries = $this->xpath->query("//rss:item[@rdf:about='$id']"); 159 | if ($entries->length > 0) { 160 | $classname = $this->itemClass; 161 | $entry = new $classname($entries->item(0), $this); 162 | return $entry; 163 | } 164 | return false; 165 | } 166 | 167 | /** 168 | * Get details of the image associated with the feed. 169 | * 170 | * @return array|false an array simply containing the child elements 171 | */ 172 | protected function getImage() 173 | { 174 | $images = $this->model->getElementsByTagName('image'); 175 | if ($images->length > 0) { 176 | $image = $images->item(0); 177 | $details = array(); 178 | if ($image->hasChildNodes()) { 179 | $details = array( 180 | 'title' => $image->getElementsByTagName('title')->item(0)->value, 181 | 'url' => $image->getElementsByTagName('url')->item(0)->value); 182 | if ($image->getElementsByTagName('link')->length > 0) { 183 | $details['link'] = 184 | $image->getElementsByTagName('link')->item(0)->value; 185 | } 186 | } else { 187 | $details = array('title' => false, 188 | 'link' => false, 189 | 'url' => $image->attributes->getNamedItem('resource')->nodeValue); 190 | } 191 | $details = array_merge($details, 192 | array('description' => false, 'height' => false, 'width' => false)); 193 | if (! empty($details)) { 194 | return $details; 195 | } 196 | } 197 | return false; 198 | } 199 | 200 | /** 201 | * The textinput element is little used, but in the interests of 202 | * completeness we will support it. 203 | * 204 | * @return array|false 205 | */ 206 | protected function getTextInput() 207 | { 208 | $inputs = $this->model->getElementsByTagName('textinput'); 209 | if ($inputs->length > 0) { 210 | $input = $inputs->item(0); 211 | $results = array(); 212 | $results['title'] = isset( 213 | $input->getElementsByTagName('title')->item(0)->value) ? 214 | $input->getElementsByTagName('title')->item(0)->value : null; 215 | $results['description'] = isset( 216 | $input->getElementsByTagName('description')->item(0)->value) ? 217 | $input->getElementsByTagName('description')->item(0)->value : null; 218 | $results['name'] = isset( 219 | $input->getElementsByTagName('name')->item(0)->value) ? 220 | $input->getElementsByTagName('name')->item(0)->value : null; 221 | $results['link'] = isset( 222 | $input->getElementsByTagName('link')->item(0)->value) ? 223 | $input->getElementsByTagName('link')->item(0)->value : null; 224 | if (empty($results['link']) and 225 | $input->attributes->getNamedItem('resource')) { 226 | $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue; 227 | } 228 | if (! empty($results)) { 229 | return $results; 230 | } 231 | } 232 | return false; 233 | } 234 | 235 | /** 236 | * Attempts to discern authorship 237 | * 238 | * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher 239 | * elements for defining authorship in RSS1. We will try each of those in 240 | * turn in order to simulate the atom author element and will return it 241 | * as text. 242 | * 243 | * @return array|false 244 | */ 245 | function getAuthor() 246 | { 247 | $options = array('creator', 'contributor', 'publisher'); 248 | foreach ($options as $element) { 249 | $test = $this->model->getElementsByTagName($element); 250 | if ($test->length > 0) { 251 | return $test->item(0)->value; 252 | } 253 | } 254 | return false; 255 | } 256 | 257 | /** 258 | * Retrieve a link 259 | * 260 | * In RSS1 a link is a text element but in order to ensure that we resolve 261 | * URLs properly we have a special function for them. 262 | * 263 | * @return string 264 | */ 265 | function getLink($offset = 0, $attribute = 'href', $params = false) 266 | { 267 | $links = $this->model->getElementsByTagName('link'); 268 | if ($links->length <= $offset) { 269 | return false; 270 | } 271 | $link = $links->item($offset); 272 | return $this->addBase($link->nodeValue, $link); 273 | } 274 | } 275 | 276 | ?> -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS1.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS1.php,v 1.10 2006/07/27 13:52:05 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This class handles RSS1.0 feeds. 26 | * 27 | * @author James Stewart 28 | * @version Release: 1.0.3 29 | * @package XML_Feed_Parser 30 | * @todo Find a Relax NG URI we can use 31 | */ 32 | class XML_Feed_Parser_RSS1 extends XML_Feed_Parser_Type 33 | { 34 | /** 35 | * The URI of the RelaxNG schema used to (optionally) validate the feed 36 | * @var string 37 | */ 38 | private $relax = 'rss10.rnc'; 39 | 40 | /** 41 | * We're likely to use XPath, so let's keep it global 42 | * @var DOMXPath 43 | */ 44 | protected $xpath; 45 | 46 | /** 47 | * The feed type we are parsing 48 | * @var string 49 | */ 50 | public $version = 'RSS 1.0'; 51 | 52 | /** 53 | * The class used to represent individual items 54 | * @var string 55 | */ 56 | protected $itemClass = 'XML_Feed_Parser_RSS1Element'; 57 | 58 | /** 59 | * The element containing entries 60 | * @var string 61 | */ 62 | protected $itemElement = 'item'; 63 | 64 | /** 65 | * Here we map those elements we're not going to handle individually 66 | * to the constructs they are. The optional second parameter in the array 67 | * tells the parser whether to 'fall back' (not apt. at the feed level) or 68 | * fail if the element is missing. If the parameter is not set, the function 69 | * will simply return false and leave it to the client to decide what to do. 70 | * @var array 71 | */ 72 | protected $map = array( 73 | 'title' => array('Text'), 74 | 'link' => array('Text'), 75 | 'description' => array('Text'), 76 | 'image' => array('Image'), 77 | 'textinput' => array('TextInput'), 78 | 'updatePeriod' => array('Text'), 79 | 'updateFrequency' => array('Text'), 80 | 'updateBase' => array('Date'), 81 | 'rights' => array('Text'), # dc:rights 82 | 'description' => array('Text'), # dc:description 83 | 'creator' => array('Text'), # dc:creator 84 | 'publisher' => array('Text'), # dc:publisher 85 | 'contributor' => array('Text'), # dc:contributor 86 | 'date' => array('Date') # dc:contributor 87 | ); 88 | 89 | /** 90 | * Here we map some elements to their atom equivalents. This is going to be 91 | * quite tricky to pull off effectively (and some users' methods may vary) 92 | * but is worth trying. The key is the atom version, the value is RSS2. 93 | * @var array 94 | */ 95 | protected $compatMap = array( 96 | 'title' => array('title'), 97 | 'link' => array('link'), 98 | 'subtitle' => array('description'), 99 | 'author' => array('creator'), 100 | 'updated' => array('date')); 101 | 102 | /** 103 | * We will be working with multiple namespaces and it is useful to 104 | * keep them together 105 | * @var array 106 | */ 107 | protected $namespaces = array( 108 | 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 109 | 'rss' => 'http://purl.org/rss/1.0/', 110 | 'dc' => 'http://purl.org/rss/1.0/modules/dc/', 111 | 'content' => 'http://purl.org/rss/1.0/modules/content/', 112 | 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/'); 113 | 114 | /** 115 | * Our constructor does nothing more than its parent. 116 | * 117 | * @param DOMDocument $xml A DOM object representing the feed 118 | * @param bool (optional) $string Whether or not to validate this feed 119 | */ 120 | function __construct(DOMDocument $model, $strict = false) 121 | { 122 | $this->model = $model; 123 | if ($strict) { 124 | $validate = $this->model->relaxNGValidate(self::getSchemaDir . 125 | DIRECTORY_SEPARATOR . $this->relax); 126 | if (! $validate) { 127 | throw new XML_Feed_Parser_Exception('Failed required validation'); 128 | } 129 | } 130 | 131 | $this->xpath = new DOMXPath($model); 132 | foreach ($this->namespaces as $key => $value) { 133 | $this->xpath->registerNamespace($key, $value); 134 | } 135 | $this->numberEntries = $this->count('item'); 136 | } 137 | 138 | /** 139 | * Allows retrieval of an entry by ID where the rdf:about attribute is used 140 | * 141 | * This is not really something that will work with RSS1 as it does not have 142 | * clear restrictions on the global uniqueness of IDs. We will employ the 143 | * _very_ hit and miss method of selecting entries based on the rdf:about 144 | * attribute. If DOMXPath::evaluate is available, we also use that to store 145 | * a reference to the entry in the array used by getEntryByOffset so that 146 | * method does not have to seek out the entry if it's requested that way. 147 | * 148 | * @param string $id any valid ID. 149 | * @return XML_Feed_Parser_RSS1Element 150 | */ 151 | function getEntryById($id) 152 | { 153 | if (isset($this->idMappings[$id])) { 154 | return $this->entries[$this->idMappings[$id]]; 155 | } 156 | 157 | $entries = $this->xpath->query("//rss:item[@rdf:about='$id']"); 158 | if ($entries->length > 0) { 159 | $classname = $this->itemClass; 160 | $entry = new $classname($entries->item(0), $this); 161 | if (in_array('evaluate', get_class_methods($this->xpath))) { 162 | $offset = $this->xpath->evaluate("count(preceding-sibling::rss:item)", $entries->item(0)); 163 | $this->entries[$offset] = $entry; 164 | } 165 | $this->idMappings[$id] = $entry; 166 | return $entry; 167 | } 168 | return false; 169 | } 170 | 171 | /** 172 | * Get details of the image associated with the feed. 173 | * 174 | * @return array|false an array simply containing the child elements 175 | */ 176 | protected function getImage() 177 | { 178 | $images = $this->model->getElementsByTagName('image'); 179 | if ($images->length > 0) { 180 | $image = $images->item(0); 181 | $details = array(); 182 | if ($image->hasChildNodes()) { 183 | $details = array( 184 | 'title' => $image->getElementsByTagName('title')->item(0)->value, 185 | 'link' => $image->getElementsByTagName('link')->item(0)->value, 186 | 'url' => $image->getElementsByTagName('url')->item(0)->value); 187 | } else { 188 | $details = array('title' => false, 189 | 'link' => false, 190 | 'url' => $image->attributes->getNamedItem('resource')->nodeValue); 191 | } 192 | $details = array_merge($details, array('description' => false, 'height' => false, 'width' => false)); 193 | if (! empty($details)) { 194 | return $details; 195 | } 196 | } 197 | return false; 198 | } 199 | 200 | /** 201 | * The textinput element is little used, but in the interests of 202 | * completeness we will support it. 203 | * 204 | * @return array|false 205 | */ 206 | protected function getTextInput() 207 | { 208 | $inputs = $this->model->getElementsByTagName('textinput'); 209 | if ($inputs->length > 0) { 210 | $input = $inputs->item(0); 211 | $results = array(); 212 | $results['title'] = isset( 213 | $input->getElementsByTagName('title')->item(0)->value) ? 214 | $input->getElementsByTagName('title')->item(0)->value : null; 215 | $results['description'] = isset( 216 | $input->getElementsByTagName('description')->item(0)->value) ? 217 | $input->getElementsByTagName('description')->item(0)->value : null; 218 | $results['name'] = isset( 219 | $input->getElementsByTagName('name')->item(0)->value) ? 220 | $input->getElementsByTagName('name')->item(0)->value : null; 221 | $results['link'] = isset( 222 | $input->getElementsByTagName('link')->item(0)->value) ? 223 | $input->getElementsByTagName('link')->item(0)->value : null; 224 | if (empty($results['link']) and 225 | $input->attributes->getNamedItem('resource')) { 226 | $results['link'] = 227 | $input->attributes->getNamedItem('resource')->nodeValue; 228 | } 229 | if (! empty($results)) { 230 | return $results; 231 | } 232 | } 233 | return false; 234 | } 235 | 236 | /** 237 | * Employs various techniques to identify the author 238 | * 239 | * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher 240 | * elements for defining authorship in RSS1. We will try each of those in 241 | * turn in order to simulate the atom author element and will return it 242 | * as text. 243 | * 244 | * @return array|false 245 | */ 246 | function getAuthor() 247 | { 248 | $options = array('creator', 'contributor', 'publisher'); 249 | foreach ($options as $element) { 250 | $test = $this->model->getElementsByTagName($element); 251 | if ($test->length > 0) { 252 | return $test->item(0)->value; 253 | } 254 | } 255 | return false; 256 | } 257 | 258 | /** 259 | * Retrieve a link 260 | * 261 | * In RSS1 a link is a text element but in order to ensure that we resolve 262 | * URLs properly we have a special function for them. 263 | * 264 | * @return string 265 | */ 266 | function getLink($offset = 0, $attribute = 'href', $params = false) 267 | { 268 | $links = $this->model->getElementsByTagName('link'); 269 | if ($links->length <= $offset) { 270 | return false; 271 | } 272 | $link = $links->item($offset); 273 | return $this->addBase($link->nodeValue, $link); 274 | } 275 | } 276 | 277 | ?> -------------------------------------------------------------------------------- /inc/Services/Twitter/Common.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 1997-2007 Joe Stump 40 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 41 | * @version Release: 0.2.0 42 | * @link http://twitter.com/help/api 43 | * @link http://twitter.com 44 | */ 45 | 46 | require_once 'Services/Twitter/Exception.php'; 47 | require_once 'Validate.php'; 48 | 49 | /** 50 | * Services_Twitter_Common 51 | * 52 | * @category Services 53 | * @package Services_Twitter 54 | * @author Joe Stump 55 | * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 56 | * @link http://twitter.com 57 | */ 58 | abstract class Services_Twitter_Common 59 | { 60 | /** 61 | * Name of call group 62 | * 63 | * @var string $name Used to overload class name for groupings 64 | * @see Services_Twitter_Common::sendRequest() 65 | */ 66 | protected $name = null; 67 | 68 | /** 69 | * Username of Twitter user 70 | * 71 | * @var string $user Twitter username 72 | */ 73 | protected $user = ''; 74 | 75 | /** 76 | * Password of Twitter user 77 | * 78 | * @var string $pass User's password for Twitter 79 | */ 80 | protected $pass = ''; 81 | 82 | /** 83 | * Options for HTTP requests and misc. 84 | * 85 | * - timetout 86 | * 87 | * @access protected 88 | * @var array $options An array of various options 89 | */ 90 | protected $options = array( 91 | 'timeout' => 30, 92 | 'userAgent' => 'Services_Twitter 0.2.0' 93 | ); 94 | 95 | /** 96 | * Constructor 97 | * 98 | * @param string $user Twitter username 99 | * @param string $pass Twitter password 100 | * 101 | * @return void 102 | */ 103 | public function __construct($user, $pass) 104 | { 105 | $this->user = $user; 106 | $this->pass = $pass; 107 | } 108 | 109 | /** 110 | * Set an option in {@link Services_Twitter_Common::$options} 111 | * 112 | * If a function exists named _set$option (e.g. _setUserAgent()) then that 113 | * method will be used instead. Otherwise, the value is set directly into 114 | * the options array. 115 | * 116 | * @param string $option Name of option 117 | * @param mixed $value Value of option 118 | * 119 | * @throws InvalidArgumentException on invalid option names 120 | * @see Services_Twitter_Common::$options 121 | * @return void 122 | */ 123 | public function setOption($option, $value) 124 | { 125 | if (!is_string($option)) { 126 | throw new InvalidArgumentException('Option names must be strings'); 127 | } 128 | 129 | $func = '_set' . ucfirst($option); 130 | if (method_exists($this, $func)) { 131 | $this->$func($value); 132 | } else { 133 | $this->options[$option] = $value; 134 | } 135 | } 136 | 137 | /** 138 | * Set a number of options at once 139 | * 140 | * @param array $options The options to set 141 | * 142 | * @return void 143 | * @see Services_Twitter_Common::setOption() 144 | */ 145 | public function setOptions(array $options) 146 | { 147 | foreach ($options as $option => $value) { 148 | $this->setOption($option, $value); 149 | } 150 | } 151 | 152 | /** 153 | * Send a request to the Twitter API 154 | * 155 | * @param string $endPoint The API endpoint WITHOUT the extension 156 | * @param array $params The API endpoint arguments to pass 157 | * @param string $method Whether to use GET or POST 158 | * 159 | * @throws Services_Twitter_Exception 160 | * @return object Instance of SimpleXMLElement 161 | */ 162 | protected function sendRequest($endPoint, 163 | array $params = array(), 164 | $method = 'GET', 165 | $output = Services_Twitter::OUTPUT_XML) 166 | { 167 | // If the $endPoint is a valid URI then we use that instead of using 168 | // the base URI. 169 | if (Validate::uri($endPoint, 170 | array('allowed_schemes' => array('http')))) { 171 | $uri = $endPoint; 172 | } else { 173 | $uri = Services_Twitter::$uri . $endPoint . '.xml'; 174 | } 175 | 176 | if ($method != 'GET' && $method != 'POST') { 177 | throw new Services_Twitter_Exception( 178 | 'Unsupported method: ' . $method 179 | ); 180 | } 181 | 182 | $ch = curl_init(); 183 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 184 | curl_setopt($ch, CURLOPT_USERAGENT, $this->options['userAgent']); 185 | curl_setopt($ch, CURLOPT_HEADER, false); 186 | 187 | // If user and pass are not set then we are using endpoints that do 188 | // not require authentication. 189 | if (isset($this->user) && isset($this->pass)) { 190 | curl_setopt($ch, CURLOPT_USERPWD, $this->user . ':' . $this->pass); 191 | } 192 | 193 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options['timeout']); 194 | 195 | // You can set a source in $params for most requests or via the 196 | // setOption() method. 197 | if (!isset($params['source'])) { 198 | if (isset($this->options['source'])) { 199 | $params['source'] = $this->options['source']; 200 | } 201 | } 202 | 203 | $sets = array(); 204 | foreach ($params as $key => $val) { 205 | $sets[] = $key . '=' . urlencode($val); 206 | } 207 | 208 | if ($method == 'POST') { 209 | curl_setopt($ch, CURLOPT_POST, 1); 210 | curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $sets)); 211 | } else { 212 | if (count($sets)) { 213 | $uri .= '?' . implode('&', $sets); 214 | } 215 | } 216 | 217 | curl_setopt($ch, CURLOPT_URL, $uri); 218 | $res = trim(curl_exec($ch)); 219 | 220 | $err = curl_errno($ch); 221 | if ($err !== CURLE_OK) { 222 | throw new Services_Twitter_Exception(curl_error($ch), $err, $uri); 223 | } 224 | 225 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 226 | if (substr($code, 0, 1) != '2') { 227 | $xml = @simplexml_load_string($res); 228 | if ($xml instanceof SimpleXMLElement && isset($xml->error)) { 229 | throw new Services_Twitter_Exception( 230 | (string)$xml->error, Services_Twitter::ERROR_UNKNOWN, $uri 231 | ); 232 | } 233 | 234 | throw new Services_Twitter_Exception( 235 | 'Unexpected HTTP status returned from API', $code, $uri 236 | ); 237 | } 238 | 239 | curl_close($ch); 240 | 241 | if (!strlen($res)) { 242 | throw new Services_Twitter_Exception( 243 | 'Empty response was received from the API', 244 | Services_Twitter::ERROR_UNKNOWN, $uri 245 | ); 246 | } 247 | 248 | switch ($output) { 249 | case Services_Twitter::OUTPUT_XML: 250 | $response = @simplexml_load_string($res); 251 | if (!$response instanceof SimpleXMLElement) { 252 | throw new Services_Twitter_Exception( 253 | 'Could not parse XML response received by the API', 254 | Services_Twitter::ERROR_UNKNOWN, $uri, $res 255 | ); 256 | } 257 | break; 258 | case Services_Twitter::OUTPUT_JSON: 259 | $response = @json_decode($res); 260 | if (!$response instanceof stdClass) { 261 | throw new Services_Twitter_Exception( 262 | 'Could not parse JSON response received by the API', 263 | Services_Twitter::ERROR_UNKNOWN, $uri, $res 264 | ); 265 | } 266 | break; 267 | } 268 | 269 | return $response; 270 | } 271 | 272 | /** 273 | * Overloaded call for API passthrough 274 | * 275 | * Takes the function called and magically creates an API endpoint based 276 | * on the class name / grouping name and the function name. For instance, 277 | * a call to $twitter->statuses->followers() would call the API endpoint 278 | * '/statuses/followers.xml' and return the results. 279 | * 280 | * @param string $function API endpoint being called 281 | * @param array $args $args[0] is an array of GET/POST arguments 282 | * 283 | * @return object Instance of SimpleXMLElement 284 | * @see Services_Twitter_Common::sendRequest() 285 | * @see Services_Twitter_Common::$name 286 | */ 287 | public function __call($function, array $args = array()) 288 | { 289 | if (isset($args[0]) && is_array($args[0]) && count($args[0])) { 290 | $params = $args[0]; 291 | } else { 292 | $params = array(); 293 | } 294 | 295 | if (!is_null($this->name)) { 296 | $endPoint = '/' . $this->name . '/' . $function; 297 | } elseif (get_class($this) != 'Services_Twitter') { 298 | $name = strtolower(array_pop(explode('_', get_class($this)))); 299 | $endPoint = '/' . $name . '/' . $function; 300 | } else { 301 | $endPoint = '/' . $function; 302 | } 303 | 304 | return $this->sendRequest($endPoint, $params); 305 | } 306 | } 307 | 308 | ?> 309 | -------------------------------------------------------------------------------- /inc/sfYaml/sfYamlInline.class.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | /** 12 | * sfYamlInline implements a YAML parser/dumper for the YAML inline syntax. 13 | * 14 | * @package symfony 15 | * @subpackage util 16 | * @author Fabien Potencier 17 | * @version SVN: $Id: sfYamlInline.class.php 9188 2008-05-22 17:11:24Z dwhittle $ 18 | */ 19 | class sfYamlInline 20 | { 21 | /** 22 | * Load YAML into a PHP array. 23 | * 24 | * @param string YAML 25 | * 26 | * @return array PHP array 27 | */ 28 | static public function load($value) 29 | { 30 | $value = trim($value); 31 | 32 | if (0 == strlen($value)) 33 | { 34 | return ''; 35 | } 36 | 37 | switch ($value[0]) 38 | { 39 | case '[': 40 | return self::parseSequence($value); 41 | case '{': 42 | return self::parseMapping($value); 43 | default: 44 | return self::parseScalar($value); 45 | } 46 | } 47 | 48 | /** 49 | * Dumps PHP array to YAML. 50 | * 51 | * @param mixed PHP 52 | * 53 | * @return string YAML 54 | */ 55 | static public function dump($value) 56 | { 57 | switch (true) 58 | { 59 | case is_resource($value): 60 | throw new InvalidArgumentException('Unable to dump PHP resources in a YAML file.'); 61 | case is_object($value): 62 | return '!!php/object:'.serialize($value); 63 | case is_array($value): 64 | return self::dumpArray($value); 65 | case is_null($value): 66 | return 'null'; 67 | case true === $value: 68 | return 'true'; 69 | case false === $value: 70 | return 'false'; 71 | case ctype_digit($value): 72 | return is_string($value) ? "'$value'" : (int) $value; 73 | case is_numeric($value): 74 | return is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : (is_string($value) ? "'$value'" : $value); 75 | case false !== strpos($value, "\n"): 76 | return sprintf('"%s"', str_replace(array('"', "\n"), array('\\"', '\n'), $value)); 77 | case preg_match('/[ \s \' " \: \{ \} \[ \] , & \*]/x', $value): 78 | return sprintf("'%s'", str_replace('\'', '\'\'', $value)); 79 | case '' == $value: 80 | return "''"; 81 | case preg_match(self::getTimestampRegex(), $value): 82 | return "'$value'"; 83 | case in_array(strtolower($value), array('true', 'on', '+', 'yes', 'y')): 84 | return "'$value'"; 85 | case in_array(strtolower($value), array('false', 'off', '-', 'no', 'n')): 86 | return "'$value'"; 87 | default: 88 | return $value; 89 | } 90 | } 91 | 92 | /** 93 | * Dumps PHP array to YAML 94 | * 95 | * @param array The array to dump 96 | * 97 | * @return string YAML 98 | */ 99 | static protected function dumpArray($value) 100 | { 101 | // array 102 | $keys = array_keys($value); 103 | if ( 104 | (1 == count($keys) && '0' == $keys[0]) 105 | || 106 | (count($keys) > 1 && array_reduce($keys, create_function('$v,$w', 'return (integer) $v + $w;'), 0) == count($keys) * (count($keys) - 1) / 2)) 107 | { 108 | $output = array(); 109 | foreach ($value as $val) 110 | { 111 | $output[] = self::dump($val); 112 | } 113 | 114 | return sprintf('[%s]', implode(', ', $output)); 115 | } 116 | 117 | // mapping 118 | $output = array(); 119 | foreach ($value as $key => $val) 120 | { 121 | $output[] = sprintf('%s: %s', self::dump($key), self::dump($val)); 122 | } 123 | 124 | return sprintf('{ %s }', implode(', ', $output)); 125 | } 126 | 127 | /** 128 | * Parses scalar to yaml 129 | * 130 | * @param scalar $scalar 131 | * @param string $delimiters 132 | * @param array String delimiter 133 | * @param integer $i 134 | * @param boolean $evaluate 135 | * 136 | * @return string YAML 137 | */ 138 | static public function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) 139 | { 140 | if (in_array($scalar[$i], $stringDelimiters)) 141 | { 142 | // quoted scalar 143 | $output = self::parseQuotedScalar($scalar, $i); 144 | 145 | // skip next delimiter 146 | ++$i; 147 | } 148 | else 149 | { 150 | // "normal" string 151 | if (!$delimiters) 152 | { 153 | $output = substr($scalar, $i); 154 | $i += strlen($output); 155 | 156 | // remove comments 157 | if (false !== $strpos = strpos($output, ' #')) 158 | { 159 | $output = rtrim(substr($output, 0, $strpos)); 160 | } 161 | } 162 | else if (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) 163 | { 164 | $output = $match[1]; 165 | $i += strlen($output); 166 | } 167 | else 168 | { 169 | throw new InvalidArgumentException(sprintf('Malformed inline YAML string (%s).', $scalar)); 170 | } 171 | 172 | $output = $evaluate ? self::evaluateScalar($output) : $output; 173 | } 174 | 175 | return $output; 176 | } 177 | 178 | /** 179 | * Parses quotes scalar 180 | * 181 | * @param string $scalar 182 | * @param integer $i 183 | * 184 | * @return string YAML 185 | */ 186 | static protected function parseQuotedScalar($scalar, &$i) 187 | { 188 | $delimiter = $scalar[$i]; 189 | ++$i; 190 | $buffer = ''; 191 | $len = strlen($scalar); 192 | $escaped = '"' == $delimiter ? '\\"' : "''"; 193 | 194 | while ($i < $len) 195 | { 196 | if (isset($scalar[$i + 1]) && $escaped == $scalar[$i].$scalar[$i + 1]) 197 | { 198 | $buffer .= $delimiter; 199 | ++$i; 200 | } 201 | else if ($delimiter == $scalar[$i]) 202 | { 203 | break; 204 | } 205 | else 206 | { 207 | $buffer .= $scalar[$i]; 208 | } 209 | 210 | ++$i; 211 | } 212 | 213 | if ('"' == $delimiter) 214 | { 215 | // evaluate the string 216 | $buffer = str_replace('\\n', "\n", $buffer); 217 | } 218 | 219 | return $buffer; 220 | } 221 | 222 | /** 223 | * Parse sequence to yaml 224 | * 225 | * @param string $sequence 226 | * @param integer $i 227 | * 228 | * @return string YAML 229 | */ 230 | static protected function parseSequence($sequence, &$i = 0) 231 | { 232 | $output = array(); 233 | $len = strlen($sequence); 234 | $i += 1; 235 | 236 | // [foo, bar, ...] 237 | while ($i < $len) 238 | { 239 | switch ($sequence[$i]) 240 | { 241 | case '[': 242 | // nested sequence 243 | $output[] = self::parseSequence($sequence, $i); 244 | break; 245 | case '{': 246 | // nested mapping 247 | $output[] = self::parseMapping($sequence, $i); 248 | break; 249 | case ']': 250 | return $output; 251 | case ',': 252 | case ' ': 253 | break; 254 | default: 255 | $isQuoted = in_array($sequence[$i], array('"', "'")); 256 | $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); 257 | 258 | if (!$isQuoted && false !== strpos($value, ': ')) 259 | { 260 | // embedded mapping? 261 | try 262 | { 263 | $value = self::parseMapping('{'.$value.'}'); 264 | } 265 | catch (InvalidArgumentException $e) 266 | { 267 | // no, it's not 268 | } 269 | } 270 | 271 | $output[] = $value; 272 | 273 | --$i; 274 | } 275 | 276 | ++$i; 277 | } 278 | 279 | throw new InvalidArgumentException(sprintf('Malformed inline YAML string %s', $sequence)); 280 | } 281 | 282 | /** 283 | * Parses mapping. 284 | * 285 | * @param string $mapping 286 | * @param integer $i 287 | * 288 | * @return string YAML 289 | */ 290 | static protected function parseMapping($mapping, &$i = 0) 291 | { 292 | $output = array(); 293 | $len = strlen($mapping); 294 | $i += 1; 295 | 296 | // {foo: bar, bar:foo, ...} 297 | while ($i < $len) 298 | { 299 | switch ($mapping[$i]) 300 | { 301 | case ' ': 302 | case ',': 303 | ++$i; 304 | continue 2; 305 | case '}': 306 | return $output; 307 | } 308 | 309 | // key 310 | $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); 311 | 312 | // value 313 | $done = false; 314 | while ($i < $len) 315 | { 316 | switch ($mapping[$i]) 317 | { 318 | case '[': 319 | // nested sequence 320 | $output[$key] = self::parseSequence($mapping, $i); 321 | $done = true; 322 | break; 323 | case '{': 324 | // nested mapping 325 | $output[$key] = self::parseMapping($mapping, $i); 326 | $done = true; 327 | break; 328 | case ':': 329 | case ' ': 330 | break; 331 | default: 332 | $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); 333 | $done = true; 334 | --$i; 335 | } 336 | 337 | ++$i; 338 | 339 | if ($done) 340 | { 341 | continue 2; 342 | } 343 | } 344 | } 345 | 346 | throw new InvalidArgumentException(sprintf('Malformed inline YAML string %s', $mapping)); 347 | } 348 | 349 | /** 350 | * Evaluates scalars and replaces magic values. 351 | * 352 | * @param string $scalar 353 | * 354 | * @return string YAML 355 | */ 356 | static protected function evaluateScalar($scalar) 357 | { 358 | $scalar = trim($scalar); 359 | 360 | switch (true) 361 | { 362 | case 'null' == strtolower($scalar): 363 | case '' == $scalar: 364 | case '~' == $scalar: 365 | return null; 366 | case 0 === strpos($scalar, '!str'): 367 | return (string) substr($scalar, 5); 368 | case 0 === strpos($scalar, '! '): 369 | return intval(self::parseScalar(substr($scalar, 2))); 370 | case 0 === strpos($scalar, '!!php/object:'): 371 | return unserialize(substr($scalar, 13)); 372 | case ctype_digit($scalar): 373 | return '0' == $scalar[0] ? octdec($scalar) : intval($scalar); 374 | case in_array(strtolower($scalar), array('true', 'on', '+', 'yes', 'y')): 375 | return true; 376 | case in_array(strtolower($scalar), array('false', 'off', '-', 'no', 'n')): 377 | return false; 378 | case is_numeric($scalar): 379 | return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); 380 | case 0 == strcasecmp($scalar, '.inf'): 381 | case 0 == strcasecmp($scalar, '.NaN'): 382 | return -log(0); 383 | case 0 == strcasecmp($scalar, '-.inf'): 384 | return log(0); 385 | case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): 386 | return floatval(str_replace(',', '', $scalar)); 387 | case preg_match(self::getTimestampRegex(), $scalar): 388 | return strtotime($scalar); 389 | default: 390 | return (string) $scalar; 391 | } 392 | } 393 | 394 | static protected function getTimestampRegex() 395 | { 396 | return <<[0-9][0-9][0-9][0-9]) 399 | -(?P[0-9][0-9]?) 400 | -(?P[0-9][0-9]?) 401 | (?:(?:[Tt]|[ \t]+) 402 | (?P[0-9][0-9]?) 403 | :(?P[0-9][0-9]) 404 | :(?P[0-9][0-9]) 405 | (?:\.(?P[0-9]*))? 406 | (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) 407 | (?::(?P[0-9][0-9]))?))?)? 408 | $~x 409 | EOF; 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/RSS2.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: RSS2.php,v 1.12 2008/03/08 18:16:45 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This class handles RSS2 feeds. 26 | * 27 | * @author James Stewart 28 | * @version Release: 1.0.3 29 | * @package XML_Feed_Parser 30 | */ 31 | class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type 32 | { 33 | /** 34 | * The URI of the RelaxNG schema used to (optionally) validate the feed 35 | * @var string 36 | */ 37 | private $relax = 'rss20.rnc'; 38 | 39 | /** 40 | * We're likely to use XPath, so let's keep it global 41 | * @var DOMXPath 42 | */ 43 | protected $xpath; 44 | 45 | /** 46 | * The feed type we are parsing 47 | * @var string 48 | */ 49 | public $version = 'RSS 2.0'; 50 | 51 | /** 52 | * The class used to represent individual items 53 | * @var string 54 | */ 55 | protected $itemClass = 'XML_Feed_Parser_RSS2Element'; 56 | 57 | /** 58 | * The element containing entries 59 | * @var string 60 | */ 61 | protected $itemElement = 'item'; 62 | 63 | /** 64 | * Here we map those elements we're not going to handle individually 65 | * to the constructs they are. The optional second parameter in the array 66 | * tells the parser whether to 'fall back' (not apt. at the feed level) or 67 | * fail if the element is missing. If the parameter is not set, the function 68 | * will simply return false and leave it to the client to decide what to do. 69 | * @var array 70 | */ 71 | protected $map = array( 72 | 'ttl' => array('Text'), 73 | 'pubDate' => array('Date'), 74 | 'lastBuildDate' => array('Date'), 75 | 'title' => array('Text'), 76 | 'link' => array('Link'), 77 | 'description' => array('Text'), 78 | 'language' => array('Text'), 79 | 'copyright' => array('Text'), 80 | 'managingEditor' => array('Text'), 81 | 'webMaster' => array('Text'), 82 | 'category' => array('Text'), 83 | 'generator' => array('Text'), 84 | 'docs' => array('Text'), 85 | 'ttl' => array('Text'), 86 | 'image' => array('Image'), 87 | 'skipDays' => array('skipDays'), 88 | 'skipHours' => array('skipHours')); 89 | 90 | /** 91 | * Here we map some elements to their atom equivalents. This is going to be 92 | * quite tricky to pull off effectively (and some users' methods may vary) 93 | * but is worth trying. The key is the atom version, the value is RSS2. 94 | * @var array 95 | */ 96 | protected $compatMap = array( 97 | 'title' => array('title'), 98 | 'rights' => array('copyright'), 99 | 'updated' => array('lastBuildDate'), 100 | 'subtitle' => array('description'), 101 | 'date' => array('pubDate'), 102 | 'author' => array('managingEditor')); 103 | 104 | protected $namespaces = array( 105 | 'dc' => 'http://purl.org/rss/1.0/modules/dc/', 106 | 'content' => 'http://purl.org/rss/1.0/modules/content/'); 107 | 108 | /** 109 | * Our constructor does nothing more than its parent. 110 | * 111 | * @param DOMDocument $xml A DOM object representing the feed 112 | * @param bool (optional) $string Whether or not to validate this feed 113 | */ 114 | function __construct(DOMDocument $model, $strict = false) 115 | { 116 | $this->model = $model; 117 | 118 | if ($strict) { 119 | if (! $this->model->relaxNGValidate($this->relax)) { 120 | throw new XML_Feed_Parser_Exception('Failed required validation'); 121 | } 122 | } 123 | 124 | $this->xpath = new DOMXPath($this->model); 125 | foreach ($this->namespaces as $key => $value) { 126 | $this->xpath->registerNamespace($key, $value); 127 | } 128 | $this->numberEntries = $this->count('item'); 129 | } 130 | 131 | /** 132 | * Retrieves an entry by ID, if the ID is specified with the guid element 133 | * 134 | * This is not really something that will work with RSS2 as it does not have 135 | * clear restrictions on the global uniqueness of IDs. But we can emulate 136 | * it by allowing access based on the 'guid' element. If DOMXPath::evaluate 137 | * is available, we also use that to store a reference to the entry in the array 138 | * used by getEntryByOffset so that method does not have to seek out the entry 139 | * if it's requested that way. 140 | * 141 | * @param string $id any valid ID. 142 | * @return XML_Feed_Parser_RSS2Element 143 | */ 144 | function getEntryById($id) 145 | { 146 | if (isset($this->idMappings[$id])) { 147 | return $this->entries[$this->idMappings[$id]]; 148 | } 149 | 150 | $entries = $this->xpath->query("//item[guid='$id']"); 151 | if ($entries->length > 0) { 152 | $entry = new $this->itemElement($entries->item(0), $this); 153 | if (in_array('evaluate', get_class_methods($this->xpath))) { 154 | $offset = $this->xpath->evaluate("count(preceding-sibling::item)", $entries->item(0)); 155 | $this->entries[$offset] = $entry; 156 | } 157 | $this->idMappings[$id] = $entry; 158 | return $entry; 159 | } 160 | } 161 | 162 | /** 163 | * Get a category from the element 164 | * 165 | * The category element is a simple text construct which can occur any number 166 | * of times. We allow access by offset or access to an array of results. 167 | * 168 | * @param string $call for compatibility with our overloading 169 | * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array 170 | * @return string|array|false 171 | */ 172 | function getCategory($call, $arguments = array()) 173 | { 174 | $categories = $this->model->getElementsByTagName('category'); 175 | $offset = empty($arguments[0]) ? 0 : $arguments[0]; 176 | $array = empty($arguments[1]) ? false : true; 177 | if ($categories->length <= $offset) { 178 | return false; 179 | } 180 | if ($array) { 181 | $list = array(); 182 | foreach ($categories as $category) { 183 | array_push($list, $category->nodeValue); 184 | } 185 | return $list; 186 | } 187 | return $categories->item($offset)->nodeValue; 188 | } 189 | 190 | /** 191 | * Get details of the image associated with the feed. 192 | * 193 | * @return array|false an array simply containing the child elements 194 | */ 195 | protected function getImage() 196 | { 197 | $images = $this->xpath->query("//image"); 198 | if ($images->length > 0) { 199 | $image = $images->item(0); 200 | $desc = $image->getElementsByTagName('description'); 201 | $description = $desc->length ? $desc->item(0)->nodeValue : false; 202 | $heigh = $image->getElementsByTagName('height'); 203 | $height = $heigh->length ? $heigh->item(0)->nodeValue : false; 204 | $widt = $image->getElementsByTagName('width'); 205 | $width = $widt->length ? $widt->item(0)->nodeValue : false; 206 | return array( 207 | 'title' => $image->getElementsByTagName('title')->item(0)->nodeValue, 208 | 'link' => $image->getElementsByTagName('link')->item(0)->nodeValue, 209 | 'url' => $image->getElementsByTagName('url')->item(0)->nodeValue, 210 | 'description' => $description, 211 | 'height' => $height, 212 | 'width' => $width); 213 | } 214 | return false; 215 | } 216 | 217 | /** 218 | * The textinput element is little used, but in the interests of 219 | * completeness... 220 | * 221 | * @return array|false 222 | */ 223 | function getTextInput() 224 | { 225 | $inputs = $this->model->getElementsByTagName('input'); 226 | if ($inputs->length > 0) { 227 | $input = $inputs->item(0); 228 | return array( 229 | 'title' => $input->getElementsByTagName('title')->item(0)->value, 230 | 'description' => 231 | $input->getElementsByTagName('description')->item(0)->value, 232 | 'name' => $input->getElementsByTagName('name')->item(0)->value, 233 | 'link' => $input->getElementsByTagName('link')->item(0)->value); 234 | } 235 | return false; 236 | } 237 | 238 | /** 239 | * Utility function for getSkipDays and getSkipHours 240 | * 241 | * This is a general function used by both getSkipDays and getSkipHours. It simply 242 | * returns an array of the values of the children of the appropriate tag. 243 | * 244 | * @param string $tagName The tag name (getSkipDays or getSkipHours) 245 | * @return array|false 246 | */ 247 | protected function getSkips($tagName) 248 | { 249 | $hours = $this->model->getElementsByTagName($tagName); 250 | if ($hours->length == 0) { 251 | return false; 252 | } 253 | $skipHours = array(); 254 | foreach($hours->item(0)->childNodes as $hour) { 255 | if ($hour instanceof DOMElement) { 256 | array_push($skipHours, $hour->nodeValue); 257 | } 258 | } 259 | return $skipHours; 260 | } 261 | 262 | /** 263 | * Retrieve skipHours data 264 | * 265 | * The skiphours element provides a list of hours on which this feed should 266 | * not be checked. We return an array of those hours (integers, 24 hour clock) 267 | * 268 | * @return array 269 | */ 270 | function getSkipHours() 271 | { 272 | return $this->getSkips('skipHours'); 273 | } 274 | 275 | /** 276 | * Retrieve skipDays data 277 | * 278 | * The skipdays element provides a list of days on which this feed should 279 | * not be checked. We return an array of those days. 280 | * 281 | * @return array 282 | */ 283 | function getSkipDays() 284 | { 285 | return $this->getSkips('skipDays'); 286 | } 287 | 288 | /** 289 | * Return content of the little-used 'cloud' element 290 | * 291 | * The cloud element is rarely used. It is designed to provide some details 292 | * of a location to update the feed. 293 | * 294 | * @return array an array of the attributes of the element 295 | */ 296 | function getCloud() 297 | { 298 | $cloud = $this->model->getElementsByTagName('cloud'); 299 | if ($cloud->length == 0) { 300 | return false; 301 | } 302 | $cloudData = array(); 303 | foreach ($cloud->item(0)->attributes as $attribute) { 304 | $cloudData[$attribute->name] = $attribute->value; 305 | } 306 | return $cloudData; 307 | } 308 | 309 | /** 310 | * Get link URL 311 | * 312 | * In RSS2 a link is a text element but in order to ensure that we resolve 313 | * URLs properly we have a special function for them. We maintain the 314 | * parameter used by the atom getLink method, though we only use the offset 315 | * parameter. 316 | * 317 | * @param int $offset The position of the link within the feed. Starts from 0 318 | * @param string $attribute The attribute of the link element required 319 | * @param array $params An array of other parameters. Not used. 320 | * @return string 321 | */ 322 | function getLink($offset, $attribute = 'href', $params = array()) 323 | { 324 | $links = $this->model->getElementsByTagName('link'); 325 | 326 | if ($links->length <= $offset) { 327 | return false; 328 | } 329 | $link = $links->item($offset); 330 | return $this->addBase($link->nodeValue, $link); 331 | } 332 | } 333 | 334 | ?> -------------------------------------------------------------------------------- /inc/XML/Feed/Parser.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 20 | * @version CVS: $Id: Parser.php,v 1.24 2006/08/15 13:04:00 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * XML_Feed_Parser_Type is an abstract class required by all of our 26 | * feed types. It makes sense to load it here to keep the other files 27 | * clean. 28 | */ 29 | require_once 'XML/Feed/Parser/Type.php'; 30 | 31 | /** 32 | * We will throw exceptions when errors occur. 33 | */ 34 | require_once 'XML/Feed/Parser/Exception.php'; 35 | 36 | /** 37 | * This is the core of the XML_Feed_Parser package. It identifies feed types 38 | * and abstracts access to them. It is an iterator, allowing for easy access 39 | * to the entire feed. 40 | * 41 | * @author James Stewart 42 | * @version Release: 1.0.3 43 | * @package XML_Feed_Parser 44 | */ 45 | class XML_Feed_Parser implements Iterator 46 | { 47 | /** 48 | * This is where we hold the feed object 49 | * @var Object 50 | */ 51 | private $feed; 52 | 53 | /** 54 | * To allow for extensions, we make a public reference to the feed model 55 | * @var DOMDocument 56 | */ 57 | public $model; 58 | 59 | /** 60 | * A map between entry ID and offset 61 | * @var array 62 | */ 63 | protected $idMappings = array(); 64 | 65 | /** 66 | * A storage space for Namespace URIs. 67 | * @var array 68 | */ 69 | private $feedNamespaces = array( 70 | 'rss2' => array( 71 | 'http://backend.userland.com/rss', 72 | 'http://backend.userland.com/rss2', 73 | 'http://blogs.law.harvard.edu/tech/rss')); 74 | /** 75 | * Detects feed types and instantiate appropriate objects. 76 | * 77 | * Our constructor takes care of detecting feed types and instantiating 78 | * appropriate classes. For now we're going to treat Atom 0.3 as Atom 1.0 79 | * but raise a warning. I do not intend to introduce full support for 80 | * Atom 0.3 as it has been deprecated, but others are welcome to. 81 | * 82 | * @param string $feed XML serialization of the feed 83 | * @param bool $strict Whether or not to validate the feed 84 | * @param bool $suppressWarnings Trigger errors for deprecated feed types? 85 | * @param bool $tidy Whether or not to try and use the tidy library on input 86 | */ 87 | function __construct($feed, $strict = false, $suppressWarnings = false, $tidy = false) 88 | { 89 | $this->model = new DOMDocument; 90 | if (! $this->model->loadXML($feed)) { 91 | if (extension_loaded('tidy') && $tidy) { 92 | $tidy = new tidy; 93 | $tidy->parseString($feed, 94 | array('input-xml' => true, 'output-xml' => true)); 95 | $tidy->cleanRepair(); 96 | if (! $this->model->loadXML((string) $tidy)) { 97 | throw new XML_Feed_Parser_Exception('Invalid input: this is not ' . 98 | 'valid XML'); 99 | } 100 | } else { 101 | throw new XML_Feed_Parser_Exception('Invalid input: this is not valid XML'); 102 | } 103 | 104 | } 105 | 106 | /* detect feed type */ 107 | $doc_element = $this->model->documentElement; 108 | $error = false; 109 | 110 | switch (true) { 111 | case ($doc_element->namespaceURI == 'http://www.w3.org/2005/Atom'): 112 | require_once 'XML/Feed/Parser/Atom.php'; 113 | require_once 'XML/Feed/Parser/AtomElement.php'; 114 | $class = 'XML_Feed_Parser_Atom'; 115 | break; 116 | case ($doc_element->namespaceURI == 'http://purl.org/atom/ns#'): 117 | require_once 'XML/Feed/Parser/Atom.php'; 118 | require_once 'XML/Feed/Parser/AtomElement.php'; 119 | $class = 'XML_Feed_Parser_Atom'; 120 | $error = 'Atom 0.3 deprecated, using 1.0 parser which won\'t provide ' . 121 | 'all options'; 122 | break; 123 | case ($doc_element->namespaceURI == 'http://purl.org/rss/1.0/' || 124 | ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1 125 | && $doc_element->childNodes->item(1)->namespaceURI == 126 | 'http://purl.org/rss/1.0/')): 127 | require_once 'XML/Feed/Parser/RSS1.php'; 128 | require_once 'XML/Feed/Parser/RSS1Element.php'; 129 | $class = 'XML_Feed_Parser_RSS1'; 130 | break; 131 | case ($doc_element->namespaceURI == 'http://purl.org/rss/1.1/' || 132 | ($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1 133 | && $doc_element->childNodes->item(1)->namespaceURI == 134 | 'http://purl.org/rss/1.1/')): 135 | require_once 'XML/Feed/Parser/RSS11.php'; 136 | require_once 'XML/Feed/Parser/RSS11Element.php'; 137 | $class = 'XML_Feed_Parser_RSS11'; 138 | break; 139 | case (($doc_element->hasChildNodes() && $doc_element->childNodes->length > 1 140 | && $doc_element->childNodes->item(1)->namespaceURI == 141 | 'http://my.netscape.com/rdf/simple/0.9/') || 142 | $doc_element->namespaceURI == 'http://my.netscape.com/rdf/simple/0.9/'): 143 | require_once 'XML/Feed/Parser/RSS09.php'; 144 | require_once 'XML/Feed/Parser/RSS09Element.php'; 145 | $class = 'XML_Feed_Parser_RSS09'; 146 | break; 147 | case ($doc_element->tagName == 'rss' and 148 | $doc_element->hasAttribute('version') && 149 | $doc_element->getAttribute('version') == 0.91): 150 | $error = 'RSS 0.91 has been superceded by RSS2.0. Using RSS2.0 parser.'; 151 | require_once 'XML/Feed/Parser/RSS2.php'; 152 | require_once 'XML/Feed/Parser/RSS2Element.php'; 153 | $class = 'XML_Feed_Parser_RSS2'; 154 | break; 155 | case ($doc_element->tagName == 'rss' and 156 | $doc_element->hasAttribute('version') && 157 | $doc_element->getAttribute('version') == 0.92): 158 | $error = 'RSS 0.92 has been superceded by RSS2.0. Using RSS2.0 parser.'; 159 | require_once 'XML/Feed/Parser/RSS2.php'; 160 | require_once 'XML/Feed/Parser/RSS2Element.php'; 161 | $class = 'XML_Feed_Parser_RSS2'; 162 | break; 163 | case (in_array($doc_element->namespaceURI, $this->feedNamespaces['rss2']) 164 | || $doc_element->tagName == 'rss'): 165 | if (! $doc_element->hasAttribute('version') || 166 | $doc_element->getAttribute('version') != 2) { 167 | $error = 'RSS version not specified. Parsing as RSS2.0'; 168 | } 169 | require_once 'XML/Feed/Parser/RSS2.php'; 170 | require_once 'XML/Feed/Parser/RSS2Element.php'; 171 | $class = 'XML_Feed_Parser_RSS2'; 172 | break; 173 | default: 174 | throw new XML_Feed_Parser_Exception('Feed type unknown'); 175 | break; 176 | } 177 | 178 | if (! $suppressWarnings && ! empty($error)) { 179 | trigger_error($error, E_USER_WARNING); 180 | } 181 | 182 | /* Instantiate feed object */ 183 | $this->feed = new $class($this->model, $strict); 184 | } 185 | 186 | /** 187 | * Proxy to allow feed element names to be used as method names 188 | * 189 | * For top-level feed elements we will provide access using methods or 190 | * attributes. This function simply passes on a request to the appropriate 191 | * feed type object. 192 | * 193 | * @param string $call - the method being called 194 | * @param array $attributes 195 | */ 196 | function __call($call, $attributes) 197 | { 198 | $attributes = array_pad($attributes, 5, false); 199 | list($a, $b, $c, $d, $e) = $attributes; 200 | return $this->feed->$call($a, $b, $c, $d, $e); 201 | } 202 | 203 | /** 204 | * Proxy to allow feed element names to be used as attribute names 205 | * 206 | * To allow variable-like access to feed-level data we use this 207 | * method. It simply passes along to __call() which in turn passes 208 | * along to the relevant object. 209 | * 210 | * @param string $val - the name of the variable required 211 | */ 212 | function __get($val) 213 | { 214 | return $this->feed->$val; 215 | } 216 | 217 | /** 218 | * Provides iteration functionality. 219 | * 220 | * Of course we must be able to iterate... This function simply increases 221 | * our internal counter. 222 | */ 223 | function next() 224 | { 225 | if (isset($this->current_item) && 226 | $this->current_item <= $this->feed->numberEntries - 1) { 227 | ++$this->current_item; 228 | } else if (! isset($this->current_item)) { 229 | $this->current_item = 0; 230 | } else { 231 | return false; 232 | } 233 | } 234 | 235 | /** 236 | * Return XML_Feed_Type object for current element 237 | * 238 | * @return XML_Feed_Parser_Type Object 239 | */ 240 | function current() 241 | { 242 | return $this->getEntryByOffset($this->current_item); 243 | } 244 | 245 | /** 246 | * For iteration -- returns the key for the current stage in the array. 247 | * 248 | * @return int 249 | */ 250 | function key() 251 | { 252 | return $this->current_item; 253 | } 254 | 255 | /** 256 | * For iteration -- tells whether we have reached the 257 | * end. 258 | * 259 | * @return bool 260 | */ 261 | function valid() 262 | { 263 | return $this->current_item < $this->feed->numberEntries; 264 | } 265 | 266 | /** 267 | * For iteration -- resets the internal counter to the beginning. 268 | */ 269 | function rewind() 270 | { 271 | $this->current_item = 0; 272 | } 273 | 274 | /** 275 | * Provides access to entries by ID if one is specified in the source feed. 276 | * 277 | * As well as allowing the items to be iterated over we want to allow 278 | * users to be able to access a specific entry. This is one of two ways of 279 | * doing that, the other being by offset. This method can be quite slow 280 | * if dealing with a large feed that hasn't yet been processed as it 281 | * instantiates objects for every entry until it finds the one needed. 282 | * 283 | * @param string $id Valid ID for the given feed format 284 | * @return XML_Feed_Parser_Type|false 285 | */ 286 | function getEntryById($id) 287 | { 288 | if (isset($this->idMappings[$id])) { 289 | return $this->getEntryByOffset($this->idMappings[$id]); 290 | } 291 | 292 | /* 293 | * Since we have not yet encountered that ID, let's go through all the 294 | * remaining entries in order till we find it. 295 | * This is a fairly slow implementation, but it should work. 296 | */ 297 | return $this->feed->getEntryById($id); 298 | } 299 | 300 | /** 301 | * Retrieve entry by numeric offset, starting from zero. 302 | * 303 | * As well as allowing the items to be iterated over we want to allow 304 | * users to be able to access a specific entry. This is one of two ways of 305 | * doing that, the other being by ID. 306 | * 307 | * @param int $offset The position of the entry within the feed, starting from 0 308 | * @return XML_Feed_Parser_Type|false 309 | */ 310 | function getEntryByOffset($offset) 311 | { 312 | if ($offset < $this->feed->numberEntries) { 313 | if (isset($this->feed->entries[$offset])) { 314 | return $this->feed->entries[$offset]; 315 | } else { 316 | try { 317 | $this->feed->getEntryByOffset($offset); 318 | } catch (Exception $e) { 319 | return false; 320 | } 321 | $id = $this->feed->entries[$offset]->getID(); 322 | $this->idMappings[$id] = $offset; 323 | return $this->feed->entries[$offset]; 324 | } 325 | } else { 326 | return false; 327 | } 328 | } 329 | 330 | /** 331 | * Retrieve version details from feed type class. 332 | * 333 | * @return void 334 | * @author James Stewart 335 | */ 336 | function version() 337 | { 338 | return $this->feed->version; 339 | } 340 | 341 | /** 342 | * Returns a string representation of the feed. 343 | * 344 | * @return String 345 | **/ 346 | function __toString() 347 | { 348 | return $this->feed->__toString(); 349 | } 350 | } 351 | ?> -------------------------------------------------------------------------------- /inc/XML/Feed/Parser/Atom.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2005 James Stewart 19 | * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 20 | * @version CVS: $Id: Atom.php,v 1.29 2008/03/30 22:00:36 jystewart Exp $ 21 | * @link http://pear.php.net/package/XML_Feed_Parser/ 22 | */ 23 | 24 | /** 25 | * This is the class that determines how we manage Atom 1.0 feeds 26 | * 27 | * How we deal with constructs: 28 | * date - return as unix datetime for use with the 'date' function unless specified otherwise 29 | * text - return as is. optional parameter will give access to attributes 30 | * person - defaults to name, but parameter based access 31 | * 32 | * @author James Stewart 33 | * @version Release: 1.0.3 34 | * @package XML_Feed_Parser 35 | */ 36 | class XML_Feed_Parser_Atom extends XML_Feed_Parser_Type 37 | { 38 | /** 39 | * The URI of the RelaxNG schema used to (optionally) validate the feed 40 | * @var string 41 | */ 42 | private $relax = 'atom.rnc'; 43 | 44 | /** 45 | * We're likely to use XPath, so let's keep it global 46 | * @var DOMXPath 47 | */ 48 | public $xpath; 49 | 50 | /** 51 | * When performing XPath queries we will use this prefix 52 | * @var string 53 | */ 54 | private $xpathPrefix = '//'; 55 | 56 | /** 57 | * The feed type we are parsing 58 | * @var string 59 | */ 60 | public $version = 'Atom 1.0'; 61 | 62 | /** 63 | * The class used to represent individual items 64 | * @var string 65 | */ 66 | protected $itemClass = 'XML_Feed_Parser_AtomElement'; 67 | 68 | /** 69 | * The element containing entries 70 | * @var string 71 | */ 72 | protected $itemElement = 'entry'; 73 | 74 | /** 75 | * Here we map those elements we're not going to handle individually 76 | * to the constructs they are. The optional second parameter in the array 77 | * tells the parser whether to 'fall back' (not apt. at the feed level) or 78 | * fail if the element is missing. If the parameter is not set, the function 79 | * will simply return false and leave it to the client to decide what to do. 80 | * @var array 81 | */ 82 | protected $map = array( 83 | 'author' => array('Person'), 84 | 'contributor' => array('Person'), 85 | 'icon' => array('Text'), 86 | 'logo' => array('Text'), 87 | 'id' => array('Text', 'fail'), 88 | 'rights' => array('Text'), 89 | 'subtitle' => array('Text'), 90 | 'title' => array('Text', 'fail'), 91 | 'updated' => array('Date', 'fail'), 92 | 'link' => array('Link'), 93 | 'generator' => array('Text'), 94 | 'category' => array('Category')); 95 | 96 | /** 97 | * Here we provide a few mappings for those very special circumstances in 98 | * which it makes sense to map back to the RSS2 spec. Key is RSS2 version 99 | * value is an array consisting of the equivalent in atom and any attributes 100 | * needed to make the mapping. 101 | * @var array 102 | */ 103 | protected $compatMap = array( 104 | 'guid' => array('id'), 105 | 'links' => array('link'), 106 | 'tags' => array('category'), 107 | 'contributors' => array('contributor')); 108 | 109 | /** 110 | * Our constructor does nothing more than its parent. 111 | * 112 | * @param DOMDocument $xml A DOM object representing the feed 113 | * @param bool (optional) $string Whether or not to validate this feed 114 | */ 115 | function __construct(DOMDocument $model, $strict = false) 116 | { 117 | $this->model = $model; 118 | 119 | if ($strict) { 120 | if (! $this->model->relaxNGValidateSource($this->relax)) { 121 | throw new XML_Feed_Parser_Exception('Failed required validation'); 122 | } 123 | } 124 | 125 | $this->xpath = new DOMXPath($this->model); 126 | $this->xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); 127 | $this->numberEntries = $this->count('entry'); 128 | } 129 | 130 | /** 131 | * Implement retrieval of an entry based on its ID for atom feeds. 132 | * 133 | * This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate 134 | * is available, we also use that to store a reference to the entry in the array 135 | * used by getEntryByOffset so that method does not have to seek out the entry 136 | * if it's requested that way. 137 | * 138 | * @param string $id any valid Atom ID. 139 | * @return XML_Feed_Parser_AtomElement 140 | */ 141 | function getEntryById($id) 142 | { 143 | if (isset($this->idMappings[$id])) { 144 | return $this->entries[$this->idMappings[$id]]; 145 | } 146 | 147 | $entries = $this->xpath->query("//atom:entry[atom:id='$id']"); 148 | 149 | if ($entries->length > 0) { 150 | $xmlBase = $entries->item(0)->baseURI; 151 | $entry = new $this->itemClass($entries->item(0), $this, $xmlBase); 152 | 153 | if (in_array('evaluate', get_class_methods($this->xpath))) { 154 | $offset = $this->xpath->evaluate("count(preceding-sibling::atom:entry)", $entries->item(0)); 155 | $this->entries[$offset] = $entry; 156 | } 157 | 158 | $this->idMappings[$id] = $entry; 159 | 160 | return $entry; 161 | } 162 | 163 | } 164 | 165 | /** 166 | * Retrieves data from a person construct. 167 | * 168 | * Get a person construct. We default to the 'name' element but allow 169 | * access to any of the elements. 170 | * 171 | * @param string $method The name of the person construct we want 172 | * @param array $arguments An array which we hope gives a 'param' 173 | * @return string|false 174 | */ 175 | protected function getPerson($method, $arguments) 176 | { 177 | $offset = empty($arguments[0]) ? 0 : $arguments[0]; 178 | $parameter = empty($arguments[1]['param']) ? 'name' : $arguments[1]['param']; 179 | $section = $this->model->getElementsByTagName($method); 180 | 181 | if ($parameter == 'url') { 182 | $parameter = 'uri'; 183 | } 184 | 185 | if ($section->length <= $offset) { 186 | return false; 187 | } 188 | 189 | $param = $section->item($offset)->getElementsByTagName($parameter); 190 | if ($param->length == 0) { 191 | return false; 192 | } 193 | return $param->item(0)->nodeValue; 194 | } 195 | 196 | /** 197 | * Retrieves an element's content where that content is a text construct. 198 | * 199 | * Get a text construct. When calling this method, the two arguments 200 | * allowed are 'offset' and 'attribute', so $parser->subtitle() would 201 | * return the content of the element, while $parser->subtitle(false, 'type') 202 | * would return the value of the type attribute. 203 | * 204 | * @todo Clarify overlap with getContent() 205 | * @param string $method The name of the text construct we want 206 | * @param array $arguments An array which we hope gives a 'param' 207 | * @return string 208 | */ 209 | protected function getText($method, $arguments) 210 | { 211 | $offset = empty($arguments[0]) ? 0: $arguments[0]; 212 | $attribute = empty($arguments[1]) ? false : $arguments[1]; 213 | $tags = $this->model->getElementsByTagName($method); 214 | 215 | if ($tags->length <= $offset) { 216 | return false; 217 | } 218 | 219 | $content = $tags->item($offset); 220 | 221 | if (! $content->hasAttribute('type')) { 222 | $content->setAttribute('type', 'text'); 223 | } 224 | $type = $content->getAttribute('type'); 225 | 226 | if (! empty($attribute) and 227 | ! ($method == 'generator' and $attribute == 'name')) { 228 | if ($content->hasAttribute($attribute)) { 229 | return $content->getAttribute($attribute); 230 | } else if ($attribute == 'href' and $content->hasAttribute('uri')) { 231 | return $content->getAttribute('uri'); 232 | } 233 | return false; 234 | } 235 | 236 | return $this->parseTextConstruct($content); 237 | } 238 | 239 | /** 240 | * Extract content appropriately from atom text constructs 241 | * 242 | * Because of different rules applied to the content element and other text 243 | * constructs, they are deployed as separate functions, but they share quite 244 | * a bit of processing. This method performs the core common process, which is 245 | * to apply the rules for different mime types in order to extract the content. 246 | * 247 | * @param DOMNode $content the text construct node to be parsed 248 | * @return String 249 | * @author James Stewart 250 | **/ 251 | protected function parseTextConstruct(DOMNode $content) 252 | { 253 | if ($content->hasAttribute('type')) { 254 | $type = $content->getAttribute('type'); 255 | } else { 256 | $type = 'text'; 257 | } 258 | 259 | if (strpos($type, 'text/') === 0) { 260 | $type = 'text'; 261 | } 262 | 263 | switch ($type) { 264 | case 'text': 265 | case 'html': 266 | return $content->textContent; 267 | break; 268 | case 'xhtml': 269 | $container = $content->getElementsByTagName('div'); 270 | if ($container->length == 0) { 271 | return false; 272 | } 273 | $contents = $container->item(0); 274 | if ($contents->hasChildNodes()) { 275 | /* Iterate through, applying xml:base and store the result */ 276 | $result = ''; 277 | foreach ($contents->childNodes as $node) { 278 | $result .= $this->traverseNode($node); 279 | } 280 | return $result; 281 | } 282 | break; 283 | case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i', $type) > 0: 284 | return $content; 285 | break; 286 | case 'application/octet-stream': 287 | default: 288 | return base64_decode(trim($content->nodeValue)); 289 | break; 290 | } 291 | return false; 292 | } 293 | /** 294 | * Get a category from the entry. 295 | * 296 | * A feed or entry can have any number of categories. A category can have the 297 | * attributes term, scheme and label. 298 | * 299 | * @param string $method The name of the text construct we want 300 | * @param array $arguments An array which we hope gives a 'param' 301 | * @return string 302 | */ 303 | function getCategory($method, $arguments) 304 | { 305 | $offset = empty($arguments[0]) ? 0: $arguments[0]; 306 | $attribute = empty($arguments[1]) ? 'term' : $arguments[1]; 307 | $categories = $this->model->getElementsByTagName('category'); 308 | if ($categories->length <= $offset) { 309 | $category = $categories->item($offset); 310 | if ($category->hasAttribute($attribute)) { 311 | return $category->getAttribute($attribute); 312 | } 313 | } 314 | return false; 315 | } 316 | 317 | /** 318 | * This element must be present at least once with rel="feed". This element may be 319 | * present any number of further times so long as there is no clash. If no 'rel' is 320 | * present and we're asked for one, we follow the example of the Universal Feed 321 | * Parser and presume 'alternate'. 322 | * 323 | * @param int $offset the position of the link within the container 324 | * @param string $attribute the attribute name required 325 | * @param array an array of attributes to search by 326 | * @return string the value of the attribute 327 | */ 328 | function getLink($offset = 0, $attribute = 'href', $params = false) 329 | { 330 | if (is_array($params) and !empty($params)) { 331 | $terms = array(); 332 | $alt_predicate = ''; 333 | $other_predicate = ''; 334 | 335 | foreach ($params as $key => $value) { 336 | if ($key == 'rel' && $value == 'alternate') { 337 | $alt_predicate = '[not(@rel) or @rel="alternate"]'; 338 | } else { 339 | $terms[] = "@$key='$value'"; 340 | } 341 | } 342 | if (!empty($terms)) { 343 | $other_predicate = '[' . join(' and ', $terms) . ']'; 344 | } 345 | $query = $this->xpathPrefix . 'atom:link' . $alt_predicate . $other_predicate; 346 | $links = $this->xpath->query($query); 347 | } else { 348 | $links = $this->model->getElementsByTagName('link'); 349 | } 350 | if ($links->length > $offset) { 351 | if ($links->item($offset)->hasAttribute($attribute)) { 352 | $value = $links->item($offset)->getAttribute($attribute); 353 | if ($attribute == 'href') { 354 | $value = $this->addBase($value, $links->item($offset)); 355 | } 356 | return $value; 357 | } else if ($attribute == 'rel') { 358 | return 'alternate'; 359 | } 360 | } 361 | return false; 362 | } 363 | } 364 | 365 | ?> -------------------------------------------------------------------------------- /inc/sfYaml/sfYamlParser.class.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | require_once(dirname(__FILE__).'/sfYamlInline.class.php'); 12 | 13 | /** 14 | * sfYamlParser class. 15 | * 16 | * @package symfony 17 | * @subpackage util 18 | * @author Fabien Potencier 19 | * @version SVN: $Id: sfYamlParser.class.php 9769 2008-06-23 03:51:06Z dwhittle $ 20 | */ 21 | class sfYamlParser 22 | { 23 | protected 24 | $value = '', 25 | $offset = 0, 26 | $lines = array(), 27 | $currentLineNb = -1, 28 | $currentLine = '', 29 | $refs = array(); 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param integer The offset of YAML document (used for line numbers in error messages) 35 | */ 36 | public function __construct($offset = 0) 37 | { 38 | $this->offset = $offset; 39 | } 40 | 41 | /** 42 | * Parses a YAML string to a PHP value. 43 | * 44 | * @param string A YAML string 45 | * 46 | * @return mixed A PHP value 47 | */ 48 | public function parse($value) 49 | { 50 | $this->value = $this->cleanup($value); 51 | $this->currentLineNb = -1; 52 | $this->currentLine = ''; 53 | $this->lines = explode("\n", $this->value); 54 | 55 | $data = array(); 56 | while ($this->moveToNextLine()) 57 | { 58 | if ($this->isCurrentLineEmpty()) 59 | { 60 | continue; 61 | } 62 | 63 | // tab? 64 | if (preg_match('#^\t+#', $this->currentLine)) 65 | { 66 | throw new InvalidArgumentException(sprintf('A YAML file cannot contain tabs as indentation at line %d (%s).', $this->getRealCurrentLineNb(), $this->currentLine)); 67 | } 68 | 69 | $isRef = $isInPlace = $isProcessed = false; 70 | if (preg_match('#^\-(\s+(?P.+?))?\s*$#', $this->currentLine, $values)) 71 | { 72 | if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) 73 | { 74 | $isRef = $matches['ref']; 75 | $values['value'] = $matches['value']; 76 | } 77 | 78 | // array 79 | if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) 80 | { 81 | $c = $this->getRealCurrentLineNb() + 1; 82 | $parser = new sfYamlParser($c); 83 | $parser->refs =& $this->refs; 84 | $data[] = $parser->parse($this->getNextEmbedBlock()); 85 | } 86 | else 87 | { 88 | if (preg_match('/^([^ ]+)\: +({.*?)$/', $values['value'], $matches)) 89 | { 90 | $data[] = array($matches[1] => sfYamlInline::load($matches[2])); 91 | } 92 | else 93 | { 94 | $data[] = $this->parseValue($values['value']); 95 | } 96 | } 97 | } 98 | else if (preg_match('#^(?P[^ ].*?) *\:(\s+(?P.+?))?\s*$#', $this->currentLine, $values)) 99 | { 100 | $key = sfYamlInline::parseScalar($values['key']); 101 | 102 | if ('<<' === $key) 103 | { 104 | if (isset($values['value']) && '*' === substr($values['value'], 0, 1)) 105 | { 106 | $isInPlace = substr($values['value'], 1); 107 | if (!array_key_exists($isInPlace, $this->refs)) 108 | { 109 | throw new InvalidArgumentException(sprintf('Reference "%s" does not exist on line %s.', $isInPlace, $this->currentLine)); 110 | } 111 | } 112 | else 113 | { 114 | if (isset($values['value']) && $values['value'] !== '') 115 | { 116 | $value = $values['value']; 117 | } else { 118 | $value = $this->getNextEmbedBlock(); 119 | } 120 | $c = $this->getRealCurrentLineNb() + 1; 121 | $parser = new sfYamlParser($c); 122 | $parser->refs =& $this->refs; 123 | $parsed = $parser->parse($value); 124 | 125 | $merged = array(); 126 | if (!is_array($parsed)) 127 | { 128 | throw new InvalidArgumentException(sprintf("YAML merge keys used with a scalar value instead of an array on line %s", $this->currentLine)); 129 | } 130 | else if (isset($parsed[0])) 131 | { 132 | // Numeric array, merge individual elements 133 | foreach (array_reverse($parsed) as $parsedItem) 134 | { 135 | if (!is_array($parsedItem)) 136 | { 137 | throw new InvalidArgumentException(sprintf("Merge items must be arrays on line %s.", $parsedItem)); 138 | } 139 | $merged = array_merge($parsedItem, $merged); 140 | } 141 | } 142 | else 143 | { 144 | // Associative array, merge 145 | $merged = array_merge($merge, $parsed); 146 | } 147 | 148 | $isProcessed = $merged; 149 | } 150 | } 151 | else if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#', $values['value'], $matches)) 152 | { 153 | $isRef = $matches['ref']; 154 | $values['value'] = $matches['value']; 155 | } 156 | 157 | if ($isProcessed) 158 | { 159 | // Merge keys 160 | $data = $isProcessed; 161 | } 162 | // hash 163 | else if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) 164 | { 165 | // if next line is less indented or equal, then it means that the current value is null 166 | if ($this->isNextLineIndented()) 167 | { 168 | $data[$key] = null; 169 | } 170 | else 171 | { 172 | $c = $this->getRealCurrentLineNb() + 1; 173 | $parser = new sfYamlParser($c); 174 | $parser->refs =& $this->refs; 175 | $data[$key] = $parser->parse($this->getNextEmbedBlock()); 176 | } 177 | } 178 | else 179 | { 180 | if ($isInPlace) 181 | { 182 | $data = $this->refs[$isInPlace]; 183 | } 184 | else 185 | { 186 | $data[$key] = $this->parseValue($values['value']); 187 | } 188 | } 189 | } 190 | else 191 | { 192 | // one liner? 193 | if (1 == count(explode("\n", rtrim($this->value, "\n")))) 194 | { 195 | $value = sfYamlInline::load($this->lines[0]); 196 | if (is_array($value)) { 197 | $first = reset($value); 198 | if ('*' === substr($first, 0, 1)) { 199 | $data = array(); 200 | foreach ($value as $alias) { 201 | $data[] = $this->refs[substr($alias, 1)]; 202 | } 203 | $value = $data; 204 | } 205 | } 206 | return $value; 207 | } 208 | 209 | throw new InvalidArgumentException(sprintf('Unable to parse line %d (%s).', $this->getRealCurrentLineNb(), $this->currentLine)); 210 | } 211 | 212 | if ($isRef) 213 | { 214 | $this->refs[$isRef] = end($data); 215 | } 216 | } 217 | 218 | return empty($data) ? null : $data; 219 | } 220 | 221 | /** 222 | * Returns the current line number (takes the offset into account). 223 | * 224 | * @return integer The current line number 225 | */ 226 | protected function getRealCurrentLineNb() 227 | { 228 | return $this->currentLineNb + $this->offset; 229 | } 230 | 231 | /** 232 | * Returns the current line indentation. 233 | * 234 | * @returns integer The current line indentation 235 | */ 236 | protected function getCurrentLineIndentation() 237 | { 238 | return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); 239 | } 240 | 241 | /** 242 | * Returns the next embed block of YAML. 243 | * 244 | * @param string A YAML string 245 | */ 246 | protected function getNextEmbedBlock() 247 | { 248 | $this->moveToNextLine(); 249 | 250 | $newIndent = $this->getCurrentLineIndentation(); 251 | 252 | if (!$this->isCurrentLineEmpty() && 0 == $newIndent) 253 | { 254 | throw new InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb(), $this->currentLine)); 255 | } 256 | 257 | $data = array(substr($this->currentLine, $newIndent)); 258 | 259 | while ($this->moveToNextLine()) 260 | { 261 | if ($this->isCurrentLineEmpty()) 262 | { 263 | if ($this->isCurrentLineBlank()) 264 | { 265 | $data[] = substr($this->currentLine, $newIndent); 266 | } 267 | 268 | continue; 269 | } 270 | 271 | $indent = $this->getCurrentLineIndentation(); 272 | 273 | if (preg_match('#^(?P *)$#', $this->currentLine, $match)) 274 | { 275 | // empty line 276 | $data[] = $match['text']; 277 | } 278 | else if ($indent >= $newIndent) 279 | { 280 | $data[] = substr($this->currentLine, $newIndent); 281 | } 282 | else if (0 == $indent) 283 | { 284 | $this->moveToPreviousLine(); 285 | 286 | break; 287 | } 288 | else 289 | { 290 | throw new InvalidArgumentException(sprintf('Indentation problem at line %d (%s)', $this->getRealCurrentLineNb(), $this->currentLine)); 291 | } 292 | } 293 | 294 | return implode("\n", $data); 295 | } 296 | 297 | /** 298 | * Moves the parser to the next line. 299 | */ 300 | protected function moveToNextLine() 301 | { 302 | if ($this->currentLineNb >= count($this->lines) - 1) 303 | { 304 | return false; 305 | } 306 | 307 | $this->currentLine = $this->lines[++$this->currentLineNb]; 308 | 309 | return true; 310 | } 311 | 312 | /** 313 | * Moves the parser to the previous line. 314 | */ 315 | protected function moveToPreviousLine() 316 | { 317 | $this->currentLine = $this->lines[--$this->currentLineNb]; 318 | } 319 | 320 | /** 321 | * Parses a YAML value. 322 | * 323 | * @param string A YAML value 324 | * 325 | * @return mixed A PHP value 326 | */ 327 | protected function parseValue($value) 328 | { 329 | if ('*' === substr($value, 0, 1)) 330 | { 331 | if (false !== $pos = strpos($value, '#')) 332 | { 333 | $value = substr($value, 1, $pos - 2); 334 | } 335 | else 336 | { 337 | $value = substr($value, 1); 338 | } 339 | 340 | if (!array_key_exists($value, $this->refs)) 341 | { 342 | throw new InvalidArgumentException(sprintf('Reference "%s" does not exist (%s).', $value, $this->currentLine)); 343 | } 344 | return $this->refs[$value]; 345 | } 346 | 347 | if (preg_match('/^(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?$/', $value, $matches)) 348 | { 349 | $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; 350 | 351 | return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); 352 | } 353 | else 354 | { 355 | return sfYamlInline::load($value); 356 | } 357 | } 358 | 359 | /** 360 | * Parses a folded scalar. 361 | * 362 | * @param string The separator that was used to begin this folded scalar (| or >) 363 | * @param string The indicator that was used to begin this folded scalar (+ or -) 364 | * @param integer The indentation that was used to begin this folded scalar 365 | * 366 | * @return string The text value 367 | */ 368 | protected function parseFoldedScalar($separator, $indicator = '', $indentation = 0) 369 | { 370 | $separator = '|' == $separator ? "\n" : ' '; 371 | $text = ''; 372 | 373 | $notEOF = $this->moveToNextLine(); 374 | 375 | while ($notEOF && $this->isCurrentLineBlank()) 376 | { 377 | $text .= "\n"; 378 | 379 | $notEOF = $this->moveToNextLine(); 380 | } 381 | 382 | if (!$notEOF) 383 | { 384 | return ''; 385 | } 386 | 387 | if (!preg_match('#^(?P'.($indentation ? str_repeat(' ', $indentation) : ' +').')(?P.*)$#', $this->currentLine, $matches)) 388 | { 389 | $this->moveToPreviousLine(); 390 | 391 | return ''; 392 | } 393 | 394 | $textIndent = $matches['indent']; 395 | $previousIndent = 0; 396 | 397 | $text .= $matches['text'].$separator; 398 | while ($this->currentLineNb + 1 < count($this->lines)) 399 | { 400 | $this->moveToNextLine(); 401 | 402 | if (preg_match('#^(?P {'.strlen($textIndent).',})(?P.+)$#', $this->currentLine, $matches)) 403 | { 404 | if (' ' == $separator && $previousIndent != $matches['indent']) 405 | { 406 | $text = substr($text, 0, -1)."\n"; 407 | } 408 | $previousIndent = $matches['indent']; 409 | 410 | $text .= str_repeat(' ', $diff = strlen($matches['indent']) - strlen($textIndent)).$matches['text'].($diff ? "\n" : $separator); 411 | } 412 | else if (preg_match('#^(?P *)$#', $this->currentLine, $matches)) 413 | { 414 | $text .= preg_replace('#^ {1,'.strlen($textIndent).'}#', '', $matches['text'])."\n"; 415 | } 416 | else 417 | { 418 | $this->moveToPreviousLine(); 419 | 420 | break; 421 | } 422 | } 423 | 424 | if (' ' == $separator) 425 | { 426 | // replace last separator by a newline 427 | $text = preg_replace('/ (\n*)$/', "\n$1", $text); 428 | } 429 | 430 | switch ($indicator) 431 | { 432 | case '': 433 | $text = preg_replace('#\n+$#s', "\n", $text); 434 | break; 435 | case '+': 436 | break; 437 | case '-': 438 | $text = preg_replace('#\n+$#s', '', $text); 439 | break; 440 | } 441 | 442 | return $text; 443 | } 444 | 445 | /** 446 | * Returns true if the next line is indented. 447 | * 448 | * @return Boolean Returns true if the next line is indented, false otherwise 449 | */ 450 | protected function isNextLineIndented() 451 | { 452 | $currentIndentation = $this->getCurrentLineIndentation(); 453 | $notEOF = $this->moveToNextLine(); 454 | 455 | while ($notEOF && $this->isCurrentLineEmpty()) 456 | { 457 | $notEOF = $this->moveToNextLine(); 458 | } 459 | 460 | if (false === $notEOF) 461 | { 462 | return false; 463 | } 464 | 465 | $ret = false; 466 | if ($this->getCurrentLineIndentation() <= $currentIndentation) 467 | { 468 | $ret = true; 469 | } 470 | 471 | $this->moveToPreviousLine(); 472 | 473 | return $ret; 474 | } 475 | 476 | /** 477 | * Returns true if the current line is blank or if it is a comment line. 478 | * 479 | * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise 480 | */ 481 | protected function isCurrentLineEmpty() 482 | { 483 | return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); 484 | } 485 | 486 | /** 487 | * Returns true if the current line is blank. 488 | * 489 | * @return Boolean Returns true if the current line is blank, false otherwise 490 | */ 491 | protected function isCurrentLineBlank() 492 | { 493 | return '' == trim($this->currentLine, ' '); 494 | } 495 | 496 | /** 497 | * Returns true if the current line is a comment line. 498 | * 499 | * @return Boolean Returns true if the current line is a comment line, false otherwise 500 | */ 501 | protected function isCurrentLineComment() 502 | { 503 | return 0 === strpos(ltrim($this->currentLine, ' '), '#'); 504 | } 505 | 506 | /** 507 | * Cleanups a YAML string to be parsed. 508 | * 509 | * @param string The input YAML string 510 | * 511 | * @return string A cleaned up YAML string 512 | */ 513 | protected function cleanup($value) 514 | { 515 | $value = str_replace(array("\r\n", "\r"), "\n", $value); 516 | 517 | if (!preg_match("#\n$#", $value)) 518 | { 519 | $value .= "\n"; 520 | } 521 | 522 | // strip YAML header 523 | preg_replace('#^\%YAML[: ][\d\.]+.*\n#s', '', $value); 524 | 525 | // remove --- 526 | $value = preg_replace('#^\-\-\-.*?\n#s', '', $value); 527 | 528 | return $value; 529 | } 530 | } 531 | --------------------------------------------------------------------------------