diff --git a/oss/piwik/trunk/libs/Zend/Feed/Abstract.php b/oss/piwik/trunk/libs/Zend/Feed/Abstract.php new file mode 100644 index 00000000..8cd77dee --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Abstract.php @@ -0,0 +1,259 @@ +setUri($uri); + $response = $client->request('GET'); + if ($response->getStatus() !== 200) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Feed failed to load, got response code ' . $response->getStatus()); + } + $this->_element = $response->getBody(); + $this->__wakeup(); + } elseif ($string !== null) { + // Retrieve the feed from $string + $this->_element = $string; + $this->__wakeup(); + } else { + // Generate the feed from the array + $header = $builder->getHeader(); + $this->_element = new DOMDocument('1.0', $header['charset']); + $root = $this->_mapFeedHeaders($header); + $this->_mapFeedEntries($root, $builder->getEntries()); + $this->_element = $root; + $this->_buildEntryCache(); + } + } + + + /** + * Load the feed as an XML DOMDocument object + * + * @return void + * @throws Zend_Feed_Exception + */ + public function __wakeup() + { + @ini_set('track_errors', 1); + $doc = new DOMDocument; + $status = @$doc->loadXML($this->_element); + @ini_restore('track_errors'); + + if (!$status) { + // prevent the class to generate an undefined variable notice (ZF-2590) + if (!isset($php_errormsg)) { + if (function_exists('xdebug_is_enabled')) { + $php_errormsg = '(error message not available, when XDebug is running)'; + } else { + $php_errormsg = '(error message not available)'; + } + } + + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception("DOMDocument cannot parse XML: $php_errormsg"); + } + + $this->_element = $doc; + } + + + /** + * Prepare for serialiation + * + * @return array + */ + public function __sleep() + { + $this->_element = $this->saveXML(); + + return array('_element'); + } + + + /** + * Cache the individual feed elements so they don't need to be + * searched for on every operation. + * + * @return void + */ + protected function _buildEntryCache() + { + $this->_entries = array(); + foreach ($this->_element->childNodes as $child) { + if ($child->localName == $this->_entryElementName) { + $this->_entries[] = $child; + } + } + } + + + /** + * Get the number of entries in this feed object. + * + * @return integer Entry count. + */ + public function count() + { + return count($this->_entries); + } + + + /** + * Required by the Iterator interface. + * + * @return void + */ + public function rewind() + { + $this->_entryIndex = 0; + } + + + /** + * Required by the Iterator interface. + * + * @return mixed The current row, or null if no rows. + */ + public function current() + { + return new $this->_entryClassName( + null, + $this->_entries[$this->_entryIndex]); + } + + + /** + * Required by the Iterator interface. + * + * @return mixed The current row number (starts at 0), or NULL if no rows + */ + public function key() + { + return $this->_entryIndex; + } + + + /** + * Required by the Iterator interface. + * + * @return mixed The next row, or null if no more rows. + */ + public function next() + { + ++$this->_entryIndex; + } + + + /** + * Required by the Iterator interface. + * + * @return boolean Whether the iteration is valid + */ + public function valid() + { + return 0 <= $this->_entryIndex && $this->_entryIndex < $this->count(); + } + + /** + * Generate the header of the feed when working in write mode + * + * @param array $array the data to use + * @return DOMElement root node + */ + abstract protected function _mapFeedHeaders($array); + + /** + * Generate the entries of the feed when working in write mode + * + * @param DOMElement $root the root node to use + * @param array $array the data to use + * @return DOMElement root node + */ + abstract protected function _mapFeedEntries(DOMElement $root, $array); + + /** + * Send feed to a http client with the correct header + * + * @throws Zend_Feed_Exception if headers have already been sent + * @return void + */ + abstract public function send(); +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Atom.php b/oss/piwik/trunk/libs/Zend/Feed/Atom.php new file mode 100644 index 00000000..8a723f86 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Atom.php @@ -0,0 +1,390 @@ + + * elements). + * + * @var string + */ + protected $_entryElementName = 'entry'; + + /** + * The default namespace for Atom feeds. + * + * @var string + */ + protected $_defaultNamespace = 'atom'; + + + /** + * Override Zend_Feed_Abstract to set up the $_element and $_entries aliases. + * + * @return void + * @throws Zend_Feed_Exception + */ + public function __wakeup() + { + parent::__wakeup(); + + // Find the base feed element and create an alias to it. + $element = $this->_element->getElementsByTagName('feed')->item(0); + if (!$element) { + // Try to find a single instead. + $element = $this->_element->getElementsByTagName($this->_entryElementName)->item(0); + if (!$element) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('No root or <' . $this->_entryElementName + . '> element found, cannot parse feed.'); + } + + $doc = new DOMDocument($this->_element->version, + $this->_element->actualEncoding); + $feed = $doc->appendChild($doc->createElement('feed')); + $feed->appendChild($doc->importNode($element, true)); + $element = $feed; + } + + $this->_element = $element; + + // Find the entries and save a pointer to them for speed and + // simplicity. + $this->_buildEntryCache(); + } + + + /** + * Easy access to tags keyed by "rel" attributes. + * + * If $elt->link() is called with no arguments, we will attempt to + * return the value of the tag(s) like all other + * method-syntax attribute access. If an argument is passed to + * link(), however, then we will return the "href" value of the + * first tag that has a "rel" attribute matching $rel: + * + * $elt->link(): returns the value of the link tag. + * $elt->link('self'): returns the href from the first in the entry. + * + * @param string $rel The "rel" attribute to look for. + * @return mixed + */ + public function link($rel = null) + { + if ($rel === null) { + return parent::__call('link', null); + } + + // index link tags by their "rel" attribute. + $links = parent::__get('link'); + if (!is_array($links)) { + if ($links instanceof Zend_Feed_Element) { + $links = array($links); + } else { + return $links; + } + } + + foreach ($links as $link) { + if (empty($link['rel'])) { + continue; + } + if ($rel == $link['rel']) { + return $link['href']; + } + } + + return null; + } + + + /** + * Make accessing some individual elements of the feed easier. + * + * Special accessors 'entry' and 'entries' are provided so that if + * you wish to iterate over an Atom feed's entries, you can do so + * using foreach ($feed->entries as $entry) or foreach + * ($feed->entry as $entry). + * + * @param string $var The property to access. + * @return mixed + */ + public function __get($var) + { + switch ($var) { + case 'entry': + // fall through to the next case + case 'entries': + return $this; + + default: + return parent::__get($var); + } + } + + /** + * Generate the header of the feed when working in write mode + * + * @param array $array the data to use + * @return DOMElement root node + */ + protected function _mapFeedHeaders($array) + { + $feed = $this->_element->createElement('feed'); + $feed->setAttribute('xmlns', 'http://www.w3.org/2005/Atom'); + + $id = $this->_element->createElement('id', $array->link); + $feed->appendChild($id); + + $title = $this->_element->createElement('title'); + $title->appendChild($this->_element->createCDATASection($array->title)); + $feed->appendChild($title); + + if (isset($array->author)) { + $author = $this->_element->createElement('author'); + $name = $this->_element->createElement('name', $array->author); + $author->appendChild($name); + if (isset($array->email)) { + $email = $this->_element->createElement('email', $array->email); + $author->appendChild($email); + } + $feed->appendChild($author); + } + + $updated = isset($array->lastUpdate) ? $array->lastUpdate : time(); + $updated = $this->_element->createElement('updated', date(DATE_ATOM, $updated)); + $feed->appendChild($updated); + + if (isset($array->published)) { + $published = $this->_element->createElement('published', date(DATE_ATOM, $array->published)); + $feed->appendChild($published); + } + + $link = $this->_element->createElement('link'); + $link->setAttribute('rel', 'self'); + $link->setAttribute('href', $array->link); + if (isset($array->language)) { + $link->setAttribute('hreflang', $array->language); + } + $feed->appendChild($link); + + if (isset($array->description)) { + $subtitle = $this->_element->createElement('subtitle'); + $subtitle->appendChild($this->_element->createCDATASection($array->description)); + $feed->appendChild($subtitle); + } + + if (isset($array->copyright)) { + $copyright = $this->_element->createElement('rights', $array->copyright); + $feed->appendChild($copyright); + } + + if (isset($array->image)) { + $image = $this->_element->createElement('logo', $array->image); + $feed->appendChild($image); + } + + $generator = !empty($array->generator) ? $array->generator : 'Zend_Feed'; + $generator = $this->_element->createElement('generator', $generator); + $feed->appendChild($generator); + + return $feed; + } + + /** + * Generate the entries of the feed when working in write mode + * + * The following nodes are constructed for each feed entry + * + * url to feed entry + * entry title + * last update + * + * short text + * long version, can contain html + * + * + * @param array $array the data to use + * @param DOMElement $root the root node to use + * @return void + */ + protected function _mapFeedEntries(DOMElement $root, $array) + { + foreach ($array as $dataentry) { + $entry = $this->_element->createElement('entry'); + + $id = $this->_element->createElement('id', isset($dataentry->guid) ? $dataentry->guid : $dataentry->link); + $entry->appendChild($id); + + $title = $this->_element->createElement('title'); + $title->appendChild($this->_element->createCDATASection($dataentry->title)); + $entry->appendChild($title); + + $updated = isset($dataentry->lastUpdate) ? $dataentry->lastUpdate : time(); + $updated = $this->_element->createElement('updated', date(DATE_ATOM, $updated)); + $entry->appendChild($updated); + + $link = $this->_element->createElement('link'); + $link->setAttribute('rel', 'alternate'); + $link->setAttribute('href', $dataentry->link); + $entry->appendChild($link); + + $summary = $this->_element->createElement('summary'); + $summary->appendChild($this->_element->createCDATASection($dataentry->description)); + $entry->appendChild($summary); + + if (isset($dataentry->content)) { + $content = $this->_element->createElement('content'); + $content->setAttribute('type', 'html'); + $content->appendChild($this->_element->createCDATASection($dataentry->content)); + $entry->appendChild($content); + } + + if (isset($dataentry->category)) { + foreach ($dataentry->category as $category) { + $node = $this->_element->createElement('category'); + $node->setAttribute('term', $category['term']); + if (isset($category['scheme'])) { + $node->setAttribute('scheme', $category['scheme']); + } + $entry->appendChild($node); + } + } + + if (isset($dataentry->source)) { + $source = $this->_element->createElement('source'); + $title = $this->_element->createElement('title', $dataentry->source['title']); + $source->appendChild($title); + $link = $this->_element->createElement('link', $dataentry->source['title']); + $link->setAttribute('rel', 'alternate'); + $link->setAttribute('href', $dataentry->source['url']); + $source->appendChild($link); + } + + if (isset($dataentry->enclosure)) { + foreach ($dataentry->enclosure as $enclosure) { + $node = $this->_element->createElement('link'); + $node->setAttribute('rel', 'enclosure'); + $node->setAttribute('href', $enclosure['url']); + if (isset($enclosure['type'])) { + $node->setAttribute('type', $enclosure['type']); + } + if (isset($enclosure['length'])) { + $node->setAttribute('length', $enclosure['length']); + } + $entry->appendChild($node); + } + } + + if (isset($dataentry->comments)) { + $comments = $this->_element->createElementNS('http://wellformedweb.org/CommentAPI/', + 'wfw:comment', + $dataentry->comments); + $entry->appendChild($comments); + } + if (isset($dataentry->commentRss)) { + $comments = $this->_element->createElementNS('http://wellformedweb.org/CommentAPI/', + 'wfw:commentRss', + $dataentry->commentRss); + $entry->appendChild($comments); + } + + $root->appendChild($entry); + } + } + + /** + * Override Zend_Feed_Element to allow formated feeds + * + * @return string + */ + public function saveXml() + { + // Return a complete document including XML prologue. + $doc = new DOMDocument($this->_element->ownerDocument->version, + $this->_element->ownerDocument->actualEncoding); + $doc->appendChild($doc->importNode($this->_element, true)); + $doc->formatOutput = true; + + return $doc->saveXML(); + } + + /** + * Send feed to a http client with the correct header + * + * @return void + * @throws Zend_Feed_Exception if headers have already been sent + */ + public function send() + { + if (headers_sent()) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Cannot send ATOM because headers have already been sent.'); + } + + header('Content-Type: application/atom+xml; charset=' . $this->_element->ownerDocument->actualEncoding); + + echo $this->saveXML(); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Builder.php b/oss/piwik/trunk/libs/Zend/Feed/Builder.php new file mode 100644 index 00000000..a9517ce5 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Builder.php @@ -0,0 +1,398 @@ + + * array( + * 'title' => 'title of the feed', //required + * 'link' => 'canonical url to the feed', //required + * 'lastUpdate' => 'timestamp of the update date', // optional + * 'published' => 'timestamp of the publication date', //optional + * 'charset' => 'charset', // required + * 'description' => 'short description of the feed', //optional + * 'author' => 'author/publisher of the feed', //optional + * 'email' => 'email of the author', //optional + * 'webmaster' => 'email address for person responsible for technical issues' // optional, ignored if atom is used + * 'copyright' => 'copyright notice', //optional + * 'image' => 'url to image', //optional + * 'generator' => 'generator', // optional + * 'language' => 'language the feed is written in', // optional + * 'ttl' => 'how long in minutes a feed can be cached before refreshing', // optional, ignored if atom is used + * 'rating' => 'The PICS rating for the channel.', // optional, ignored if atom is used + * 'cloud' => array( + * 'domain' => 'domain of the cloud, e.g. rpc.sys.com' // required + * 'port' => 'port to connect to' // optional, default to 80 + * 'path' => 'path of the cloud, e.g. /RPC2 //required + * 'registerProcedure' => 'procedure to call, e.g. myCloud.rssPleaseNotify' // required + * 'protocol' => 'protocol to use, e.g. soap or xml-rpc' // required + * ), a cloud to be notified of updates // optional, ignored if atom is used + * 'textInput' => array( + * 'title' => 'the label of the Submit button in the text input area' // required, + * 'description' => 'explains the text input area' // required + * 'name' => 'the name of the text object in the text input area' // required + * 'link' => 'the URL of the CGI script that processes text input requests' // required + * ) // a text input box that can be displayed with the feed // optional, ignored if atom is used + * 'skipHours' => array( + * 'hour in 24 format', // e.g 13 (1pm) + * // up to 24 rows whose value is a number between 0 and 23 + * ) // Hint telling aggregators which hours they can skip // optional, ignored if atom is used + * 'skipDays ' => array( + * 'a day to skip', // e.g Monday + * // up to 7 rows whose value is a Monday, Tuesday, Wednesday, Thursday, Friday, Saturday or Sunday + * ) // Hint telling aggregators which days they can skip // optional, ignored if atom is used + * 'itunes' => array( + * 'author' => 'Artist column' // optional, default to the main author value + * 'owner' => array( + * 'name' => 'name of the owner' // optional, default to main author value + * 'email' => 'email of the owner' // optional, default to main email value + * ) // Owner of the podcast // optional + * 'image' => 'album/podcast art' // optional, default to the main image value + * 'subtitle' => 'short description' // optional, default to the main description value + * 'summary' => 'longer description' // optional, default to the main description value + * 'block' => 'Prevent an episode from appearing (yes|no)' // optional + * 'category' => array( + * array('main' => 'main category', // required + * 'sub' => 'sub category' // optional + * ), + * // up to 3 rows + * ) // 'Category column and in iTunes Music Store Browse' // required + * 'explicit' => 'parental advisory graphic (yes|no|clean)' // optional + * 'keywords' => 'a comma separated list of 12 keywords maximum' // optional + * 'new-feed-url' => 'used to inform iTunes of new feed URL location' // optional + * ) // Itunes extension data // optional, ignored if atom is used + * 'entries' => array( + * array( + * 'title' => 'title of the feed entry', //required + * 'link' => 'url to a feed entry', //required + * 'description' => 'short version of a feed entry', // only text, no html, required + * 'guid' => 'id of the article, if not given link value will used', //optional + * 'content' => 'long version', // can contain html, optional + * 'lastUpdate' => 'timestamp of the publication date', // optional + * 'comments' => 'comments page of the feed entry', // optional + * 'commentRss' => 'the feed url of the associated comments', // optional + * 'source' => array( + * 'title' => 'title of the original source' // required, + * 'url' => 'url of the original source' // required + * ) // original source of the feed entry // optional + * 'category' => array( + * array( + * 'term' => 'first category label' // required, + * 'scheme' => 'url that identifies a categorization scheme' // optional + * ), + * array( + * //data for the second category and so on + * ) + * ) // list of the attached categories // optional + * 'enclosure' => array( + * array( + * 'url' => 'url of the linked enclosure' // required + * 'type' => 'mime type of the enclosure' // optional + * 'length' => 'length of the linked content in octets' // optional + * ), + * array( + * //data for the second enclosure and so on + * ) + * ) // list of the enclosures of the feed entry // optional + * ), + * array( + * //data for the second entry and so on + * ) + * ) + * ); + * + * + * @param array $data + * @return void + */ + public function __construct(array $data) + { + $this->_data = $data; + $this->_createHeader($data); + if (isset($data['entries'])) { + $this->_createEntries($data['entries']); + } + } + + /** + * Returns an instance of Zend_Feed_Builder_Header + * describing the header of the feed + * + * @return Zend_Feed_Builder_Header + */ + public function getHeader() + { + return $this->_header; + } + + /** + * Returns an array of Zend_Feed_Builder_Entry instances + * describing the entries of the feed + * + * @return array of Zend_Feed_Builder_Entry + */ + public function getEntries() + { + return $this->_entries; + } + + /** + * Create the Zend_Feed_Builder_Header instance + * + * @param array $data + * @throws Zend_Feed_Builder_Exception + * @return void + */ + protected function _createHeader(array $data) + { + $mandatories = array('title', 'link', 'charset'); + foreach ($mandatories as $mandatory) { + if (!isset($data[$mandatory])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("$mandatory key is missing"); + } + } + $this->_header = new Zend_Feed_Builder_Header($data['title'], $data['link'], $data['charset']); + if (isset($data['lastUpdate'])) { + $this->_header->setLastUpdate($data['lastUpdate']); + } + if (isset($data['published'])) { + $this->_header->setPublishedDate($data['published']); + } + if (isset($data['description'])) { + $this->_header->setDescription($data['description']); + } + if (isset($data['author'])) { + $this->_header->setAuthor($data['author']); + } + if (isset($data['email'])) { + $this->_header->setEmail($data['email']); + } + if (isset($data['webmaster'])) { + $this->_header->setWebmaster($data['webmaster']); + } + if (isset($data['copyright'])) { + $this->_header->setCopyright($data['copyright']); + } + if (isset($data['image'])) { + $this->_header->setImage($data['image']); + } + if (isset($data['generator'])) { + $this->_header->setGenerator($data['generator']); + } + if (isset($data['language'])) { + $this->_header->setLanguage($data['language']); + } + if (isset($data['ttl'])) { + $this->_header->setTtl($data['ttl']); + } + if (isset($data['rating'])) { + $this->_header->setRating($data['rating']); + } + if (isset($data['cloud'])) { + $mandatories = array('domain', 'path', 'registerProcedure', 'protocol'); + foreach ($mandatories as $mandatory) { + if (!isset($data['cloud'][$mandatory])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to define $mandatory property of your cloud"); + } + } + $uri_str = 'http://' . $data['cloud']['domain'] . $data['cloud']['path']; + $this->_header->setCloud($uri_str, $data['cloud']['registerProcedure'], $data['cloud']['protocol']); + } + if (isset($data['textInput'])) { + $mandatories = array('title', 'description', 'name', 'link'); + foreach ($mandatories as $mandatory) { + if (!isset($data['textInput'][$mandatory])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to define $mandatory property of your textInput"); + } + } + $this->_header->setTextInput($data['textInput']['title'], + $data['textInput']['description'], + $data['textInput']['name'], + $data['textInput']['link']); + } + if (isset($data['skipHours'])) { + $this->_header->setSkipHours($data['skipHours']); + } + if (isset($data['skipDays'])) { + $this->_header->setSkipDays($data['skipDays']); + } + if (isset($data['itunes'])) { + $itunes = new Zend_Feed_Builder_Header_Itunes($data['itunes']['category']); + if (isset($data['itunes']['author'])) { + $itunes->setAuthor($data['itunes']['author']); + } + if (isset($data['itunes']['owner'])) { + $name = isset($data['itunes']['owner']['name']) ? $data['itunes']['owner']['name'] : ''; + $email = isset($data['itunes']['owner']['email']) ? $data['itunes']['owner']['email'] : ''; + $itunes->setOwner($name, $email); + } + if (isset($data['itunes']['image'])) { + $itunes->setImage($data['itunes']['image']); + } + if (isset($data['itunes']['subtitle'])) { + $itunes->setSubtitle($data['itunes']['subtitle']); + } + if (isset($data['itunes']['summary'])) { + $itunes->setSummary($data['itunes']['summary']); + } + if (isset($data['itunes']['block'])) { + $itunes->setBlock($data['itunes']['block']); + } + if (isset($data['itunes']['explicit'])) { + $itunes->setExplicit($data['itunes']['explicit']); + } + if (isset($data['itunes']['keywords'])) { + $itunes->setKeywords($data['itunes']['keywords']); + } + if (isset($data['itunes']['new-feed-url'])) { + $itunes->setNewFeedUrl($data['itunes']['new-feed-url']); + } + + $this->_header->setITunes($itunes); + } + } + + /** + * Create the array of article entries + * + * @param array $data + * @throws Zend_Feed_Builder_Exception + * @return void + */ + protected function _createEntries(array $data) + { + foreach ($data as $row) { + $mandatories = array('title', 'link', 'description'); + foreach ($mandatories as $mandatory) { + if (!isset($row[$mandatory])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("$mandatory key is missing"); + } + } + $entry = new Zend_Feed_Builder_Entry($row['title'], $row['link'], $row['description']); + if (isset($row['author'])) { + $entry->setAuthor($row['author']); + } + if (isset($row['guid'])) { + $entry->setId($row['guid']); + } + if (isset($row['content'])) { + $entry->setContent($row['content']); + } + if (isset($row['lastUpdate'])) { + $entry->setLastUpdate($row['lastUpdate']); + } + if (isset($row['comments'])) { + $entry->setCommentsUrl($row['comments']); + } + if (isset($row['commentRss'])) { + $entry->setCommentsRssUrl($row['commentRss']); + } + if (isset($row['source'])) { + $mandatories = array('title', 'url'); + foreach ($mandatories as $mandatory) { + if (!isset($row['source'][$mandatory])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("$mandatory key of source property is missing"); + } + } + $entry->setSource($row['source']['title'], $row['source']['url']); + } + if (isset($row['category'])) { + $entry->setCategories($row['category']); + } + if (isset($row['enclosure'])) { + $entry->setEnclosures($row['enclosure']); + } + + $this->_entries[] = $entry; + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Builder/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Builder/Entry.php new file mode 100644 index 00000000..6e7edc67 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Builder/Entry.php @@ -0,0 +1,297 @@ +offsetSet('title', $title); + $this->offsetSet('link', $link); + $this->offsetSet('description', $description); + $this->setLastUpdate(time()); + } + + /** + * Read only properties accessor + * + * @param string $name property to read + * @return mixed + */ + public function __get($name) + { + if (!$this->offsetExists($name)) { + return NULL; + } + + return $this->offsetGet($name); + } + + /** + * Write properties accessor + * + * @param string $name name of the property to set + * @param mixed $value value to set + * @return void + */ + public function __set($name, $value) + { + $this->offsetSet($name, $value); + } + + /** + * Isset accessor + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Unset accessor + * + * @param string $key + * @return void + */ + public function __unset($key) + { + if ($this->offsetExists($key)) { + $this->offsetUnset($key); + } + } + + /** + * Sets the author of the entry + * + * @param string $author + * @return Zend_Feed_Builder_Entry + */ + public function setAuthor($author) + { + $this->offsetSet('author', $author); + return $this; + } + + /** + * Sets the id/guid of the entry + * + * @param string $id + * @return Zend_Feed_Builder_Entry + */ + public function setId($id) + { + $this->offsetSet('guid', $id); + return $this; + } + + /** + * Sets the full html content of the entry + * + * @param string $content + * @return Zend_Feed_Builder_Entry + */ + public function setContent($content) + { + $this->offsetSet('content', $content); + return $this; + } + + /** + * Timestamp of the update date + * + * @param int $lastUpdate + * @return Zend_Feed_Builder_Entry + */ + public function setLastUpdate($lastUpdate) + { + $this->offsetSet('lastUpdate', $lastUpdate); + return $this; + } + + /** + * Sets the url of the commented page associated to the entry + * + * @param string $comments + * @return Zend_Feed_Builder_Entry + */ + public function setCommentsUrl($comments) + { + $this->offsetSet('comments', $comments); + return $this; + } + + /** + * Sets the url of the comments feed link + * + * @param string $commentRss + * @return Zend_Feed_Builder_Entry + */ + public function setCommentsRssUrl($commentRss) + { + $this->offsetSet('commentRss', $commentRss); + return $this; + } + + /** + * Defines a reference to the original source + * + * @param string $title + * @param string $url + * @return Zend_Feed_Builder_Entry + */ + public function setSource($title, $url) + { + $this->offsetSet('source', array('title' => $title, + 'url' => $url)); + return $this; + } + + /** + * Sets the categories of the entry + * Format of the array: + * + * array( + * array( + * 'term' => 'first category label', + * 'scheme' => 'url that identifies a categorization scheme' // optional + * ), + * // second category and so one + * ) + * + * + * @param array $categories + * @return Zend_Feed_Builder_Entry + */ + public function setCategories(array $categories) + { + foreach ($categories as $category) { + $this->addCategory($category); + } + return $this; + } + + /** + * Add a category to the entry + * + * @param array $category see Zend_Feed_Builder_Entry::setCategories() for format + * @return Zend_Feed_Builder_Entry + * @throws Zend_Feed_Builder_Exception + */ + public function addCategory(array $category) + { + if (empty($category['term'])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to define the name of the category"); + } + + if (!$this->offsetExists('category')) { + $categories = array($category); + } else { + $categories = $this->offsetGet('category'); + $categories[] = $category; + } + $this->offsetSet('category', $categories); + return $this; + } + + /** + * Sets the enclosures of the entry + * Format of the array: + * + * array( + * array( + * 'url' => 'url of the linked enclosure', + * 'type' => 'mime type of the enclosure' // optional + * 'length' => 'length of the linked content in octets' // optional + * ), + * // second enclosure and so one + * ) + * + * + * @param array $enclosures + * @return Zend_Feed_Builder_Entry + * @throws Zend_Feed_Builder_Exception + */ + public function setEnclosures(array $enclosures) + { + foreach ($enclosures as $enclosure) { + if (empty($enclosure['url'])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to supply an url for your enclosure"); + } + $type = isset($enclosure['type']) ? $enclosure['type'] : ''; + $length = isset($enclosure['length']) ? $enclosure['length'] : ''; + $this->addEnclosure($enclosure['url'], $type, $length); + } + return $this; + } + + /** + * Add an enclosure to the entry + * + * @param string $url + * @param string $type + * @param string $length + * @return Zend_Feed_Builder_Entry + */ + public function addEnclosure($url, $type = '', $length = '') + { + if (!$this->offsetExists('enclosure')) { + $enclosure = array(); + } else { + $enclosure = $this->offsetGet('enclosure'); + } + $enclosure[] = array('url' => $url, + 'type' => $type, + 'length' => $length); + $this->offsetSet('enclosure', $enclosure); + return $this; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Builder/Exception.php b/oss/piwik/trunk/libs/Zend/Feed/Builder/Exception.php new file mode 100644 index 00000000..9037cc89 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Builder/Exception.php @@ -0,0 +1,40 @@ +offsetSet('title', $title); + $this->offsetSet('link', $link); + $this->offsetSet('charset', $charset); + $this->setLastUpdate(time()) + ->setGenerator('Zend_Feed'); + } + + /** + * Read only properties accessor + * + * @param string $name property to read + * @return mixed + */ + public function __get($name) + { + if (!$this->offsetExists($name)) { + return NULL; + } + + return $this->offsetGet($name); + } + + /** + * Write properties accessor + * + * @param string $name name of the property to set + * @param mixed $value value to set + * @return void + */ + public function __set($name, $value) + { + $this->offsetSet($name, $value); + } + + /** + * Isset accessor + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Unset accessor + * + * @param string $key + * @return void + */ + public function __unset($key) + { + if ($this->offsetExists($key)) { + $this->offsetUnset($key); + } + } + + /** + * Timestamp of the update date + * + * @param int $lastUpdate + * @return Zend_Feed_Builder_Header + */ + public function setLastUpdate($lastUpdate) + { + $this->offsetSet('lastUpdate', $lastUpdate); + return $this; + } + + /** + * Timestamp of the publication date + * + * @param int $published + * @return Zend_Feed_Builder_Header + */ + public function setPublishedDate($published) + { + $this->offsetSet('published', $published); + return $this; + } + + /** + * Short description of the feed + * + * @param string $description + * @return Zend_Feed_Builder_Header + */ + public function setDescription($description) + { + $this->offsetSet('description', $description); + return $this; + } + + /** + * Sets the author of the feed + * + * @param string $author + * @return Zend_Feed_Builder_Header + */ + public function setAuthor($author) + { + $this->offsetSet('author', $author); + return $this; + } + + /** + * Sets the author's email + * + * @param string $email + * @return Zend_Feed_Builder_Header + * @throws Zend_Feed_Builder_Exception + */ + public function setEmail($email) + { + /** + * @see Zend_Validate_EmailAddress + */ + // require_once 'Zend/Validate/EmailAddress.php'; + $validate = new Zend_Validate_EmailAddress(); + if (!$validate->isValid($email)) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set a valid email address into the email property"); + } + $this->offsetSet('email', $email); + return $this; + } + + /** + * Sets the copyright notice + * + * @param string $copyright + * @return Zend_Feed_Builder_Header + */ + public function setCopyright($copyright) + { + $this->offsetSet('copyright', $copyright); + return $this; + } + + /** + * Sets the image of the feed + * + * @param string $image + * @return Zend_Feed_Builder_Header + */ + public function setImage($image) + { + $this->offsetSet('image', $image); + return $this; + } + + /** + * Sets the generator of the feed + * + * @param string $generator + * @return Zend_Feed_Builder_Header + */ + public function setGenerator($generator) + { + $this->offsetSet('generator', $generator); + return $this; + } + + /** + * Sets the language of the feed + * + * @param string $language + * @return Zend_Feed_Builder_Header + */ + public function setLanguage($language) + { + $this->offsetSet('language', $language); + return $this; + } + + /** + * Email address for person responsible for technical issues + * Ignored if atom is used + * + * @param string $webmaster + * @return Zend_Feed_Builder_Header + * @throws Zend_Feed_Builder_Exception + */ + public function setWebmaster($webmaster) + { + /** + * @see Zend_Validate_EmailAddress + */ + // require_once 'Zend/Validate/EmailAddress.php'; + $validate = new Zend_Validate_EmailAddress(); + if (!$validate->isValid($webmaster)) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set a valid email address into the webmaster property"); + } + $this->offsetSet('webmaster', $webmaster); + return $this; + } + + /** + * How long in minutes a feed can be cached before refreshing + * Ignored if atom is used + * + * @param int $ttl + * @return Zend_Feed_Builder_Header + * @throws Zend_Feed_Builder_Exception + */ + public function setTtl($ttl) + { + /** + * @see Zend_Validate_Int + */ + // require_once 'Zend/Validate/Int.php'; + $validate = new Zend_Validate_Int(); + if (!$validate->isValid($ttl)) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set an integer value to the ttl property"); + } + $this->offsetSet('ttl', $ttl); + return $this; + } + + /** + * PICS rating for the feed + * Ignored if atom is used + * + * @param string $rating + * @return Zend_Feed_Builder_Header + */ + public function setRating($rating) + { + $this->offsetSet('rating', $rating); + return $this; + } + + /** + * Cloud to be notified of updates of the feed + * Ignored if atom is used + * + * @param string|Zend_Uri_Http $uri + * @param string $procedure procedure to call, e.g. myCloud.rssPleaseNotify + * @param string $protocol protocol to use, e.g. soap or xml-rpc + * @return Zend_Feed_Builder_Header + * @throws Zend_Feed_Builder_Exception + */ + public function setCloud($uri, $procedure, $protocol) + { + if (is_string($uri) && Zend_Uri_Http::check($uri)) { + $uri = Zend_Uri::factory($uri); + } + if (!$uri instanceof Zend_Uri_Http) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception('Passed parameter is not a valid HTTP URI'); + } + if (!$uri->getPort()) { + $uri->setPort(80); + } + $this->offsetSet('cloud', array('uri' => $uri, + 'procedure' => $procedure, + 'protocol' => $protocol)); + return $this; + } + + /** + * A text input box that can be displayed with the feed + * Ignored if atom is used + * + * @param string $title the label of the Submit button in the text input area + * @param string $description explains the text input area + * @param string $name the name of the text object in the text input area + * @param string $link the URL of the CGI script that processes text input requests + * @return Zend_Feed_Builder_Header + */ + public function setTextInput($title, $description, $name, $link) + { + $this->offsetSet('textInput', array('title' => $title, + 'description' => $description, + 'name' => $name, + 'link' => $link)); + return $this; + } + + /** + * Hint telling aggregators which hours they can skip + * Ignored if atom is used + * + * @param array $hours list of hours in 24 format + * @return Zend_Feed_Builder_Header + * @throws Zend_Feed_Builder_Exception + */ + public function setSkipHours(array $hours) + { + if (count($hours) > 24) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you can not have more than 24 rows in the skipHours property"); + } + foreach ($hours as $hour) { + if ($hour < 0 || $hour > 23) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("$hour has te be between 0 and 23"); + } + } + $this->offsetSet('skipHours', $hours); + return $this; + } + + /** + * Hint telling aggregators which days they can skip + * Ignored if atom is used + * + * @param array $days list of days to skip, e.g. Monday + * @return Zend_Feed_Builder_Header + * @throws Zend_Feed_Builder_Exception + */ + public function setSkipDays(array $days) + { + if (count($days) > 7) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you can not have more than 7 days in the skipDays property"); + } + $valid = array('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'); + foreach ($days as $day) { + if (!in_array(strtolower($day), $valid)) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("$day is not a valid day"); + } + } + $this->offsetSet('skipDays', $days); + return $this; + } + + /** + * Sets the iTunes rss extension + * + * @param Zend_Feed_Builder_Header_Itunes $itunes + * @return Zend_Feed_Builder_Header + */ + public function setITunes(Zend_Feed_Builder_Header_Itunes $itunes) + { + $this->offsetSet('itunes', $itunes); + return $this; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Builder/Header/Itunes.php b/oss/piwik/trunk/libs/Zend/Feed/Builder/Header/Itunes.php new file mode 100644 index 00000000..9445460c --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Builder/Header/Itunes.php @@ -0,0 +1,288 @@ +setCategories($categories); + } + + /** + * Sets the categories column and in iTunes Music Store Browse + * $categories must conform to the following format: + * + * array(array('main' => 'main category', + * 'sub' => 'sub category' // optionnal + * ), + * // up to 3 rows + * ) + * + * + * @param array $categories + * @return Zend_Feed_Builder_Header_Itunes + * @throws Zend_Feed_Builder_Exception + */ + public function setCategories(array $categories) + { + $nb = count($categories); + if (0 === $nb) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set at least one itunes category"); + } + if ($nb > 3) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set at most three itunes categories"); + } + foreach ($categories as $i => $category) { + if (empty($category['main'])) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set the main category (category #$i)"); + } + } + $this->offsetSet('category', $categories); + return $this; + } + + /** + * Sets the artist value, default to the feed's author value + * + * @param string $author + * @return Zend_Feed_Builder_Header_Itunes + */ + public function setAuthor($author) + { + $this->offsetSet('author', $author); + return $this; + } + + /** + * Sets the owner of the postcast + * + * @param string $name default to the feed's author value + * @param string $email default to the feed's email value + * @return Zend_Feed_Builder_Header_Itunes + * @throws Zend_Feed_Builder_Exception + */ + public function setOwner($name = '', $email = '') + { + if (!empty($email)) { + /** + * @see Zend_Validate_EmailAddress + */ + // require_once 'Zend/Validate/EmailAddress.php'; + $validate = new Zend_Validate_EmailAddress(); + if (!$validate->isValid($email)) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set a valid email address into the itunes owner's email property"); + } + } + $this->offsetSet('owner', array('name' => $name, 'email' => $email)); + return $this; + } + + /** + * Sets the album/podcast art picture + * Default to the feed's image value + * + * @param string $image + * @return Zend_Feed_Builder_Header_Itunes + */ + public function setImage($image) + { + $this->offsetSet('image', $image); + return $this; + } + + /** + * Sets the short description of the podcast + * Default to the feed's description + * + * @param string $subtitle + * @return Zend_Feed_Builder_Header_Itunes + */ + public function setSubtitle($subtitle) + { + $this->offsetSet('subtitle', $subtitle); + return $this; + } + + /** + * Sets the longer description of the podcast + * Default to the feed's description + * + * @param string $summary + * @return Zend_Feed_Builder_Header_Itunes + */ + public function setSummary($summary) + { + $this->offsetSet('summary', $summary); + return $this; + } + + /** + * Prevent a feed from appearing + * + * @param string $block can be 'yes' or 'no' + * @return Zend_Feed_Builder_Header_Itunes + * @throws Zend_Feed_Builder_Exception + */ + public function setBlock($block) + { + $block = strtolower($block); + if (!in_array($block, array('yes', 'no'))) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set yes or no to the itunes block property"); + } + $this->offsetSet('block', $block); + return $this; + } + + /** + * Configuration of the parental advisory graphic + * + * @param string $explicit can be 'yes', 'no' or 'clean' + * @return Zend_Feed_Builder_Header_Itunes + * @throws Zend_Feed_Builder_Exception + */ + public function setExplicit($explicit) + { + $explicit = strtolower($explicit); + if (!in_array($explicit, array('yes', 'no', 'clean'))) { + /** + * @see Zend_Feed_Builder_Exception + */ + // require_once 'Zend/Feed/Builder/Exception.php'; + throw new Zend_Feed_Builder_Exception("you have to set yes, no or clean to the itunes explicit property"); + } + $this->offsetSet('explicit', $explicit); + return $this; + } + + /** + * Sets a comma separated list of 12 keywords maximum + * + * @param string $keywords + * @return Zend_Feed_Builder_Header_Itunes + */ + public function setKeywords($keywords) + { + $this->offsetSet('keywords', $keywords); + return $this; + } + + /** + * Sets the new feed URL location + * + * @param string $url + * @return Zend_Feed_Builder_Header_Itunes + */ + public function setNewFeedUrl($url) + { + $this->offsetSet('new_feed_url', $url); + return $this; + } + + /** + * Read only properties accessor + * + * @param string $name property to read + * @return mixed + */ + public function __get($name) + { + if (!$this->offsetExists($name)) { + return NULL; + } + + return $this->offsetGet($name); + } + + /** + * Write properties accessor + * + * @param string $name name of the property to set + * @param mixed $value value to set + * @return void + */ + public function __set($name, $value) + { + $this->offsetSet($name, $value); + } + + /** + * Isset accessor + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + return $this->offsetExists($key); + } + + /** + * Unset accessor + * + * @param string $key + * @return void + */ + public function __unset($key) + { + if ($this->offsetExists($key)) { + $this->offsetUnset($key); + } + } + +} \ No newline at end of file diff --git a/oss/piwik/trunk/libs/Zend/Feed/Builder/Interface.php b/oss/piwik/trunk/libs/Zend/Feed/Builder/Interface.php new file mode 100644 index 00000000..9dce55f8 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Builder/Interface.php @@ -0,0 +1,52 @@ +_element = $element; + } + + + /** + * Get a DOM representation of the element + * + * Returns the underlying DOM object, which can then be + * manipulated with full DOM methods. + * + * @return DOMDocument + */ + public function getDOM() + { + return $this->_element; + } + + + /** + * Update the object from a DOM element + * + * Take a DOMElement object, which may be originally from a call + * to getDOM() or may be custom created, and use it as the + * DOM tree for this Zend_Feed_Element. + * + * @param DOMElement $element + * @return void + */ + public function setDOM(DOMElement $element) + { + $this->_element = $this->_element->ownerDocument->importNode($element, true); + } + + /** + * Set the parent element of this object to another + * Zend_Feed_Element. + * + * @param Zend_Feed_Element $element + * @return void + */ + public function setParent(Zend_Feed_Element $element) + { + $this->_parentElement = $element; + $this->_appended = false; + } + + + /** + * Appends this element to its parent if necessary. + * + * @return void + */ + protected function ensureAppended() + { + if (!$this->_appended) { + $this->_parentElement->getDOM()->appendChild($this->_element); + $this->_appended = true; + $this->_parentElement->ensureAppended(); + } + } + + + /** + * Get an XML string representation of this element + * + * Returns a string of this element's XML, including the XML + * prologue. + * + * @return string + */ + public function saveXml() + { + // Return a complete document including XML prologue. + $doc = new DOMDocument($this->_element->ownerDocument->version, + $this->_element->ownerDocument->actualEncoding); + $doc->appendChild($doc->importNode($this->_element, true)); + return $doc->saveXML(); + } + + + /** + * Get the XML for only this element + * + * Returns a string of this element's XML without prologue. + * + * @return string + */ + public function saveXmlFragment() + { + return $this->_element->ownerDocument->saveXML($this->_element); + } + + /** + * Get encoding + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Set encoding + * + * @param string $value Encoding to use + * @return Zend_Feed_Element + */ + public function setEncoding($value) + { + $this->_encoding = (string) $value; + return $this; + } + + /** + * Map variable access onto the underlying entry representation. + * + * Get-style access returns a Zend_Feed_Element representing the + * child element accessed. To get string values, use method syntax + * with the __call() overriding. + * + * @param string $var The property to access. + * @return mixed + */ + public function __get($var) + { + $nodes = $this->_children($var); + $length = count($nodes); + + if ($length == 1) { + return new Zend_Feed_Element($nodes[0]); + } elseif ($length > 1) { + return array_map(create_function('$e', 'return new Zend_Feed_Element($e);'), $nodes); + } else { + // When creating anonymous nodes for __set chaining, don't + // call appendChild() on them. Instead we pass the current + // element to them as an extra reference; the child is + // then responsible for appending itself when it is + // actually set. This way "if ($foo->bar)" doesn't create + // a phantom "bar" element in our tree. + if (strpos($var, ':') !== false) { + list($ns, $elt) = explode(':', $var, 2); + $node = $this->_element->ownerDocument->createElementNS(Zend_Feed::lookupNamespace($ns), $elt); + } else { + $node = $this->_element->ownerDocument->createElement($var); + } + $node = new self($node); + $node->setParent($this); + return $node; + } + } + + + /** + * Map variable sets onto the underlying entry representation. + * + * @param string $var The property to change. + * @param string $val The property's new value. + * @return void + * @throws Zend_Feed_Exception + */ + public function __set($var, $val) + { + $this->ensureAppended(); + + $nodes = $this->_children($var); + if (!$nodes) { + if (strpos($var, ':') !== false) { + list($ns, $elt) = explode(':', $var, 2); + $node = $this->_element->ownerDocument->createElementNS(Zend_Feed::lookupNamespace($ns), + $var, htmlspecialchars($val, ENT_NOQUOTES, $this->getEncoding())); + $this->_element->appendChild($node); + } else { + $node = $this->_element->ownerDocument->createElement($var, + htmlspecialchars($val, ENT_NOQUOTES, $this->getEncoding())); + $this->_element->appendChild($node); + } + } elseif (count($nodes) > 1) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Cannot set the value of multiple tags simultaneously.'); + } else { + $nodes[0]->nodeValue = $val; + } + } + + + /** + * Map isset calls onto the underlying entry representation. + * + * @param string $var + * @return boolean + */ + public function __isset($var) + { + // Look for access of the form {ns:var}. We don't use + // _children() here because we can break out of the loop + // immediately once we find something. + if (strpos($var, ':') !== false) { + list($ns, $elt) = explode(':', $var, 2); + foreach ($this->_element->childNodes as $child) { + if ($child->localName == $elt && $child->prefix == $ns) { + return true; + } + } + } else { + foreach ($this->_element->childNodes as $child) { + if ($child->localName == $var) { + return true; + } + } + } + } + + + /** + * Get the value of an element with method syntax. + * + * Map method calls to get the string value of the requested + * element. If there are multiple elements that match, this will + * return an array of those objects. + * + * @param string $var The element to get the string value of. + * @param mixed $unused This parameter is not used. + * @return mixed The node's value, null, or an array of nodes. + */ + public function __call($var, $unused) + { + $nodes = $this->_children($var); + + if (!$nodes) { + return null; + } elseif (count($nodes) > 1) { + return $nodes; + } else { + return $nodes[0]->nodeValue; + } + } + + + /** + * Remove all children matching $var. + * + * @param string $var + * @return void + */ + public function __unset($var) + { + $nodes = $this->_children($var); + foreach ($nodes as $node) { + $parent = $node->parentNode; + $parent->removeChild($node); + } + } + + + /** + * Returns the nodeValue of this element when this object is used + * in a string context. + * + * @return string + */ + public function __toString() + { + return $this->_element->nodeValue; + } + + + /** + * Finds children with tagnames matching $var + * + * Similar to SimpleXML's children() method. + * + * @param string $var Tagname to match, can be either namespace:tagName or just tagName. + * @return array + */ + protected function _children($var) + { + $found = array(); + + // Look for access of the form {ns:var}. + if (strpos($var, ':') !== false) { + list($ns, $elt) = explode(':', $var, 2); + foreach ($this->_element->childNodes as $child) { + if ($child->localName == $elt && $child->prefix == $ns) { + $found[] = $child; + } + } + } else { + foreach ($this->_element->childNodes as $child) { + if ($child->localName == $var) { + $found[] = $child; + } + } + } + + return $found; + } + + + /** + * Required by the ArrayAccess interface. + * + * @param string $offset + * @return boolean + */ + public function offsetExists($offset) + { + if (strpos($offset, ':') !== false) { + list($ns, $attr) = explode(':', $offset, 2); + return $this->_element->hasAttributeNS(Zend_Feed::lookupNamespace($ns), $attr); + } else { + return $this->_element->hasAttribute($offset); + } + } + + + /** + * Required by the ArrayAccess interface. + * + * @param string $offset + * @return string + */ + public function offsetGet($offset) + { + if (strpos($offset, ':') !== false) { + list($ns, $attr) = explode(':', $offset, 2); + return $this->_element->getAttributeNS(Zend_Feed::lookupNamespace($ns), $attr); + } else { + return $this->_element->getAttribute($offset); + } + } + + + /** + * Required by the ArrayAccess interface. + * + * @param string $offset + * @param string $value + * @return string + */ + public function offsetSet($offset, $value) + { + $this->ensureAppended(); + + if (strpos($offset, ':') !== false) { + list($ns, $attr) = explode(':', $offset, 2); + // DOMElement::setAttributeNS() requires $qualifiedName to have a prefix + return $this->_element->setAttributeNS(Zend_Feed::lookupNamespace($ns), $offset, $value); + } else { + return $this->_element->setAttribute($offset, $value); + } + } + + + /** + * Required by the ArrayAccess interface. + * + * @param string $offset + * @return boolean + */ + public function offsetUnset($offset) + { + if (strpos($offset, ':') !== false) { + list($ns, $attr) = explode(':', $offset, 2); + return $this->_element->removeAttributeNS(Zend_Feed::lookupNamespace($ns), $attr); + } else { + return $this->_element->removeAttribute($offset); + } + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Entry/Abstract.php b/oss/piwik/trunk/libs/Zend/Feed/Entry/Abstract.php new file mode 100644 index 00000000..60c10cc8 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Entry/Abstract.php @@ -0,0 +1,124 @@ +loadXML($element); + @ini_restore('track_errors'); + + if (!$status) { + // prevent the class to generate an undefined variable notice (ZF-2590) + if (!isset($php_errormsg)) { + if (function_exists('xdebug_is_enabled')) { + $php_errormsg = '(error message not available, when XDebug is running)'; + } else { + $php_errormsg = '(error message not available)'; + } + } + + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception("DOMDocument cannot parse XML: $php_errormsg"); + } + + $element = $doc->getElementsByTagName($this->_rootElement)->item(0); + if (!$element) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('No root <' . $this->_rootElement . '> element found, cannot parse feed.'); + } + } else { + $doc = new DOMDocument('1.0', 'utf-8'); + if ($this->_rootNamespace !== null) { + $element = $doc->createElementNS(Zend_Feed::lookupNamespace($this->_rootNamespace), $this->_rootElement); + } else { + $element = $doc->createElement($this->_rootElement); + } + } + } + + parent::__construct($element); + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Entry/Atom.php b/oss/piwik/trunk/libs/Zend/Feed/Entry/Atom.php new file mode 100644 index 00000000..01a17232 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Entry/Atom.php @@ -0,0 +1,280 @@ +link('edit'); + if (!$deleteUri) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Cannot delete entry; no link rel="edit" is present.'); + } + + // DELETE + $client = Zend_Feed::getHttpClient(); + do { + $client->setUri($deleteUri); + if (Zend_Feed::getHttpMethodOverride()) { + $client->setHeader('X-HTTP-Method-Override', 'DELETE'); + $response = $client->request('POST'); + } else { + $response = $client->request('DELETE'); + } + $httpStatus = $response->getStatus(); + switch ((int) $httpStatus / 100) { + // Success + case 2: + return true; + // Redirect + case 3: + $deleteUri = $response->getHeader('Location'); + continue; + // Error + default: + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception("Expected response code 2xx, got $httpStatus"); + } + } while (true); + } + + + /** + * Save a new or updated Atom entry. + * + * Save is used to either create new entries or to save changes to + * existing ones. If we have a link rel="edit", we are changing + * an existing entry. In this case we re-serialize the entry and + * PUT it to the edit URI, checking for a 200 OK result. + * + * For posting new entries, you must specify the $postUri + * parameter to save() to tell the object where to post itself. + * We use $postUri and POST the serialized entry there, checking + * for a 201 Created response. If the insert is successful, we + * then parse the response from the POST to get any values that + * the server has generated: an id, an updated time, and its new + * link rel="edit". + * + * @param string $postUri Location to POST for creating new entries. + * @return void + * @throws Zend_Feed_Exception + */ + public function save($postUri = null) + { + if ($this->id()) { + // If id is set, look for link rel="edit" in the + // entry object and PUT. + $editUri = $this->link('edit'); + if (!$editUri) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Cannot edit entry; no link rel="edit" is present.'); + } + + $client = Zend_Feed::getHttpClient(); + $client->setUri($editUri); + if (Zend_Feed::getHttpMethodOverride()) { + $client->setHeaders(array('X-HTTP-Method-Override: PUT', + 'Content-Type: ' . self::CONTENT_TYPE)); + $client->setRawData($this->saveXML()); + $response = $client->request('POST'); + } else { + $client->setHeaders('Content-Type', self::CONTENT_TYPE); + $client->setRawData($this->saveXML()); + $response = $client->request('PUT'); + } + if ($response->getStatus() !== 200) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Expected response code 200, got ' . $response->getStatus()); + } + } else { + if ($postUri === null) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('PostURI must be specified to save new entries.'); + } + $client = Zend_Feed::getHttpClient(); + $client->setUri($postUri); + $client->setHeaders('Content-Type', self::CONTENT_TYPE); + $client->setRawData($this->saveXML()); + $response = $client->request('POST'); + + if ($response->getStatus() !== 201) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Expected response code 201, got ' + . $response->getStatus()); + } + } + + // Update internal properties using $client->responseBody; + @ini_set('track_errors', 1); + $newEntry = new DOMDocument; + $status = @$newEntry->loadXML($response->getBody()); + @ini_restore('track_errors'); + + if (!$status) { + // prevent the class to generate an undefined variable notice (ZF-2590) + if (!isset($php_errormsg)) { + if (function_exists('xdebug_is_enabled')) { + $php_errormsg = '(error message not available, when XDebug is running)'; + } else { + $php_errormsg = '(error message not available)'; + } + } + + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('XML cannot be parsed: ' . $php_errormsg); + } + + $newEntry = $newEntry->getElementsByTagName($this->_rootElement)->item(0); + if (!$newEntry) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('No root element found in server response:' + . "\n\n" . $client->responseBody); + } + + if ($this->_element->parentNode) { + $oldElement = $this->_element; + $this->_element = $oldElement->ownerDocument->importNode($newEntry, true); + $oldElement->parentNode->replaceChild($this->_element, $oldElement); + } else { + $this->_element = $newEntry; + } + } + + + /** + * Easy access to tags keyed by "rel" attributes. + * + * If $elt->link() is called with no arguments, we will attempt to + * return the value of the tag(s) like all other + * method-syntax attribute access. If an argument is passed to + * link(), however, then we will return the "href" value of the + * first tag that has a "rel" attribute matching $rel: + * + * $elt->link(): returns the value of the link tag. + * $elt->link('self'): returns the href from the first in the entry. + * + * @param string $rel The "rel" attribute to look for. + * @return mixed + */ + public function link($rel = null) + { + if ($rel === null) { + return parent::__call('link', null); + } + + // index link tags by their "rel" attribute. + $links = parent::__get('link'); + if (!is_array($links)) { + if ($links instanceof Zend_Feed_Element) { + $links = array($links); + } else { + return $links; + } + } + + foreach ($links as $link) { + if (empty($link['rel'])) { + $link['rel'] = 'alternate'; // see Atom 1.0 spec + } + if ($rel == $link['rel']) { + return $link['href']; + } + } + + return null; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Entry/Rss.php b/oss/piwik/trunk/libs/Zend/Feed/Entry/Rss.php new file mode 100644 index 00000000..a03fe805 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Entry/Rss.php @@ -0,0 +1,122 @@ +_element->lookupPrefix('http://purl.org/rss/1.0/modules/content/'); + return parent::__get("$prefix:encoded"); + default: + return parent::__get($var); + } + } + + /** + * Overwrites parent::_set method to enable write access + * to content:encoded element. + * + * @param string $var The property to change. + * @param string $val The property's new value. + * @return void + */ + public function __set($var, $value) + { + switch ($var) { + case 'content': + parent::__set('content:encoded', $value); + break; + default: + parent::__set($var, $value); + } + } + + /** + * Overwrites parent::_isset method to enable access + * to content:encoded element. + * + * @param string $var + * @return boolean + */ + public function __isset($var) + { + switch ($var) { + case 'content': + // don't use other callback to prevent invalid returned value + return $this->content() !== null; + default: + return parent::__isset($var); + } + } + + /** + * Overwrites parent::_call method to enable read access + * to content:encoded element. + * Please note that method-style write access is not currently supported + * by parent method, consequently this method doesn't as well. + * + * @param string $var The element to get the string value of. + * @param mixed $unused This parameter is not used. + * @return mixed The node's value, null, or an array of nodes. + */ + public function __call($var, $unused) + { + switch ($var) { + case 'content': + $prefix = $this->_element->lookupPrefix('http://purl.org/rss/1.0/modules/content/'); + return parent::__call("$prefix:encoded", $unused); + default: + return parent::__call($var, $unused); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Exception.php b/oss/piwik/trunk/libs/Zend/Feed/Exception.php new file mode 100644 index 00000000..1142c88a --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Exception.php @@ -0,0 +1,42 @@ +getHubs(); + } + + /** + * Allows the external environment to make Zend_Oauth use a specific + * Client instance. + * + * @param Zend_Http_Client $httpClient + * @return void + */ + public static function setHttpClient(Zend_Http_Client $httpClient) + { + self::$httpClient = $httpClient; + } + + /** + * Return the singleton instance of the HTTP Client. Note that + * the instance is reset and cleared of previous parameters GET/POST. + * Headers are NOT reset but handled by this component if applicable. + * + * @return Zend_Http_Client + */ + public static function getHttpClient() + { + if (!isset(self::$httpClient)): + self::$httpClient = new Zend_Http_Client; + else: + self::$httpClient->resetParameters(); + endif; + return self::$httpClient; + } + + /** + * Simple mechanism to delete the entire singleton HTTP Client instance + * which forces an new instantiation for subsequent requests. + * + * @return void + */ + public static function clearHttpClient() + { + self::$httpClient = null; + } + + /** + * RFC 3986 safe url encoding method + * + * @param string $string + * @return string + */ + public static function urlencode($string) + { + $rawencoded = rawurlencode($string); + $rfcencoded = str_replace('%7E', '~', $rawencoded); + return $rfcencoded; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/CallbackAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/CallbackAbstract.php new file mode 100644 index 00000000..2fae31b8 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/CallbackAbstract.php @@ -0,0 +1,307 @@ +setConfig($config); + } + } + + /** + * Process any injected configuration options + * + * @param array|Zend_Config $options Options array or Zend_Config instance + * @return Zend_Feed_Pubsubhubbub_CallbackAbstract + */ + public function setConfig($config) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + } elseif (!is_array($config)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Array or Zend_Config object' + . 'expected, got ' . gettype($config)); + } + if (array_key_exists('storage', $config)) { + $this->setStorage($config['storage']); + } + return $this; + } + + /** + * Send the response, including all headers. + * If you wish to handle this via Zend_Controller, use the getter methods + * to retrieve any data needed to be set on your HTTP Response object, or + * simply give this object the HTTP Response instance to work with for you! + * + * @return void + */ + public function sendResponse() + { + $this->getHttpResponse()->sendResponse(); + } + + /** + * Sets an instance of Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface used + * to background save any verification tokens associated with a subscription + * or other. + * + * @param Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface $storage + * @return Zend_Feed_Pubsubhubbub_CallbackAbstract + */ + public function setStorage(Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface $storage) + { + $this->_storage = $storage; + return $this; + } + + /** + * Gets an instance of Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface used + * to background save any verification tokens associated with a subscription + * or other. + * + * @return Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface + */ + public function getStorage() + { + if ($this->_storage === null) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('No storage object has been' + . ' set that subclasses Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface'); + } + return $this->_storage; + } + + /** + * An instance of a class handling Http Responses. This is implemented in + * Zend_Feed_Pubsubhubbub_HttpResponse which shares an unenforced interface with + * (i.e. not inherited from) Zend_Controller_Response_Http. + * + * @param Zend_Feed_Pubsubhubbub_HttpResponse|Zend_Controller_Response_Http $httpResponse + * @return Zend_Feed_Pubsubhubbub_CallbackAbstract + */ + public function setHttpResponse($httpResponse) + { + if (!is_object($httpResponse) + || (!$httpResponse instanceof Zend_Feed_Pubsubhubbub_HttpResponse + && !$httpResponse instanceof Zend_Controller_Response_Http) + ) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('HTTP Response object must' + . ' implement one of Zend_Feed_Pubsubhubbub_HttpResponse or' + . ' Zend_Controller_Response_Http'); + } + $this->_httpResponse = $httpResponse; + return $this; + } + + /** + * An instance of a class handling Http Responses. This is implemented in + * Zend_Feed_Pubsubhubbub_HttpResponse which shares an unenforced interface with + * (i.e. not inherited from) Zend_Controller_Response_Http. + * + * @return Zend_Feed_Pubsubhubbub_HttpResponse|Zend_Controller_Response_Http + */ + public function getHttpResponse() + { + if ($this->_httpResponse === null) { + $this->_httpResponse = new Zend_Feed_Pubsubhubbub_HttpResponse; + } + return $this->_httpResponse; + } + + /** + * Sets the number of Subscribers for which any updates are on behalf of. + * In other words, is this class serving one or more subscribers? How many? + * Defaults to 1 if left unchanged. + * + * @param string|int $count + * @return Zend_Feed_Pubsubhubbub_CallbackAbstract + */ + public function setSubscriberCount($count) + { + $count = intval($count); + if ($count <= 0) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Subscriber count must be' + . ' greater than zero'); + } + $this->_subscriberCount = $count; + return $this; + } + + /** + * Gets the number of Subscribers for which any updates are on behalf of. + * In other words, is this class serving one or more subscribers? How many? + * + * @return int + */ + public function getSubscriberCount() + { + return $this->_subscriberCount; + } + + /** + * Attempt to detect the callback URL (specifically the path forward) + */ + protected function _detectCallbackUrl() + { + $callbackUrl = ''; + if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $callbackUrl = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $callbackUrl = $_SERVER['REQUEST_URI']; + $scheme = 'http'; + if ($_SERVER['HTTPS'] == 'on') { + $scheme = 'https'; + } + $schemeAndHttpHost = $scheme . '://' . $this->_getHttpHost(); + if (strpos($callbackUrl, $schemeAndHttpHost) === 0) { + $callbackUrl = substr($callbackUrl, strlen($schemeAndHttpHost)); + } + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { + $callbackUrl= $_SERVER['ORIG_PATH_INFO']; + if (!empty($_SERVER['QUERY_STRING'])) { + $callbackUrl .= '?' . $_SERVER['QUERY_STRING']; + } + } + return $callbackUrl; + } + + /** + * Get the HTTP host + * + * @return string + */ + protected function _getHttpHost() + { + if (!empty($_SERVER['HTTP_HOST'])) { + return $_SERVER['HTTP_HOST']; + } + $scheme = 'http'; + if ($_SERVER['HTTPS'] == 'on') { + $scheme = 'https'; + } + $name = $_SERVER['SERVER_NAME']; + $port = $_SERVER['SERVER_PORT']; + if (($scheme == 'http' && $port == 80) + || ($scheme == 'https' && $port == 443) + ) { + return $name; + } else { + return $name . ':' . $port; + } + } + + /** + * Retrieve a Header value from either $_SERVER or Apache + * + * @param string $header + */ + protected function _getHeader($header) + { + $temp = strtoupper(str_replace('-', '_', $header)); + if (!empty($_SERVER[$temp])) { + return $_SERVER[$temp]; + } + $temp = 'HTTP_' . strtoupper(str_replace('-', '_', $header)); + if (!empty($_SERVER[$temp])) { + return $_SERVER[$temp]; + } + if (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + if (!empty($headers[$header])) { + return $headers[$header]; + } + } + return false; + } + + /** + * Return the raw body of the request + * + * @return string|false Raw body, or false if not present + */ + protected function _getRawBody() + { + $body = file_get_contents('php://input'); + if (strlen(trim($body)) == 0 && isset($GLOBALS['HTTP_RAW_POST_DATA'])) { + $body = $GLOBALS['HTTP_RAW_POST_DATA']; + } + if (strlen(trim($body)) > 0) { + return $body; + } + return false; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/CallbackInterface.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/CallbackInterface.php new file mode 100644 index 00000000..ce30a6bb --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/CallbackInterface.php @@ -0,0 +1,68 @@ +sendHeaders(); + echo $this->getBody(); + } + + /** + * Send all headers + * + * Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code} + * has been specified, it is sent with the first header. + * + * @return void + */ + public function sendHeaders() + { + if (count($this->_headers) || (200 != $this->_httpResponseCode)) { + $this->canSendHeaders(true); + } elseif (200 == $this->_httpResponseCode) { + return; + } + $httpCodeSent = false; + foreach ($this->_headers as $header) { + if (!$httpCodeSent && $this->_httpResponseCode) { + header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode); + $httpCodeSent = true; + } else { + header($header['name'] . ': ' . $header['value'], $header['replace']); + } + } + if (!$httpCodeSent) { + header('HTTP/1.1 ' . $this->_httpResponseCode); + $httpCodeSent = true; + } + } + + /** + * Set a header + * + * If $replace is true, replaces any headers already defined with that + * $name. + * + * @param string $name + * @param string $value + * @param boolean $replace + * @return Zend_Feed_Pubsubhubbub_HttpResponse + */ + public function setHeader($name, $value, $replace = false) + { + $name = $this->_normalizeHeader($name); + $value = (string) $value; + if ($replace) { + foreach ($this->_headers as $key => $header) { + if ($name == $header['name']) { + unset($this->_headers[$key]); + } + } + } + $this->_headers[] = array( + 'name' => $name, + 'value' => $value, + 'replace' => $replace, + ); + + return $this; + } + + /** + * Check if a specific Header is set and return its value + * + * @param string $name + * @return string|null + */ + public function getHeader($name) + { + $name = $this->_normalizeHeader($name); + foreach ($this->_headers as $header) { + if ($header['name'] == $name) { + return $header['value']; + } + } + } + + /** + * Return array of headers; see {@link $_headers} for format + * + * @return array + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Can we send headers? + * + * @param boolean $throw Whether or not to throw an exception if headers have been sent; defaults to false + * @return boolean + * @throws Zend_Feed_Pubsubhubbub_Exception + */ + public function canSendHeaders($throw = false) + { + $ok = headers_sent($file, $line); + if ($ok && $throw) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line); + } + return !$ok; + } + + /** + * Set HTTP response code to use with headers + * + * @param int $code + * @return Zend_Feed_Pubsubhubbub_HttpResponse + */ + public function setHttpResponseCode($code) + { + if (!is_int($code) || (100 > $code) || (599 < $code)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid HTTP response' + . ' code:' . $code); + } + $this->_httpResponseCode = $code; + return $this; + } + + /** + * Retrieve HTTP response code + * + * @return int + */ + public function getHttpResponseCode() + { + return $this->_httpResponseCode; + } + + /** + * Set body content + * + * @param string $content + * @return Zend_Feed_Pubsubhubbub_HttpResponse + */ + public function setBody($content) + { + $this->_body = (string) $content; + $this->setHeader('content-length', strlen($content)); + return $this; + } + + /** + * Return the body content + * + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * Normalizes a header name to X-Capitalized-Names + * + * @param string $name + * @return string + */ + protected function _normalizeHeader($name) + { + $filtered = str_replace(array('-', '_'), ' ', (string) $name); + $filtered = ucwords(strtolower($filtered)); + $filtered = str_replace(' ', '-', $filtered); + return $filtered; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/ModelAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/ModelAbstract.php new file mode 100644 index 00000000..eab4b563 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/ModelAbstract.php @@ -0,0 +1,64 @@ +_db = new Zend_Db_Table($table); + } else { + $this->_db = $tableGateway; + } + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/Subscription.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/Subscription.php new file mode 100644 index 00000000..ea971b4f --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/Subscription.php @@ -0,0 +1,131 @@ +_db->find($data['id']); + if ($result) { + $data['created_time'] = $result->current()->created_time; + $now = new Zend_Date; + if ($data['lease_seconds']) { + $data['expiration_time'] = $now->add($data['lease_seconds'], Zend_Date::SECOND) + ->get('yyyy-MM-dd HH:mm:ss'); + } + $this->_db->update( + $data, + $this->_db->getAdapter()->quoteInto('id = ?', $data['id']) + ); + return false; + } + + $this->_db->insert($data); + return true; + } + + /** + * Get subscription by ID/key + * + * @param string $key + * @return array + */ + public function getSubscription($key) + { + if (empty($key) || !is_string($key)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "key"' + .' of "' . $key . '" must be a non-empty string'); + } + $result = $this->_db->find($key); + if ($result) { + return (array) $result->current(); + } + return false; + } + + /** + * Determine if a subscription matching the key exists + * + * @param string $key + * @return bool + */ + public function hasSubscription($key) + { + if (empty($key) || !is_string($key)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "key"' + .' of "' . $key . '" must be a non-empty string'); + } + $result = $this->_db->find($key); + if ($result) { + return true; + } + return false; + } + + /** + * Delete a subscription + * + * @param string $key + * @return bool + */ + public function deleteSubscription($key) + { + $result = $this->_db->find($key); + if ($result) { + $this->_db->delete( + $this->_db->getAdapter()->quoteInto('id = ?', $key) + ); + return true; + } + return false; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/SubscriptionInterface.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/SubscriptionInterface.php new file mode 100644 index 00000000..f8a6e6a3 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Model/SubscriptionInterface.php @@ -0,0 +1,64 @@ +setConfig($config); + } + } + + /** + * Process any injected configuration options + * + * @param array|Zend_Config $options Options array or Zend_Config instance + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function setConfig($config) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + } elseif (!is_array($config)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Array or Zend_Config object' + . 'expected, got ' . gettype($config)); + } + if (array_key_exists('hubUrls', $config)) { + $this->addHubUrls($config['hubUrls']); + } + if (array_key_exists('updatedTopicUrls', $config)) { + $this->addUpdatedTopicUrls($config['updatedTopicUrls']); + } + if (array_key_exists('parameters', $config)) { + $this->setParameters($config['parameters']); + } + return $this; + } + + /** + * Add a Hub Server URL supported by Publisher + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function addHubUrl($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + .' of "' . $url . '" must be a non-empty string and a valid' + .'URL'); + } + $this->_hubUrls[] = $url; + return $this; + } + + /** + * Add an array of Hub Server URLs supported by Publisher + * + * @param array $urls + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function addHubUrls(array $urls) + { + foreach ($urls as $url) { + $this->addHubUrl($url); + } + return $this; + } + + /** + * Remove a Hub Server URL + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function removeHubUrl($url) + { + if (!in_array($url, $this->getHubUrls())) { + return $this; + } + $key = array_search($url, $this->_hubUrls); + unset($this->_hubUrls[$key]); + return $this; + } + + /** + * Return an array of unique Hub Server URLs currently available + * + * @return array + */ + public function getHubUrls() + { + $this->_hubUrls = array_unique($this->_hubUrls); + return $this->_hubUrls; + } + + /** + * Add a URL to a topic (Atom or RSS feed) which has been updated + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function addUpdatedTopicUrl($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + .' of "' . $url . '" must be a non-empty string and a valid' + .'URL'); + } + $this->_updatedTopicUrls[] = $url; + return $this; + } + + /** + * Add an array of Topic URLs which have been updated + * + * @param array $urls + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function addUpdatedTopicUrls(array $urls) + { + foreach ($urls as $url) { + $this->addUpdatedTopicUrl($url); + } + return $this; + } + + /** + * Remove an updated topic URL + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function removeUpdatedTopicUrl($url) + { + if (!in_array($url, $this->getUpdatedTopicUrls())) { + return $this; + } + $key = array_search($url, $this->_updatedTopicUrls); + unset($this->_updatedTopicUrls[$key]); + return $this; + } + + /** + * Return an array of unique updated topic URLs currently available + * + * @return array + */ + public function getUpdatedTopicUrls() + { + $this->_updatedTopicUrls = array_unique($this->_updatedTopicUrls); + return $this->_updatedTopicUrls; + } + + /** + * Notifies a single Hub Server URL of changes + * + * @param string $url The Hub Server's URL + * @return void + * @throws Zend_Feed_Pubsubhubbub_Exception Thrown on failure + */ + public function notifyHub($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + .' of "' . $url . '" must be a non-empty string and a valid' + .'URL'); + } + $client = $this->_getHttpClient(); + $client->setUri($url); + $response = $client->request(); + if ($response->getStatus() !== 204) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Notification to Hub Server ' + . 'at "' . $url . '" appears to have failed with a status code of "' + . $response->getStatus() . '" and message "' + . $response->getMessage() . '"'); + } + } + + /** + * Notifies all Hub Server URLs of changes + * + * If a Hub notification fails, certain data will be retained in an + * an array retrieved using getErrors(), if a failure occurs for any Hubs + * the isSuccess() check will return FALSE. This method is designed not + * to needlessly fail with an Exception/Error unless from Zend_Http_Client. + * + * @return void + * @throws Zend_Feed_Pubsubhubbub_Exception Thrown if no hubs attached + */ + public function notifyAll() + { + $client = $this->_getHttpClient(); + $hubs = $this->getHubUrls(); + if (empty($hubs)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('No Hub Server URLs' + . ' have been set so no notifcations can be sent'); + } + $this->_errors = array(); + foreach ($hubs as $url) { + $client->setUri($url); + $response = $client->request(); + if ($response->getStatus() !== 204) { + $this->_errors[] = array( + 'response' => $response, + 'hubUrl' => $url + ); + } + } + } + + /** + * Add an optional parameter to the update notification requests + * + * @param string $name + * @param string|null $value + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function setParameter($name, $value = null) + { + if (is_array($name)) { + $this->setParameters($name); + return $this; + } + if (empty($name) || !is_string($name)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "name"' + .' of "' . $name . '" must be a non-empty string'); + } + if ($value === null) { + $this->removeParameter($name); + return $this; + } + if (empty($value) || (!is_string($value) && !is_null($value))) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "value"' + .' of "' . $value . '" must be a non-empty string'); + } + $this->_parameters[$name] = $value; + return $this; + } + + /** + * Add an optional parameter to the update notification requests + * + * @param array $parameters + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function setParameters(array $parameters) + { + foreach ($parameters as $name => $value) { + $this->setParameter($name, $value); + } + return $this; + } + + /** + * Remove an optional parameter for the notification requests + * + * @param string $name + * @return Zend_Feed_Pubsubhubbub_Publisher + */ + public function removeParameter($name) + { + if (empty($name) || !is_string($name)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "name"' + .' of "' . $name . '" must be a non-empty string'); + } + if (array_key_exists($name, $this->_parameters)) { + unset($this->_parameters[$name]); + } + return $this; + } + + /** + * Return an array of optional parameters for notification requests + * + * @return array + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * Returns a boolean indicator of whether the notifications to Hub + * Servers were ALL successful. If even one failed, FALSE is returned. + * + * @return bool + */ + public function isSuccess() + { + if (count($this->_errors) > 0) { + return false; + } + return true; + } + + /** + * Return an array of errors met from any failures, including keys: + * 'response' => the Zend_Http_Response object from the failure + * 'hubUrl' => the URL of the Hub Server whose notification failed + * + * @return array + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * Get a basic prepared HTTP client for use + * + * @return Zend_Http_Client + */ + protected function _getHttpClient() + { + $client = Zend_Feed_Pubsubhubbub::getHttpClient(); + $client->setMethod(Zend_Http_Client::POST); + $client->setConfig(array( + 'useragent' => 'Zend_Feed_Pubsubhubbub_Publisher/' . Zend_Version::VERSION, + )); + $params = array(); + $params[] = 'hub.mode=publish'; + $topics = $this->getUpdatedTopicUrls(); + if (empty($topics)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('No updated topic URLs' + . ' have been set'); + } + foreach ($topics as $topicUrl) { + $params[] = 'hub.url=' . urlencode($topicUrl); + } + $optParams = $this->getParameters(); + foreach ($optParams as $name => $value) { + $params[] = urlencode($name) . '=' . urlencode($value); + } + $paramString = implode('&', $params); + $client->setRawData($paramString); + return $client; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Subscriber.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Subscriber.php new file mode 100644 index 00000000..9921bce5 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Subscriber.php @@ -0,0 +1,856 @@ +setConfig($config); + } + } + + /** + * Process any injected configuration options + * + * @param array|Zend_Config $options Options array or Zend_Config instance + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setConfig($config) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + } elseif (!is_array($config)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Array or Zend_Config object' + . ' expected, got ' . gettype($config)); + } + if (array_key_exists('hubUrls', $config)) { + $this->addHubUrls($config['hubUrls']); + } + if (array_key_exists('callbackUrl', $config)) { + $this->setCallbackUrl($config['callbackUrl']); + } + if (array_key_exists('topicUrl', $config)) { + $this->setTopicUrl($config['topicUrl']); + } + if (array_key_exists('storage', $config)) { + $this->setStorage($config['storage']); + } + if (array_key_exists('leaseSeconds', $config)) { + $this->setLeaseSeconds($config['leaseSeconds']); + } + if (array_key_exists('parameters', $config)) { + $this->setParameters($config['parameters']); + } + if (array_key_exists('authentications', $config)) { + $this->addAuthentications($config['authentications']); + } + if (array_key_exists('usePathParameter', $config)) { + $this->usePathParameter($config['usePathParameter']); + } + if (array_key_exists('preferredVerificationMode', $config)) { + $this->setPreferredVerificationMode( + $config['preferredVerificationMode'] + ); + } + return $this; + } + + /** + * Set the topic URL (RSS or Atom feed) to which the intended (un)subscribe + * event will relate + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setTopicUrl($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + .' of "' . $url . '" must be a non-empty string and a valid' + .' URL'); + } + $this->_topicUrl = $url; + return $this; + } + + /** + * Set the topic URL (RSS or Atom feed) to which the intended (un)subscribe + * event will relate + * + * @return string + */ + public function getTopicUrl() + { + if (empty($this->_topicUrl)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('A valid Topic (RSS or Atom' + . ' feed) URL MUST be set before attempting any operation'); + } + return $this->_topicUrl; + } + + /** + * Set the number of seconds for which any subscription will remain valid + * + * @param int $seconds + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setLeaseSeconds($seconds) + { + $seconds = intval($seconds); + if ($seconds <= 0) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Expected lease seconds' + . ' must be an integer greater than zero'); + } + $this->_leaseSeconds = $seconds; + return $this; + } + + /** + * Get the number of lease seconds on subscriptions + * + * @return int + */ + public function getLeaseSeconds() + { + return $this->_leaseSeconds; + } + + /** + * Set the callback URL to be used by Hub Servers when communicating with + * this Subscriber + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setCallbackUrl($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + . ' of "' . $url . '" must be a non-empty string and a valid' + . ' URL'); + } + $this->_callbackUrl = $url; + return $this; + } + + /** + * Get the callback URL to be used by Hub Servers when communicating with + * this Subscriber + * + * @return string + */ + public function getCallbackUrl() + { + if (empty($this->_callbackUrl)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('A valid Callback URL MUST be' + . ' set before attempting any operation'); + } + return $this->_callbackUrl; + } + + /** + * Set preferred verification mode (sync or async). By default, this + * Subscriber prefers synchronous verification, but does support + * asynchronous if that's the Hub Server's utilised mode. + * + * Zend_Feed_Pubsubhubbub_Subscriber will always send both modes, whose + * order of occurance in the parameter list determines this preference. + * + * @param string $mode Should be 'sync' or 'async' + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setPreferredVerificationMode($mode) + { + if ($mode !== Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC + && $mode !== Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid preferred' + . ' mode specified: "' . $mode . '" but should be one of' + . ' Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC or' + . ' Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC'); + } + $this->_preferredVerificationMode = $mode; + return $this; + } + + /** + * Get preferred verification mode (sync or async). + * + * @return string + */ + public function getPreferredVerificationMode() + { + return $this->_preferredVerificationMode; + } + + /** + * Add a Hub Server URL supported by Publisher + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function addHubUrl($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + . ' of "' . $url . '" must be a non-empty string and a valid' + . ' URL'); + } + $this->_hubUrls[] = $url; + return $this; + } + + /** + * Add an array of Hub Server URLs supported by Publisher + * + * @param array $urls + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function addHubUrls(array $urls) + { + foreach ($urls as $url) { + $this->addHubUrl($url); + } + return $this; + } + + /** + * Remove a Hub Server URL + * + * @param string $url + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function removeHubUrl($url) + { + if (!in_array($url, $this->getHubUrls())) { + return $this; + } + $key = array_search($url, $this->_hubUrls); + unset($this->_hubUrls[$key]); + return $this; + } + + /** + * Return an array of unique Hub Server URLs currently available + * + * @return array + */ + public function getHubUrls() + { + $this->_hubUrls = array_unique($this->_hubUrls); + return $this->_hubUrls; + } + + /** + * Add authentication credentials for a given URL + * + * @param string $url + * @param array $authentication + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function addAuthentication($url, array $authentication) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "url"' + . ' of "' . $url . '" must be a non-empty string and a valid' + . ' URL'); + } + $this->_authentications[$url] = $authentication; + return $this; + } + + /** + * Add authentication credentials for hub URLs + * + * @param array $authentications + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function addAuthentications(array $authentications) + { + foreach ($authentications as $url => $authentication) { + $this->addAuthentication($url, $authentication); + } + return $this; + } + + /** + * Get all hub URL authentication credentials + * + * @return array + */ + public function getAuthentications() + { + return $this->_authentications; + } + + /** + * Set flag indicating whether or not to use a path parameter + * + * @param bool $bool + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function usePathParameter($bool = true) + { + $this->_usePathParameter = $bool; + return $this; + } + + /** + * Add an optional parameter to the (un)subscribe requests + * + * @param string $name + * @param string|null $value + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setParameter($name, $value = null) + { + if (is_array($name)) { + $this->setParameters($name); + return $this; + } + if (empty($name) || !is_string($name)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "name"' + . ' of "' . $name . '" must be a non-empty string'); + } + if ($value === null) { + $this->removeParameter($name); + return $this; + } + if (empty($value) || (!is_string($value) && !is_null($value))) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "value"' + . ' of "' . $value . '" must be a non-empty string'); + } + $this->_parameters[$name] = $value; + return $this; + } + + /** + * Add an optional parameter to the (un)subscribe requests + * + * @param string $name + * @param string|null $value + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setParameters(array $parameters) + { + foreach ($parameters as $name => $value) { + $this->setParameter($name, $value); + } + return $this; + } + + /** + * Remove an optional parameter for the (un)subscribe requests + * + * @param string $name + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function removeParameter($name) + { + if (empty($name) || !is_string($name)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid parameter "name"' + . ' of "' . $name . '" must be a non-empty string'); + } + if (array_key_exists($name, $this->_parameters)) { + unset($this->_parameters[$name]); + } + return $this; + } + + /** + * Return an array of optional parameters for (un)subscribe requests + * + * @return array + */ + public function getParameters() + { + return $this->_parameters; + } + + /** + * Sets an instance of Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface used to background + * save any verification tokens associated with a subscription or other. + * + * @param Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface $storage + * @return Zend_Feed_Pubsubhubbub_Subscriber + */ + public function setStorage(Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface $storage) + { + $this->_storage = $storage; + return $this; + } + + /** + * Gets an instance of Zend_Feed_Pubsubhubbub_Storage_StorageInterface used + * to background save any verification tokens associated with a subscription + * or other. + * + * @return Zend_Feed_Pubsubhubbub_Model_SubscriptionInterface + */ + public function getStorage() + { + if ($this->_storage === null) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('No storage vehicle ' + . 'has been set.'); + } + return $this->_storage; + } + + /** + * Subscribe to one or more Hub Servers using the stored Hub URLs + * for the given Topic URL (RSS or Atom feed) + * + * @return void + */ + public function subscribeAll() + { + return $this->_doRequest('subscribe'); + } + + /** + * Unsubscribe from one or more Hub Servers using the stored Hub URLs + * for the given Topic URL (RSS or Atom feed) + * + * @return void + */ + public function unsubscribeAll() + { + return $this->_doRequest('unsubscribe'); + } + + /** + * Returns a boolean indicator of whether the notifications to Hub + * Servers were ALL successful. If even one failed, FALSE is returned. + * + * @return bool + */ + public function isSuccess() + { + if (count($this->_errors) > 0) { + return false; + } + return true; + } + + /** + * Return an array of errors met from any failures, including keys: + * 'response' => the Zend_Http_Response object from the failure + * 'hubUrl' => the URL of the Hub Server whose notification failed + * + * @return array + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * Return an array of Hub Server URLs who returned a response indicating + * operation in Asynchronous Verification Mode, i.e. they will not confirm + * any (un)subscription immediately but at a later time (Hubs may be + * doing this as a batch process when load balancing) + * + * @return array + */ + public function getAsyncHubs() + { + return $this->_asyncHubs; + } + + /** + * Executes an (un)subscribe request + * + * @param string $mode + * @return void + */ + protected function _doRequest($mode) + { + $client = $this->_getHttpClient(); + $hubs = $this->getHubUrls(); + if (empty($hubs)) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('No Hub Server URLs' + . ' have been set so no subscriptions can be attempted'); + } + $this->_errors = array(); + $this->_asyncHubs = array(); + foreach ($hubs as $url) { + if (array_key_exists($url, $this->_authentications)) { + $auth = $this->_authentications[$url]; + $client->setAuth($auth[0], $auth[1]); + } + $client->setUri($url); + $client->setRawData($this->_getRequestParameters($url, $mode)); + $response = $client->request(); + if ($response->getStatus() !== 204 + && $response->getStatus() !== 202 + ) { + $this->_errors[] = array( + 'response' => $response, + 'hubUrl' => $url, + ); + /** + * At first I thought it was needed, but the backend storage will + * allow tracking async without any user interference. It's left + * here in case the user is interested in knowing what Hubs + * are using async verification modes so they may update Models and + * move these to asynchronous processes. + */ + } elseif ($response->getStatus() == 202) { + $this->_asyncHubs[] = array( + 'response' => $response, + 'hubUrl' => $url, + ); + } + } + } + + /** + * Get a basic prepared HTTP client for use + * + * @param string $mode Must be "subscribe" or "unsubscribe" + * @return Zend_Http_Client + */ + protected function _getHttpClient() + { + $client = Zend_Feed_Pubsubhubbub::getHttpClient(); + $client->setMethod(Zend_Http_Client::POST); + $client->setConfig(array('useragent' => 'Zend_Feed_Pubsubhubbub_Subscriber/' + . Zend_Version::VERSION)); + return $client; + } + + /** + * Return a list of standard protocol/optional parameters for addition to + * client's POST body that are specific to the current Hub Server URL + * + * @param string $hubUrl + * @param mode $hubUrl + * @return string + */ + protected function _getRequestParameters($hubUrl, $mode) + { + if (!in_array($mode, array('subscribe', 'unsubscribe'))) { + // require_once 'Zend/Feed/Pubsubhubbub/Exception.php'; + throw new Zend_Feed_Pubsubhubbub_Exception('Invalid mode specified: "' + . $mode . '" which should have been "subscribe" or "unsubscribe"'); + } + + $params = array( + 'hub.mode' => $mode, + 'hub.topic' => $this->getTopicUrl(), + ); + + if ($this->getPreferredVerificationMode() + == Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC + ) { + $vmodes = array( + Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC, + Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC, + ); + } else { + $vmodes = array( + Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_ASYNC, + Zend_Feed_Pubsubhubbub::VERIFICATION_MODE_SYNC, + ); + } + $params['hub.verify'] = array(); + foreach($vmodes as $vmode) { + $params['hub.verify'][] = $vmode; + } + + /** + * Establish a persistent verify_token and attach key to callback + * URL's path/querystring + */ + $key = $this->_generateSubscriptionKey($params, $hubUrl); + $token = $this->_generateVerifyToken(); + $params['hub.verify_token'] = $token; + + // Note: query string only usable with PuSH 0.2 Hubs + if (!$this->_usePathParameter) { + $params['hub.callback'] = $this->getCallbackUrl() + . '?xhub.subscription=' . Zend_Feed_Pubsubhubbub::urlencode($key); + } else { + $params['hub.callback'] = rtrim($this->getCallbackUrl(), '/') + . '/' . Zend_Feed_Pubsubhubbub::urlencode($key); + } + if ($mode == 'subscribe' && !is_null($this->getLeaseSeconds())) { + $params['hub.lease_seconds'] = $this->getLeaseSeconds(); + } + + // hub.secret not currently supported + $optParams = $this->getParameters(); + foreach ($optParams as $name => $value) { + $params[$name] = $value; + } + + // store subscription to storage + $now = new Zend_Date; + $expires = null; + if (isset($params['hub.lease_seconds'])) { + $expires = $now->add($params['hub.lease_seconds'], Zend_Date::SECOND) + ->get('yyyy-MM-dd HH:mm:ss'); + } + $data = array( + 'id' => $key, + 'topic_url' => $params['hub.topic'], + 'hub_url' => $hubUrl, + 'created_time' => $now->get('yyyy-MM-dd HH:mm:ss'), + 'lease_seconds' => $expires, + 'verify_token' => hash('sha256', $params['hub.verify_token']), + 'secret' => null, + 'expiration_time' => $expires, + 'subscription_state' => Zend_Feed_Pubsubhubbub::SUBSCRIPTION_NOTVERIFIED, + ); + $this->getStorage()->setSubscription($data); + + return $this->_toByteValueOrderedString( + $this->_urlEncode($params) + ); + } + + /** + * Simple helper to generate a verification token used in (un)subscribe + * requests to a Hub Server. Follows no particular method, which means + * it might be improved/changed in future. + * + * @param string $hubUrl The Hub Server URL for which this token will apply + * @return string + */ + protected function _generateVerifyToken() + { + if (!empty($this->_testStaticToken)) { + return $this->_testStaticToken; + } + return uniqid(rand(), true) . time(); + } + + /** + * Simple helper to generate a verification token used in (un)subscribe + * requests to a Hub Server. + * + * @param string $hubUrl The Hub Server URL for which this token will apply + * @return string + */ + protected function _generateSubscriptionKey(array $params, $hubUrl) + { + $keyBase = $params['hub.topic'] . $hubUrl; + $key = md5($keyBase); + return $key; + } + + /** + * URL Encode an array of parameters + * + * @param array $params + * @return array + */ + protected function _urlEncode(array $params) + { + $encoded = array(); + foreach ($params as $key => $value) { + if (is_array($value)) { + $ekey = Zend_Feed_Pubsubhubbub::urlencode($key); + $encoded[$ekey] = array(); + foreach ($value as $duplicateKey) { + $encoded[$ekey][] + = Zend_Feed_Pubsubhubbub::urlencode($duplicateKey); + } + } else { + $encoded[Zend_Feed_Pubsubhubbub::urlencode($key)] + = Zend_Feed_Pubsubhubbub::urlencode($value); + } + } + return $encoded; + } + + /** + * Order outgoing parameters + * + * @param array $params + * @return array + */ + protected function _toByteValueOrderedString(array $params) + { + $return = array(); + uksort($params, 'strnatcmp'); + foreach ($params as $key => $value) { + if (is_array($value)) { + foreach ($value as $keyduplicate) { + $return[] = $key . '=' . $keyduplicate; + } + } else { + $return[] = $key . '=' . $value; + } + } + return implode('&', $return); + } + + /** + * This is STRICTLY for testing purposes only... + */ + protected $_testStaticToken = null; + + final public function setTestStaticToken($token) + { + $this->_testStaticToken = (string) $token; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Subscriber/Callback.php b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Subscriber/Callback.php new file mode 100644 index 00000000..1f423758 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Pubsubhubbub/Subscriber/Callback.php @@ -0,0 +1,328 @@ +_subscriptionKey = $key; + return $this; + } + + /** + * Handle any callback from a Hub Server responding to a subscription or + * unsubscription request. This should be the Hub Server confirming the + * the request prior to taking action on it. + * + * @param array $httpGetData GET data if available and not in $_GET + * @param bool $sendResponseNow Whether to send response now or when asked + * @return void + */ + public function handle(array $httpGetData = null, $sendResponseNow = false) + { + if ($httpGetData === null) { + $httpGetData = $_GET; + } + + /** + * Handle any feed updates (sorry for the mess :P) + * + * This DOES NOT attempt to process a feed update. Feed updates + * SHOULD be validated/processed by an asynchronous process so as + * to avoid holding up responses to the Hub. + */ + if (strtolower($_SERVER['REQUEST_METHOD']) == 'post' + && $this->_hasValidVerifyToken(null, false) + && ($this->_getHeader('Content-Type') == 'application/atom+xml' + || $this->_getHeader('Content-Type') == 'application/rss+xml' + || $this->_getHeader('Content-Type') == 'application/xml' + || $this->_getHeader('Content-Type') == 'text/xml' + || $this->_getHeader('Content-Type') == 'application/rdf+xml') + ) { + $this->setFeedUpdate($this->_getRawBody()); + $this->getHttpResponse() + ->setHeader('X-Hub-On-Behalf-Of', $this->getSubscriberCount()); + /** + * Handle any (un)subscribe confirmation requests + */ + } elseif ($this->isValidHubVerification($httpGetData)) { + $data = $this->_currentSubscriptionData; + $this->getHttpResponse()->setBody($httpGetData['hub_challenge']); + $data['subscription_state'] = Zend_Feed_Pubsubhubbub::SUBSCRIPTION_VERIFIED; + if (isset($httpGetData['hub_lease_seconds'])) { + $data['lease_seconds'] = $httpGetData['hub_lease_seconds']; + } + $this->getStorage()->setSubscription($data); + /** + * Hey, C'mon! We tried everything else! + */ + } else { + $this->getHttpResponse()->setHttpResponseCode(404); + } + if ($sendResponseNow) { + $this->sendResponse(); + } + } + + /** + * Checks validity of the request simply by making a quick pass and + * confirming the presence of all REQUIRED parameters. + * + * @param array $httpGetData + * @return bool + */ + public function isValidHubVerification(array $httpGetData) + { + /** + * As per the specification, the hub.verify_token is OPTIONAL. This + * implementation of Pubsubhubbub considers it REQUIRED and will + * always send a hub.verify_token parameter to be echoed back + * by the Hub Server. Therefore, its absence is considered invalid. + */ + if (strtolower($_SERVER['REQUEST_METHOD']) !== 'get') { + return false; + } + $required = array( + 'hub_mode', + 'hub_topic', + 'hub_challenge', + 'hub_verify_token', + ); + foreach ($required as $key) { + if (!array_key_exists($key, $httpGetData)) { + return false; + } + } + if ($httpGetData['hub_mode'] !== 'subscribe' + && $httpGetData['hub_mode'] !== 'unsubscribe' + ) { + return false; + } + if ($httpGetData['hub_mode'] == 'subscribe' + && !array_key_exists('hub_lease_seconds', $httpGetData) + ) { + return false; + } + if (!Zend_Uri::check($httpGetData['hub_topic'])) { + return false; + } + + /** + * Attempt to retrieve any Verification Token Key attached to Callback + * URL's path by our Subscriber implementation + */ + if (!$this->_hasValidVerifyToken($httpGetData)) { + return false; + } + return true; + } + + /** + * Sets a newly received feed (Atom/RSS) sent by a Hub as an update to a + * Topic we've subscribed to. + * + * @param string $feed + * @return Zend_Feed_Pubsubhubbub_Subscriber_Callback + */ + public function setFeedUpdate($feed) + { + $this->_feedUpdate = $feed; + return $this; + } + + /** + * Check if any newly received feed (Atom/RSS) update was received + * + * @return bool + */ + public function hasFeedUpdate() + { + if (is_null($this->_feedUpdate)) { + return false; + } + return true; + } + + /** + * Gets a newly received feed (Atom/RSS) sent by a Hub as an update to a + * Topic we've subscribed to. + * + * @return string + */ + public function getFeedUpdate() + { + return $this->_feedUpdate; + } + + /** + * Check for a valid verify_token. By default attempts to compare values + * with that sent from Hub, otherwise merely ascertains its existence. + * + * @param array $httpGetData + * @param bool $checkValue + * @return bool + */ + protected function _hasValidVerifyToken(array $httpGetData = null, $checkValue = true) + { + $verifyTokenKey = $this->_detectVerifyTokenKey($httpGetData); + if (empty($verifyTokenKey)) { + return false; + } + $verifyTokenExists = $this->getStorage()->hasSubscription($verifyTokenKey); + if (!$verifyTokenExists) { + return false; + } + if ($checkValue) { + $data = $this->getStorage()->getSubscription($verifyTokenKey); + $verifyToken = $data['verify_token']; + if ($verifyToken !== hash('sha256', $httpGetData['hub_verify_token'])) { + return false; + } + $this->_currentSubscriptionData = $data; + return true; + } + return true; + } + + /** + * Attempt to detect the verification token key. This would be passed in + * the Callback URL (which we are handling with this class!) as a URI + * path part (the last part by convention). + * + * @param null|array $httpGetData + * @return false|string + */ + protected function _detectVerifyTokenKey(array $httpGetData = null) + { + /** + * Available when sub keys encoding in Callback URL path + */ + if (isset($this->_subscriptionKey)) { + return $this->_subscriptionKey; + } + + /** + * Available only if allowed by PuSH 0.2 Hubs + */ + if (is_array($httpGetData) + && isset($httpGetData['xhub_subscription']) + ) { + return $httpGetData['xhub_subscription']; + } + + /** + * Available (possibly) if corrupted in transit and not part of $_GET + */ + $params = $this->_parseQueryString(); + if (isset($params['xhub.subscription'])) { + return rawurldecode($params['xhub.subscription']); + } + + return false; + } + + /** + * Build an array of Query String parameters. + * This bypasses $_GET which munges parameter names and cannot accept + * multiple parameters with the same key. + * + * @return array|void + */ + protected function _parseQueryString() + { + $params = array(); + $queryString = ''; + if (isset($_SERVER['QUERY_STRING'])) { + $queryString = $_SERVER['QUERY_STRING']; + } + if (empty($queryString)) { + return array(); + } + $parts = explode('&', $queryString); + foreach ($parts as $kvpair) { + $pair = explode('=', $kvpair); + $key = rawurldecode($pair[0]); + $value = rawurldecode($pair[1]); + if (isset($params[$key])) { + if (is_array($params[$key])) { + $params[$key][] = $value; + } else { + $params[$key] = array($params[$key], $value); + } + } else { + $params[$key] = $value; + } + } + return $params; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader.php b/oss/piwik/trunk/libs/Zend/Feed/Reader.php new file mode 100644 index 00000000..26bf661c --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader.php @@ -0,0 +1,721 @@ + array( + 'DublinCore_Feed', + 'Atom_Feed' + ), + 'entry' => array( + 'Content_Entry', + 'DublinCore_Entry', + 'Atom_Entry' + ), + 'core' => array( + 'DublinCore_Feed', + 'Atom_Feed', + 'Content_Entry', + 'DublinCore_Entry', + 'Atom_Entry' + ) + ); + + /** + * Get the Feed cache + * + * @return Zend_Cache_Core + */ + public static function getCache() + { + return self::$_cache; + } + + /** + * Set the feed cache + * + * @param Zend_Cache_Core $cache + * @return void + */ + public static function setCache(Zend_Cache_Core $cache) + { + self::$_cache = $cache; + } + + /** + * Set the HTTP client instance + * + * Sets the HTTP client object to use for retrieving the feeds. + * + * @param Zend_Http_Client $httpClient + * @return void + */ + public static function setHttpClient(Zend_Http_Client $httpClient) + { + self::$_httpClient = $httpClient; + } + + + /** + * Gets the HTTP client object. If none is set, a new Zend_Http_Client will be used. + * + * @return Zend_Http_Client_Abstract + */ + public static function getHttpClient() + { + if (!self::$_httpClient instanceof Zend_Http_Client) { + /** + * @see Zend_Http_Client + */ + // require_once 'Zend/Http/Client.php'; + self::$_httpClient = new Zend_Http_Client(); + } + + return self::$_httpClient; + } + + /** + * Toggle using POST instead of PUT and DELETE HTTP methods + * + * Some feed implementations do not accept PUT and DELETE HTTP + * methods, or they can't be used because of proxies or other + * measures. This allows turning on using POST where PUT and + * DELETE would normally be used; in addition, an + * X-Method-Override header will be sent with a value of PUT or + * DELETE as appropriate. + * + * @param boolean $override Whether to override PUT and DELETE. + * @return void + */ + public static function setHttpMethodOverride($override = true) + { + self::$_httpMethodOverride = $override; + } + + /** + * Get the HTTP override state + * + * @return boolean + */ + public static function getHttpMethodOverride() + { + return self::$_httpMethodOverride; + } + + /** + * Set the flag indicating whether or not to use HTTP conditional GET + * + * @param bool $bool + * @return void + */ + public static function useHttpConditionalGet($bool = true) + { + self::$_httpConditionalGet = $bool; + } + + /** + * Import a feed by providing a URL + * + * @param string $url The URL to the feed + * @param string $etag OPTIONAL Last received ETag for this resource + * @param string $lastModified OPTIONAL Last-Modified value for this resource + * @return Zend_Feed_Reader_FeedInterface + */ + public static function import($uri, $etag = null, $lastModified = null) + { + $cache = self::getCache(); + $feed = null; + $responseXml = ''; + $client = self::getHttpClient(); + $client->resetParameters(); + $client->setHeaders('If-None-Match', null); + $client->setHeaders('If-Modified-Since', null); + $client->setUri($uri); + $cacheId = 'Zend_Feed_Reader_' . md5($uri); + + if (self::$_httpConditionalGet && $cache) { + $data = $cache->load($cacheId); + if ($data) { + if (is_null($etag)) { + $etag = $cache->load($cacheId.'_etag'); + } + if (is_null($lastModified)) { + $lastModified = $cache->load($cacheId.'_lastmodified');; + } + if ($etag) { + $client->setHeaders('If-None-Match', $etag); + } + if ($lastModified) { + $client->setHeaders('If-Modified-Since', $lastModified); + } + } + $response = $client->request('GET'); + if ($response->getStatus() !== 200 && $response->getStatus() !== 304) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Feed failed to load, got response code ' . $response->getStatus()); + } + if ($response->getStatus() == 304) { + $responseXml = $data; + } else { + $responseXml = $response->getBody(); + $cache->save($responseXml, $cacheId); + if ($response->getHeader('ETag')) { + $cache->save($response->getHeader('ETag'), $cacheId.'_etag'); + } + if ($response->getHeader('Last-Modified')) { + $cache->save($response->getHeader('Last-Modified'), $cacheId.'_lastmodified'); + } + } + return self::importString($responseXml); + } elseif ($cache) { + $data = $cache->load($cacheId); + if ($data !== false) { + return self::importString($data); + } + $response = $client->request('GET'); + if ($response->getStatus() !== 200) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Feed failed to load, got response code ' . $response->getStatus()); + } + $responseXml = $response->getBody(); + $cache->save($responseXml, $cacheId); + return self::importString($responseXml); + } else { + $response = $client->request('GET'); + if ($response->getStatus() !== 200) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Feed failed to load, got response code ' . $response->getStatus()); + } + $reader = self::importString($response->getBody()); + $reader->setOriginalSourceUri($uri); + return $reader; + } + } + + /** + * Import a feed by providing a Zend_Feed_Abstract object + * + * @param Zend_Feed_Abstract $feed A fully instantiated Zend_Feed object + * @return Zend_Feed_Reader_FeedInterface + */ + public static function importFeed(Zend_Feed_Abstract $feed) + { + $dom = $feed->getDOM()->ownerDocument; + $type = self::detectType($dom); + self::_registerCoreExtensions(); + if (substr($type, 0, 3) == 'rss') { + $reader = new Zend_Feed_Reader_Feed_Rss($dom, $type); + } else { + $reader = new Zend_Feed_Reader_Feed_Atom($dom, $type); + } + + return $reader; + } + + /** + * Import a feed froma string + * + * @param string $string + * @return Zend_Feed_Reader_FeedInterface + */ + public static function importString($string) + { + $libxml_errflag = libxml_use_internal_errors(true); + $dom = new DOMDocument; + $status = $dom->loadXML($string); + libxml_use_internal_errors($libxml_errflag); + + if (!$status) { + // Build error message + $error = libxml_get_last_error(); + if ($error && $error->message) { + $errormsg = "DOMDocument cannot parse XML: {$error->message}"; + } else { + $errormsg = "DOMDocument cannot parse XML: Please check the XML document's validity"; + } + + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception($errormsg); + } + + $type = self::detectType($dom); + + self::_registerCoreExtensions(); + + if (substr($type, 0, 3) == 'rss') { + $reader = new Zend_Feed_Reader_Feed_Rss($dom, $type); + } elseif (substr($type, 8, 5) == 'entry') { + $reader = new Zend_Feed_Reader_Entry_Atom($dom->documentElement, 0, Zend_Feed_Reader::TYPE_ATOM_10); + } elseif (substr($type, 0, 4) == 'atom') { + $reader = new Zend_Feed_Reader_Feed_Atom($dom, $type); + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('The URI used does not point to a ' + . 'valid Atom, RSS or RDF feed that Zend_Feed_Reader can parse.'); + } + return $reader; + } + + /** + * Imports a feed from a file located at $filename. + * + * @param string $filename + * @throws Zend_Feed_Exception + * @return Zend_Feed_Reader_FeedInterface + */ + public static function importFile($filename) + { + @ini_set('track_errors', 1); + $feed = @file_get_contents($filename); + @ini_restore('track_errors'); + if ($feed === false) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception("File could not be loaded: $php_errormsg"); + } + return self::importString($feed); + } + + public static function findFeedLinks($uri) + { + // Get the HTTP response from $uri and save the contents + $client = self::getHttpClient(); + $client->setUri($uri); + $response = $client->request(); + if ($response->getStatus() !== 200) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception("Failed to access $uri, got response code " . $response->getStatus()); + } + $responseHtml = $response->getBody(); + $libxml_errflag = libxml_use_internal_errors(true); + $dom = new DOMDocument; + $status = $dom->loadHTML($responseHtml); + libxml_use_internal_errors($libxml_errflag); + if (!$status) { + // Build error message + $error = libxml_get_last_error(); + if ($error && $error->message) { + $errormsg = "DOMDocument cannot parse HTML: {$error->message}"; + } else { + $errormsg = "DOMDocument cannot parse HTML: Please check the XML document's validity"; + } + + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception($errormsg); + } + $feedSet = new Zend_Feed_Reader_FeedSet; + $links = $dom->getElementsByTagName('link'); + $feedSet->addLinks($links, $uri); + return $feedSet; + } + + /** + * Detect the feed type of the provided feed + * + * @param Zend_Feed_Abstract|DOMDocument|string $feed + * @return string + */ + public static function detectType($feed, $specOnly = false) + { + if ($feed instanceof Zend_Feed_Reader_FeedInterface) { + $dom = $feed->getDomDocument(); + } elseif($feed instanceof DOMDocument) { + $dom = $feed; + } elseif(is_string($feed) && !empty($feed)) { + @ini_set('track_errors', 1); + $dom = new DOMDocument; + $status = @$dom->loadXML($feed); + @ini_restore('track_errors'); + if (!$status) { + if (!isset($php_errormsg)) { + if (function_exists('xdebug_is_enabled')) { + $php_errormsg = '(error message not available, when XDebug is running)'; + } else { + $php_errormsg = '(error message not available)'; + } + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception("DOMDocument cannot parse XML: $php_errormsg"); + } + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid object/scalar provided: must' + . ' be of type Zend_Feed_Reader_FeedInterface, DomDocument or string'); + } + $xpath = new DOMXPath($dom); + + if ($xpath->query('/rss')->length) { + $type = self::TYPE_RSS_ANY; + $version = $xpath->evaluate('string(/rss/@version)'); + + if (strlen($version) > 0) { + switch($version) { + case '2.0': + $type = self::TYPE_RSS_20; + break; + + case '0.94': + $type = self::TYPE_RSS_094; + break; + + case '0.93': + $type = self::TYPE_RSS_093; + break; + + case '0.92': + $type = self::TYPE_RSS_092; + break; + + case '0.91': + $type = self::TYPE_RSS_091; + break; + } + } + + return $type; + } + + $xpath->registerNamespace('rdf', self::NAMESPACE_RDF); + + if ($xpath->query('/rdf:RDF')->length) { + $xpath->registerNamespace('rss', self::NAMESPACE_RSS_10); + + if ($xpath->query('/rdf:RDF/rss:channel')->length + || $xpath->query('/rdf:RDF/rss:image')->length + || $xpath->query('/rdf:RDF/rss:item')->length + || $xpath->query('/rdf:RDF/rss:textinput')->length + ) { + return self::TYPE_RSS_10; + } + + $xpath->registerNamespace('rss', self::NAMESPACE_RSS_090); + + if ($xpath->query('/rdf:RDF/rss:channel')->length + || $xpath->query('/rdf:RDF/rss:image')->length + || $xpath->query('/rdf:RDF/rss:item')->length + || $xpath->query('/rdf:RDF/rss:textinput')->length + ) { + return self::TYPE_RSS_090; + } + } + + $type = self::TYPE_ATOM_ANY; + $xpath->registerNamespace('atom', self::NAMESPACE_ATOM_10); + + if ($xpath->query('//atom:feed')->length) { + return self::TYPE_ATOM_10; + } + + if ($xpath->query('//atom:entry')->length) { + if ($specOnly == true) { + return self::TYPE_ATOM_10; + } else { + return self::TYPE_ATOM_10_ENTRY; + } + } + + $xpath->registerNamespace('atom', self::NAMESPACE_ATOM_03); + + if ($xpath->query('//atom:feed')->length) { + return self::TYPE_ATOM_03; + } + + return self::TYPE_ANY; + } + + /** + * Set plugin loader for use with Extensions + * + * @param Zend_Loader_PluginLoader_Interface $loader + */ + public static function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader) + { + self::$_pluginLoader = $loader; + } + + /** + * Get plugin loader for use with Extensions + * + * @return Zend_Loader_PluginLoader_Interface $loader + */ + public static function getPluginLoader() + { + if (!isset(self::$_pluginLoader)) { + // require_once 'Zend/Loader/PluginLoader.php'; + self::$_pluginLoader = new Zend_Loader_PluginLoader(array( + 'Zend_Feed_Reader_Extension_' => 'Zend/Feed/Reader/Extension/', + )); + } + return self::$_pluginLoader; + } + + /** + * Add prefix path for loading Extensions + * + * @param string $prefix + * @param string $path + * @return void + */ + public static function addPrefixPath($prefix, $path) + { + $prefix = rtrim($prefix, '_'); + $path = rtrim($path, DIRECTORY_SEPARATOR); + self::getPluginLoader()->addPrefixPath($prefix, $path); + } + + /** + * Add multiple Extension prefix paths at once + * + * @param array $spec + * @return void + */ + public static function addPrefixPaths(array $spec) + { + if (isset($spec['prefix']) && isset($spec['path'])) { + self::addPrefixPath($spec['prefix'], $spec['path']); + } + foreach ($spec as $prefixPath) { + if (isset($prefixPath['prefix']) && isset($prefixPath['path'])) { + self::addPrefixPath($prefixPath['prefix'], $prefixPath['path']); + } + } + } + + /** + * Register an Extension by name + * + * @param string $name + * @return void + * @throws Zend_Feed_Exception if unable to resolve Extension class + */ + public static function registerExtension($name) + { + $feedName = $name . '_Feed'; + $entryName = $name . '_Entry'; + if (self::isRegistered($name)) { + if (self::getPluginLoader()->isLoaded($feedName) || + self::getPluginLoader()->isLoaded($entryName)) { + return; + } + } + try { + self::getPluginLoader()->load($feedName); + self::$_extensions['feed'][] = $feedName; + } catch (Zend_Loader_PluginLoader_Exception $e) { + } + try { + self::getPluginLoader()->load($entryName); + self::$_extensions['entry'][] = $entryName; + } catch (Zend_Loader_PluginLoader_Exception $e) { + } + if (!self::getPluginLoader()->isLoaded($feedName) + && !self::getPluginLoader()->isLoaded($entryName) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Could not load extension: ' . $name + . 'using Plugin Loader. Check prefix paths are configured and extension exists.'); + } + } + + /** + * Is a given named Extension registered? + * + * @param string $extensionName + * @return boolean + */ + public static function isRegistered($extensionName) + { + $feedName = $extensionName . '_Feed'; + $entryName = $extensionName . '_Entry'; + if (in_array($feedName, self::$_extensions['feed']) + || in_array($entryName, self::$_extensions['entry']) + ) { + return true; + } + return false; + } + + /** + * Get a list of extensions + * + * @return array + */ + public static function getExtensions() + { + return self::$_extensions; + } + + /** + * Reset class state to defaults + * + * @return void + */ + public static function reset() + { + self::$_cache = null; + self::$_httpClient = null; + self::$_httpMethodOverride = false; + self::$_httpConditionalGet = false; + self::$_pluginLoader = null; + self::$_prefixPaths = array(); + self::$_extensions = array( + 'feed' => array( + 'DublinCore_Feed', + 'Atom_Feed' + ), + 'entry' => array( + 'Content_Entry', + 'DublinCore_Entry', + 'Atom_Entry' + ), + 'core' => array( + 'DublinCore_Feed', + 'Atom_Feed', + 'Content_Entry', + 'DublinCore_Entry', + 'Atom_Entry' + ) + ); + } + + /** + * Register core (default) extensions + * + * @return void + */ + protected static function _registerCoreExtensions() + { + self::registerExtension('DublinCore'); + self::registerExtension('Content'); + self::registerExtension('Atom'); + self::registerExtension('Slash'); + self::registerExtension('WellFormedWeb'); + self::registerExtension('Thread'); + self::registerExtension('Podcast'); + } + + /** + * Utility method to apply array_unique operation to a multidimensional + * array. + * + * @param array + * @return array + */ + public static function arrayUnique(array $array) + { + foreach ($array as &$value) { + $value = serialize($value); + } + $array = array_unique($array); + foreach ($array as &$value) { + $value = unserialize($value); + } + return $array; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection.php new file mode 100644 index 00000000..fba2b52b --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection.php @@ -0,0 +1,33 @@ +getIterator() as $element) { + $authors[] = $element['name']; + } + return array_unique($authors); + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection/Category.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection/Category.php new file mode 100644 index 00000000..2587c132 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection/Category.php @@ -0,0 +1,57 @@ +getIterator() as $element) { + if (isset($element['label']) && !empty($element['label'])) { + $categories[] = $element['label']; + } else { + $categories[] = $element['term']; + } + } + return array_unique($categories); + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection/CollectionAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection/CollectionAbstract.php new file mode 100644 index 00000000..7b6dbd03 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Collection/CollectionAbstract.php @@ -0,0 +1,41 @@ +_xpathQuery = '//atom:entry[' . ($this->_entryKey + 1) . ']'; + + $atomClass = Zend_Feed_Reader::getPluginLoader()->getClassName('Atom_Entry'); + $this->_extensions['Atom_Entry'] = new $atomClass($entry, $entryKey, $type); + + $threadClass = Zend_Feed_Reader::getPluginLoader()->getClassName('Thread_Entry'); + $this->_extensions['Thread_Entry'] = new $threadClass($entry, $entryKey, $type); + + $threadClass = Zend_Feed_Reader::getPluginLoader()->getClassName('DublinCore_Entry'); + $this->_extensions['DublinCore_Entry'] = new $threadClass($entry, $entryKey, $type); + } + + /** + * Get the specified author + * + * @param int $index + * @return string|null + */ + public function getAuthor($index = 0) + { + $authors = $this->getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $people = $this->getExtension('Atom')->getAuthors(); + + $this->_data['authors'] = $people; + + return $this->_data['authors']; + } + + /** + * Get the entry content + * + * @return string + */ + public function getContent() + { + if (array_key_exists('content', $this->_data)) { + return $this->_data['content']; + } + + $content = $this->getExtension('Atom')->getContent(); + + $this->_data['content'] = $content; + + return $this->_data['content']; + } + + /** + * Get the entry creation date + * + * @return string + */ + public function getDateCreated() + { + if (array_key_exists('datecreated', $this->_data)) { + return $this->_data['datecreated']; + } + + $dateCreated = $this->getExtension('Atom')->getDateCreated(); + + $this->_data['datecreated'] = $dateCreated; + + return $this->_data['datecreated']; + } + + /** + * Get the entry modification date + * + * @return string + */ + public function getDateModified() + { + if (array_key_exists('datemodified', $this->_data)) { + return $this->_data['datemodified']; + } + + $dateModified = $this->getExtension('Atom')->getDateModified(); + + $this->_data['datemodified'] = $dateModified; + + return $this->_data['datemodified']; + } + + /** + * Get the entry description + * + * @return string + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = $this->getExtension('Atom')->getDescription(); + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the entry enclosure + * + * @return string + */ + public function getEnclosure() + { + if (array_key_exists('enclosure', $this->_data)) { + return $this->_data['enclosure']; + } + + $enclosure = $this->getExtension('Atom')->getEnclosure(); + + $this->_data['enclosure'] = $enclosure; + + return $this->_data['enclosure']; + } + + /** + * Get the entry ID + * + * @return string + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = $this->getExtension('Atom')->getId(); + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get a specific link + * + * @param int $index + * @return string + */ + public function getLink($index = 0) + { + if (!array_key_exists('links', $this->_data)) { + $this->getLinks(); + } + + if (isset($this->_data['links'][$index])) { + return $this->_data['links'][$index]; + } + + return null; + } + + /** + * Get all links + * + * @return array + */ + public function getLinks() + { + if (array_key_exists('links', $this->_data)) { + return $this->_data['links']; + } + + $links = $this->getExtension('Atom')->getLinks(); + + $this->_data['links'] = $links; + + return $this->_data['links']; + } + + /** + * Get a permalink to the entry + * + * @return string + */ + public function getPermalink() + { + return $this->getLink(0); + } + + /** + * Get the entry title + * + * @return string + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = $this->getExtension('Atom')->getTitle(); + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * Get the number of comments/replies for current entry + * + * @return integer + */ + public function getCommentCount() + { + if (array_key_exists('commentcount', $this->_data)) { + return $this->_data['commentcount']; + } + + $commentcount = $this->getExtension('Thread')->getCommentCount(); + + if (!$commentcount) { + $commentcount = $this->getExtension('Atom')->getCommentCount(); + } + + $this->_data['commentcount'] = $commentcount; + + return $this->_data['commentcount']; + } + + /** + * Returns a URI pointing to the HTML page where comments can be made on this entry + * + * @return string + */ + public function getCommentLink() + { + if (array_key_exists('commentlink', $this->_data)) { + return $this->_data['commentlink']; + } + + $commentlink = $this->getExtension('Atom')->getCommentLink(); + + $this->_data['commentlink'] = $commentlink; + + return $this->_data['commentlink']; + } + + /** + * Returns a URI pointing to a feed of all comments for this entry + * + * @return string + */ + public function getCommentFeedLink() + { + if (array_key_exists('commentfeedlink', $this->_data)) { + return $this->_data['commentfeedlink']; + } + + $commentfeedlink = $this->getExtension('Atom')->getCommentFeedLink(); + + $this->_data['commentfeedlink'] = $commentfeedlink; + + return $this->_data['commentfeedlink']; + } + + /** + * Get category data as a Zend_Feed_Reader_Collection_Category object + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + $categoryCollection = $this->getExtension('Atom')->getCategories(); + + if (count($categoryCollection) == 0) { + $categoryCollection = $this->getExtension('DublinCore')->getCategories(); + } + + $this->_data['categories'] = $categoryCollection; + + return $this->_data['categories']; + } + + /** + * Get source feed metadata from the entry + * + * @return Zend_Feed_Reader_Feed_Atom_Source|null + */ + public function getSource() + { + if (array_key_exists('source', $this->_data)) { + return $this->_data['source']; + } + + $source = $this->getExtension('Atom')->getSource(); + + $this->_data['source'] = $source; + + return $this->_data['source']; + } + + /** + * Set the XPath query (incl. on all Extensions) + * + * @param DOMXPath $xpath + */ + public function setXpath(DOMXPath $xpath) + { + parent::setXpath($xpath); + foreach ($this->_extensions as $extension) { + $extension->setXpath($this->_xpath); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Entry/Rss.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Entry/Rss.php new file mode 100644 index 00000000..21a30a98 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Entry/Rss.php @@ -0,0 +1,668 @@ +_xpathQueryRss = '//item[' . ($this->_entryKey+1) . ']'; + $this->_xpathQueryRdf = '//rss:item[' . ($this->_entryKey+1) . ']'; + + $pluginLoader = Zend_Feed_Reader::getPluginLoader(); + + $dublinCoreClass = $pluginLoader->getClassName('DublinCore_Entry'); + $this->_extensions['DublinCore_Entry'] = new $dublinCoreClass($entry, $entryKey, $type); + + $contentClass = $pluginLoader->getClassName('Content_Entry'); + $this->_extensions['Content_Entry'] = new $contentClass($entry, $entryKey, $type); + + $atomClass = $pluginLoader->getClassName('Atom_Entry'); + $this->_extensions['Atom_Entry'] = new $atomClass($entry, $entryKey, $type); + + $wfwClass = $pluginLoader->getClassName('WellFormedWeb_Entry'); + $this->_extensions['WellFormedWeb_Entry'] = new $wfwClass($entry, $entryKey, $type); + + $slashClass = $pluginLoader->getClassName('Slash_Entry'); + $this->_extensions['Slash_Entry'] = new $slashClass($entry, $entryKey, $type); + + $threadClass = $pluginLoader->getClassName('Thread_Entry'); + $this->_extensions['Thread_Entry'] = new $threadClass($entry, $entryKey, $type); + } + + /** + * Get an author entry + * + * @param DOMElement $element + * @return string + */ + public function getAuthor($index = 0) + { + $authors = $this->getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $authors = array(); + $authors_dc = $this->getExtension('DublinCore')->getAuthors(); + if (!empty($authors_dc)) { + foreach ($authors_dc as $author) { + $authors[] = array( + 'name' => $author['name'] + ); + } + } + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $list = $this->_xpath->query($this->_xpathQueryRss . '//author'); + } else { + $list = $this->_xpath->query($this->_xpathQueryRdf . '//rss:author'); + } + if ($list->length) { + foreach ($list as $author) { + $string = trim($author->nodeValue); + $email = null; + $name = null; + $data = array(); + // Pretty rough parsing - but it's a catchall + if (preg_match("/^.*@[^ ]*/", $string, $matches)) { + $data['email'] = trim($matches[0]); + if (preg_match("/\((.*)\)$/", $string, $matches)) { + $data['name'] = $matches[1]; + } + $authors[] = $data; + } + } + } + + if (count($authors) == 0) { + $authors = $this->getExtension('Atom')->getAuthors(); + } else { + $authors = new Zend_Feed_Reader_Collection_Author( + Zend_Feed_Reader::arrayUnique($authors) + ); + } + + if (count($authors) == 0) { + $authors = null; + } + + $this->_data['authors'] = $authors; + + return $this->_data['authors']; + } + + /** + * Get the entry content + * + * @return string + */ + public function getContent() + { + if (array_key_exists('content', $this->_data)) { + return $this->_data['content']; + } + + $content = $this->getExtension('Content')->getContent(); + + if (!$content) { + $content = $this->getDescription(); + } + + if (empty($content)) { + $content = $this->getExtension('Atom')->getContent(); + } + + $this->_data['content'] = $content; + + return $this->_data['content']; + } + + /** + * Get the entry's date of creation + * + * @return string + */ + public function getDateCreated() + { + return $this->getDateModified(); + } + + /** + * Get the entry's date of modification + * + * @return string + */ + public function getDateModified() + { + if (array_key_exists('datemodified', $this->_data)) { + return $this->_data['datemodified']; + } + + $dateModified = null; + $date = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090 + ) { + $dateModified = $this->_xpath->evaluate('string('.$this->_xpathQueryRss.'/pubDate)'); + if ($dateModified) { + $dateModifiedParsed = strtotime($dateModified); + if ($dateModifiedParsed) { + $date = new Zend_Date($dateModifiedParsed); + } else { + $dateStandards = array(Zend_Date::RSS, Zend_Date::RFC_822, + Zend_Date::RFC_2822, Zend_Date::DATES); + $date = new Zend_Date; + foreach ($dateStandards as $standard) { + try { + $date->set($dateModified, $standard); + break; + } catch (Zend_Date_Exception $e) { + if ($standard == Zend_Date::DATES) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception( + 'Could not load date due to unrecognised' + .' format (should follow RFC 822 or 2822):' + . $e->getMessage(), + 0, $e + ); + } + } + } + } + } + } + + if (!$date) { + $date = $this->getExtension('DublinCore')->getDate(); + } + + if (!$date) { + $date = $this->getExtension('Atom')->getDateModified(); + } + + if (!$date) { + $date = null; + } + + $this->_data['datemodified'] = $date; + + return $this->_data['datemodified']; + } + + /** + * Get the entry description + * + * @return string + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090 + ) { + $description = $this->_xpath->evaluate('string('.$this->_xpathQueryRss.'/description)'); + } else { + $description = $this->_xpath->evaluate('string('.$this->_xpathQueryRdf.'/rss:description)'); + } + + if (!$description) { + $description = $this->getExtension('DublinCore')->getDescription(); + } + + if (empty($description)) { + $description = $this->getExtension('Atom')->getDescription(); + } + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the entry enclosure + * @return string + */ + public function getEnclosure() + { + if (array_key_exists('enclosure', $this->_data)) { + return $this->_data['enclosure']; + } + + $enclosure = null; + + if ($this->getType() == Zend_Feed_Reader::TYPE_RSS_20) { + $nodeList = $this->_xpath->query($this->_xpathQueryRss . '/enclosure'); + + if ($nodeList->length > 0) { + $enclosure = new stdClass(); + $enclosure->url = $nodeList->item(0)->getAttribute('url'); + $enclosure->length = $nodeList->item(0)->getAttribute('length'); + $enclosure->type = $nodeList->item(0)->getAttribute('type'); + } + } + + if (!$enclosure) { + $enclosure = $this->getExtension('Atom')->getEnclosure(); + } + + $this->_data['enclosure'] = $enclosure; + + return $this->_data['enclosure']; + } + + /** + * Get the entry ID + * + * @return string + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090 + ) { + $id = $this->_xpath->evaluate('string('.$this->_xpathQueryRss.'/guid)'); + } + + if (!$id) { + $id = $this->getExtension('DublinCore')->getId(); + } + + if (empty($id)) { + $id = $this->getExtension('Atom')->getId(); + } + + if (!$id) { + if ($this->getPermalink()) { + $id = $this->getPermalink(); + } elseif ($this->getTitle()) { + $id = $this->getTitle(); + } else { + $id = null; + } + } + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get a specific link + * + * @param int $index + * @return string + */ + public function getLink($index = 0) + { + if (!array_key_exists('links', $this->_data)) { + $this->getLinks(); + } + + if (isset($this->_data['links'][$index])) { + return $this->_data['links'][$index]; + } + + return null; + } + + /** + * Get all links + * + * @return array + */ + public function getLinks() + { + if (array_key_exists('links', $this->_data)) { + return $this->_data['links']; + } + + $links = array(); + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $list = $this->_xpath->query($this->_xpathQueryRss.'//link'); + } else { + $list = $this->_xpath->query($this->_xpathQueryRdf.'//rss:link'); + } + + if (!$list->length) { + $links = $this->getExtension('Atom')->getLinks(); + } else { + foreach ($list as $link) { + $links[] = $link->nodeValue; + } + } + + $this->_data['links'] = $links; + + return $this->_data['links']; + } + + /** + * Get all categories + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $list = $this->_xpath->query($this->_xpathQueryRss.'//category'); + } else { + $list = $this->_xpath->query($this->_xpathQueryRdf.'//rss:category'); + } + + if ($list->length) { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + foreach ($list as $category) { + $categoryCollection[] = array( + 'term' => $category->nodeValue, + 'scheme' => $category->getAttribute('domain'), + 'label' => $category->nodeValue, + ); + } + } else { + $categoryCollection = $this->getExtension('DublinCore')->getCategories(); + } + + if (count($categoryCollection) == 0) { + $categoryCollection = $this->getExtension('Atom')->getCategories(); + } + + $this->_data['categories'] = $categoryCollection; + + return $this->_data['categories']; + } + + /** + * Get a permalink to the entry + * + * @return string + */ + public function getPermalink() + { + return $this->getLink(0); + } + + /** + * Get the entry title + * + * @return string + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090 + ) { + $title = $this->_xpath->evaluate('string('.$this->_xpathQueryRss.'/title)'); + } else { + $title = $this->_xpath->evaluate('string('.$this->_xpathQueryRdf.'/rss:title)'); + } + + if (!$title) { + $title = $this->getExtension('DublinCore')->getTitle(); + } + + if (!$title) { + $title = $this->getExtension('Atom')->getTitle(); + } + + if (!$title) { + $title = null; + } + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * Get the number of comments/replies for current entry + * + * @return string|null + */ + public function getCommentCount() + { + if (array_key_exists('commentcount', $this->_data)) { + return $this->_data['commentcount']; + } + + $commentcount = $this->getExtension('Slash')->getCommentCount(); + + if (!$commentcount) { + $commentcount = $this->getExtension('Thread')->getCommentCount(); + } + + if (!$commentcount) { + $commentcount = $this->getExtension('Atom')->getCommentCount(); + } + + if (!$commentcount) { + $commentcount = null; + } + + $this->_data['commentcount'] = $commentcount; + + return $this->_data['commentcount']; + } + + /** + * Returns a URI pointing to the HTML page where comments can be made on this entry + * + * @return string + */ + public function getCommentLink() + { + if (array_key_exists('commentlink', $this->_data)) { + return $this->_data['commentlink']; + } + + $commentlink = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090 + ) { + $commentlink = $this->_xpath->evaluate('string('.$this->_xpathQueryRss.'/comments)'); + } + + if (!$commentlink) { + $commentlink = $this->getExtension('Atom')->getCommentLink(); + } + + if (!$commentlink) { + $commentlink = null; + } + + $this->_data['commentlink'] = $commentlink; + + return $this->_data['commentlink']; + } + + /** + * Returns a URI pointing to a feed of all comments for this entry + * + * @return string + */ + public function getCommentFeedLink() + { + if (array_key_exists('commentfeedlink', $this->_data)) { + return $this->_data['commentfeedlink']; + } + + $commentfeedlink = $this->getExtension('WellFormedWeb')->getCommentFeedLink(); + + if (!$commentfeedlink) { + $commentfeedlink = $this->getExtension('Atom')->getCommentFeedLink('rss'); + } + + if (!$commentfeedlink) { + $commentfeedlink = $this->getExtension('Atom')->getCommentFeedLink('rdf'); + } + + if (!$commentfeedlink) { + $commentfeedlink = null; + } + + $this->_data['commentfeedlink'] = $commentfeedlink; + + return $this->_data['commentfeedlink']; + } + + /** + * Set the XPath query (incl. on all Extensions) + * + * @param DOMXPath $xpath + */ + public function setXpath(DOMXPath $xpath) + { + parent::setXpath($xpath); + foreach ($this->_extensions as $extension) { + $extension->setXpath($this->_xpath); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/EntryAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/EntryAbstract.php new file mode 100644 index 00000000..f73e1d1e --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/EntryAbstract.php @@ -0,0 +1,238 @@ +_entry = $entry; + $this->_entryKey = $entryKey; + $this->_domDocument = $entry->ownerDocument; + if ($type !== null) { + $this->_data['type'] = $type; + } else { + $this->_data['type'] = Zend_Feed_Reader::detectType($feed); + } + $this->_loadExtensions(); + } + + /** + * Get the DOM + * + * @return DOMDocument + */ + public function getDomDocument() + { + return $this->_domDocument; + } + + /** + * Get the entry element + * + * @return DOMElement + */ + public function getElement() + { + return $this->_entry; + } + + /** + * Get the Entry's encoding + * + * @return string + */ + public function getEncoding() + { + $assumed = $this->getDomDocument()->encoding; + if (empty($assumed)) { + $assumed = 'UTF-8'; + } + return $assumed; + } + + /** + * Get entry as xml + * + * @return string + */ + public function saveXml() + { + $dom = new DOMDocument('1.0', $this->getEncoding()); + $entry = $dom->importNode($this->getElement(), true); + $dom->appendChild($entry); + return $dom->saveXml(); + } + + /** + * Get the entry type + * + * @return string + */ + public function getType() + { + return $this->_data['type']; + } + + /** + * Get the XPath query object + * + * @return DOMXPath + */ + public function getXpath() + { + if (!$this->_xpath) { + $this->setXpath(new DOMXPath($this->getDomDocument())); + } + return $this->_xpath; + } + + /** + * Set the XPath query + * + * @param DOMXPath $xpath + * @return Zend_Feed_Reader_Entry_EntryAbstract + */ + public function setXpath(DOMXPath $xpath) + { + $this->_xpath = $xpath; + return $this; + } + + /** + * Get registered extensions + * + * @return array + */ + public function getExtensions() + { + return $this->_extensions; + } + + /** + * Return an Extension object with the matching name (postfixed with _Entry) + * + * @param string $name + * @return Zend_Feed_Reader_Extension_EntryAbstract + */ + public function getExtension($name) + { + if (array_key_exists($name . '_Entry', $this->_extensions)) { + return $this->_extensions[$name . '_Entry']; + } + return null; + } + + /** + * Method overloading: call given method on first extension implementing it + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Feed_Exception if no extensions implements the method + */ + public function __call($method, $args) + { + foreach ($this->_extensions as $extension) { + if (method_exists($extension, $method)) { + return call_user_func_array(array($extension, $method), $args); + } + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Method: ' . $method + . 'does not exist and could not be located on a registered Extension'); + } + + /** + * Load extensions from Zend_Feed_Reader + * + * @return void + */ + protected function _loadExtensions() + { + $all = Zend_Feed_Reader::getExtensions(); + $feed = $all['entry']; + foreach ($feed as $extension) { + if (in_array($extension, $all['core'])) { + continue; + } + $className = Zend_Feed_Reader::getPluginLoader()->getClassName($extension); + $this->_extensions[$extension] = new $className( + $this->getElement(), $this->_entryKey, $this->_data['type'] + ); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/EntryInterface.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/EntryInterface.php new file mode 100644 index 00000000..392a533a --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/EntryInterface.php @@ -0,0 +1,143 @@ +getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $authors = array(); + $list = $this->getXpath()->query($this->getXpathPrefix() . '//atom:author'); + + if (!$list->length) { + /** + * TODO: Limit query to feed level els only! + */ + $list = $this->getXpath()->query('//atom:author'); + } + + if ($list->length) { + foreach ($list as $author) { + $author = $this->_getAuthor($author); + if (!empty($author)) { + $authors[] = $author; + } + } + } + + if (count($authors) == 0) { + $authors = null; + } else { + $authors = new Zend_Feed_Reader_Collection_Author( + Zend_Feed_Reader::arrayUnique($authors) + ); + } + + $this->_data['authors'] = $authors; + return $this->_data['authors']; + } + + /** + * Get the entry content + * + * @return string + */ + public function getContent() + { + if (array_key_exists('content', $this->_data)) { + return $this->_data['content']; + } + + $content = null; + + $el = $this->getXpath()->query($this->getXpathPrefix() . '/atom:content'); + if($el->length > 0) { + $el = $el->item(0); + $type = $el->getAttribute('type'); + switch ($type) { + case '': + case 'text': + case 'text/plain': + case 'html': + case 'text/html': + $content = $el->nodeValue; + break; + case 'xhtml': + $this->getXpath()->registerNamespace('xhtml', 'http://www.w3.org/1999/xhtml'); + $xhtml = $this->getXpath()->query( + $this->getXpathPrefix() . '/atom:content/xhtml:div' + )->item(0); + //$xhtml->setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $d = new DOMDocument('1.0', $this->getEncoding()); + $xhtmls = $d->importNode($xhtml, true); + $d->appendChild($xhtmls); + $content = $this->_collectXhtml( + $d->saveXML(), + $d->lookupPrefix('http://www.w3.org/1999/xhtml') + ); + break; + } + } + + //var_dump($content); exit; + + if (!$content) { + $content = $this->getDescription(); + } + + $this->_data['content'] = trim($content); + + return $this->_data['content']; + } + + /** + * Parse out XHTML to remove the namespacing + */ + protected function _collectXhtml($xhtml, $prefix) + { + if (!empty($prefix)) $prefix = $prefix . ':'; + $matches = array( + "/<\?xml[^<]*>[^<]*<" . $prefix . "div[^<]*/", + "/<\/" . $prefix . "div>\s*$/" + ); + $xhtml = preg_replace($matches, '', $xhtml); + if (!empty($prefix)) { + $xhtml = preg_replace("/(<[\/]?)" . $prefix . "([a-zA-Z]+)/", '$1$2', $xhtml); + } + return $xhtml; + } + + /** + * Get the entry creation date + * + * @return string + */ + public function getDateCreated() + { + if (array_key_exists('datecreated', $this->_data)) { + return $this->_data['datecreated']; + } + + $date = null; + + if ($this->_getAtomType() === Zend_Feed_Reader::TYPE_ATOM_03) { + $dateCreated = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:created)'); + } else { + $dateCreated = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:published)'); + } + + if ($dateCreated) { + $date = new Zend_Date; + $date->set($dateCreated, Zend_Date::ISO_8601); + } + + $this->_data['datecreated'] = $date; + + return $this->_data['datecreated']; + } + + /** + * Get the entry modification date + * + * @return string + */ + public function getDateModified() + { + if (array_key_exists('datemodified', $this->_data)) { + return $this->_data['datemodified']; + } + + $date = null; + + if ($this->_getAtomType() === Zend_Feed_Reader::TYPE_ATOM_03) { + $dateModified = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:modified)'); + } else { + $dateModified = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:updated)'); + } + + if ($dateModified) { + $date = new Zend_Date; + $date->set($dateModified, Zend_Date::ISO_8601); + } + + $this->_data['datemodified'] = $date; + + return $this->_data['datemodified']; + } + + /** + * Get the entry description + * + * @return string + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:summary)'); + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the entry enclosure + * + * @return string + */ + public function getEnclosure() + { + if (array_key_exists('enclosure', $this->_data)) { + return $this->_data['enclosure']; + } + + $enclosure = null; + + $nodeList = $this->getXpath()->query($this->getXpathPrefix() . '/atom:link[@rel="enclosure"]'); + + if ($nodeList->length > 0) { + $enclosure = new stdClass(); + $enclosure->url = $nodeList->item(0)->getAttribute('href'); + $enclosure->length = $nodeList->item(0)->getAttribute('length'); + $enclosure->type = $nodeList->item(0)->getAttribute('type'); + } + + $this->_data['enclosure'] = $enclosure; + + return $this->_data['enclosure']; + } + + /** + * Get the entry ID + * + * @return string + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:id)'); + + if (!$id) { + if ($this->getPermalink()) { + $id = $this->getPermalink(); + } elseif ($this->getTitle()) { + $id = $this->getTitle(); + } else { + $id = null; + } + } + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get the base URI of the feed (if set). + * + * @return string|null + */ + public function getBaseUrl() + { + if (array_key_exists('baseUrl', $this->_data)) { + return $this->_data['baseUrl']; + } + + $baseUrl = $this->getXpath()->evaluate('string(' + . $this->getXpathPrefix() . '/@xml:base[1]' + . ')'); + + if (!$baseUrl) { + $baseUrl = $this->getXpath()->evaluate('string(//@xml:base[1])'); + } + + if (!$baseUrl) { + $baseUrl = null; + } + + $this->_data['baseUrl'] = $baseUrl; + + return $this->_data['baseUrl']; + } + + /** + * Get a specific link + * + * @param int $index + * @return string + */ + public function getLink($index = 0) + { + if (!array_key_exists('links', $this->_data)) { + $this->getLinks(); + } + + if (isset($this->_data['links'][$index])) { + return $this->_data['links'][$index]; + } + + return null; + } + + /** + * Get all links + * + * @return array + */ + public function getLinks() + { + if (array_key_exists('links', $this->_data)) { + return $this->_data['links']; + } + + $links = array(); + + $list = $this->getXpath()->query( + $this->getXpathPrefix() . '//atom:link[@rel="alternate"]/@href' . '|' . + $this->getXpathPrefix() . '//atom:link[not(@rel)]/@href' + ); + + if ($list->length) { + foreach ($list as $link) { + $links[] = $this->_absolutiseUri($link->value); + } + } + + $this->_data['links'] = $links; + + return $this->_data['links']; + } + + /** + * Get a permalink to the entry + * + * @return string + */ + public function getPermalink() + { + return $this->getLink(0); + } + + /** + * Get the entry title + * + * @return string + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = $this->getXpath()->evaluate('string(' . $this->getXpathPrefix() . '/atom:title)'); + + if (!$title) { + $title = null; + } + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * Get the number of comments/replies for current entry + * + * @return integer + */ + public function getCommentCount() + { + if (array_key_exists('commentcount', $this->_data)) { + return $this->_data['commentcount']; + } + + $count = null; + + $this->getXpath()->registerNamespace('thread10', 'http://purl.org/syndication/thread/1.0'); + $list = $this->getXpath()->query( + $this->getXpathPrefix() . '//atom:link[@rel="replies"]/@thread10:count' + ); + + if ($list->length) { + $count = $list->item(0)->value; + } + + $this->_data['commentcount'] = $count; + + return $this->_data['commentcount']; + } + + /** + * Returns a URI pointing to the HTML page where comments can be made on this entry + * + * @return string + */ + public function getCommentLink() + { + if (array_key_exists('commentlink', $this->_data)) { + return $this->_data['commentlink']; + } + + $link = null; + + $list = $this->getXpath()->query( + $this->getXpathPrefix() . '//atom:link[@rel="replies" and @type="text/html"]/@href' + ); + + if ($list->length) { + $link = $list->item(0)->value; + $link = $this->_absolutiseUri($link); + } + + $this->_data['commentlink'] = $link; + + return $this->_data['commentlink']; + } + + /** + * Returns a URI pointing to a feed of all comments for this entry + * + * @return string + */ + public function getCommentFeedLink($type = 'atom') + { + if (array_key_exists('commentfeedlink', $this->_data)) { + return $this->_data['commentfeedlink']; + } + + $link = null; + + $list = $this->getXpath()->query( + $this->getXpathPrefix() . '//atom:link[@rel="replies" and @type="application/'.$type.'+xml"]/@href' + ); + + if ($list->length) { + $link = $list->item(0)->value; + $link = $this->_absolutiseUri($link); + } + + $this->_data['commentfeedlink'] = $link; + + return $this->_data['commentfeedlink']; + } + + /** + * Get all categories + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + if ($this->_getAtomType() == Zend_Feed_Reader::TYPE_ATOM_10) { + $list = $this->getXpath()->query($this->getXpathPrefix() . '//atom:category'); + } else { + /** + * Since Atom 0.3 did not support categories, it would have used the + * Dublin Core extension. However there is a small possibility Atom 0.3 + * may have been retrofittied to use Atom 1.0 instead. + */ + $this->getXpath()->registerNamespace('atom10', Zend_Feed_Reader::NAMESPACE_ATOM_10); + $list = $this->getXpath()->query($this->getXpathPrefix() . '//atom10:category'); + } + + if ($list->length) { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + foreach ($list as $category) { + $categoryCollection[] = array( + 'term' => $category->getAttribute('term'), + 'scheme' => $category->getAttribute('scheme'), + 'label' => $category->getAttribute('label') + ); + } + } else { + return new Zend_Feed_Reader_Collection_Category; + } + + $this->_data['categories'] = $categoryCollection; + + return $this->_data['categories']; + } + + /** + * Get source feed metadata from the entry + * + * @return Zend_Feed_Reader_Feed_Atom_Source|null + */ + public function getSource() + { + if (array_key_exists('source', $this->_data)) { + return $this->_data['source']; + } + + $source = null; + // TODO: Investigate why _getAtomType() fails here. Is it even needed? + if ($this->getType() == Zend_Feed_Reader::TYPE_ATOM_10) { + $list = $this->getXpath()->query($this->getXpathPrefix() . '/atom:source[1]'); + if ($list->length) { + $element = $list->item(0); + $source = new Zend_Feed_Reader_Feed_Atom_Source($element, $this->getXpathPrefix()); + } + } + + $this->_data['source'] = $source; + return $this->_data['source']; + } + + /** + * Attempt to absolutise the URI, i.e. if a relative URI apply the + * xml:base value as a prefix to turn into an absolute URI. + */ + protected function _absolutiseUri($link) + { + if (!Zend_Uri::check($link)) { + if (!is_null($this->getBaseUrl())) { + $link = $this->getBaseUrl() . $link; + if (!Zend_Uri::check($link)) { + $link = null; + } + } + } + return $link; + } + + /** + * Get an author entry + * + * @param DOMElement $element + * @return string + */ + protected function _getAuthor(DOMElement $element) + { + $author = array(); + + $emailNode = $element->getElementsByTagName('email'); + $nameNode = $element->getElementsByTagName('name'); + $uriNode = $element->getElementsByTagName('uri'); + + if ($emailNode->length && strlen($emailNode->item(0)->nodeValue) > 0) { + $author['email'] = $emailNode->item(0)->nodeValue; + } + + if ($nameNode->length && strlen($nameNode->item(0)->nodeValue) > 0) { + $author['name'] = $nameNode->item(0)->nodeValue; + } + + if ($uriNode->length && strlen($uriNode->item(0)->nodeValue) > 0) { + $author['uri'] = $uriNode->item(0)->nodeValue; + } + + if (empty($author)) { + return null; + } + return $author; + } + + /** + * Register the default namespaces for the current feed format + */ + protected function _registerNamespaces() + { + switch ($this->_getAtomType()) { + case Zend_Feed_Reader::TYPE_ATOM_03: + $this->getXpath()->registerNamespace('atom', Zend_Feed_Reader::NAMESPACE_ATOM_03); + break; + default: + $this->getXpath()->registerNamespace('atom', Zend_Feed_Reader::NAMESPACE_ATOM_10); + break; + } + } + + /** + * Detect the presence of any Atom namespaces in use + */ + protected function _getAtomType() + { + $dom = $this->getDomDocument(); + $prefixAtom03 = $dom->lookupPrefix(Zend_Feed_Reader::NAMESPACE_ATOM_03); + $prefixAtom10 = $dom->lookupPrefix(Zend_Feed_Reader::NAMESPACE_ATOM_10); + if ($dom->isDefaultNamespace(Zend_Feed_Reader::NAMESPACE_ATOM_03) + || !empty($prefixAtom03)) { + return Zend_Feed_Reader::TYPE_ATOM_03; + } + if ($dom->isDefaultNamespace(Zend_Feed_Reader::NAMESPACE_ATOM_10) + || !empty($prefixAtom10)) { + return Zend_Feed_Reader::TYPE_ATOM_10; + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Atom/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Atom/Feed.php new file mode 100644 index 00000000..3e0e2079 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Atom/Feed.php @@ -0,0 +1,566 @@ +getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $list = $this->_xpath->query('//atom:author'); + + $authors = array(); + + if ($list->length) { + foreach ($list as $author) { + $author = $this->_getAuthor($author); + if (!empty($author)) { + $authors[] = $author; + } + } + } + + if (count($authors) == 0) { + $authors = null; + } else { + $authors = new Zend_Feed_Reader_Collection_Author( + Zend_Feed_Reader::arrayUnique($authors) + ); + } + + $this->_data['authors'] = $authors; + + return $this->_data['authors']; + } + + /** + * Get the copyright entry + * + * @return string|null + */ + public function getCopyright() + { + if (array_key_exists('copyright', $this->_data)) { + return $this->_data['copyright']; + } + + $copyright = null; + + if ($this->getType() === Zend_Feed_Reader::TYPE_ATOM_03) { + $copyright = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:copyright)'); + } else { + $copyright = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:rights)'); + } + + if (!$copyright) { + $copyright = null; + } + + $this->_data['copyright'] = $copyright; + + return $this->_data['copyright']; + } + + /** + * Get the feed creation date + * + * @return Zend_Date|null + */ + public function getDateCreated() + { + if (array_key_exists('datecreated', $this->_data)) { + return $this->_data['datecreated']; + } + + $date = null; + + if ($this->getType() === Zend_Feed_Reader::TYPE_ATOM_03) { + $dateCreated = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:created)'); + } else { + $dateCreated = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:published)'); + } + + if ($dateCreated) { + $date = new Zend_Date; + $date->set($dateCreated, Zend_Date::ISO_8601); + } + + $this->_data['datecreated'] = $date; + + return $this->_data['datecreated']; + } + + /** + * Get the feed modification date + * + * @return Zend_Date|null + */ + public function getDateModified() + { + if (array_key_exists('datemodified', $this->_data)) { + return $this->_data['datemodified']; + } + + $date = null; + + if ($this->getType() === Zend_Feed_Reader::TYPE_ATOM_03) { + $dateModified = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:modified)'); + } else { + $dateModified = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:updated)'); + } + + if ($dateModified) { + $date = new Zend_Date; + $date->set($dateModified, Zend_Date::ISO_8601); + } + + $this->_data['datemodified'] = $date; + + return $this->_data['datemodified']; + } + + /** + * Get the feed description + * + * @return string|null + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = null; + + if ($this->getType() === Zend_Feed_Reader::TYPE_ATOM_03) { + $description = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:tagline)'); // TODO: Is this the same as subtitle? + } else { + $description = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:subtitle)'); + } + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the feed generator entry + * + * @return string|null + */ + public function getGenerator() + { + if (array_key_exists('generator', $this->_data)) { + return $this->_data['generator']; + } + // TODO: Add uri support + $generator = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:generator)'); + + if (!$generator) { + $generator = null; + } + + $this->_data['generator'] = $generator; + + return $this->_data['generator']; + } + + /** + * Get the feed ID + * + * @return string|null + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:id)'); + + if (!$id) { + if ($this->getLink()) { + $id = $this->getLink(); + } elseif ($this->getTitle()) { + $id = $this->getTitle(); + } else { + $id = null; + } + } + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get the feed language + * + * @return string|null + */ + public function getLanguage() + { + if (array_key_exists('language', $this->_data)) { + return $this->_data['language']; + } + + $language = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:lang)'); + + if (!$language) { + $language = $this->_xpath->evaluate('string(//@xml:lang[1])'); + } + + if (!$language) { + $language = null; + } + + $this->_data['language'] = $language; + + return $this->_data['language']; + } + + /** + * Get the feed image + * + * @return array|null + */ + public function getImage() + { + if (array_key_exists('image', $this->_data)) { + return $this->_data['image']; + } + + $imageUrl = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:logo)'); + + if (!$imageUrl) { + $image = null; + } else { + $image = array('uri'=>$imageUrl); + } + + $this->_data['image'] = $image; + + return $this->_data['image']; + } + + /** + * Get the base URI of the feed (if set). + * + * @return string|null + */ + public function getBaseUrl() + { + if (array_key_exists('baseUrl', $this->_data)) { + return $this->_data['baseUrl']; + } + + $baseUrl = $this->_xpath->evaluate('string(//@xml:base[1])'); + + if (!$baseUrl) { + $baseUrl = null; + } + $this->_data['baseUrl'] = $baseUrl; + + return $this->_data['baseUrl']; + } + + /** + * Get a link to the source website + * + * @return string|null + */ + public function getLink() + { + if (array_key_exists('link', $this->_data)) { + return $this->_data['link']; + } + + $link = null; + + $list = $this->_xpath->query( + $this->getXpathPrefix() . '/atom:link[@rel="alternate"]/@href' . '|' . + $this->getXpathPrefix() . '/atom:link[not(@rel)]/@href' + ); + + if ($list->length) { + $link = $list->item(0)->nodeValue; + $link = $this->_absolutiseUri($link); + } + + $this->_data['link'] = $link; + + return $this->_data['link']; + } + + /** + * Get a link to the feed's XML Url + * + * @return string|null + */ + public function getFeedLink() + { + if (array_key_exists('feedlink', $this->_data)) { + return $this->_data['feedlink']; + } + + $link = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:link[@rel="self"]/@href)'); + + $link = $this->_absolutiseUri($link); + + $this->_data['feedlink'] = $link; + + return $this->_data['feedlink']; + } + + /** + * Get an array of any supported Pusubhubbub endpoints + * + * @return array|null + */ + public function getHubs() + { + if (array_key_exists('hubs', $this->_data)) { + return $this->_data['hubs']; + } + $hubs = array(); + + $list = $this->_xpath->query($this->getXpathPrefix() + . '//atom:link[@rel="hub"]/@href'); + + if ($list->length) { + foreach ($list as $uri) { + $hubs[] = $this->_absolutiseUri($uri->nodeValue); + } + } else { + $hubs = null; + } + + $this->_data['hubs'] = $hubs; + + return $this->_data['hubs']; + } + + /** + * Get the feed title + * + * @return string|null + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/atom:title)'); + + if (!$title) { + $title = null; + } + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * Get all categories + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + if ($this->getType() == Zend_Feed_Reader::TYPE_ATOM_10) { + $list = $this->_xpath->query($this->getXpathPrefix() . '/atom:category'); + } else { + /** + * Since Atom 0.3 did not support categories, it would have used the + * Dublin Core extension. However there is a small possibility Atom 0.3 + * may have been retrofittied to use Atom 1.0 instead. + */ + $this->_xpath->registerNamespace('atom10', Zend_Feed_Reader::NAMESPACE_ATOM_10); + $list = $this->_xpath->query($this->getXpathPrefix() . '/atom10:category'); + } + + if ($list->length) { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + foreach ($list as $category) { + $categoryCollection[] = array( + 'term' => $category->getAttribute('term'), + 'scheme' => $category->getAttribute('scheme'), + 'label' => $category->getAttribute('label') + ); + } + } else { + return new Zend_Feed_Reader_Collection_Category; + } + + $this->_data['categories'] = $categoryCollection; + + return $this->_data['categories']; + } + + /** + * Get an author entry in RSS format + * + * @param DOMElement $element + * @return string + */ + protected function _getAuthor(DOMElement $element) + { + $author = array(); + + $emailNode = $element->getElementsByTagName('email'); + $nameNode = $element->getElementsByTagName('name'); + $uriNode = $element->getElementsByTagName('uri'); + + if ($emailNode->length && strlen($emailNode->item(0)->nodeValue) > 0) { + $author['email'] = $emailNode->item(0)->nodeValue; + } + + if ($nameNode->length && strlen($nameNode->item(0)->nodeValue) > 0) { + $author['name'] = $nameNode->item(0)->nodeValue; + } + + if ($uriNode->length && strlen($uriNode->item(0)->nodeValue) > 0) { + $author['uri'] = $uriNode->item(0)->nodeValue; + } + + if (empty($author)) { + return null; + } + return $author; + } + + /** + * Attempt to absolutise the URI, i.e. if a relative URI apply the + * xml:base value as a prefix to turn into an absolute URI. + */ + protected function _absolutiseUri($link) + { + if (!Zend_Uri::check($link)) { + if (!is_null($this->getBaseUrl())) { + $link = $this->getBaseUrl() . $link; + if (!Zend_Uri::check($link)) { + $link = null; + } + } + } + return $link; + } + + /** + * Register the default namespaces for the current feed format + */ + protected function _registerNamespaces() + { + if ($this->getType() == Zend_Feed_Reader::TYPE_ATOM_10 + || $this->getType() == Zend_Feed_Reader::TYPE_ATOM_03 + ) { + return; // pre-registered at Feed level + } + $atomDetected = $this->_getAtomType(); + switch ($atomDetected) { + case Zend_Feed_Reader::TYPE_ATOM_03: + $this->_xpath->registerNamespace('atom', Zend_Feed_Reader::NAMESPACE_ATOM_03); + break; + default: + $this->_xpath->registerNamespace('atom', Zend_Feed_Reader::NAMESPACE_ATOM_10); + break; + } + } + + /** + * Detect the presence of any Atom namespaces in use + */ + protected function _getAtomType() + { + $dom = $this->getDomDocument(); + $prefixAtom03 = $dom->lookupPrefix(Zend_Feed_Reader::NAMESPACE_ATOM_03); + $prefixAtom10 = $dom->lookupPrefix(Zend_Feed_Reader::NAMESPACE_ATOM_10); + if ($dom->isDefaultNamespace(Zend_Feed_Reader::NAMESPACE_ATOM_10) + || !empty($prefixAtom10)) { + return Zend_Feed_Reader::TYPE_ATOM_10; + } + if ($dom->isDefaultNamespace(Zend_Feed_Reader::NAMESPACE_ATOM_03) + || !empty($prefixAtom03)) { + return Zend_Feed_Reader::TYPE_ATOM_03; + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Content/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Content/Entry.php new file mode 100644 index 00000000..ca10f24a --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Content/Entry.php @@ -0,0 +1,61 @@ +getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090 + ) { + $content = $this->_xpath->evaluate('string('.$this->getXpathPrefix().'/content:encoded)'); + } else { + $content = $this->_xpath->evaluate('string('.$this->getXpathPrefix().'/content:encoded)'); + } + return $content; + } + + /** + * Register RSS Content Module namespace + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php new file mode 100644 index 00000000..5e78db10 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/CreativeCommons/Entry.php @@ -0,0 +1,97 @@ +getLicenses(); + + if (isset($licenses[$index])) { + return $licenses[$index]; + } + + return null; + } + + /** + * Get the entry licenses + * + * @return array + */ + public function getLicenses() + { + $name = 'licenses'; + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + $licenses = array(); + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//cc:license'); + + if ($list->length) { + foreach ($list as $license) { + $licenses[] = $license->nodeValue; + } + + $licenses = array_unique($licenses); + } else { + $cc = new Zend_Feed_Reader_Extension_CreativeCommons_Feed( + $this->_domDocument, $this->_data['type'], $this->_xpath + ); + $licenses = $cc->getLicenses(); + } + + $this->_data[$name] = $licenses; + + return $this->_data[$name]; + } + + /** + * Register Creative Commons namespaces + * + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('cc', 'http://backend.userland.com/creativeCommonsRssModule'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php new file mode 100644 index 00000000..3d29c691 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/CreativeCommons/Feed.php @@ -0,0 +1,89 @@ +getLicenses(); + + if (isset($licenses[$index])) { + return $licenses[$index]; + } + + return null; + } + + /** + * Get the entry licenses + * + * @return array + */ + public function getLicenses() + { + $name = 'licenses'; + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + $licenses = array(); + $list = $this->_xpath->evaluate('channel/cc:license'); + + if ($list->length) { + foreach ($list as $license) { + $licenses[] = $license->nodeValue; + } + + $licenses = array_unique($licenses); + } + + $this->_data[$name] = $licenses; + + return $this->_data[$name]; + } + + /** + * Register Creative Commons namespaces + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('cc', 'http://backend.userland.com/creativeCommonsRssModule'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/DublinCore/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/DublinCore/Entry.php new file mode 100644 index 00000000..4ad104b6 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/DublinCore/Entry.php @@ -0,0 +1,266 @@ +getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $authors = array(); + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc11:creator'); + + if (!$list->length) { + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc10:creator'); + } + if (!$list->length) { + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc11:publisher'); + + if (!$list->length) { + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc10:publisher'); + } + } + + if ($list->length) { + foreach ($list as $author) { + $authors[] = array( + 'name' => $author->nodeValue + ); + } + $authors = new Zend_Feed_Reader_Collection_Author( + Zend_Feed_Reader::arrayUnique($authors) + ); + } else { + $authors = null; + } + + $this->_data['authors'] = $authors; + + return $this->_data['authors']; + } + + /** + * Get categories (subjects under DC) + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc11:subject'); + + if (!$list->length) { + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc10:subject'); + } + + if ($list->length) { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + foreach ($list as $category) { + $categoryCollection[] = array( + 'term' => $category->nodeValue, + 'scheme' => null, + 'label' => $category->nodeValue, + ); + } + } else { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + } + + $this->_data['categories'] = $categoryCollection; + return $this->_data['categories']; + } + + + /** + * Get the entry content + * + * @return string + */ + public function getContent() + { + return $this->getDescription(); + } + + /** + * Get the entry description + * + * @return string + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = null; + $description = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:description)'); + + if (!$description) { + $description = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:description)'); + } + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the entry ID + * + * @return string + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = null; + $id = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:identifier)'); + + if (!$id) { + $id = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:identifier)'); + } + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get the entry title + * + * @return string + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = null; + $title = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:title)'); + + if (!$title) { + $title = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:title)'); + } + + if (!$title) { + $title = null; + } + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * + * + * @return Zend_Date|null + */ + public function getDate() + { + if (array_key_exists('date', $this->_data)) { + return $this->_data['date']; + } + + $d = null; + $date = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:date)'); + + if (!$date) { + $date = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:date)'); + } + + if ($date) { + $d = new Zend_Date; + $d->set($date, Zend_Date::ISO_8601); + } + + $this->_data['date'] = $d; + + return $this->_data['date']; + } + + /** + * Register DC namespaces + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('dc10', 'http://purl.org/dc/elements/1.0/'); + $this->_xpath->registerNamespace('dc11', 'http://purl.org/dc/elements/1.1/'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/DublinCore/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/DublinCore/Feed.php new file mode 100644 index 00000000..4e4bcf48 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/DublinCore/Feed.php @@ -0,0 +1,309 @@ +getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $authors = array(); + $list = $this->_xpath->query('//dc11:creator'); + + if (!$list->length) { + $list = $this->_xpath->query('//dc10:creator'); + } + if (!$list->length) { + $list = $this->_xpath->query('//dc11:publisher'); + + if (!$list->length) { + $list = $this->_xpath->query('//dc10:publisher'); + } + } + + if ($list->length) { + foreach ($list as $author) { + $authors[] = array( + 'name' => $author->nodeValue + ); + } + $authors = new Zend_Feed_Reader_Collection_Author( + Zend_Feed_Reader::arrayUnique($authors) + ); + } else { + $authors = null; + } + + $this->_data['authors'] = $authors; + + return $this->_data['authors']; + } + + /** + * Get the copyright entry + * + * @return string|null + */ + public function getCopyright() + { + if (array_key_exists('copyright', $this->_data)) { + return $this->_data['copyright']; + } + + $copyright = null; + $copyright = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:rights)'); + + if (!$copyright) { + $copyright = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:rights)'); + } + + if (!$copyright) { + $copyright = null; + } + + $this->_data['copyright'] = $copyright; + + return $this->_data['copyright']; + } + + /** + * Get the feed description + * + * @return string|null + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = null; + $description = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:description)'); + + if (!$description) { + $description = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:description)'); + } + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the feed ID + * + * @return string|null + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = null; + $id = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:identifier)'); + + if (!$id) { + $id = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:identifier)'); + } + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get the feed language + * + * @return string|null + */ + public function getLanguage() + { + if (array_key_exists('language', $this->_data)) { + return $this->_data['language']; + } + + $language = null; + $language = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:language)'); + + if (!$language) { + $language = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:language)'); + } + + if (!$language) { + $language = null; + } + + $this->_data['language'] = $language; + + return $this->_data['language']; + } + + /** + * Get the feed title + * + * @return string|null + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = null; + $title = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:title)'); + + if (!$title) { + $title = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:title)'); + } + + if (!$title) { + $title = null; + } + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * + * + * @return Zend_Date|null + */ + public function getDate() + { + if (array_key_exists('date', $this->_data)) { + return $this->_data['date']; + } + + $d = null; + $date = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc11:date)'); + + if (!$date) { + $date = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/dc10:date)'); + } + + if ($date) { + $d = new Zend_Date; + $d->set($date, Zend_Date::ISO_8601); + } + + $this->_data['date'] = $d; + + return $this->_data['date']; + } + + /** + * Get categories (subjects under DC) + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc11:subject'); + + if (!$list->length) { + $list = $this->_xpath->evaluate($this->getXpathPrefix() . '//dc10:subject'); + } + + if ($list->length) { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + foreach ($list as $category) { + $categoryCollection[] = array( + 'term' => $category->nodeValue, + 'scheme' => null, + 'label' => $category->nodeValue, + ); + } + } else { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + } + + $this->_data['categories'] = $categoryCollection; + return $this->_data['categories']; + } + + /** + * Register the default namespaces for the current feed format + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('dc10', 'http://purl.org/dc/elements/1.0/'); + $this->_xpath->registerNamespace('dc11', 'http://purl.org/dc/elements/1.1/'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/EntryAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/EntryAbstract.php new file mode 100644 index 00000000..299c1bfb --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/EntryAbstract.php @@ -0,0 +1,200 @@ +_entry = $entry; + $this->_entryKey = $entryKey; + $this->_domDocument = $entry->ownerDocument; + + if (!is_null($type)) { + $this->_data['type'] = $type; + } else { + $this->_data['type'] = Zend_Feed_Reader::detectType($entry->ownerDocument, true); + } + // set the XPath query prefix for the entry being queried + if ($this->getType() == Zend_Feed_Reader::TYPE_RSS_10 + || $this->getType() == Zend_Feed_Reader::TYPE_RSS_090 + ) { + $this->setXpathPrefix('//rss:item[' . ($this->_entryKey+1) . ']'); + } elseif ($this->getType() == Zend_Feed_Reader::TYPE_ATOM_10 + || $this->getType() == Zend_Feed_Reader::TYPE_ATOM_03 + ) { + $this->setXpathPrefix('//atom:entry[' . ($this->_entryKey+1) . ']'); + } else { + $this->setXpathPrefix('//item[' . ($this->_entryKey+1) . ']'); + } + } + + /** + * Get the DOM + * + * @return DOMDocument + */ + public function getDomDocument() + { + return $this->_domDocument; + } + + /** + * Get the Entry's encoding + * + * @return string + */ + public function getEncoding() + { + $assumed = $this->getDomDocument()->encoding; + return $assumed; + } + + /** + * Get the entry type + * + * @return string + */ + public function getType() + { + return $this->_data['type']; + } + + /** + * Set the XPath query + * + * @param DOMXPath $xpath + * @return Zend_Feed_Reader_Extension_EntryAbstract + */ + public function setXpath(DOMXPath $xpath) + { + $this->_xpath = $xpath; + $this->_registerNamespaces(); + return $this; + } + + /** + * Get the XPath query object + * + * @return DOMXPath + */ + public function getXpath() + { + if (!$this->_xpath) { + $this->setXpath(new DOMXPath($this->getDomDocument())); + } + return $this->_xpath; + } + + /** + * Serialize the entry to an array + * + * @return array + */ + public function toArray() + { + return $this->_data; + } + + /** + * Get the XPath prefix + * + * @return string + */ + public function getXpathPrefix() + { + return $this->_xpathPrefix; + } + + /** + * Set the XPath prefix + * + * @param string $prefix + * @return Zend_Feed_Reader_Extension_EntryAbstract + */ + public function setXpathPrefix($prefix) + { + $this->_xpathPrefix = $prefix; + return $this; + } + + /** + * Register XML namespaces + * + * @return void + */ + protected abstract function _registerNamespaces(); +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/FeedAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/FeedAbstract.php new file mode 100644 index 00000000..23d97d3f --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/FeedAbstract.php @@ -0,0 +1,189 @@ +_domDocument = $dom; + + if ($type !== null) { + $this->_data['type'] = $type; + } else { + $this->_data['type'] = Zend_Feed_Reader::detectType($dom); + } + + if ($xpath !== null) { + $this->_xpath = $xpath; + } else { + $this->_xpath = new DOMXPath($this->_domDocument); + } + + $this->_registerNamespaces(); + } + + /** + * Get the DOM + * + * @return DOMDocument + */ + public function getDomDocument() + { + return $this->_domDocument; + } + + /** + * Get the Feed's encoding + * + * @return string + */ + public function getEncoding() + { + $assumed = $this->getDomDocument()->encoding; + return $assumed; + } + + /** + * Get the feed type + * + * @return string + */ + public function getType() + { + return $this->_data['type']; + } + + + /** + * Return the feed as an array + * + * @return array + */ + public function toArray() // untested + { + return $this->_data; + } + + /** + * Set the XPath query + * + * @param DOMXPath $xpath + * @return Zend_Feed_Reader_Extension_EntryAbstract + */ + public function setXpath(DOMXPath $xpath) + { + $this->_xpath = $xpath; + $this->_registerNamespaces(); + return $this; + } + + /** + * Get the DOMXPath object + * + * @return string + */ + public function getXpath() + { + return $this->_xpath; + } + + /** + * Get the XPath prefix + * + * @return string + */ + public function getXpathPrefix() + { + return $this->_xpathPrefix; + } + + /** + * Set the XPath prefix + * + * @return Zend_Feed_Reader_Feed_Atom + */ + public function setXpathPrefix($prefix) + { + $this->_xpathPrefix = $prefix; + } + + /** + * Register the default namespaces for the current feed format + */ + abstract protected function _registerNamespaces(); +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Podcast/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Podcast/Entry.php new file mode 100644 index 00000000..bbe340ce --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Podcast/Entry.php @@ -0,0 +1,202 @@ +_data['author'])) { + return $this->_data['author']; + } + + $author = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:author)'); + + if (!$author) { + $author = null; + } + + $this->_data['author'] = $author; + + return $this->_data['author']; + } + + /** + * Get the entry block + * + * @return string + */ + public function getBlock() + { + if (isset($this->_data['block'])) { + return $this->_data['block']; + } + + $block = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:block)'); + + if (!$block) { + $block = null; + } + + $this->_data['block'] = $block; + + return $this->_data['block']; + } + + /** + * Get the entry duration + * + * @return string + */ + public function getDuration() + { + if (isset($this->_data['duration'])) { + return $this->_data['duration']; + } + + $duration = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:duration)'); + + if (!$duration) { + $duration = null; + } + + $this->_data['duration'] = $duration; + + return $this->_data['duration']; + } + + /** + * Get the entry explicit + * + * @return string + */ + public function getExplicit() + { + if (isset($this->_data['explicit'])) { + return $this->_data['explicit']; + } + + $explicit = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:explicit)'); + + if (!$explicit) { + $explicit = null; + } + + $this->_data['explicit'] = $explicit; + + return $this->_data['explicit']; + } + + /** + * Get the entry keywords + * + * @return string + */ + public function getKeywords() + { + if (isset($this->_data['keywords'])) { + return $this->_data['keywords']; + } + + $keywords = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:keywords)'); + + if (!$keywords) { + $keywords = null; + } + + $this->_data['keywords'] = $keywords; + + return $this->_data['keywords']; + } + + /** + * Get the entry subtitle + * + * @return string + */ + public function getSubtitle() + { + if (isset($this->_data['subtitle'])) { + return $this->_data['subtitle']; + } + + $subtitle = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:subtitle)'); + + if (!$subtitle) { + $subtitle = null; + } + + $this->_data['subtitle'] = $subtitle; + + return $this->_data['subtitle']; + } + + /** + * Get the entry summary + * + * @return string + */ + public function getSummary() + { + if (isset($this->_data['summary'])) { + return $this->_data['summary']; + } + + $summary = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:summary)'); + + if (!$summary) { + $summary = null; + } + + $this->_data['summary'] = $summary; + + return $this->_data['summary']; + } + + /** + * Register iTunes namespace + * + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('itunes', 'http://www.itunes.com/dtds/podcast-1.0.dtd'); + } +} \ No newline at end of file diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Podcast/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Podcast/Feed.php new file mode 100644 index 00000000..c4ea180f --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Podcast/Feed.php @@ -0,0 +1,293 @@ +_data['author'])) { + return $this->_data['author']; + } + + $author = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:author)'); + + if (!$author) { + $author = null; + } + + $this->_data['author'] = $author; + + return $this->_data['author']; + } + + /** + * Get the entry block + * + * @return string + */ + public function getBlock() + { + if (isset($this->_data['block'])) { + return $this->_data['block']; + } + + $block = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:block)'); + + if (!$block) { + $block = null; + } + + $this->_data['block'] = $block; + + return $this->_data['block']; + } + + /** + * Get the entry category + * + * @return string + */ + public function getCategories() + { + if (isset($this->_data['categories'])) { + return $this->_data['categories']; + } + + $categoryList = $this->_xpath->query($this->getXpathPrefix() . '/itunes:category'); + + $categories = array(); + + if ($categoryList->length > 0) { + foreach ($categoryList as $node) { + $children = null; + + if ($node->childNodes->length > 0) { + $children = array(); + + foreach ($node->childNodes as $childNode) { + if (!($childNode instanceof DOMText)) { + $children[$childNode->getAttribute('text')] = null; + } + } + } + + $categories[$node->getAttribute('text')] = $children; + } + } + + + if (!$categories) { + $categories = null; + } + + $this->_data['categories'] = $categories; + + return $this->_data['categories']; + } + + /** + * Get the entry explicit + * + * @return string + */ + public function getExplicit() + { + if (isset($this->_data['explicit'])) { + return $this->_data['explicit']; + } + + $explicit = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:explicit)'); + + if (!$explicit) { + $explicit = null; + } + + $this->_data['explicit'] = $explicit; + + return $this->_data['explicit']; + } + + /** + * Get the entry image + * + * @return string + */ + public function getImage() + { + if (isset($this->_data['image'])) { + return $this->_data['image']; + } + + $image = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:image/@href)'); + + if (!$image) { + $image = null; + } + + $this->_data['image'] = $image; + + return $this->_data['image']; + } + + /** + * Get the entry keywords + * + * @return string + */ + public function getKeywords() + { + if (isset($this->_data['keywords'])) { + return $this->_data['keywords']; + } + + $keywords = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:keywords)'); + + if (!$keywords) { + $keywords = null; + } + + $this->_data['keywords'] = $keywords; + + return $this->_data['keywords']; + } + + /** + * Get the entry's new feed url + * + * @return string + */ + public function getNewFeedUrl() + { + if (isset($this->_data['new-feed-url'])) { + return $this->_data['new-feed-url']; + } + + $newFeedUrl = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:new-feed-url)'); + + if (!$newFeedUrl) { + $newFeedUrl = null; + } + + $this->_data['new-feed-url'] = $newFeedUrl; + + return $this->_data['new-feed-url']; + } + + /** + * Get the entry owner + * + * @return string + */ + public function getOwner() + { + if (isset($this->_data['owner'])) { + return $this->_data['owner']; + } + + $owner = null; + + $email = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:owner/itunes:email)'); + $name = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:owner/itunes:name)'); + + if (!empty($email)) { + $owner = $email . (empty($name) ? '' : ' (' . $name . ')'); + } else if (!empty($name)) { + $owner = $name; + } + + if (!$owner) { + $owner = null; + } + + $this->_data['owner'] = $owner; + + return $this->_data['owner']; + } + + /** + * Get the entry subtitle + * + * @return string + */ + public function getSubtitle() + { + if (isset($this->_data['subtitle'])) { + return $this->_data['subtitle']; + } + + $subtitle = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:subtitle)'); + + if (!$subtitle) { + $subtitle = null; + } + + $this->_data['subtitle'] = $subtitle; + + return $this->_data['subtitle']; + } + + /** + * Get the entry summary + * + * @return string + */ + public function getSummary() + { + if (isset($this->_data['summary'])) { + return $this->_data['summary']; + } + + $summary = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/itunes:summary)'); + + if (!$summary) { + $summary = null; + } + + $this->_data['summary'] = $summary; + + return $this->_data['summary']; + } + + /** + * Register iTunes namespace + * + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('itunes', 'http://www.itunes.com/dtds/podcast-1.0.dtd'); + } +} \ No newline at end of file diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Slash/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Slash/Entry.php new file mode 100644 index 00000000..222c0e10 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Slash/Entry.php @@ -0,0 +1,144 @@ +_getData('section'); + } + + /** + * Get the entry department + * + * @return string|null + */ + public function getDepartment() + { + return $this->_getData('department'); + } + + /** + * Get the entry hit_parade + * + * @return array + */ + public function getHitParade() + { + $name = 'hit_parade'; + + if (isset($this->_data[$name])) { + return $this->_data[$name]; + } + + $stringParade = $this->_getData($name); + $hitParade = array(); + + if (!empty($stringParade)) { + $stringParade = explode(',', $stringParade); + + foreach ($stringParade as $hit) + $hitParade[] = $hit + 0; //cast to integer + } + + $this->_data[$name] = $hitParade; + return $hitParade; + } + + /** + * Get the entry comments + * + * @return int + */ + public function getCommentCount() + { + $name = 'comments'; + + if (isset($this->_data[$name])) { + return $this->_data[$name]; + } + + $comments = $this->_getData($name, 'string'); + + if (!$comments) { + $this->_data[$name] = null; + return $this->_data[$name]; + } + + return $comments; + } + + /** + * Get the entry data specified by name + * @param string $name + * @param string $type + * + * @return mixed|null + */ + protected function _getData($name, $type = 'string') + { + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + $data = $this->_xpath->evaluate($type . '(' . $this->getXpathPrefix() . '/slash10:' . $name . ')'); + + if (!$data) { + $data = null; + } + + $this->_data[$name] = $data; + + return $data; + } + + /** + * Register Slash namespaces + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('slash10', 'http://purl.org/rss/1.0/modules/slash/'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Syndication/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Syndication/Feed.php new file mode 100644 index 00000000..5b383cfe --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Syndication/Feed.php @@ -0,0 +1,168 @@ +_getData($name); + + if ($period === null) { + $this->_data[$name] = 'daily'; + return 'daily'; //Default specified by spec + } + + switch ($period) + { + case 'hourly': + case 'daily': + case 'weekly': + case 'yearly': + return $period; + default: + throw new Zend_Feed_Exception("Feed specified invalid update period: '$period'." + . " Must be one of hourly, daily, weekly or yearly" + ); + } + } + + /** + * Get update frequency + * @return int + */ + public function getUpdateFrequency() + { + $name = 'updateFrequency'; + $freq = $this->_getData($name, 'number'); + + if (!$freq || $freq < 1) { + $this->_data[$name] = 1; + return 1; + } + + return $freq; + } + + /** + * Get update frequency as ticks + * @return int + */ + public function getUpdateFrequencyAsTicks() + { + $name = 'updateFrequency'; + $freq = $this->_getData($name, 'number'); + + if (!$freq || $freq < 1) { + $this->_data[$name] = 1; + $freq = 1; + } + + $period = $this->getUpdatePeriod(); + $ticks = 1; + + switch ($period) + { + //intentional fall through + case 'yearly': + $ticks *= 52; //TODO: fix generalisation, how? + case 'weekly': + $ticks *= 7; + case 'daily': + $ticks *= 24; + case 'hourly': + $ticks *= 3600; + break; + default: //Never arrive here, exception thrown in getPeriod() + break; + } + + return $ticks / $freq; + } + + /** + * Get update base + * + * @return Zend_Date|null + */ + public function getUpdateBase() + { + $updateBase = $this->_getData('updateBase'); + $date = null; + if ($updateBase) { + $date = new Zend_Date; + $date->set($updateBase, Zend_Date::W3C); + } + return $date; + } + + /** + * Get the entry data specified by name + * + * @param string $name + * @param string $type + * @return mixed|null + */ + private function _getData($name, $type = 'string') + { + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + $data = $this->_xpath->evaluate($type . '(' . $this->getXpathPrefix() . '/syn10:' . $name . ')'); + + if (!$data) { + $data = null; + } + + $this->_data[$name] = $data; + + return $data; + } + + /** + * Register Syndication namespaces + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('syn10', 'http://purl.org/rss/1.0/modules/syndication/'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Thread/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Thread/Entry.php new file mode 100644 index 00000000..e8a4bde9 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/Thread/Entry.php @@ -0,0 +1,91 @@ +_getData('total'); + } + + /** + * Get the entry data specified by name + * + * @param string $name + * @param string $type + * @return mixed|null + */ + protected function _getData($name) + { + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } + + $data = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/thread10:' . $name . ')'); + + if (!$data) { + $data = null; + } + + $this->_data[$name] = $data; + + return $data; + } + + /** + * Register Atom Thread Extension 1.0 namespace + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('thread10', 'http://purl.org/syndication/thread/1.0'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php new file mode 100644 index 00000000..fe474483 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Extension/WellFormedWeb/Entry.php @@ -0,0 +1,73 @@ +_data)) { + return $this->_data[$name]; + } + + $data = $this->_xpath->evaluate('string(' . $this->getXpathPrefix() . '/wfw:' . $name . ')'); + + if (!$data) { + $data = null; + } + + $this->_data[$name] = $data; + + return $data; + } + + /** + * Register Slash namespaces + * + * @return void + */ + protected function _registerNamespaces() + { + $this->_xpath->registerNamespace('wfw', 'http://wellformedweb.org/CommentAPI/'); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Atom.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Atom.php new file mode 100644 index 00000000..81993559 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Atom.php @@ -0,0 +1,421 @@ +getClassName('Atom_Feed'); + $this->_extensions['Atom_Feed'] = new $atomClass($dom, $this->_data['type'], $this->_xpath); + $atomClass = Zend_Feed_Reader::getPluginLoader()->getClassName('DublinCore_Feed'); + $this->_extensions['DublinCore_Feed'] = new $atomClass($dom, $this->_data['type'], $this->_xpath); + foreach ($this->_extensions as $extension) { + $extension->setXpathPrefix('/atom:feed'); + } + } + + /** + * Get a single author + * + * @param int $index + * @return string|null + */ + public function getAuthor($index = 0) + { + $authors = $this->getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $authors = $this->getExtension('Atom')->getAuthors(); + + $this->_data['authors'] = $authors; + + return $this->_data['authors']; + } + + /** + * Get the copyright entry + * + * @return string|null + */ + public function getCopyright() + { + if (array_key_exists('copyright', $this->_data)) { + return $this->_data['copyright']; + } + + $copyright = $this->getExtension('Atom')->getCopyright(); + + if (!$copyright) { + $copyright = null; + } + + $this->_data['copyright'] = $copyright; + + return $this->_data['copyright']; + } + + /** + * Get the feed creation date + * + * @return string|null + */ + public function getDateCreated() + { + if (array_key_exists('datecreated', $this->_data)) { + return $this->_data['datecreated']; + } + + $dateCreated = $this->getExtension('Atom')->getDateCreated(); + + if (!$dateCreated) { + $dateCreated = null; + } + + $this->_data['datecreated'] = $dateCreated; + + return $this->_data['datecreated']; + } + + /** + * Get the feed modification date + * + * @return string|null + */ + public function getDateModified() + { + if (array_key_exists('datemodified', $this->_data)) { + return $this->_data['datemodified']; + } + + $dateModified = $this->getExtension('Atom')->getDateModified(); + + if (!$dateModified) { + $dateModified = null; + } + + $this->_data['datemodified'] = $dateModified; + + return $this->_data['datemodified']; + } + + /** + * Get the feed lastBuild date. This is not implemented in Atom. + * + * @return string|null + */ + public function getLastBuildDate() + { + return null; + } + + /** + * Get the feed description + * + * @return string|null + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = $this->getExtension('Atom')->getDescription(); + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the feed generator entry + * + * @return string|null + */ + public function getGenerator() + { + if (array_key_exists('generator', $this->_data)) { + return $this->_data['generator']; + } + + $generator = $this->getExtension('Atom')->getGenerator(); + + $this->_data['generator'] = $generator; + + return $this->_data['generator']; + } + + /** + * Get the feed ID + * + * @return string|null + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = $this->getExtension('Atom')->getId(); + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get the feed language + * + * @return string|null + */ + public function getLanguage() + { + if (array_key_exists('language', $this->_data)) { + return $this->_data['language']; + } + + $language = $this->getExtension('Atom')->getLanguage(); + + if (!$language) { + $language = $this->_xpath->evaluate('string(//@xml:lang[1])'); + } + + if (!$language) { + $language = null; + } + + $this->_data['language'] = $language; + + return $this->_data['language']; + } + + /** + * Get a link to the source website + * + * @return string|null + */ + public function getBaseUrl() + { + if (array_key_exists('baseUrl', $this->_data)) { + return $this->_data['baseUrl']; + } + + $baseUrl = $this->getExtension('Atom')->getBaseUrl(); + + $this->_data['baseUrl'] = $baseUrl; + + return $this->_data['baseUrl']; + } + + /** + * Get a link to the source website + * + * @return string|null + */ + public function getLink() + { + if (array_key_exists('link', $this->_data)) { + return $this->_data['link']; + } + + $link = $this->getExtension('Atom')->getLink(); + + $this->_data['link'] = $link; + + return $this->_data['link']; + } + + /** + * Get feed image data + * + * @return array|null + */ + public function getImage() + { + if (array_key_exists('image', $this->_data)) { + return $this->_data['image']; + } + + $link = $this->getExtension('Atom')->getImage(); + + $this->_data['image'] = $link; + + return $this->_data['image']; + } + + /** + * Get a link to the feed's XML Url + * + * @return string|null + */ + public function getFeedLink() + { + if (array_key_exists('feedlink', $this->_data)) { + return $this->_data['feedlink']; + } + + $link = $this->getExtension('Atom')->getFeedLink(); + + if (is_null($link) || empty($link)) { + $link = $this->getOriginalSourceUri(); + } + + $this->_data['feedlink'] = $link; + + return $this->_data['feedlink']; + } + + /** + * Get the feed title + * + * @return string|null + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = $this->getExtension('Atom')->getTitle(); + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * Get an array of any supported Pusubhubbub endpoints + * + * @return array|null + */ + public function getHubs() + { + if (array_key_exists('hubs', $this->_data)) { + return $this->_data['hubs']; + } + + $hubs = $this->getExtension('Atom')->getHubs(); + + $this->_data['hubs'] = $hubs; + + return $this->_data['hubs']; + } + + /** + * Get all categories + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + $categoryCollection = $this->getExtension('Atom')->getCategories(); + + if (count($categoryCollection) == 0) { + $categoryCollection = $this->getExtension('DublinCore')->getCategories(); + } + + $this->_data['categories'] = $categoryCollection; + + return $this->_data['categories']; + } + + /** + * Read all entries to the internal entries array + * + * @return void + */ + protected function _indexEntries() + { + if ($this->getType() == Zend_Feed_Reader::TYPE_ATOM_10 || + $this->getType() == Zend_Feed_Reader::TYPE_ATOM_03) { + $entries = array(); + $entries = $this->_xpath->evaluate('//atom:entry'); + + foreach($entries as $index=>$entry) { + $this->_entries[$index] = $entry; + } + } + } + + /** + * Register the default namespaces for the current feed format + * + */ + protected function _registerNamespaces() + { + switch ($this->_data['type']) { + case Zend_Feed_Reader::TYPE_ATOM_03: + $this->_xpath->registerNamespace('atom', Zend_Feed_Reader::NAMESPACE_ATOM_03); + break; + case Zend_Feed_Reader::TYPE_ATOM_10: + default: + $this->_xpath->registerNamespace('atom', Zend_Feed_Reader::NAMESPACE_ATOM_10); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Atom/Source.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Atom/Source.php new file mode 100644 index 00000000..bdd972f9 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Atom/Source.php @@ -0,0 +1,102 @@ +_domDocument = $source->ownerDocument; + $this->_xpath = new DOMXPath($this->_domDocument); + $this->_data['type'] = $type; + $this->_registerNamespaces(); + $this->_loadExtensions(); + + $atomClass = Zend_Feed_Reader::getPluginLoader()->getClassName('Atom_Feed'); + $this->_extensions['Atom_Feed'] = new $atomClass($this->_domDocument, $this->_data['type'], $this->_xpath); + $atomClass = Zend_Feed_Reader::getPluginLoader()->getClassName('DublinCore_Feed'); + $this->_extensions['DublinCore_Feed'] = new $atomClass($this->_domDocument, $this->_data['type'], $this->_xpath); + foreach ($this->_extensions as $extension) { + $extension->setXpathPrefix(rtrim($xpathPrefix, '/') . '/atom:source'); + } + } + + /** + * Since this is not an Entry carrier but a vehicle for Feed metadata, any + * applicable Entry methods are stubbed out and do nothing. + */ + + /** + * @return void + */ + public function count() {} + + /** + * @return void + */ + public function current() {} + + /** + * @return void + */ + public function key() {} + + /** + * @return void + */ + public function next() {} + + /** + * @return void + */ + public function rewind() {} + + /** + * @return void + */ + public function valid() {} + + /** + * @return void + */ + protected function _indexEntries() {} + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Rss.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Rss.php new file mode 100644 index 00000000..bff78c34 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/Feed/Rss.php @@ -0,0 +1,733 @@ +getClassName('DublinCore_Feed'); + $this->_extensions['DublinCore_Feed'] = new $dublinCoreClass($dom, $this->_data['type'], $this->_xpath); + $atomClass = Zend_Feed_Reader::getPluginLoader()->getClassName('Atom_Feed'); + $this->_extensions['Atom_Feed'] = new $atomClass($dom, $this->_data['type'], $this->_xpath); + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $xpathPrefix = '/rss/channel'; + } else { + $xpathPrefix = '/rdf:RDF/rss:channel'; + } + foreach ($this->_extensions as $extension) { + $extension->setXpathPrefix($xpathPrefix); + } + } + + /** + * Get a single author + * + * @param int $index + * @return string|null + */ + public function getAuthor($index = 0) + { + $authors = $this->getAuthors(); + + if (isset($authors[$index])) { + return $authors[$index]; + } + + return null; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (array_key_exists('authors', $this->_data)) { + return $this->_data['authors']; + } + + $authors = array(); + $authors_dc = $this->getExtension('DublinCore')->getAuthors(); + if (!empty($authors_dc)) { + foreach ($authors_dc as $author) { + $authors[] = array( + 'name' => $author['name'] + ); + } + } + + /** + * Technically RSS doesn't specific author element use at the feed level + * but it's supported on a "just in case" basis. + */ + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 + && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $list = $this->_xpath->query('//author'); + } else { + $list = $this->_xpath->query('//rss:author'); + } + if ($list->length) { + foreach ($list as $author) { + $string = trim($author->nodeValue); + $email = null; + $name = null; + $data = array(); + // Pretty rough parsing - but it's a catchall + if (preg_match("/^.*@[^ ]*/", $string, $matches)) { + $data['email'] = trim($matches[0]); + if (preg_match("/\((.*)\)$/", $string, $matches)) { + $data['name'] = $matches[1]; + } + $authors[] = $data; + } + } + } + + if (count($authors) == 0) { + $authors = $this->getExtension('Atom')->getAuthors(); + } else { + $authors = new Zend_Feed_Reader_Collection_Author( + Zend_Feed_Reader::arrayUnique($authors) + ); + } + + if (count($authors) == 0) { + $authors = null; + } + + $this->_data['authors'] = $authors; + + return $this->_data['authors']; + } + + /** + * Get the copyright entry + * + * @return string|null + */ + public function getCopyright() + { + if (array_key_exists('copyright', $this->_data)) { + return $this->_data['copyright']; + } + + $copyright = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $copyright = $this->_xpath->evaluate('string(/rss/channel/copyright)'); + } + + if (!$copyright && !is_null($this->getExtension('DublinCore'))) { + $copyright = $this->getExtension('DublinCore')->getCopyright(); + } + + if (empty($copyright)) { + $copyright = $this->getExtension('Atom')->getCopyright(); + } + + if (!$copyright) { + $copyright = null; + } + + $this->_data['copyright'] = $copyright; + + return $this->_data['copyright']; + } + + /** + * Get the feed creation date + * + * @return string|null + */ + public function getDateCreated() + { + return $this->getDateModified(); + } + + /** + * Get the feed modification date + * + * @return Zend_Date + */ + public function getDateModified() + { + if (array_key_exists('datemodified', $this->_data)) { + return $this->_data['datemodified']; + } + + $dateModified = null; + $date = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $dateModified = $this->_xpath->evaluate('string(/rss/channel/pubDate)'); + if (!$dateModified) { + $dateModified = $this->_xpath->evaluate('string(/rss/channel/lastBuildDate)'); + } + if ($dateModified) { + $dateModifiedParsed = strtotime($dateModified); + if ($dateModifiedParsed) { + $date = new Zend_Date($dateModifiedParsed); + } else { + $dateStandards = array(Zend_Date::RSS, Zend_Date::RFC_822, + Zend_Date::RFC_2822, Zend_Date::DATES); + $date = new Zend_Date; + foreach ($dateStandards as $standard) { + try { + $date->set($dateModified, $standard); + break; + } catch (Zend_Date_Exception $e) { + if ($standard == Zend_Date::DATES) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception( + 'Could not load date due to unrecognised' + .' format (should follow RFC 822 or 2822):' + . $e->getMessage(), + 0, $e + ); + } + } + } + } + } + } + + if (!$date) { + $date = $this->getExtension('DublinCore')->getDate(); + } + + if (!$date) { + $date = $this->getExtension('Atom')->getDateModified(); + } + + if (!$date) { + $date = null; + } + + $this->_data['datemodified'] = $date; + + return $this->_data['datemodified']; + } + + /** + * Get the feed lastBuild date + * + * @return Zend_Date + */ + public function getLastBuildDate() + { + if (array_key_exists('lastBuildDate', $this->_data)) { + return $this->_data['lastBuildDate']; + } + + $lastBuildDate = null; + $date = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $lastBuildDate = $this->_xpath->evaluate('string(/rss/channel/lastBuildDate)'); + if ($lastBuildDate) { + $lastBuildDateParsed = strtotime($lastBuildDate); + if ($lastBuildDateParsed) { + $date = new Zend_Date($lastBuildDateParsed); + } else { + $dateStandards = array(Zend_Date::RSS, Zend_Date::RFC_822, + Zend_Date::RFC_2822, Zend_Date::DATES); + $date = new Zend_Date; + foreach ($dateStandards as $standard) { + try { + $date->set($lastBuildDate, $standard); + break; + } catch (Zend_Date_Exception $e) { + if ($standard == Zend_Date::DATES) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception( + 'Could not load date due to unrecognised' + .' format (should follow RFC 822 or 2822):' + . $e->getMessage(), + 0, $e + ); + } + } + } + } + } + } + + if (!$date) { + $date = null; + } + + $this->_data['lastBuildDate'] = $date; + + return $this->_data['lastBuildDate']; + } + + /** + * Get the feed description + * + * @return string|null + */ + public function getDescription() + { + if (array_key_exists('description', $this->_data)) { + return $this->_data['description']; + } + + $description = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $description = $this->_xpath->evaluate('string(/rss/channel/description)'); + } else { + $description = $this->_xpath->evaluate('string(/rdf:RDF/rss:channel/rss:description)'); + } + + if (!$description && !is_null($this->getExtension('DublinCore'))) { + $description = $this->getExtension('DublinCore')->getDescription(); + } + + if (empty($description)) { + $description = $this->getExtension('Atom')->getDescription(); + } + + if (!$description) { + $description = null; + } + + $this->_data['description'] = $description; + + return $this->_data['description']; + } + + /** + * Get the feed ID + * + * @return string|null + */ + public function getId() + { + if (array_key_exists('id', $this->_data)) { + return $this->_data['id']; + } + + $id = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $id = $this->_xpath->evaluate('string(/rss/channel/guid)'); + } + + if (!$id && !is_null($this->getExtension('DublinCore'))) { + $id = $this->getExtension('DublinCore')->getId(); + } + + if (empty($id)) { + $id = $this->getExtension('Atom')->getId(); + } + + if (!$id) { + if ($this->getLink()) { + $id = $this->getLink(); + } elseif ($this->getTitle()) { + $id = $this->getTitle(); + } else { + $id = null; + } + } + + $this->_data['id'] = $id; + + return $this->_data['id']; + } + + /** + * Get the feed image data + * + * @return array|null + */ + public function getImage() + { + if (array_key_exists('image', $this->_data)) { + return $this->_data['image']; + } + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $list = $this->_xpath->query('/rss/channel/image'); + $prefix = '/rss/channel/image[1]'; + } else { + $list = $this->_xpath->query('/rdf:RDF/rss:channel/rss:image'); + $prefix = '/rdf:RDF/rss:channel/rss:image[1]'; + } + if ($list->length > 0) { + $image = array(); + $value = $this->_xpath->evaluate('string(' . $prefix . '/url)'); + if ($value) { + $image['uri'] = $value; + } + $value = $this->_xpath->evaluate('string(' . $prefix . '/link)'); + if ($value) { + $image['link'] = $value; + } + $value = $this->_xpath->evaluate('string(' . $prefix . '/title)'); + if ($value) { + $image['title'] = $value; + } + $value = $this->_xpath->evaluate('string(' . $prefix . '/height)'); + if ($value) { + $image['height'] = $value; + } + $value = $this->_xpath->evaluate('string(' . $prefix . '/width)'); + if ($value) { + $image['width'] = $value; + } + $value = $this->_xpath->evaluate('string(' . $prefix . '/description)'); + if ($value) { + $image['description'] = $value; + } + } else { + $image = null; + } + + $this->_data['image'] = $image; + + return $this->_data['image']; + } + + /** + * Get the feed language + * + * @return string|null + */ + public function getLanguage() + { + if (array_key_exists('language', $this->_data)) { + return $this->_data['language']; + } + + $language = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $language = $this->_xpath->evaluate('string(/rss/channel/language)'); + } + + if (!$language && !is_null($this->getExtension('DublinCore'))) { + $language = $this->getExtension('DublinCore')->getLanguage(); + } + + if (empty($language)) { + $language = $this->getExtension('Atom')->getLanguage(); + } + + if (!$language) { + $language = $this->_xpath->evaluate('string(//@xml:lang[1])'); + } + + if (!$language) { + $language = null; + } + + $this->_data['language'] = $language; + + return $this->_data['language']; + } + + /** + * Get a link to the feed + * + * @return string|null + */ + public function getLink() + { + if (array_key_exists('link', $this->_data)) { + return $this->_data['link']; + } + + $link = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $link = $this->_xpath->evaluate('string(/rss/channel/link)'); + } else { + $link = $this->_xpath->evaluate('string(/rdf:RDF/rss:channel/rss:link)'); + } + + if (empty($link)) { + $link = $this->getExtension('Atom')->getLink(); + } + + if (!$link) { + $link = null; + } + + $this->_data['link'] = $link; + + return $this->_data['link']; + } + + /** + * Get a link to the feed XML + * + * @return string|null + */ + public function getFeedLink() + { + if (array_key_exists('feedlink', $this->_data)) { + return $this->_data['feedlink']; + } + + $link = null; + + $link = $this->getExtension('Atom')->getFeedLink(); + + if (is_null($link) || empty($link)) { + $link = $this->getOriginalSourceUri(); + } + + $this->_data['feedlink'] = $link; + + return $this->_data['feedlink']; + } + + /** + * Get the feed generator entry + * + * @return string|null + */ + public function getGenerator() + { + if (array_key_exists('generator', $this->_data)) { + return $this->_data['generator']; + } + + $generator = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $generator = $this->_xpath->evaluate('string(/rss/channel/generator)'); + } + + if (!$generator) { + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $generator = $this->_xpath->evaluate('string(/rss/channel/atom:generator)'); + } else { + $generator = $this->_xpath->evaluate('string(/rdf:RDF/rss:channel/atom:generator)'); + } + } + + if (empty($generator)) { + $generator = $this->getExtension('Atom')->getGenerator(); + } + + if (!$generator) { + $generator = null; + } + + $this->_data['generator'] = $generator; + + return $this->_data['generator']; + } + + /** + * Get the feed title + * + * @return string|null + */ + public function getTitle() + { + if (array_key_exists('title', $this->_data)) { + return $this->_data['title']; + } + + $title = null; + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $title = $this->_xpath->evaluate('string(/rss/channel/title)'); + } else { + $title = $this->_xpath->evaluate('string(/rdf:RDF/rss:channel/rss:title)'); + } + + if (!$title && !is_null($this->getExtension('DublinCore'))) { + $title = $this->getExtension('DublinCore')->getTitle(); + } + + if (!$title) { + $title = $this->getExtension('Atom')->getTitle(); + } + + if (!$title) { + $title = null; + } + + $this->_data['title'] = $title; + + return $this->_data['title']; + } + + /** + * Get an array of any supported Pusubhubbub endpoints + * + * @return array|null + */ + public function getHubs() + { + if (array_key_exists('hubs', $this->_data)) { + return $this->_data['hubs']; + } + + $hubs = $this->getExtension('Atom')->getHubs(); + + if (empty($hubs)) { + $hubs = null; + } else { + $hubs = array_unique($hubs); + } + + $this->_data['hubs'] = $hubs; + + return $this->_data['hubs']; + } + + /** + * Get all categories + * + * @return Zend_Feed_Reader_Collection_Category + */ + public function getCategories() + { + if (array_key_exists('categories', $this->_data)) { + return $this->_data['categories']; + } + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && + $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $list = $this->_xpath->query('/rss/channel//category'); + } else { + $list = $this->_xpath->query('/rdf:RDF/rss:channel//rss:category'); + } + + if ($list->length) { + $categoryCollection = new Zend_Feed_Reader_Collection_Category; + foreach ($list as $category) { + $categoryCollection[] = array( + 'term' => $category->nodeValue, + 'scheme' => $category->getAttribute('domain'), + 'label' => $category->nodeValue, + ); + } + } else { + $categoryCollection = $this->getExtension('DublinCore')->getCategories(); + } + + if (count($categoryCollection) == 0) { + $categoryCollection = $this->getExtension('Atom')->getCategories(); + } + + $this->_data['categories'] = $categoryCollection; + + return $this->_data['categories']; + } + + /** + * Read all entries to the internal entries array + * + */ + protected function _indexEntries() + { + $entries = array(); + + if ($this->getType() !== Zend_Feed_Reader::TYPE_RSS_10 && $this->getType() !== Zend_Feed_Reader::TYPE_RSS_090) { + $entries = $this->_xpath->evaluate('//item'); + } else { + $entries = $this->_xpath->evaluate('//rss:item'); + } + + foreach($entries as $index=>$entry) { + $this->_entries[$index] = $entry; + } + } + + /** + * Register the default namespaces for the current feed format + * + */ + protected function _registerNamespaces() + { + switch ($this->_data['type']) { + case Zend_Feed_Reader::TYPE_RSS_10: + $this->_xpath->registerNamespace('rdf', Zend_Feed_Reader::NAMESPACE_RDF); + $this->_xpath->registerNamespace('rss', Zend_Feed_Reader::NAMESPACE_RSS_10); + break; + + case Zend_Feed_Reader::TYPE_RSS_090: + $this->_xpath->registerNamespace('rdf', Zend_Feed_Reader::NAMESPACE_RDF); + $this->_xpath->registerNamespace('rss', Zend_Feed_Reader::NAMESPACE_RSS_090); + break; + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/FeedAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/FeedAbstract.php new file mode 100644 index 00000000..bda9f23b --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/FeedAbstract.php @@ -0,0 +1,321 @@ +_domDocument = $domDocument; + $this->_xpath = new DOMXPath($this->_domDocument); + + if ($type !== null) { + $this->_data['type'] = $type; + } else { + $this->_data['type'] = Zend_Feed_Reader::detectType($this->_domDocument); + } + $this->_registerNamespaces(); + $this->_indexEntries(); + $this->_loadExtensions(); + } + + /** + * Set an original source URI for the feed being parsed. This value + * is returned from getFeedLink() method if the feed does not carry + * a self-referencing URI. + * + * @param string $uri + */ + public function setOriginalSourceUri($uri) + { + $this->_originalSourceUri = $uri; + } + + /** + * Get an original source URI for the feed being parsed. Returns null if + * unset or the feed was not imported from a URI. + * + * @return string|null + */ + public function getOriginalSourceUri() + { + return $this->_originalSourceUri; + } + + /** + * Get the number of feed entries. + * Required by the Iterator interface. + * + * @return int + */ + public function count() + { + return count($this->_entries); + } + + /** + * Return the current entry + * + * @return Zend_Feed_Reader_EntryInterface + */ + public function current() + { + if (substr($this->getType(), 0, 3) == 'rss') { + $reader = new Zend_Feed_Reader_Entry_Rss($this->_entries[$this->key()], $this->key(), $this->getType()); + } else { + $reader = new Zend_Feed_Reader_Entry_Atom($this->_entries[$this->key()], $this->key(), $this->getType()); + } + + $reader->setXpath($this->_xpath); + + return $reader; + } + + /** + * Get the DOM + * + * @return DOMDocument + */ + public function getDomDocument() + { + return $this->_domDocument; + } + + /** + * Get the Feed's encoding + * + * @return string + */ + public function getEncoding() + { + $assumed = $this->getDomDocument()->encoding; + if (empty($assumed)) { + $assumed = 'UTF-8'; + } + return $assumed; + } + + /** + * Get feed as xml + * + * @return string + */ + public function saveXml() + { + return $this->getDomDocument()->saveXml(); + } + + /** + * Get the DOMElement representing the items/feed element + * + * @return DOMElement + */ + public function getElement() + { + return $this->getDomDocument()->documentElement; + } + + /** + * Get the DOMXPath object for this feed + * + * @return DOMXPath + */ + public function getXpath() + { + return $this->_xpath; + } + + /** + * Get the feed type + * + * @return string + */ + public function getType() + { + return $this->_data['type']; + } + + /** + * Return the current feed key + * + * @return unknown + */ + public function key() + { + return $this->_entriesKey; + } + + /** + * Move the feed pointer forward + * + */ + public function next() + { + ++$this->_entriesKey; + } + + /** + * Reset the pointer in the feed object + * + */ + public function rewind() + { + $this->_entriesKey = 0; + } + + /** + * Check to see if the iterator is still valid + * + * @return boolean + */ + public function valid() + { + return 0 <= $this->_entriesKey && $this->_entriesKey < $this->count(); + } + + public function getExtensions() + { + return $this->_extensions; + } + + public function __call($method, $args) + { + foreach ($this->_extensions as $extension) { + if (method_exists($extension, $method)) { + return call_user_func_array(array($extension, $method), $args); + } + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Method: ' . $method + . 'does not exist and could not be located on a registered Extension'); + } + + /** + * Return an Extension object with the matching name (postfixed with _Feed) + * + * @param string $name + * @return Zend_Feed_Reader_Extension_FeedAbstract + */ + public function getExtension($name) + { + if (array_key_exists($name . '_Feed', $this->_extensions)) { + return $this->_extensions[$name . '_Feed']; + } + return null; + } + + protected function _loadExtensions() + { + $all = Zend_Feed_Reader::getExtensions(); + $feed = $all['feed']; + foreach ($feed as $extension) { + if (in_array($extension, $all['core'])) { + continue; + } + $className = Zend_Feed_Reader::getPluginLoader()->getClassName($extension); + $this->_extensions[$extension] = new $className( + $this->getDomDocument(), $this->_data['type'], $this->_xpath + ); + } + } + + /** + * Read all entries to the internal entries array + * + */ + abstract protected function _indexEntries(); + + /** + * Register the default namespaces for the current feed format + * + */ + abstract protected function _registerNamespaces(); +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Reader/FeedInterface.php b/oss/piwik/trunk/libs/Zend/Feed/Reader/FeedInterface.php new file mode 100644 index 00000000..35060baf --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Reader/FeedInterface.php @@ -0,0 +1,122 @@ +getAttribute('rel')) !== 'alternate' + || !$link->getAttribute('type') || !$link->getAttribute('href')) { + continue; + } + if (!isset($this->rss) && $link->getAttribute('type') == 'application/rss+xml') { + $this->rss = $this->_absolutiseUri(trim($link->getAttribute('href')), $uri); + } elseif(!isset($this->atom) && $link->getAttribute('type') == 'application/atom+xml') { + $this->atom = $this->_absolutiseUri(trim($link->getAttribute('href')), $uri); + } elseif(!isset($this->rdf) && $link->getAttribute('type') == 'application/rdf+xml') { + $this->rdf = $this->_absolutiseUri(trim($link->getAttribute('href')), $uri); + } + $this[] = new self(array( + 'rel' => 'alternate', + 'type' => $link->getAttribute('type'), + 'href' => $this->_absolutiseUri(trim($link->getAttribute('href')), $uri), + )); + } + } + + /** + * Attempt to turn a relative URI into an absolute URI + */ + protected function _absolutiseUri($link, $uri = null) + { + if (!Zend_Uri::check($link)) { + if (!is_null($uri)) { + $uri = Zend_Uri::factory($uri); + + if ($link[0] !== '/') { + $link = $uri->getPath() . '/' . $link; + } + + $link = $uri->getScheme() . '://' . $uri->getHost() . '/' . $this->_canonicalizePath($link); + if (!Zend_Uri::check($link)) { + $link = null; + } + } + } + return $link; + } + + /** + * Canonicalize relative path + */ + protected function _canonicalizePath($path) + { + $parts = array_filter(explode('/', $path)); + $absolutes = array(); + foreach ($parts as $part) { + if ('.' == $part) { + continue; + } + if ('..' == $part) { + array_pop($absolutes); + } else { + $absolutes[] = $part; + } + } + return implode('/', $absolutes); + } + + /** + * Supports lazy loading of feeds using Zend_Feed_Reader::import() but + * delegates any other operations to the parent class. + * + * @param string $offset + * @return mixed + * @uses Zend_Feed_Reader + */ + public function offsetGet($offset) + { + if ($offset == 'feed' && !$this->offsetExists('feed')) { + if (!$this->offsetExists('href')) { + return null; + } + $feed = Zend_Feed_Reader::import($this->offsetGet('href')); + $this->offsetSet('feed', $feed); + return $feed; + } + return parent::offsetGet($offset); + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Rss.php b/oss/piwik/trunk/libs/Zend/Feed/Rss.php new file mode 100644 index 00000000..19eaa8ec --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Rss.php @@ -0,0 +1,530 @@ +s). + * + * @var string + */ + protected $_entryElementName = 'item'; + + /** + * The default namespace for RSS channels. + * + * @var string + */ + protected $_defaultNamespace = 'rss'; + + /** + * Override Zend_Feed_Abstract to set up the $_element and $_entries aliases. + * + * @return void + * @throws Zend_Feed_Exception + */ + public function __wakeup() + { + parent::__wakeup(); + + // Find the base channel element and create an alias to it. + $rdfTags = $this->_element->getElementsByTagNameNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'RDF'); + if ($rdfTags->length != 0) { + $this->_element = $rdfTags->item(0); + } else { + $this->_element = $this->_element->getElementsByTagName('channel')->item(0); + } + if (!$this->_element) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('No root element found, cannot parse channel.'); + } + + // Find the entries and save a pointer to them for speed and + // simplicity. + $this->_buildEntryCache(); + } + + + /** + * Make accessing some individual elements of the channel easier. + * + * Special accessors 'item' and 'items' are provided so that if + * you wish to iterate over an RSS channel's items, you can do so + * using foreach ($channel->items as $item) or foreach + * ($channel->item as $item). + * + * @param string $var The property to access. + * @return mixed + */ + public function __get($var) + { + switch ($var) { + case 'item': + // fall through to the next case + case 'items': + return $this; + + default: + return parent::__get($var); + } + } + + /** + * Generate the header of the feed when working in write mode + * + * @param array $array the data to use + * @return DOMElement root node + */ + protected function _mapFeedHeaders($array) + { + $channel = $this->_element->createElement('channel'); + + $title = $this->_element->createElement('title'); + $title->appendChild($this->_element->createCDATASection($array->title)); + $channel->appendChild($title); + + $link = $this->_element->createElement('link', $array->link); + $channel->appendChild($link); + + $desc = isset($array->description) ? $array->description : ''; + $description = $this->_element->createElement('description'); + $description->appendChild($this->_element->createCDATASection($desc)); + $channel->appendChild($description); + + $pubdate = isset($array->lastUpdate) ? $array->lastUpdate : time(); + $pubdate = $this->_element->createElement('pubDate', date(DATE_RSS, $pubdate)); + $channel->appendChild($pubdate); + + if (isset($array->published)) { + $lastBuildDate = $this->_element->createElement('lastBuildDate', date(DATE_RSS, $array->published)); + $channel->appendChild($lastBuildDate); + } + + $editor = ''; + if (!empty($array->email)) { + $editor .= $array->email; + } + if (!empty($array->author)) { + $editor .= ' (' . $array->author . ')'; + } + if (!empty($editor)) { + $author = $this->_element->createElement('managingEditor', ltrim($editor)); + $channel->appendChild($author); + } + if (isset($array->webmaster)) { + $channel->appendChild($this->_element->createElement('webMaster', $array->webmaster)); + } + + if (!empty($array->copyright)) { + $copyright = $this->_element->createElement('copyright', $array->copyright); + $channel->appendChild($copyright); + } + + if (isset($array->category)) { + $category = $this->_element->createElement('category', $array->category); + $channel->appendChild($category); + } + + if (!empty($array->image)) { + $image = $this->_element->createElement('image'); + $url = $this->_element->createElement('url', $array->image); + $image->appendChild($url); + $imagetitle = $this->_element->createElement('title'); + $imagetitle->appendChild($this->_element->createCDATASection($array->title)); + $image->appendChild($imagetitle); + $imagelink = $this->_element->createElement('link', $array->link); + $image->appendChild($imagelink); + + $channel->appendChild($image); + } + + $generator = !empty($array->generator) ? $array->generator : 'Zend_Feed'; + $generator = $this->_element->createElement('generator', $generator); + $channel->appendChild($generator); + + if (!empty($array->language)) { + $language = $this->_element->createElement('language', $array->language); + $channel->appendChild($language); + } + + $doc = $this->_element->createElement('docs', 'http://blogs.law.harvard.edu/tech/rss'); + $channel->appendChild($doc); + + if (isset($array->cloud)) { + $cloud = $this->_element->createElement('cloud'); + $cloud->setAttribute('domain', $array->cloud['uri']->getHost()); + $cloud->setAttribute('port', $array->cloud['uri']->getPort()); + $cloud->setAttribute('path', $array->cloud['uri']->getPath()); + $cloud->setAttribute('registerProcedure', $array->cloud['procedure']); + $cloud->setAttribute('protocol', $array->cloud['protocol']); + $channel->appendChild($cloud); + } + + if (isset($array->ttl)) { + $ttl = $this->_element->createElement('ttl', $array->ttl); + $channel->appendChild($ttl); + } + + if (isset($array->rating)) { + $rating = $this->_element->createElement('rating', $array->rating); + $channel->appendChild($rating); + } + + if (isset($array->textInput)) { + $textinput = $this->_element->createElement('textInput'); + $textinput->appendChild($this->_element->createElement('title', $array->textInput['title'])); + $textinput->appendChild($this->_element->createElement('description', $array->textInput['description'])); + $textinput->appendChild($this->_element->createElement('name', $array->textInput['name'])); + $textinput->appendChild($this->_element->createElement('link', $array->textInput['link'])); + $channel->appendChild($textinput); + } + + if (isset($array->skipHours)) { + $skipHours = $this->_element->createElement('skipHours'); + foreach ($array->skipHours as $hour) { + $skipHours->appendChild($this->_element->createElement('hour', $hour)); + } + $channel->appendChild($skipHours); + } + + if (isset($array->skipDays)) { + $skipDays = $this->_element->createElement('skipDays'); + foreach ($array->skipDays as $day) { + $skipDays->appendChild($this->_element->createElement('day', $day)); + } + $channel->appendChild($skipDays); + } + + if (isset($array->itunes)) { + $this->_buildiTunes($channel, $array); + } + + return $channel; + } + + /** + * Adds the iTunes extensions to a root node + * + * @param DOMElement $root + * @param array $array + * @return void + */ + private function _buildiTunes(DOMElement $root, $array) + { + /* author node */ + $author = ''; + if (isset($array->itunes->author)) { + $author = $array->itunes->author; + } elseif (isset($array->author)) { + $author = $array->author; + } + if (!empty($author)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:author', $author); + $root->appendChild($node); + } + + /* owner node */ + $author = ''; + $email = ''; + if (isset($array->itunes->owner)) { + $itunesOwner = $array->itunes->owner; + if (isset($itunesOwner['name'])) { + $author = $itunesOwner['name']; + } + if (isset($itunesOwner['email'])) { + $email = $itunesOwner['email']; + } + } + if (empty($author) && isset($array->author)) { + $author = $array->author; + } + if (empty($email) && isset($array->email)) { + $email = $array->email; + } + if (!empty($author) || !empty($email)) { + $owner = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:owner'); + if (!empty($author)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:name', $author); + $owner->appendChild($node); + } + if (!empty($email)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:email', $email); + $owner->appendChild($node); + } + $root->appendChild($owner); + } + $image = ''; + if (isset($array->itunes->image)) { + $image = $array->itunes->image; + } elseif (isset($array->image)) { + $image = $array->image; + } + if (!empty($image)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:image'); + $node->setAttribute('href', $image); + $root->appendChild($node); + } + $subtitle = ''; + if (isset($array->itunes->subtitle)) { + $subtitle = $array->itunes->subtitle; + } elseif (isset($array->description)) { + $subtitle = $array->description; + } + if (!empty($subtitle)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:subtitle', $subtitle); + $root->appendChild($node); + } + $summary = ''; + if (isset($array->itunes->summary)) { + $summary = $array->itunes->summary; + } elseif (isset($array->description)) { + $summary = $array->description; + } + if (!empty($summary)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:summary', $summary); + $root->appendChild($node); + } + if (isset($array->itunes->block)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:block', $array->itunes->block); + $root->appendChild($node); + } + if (isset($array->itunes->explicit)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:explicit', $array->itunes->explicit); + $root->appendChild($node); + } + if (isset($array->itunes->keywords)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:keywords', $array->itunes->keywords); + $root->appendChild($node); + } + if (isset($array->itunes->new_feed_url)) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:new-feed-url', $array->itunes->new_feed_url); + $root->appendChild($node); + } + if (isset($array->itunes->category)) { + foreach ($array->itunes->category as $i => $category) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:category'); + $node->setAttribute('text', $category['main']); + $root->appendChild($node); + $add_end_category = false; + if (!empty($category['sub'])) { + $add_end_category = true; + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:category'); + $node->setAttribute('text', $category['sub']); + $root->appendChild($node); + } + if ($i > 0 || $add_end_category) { + $node = $this->_element->createElementNS('http://www.itunes.com/DTDs/Podcast-1.0.dtd', 'itunes:category'); + $root->appendChild($node); + } + } + } + } + + /** + * Generate the entries of the feed when working in write mode + * + * The following nodes are constructed for each feed entry + * + * entry title + * url to feed entry + * url to feed entry + * short text + * long version, can contain html + * + * + * @param DOMElement $root the root node to use + * @param array $array the data to use + * @return void + */ + protected function _mapFeedEntries(DOMElement $root, $array) + { + Zend_Feed::registerNamespace('content', 'http://purl.org/rss/1.0/modules/content/'); + + foreach ($array as $dataentry) { + $item = $this->_element->createElement('item'); + + $title = $this->_element->createElement('title'); + $title->appendChild($this->_element->createCDATASection($dataentry->title)); + $item->appendChild($title); + + if (isset($dataentry->author)) { + $author = $this->_element->createElement('author', $dataentry->author); + $item->appendChild($author); + } + + $link = $this->_element->createElement('link', $dataentry->link); + $item->appendChild($link); + + if (isset($dataentry->guid)) { + $guid = $this->_element->createElement('guid', $dataentry->guid); + if (!Zend_Uri::check($dataentry->guid)) { + $guid->setAttribute('isPermaLink', 'false'); + } + $item->appendChild($guid); + } + + $description = $this->_element->createElement('description'); + $description->appendChild($this->_element->createCDATASection($dataentry->description)); + $item->appendChild($description); + + if (isset($dataentry->content)) { + $content = $this->_element->createElement('content:encoded'); + $content->appendChild($this->_element->createCDATASection($dataentry->content)); + $item->appendChild($content); + } + + $pubdate = isset($dataentry->lastUpdate) ? $dataentry->lastUpdate : time(); + $pubdate = $this->_element->createElement('pubDate', date(DATE_RSS, $pubdate)); + $item->appendChild($pubdate); + + if (isset($dataentry->category)) { + foreach ($dataentry->category as $category) { + $node = $this->_element->createElement('category', $category['term']); + if (isset($category['scheme'])) { + $node->setAttribute('domain', $category['scheme']); + } + $item->appendChild($node); + } + } + + if (isset($dataentry->source)) { + $source = $this->_element->createElement('source', $dataentry->source['title']); + $source->setAttribute('url', $dataentry->source['url']); + $item->appendChild($source); + } + + if (isset($dataentry->comments)) { + $comments = $this->_element->createElement('comments', $dataentry->comments); + $item->appendChild($comments); + } + if (isset($dataentry->commentRss)) { + $comments = $this->_element->createElementNS('http://wellformedweb.org/CommentAPI/', + 'wfw:commentRss', + $dataentry->commentRss); + $item->appendChild($comments); + } + + + if (isset($dataentry->enclosure)) { + foreach ($dataentry->enclosure as $enclosure) { + $node = $this->_element->createElement('enclosure'); + $node->setAttribute('url', $enclosure['url']); + if (isset($enclosure['type'])) { + $node->setAttribute('type', $enclosure['type']); + } + if (isset($enclosure['length'])) { + $node->setAttribute('length', $enclosure['length']); + } + $item->appendChild($node); + } + } + + $root->appendChild($item); + } + } + + /** + * Override Zend_Feed_Element to include root node + * + * @return string + */ + public function saveXml() + { + // Return a complete document including XML prologue. + $doc = new DOMDocument($this->_element->ownerDocument->version, + $this->_element->ownerDocument->actualEncoding); + $root = $doc->createElement('rss'); + + // Use rss version 2.0 + $root->setAttribute('version', '2.0'); + + // Content namespace + $root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:content', 'http://purl.org/rss/1.0/modules/content/'); + $root->appendChild($doc->importNode($this->_element, true)); + + // Append root node + $doc->appendChild($root); + + // Format output + $doc->formatOutput = true; + + return $doc->saveXML(); + } + + /** + * Send feed to a http client with the correct header + * + * @return void + * @throws Zend_Feed_Exception if headers have already been sent + */ + public function send() + { + if (headers_sent()) { + /** + * @see Zend_Feed_Exception + */ + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Cannot send RSS because headers have already been sent.'); + } + + header('Content-Type: application/rss+xml; charset=' . $this->_element->ownerDocument->actualEncoding); + + echo $this->saveXml(); + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer.php b/oss/piwik/trunk/libs/Zend/Feed/Writer.php new file mode 100644 index 00000000..1a40f63c --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer.php @@ -0,0 +1,267 @@ + array(), + 'feed' => array(), + 'entryRenderer' => array(), + 'feedRenderer' => array(), + ); + + /** + * Set plugin loader for use with Extensions + * + * @param Zend_Loader_PluginLoader_Interface + */ + public static function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader) + { + self::$_pluginLoader = $loader; + } + + /** + * Get plugin loader for use with Extensions + * + * @return Zend_Loader_PluginLoader_Interface + */ + public static function getPluginLoader() + { + if (!isset(self::$_pluginLoader)) { + // require_once 'Zend/Loader/PluginLoader.php'; + self::$_pluginLoader = new Zend_Loader_PluginLoader(array( + 'Zend_Feed_Writer_Extension_' => 'Zend/Feed/Writer/Extension/', + )); + } + return self::$_pluginLoader; + } + + /** + * Add prefix path for loading Extensions + * + * @param string $prefix + * @param string $path + * @return void + */ + public static function addPrefixPath($prefix, $path) + { + $prefix = rtrim($prefix, '_'); + $path = rtrim($path, DIRECTORY_SEPARATOR); + self::getPluginLoader()->addPrefixPath($prefix, $path); + } + + /** + * Add multiple Extension prefix paths at once + * + * @param array $spec + * @return void + */ + public static function addPrefixPaths(array $spec) + { + if (isset($spec['prefix']) && isset($spec['path'])) { + self::addPrefixPath($spec['prefix'], $spec['path']); + } + foreach ($spec as $prefixPath) { + if (isset($prefixPath['prefix']) && isset($prefixPath['path'])) { + self::addPrefixPath($prefixPath['prefix'], $prefixPath['path']); + } + } + } + + /** + * Register an Extension by name + * + * @param string $name + * @return void + * @throws Zend_Feed_Exception if unable to resolve Extension class + */ + public static function registerExtension($name) + { + $feedName = $name . '_Feed'; + $entryName = $name . '_Entry'; + $feedRendererName = $name . '_Renderer_Feed'; + $entryRendererName = $name . '_Renderer_Entry'; + if (self::isRegistered($name)) { + if (self::getPluginLoader()->isLoaded($feedName) + || self::getPluginLoader()->isLoaded($entryName) + || self::getPluginLoader()->isLoaded($feedRendererName) + || self::getPluginLoader()->isLoaded($entryRendererName) + ) { + return; + } + } + try { + self::getPluginLoader()->load($feedName); + self::$_extensions['feed'][] = $feedName; + } catch (Zend_Loader_PluginLoader_Exception $e) { + } + try { + self::getPluginLoader()->load($entryName); + self::$_extensions['entry'][] = $entryName; + } catch (Zend_Loader_PluginLoader_Exception $e) { + } + try { + self::getPluginLoader()->load($feedRendererName); + self::$_extensions['feedRenderer'][] = $feedRendererName; + } catch (Zend_Loader_PluginLoader_Exception $e) { + } + try { + self::getPluginLoader()->load($entryRendererName); + self::$_extensions['entryRenderer'][] = $entryRendererName; + } catch (Zend_Loader_PluginLoader_Exception $e) { + } + if (!self::getPluginLoader()->isLoaded($feedName) + && !self::getPluginLoader()->isLoaded($entryName) + && !self::getPluginLoader()->isLoaded($feedRendererName) + && !self::getPluginLoader()->isLoaded($entryRendererName) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Could not load extension: ' . $name + . 'using Plugin Loader. Check prefix paths are configured and extension exists.'); + } + } + + /** + * Is a given named Extension registered? + * + * @param string $extensionName + * @return boolean + */ + public static function isRegistered($extensionName) + { + $feedName = $extensionName . '_Feed'; + $entryName = $extensionName . '_Entry'; + $feedRendererName = $extensionName . '_Renderer_Feed'; + $entryRendererName = $extensionName . '_Renderer_Entry'; + if (in_array($feedName, self::$_extensions['feed']) + || in_array($entryName, self::$_extensions['entry']) + || in_array($feedRendererName, self::$_extensions['feedRenderer']) + || in_array($entryRendererName, self::$_extensions['entryRenderer']) + ) { + return true; + } + return false; + } + + /** + * Get a list of extensions + * + * @return array + */ + public static function getExtensions() + { + return self::$_extensions; + } + + /** + * Reset class state to defaults + * + * @return void + */ + public static function reset() + { + self::$_pluginLoader = null; + self::$_prefixPaths = array(); + self::$_extensions = array( + 'entry' => array(), + 'feed' => array(), + 'entryRenderer' => array(), + 'feedRenderer' => array(), + ); + } + + /** + * Register core (default) extensions + * + * @return void + */ + public static function registerCoreExtensions() + { + self::registerExtension('DublinCore'); + self::registerExtension('Content'); + self::registerExtension('Atom'); + self::registerExtension('Slash'); + self::registerExtension('WellFormedWeb'); + self::registerExtension('Threading'); + self::registerExtension('ITunes'); + } + + public static function lcfirst($str) + { + $str[0] = strtolower($str[0]); + return $str; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Deleted.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Deleted.php new file mode 100644 index 00000000..19bb56b4 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Deleted.php @@ -0,0 +1,202 @@ +_data['encoding'] = $encoding; + } + + /** + * Get the feed character encoding + * + * @return string|null + */ + public function getEncoding() + { + if (!array_key_exists('encoding', $this->_data)) { + return 'UTF-8'; + } + return $this->_data['encoding']; + } + + /** + * Unset a specific data point + * + * @param string $name + */ + public function remove($name) + { + if (isset($this->_data[$name])) { + unset($this->_data[$name]); + } + } + + /** + * Set the current feed type being exported to "rss" or "atom". This allows + * other objects to gracefully choose whether to execute or not, depending + * on their appropriateness for the current type, e.g. renderers. + * + * @param string $type + */ + public function setType($type) + { + $this->_type = $type; + } + + /** + * Retrieve the current or last feed type exported. + * + * @return string Value will be "rss" or "atom" + */ + public function getType() + { + return $this->_type; + } + + public function setReference($reference) + { + if (empty($reference) || !is_string($reference)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: reference must be a non-empty string'); + } + $this->_data['reference'] = $reference; + } + + public function getReference() + { + if (!array_key_exists('reference', $this->_data)) { + return null; + } + return $this->_data['reference']; + } + + public function setWhen($date = null) + { + $zdate = null; + if (is_null($date)) { + $zdate = new Zend_Date; + } elseif (ctype_digit($date) && strlen($date) == 10) { + $zdate = new Zend_Date($date, Zend_Date::TIMESTAMP); + } elseif ($date instanceof Zend_Date) { + $zdate = $date; + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid Zend_Date object or UNIX Timestamp passed as parameter'); + } + $this->_data['when'] = $zdate; + } + + public function getWhen() + { + if (!array_key_exists('when', $this->_data)) { + return null; + } + return $this->_data['when']; + } + + public function setBy(array $by) + { + $author = array(); + if (!array_key_exists('name', $by) + || empty($by['name']) + || !is_string($by['name']) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: author array must include a "name" key with a non-empty string value'); + } + $author['name'] = $by['name']; + if (isset($by['email'])) { + if (empty($by['email']) || !is_string($by['email'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "email" array value must be a non-empty string'); + } + $author['email'] = $by['email']; + } + if (isset($by['uri'])) { + if (empty($by['uri']) + || !is_string($by['uri']) + || !Zend_Uri::check($by['uri']) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" array value must be a non-empty string and valid URI/IRI'); + } + $author['uri'] = $by['uri']; + } + $this->_data['by'] = $author; + } + + public function getBy() + { + if (!array_key_exists('by', $this->_data)) { + return null; + } + return $this->_data['by']; + } + + public function setComment($comment) + { + $this->_data['comment'] = $comment; + } + + public function getComment() + { + if (!array_key_exists('comment', $this->_data)) { + return null; + } + return $this->_data['comment']; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Entry.php new file mode 100644 index 00000000..01d290e6 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Entry.php @@ -0,0 +1,761 @@ +_loadExtensions(); + } + + /** + * Set a single author + * + * @param int $index + * @return string|null + */ + public function addAuthor($name, $email = null, $uri = null) + { + $author = array(); + if (is_array($name)) { + if (!array_key_exists('name', $name) + || empty($name['name']) + || !is_string($name['name']) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: author array must include a "name" key with a non-empty string value'); + } + $author['name'] = $name['name']; + if (isset($name['email'])) { + if (empty($name['email']) || !is_string($name['email'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "email" array value must be a non-empty string'); + } + $author['email'] = $name['email']; + } + if (isset($name['uri'])) { + if (empty($name['uri']) + || !is_string($name['uri']) + || !Zend_Uri::check($name['uri']) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" array value must be a non-empty string and valid URI/IRI'); + } + $author['uri'] = $name['uri']; + } + /** + * @deprecated + * Array notation (above) is preferred and will be the sole supported input from ZF 2.0 + */ + } else { + if (empty($name['name']) || !is_string($name['name'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "name" must be a non-empty string value'); + } + $author['name'] = $name; + if (isset($email)) { + if (empty($email) || !is_string($email)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "email" value must be a non-empty string'); + } + $author['email'] = $email; + } + if (isset($uri)) { + if (empty($uri) || !is_string($uri) || !Zend_Uri::check($uri)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" value must be a non-empty string and valid URI/IRI'); + } + $author['uri'] = $uri; + } + } + $this->_data['authors'][] = $author; + } + + /** + * Set an array with feed authors + * + * @return array + */ + public function addAuthors(array $authors) + { + foreach($authors as $author) { + $this->addAuthor($author); + } + } + + /** + * Set the feed character encoding + * + * @return string|null + */ + public function setEncoding($encoding) + { + if (empty($encoding) || !is_string($encoding)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['encoding'] = $encoding; + } + + /** + * Get the feed character encoding + * + * @return string|null + */ + public function getEncoding() + { + if (!array_key_exists('encoding', $this->_data)) { + return 'UTF-8'; + } + return $this->_data['encoding']; + } + + /** + * Set the copyright entry + * + * @return string|null + */ + public function setCopyright($copyright) + { + if (empty($copyright) || !is_string($copyright)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['copyright'] = $copyright; + } + + /** + * Set the entry's content + * + * @return string|null + */ + public function setContent($content) + { + if (empty($content) || !is_string($content)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['content'] = $content; + } + + /** + * Set the feed creation date + * + * @return string|null + */ + public function setDateCreated($date = null) + { + $zdate = null; + if (is_null($date)) { + $zdate = new Zend_Date; + } elseif (ctype_digit($date) && strlen($date) == 10) { + $zdate = new Zend_Date($date, Zend_Date::TIMESTAMP); + } elseif ($date instanceof Zend_Date) { + $zdate = $date; + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid Zend_Date object or UNIX Timestamp passed as parameter'); + } + $this->_data['dateCreated'] = $zdate; + } + + /** + * Set the feed modification date + * + * @return string|null + */ + public function setDateModified($date = null) + { + $zdate = null; + if (is_null($date)) { + $zdate = new Zend_Date; + } elseif (ctype_digit($date) && strlen($date) == 10) { + $zdate = new Zend_Date($date, Zend_Date::TIMESTAMP); + } elseif ($date instanceof Zend_Date) { + $zdate = $date; + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid Zend_Date object or UNIX Timestamp passed as parameter'); + } + $this->_data['dateModified'] = $zdate; + } + + /** + * Set the feed description + * + * @return string|null + */ + public function setDescription($description) + { + if (empty($description) || !is_string($description)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['description'] = $description; + } + + /** + * Set the feed ID + * + * @return string|null + */ + public function setId($id) + { + if (empty($id) || !is_string($id)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['id'] = $id; + } + + /** + * Set a link to the HTML source of this entry + * + * @return string|null + */ + public function setLink($link) + { + if (empty($link) || !is_string($link) || !Zend_Uri::check($link)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string and valid URI/IRI'); + } + $this->_data['link'] = $link; + } + + /** + * Set the number of comments associated with this entry + * + * @return string|null + */ + public function setCommentCount($count) + { + if (empty($count) || !is_numeric($count) || (int) $count < 0) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "count" must be a non-empty integer number'); + } + $this->_data['commentCount'] = (int) $count; + } + + /** + * Set a link to a HTML page containing comments associated with this entry + * + * @return string|null + */ + public function setCommentLink($link) + { + if (empty($link) || !is_string($link) || !Zend_Uri::check($link)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "link" must be a non-empty string and valid URI/IRI'); + } + $this->_data['commentLink'] = $link; + } + + /** + * Set a link to an XML feed for any comments associated with this entry + * + * @return string|null + */ + public function setCommentFeedLink(array $link) + { + if (!isset($link['uri']) || !is_string($link['uri']) || !Zend_Uri::check($link['uri'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "link" must be a non-empty string and valid URI/IRI'); + } + if (!isset($link['type']) || !in_array($link['type'], array('atom', 'rss', 'rdf'))) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "type" must be one' + . ' of "atom", "rss" or "rdf"'); + } + if (!isset($this->_data['commentFeedLinks'])) { + $this->_data['commentFeedLinks'] = array(); + } + $this->_data['commentFeedLinks'][] = $link; + } + + /** + * Set a links to an XML feed for any comments associated with this entry. + * Each link is an array with keys "uri" and "type", where type is one of: + * "atom", "rss" or "rdf". + * + * @return string|null + */ + public function setCommentFeedLinks(array $links) + { + foreach ($links as $link) { + $this->setCommentFeedLink($link); + } + } + + /** + * Set the feed title + * + * @return string|null + */ + public function setTitle($title) + { + if (empty($title) || !is_string($title)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['title'] = $title; + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (!array_key_exists('authors', $this->_data)) { + return null; + } + return $this->_data['authors']; + } + + /** + * Get the entry content + * + * @return string + */ + public function getContent() + { + if (!array_key_exists('content', $this->_data)) { + return null; + } + return $this->_data['content']; + } + + /** + * Get the entry copyright information + * + * @return string + */ + public function getCopyright() + { + if (!array_key_exists('copyright', $this->_data)) { + return null; + } + return $this->_data['copyright']; + } + + /** + * Get the entry creation date + * + * @return string + */ + public function getDateCreated() + { + if (!array_key_exists('dateCreated', $this->_data)) { + return null; + } + return $this->_data['dateCreated']; + } + + /** + * Get the entry modification date + * + * @return string + */ + public function getDateModified() + { + if (!array_key_exists('dateModified', $this->_data)) { + return null; + } + return $this->_data['dateModified']; + } + + /** + * Get the entry description + * + * @return string + */ + public function getDescription() + { + if (!array_key_exists('description', $this->_data)) { + return null; + } + return $this->_data['description']; + } + + /** + * Get the entry ID + * + * @return string + */ + public function getId() + { + if (!array_key_exists('id', $this->_data)) { + return null; + } + return $this->_data['id']; + } + + /** + * Get a link to the HTML source + * + * @return string|null + */ + public function getLink() + { + if (!array_key_exists('link', $this->_data)) { + return null; + } + return $this->_data['link']; + } + + + /** + * Get all links + * + * @return array + */ + public function getLinks() + { + if (!array_key_exists('links', $this->_data)) { + return null; + } + return $this->_data['links']; + } + + /** + * Get the entry title + * + * @return string + */ + public function getTitle() + { + if (!array_key_exists('title', $this->_data)) { + return null; + } + return $this->_data['title']; + } + + /** + * Get the number of comments/replies for current entry + * + * @return integer + */ + public function getCommentCount() + { + if (!array_key_exists('commentCount', $this->_data)) { + return null; + } + return $this->_data['commentCount']; + } + + /** + * Returns a URI pointing to the HTML page where comments can be made on this entry + * + * @return string + */ + public function getCommentLink() + { + if (!array_key_exists('commentLink', $this->_data)) { + return null; + } + return $this->_data['commentLink']; + } + + /** + * Returns an array of URIs pointing to a feed of all comments for this entry + * where the array keys indicate the feed type (atom, rss or rdf). + * + * @return string + */ + public function getCommentFeedLinks() + { + if (!array_key_exists('commentFeedLinks', $this->_data)) { + return null; + } + return $this->_data['commentFeedLinks']; + } + + /** + * Add a entry category + * + * @param string $category + */ + public function addCategory(array $category) + { + if (!isset($category['term'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Each category must be an array and ' + . 'contain at least a "term" element containing the machine ' + . ' readable category name'); + } + if (isset($category['scheme'])) { + if (empty($category['scheme']) + || !is_string($category['scheme']) + || !Zend_Uri::check($category['scheme']) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('The Atom scheme or RSS domain of' + . ' a category must be a valid URI'); + } + } + if (!isset($this->_data['categories'])) { + $this->_data['categories'] = array(); + } + $this->_data['categories'][] = $category; + } + + /** + * Set an array of entry categories + * + * @param array $categories + */ + public function addCategories(array $categories) + { + foreach ($categories as $category) { + $this->addCategory($category); + } + } + + /** + * Get the entry categories + * + * @return string|null + */ + public function getCategories() + { + if (!array_key_exists('categories', $this->_data)) { + return null; + } + return $this->_data['categories']; + } + + /** + * Adds an enclosure to the entry. The array parameter may contain the + * keys 'uri', 'type' and 'length'. Only 'uri' is required for Atom, though the + * others must also be provided or RSS rendering (where they are required) + * will throw an Exception. + * + * @param array $enclosures + */ + public function setEnclosure(array $enclosure) + { + if (!isset($enclosure['uri'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Enclosure "uri" is not set'); + } + if (!Zend_Uri::check($enclosure['uri'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Enclosure "uri" is not a valid URI/IRI'); + } + $this->_data['enclosure'] = $enclosure; + } + + /** + * Retrieve an array of all enclosures to be added to entry. + * + * @return array + */ + public function getEnclosure() + { + if (!array_key_exists('enclosure', $this->_data)) { + return null; + } + return $this->_data['enclosure']; + } + + /** + * Unset a specific data point + * + * @param string $name + */ + public function remove($name) + { + if (isset($this->_data[$name])) { + unset($this->_data[$name]); + } + } + + /** + * Get registered extensions + * + * @return array + */ + public function getExtensions() + { + return $this->_extensions; + } + + /** + * Return an Extension object with the matching name (postfixed with _Entry) + * + * @param string $name + * @return object + */ + public function getExtension($name) + { + if (array_key_exists($name . '_Entry', $this->_extensions)) { + return $this->_extensions[$name . '_Entry']; + } + return null; + } + + /** + * Set the current feed type being exported to "rss" or "atom". This allows + * other objects to gracefully choose whether to execute or not, depending + * on their appropriateness for the current type, e.g. renderers. + * + * @param string $type + */ + public function setType($type) + { + $this->_type = $type; + } + + /** + * Retrieve the current or last feed type exported. + * + * @return string Value will be "rss" or "atom" + */ + public function getType() + { + return $this->_type; + } + + /** + * Method overloading: call given method on first extension implementing it + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Feed_Exception if no extensions implements the method + */ + public function __call($method, $args) + { + foreach ($this->_extensions as $extension) { + try { + return call_user_func_array(array($extension, $method), $args); + } catch (Zend_Feed_Writer_Exception_InvalidMethodException $e) { + } + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Method: ' . $method + . ' does not exist and could not be located on a registered Extension'); + } + + /** + * Creates a new Zend_Feed_Writer_Source data container for use. This is NOT + * added to the current feed automatically, but is necessary to create a + * container with some initial values preset based on the current feed data. + * + * @return Zend_Feed_Writer_Source + */ + public function createSource() + { + $source = new Zend_Feed_Writer_Source; + if ($this->getEncoding()) { + $source->setEncoding($this->getEncoding()); + } + $source->setType($this->getType()); + return $source; + } + + /** + * Appends a Zend_Feed_Writer_Entry object representing a new entry/item + * the feed data container's internal group of entries. + * + * @param Zend_Feed_Writer_Source $source + */ + public function setSource(Zend_Feed_Writer_Source $source) + { + $this->_data['source'] = $source; + } + + /** + * @return Zend_Feed_Writer_Source + */ + public function getSource() + { + if (isset($this->_data['source'])) { + return $this->_data['source']; + } + return null; + } + + /** + * Load extensions from Zend_Feed_Writer + * + * @return void + */ + protected function _loadExtensions() + { + $all = Zend_Feed_Writer::getExtensions(); + $exts = $all['entry']; + foreach ($exts as $ext) { + $className = Zend_Feed_Writer::getPluginLoader()->getClassName($ext); + $this->_extensions[$ext] = new $className(); + $this->_extensions[$ext]->setEncoding($this->getEncoding()); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Exception/InvalidMethodException.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Exception/InvalidMethodException.php new file mode 100644 index 00000000..4510f12b --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Exception/InvalidMethodException.php @@ -0,0 +1,41 @@ +getType()) == 'atom') { + return; + } + $this->_setFeedLinks($this->_dom, $this->_base); + $this->_setHubs($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append namespaces to root element of feed + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:atom', + 'http://www.w3.org/2005/Atom'); + } + + /** + * Set feed link elements + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setFeedLinks(DOMDocument $dom, DOMElement $root) + { + $flinks = $this->getDataContainer()->getFeedLinks(); + if(!$flinks || empty($flinks)) { + return; + } + foreach ($flinks as $type => $href) { + $mime = 'application/' . strtolower($type) . '+xml'; + $flink = $dom->createElement('atom:link'); + $root->appendChild($flink); + $flink->setAttribute('rel', 'self'); + $flink->setAttribute('type', $mime); + $flink->setAttribute('href', $href); + } + $this->_called = true; + } + + /** + * Set PuSH hubs + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setHubs(DOMDocument $dom, DOMElement $root) + { + $hubs = $this->getDataContainer()->getHubs(); + if (!$hubs || empty($hubs)) { + return; + } + foreach ($hubs as $hubUrl) { + $hub = $dom->createElement('atom:link'); + $hub->setAttribute('rel', 'hub'); + $hub->setAttribute('href', $hubUrl); + $root->appendChild($hub); + } + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/Content/Renderer/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/Content/Renderer/Entry.php new file mode 100644 index 00000000..0036c632 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/Content/Renderer/Entry.php @@ -0,0 +1,92 @@ +getType()) == 'atom') { + return; + } + $this->_setContent($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append namespaces to root element + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:content', + 'http://purl.org/rss/1.0/modules/content/'); + } + + /** + * Set entry content + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setContent(DOMDocument $dom, DOMElement $root) + { + $content = $this->getDataContainer()->getContent(); + if (!$content) { + return; + } + $element = $dom->createElement('content:encoded'); + $root->appendChild($element); + $cdata = $dom->createCDATASection($content); + $element->appendChild($cdata); + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/DublinCore/Renderer/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/DublinCore/Renderer/Entry.php new file mode 100644 index 00000000..c433ea3a --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/DublinCore/Renderer/Entry.php @@ -0,0 +1,96 @@ +getType()) == 'atom') { + return; + } + $this->_setAuthors($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append namespaces to entry + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:dc', + 'http://purl.org/dc/elements/1.1/'); + } + + /** + * Set entry author elements + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->getDataContainer()->getAuthors(); + if (!$authors || empty($authors)) { + return; + } + foreach ($authors as $data) { + $author = $this->_dom->createElement('dc:creator'); + if (array_key_exists('name', $data)) { + $text = $dom->createTextNode($data['name']); + $author->appendChild($text); + $root->appendChild($author); + } + } + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/DublinCore/Renderer/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/DublinCore/Renderer/Feed.php new file mode 100644 index 00000000..e1a01050 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/DublinCore/Renderer/Feed.php @@ -0,0 +1,96 @@ +getType()) == 'atom') { + return; + } + $this->_setAuthors($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append namespaces to feed element + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:dc', + 'http://purl.org/dc/elements/1.1/'); + } + + /** + * Set feed authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->getDataContainer()->getAuthors(); + if (!$authors || empty($authors)) { + return; + } + foreach ($authors as $data) { + $author = $this->_dom->createElement('dc:creator'); + if (array_key_exists('name', $data)) { + $text = $dom->createTextNode($data['name']); + $author->appendChild($text); + $root->appendChild($author); + } + } + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Entry.php new file mode 100644 index 00000000..1d7fbc50 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Entry.php @@ -0,0 +1,242 @@ +_encoding = $enc; + return $this; + } + + /** + * Get feed encoding + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Set a block value of "yes" or "no". You may also set an empty string. + * + * @param string + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function setItunesBlock($value) + { + if (!ctype_alpha($value) && strlen($value) > 0) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "block" may only' + . ' contain alphabetic characters'); + } + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "block" may only' + . ' contain a maximum of 255 characters'); + } + $this->_data['block'] = $value; + } + + /** + * Add authors to itunes entry + * + * @param array $values + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function addItunesAuthors(array $values) + { + foreach ($values as $value) { + $this->addItunesAuthor($value); + } + return $this; + } + + /** + * Add author to itunes entry + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function addItunesAuthor($value) + { + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "author" may only' + . ' contain a maximum of 255 characters each'); + } + if (!isset($this->_data['authors'])) { + $this->_data['authors'] = array(); + } + $this->_data['authors'][] = $value; + return $this; + } + + /** + * Set duration + * + * @param int $value + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function setItunesDuration($value) + { + $value = (string) $value; + if (!ctype_digit($value) + && !preg_match("/^\d+:[0-5]{1}[0-9]{1}$/", $value) + && !preg_match("/^\d+:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}$/", $value) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "duration" may only' + . ' be of a specified [[HH:]MM:]SS format'); + } + $this->_data['duration'] = $value; + return $this; + } + + /** + * Set "explicit" flag + * + * @param bool $value + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function setItunesExplicit($value) + { + if (!in_array($value, array('yes','no','clean'))) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "explicit" may only' + . ' be one of "yes", "no" or "clean"'); + } + $this->_data['explicit'] = $value; + return $this; + } + + /** + * Set keywords + * + * @param array $value + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function setItunesKeywords(array $value) + { + if (count($value) > 12) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "keywords" may only' + . ' contain a maximum of 12 terms'); + } + $concat = implode(',', $value); + if (iconv_strlen($concat, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "keywords" may only' + . ' have a concatenated length of 255 chars where terms are delimited' + . ' by a comma'); + } + $this->_data['keywords'] = $value; + return $this; + } + + /** + * Set subtitle + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function setItunesSubtitle($value) + { + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "subtitle" may only' + . ' contain a maximum of 255 characters'); + } + $this->_data['subtitle'] = $value; + return $this; + } + + /** + * Set summary + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Entry + */ + public function setItunesSummary($value) + { + if (iconv_strlen($value, $this->getEncoding()) > 4000) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "summary" may only' + . ' contain a maximum of 4000 characters'); + } + $this->_data['summary'] = $value; + return $this; + } + + /** + * Overloading to itunes specific setters + * + * @param string $method + * @param array $params + * @return mixed + */ + public function __call($method, array $params) + { + $point = Zend_Feed_Writer::lcfirst(substr($method, 9)); + if (!method_exists($this, 'setItunes' . ucfirst($point)) + && !method_exists($this, 'addItunes' . ucfirst($point)) + ) { + // require_once 'Zend/Feed/Writer/Exception/InvalidMethodException.php'; + throw new Zend_Feed_Writer_Exception_InvalidMethodException( + 'invalid method: ' . $method + ); + } + if (!array_key_exists($point, $this->_data) + || empty($this->_data[$point]) + ) { + return null; + } + return $this->_data[$point]; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Feed.php new file mode 100644 index 00000000..caf0440a --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Feed.php @@ -0,0 +1,361 @@ +_encoding = $enc; + return $this; + } + + /** + * Get feed encoding + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Set a block value of "yes" or "no". You may also set an empty string. + * + * @param string + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesBlock($value) + { + if (!ctype_alpha($value) && strlen($value) > 0) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "block" may only' + . ' contain alphabetic characters'); + } + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "block" may only' + . ' contain a maximum of 255 characters'); + } + $this->_data['block'] = $value; + return $this; + } + + /** + * Add feed authors + * + * @param array $values + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function addItunesAuthors(array $values) + { + foreach ($values as $value) { + $this->addItunesAuthor($value); + } + return $this; + } + + /** + * Add feed author + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function addItunesAuthor($value) + { + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "author" may only' + . ' contain a maximum of 255 characters each'); + } + if (!isset($this->_data['authors'])) { + $this->_data['authors'] = array(); + } + $this->_data['authors'][] = $value; + return $this; + } + + /** + * Set feed categories + * + * @param array $values + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesCategories(array $values) + { + if (!isset($this->_data['categories'])) { + $this->_data['categories'] = array(); + } + foreach ($values as $key=>$value) { + if (!is_array($value)) { + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "category" may only' + . ' contain a maximum of 255 characters each'); + } + $this->_data['categories'][] = $value; + } else { + if (iconv_strlen($key, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "category" may only' + . ' contain a maximum of 255 characters each'); + } + $this->_data['categories'][$key] = array(); + foreach ($value as $val) { + if (iconv_strlen($val, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "category" may only' + . ' contain a maximum of 255 characters each'); + } + $this->_data['categories'][$key][] = $val; + } + } + } + return $this; + } + + /** + * Set feed image (icon) + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesImage($value) + { + if (!Zend_Uri::check($value)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "image" may only' + . ' be a valid URI/IRI'); + } + if (!in_array(substr($value, -3), array('jpg','png'))) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "image" may only' + . ' use file extension "jpg" or "png" which must be the last three' + . ' characters of the URI (i.e. no query string or fragment)'); + } + $this->_data['image'] = $value; + return $this; + } + + /** + * Set feed cumulative duration + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesDuration($value) + { + $value = (string) $value; + if (!ctype_digit($value) + && !preg_match("/^\d+:[0-5]{1}[0-9]{1}$/", $value) + && !preg_match("/^\d+:[0-5]{1}[0-9]{1}:[0-5]{1}[0-9]{1}$/", $value) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "duration" may only' + . ' be of a specified [[HH:]MM:]SS format'); + } + $this->_data['duration'] = $value; + return $this; + } + + /** + * Set "explicit" flag + * + * @param bool $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesExplicit($value) + { + if (!in_array($value, array('yes','no','clean'))) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "explicit" may only' + . ' be one of "yes", "no" or "clean"'); + } + $this->_data['explicit'] = $value; + return $this; + } + + /** + * Set feed keywords + * + * @param array $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesKeywords(array $value) + { + if (count($value) > 12) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "keywords" may only' + . ' contain a maximum of 12 terms'); + } + $concat = implode(',', $value); + if (iconv_strlen($concat, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "keywords" may only' + . ' have a concatenated length of 255 chars where terms are delimited' + . ' by a comma'); + } + $this->_data['keywords'] = $value; + return $this; + } + + /** + * Set new feed URL + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesNewFeedUrl($value) + { + if (!Zend_Uri::check($value)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "newFeedUrl" may only' + . ' be a valid URI/IRI'); + } + $this->_data['newFeedUrl'] = $value; + return $this; + } + + /** + * Add feed owners + * + * @param array $values + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function addItunesOwners(array $values) + { + foreach ($values as $value) { + $this->addItunesOwner($value); + } + return $this; + } + + /** + * Add feed owner + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function addItunesOwner(array $value) + { + if (!isset($value['name']) || !isset($value['email'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "owner" must' + . ' be an array containing keys "name" and "email"'); + } + if (iconv_strlen($value['name'], $this->getEncoding()) > 255 + || iconv_strlen($value['email'], $this->getEncoding()) > 255 + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: any "owner" may only' + . ' contain a maximum of 255 characters each for "name" and "email"'); + } + if (!isset($this->_data['owners'])) { + $this->_data['owners'] = array(); + } + $this->_data['owners'][] = $value; + return $this; + } + + /** + * Set feed subtitle + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesSubtitle($value) + { + if (iconv_strlen($value, $this->getEncoding()) > 255) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "subtitle" may only' + . ' contain a maximum of 255 characters'); + } + $this->_data['subtitle'] = $value; + return $this; + } + + /** + * Set feed summary + * + * @param string $value + * @return Zend_Feed_Writer_Extension_ITunes_Feed + */ + public function setItunesSummary($value) + { + if (iconv_strlen($value, $this->getEncoding()) > 4000) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('invalid parameter: "summary" may only' + . ' contain a maximum of 4000 characters'); + } + $this->_data['summary'] = $value; + return $this; + } + + /** + * Overloading: proxy to internal setters + * + * @param string $method + * @param array $params + * @return mixed + */ + public function __call($method, array $params) + { + $point = Zend_Feed_Writer::lcfirst(substr($method, 9)); + if (!method_exists($this, 'setItunes' . ucfirst($point)) + && !method_exists($this, 'addItunes' . ucfirst($point)) + ) { + // require_once 'Zend/Feed/Writer/Exception/InvalidMethodException.php'; + throw new Zend_Feed_Writer_Exception_InvalidMethodException( + 'invalid method: ' . $method + ); + } + if (!array_key_exists($point, $this->_data) || empty($this->_data[$point])) { + return null; + } + return $this->_data[$point]; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Renderer/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Renderer/Entry.php new file mode 100644 index 00000000..046cb546 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Renderer/Entry.php @@ -0,0 +1,216 @@ +_setAuthors($this->_dom, $this->_base); + $this->_setBlock($this->_dom, $this->_base); + $this->_setDuration($this->_dom, $this->_base); + $this->_setExplicit($this->_dom, $this->_base); + $this->_setKeywords($this->_dom, $this->_base); + $this->_setSubtitle($this->_dom, $this->_base); + $this->_setSummary($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append namespaces to entry root + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:itunes', + 'http://www.itunes.com/dtds/podcast-1.0.dtd'); + } + + /** + * Set entry authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->getDataContainer()->getItunesAuthors(); + if (!$authors || empty($authors)) { + return; + } + foreach ($authors as $author) { + $el = $dom->createElement('itunes:author'); + $text = $dom->createTextNode($author); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + } + + /** + * Set itunes block + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setBlock(DOMDocument $dom, DOMElement $root) + { + $block = $this->getDataContainer()->getItunesBlock(); + if (is_null($block)) { + return; + } + $el = $dom->createElement('itunes:block'); + $text = $dom->createTextNode($block); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set entry duration + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDuration(DOMDocument $dom, DOMElement $root) + { + $duration = $this->getDataContainer()->getItunesDuration(); + if (!$duration) { + return; + } + $el = $dom->createElement('itunes:duration'); + $text = $dom->createTextNode($duration); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set explicit flag + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setExplicit(DOMDocument $dom, DOMElement $root) + { + $explicit = $this->getDataContainer()->getItunesExplicit(); + if (is_null($explicit)) { + return; + } + $el = $dom->createElement('itunes:explicit'); + $text = $dom->createTextNode($explicit); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set entry keywords + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setKeywords(DOMDocument $dom, DOMElement $root) + { + $keywords = $this->getDataContainer()->getItunesKeywords(); + if (!$keywords || empty($keywords)) { + return; + } + $el = $dom->createElement('itunes:keywords'); + $text = $dom->createTextNode(implode(',', $keywords)); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set entry subtitle + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setSubtitle(DOMDocument $dom, DOMElement $root) + { + $subtitle = $this->getDataContainer()->getItunesSubtitle(); + if (!$subtitle) { + return; + } + $el = $dom->createElement('itunes:subtitle'); + $text = $dom->createTextNode($subtitle); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set entry summary + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setSummary(DOMDocument $dom, DOMElement $root) + { + $summary = $this->getDataContainer()->getItunesSummary(); + if (!$summary) { + return; + } + $el = $dom->createElement('itunes:summary'); + $text = $dom->createTextNode($summary); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Renderer/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Renderer/Feed.php new file mode 100644 index 00000000..192d19ec --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/ITunes/Renderer/Feed.php @@ -0,0 +1,320 @@ +_setAuthors($this->_dom, $this->_base); + $this->_setBlock($this->_dom, $this->_base); + $this->_setCategories($this->_dom, $this->_base); + $this->_setImage($this->_dom, $this->_base); + $this->_setDuration($this->_dom, $this->_base); + $this->_setExplicit($this->_dom, $this->_base); + $this->_setKeywords($this->_dom, $this->_base); + $this->_setNewFeedUrl($this->_dom, $this->_base); + $this->_setOwners($this->_dom, $this->_base); + $this->_setSubtitle($this->_dom, $this->_base); + $this->_setSummary($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append feed namespaces + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:itunes', + 'http://www.itunes.com/dtds/podcast-1.0.dtd'); + } + + /** + * Set feed authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->getDataContainer()->getItunesAuthors(); + if (!$authors || empty($authors)) { + return; + } + foreach ($authors as $author) { + $el = $dom->createElement('itunes:author'); + $text = $dom->createTextNode($author); + $el->appendChild($text); + $root->appendChild($el); + } + $this->_called = true; + } + + /** + * Set feed itunes block + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setBlock(DOMDocument $dom, DOMElement $root) + { + $block = $this->getDataContainer()->getItunesBlock(); + if (is_null($block)) { + return; + } + $el = $dom->createElement('itunes:block'); + $text = $dom->createTextNode($block); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set feed categories + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCategories(DOMDocument $dom, DOMElement $root) + { + $cats = $this->getDataContainer()->getItunesCategories(); + if (!$cats || empty($cats)) { + return; + } + foreach ($cats as $key=>$cat) { + if (!is_array($cat)) { + $el = $dom->createElement('itunes:category'); + $el->setAttribute('text', $cat); + $root->appendChild($el); + } else { + $el = $dom->createElement('itunes:category'); + $el->setAttribute('text', $key); + $root->appendChild($el); + foreach ($cat as $subcat) { + $el2 = $dom->createElement('itunes:category'); + $el2->setAttribute('text', $subcat); + $el->appendChild($el2); + } + } + } + $this->_called = true; + } + + /** + * Set feed image (icon) + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setImage(DOMDocument $dom, DOMElement $root) + { + $image = $this->getDataContainer()->getItunesImage(); + if (!$image) { + return; + } + $el = $dom->createElement('itunes:image'); + $el->setAttribute('href', $image); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set feed cumulative duration + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDuration(DOMDocument $dom, DOMElement $root) + { + $duration = $this->getDataContainer()->getItunesDuration(); + if (!$duration) { + return; + } + $el = $dom->createElement('itunes:duration'); + $text = $dom->createTextNode($duration); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set explicit flag + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setExplicit(DOMDocument $dom, DOMElement $root) + { + $explicit = $this->getDataContainer()->getItunesExplicit(); + if (is_null($explicit)) { + return; + } + $el = $dom->createElement('itunes:explicit'); + $text = $dom->createTextNode($explicit); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set feed keywords + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setKeywords(DOMDocument $dom, DOMElement $root) + { + $keywords = $this->getDataContainer()->getItunesKeywords(); + if (!$keywords || empty($keywords)) { + return; + } + $el = $dom->createElement('itunes:keywords'); + $text = $dom->createTextNode(implode(',', $keywords)); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set feed's new URL + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setNewFeedUrl(DOMDocument $dom, DOMElement $root) + { + $url = $this->getDataContainer()->getItunesNewFeedUrl(); + if (!$url) { + return; + } + $el = $dom->createElement('itunes:new-feed-url'); + $text = $dom->createTextNode($url); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set feed owners + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setOwners(DOMDocument $dom, DOMElement $root) + { + $owners = $this->getDataContainer()->getItunesOwners(); + if (!$owners || empty($owners)) { + return; + } + foreach ($owners as $owner) { + $el = $dom->createElement('itunes:owner'); + $name = $dom->createElement('itunes:name'); + $text = $dom->createTextNode($owner['name']); + $name->appendChild($text); + $email = $dom->createElement('itunes:email'); + $text = $dom->createTextNode($owner['email']); + $email->appendChild($text); + $root->appendChild($el); + $el->appendChild($name); + $el->appendChild($email); + } + $this->_called = true; + } + + /** + * Set feed subtitle + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setSubtitle(DOMDocument $dom, DOMElement $root) + { + $subtitle = $this->getDataContainer()->getItunesSubtitle(); + if (!$subtitle) { + return; + } + $el = $dom->createElement('itunes:subtitle'); + $text = $dom->createTextNode($subtitle); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } + + /** + * Set feed summary + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setSummary(DOMDocument $dom, DOMElement $root) + { + $summary = $this->getDataContainer()->getItunesSummary(); + if (!$summary) { + return; + } + $el = $dom->createElement('itunes:summary'); + $text = $dom->createTextNode($summary); + $el->appendChild($text); + $root->appendChild($el); + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/RendererAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/RendererAbstract.php new file mode 100644 index 00000000..b381be36 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/RendererAbstract.php @@ -0,0 +1,179 @@ +_container = $container; + } + + /** + * Set feed encoding + * + * @param string $enc + * @return Zend_Feed_Writer_Extension_RendererAbstract + */ + public function setEncoding($enc) + { + $this->_encoding = $enc; + return $this; + } + + /** + * Get feed encoding + * + * @return void + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Set DOMDocument and DOMElement on which to operate + * + * @param DOMDocument $dom + * @param DOMElement $base + * @return Zend_Feed_Writer_Extension_RendererAbstract + */ + public function setDomDocument(DOMDocument $dom, DOMElement $base) + { + $this->_dom = $dom; + $this->_base = $base; + return $this; + } + + /** + * Get data container being rendered + * + * @return mixed + */ + public function getDataContainer() + { + return $this->_container; + } + + /** + * Set feed type + * + * @param string $type + * @return Zend_Feed_Writer_Extension_RendererAbstract + */ + public function setType($type) + { + $this->_type = $type; + return $this; + } + + /** + * Get feedtype + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Set root element of document + * + * @param DOMElement $root + * @return Zend_Feed_Writer_Extension_RendererAbstract + */ + public function setRootElement(DOMElement $root) + { + $this->_rootElement = $root; + return $this; + } + + /** + * Get root element + * + * @return DOMElement + */ + public function getRootElement() + { + return $this->_rootElement; + } + + /** + * Append namespaces to feed + * + * @return void + */ + abstract protected function _appendNamespaces(); +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/RendererInterface.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/RendererInterface.php new file mode 100644 index 00000000..e9c90141 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/RendererInterface.php @@ -0,0 +1,59 @@ +getType()) == 'atom') { + return; // RSS 2.0 only + } + $this->_setCommentCount($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append entry namespaces + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:slash', + 'http://purl.org/rss/1.0/modules/slash/'); + } + + /** + * Set entry comment count + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCommentCount(DOMDocument $dom, DOMElement $root) + { + $count = $this->getDataContainer()->getCommentCount(); + if (!$count) { + $count = 0; + } + $tcount = $this->_dom->createElement('slash:comments'); + $tcount->nodeValue = $count; + $root->appendChild($tcount); + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/Threading/Renderer/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/Threading/Renderer/Entry.php new file mode 100644 index 00000000..69e731d4 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/Threading/Renderer/Entry.php @@ -0,0 +1,145 @@ +getType()) == 'rss') { + return; // Atom 1.0 only + } + $this->_setCommentLink($this->_dom, $this->_base); + $this->_setCommentFeedLinks($this->_dom, $this->_base); + $this->_setCommentCount($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append entry namespaces + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:thr', + 'http://purl.org/syndication/thread/1.0'); + } + + /** + * Set comment link + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCommentLink(DOMDocument $dom, DOMElement $root) + { + $link = $this->getDataContainer()->getCommentLink(); + if (!$link) { + return; + } + $clink = $this->_dom->createElement('link'); + $clink->setAttribute('rel', 'replies'); + $clink->setAttribute('type', 'text/html'); + $clink->setAttribute('href', $link); + $count = $this->getDataContainer()->getCommentCount(); + if (!is_null($count)) { + $clink->setAttribute('thr:count', $count); + } + $root->appendChild($clink); + $this->_called = true; + } + + /** + * Set comment feed links + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCommentFeedLinks(DOMDocument $dom, DOMElement $root) + { + $links = $this->getDataContainer()->getCommentFeedLinks(); + if (!$links || empty($links)) { + return; + } + foreach ($links as $link) { + $flink = $this->_dom->createElement('link'); + $flink->setAttribute('rel', 'replies'); + $flink->setAttribute('type', 'application/'. $link['type'] .'+xml'); + $flink->setAttribute('href', $link['uri']); + $count = $this->getDataContainer()->getCommentCount(); + if (!is_null($count)) { + $flink->setAttribute('thr:count', $count); + } + $root->appendChild($flink); + $this->_called = true; + } + } + + /** + * Set entry comment count + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCommentCount(DOMDocument $dom, DOMElement $root) + { + $count = $this->getDataContainer()->getCommentCount(); + if (is_null($count)) { + return; + } + $tcount = $this->_dom->createElement('thr:total'); + $tcount->nodeValue = $count; + $root->appendChild($tcount); + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/WellFormedWeb/Renderer/Entry.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/WellFormedWeb/Renderer/Entry.php new file mode 100644 index 00000000..c57244ae --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Extension/WellFormedWeb/Renderer/Entry.php @@ -0,0 +1,96 @@ +getType()) == 'atom') { + return; // RSS 2.0 only + } + $this->_setCommentFeedLinks($this->_dom, $this->_base); + if ($this->_called) { + $this->_appendNamespaces(); + } + } + + /** + * Append entry namespaces + * + * @return void + */ + protected function _appendNamespaces() + { + $this->getRootElement()->setAttribute('xmlns:wfw', + 'http://wellformedweb.org/CommentAPI/'); + } + + /** + * Set entry comment feed links + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCommentFeedLinks(DOMDocument $dom, DOMElement $root) + { + $links = $this->getDataContainer()->getCommentFeedLinks(); + if (!$links || empty($links)) { + return; + } + foreach ($links as $link) { + if ($link['type'] == 'rss') { + $flink = $this->_dom->createElement('wfw:commentRss'); + $text = $dom->createTextNode($link['uri']); + $flink->appendChild($text); + $root->appendChild($flink); + } + } + $this->_called = true; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Feed.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Feed.php new file mode 100644 index 00000000..724caed1 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Feed.php @@ -0,0 +1,281 @@ +getEncoding()) { + $entry->setEncoding($this->getEncoding()); + } + $entry->setType($this->getType()); + return $entry; + } + + /** + * Appends a Zend_Feed_Writer_Deleted object representing a new entry tombstone + * to the feed data container's internal group of entries. + * + * @param Zend_Feed_Writer_Deleted $entry + */ + public function addTombstone(Zend_Feed_Writer_Deleted $deleted) + { + $this->_entries[] = $deleted; + } + + /** + * Creates a new Zend_Feed_Writer_Deleted data container for use. This is NOT + * added to the current feed automatically, but is necessary to create a + * container with some initial values preset based on the current feed data. + * + * @return Zend_Feed_Writer_Deleted + */ + public function createTombstone() + { + $deleted = new Zend_Feed_Writer_Deleted; + if ($this->getEncoding()) { + $deleted->setEncoding($this->getEncoding()); + } + $deleted->setType($this->getType()); + return $deleted; + } + + /** + * Appends a Zend_Feed_Writer_Entry object representing a new entry/item + * the feed data container's internal group of entries. + * + * @param Zend_Feed_Writer_Entry $entry + */ + public function addEntry(Zend_Feed_Writer_Entry $entry) + { + $this->_entries[] = $entry; + } + + /** + * Removes a specific indexed entry from the internal queue. Entries must be + * added to a feed container in order to be indexed. + * + * @param int $index + */ + public function removeEntry($index) + { + if (isset($this->_entries[$index])) { + unset($this->_entries[$index]); + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Undefined index: ' . $index . '. Entry does not exist.'); + } + + /** + * Retrieve a specific indexed entry from the internal queue. Entries must be + * added to a feed container in order to be indexed. + * + * @param int $index + */ + public function getEntry($index = 0) + { + if (isset($this->_entries[$index])) { + return $this->_entries[$index]; + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Undefined index: ' . $index . '. Entry does not exist.'); + } + + /** + * Orders all indexed entries by date, thus offering date ordered readable + * content where a parser (or Homo Sapien) ignores the generic rule that + * XML element order is irrelevant and has no intrinsic meaning. + * + * Using this method will alter the original indexation. + * + * @return void + */ + public function orderByDate() + { + /** + * Could do with some improvement for performance perhaps + */ + $timestamp = time(); + $entries = array(); + foreach ($this->_entries as $entry) { + if ($entry->getDateModified()) { + $timestamp = (int) $entry->getDateModified()->get(Zend_Date::TIMESTAMP); + } elseif ($entry->getDateCreated()) { + $timestamp = (int) $entry->getDateCreated()->get(Zend_Date::TIMESTAMP); + } + $entries[$timestamp] = $entry; + } + krsort($entries, SORT_NUMERIC); + $this->_entries = array_values($entries); + } + + /** + * Get the number of feed entries. + * Required by the Iterator interface. + * + * @return int + */ + public function count() + { + return count($this->_entries); + } + + /** + * Return the current entry + * + * @return Zend_Feed_Reader_Entry_Interface + */ + public function current() + { + return $this->_entries[$this->key()]; + } + + /** + * Return the current feed key + * + * @return unknown + */ + public function key() + { + return $this->_entriesKey; + } + + /** + * Move the feed pointer forward + * + * @return void + */ + public function next() + { + ++$this->_entriesKey; + } + + /** + * Reset the pointer in the feed object + * + * @return void + */ + public function rewind() + { + $this->_entriesKey = 0; + } + + /** + * Check to see if the iterator is still valid + * + * @return boolean + */ + public function valid() + { + return 0 <= $this->_entriesKey && $this->_entriesKey < $this->count(); + } + + /** + * Attempt to build and return the feed resulting from the data set + * + * @param $type The feed type "rss" or "atom" to export as + * @return string + */ + public function export($type, $ignoreExceptions = false) + { + $this->setType(strtolower($type)); + $type = ucfirst($this->getType()); + if ($type !== 'Rss' && $type !== 'Atom') { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid feed type specified: ' . $type . '.' + . ' Should be one of "rss" or "atom".'); + } + $renderClass = 'Zend_Feed_Writer_Renderer_Feed_' . $type; + $renderer = new $renderClass($this); + if ($ignoreExceptions) { + $renderer->ignoreExceptions(); + } + return $renderer->render()->saveXml(); + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Feed/FeedAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Feed/FeedAbstract.php new file mode 100644 index 00000000..bc2a0fa0 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Feed/FeedAbstract.php @@ -0,0 +1,805 @@ +_loadExtensions(); + } + + /** + * Set a single author + * + * @param int $index + * @return string|null + */ + public function addAuthor($name, $email = null, $uri = null) + { + $author = array(); + if (is_array($name)) { + if (!array_key_exists('name', $name) || empty($name['name']) || !is_string($name['name'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: author array must include a "name" key with a non-empty string value'); + } + $author['name'] = $name['name']; + if (isset($name['email'])) { + if (empty($name['email']) || !is_string($name['email'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "email" array value must be a non-empty string'); + } + $author['email'] = $name['email']; + } + if (isset($name['uri'])) { + if (empty($name['uri']) || !is_string($name['uri']) || !Zend_Uri::check($name['uri'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" array value must be a non-empty string and valid URI/IRI'); + } + $author['uri'] = $name['uri']; + } + } else { + if (empty($name['name']) || !is_string($name['name'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "name" must be a non-empty string value'); + } + $author['name'] = $name; + if (isset($email)) { + if (empty($email) || !is_string($email)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "email" value must be a non-empty string'); + } + $author['email'] = $email; + } + if (isset($uri)) { + if (empty($uri) || !is_string($uri) || !Zend_Uri::check($uri)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" value must be a non-empty string and valid URI/IRI'); + } + $author['uri'] = $uri; + } + } + $this->_data['authors'][] = $author; + } + + /** + * Set an array with feed authors + * + * @return array + */ + public function addAuthors(array $authors) + { + foreach($authors as $author) { + $this->addAuthor($author); + } + } + + /** + * Set the copyright entry + * + * @return string|null + */ + public function setCopyright($copyright) + { + if (empty($copyright) || !is_string($copyright)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['copyright'] = $copyright; + } + + /** + * Set the feed creation date + * + * @param null|integer|Zend_Date + */ + public function setDateCreated($date = null) + { + $zdate = null; + if (is_null($date)) { + $zdate = new Zend_Date; + } elseif (ctype_digit($date) && strlen($date) == 10) { + $zdate = new Zend_Date($date, Zend_Date::TIMESTAMP); + } elseif ($date instanceof Zend_Date) { + $zdate = $date; + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid Zend_Date object or UNIX Timestamp passed as parameter'); + } + $this->_data['dateCreated'] = $zdate; + } + + /** + * Set the feed modification date + * + * @param null|integer|Zend_Date + */ + public function setDateModified($date = null) + { + $zdate = null; + if (is_null($date)) { + $zdate = new Zend_Date; + } elseif (ctype_digit($date) && strlen($date) == 10) { + $zdate = new Zend_Date($date, Zend_Date::TIMESTAMP); + } elseif ($date instanceof Zend_Date) { + $zdate = $date; + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid Zend_Date object or UNIX Timestamp passed as parameter'); + } + $this->_data['dateModified'] = $zdate; + } + + /** + * Set the feed last-build date. Ignored for Atom 1.0. + * + * @param null|integer|Zend_Date + */ + public function setLastBuildDate($date = null) + { + $zdate = null; + if (is_null($date)) { + $zdate = new Zend_Date; + } elseif (ctype_digit($date) && strlen($date) == 10) { + $zdate = new Zend_Date($date, Zend_Date::TIMESTAMP); + } elseif ($date instanceof Zend_Date) { + $zdate = $date; + } else { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid Zend_Date object or UNIX Timestamp passed as parameter'); + } + $this->_data['lastBuildDate'] = $zdate; + } + + /** + * Set the feed description + * + * @return string|null + */ + public function setDescription($description) + { + if (empty($description) || !is_string($description)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['description'] = $description; + } + + /** + * Set the feed generator entry + * + * @return string|null + */ + public function setGenerator($name, $version = null, $uri = null) + { + if (is_array($name)) { + $data = $name; + if (empty($data['name']) || !is_string($data['name'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "name" must be a non-empty string'); + } + $generator = array('name' => $data['name']); + if (isset($data['version'])) { + if (empty($data['version']) || !is_string($data['version'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "version" must be a non-empty string'); + } + $generator['version'] = $data['version']; + } + if (isset($data['uri'])) { + if (empty($data['uri']) || !is_string($data['uri']) || !Zend_Uri::check($data['uri'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" must be a non-empty string and a valid URI/IRI'); + } + $generator['uri'] = $data['uri']; + } + } else { + if (empty($name) || !is_string($name)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "name" must be a non-empty string'); + } + $generator = array('name' => $name); + if (isset($version)) { + if (empty($version) || !is_string($version)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "version" must be a non-empty string'); + } + $generator['version'] = $version; + } + if (isset($uri)) { + if (empty($uri) || !is_string($uri) || !Zend_Uri::check($uri)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "uri" must be a non-empty string and a valid URI/IRI'); + } + $generator['uri'] = $uri; + } + } + $this->_data['generator'] = $generator; + } + + /** + * Set the feed ID - URI or URN (via PCRE pattern) supported + * + * @param string $id + */ + public function setId($id) + { + if ((empty($id) || !is_string($id) || !Zend_Uri::check($id)) && + !preg_match("#^urn:[a-zA-Z0-9][a-zA-Z0-9\-]{1,31}:([a-zA-Z0-9\(\)\+\,\.\:\=\@\;\$\_\!\*\-]|%[0-9a-fA-F]{2})*#", $id)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string and valid URI/IRI'); + } + $this->_data['id'] = $id; + } + + /** + * Set a feed image (URI at minimum). Parameter is a single array with the + * required key 'uri'. When rendering as RSS, the required keys are 'uri', + * 'title' and 'link'. RSS also specifies three optional parameters 'width', + * 'height' and 'description'. Only 'uri' is required and used for Atom rendering. + * + * @param array $data + */ + public function setImage(array $data) + { + if (empty($data['uri']) || !is_string($data['uri']) + || !Zend_Uri::check($data['uri'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter \'uri\'' + . ' must be a non-empty string and valid URI/IRI'); + } + $this->_data['image'] = $data; + } + + /** + * Set the feed language + * + * @return string|null + */ + public function setLanguage($language) + { + if (empty($language) || !is_string($language)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['language'] = $language; + } + + /** + * Set a link to the HTML source + * + * @param string $link + */ + public function setLink($link) + { + if (empty($link) || !is_string($link) || !Zend_Uri::check($link)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string and valid URI/IRI'); + } + $this->_data['link'] = $link; + } + + /** + * Set a link to an XML feed for any feed type/version + * + * @return string|null + */ + public function setFeedLink($link, $type) + { + if (empty($link) || !is_string($link) || !Zend_Uri::check($link)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "link"" must be a non-empty string and valid URI/IRI'); + } + if (!in_array(strtolower($type), array('rss', 'rdf', 'atom'))) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "type"; You must declare the type of feed the link points to, i.e. RSS, RDF or Atom'); + } + $this->_data['feedLinks'][strtolower($type)] = $link; + } + + /** + * Set the feed title + * + * @return string|null + */ + public function setTitle($title) + { + if (empty($title) || !is_string($title)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['title'] = $title; + } + + /** + * Set the feed character encoding + * + * @param string $encoding + */ + public function setEncoding($encoding) + { + if (empty($encoding) || !is_string($encoding)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: parameter must be a non-empty string'); + } + $this->_data['encoding'] = $encoding; + } + + /** + * Set the feed's base URL + * + * @param string $url + */ + public function setBaseUrl($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "url" array value' + . ' must be a non-empty string and valid URI/IRI'); + } + $this->_data['baseUrl'] = $url; + } + + /** + * Add a Pubsubhubbub hub endpoint URL + * + * @param string $url + */ + public function addHub($url) + { + if (empty($url) || !is_string($url) || !Zend_Uri::check($url)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: "url" array value' + . ' must be a non-empty string and valid URI/IRI'); + } + if (!isset($this->_data['hubs'])) { + $this->_data['hubs'] = array(); + } + $this->_data['hubs'][] = $url; + } + + /** + * Add Pubsubhubbub hub endpoint URLs + * + * @param array $urls + */ + public function addHubs(array $urls) + { + foreach ($urls as $url) { + $this->addHub($url); + } + } + + /** + * Add a feed category + * + * @param string $category + */ + public function addCategory(array $category) + { + if (!isset($category['term'])) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Each category must be an array and ' + . 'contain at least a "term" element containing the machine ' + . ' readable category name'); + } + if (isset($category['scheme'])) { + if (empty($category['scheme']) + || !is_string($category['scheme']) + || !Zend_Uri::check($category['scheme']) + ) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('The Atom scheme or RSS domain of' + . ' a category must be a valid URI'); + } + } + if (!isset($this->_data['categories'])) { + $this->_data['categories'] = array(); + } + $this->_data['categories'][] = $category; + } + + /** + * Set an array of feed categories + * + * @param array $categories + */ + public function addCategories(array $categories) + { + foreach ($categories as $category) { + $this->addCategory($category); + } + } + + /** + * Get a single author + * + * @param int $index + * @return string|null + */ + public function getAuthor($index = 0) + { + if (isset($this->_data['authors'][$index])) { + return $this->_data['authors'][$index]; + } else { + return null; + } + } + + /** + * Get an array with feed authors + * + * @return array + */ + public function getAuthors() + { + if (!array_key_exists('authors', $this->_data)) { + return null; + } + return $this->_data['authors']; + } + + /** + * Get the copyright entry + * + * @return string|null + */ + public function getCopyright() + { + if (!array_key_exists('copyright', $this->_data)) { + return null; + } + return $this->_data['copyright']; + } + + /** + * Get the feed creation date + * + * @return string|null + */ + public function getDateCreated() + { + if (!array_key_exists('dateCreated', $this->_data)) { + return null; + } + return $this->_data['dateCreated']; + } + + /** + * Get the feed modification date + * + * @return string|null + */ + public function getDateModified() + { + if (!array_key_exists('dateModified', $this->_data)) { + return null; + } + return $this->_data['dateModified']; + } + + /** + * Get the feed last-build date + * + * @return string|null + */ + public function getLastBuildDate() + { + if (!array_key_exists('lastBuildDate', $this->_data)) { + return null; + } + return $this->_data['lastBuildDate']; + } + + /** + * Get the feed description + * + * @return string|null + */ + public function getDescription() + { + if (!array_key_exists('description', $this->_data)) { + return null; + } + return $this->_data['description']; + } + + /** + * Get the feed generator entry + * + * @return string|null + */ + public function getGenerator() + { + if (!array_key_exists('generator', $this->_data)) { + return null; + } + return $this->_data['generator']; + } + + /** + * Get the feed ID + * + * @return string|null + */ + public function getId() + { + if (!array_key_exists('id', $this->_data)) { + return null; + } + return $this->_data['id']; + } + + /** + * Get the feed image URI + * + * @return array + */ + public function getImage() + { + if (!array_key_exists('image', $this->_data)) { + return null; + } + return $this->_data['image']; + } + + /** + * Get the feed language + * + * @return string|null + */ + public function getLanguage() + { + if (!array_key_exists('language', $this->_data)) { + return null; + } + return $this->_data['language']; + } + + /** + * Get a link to the HTML source + * + * @return string|null + */ + public function getLink() + { + if (!array_key_exists('link', $this->_data)) { + return null; + } + return $this->_data['link']; + } + + /** + * Get a link to the XML feed + * + * @return string|null + */ + public function getFeedLinks() + { + if (!array_key_exists('feedLinks', $this->_data)) { + return null; + } + return $this->_data['feedLinks']; + } + + /** + * Get the feed title + * + * @return string|null + */ + public function getTitle() + { + if (!array_key_exists('title', $this->_data)) { + return null; + } + return $this->_data['title']; + } + + /** + * Get the feed character encoding + * + * @return string|null + */ + public function getEncoding() + { + if (!array_key_exists('encoding', $this->_data)) { + return 'UTF-8'; + } + return $this->_data['encoding']; + } + + /** + * Get the feed's base url + * + * @return string|null + */ + public function getBaseUrl() + { + if (!array_key_exists('baseUrl', $this->_data)) { + return null; + } + return $this->_data['baseUrl']; + } + + /** + * Get the URLs used as Pubsubhubbub hubs endpoints + * + * @return string|null + */ + public function getHubs() + { + if (!array_key_exists('hubs', $this->_data)) { + return null; + } + return $this->_data['hubs']; + } + + /** + * Get the feed categories + * + * @return string|null + */ + public function getCategories() + { + if (!array_key_exists('categories', $this->_data)) { + return null; + } + return $this->_data['categories']; + } + + /** + * Resets the instance and deletes all data + * + * @return void + */ + public function reset() + { + $this->_data = array(); + } + + /** + * Set the current feed type being exported to "rss" or "atom". This allows + * other objects to gracefully choose whether to execute or not, depending + * on their appropriateness for the current type, e.g. renderers. + * + * @param string $type + */ + public function setType($type) + { + $this->_type = $type; + } + + /** + * Retrieve the current or last feed type exported. + * + * @return string Value will be "rss" or "atom" + */ + public function getType() + { + return $this->_type; + } + + /** + * Unset a specific data point + * + * @param string $name + */ + public function remove($name) + { + if (isset($this->_data[$name])) { + unset($this->_data[$name]); + } + } + + /** + * Method overloading: call given method on first extension implementing it + * + * @param string $method + * @param array $args + * @return mixed + * @throws Zend_Feed_Exception if no extensions implements the method + */ + public function __call($method, $args) + { + foreach ($this->_extensions as $extension) { + try { + return call_user_func_array(array($extension, $method), $args); + } catch (Zend_Feed_Writer_Exception_InvalidMethodException $e) { + } + } + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Method: ' . $method + . ' does not exist and could not be located on a registered Extension'); + } + + /** + * Load extensions from Zend_Feed_Writer + * + * @return void + */ + protected function _loadExtensions() + { + $all = Zend_Feed_Writer::getExtensions(); + $exts = $all['feed']; + foreach ($exts as $ext) { + $className = Zend_Feed_Writer::getPluginLoader()->getClassName($ext); + $this->_extensions[$ext] = new $className(); + $this->_extensions[$ext]->setEncoding($this->getEncoding()); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Atom.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Atom.php new file mode 100644 index 00000000..76095e89 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Atom.php @@ -0,0 +1,410 @@ +_dom = new DOMDocument('1.0', $this->_container->getEncoding()); + $this->_dom->formatOutput = true; + $entry = $this->_dom->createElementNS(Zend_Feed_Writer::NAMESPACE_ATOM_10, 'entry'); + $this->_dom->appendChild($entry); + + $this->_setSource($this->_dom, $entry); + $this->_setTitle($this->_dom, $entry); + $this->_setDescription($this->_dom, $entry); + $this->_setDateCreated($this->_dom, $entry); + $this->_setDateModified($this->_dom, $entry); + $this->_setLink($this->_dom, $entry); + $this->_setId($this->_dom, $entry); + $this->_setAuthors($this->_dom, $entry); + $this->_setEnclosure($this->_dom, $entry); + $this->_setContent($this->_dom, $entry); + $this->_setCategories($this->_dom, $entry); + + foreach ($this->_extensions as $ext) { + $ext->setType($this->getType()); + $ext->setRootElement($this->getRootElement()); + $ext->setDomDocument($this->getDomDocument(), $entry); + $ext->render(); + } + + return $this; + } + + /** + * Set entry title + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setTitle(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getTitle()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 entry elements MUST contain exactly one' + . ' atom:title element but a title has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $title = $dom->createElement('title'); + $root->appendChild($title); + $title->setAttribute('type', 'html'); + $cdata = $dom->createCDATASection($this->getDataContainer()->getTitle()); + $title->appendChild($cdata); + } + + /** + * Set entry description + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDescription(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDescription()) { + return; // unless src content or base64 + } + $subtitle = $dom->createElement('summary'); + $root->appendChild($subtitle); + $subtitle->setAttribute('type', 'html'); + $cdata = $dom->createCDATASection( + $this->getDataContainer()->getDescription() + ); + $subtitle->appendChild($cdata); + } + + /** + * Set date entry was modified + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateModified(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDateModified()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 entry elements MUST contain exactly one' + . ' atom:updated element but a modification date has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + $updated = $dom->createElement('updated'); + $root->appendChild($updated); + $text = $dom->createTextNode( + $this->getDataContainer()->getDateModified()->get(Zend_Date::ISO_8601) + ); + $updated->appendChild($text); + } + + /** + * Set date entry was created + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateCreated(DOMDocument $dom, DOMElement $root) + { + if (!$this->getDataContainer()->getDateCreated()) { + return; + } + $el = $dom->createElement('published'); + $root->appendChild($el); + $text = $dom->createTextNode( + $this->getDataContainer()->getDateCreated()->get(Zend_Date::ISO_8601) + ); + $el->appendChild($text); + } + + /** + * Set entry authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->_container->getAuthors(); + if ((!$authors || empty($authors))) { + /** + * This will actually trigger an Exception at the feed level if + * a feed level author is not set. + */ + return; + } + foreach ($authors as $data) { + $author = $this->_dom->createElement('author'); + $name = $this->_dom->createElement('name'); + $author->appendChild($name); + $root->appendChild($author); + $text = $dom->createTextNode($data['name']); + $name->appendChild($text); + if (array_key_exists('email', $data)) { + $email = $this->_dom->createElement('email'); + $author->appendChild($email); + $text = $dom->createTextNode($data['email']); + $email->appendChild($text); + } + if (array_key_exists('uri', $data)) { + $uri = $this->_dom->createElement('uri'); + $author->appendChild($uri); + $text = $dom->createTextNode($data['uri']); + $uri->appendChild($text); + } + } + } + + /** + * Set entry enclosure + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setEnclosure(DOMDocument $dom, DOMElement $root) + { + $data = $this->_container->getEnclosure(); + if ((!$data || empty($data))) { + return; + } + $enclosure = $this->_dom->createElement('link'); + $enclosure->setAttribute('rel', 'enclosure'); + if (isset($data['type'])) { + $enclosure->setAttribute('type', $data['type']); + } + if (isset($data['length'])) { + $enclosure->setAttribute('length', $data['length']); + } + $enclosure->setAttribute('href', $data['uri']); + $root->appendChild($enclosure); + } + + protected function _setLink(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getLink()) { + return; + } + $link = $dom->createElement('link'); + $root->appendChild($link); + $link->setAttribute('rel', 'alternate'); + $link->setAttribute('type', 'text/html'); + $link->setAttribute('href', $this->getDataContainer()->getLink()); + } + + /** + * Set entry identifier + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setId(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getId() + && !$this->getDataContainer()->getLink()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 entry elements MUST contain exactly one ' + . 'atom:id element, or as an alternative, we can use the same ' + . 'value as atom:link however neither a suitable link nor an ' + . 'id have been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + if (!$this->getDataContainer()->getId()) { + $this->getDataContainer()->setId( + $this->getDataContainer()->getLink()); + } + if (!Zend_Uri::check($this->getDataContainer()->getId()) && + !preg_match("#^urn:[a-zA-Z0-9][a-zA-Z0-9\-]{1,31}:([a-zA-Z0-9\(\)\+\,\.\:\=\@\;\$\_\!\*\-]|%[0-9a-fA-F]{2})*#", $this->getDataContainer()->getId())) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Atom 1.0 IDs must be a valid URI/IRI'); + } + $id = $dom->createElement('id'); + $root->appendChild($id); + $text = $dom->createTextNode($this->getDataContainer()->getId()); + $id->appendChild($text); + } + + /** + * Set entry content + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setContent(DOMDocument $dom, DOMElement $root) + { + $content = $this->getDataContainer()->getContent(); + if (!$content && !$this->getDataContainer()->getLink()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 entry elements MUST contain exactly one ' + . 'atom:content element, or as an alternative, at least one link ' + . 'with a rel attribute of "alternate" to indicate an alternate ' + . 'method to consume the content.'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + if (!$content) { + return; + } + $element = $dom->createElement('content'); + $element->setAttribute('type', 'xhtml'); + $xhtmlElement = $this->_loadXhtml($content); + $xhtml = $dom->importNode($xhtmlElement, true); + $element->appendChild($xhtml); + $root->appendChild($element); + } + + /** + * Load a HTML string and attempt to normalise to XML + */ + protected function _loadXhtml($content) + { + $xhtml = ''; + if (class_exists('tidy', false)) { + $tidy = new tidy; + $config = array( + 'output-xhtml' => true, + 'show-body-only' => true, + 'quote-nbsp' => false + ); + $encoding = str_replace('-', '', $this->getEncoding()); + $tidy->parseString($content, $config, $encoding); + $tidy->cleanRepair(); + $xhtml = (string) $tidy; + } else { + $xhtml = $content; + } + $xhtml = preg_replace(array( + "/(<[\/]?)([a-zA-Z]+)/" + ), '$1xhtml:$2', $xhtml); + $dom = new DOMDocument('1.0', $this->getEncoding()); + $dom->loadXML('' + . $xhtml . ''); + return $dom->documentElement; + } + + /** + * Set entry cateories + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCategories(DOMDocument $dom, DOMElement $root) + { + $categories = $this->getDataContainer()->getCategories(); + if (!$categories) { + return; + } + foreach ($categories as $cat) { + $category = $dom->createElement('category'); + $category->setAttribute('term', $cat['term']); + if (isset($cat['label'])) { + $category->setAttribute('label', $cat['label']); + } else { + $category->setAttribute('label', $cat['term']); + } + if (isset($cat['scheme'])) { + $category->setAttribute('scheme', $cat['scheme']); + } + $root->appendChild($category); + } + } + + /** + * Append Source element (Atom 1.0 Feed Metadata) + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setSource(DOMDocument $dom, DOMElement $root) + { + $source = $this->getDataContainer()->getSource(); + if (!$source) { + return; + } + $renderer = new Zend_Feed_Writer_Renderer_Feed_Atom_Source($source); + $renderer->setType($this->getType()); + $element = $renderer->render()->getElement(); + $imported = $dom->importNode($element, true); + $root->appendChild($imported); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Atom/Deleted.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Atom/Deleted.php new file mode 100644 index 00000000..fb752629 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Atom/Deleted.php @@ -0,0 +1,121 @@ +_dom = new DOMDocument('1.0', $this->_container->getEncoding()); + $this->_dom->formatOutput = true; + $entry = $this->_dom->createElement('at:deleted-entry'); + $this->_dom->appendChild($entry); + + $entry->setAttribute('ref', $this->_container->getReference()); + $entry->setAttribute('when', $this->_container->getWhen()->get(Zend_Date::ISO_8601)); + + $this->_setBy($this->_dom, $entry); + $this->_setComment($this->_dom, $entry); + + return $this; + } + + /** + * Set tombstone comment + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setComment(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getComment()) { + return; + } + $c = $dom->createElement('at:comment'); + $root->appendChild($c); + $c->setAttribute('type', 'html'); + $cdata = $dom->createCDATASection($this->getDataContainer()->getComment()); + $c->appendChild($cdata); + } + + /** + * Set entry authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setBy(DOMDocument $dom, DOMElement $root) + { + $data = $this->_container->getBy(); + if ((!$data || empty($data))) { + return; + } + $author = $this->_dom->createElement('at:by'); + $name = $this->_dom->createElement('name'); + $author->appendChild($name); + $root->appendChild($author); + $text = $dom->createTextNode($data['name']); + $name->appendChild($text); + if (array_key_exists('email', $data)) { + $email = $this->_dom->createElement('email'); + $author->appendChild($email); + $text = $dom->createTextNode($data['email']); + $email->appendChild($text); + } + if (array_key_exists('uri', $data)) { + $uri = $this->_dom->createElement('uri'); + $author->appendChild($uri); + $text = $dom->createTextNode($data['uri']); + $uri->appendChild($text); + } + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Rss.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Rss.php new file mode 100644 index 00000000..74e80f57 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Entry/Rss.php @@ -0,0 +1,346 @@ +_dom = new DOMDocument('1.0', $this->_container->getEncoding()); + $this->_dom->formatOutput = true; + $this->_dom->substituteEntities = false; + $entry = $this->_dom->createElement('item'); + $this->_dom->appendChild($entry); + + $this->_setTitle($this->_dom, $entry); + $this->_setDescription($this->_dom, $entry); + $this->_setDateCreated($this->_dom, $entry); + $this->_setDateModified($this->_dom, $entry); + $this->_setLink($this->_dom, $entry); + $this->_setId($this->_dom, $entry); + $this->_setAuthors($this->_dom, $entry); + $this->_setEnclosure($this->_dom, $entry); + $this->_setCommentLink($this->_dom, $entry); + $this->_setCategories($this->_dom, $entry); + foreach ($this->_extensions as $ext) { + $ext->setType($this->getType()); + $ext->setRootElement($this->getRootElement()); + $ext->setDomDocument($this->getDomDocument(), $entry); + $ext->render(); + } + + return $this; + } + + /** + * Set entry title + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setTitle(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDescription() + && !$this->getDataContainer()->getTitle()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'RSS 2.0 entry elements SHOULD contain exactly one' + . ' title element but a title has not been set. In addition, there' + . ' is no description as required in the absence of a title.'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $title = $dom->createElement('title'); + $root->appendChild($title); + $text = $dom->createTextNode($this->getDataContainer()->getTitle()); + $title->appendChild($text); + } + + /** + * Set entry description + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDescription(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDescription() + && !$this->getDataContainer()->getTitle()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'RSS 2.0 entry elements SHOULD contain exactly one' + . ' description element but a description has not been set. In' + . ' addition, there is no title element as required in the absence' + . ' of a description.'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + if (!$this->getDataContainer()->getDescription()) { + return; + } + $subtitle = $dom->createElement('description'); + $root->appendChild($subtitle); + $text = $dom->createCDATASection($this->getDataContainer()->getDescription()); + $subtitle->appendChild($text); + } + + /** + * Set date entry was last modified + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateModified(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDateModified()) { + return; + } + + $updated = $dom->createElement('pubDate'); + $root->appendChild($updated); + $text = $dom->createTextNode( + $this->getDataContainer()->getDateModified()->get(Zend_Date::RSS) + ); + $updated->appendChild($text); + } + + /** + * Set date entry was created + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateCreated(DOMDocument $dom, DOMElement $root) + { + if (!$this->getDataContainer()->getDateCreated()) { + return; + } + if (!$this->getDataContainer()->getDateModified()) { + $this->getDataContainer()->setDateModified( + $this->getDataContainer()->getDateCreated() + ); + } + } + + /** + * Set entry authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->_container->getAuthors(); + if ((!$authors || empty($authors))) { + return; + } + foreach ($authors as $data) { + $author = $this->_dom->createElement('author'); + $name = $data['name']; + if (array_key_exists('email', $data)) { + $name = $data['email'] . ' (' . $data['name'] . ')'; + } + $text = $dom->createTextNode($name); + $author->appendChild($text); + $root->appendChild($author); + } + } + + /** + * Set entry enclosure + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setEnclosure(DOMDocument $dom, DOMElement $root) + { + $data = $this->_container->getEnclosure(); + if ((!$data || empty($data))) { + return; + } + if (!isset($data['type'])) { + // require_once 'Zend/Feed/Exception.php'; + $exception = new Zend_Feed_Exception('Enclosure "type" is not set'); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + if (!isset($data['length'])) { + // require_once 'Zend/Feed/Exception.php'; + $exception = new Zend_Feed_Exception('Enclosure "length" is not set'); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + if (isset($data['length']) && (int) $data['length'] <= 0) { + // require_once 'Zend/Feed/Exception.php'; + $exception = new Zend_Feed_Exception('Enclosure "length" must be an integer' + . ' indicating the content\'s length in bytes'); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $enclosure = $this->_dom->createElement('enclosure'); + $enclosure->setAttribute('type', $data['type']); + $enclosure->setAttribute('length', $data['length']); + $enclosure->setAttribute('url', $data['uri']); + $root->appendChild($enclosure); + } + + /** + * Set link to entry + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setLink(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getLink()) { + return; + } + $link = $dom->createElement('link'); + $root->appendChild($link); + $text = $dom->createTextNode($this->getDataContainer()->getLink()); + $link->appendChild($text); + } + + /** + * Set entry identifier + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setId(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getId() + && !$this->getDataContainer()->getLink()) { + return; + } + + $id = $dom->createElement('guid'); + $root->appendChild($id); + if (!$this->getDataContainer()->getId()) { + $this->getDataContainer()->setId( + $this->getDataContainer()->getLink()); + } + $text = $dom->createTextNode($this->getDataContainer()->getId()); + $id->appendChild($text); + if (!Zend_Uri::check($this->getDataContainer()->getId())) { + $id->setAttribute('isPermaLink', 'false'); + } + } + + /** + * Set link to entry comments + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCommentLink(DOMDocument $dom, DOMElement $root) + { + $link = $this->getDataContainer()->getCommentLink(); + if (!$link) { + return; + } + $clink = $this->_dom->createElement('comments'); + $text = $dom->createTextNode($link); + $clink->appendChild($text); + $root->appendChild($clink); + } + + /** + * Set entry categories + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCategories(DOMDocument $dom, DOMElement $root) + { + $categories = $this->getDataContainer()->getCategories(); + if (!$categories) { + return; + } + foreach ($categories as $cat) { + $category = $dom->createElement('category'); + if (isset($cat['scheme'])) { + $category->setAttribute('domain', $cat['scheme']); + } + $text = $dom->createCDATASection($cat['term']); + $category->appendChild($text); + $root->appendChild($category); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom.php new file mode 100644 index 00000000..2c88dd42 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom.php @@ -0,0 +1,130 @@ +_container->getEncoding()) { + $this->_container->setEncoding('UTF-8'); + } + $this->_dom = new DOMDocument('1.0', $this->_container->getEncoding()); + $this->_dom->formatOutput = true; + $root = $this->_dom->createElementNS( + Zend_Feed_Writer::NAMESPACE_ATOM_10, 'feed' + ); + $this->setRootElement($root); + $this->_dom->appendChild($root); + $this->_setLanguage($this->_dom, $root); + $this->_setBaseUrl($this->_dom, $root); + $this->_setTitle($this->_dom, $root); + $this->_setDescription($this->_dom, $root); + $this->_setImage($this->_dom, $root); + $this->_setDateCreated($this->_dom, $root); + $this->_setDateModified($this->_dom, $root); + $this->_setGenerator($this->_dom, $root); + $this->_setLink($this->_dom, $root); + $this->_setFeedLinks($this->_dom, $root); + $this->_setId($this->_dom, $root); + $this->_setAuthors($this->_dom, $root); + $this->_setCopyright($this->_dom, $root); + $this->_setCategories($this->_dom, $root); + $this->_setHubs($this->_dom, $root); + + foreach ($this->_extensions as $ext) { + $ext->setType($this->getType()); + $ext->setRootElement($this->getRootElement()); + $ext->setDomDocument($this->getDomDocument(), $root); + $ext->render(); + } + + foreach ($this->_container as $entry) { + if ($this->getDataContainer()->getEncoding()) { + $entry->setEncoding($this->getDataContainer()->getEncoding()); + } + if ($entry instanceof Zend_Feed_Writer_Entry) { + $renderer = new Zend_Feed_Writer_Renderer_Entry_Atom($entry); + } else { + if (!$this->_dom->documentElement->hasAttribute('xmlns:at')) { + $this->_dom->documentElement->setAttribute( + 'xmlns:at', 'http://purl.org/atompub/tombstones/1.0' + ); + } + $renderer = new Zend_Feed_Writer_Renderer_Entry_Atom_Deleted($entry); + } + if ($this->_ignoreExceptions === true) { + $renderer->ignoreExceptions(); + } + $renderer->setType($this->getType()); + $renderer->setRootElement($this->_dom->documentElement); + $renderer->render(); + $element = $renderer->getElement(); + $imported = $this->_dom->importNode($element, true); + $root->appendChild($imported); + } + return $this; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom/AtomAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom/AtomAbstract.php new file mode 100644 index 00000000..254516e5 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom/AtomAbstract.php @@ -0,0 +1,427 @@ +getDataContainer()->getLanguage()) { + $root->setAttribute('xml:lang', $this->getDataContainer() + ->getLanguage()); + } + } + + /** + * Set feed title + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setTitle(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getTitle()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 feed elements MUST contain exactly one' + . ' atom:title element but a title has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + $title = $dom->createElement('title'); + $root->appendChild($title); + $title->setAttribute('type', 'text'); + $text = $dom->createTextNode($this->getDataContainer()->getTitle()); + $title->appendChild($text); + } + + /** + * Set feed description + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDescription(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDescription()) { + return; + } + $subtitle = $dom->createElement('subtitle'); + $root->appendChild($subtitle); + $subtitle->setAttribute('type', 'text'); + $text = $dom->createTextNode($this->getDataContainer()->getDescription()); + $subtitle->appendChild($text); + } + + /** + * Set date feed was last modified + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateModified(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDateModified()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 feed elements MUST contain exactly one' + . ' atom:updated element but a modification date has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + $updated = $dom->createElement('updated'); + $root->appendChild($updated); + $text = $dom->createTextNode( + $this->getDataContainer()->getDateModified()->get(Zend_Date::ISO_8601) + ); + $updated->appendChild($text); + } + + /** + * Set feed generator string + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setGenerator(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getGenerator()) { + $this->getDataContainer()->setGenerator('Zend_Feed_Writer', + Zend_Version::VERSION, 'http://framework.zend.com'); + } + + $gdata = $this->getDataContainer()->getGenerator(); + $generator = $dom->createElement('generator'); + $root->appendChild($generator); + $text = $dom->createTextNode($gdata['name']); + $generator->appendChild($text); + if (array_key_exists('uri', $gdata)) { + $generator->setAttribute('uri', $gdata['uri']); + } + if (array_key_exists('version', $gdata)) { + $generator->setAttribute('version', $gdata['version']); + } + } + + /** + * Set link to feed + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setLink(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getLink()) { + return; + } + $link = $dom->createElement('link'); + $root->appendChild($link); + $link->setAttribute('rel', 'alternate'); + $link->setAttribute('type', 'text/html'); + $link->setAttribute('href', $this->getDataContainer()->getLink()); + } + + /** + * Set feed links + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setFeedLinks(DOMDocument $dom, DOMElement $root) + { + $flinks = $this->getDataContainer()->getFeedLinks(); + if(!$flinks || !array_key_exists('atom', $flinks)) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 feed elements SHOULD contain one atom:link ' + . 'element with a rel attribute value of "self". This is the ' + . 'preferred URI for retrieving Atom Feed Documents representing ' + . 'this Atom feed but a feed link has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + foreach ($flinks as $type => $href) { + $mime = 'application/' . strtolower($type) . '+xml'; + $flink = $dom->createElement('link'); + $root->appendChild($flink); + $flink->setAttribute('rel', 'self'); + $flink->setAttribute('type', $mime); + $flink->setAttribute('href', $href); + } + } + + /** + * Set feed authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->_container->getAuthors(); + if (!$authors || empty($authors)) { + /** + * Technically we should defer an exception until we can check + * that all entries contain an author. If any entry is missing + * an author, then a missing feed author element is invalid + */ + return; + } + foreach ($authors as $data) { + $author = $this->_dom->createElement('author'); + $name = $this->_dom->createElement('name'); + $author->appendChild($name); + $root->appendChild($author); + $text = $dom->createTextNode($data['name']); + $name->appendChild($text); + if (array_key_exists('email', $data)) { + $email = $this->_dom->createElement('email'); + $author->appendChild($email); + $text = $dom->createTextNode($data['email']); + $email->appendChild($text); + } + if (array_key_exists('uri', $data)) { + $uri = $this->_dom->createElement('uri'); + $author->appendChild($uri); + $text = $dom->createTextNode($data['uri']); + $uri->appendChild($text); + } + } + } + + /** + * Set feed identifier + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setId(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getId() + && !$this->getDataContainer()->getLink()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Atom 1.0 feed elements MUST contain exactly one ' + . 'atom:id element, or as an alternative, we can use the same ' + . 'value as atom:link however neither a suitable link nor an ' + . 'id have been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + if (!$this->getDataContainer()->getId()) { + $this->getDataContainer()->setId( + $this->getDataContainer()->getLink()); + } + $id = $dom->createElement('id'); + $root->appendChild($id); + $text = $dom->createTextNode($this->getDataContainer()->getId()); + $id->appendChild($text); + } + + /** + * Set feed copyright + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCopyright(DOMDocument $dom, DOMElement $root) + { + $copyright = $this->getDataContainer()->getCopyright(); + if (!$copyright) { + return; + } + $copy = $dom->createElement('rights'); + $root->appendChild($copy); + $text = $dom->createTextNode($copyright); + $copy->appendChild($text); + } + + /** + * Set feed level logo (image) + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setImage(DOMDocument $dom, DOMElement $root) + { + $image = $this->getDataContainer()->getImage(); + if (!$image) { + return; + } + $img = $dom->createElement('logo'); + $root->appendChild($img); + $text = $dom->createTextNode($image['uri']); + $img->appendChild($text); + } + + /** + * Set date feed was created + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateCreated(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDateCreated()) { + return; + } + if(!$this->getDataContainer()->getDateModified()) { + $this->getDataContainer()->setDateModified( + $this->getDataContainer()->getDateCreated() + ); + } + } + + /** + * Set base URL to feed links + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setBaseUrl(DOMDocument $dom, DOMElement $root) + { + $baseUrl = $this->getDataContainer()->getBaseUrl(); + if (!$baseUrl) { + return; + } + $root->setAttribute('xml:base', $baseUrl); + } + + /** + * Set hubs to which this feed pushes + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setHubs(DOMDocument $dom, DOMElement $root) + { + $hubs = $this->getDataContainer()->getHubs(); + if (!$hubs) { + return; + } + foreach ($hubs as $hubUrl) { + $hub = $dom->createElement('link'); + $hub->setAttribute('rel', 'hub'); + $hub->setAttribute('href', $hubUrl); + $root->appendChild($hub); + } + } + + /** + * Set feed cateories + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCategories(DOMDocument $dom, DOMElement $root) + { + $categories = $this->getDataContainer()->getCategories(); + if (!$categories) { + return; + } + foreach ($categories as $cat) { + $category = $dom->createElement('category'); + $category->setAttribute('term', $cat['term']); + if (isset($cat['label'])) { + $category->setAttribute('label', $cat['label']); + } else { + $category->setAttribute('label', $cat['term']); + } + if (isset($cat['scheme'])) { + $category->setAttribute('scheme', $cat['scheme']); + } + $root->appendChild($category); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom/Source.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom/Source.php new file mode 100644 index 00000000..125fed1e --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Atom/Source.php @@ -0,0 +1,110 @@ +_container->getEncoding()) { + $this->_container->setEncoding('UTF-8'); + } + $this->_dom = new DOMDocument('1.0', $this->_container->getEncoding()); + $this->_dom->formatOutput = true; + $root = $this->_dom->createElement('source'); + $this->setRootElement($root); + $this->_dom->appendChild($root); + $this->_setLanguage($this->_dom, $root); + $this->_setBaseUrl($this->_dom, $root); + $this->_setTitle($this->_dom, $root); + $this->_setDescription($this->_dom, $root); + $this->_setDateCreated($this->_dom, $root); + $this->_setDateModified($this->_dom, $root); + $this->_setGenerator($this->_dom, $root); + $this->_setLink($this->_dom, $root); + $this->_setFeedLinks($this->_dom, $root); + $this->_setId($this->_dom, $root); + $this->_setAuthors($this->_dom, $root); + $this->_setCopyright($this->_dom, $root); + $this->_setCategories($this->_dom, $root); + + foreach ($this->_extensions as $ext) { + $ext->setType($this->getType()); + $ext->setRootElement($this->getRootElement()); + $ext->setDomDocument($this->getDomDocument(), $root); + $ext->render(); + } + return $this; + } + + /** + * Set feed generator string + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setGenerator(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getGenerator()) { + return; + } + + $gdata = $this->getDataContainer()->getGenerator(); + $generator = $dom->createElement('generator'); + $root->appendChild($generator); + $text = $dom->createTextNode($gdata['name']); + $generator->appendChild($text); + if (array_key_exists('uri', $gdata)) { + $generator->setAttribute('uri', $gdata['uri']); + } + if (array_key_exists('version', $gdata)) { + $generator->setAttribute('version', $gdata['version']); + } + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Rss.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Rss.php new file mode 100644 index 00000000..07b94e7b --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/Feed/Rss.php @@ -0,0 +1,505 @@ +_container->getEncoding()) { + $this->_container->setEncoding('UTF-8'); + } + $this->_dom = new DOMDocument('1.0', $this->_container->getEncoding()); + $this->_dom->formatOutput = true; + $this->_dom->substituteEntities = false; + $rss = $this->_dom->createElement('rss'); + $this->setRootElement($rss); + $rss->setAttribute('version', '2.0'); + + $channel = $this->_dom->createElement('channel'); + $rss->appendChild($channel); + $this->_dom->appendChild($rss); + $this->_setLanguage($this->_dom, $channel); + $this->_setBaseUrl($this->_dom, $channel); + $this->_setTitle($this->_dom, $channel); + $this->_setDescription($this->_dom, $channel); + $this->_setImage($this->_dom, $channel); + $this->_setDateCreated($this->_dom, $channel); + $this->_setDateModified($this->_dom, $channel); + $this->_setLastBuildDate($this->_dom, $channel); + $this->_setGenerator($this->_dom, $channel); + $this->_setLink($this->_dom, $channel); + $this->_setAuthors($this->_dom, $channel); + $this->_setCopyright($this->_dom, $channel); + $this->_setCategories($this->_dom, $channel); + + foreach ($this->_extensions as $ext) { + $ext->setType($this->getType()); + $ext->setRootElement($this->getRootElement()); + $ext->setDomDocument($this->getDomDocument(), $channel); + $ext->render(); + } + + foreach ($this->_container as $entry) { + if ($this->getDataContainer()->getEncoding()) { + $entry->setEncoding($this->getDataContainer()->getEncoding()); + } + if ($entry instanceof Zend_Feed_Writer_Entry) { + $renderer = new Zend_Feed_Writer_Renderer_Entry_Rss($entry); + } else { + continue; + } + if ($this->_ignoreExceptions === true) { + $renderer->ignoreExceptions(); + } + $renderer->setType($this->getType()); + $renderer->setRootElement($this->_dom->documentElement); + $renderer->render(); + $element = $renderer->getElement(); + $imported = $this->_dom->importNode($element, true); + $channel->appendChild($imported); + } + return $this; + } + + /** + * Set feed language + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setLanguage(DOMDocument $dom, DOMElement $root) + { + $lang = $this->getDataContainer()->getLanguage(); + if (!$lang) { + return; + } + $language = $dom->createElement('language'); + $root->appendChild($language); + $language->nodeValue = $lang; + } + + /** + * Set feed title + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setTitle(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getTitle()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'RSS 2.0 feed elements MUST contain exactly one' + . ' title element but a title has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + + $title = $dom->createElement('title'); + $root->appendChild($title); + $text = $dom->createTextNode($this->getDataContainer()->getTitle()); + $title->appendChild($text); + } + + /** + * Set feed description + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDescription(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDescription()) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'RSS 2.0 feed elements MUST contain exactly one' + . ' description element but one has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $subtitle = $dom->createElement('description'); + $root->appendChild($subtitle); + $text = $dom->createTextNode($this->getDataContainer()->getDescription()); + $subtitle->appendChild($text); + } + + /** + * Set date feed was last modified + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateModified(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDateModified()) { + return; + } + + $updated = $dom->createElement('pubDate'); + $root->appendChild($updated); + $text = $dom->createTextNode( + $this->getDataContainer()->getDateModified()->get(Zend_Date::RSS) + ); + $updated->appendChild($text); + } + + /** + * Set feed generator string + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setGenerator(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getGenerator()) { + $this->getDataContainer()->setGenerator('Zend_Feed_Writer', + Zend_Version::VERSION, 'http://framework.zend.com'); + } + + $gdata = $this->getDataContainer()->getGenerator(); + $generator = $dom->createElement('generator'); + $root->appendChild($generator); + $name = $gdata['name']; + if (array_key_exists('version', $gdata)) { + $name .= ' ' . $gdata['version']; + } + if (array_key_exists('uri', $gdata)) { + $name .= ' (' . $gdata['uri'] . ')'; + } + $text = $dom->createTextNode($name); + $generator->appendChild($text); + } + + /** + * Set link to feed + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setLink(DOMDocument $dom, DOMElement $root) + { + $value = $this->getDataContainer()->getLink(); + if(!$value) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'RSS 2.0 feed elements MUST contain exactly one' + . ' link element but one has not been set'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $link = $dom->createElement('link'); + $root->appendChild($link); + $text = $dom->createTextNode($value); + $link->appendChild($text); + if (!Zend_Uri::check($value)) { + $link->setAttribute('isPermaLink', 'false'); + } + } + + /** + * Set feed authors + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setAuthors(DOMDocument $dom, DOMElement $root) + { + $authors = $this->getDataContainer()->getAuthors(); + if (!$authors || empty($authors)) { + return; + } + foreach ($authors as $data) { + $author = $this->_dom->createElement('author'); + $name = $data['name']; + if (array_key_exists('email', $data)) { + $name = $data['email'] . ' (' . $data['name'] . ')'; + } + $text = $dom->createTextNode($name); + $author->appendChild($text); + $root->appendChild($author); + } + } + + /** + * Set feed copyright + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCopyright(DOMDocument $dom, DOMElement $root) + { + $copyright = $this->getDataContainer()->getCopyright(); + if (!$copyright) { + return; + } + $copy = $dom->createElement('copyright'); + $root->appendChild($copy); + $text = $dom->createTextNode($copyright); + $copy->appendChild($text); + } + + /** + * Set feed channel image + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setImage(DOMDocument $dom, DOMElement $root) + { + $image = $this->getDataContainer()->getImage(); + if (!$image) { + return; + } + if (!isset($image['title']) || empty($image['title']) + || !is_string($image['title'])) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'RSS 2.0 feed images must include a title'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + if (empty($image['link']) || !is_string($image['link']) + || !Zend_Uri::check($image['link'])) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Invalid parameter: parameter \'link\'' + . ' must be a non-empty string and valid URI/IRI'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $img = $dom->createElement('image'); + $root->appendChild($img); + $url = $dom->createElement('url'); + $text = $dom->createTextNode($image['uri']); + $url->appendChild($text); + $title = $dom->createElement('title'); + $text = $dom->createTextNode($image['title']); + $title->appendChild($text); + $link = $dom->createElement('link'); + $text = $dom->createTextNode($image['link']); + $link->appendChild($text); + $img->appendChild($url); + $img->appendChild($title); + $img->appendChild($link); + if (isset($image['height'])) { + if (!ctype_digit((string) $image['height']) || $image['height'] > 400) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Invalid parameter: parameter \'height\'' + . ' must be an integer not exceeding 400'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $height = $dom->createElement('height'); + $text = $dom->createTextNode($image['height']); + $height->appendChild($text); + $img->appendChild($height); + } + if (isset($image['width'])) { + if (!ctype_digit((string) $image['width']) || $image['width'] > 144) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Invalid parameter: parameter \'width\'' + . ' must be an integer not exceeding 144'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $width = $dom->createElement('width'); + $text = $dom->createTextNode($image['width']); + $width->appendChild($text); + $img->appendChild($width); + } + if (isset($image['description'])) { + if (empty($image['description']) || !is_string($image['description'])) { + // require_once 'Zend/Feed/Exception.php'; + $message = 'Invalid parameter: parameter \'description\'' + . ' must be a non-empty string'; + $exception = new Zend_Feed_Exception($message); + if (!$this->_ignoreExceptions) { + throw $exception; + } else { + $this->_exceptions[] = $exception; + return; + } + } + $desc = $dom->createElement('description'); + $text = $dom->createTextNode($image['description']); + $desc->appendChild($text); + $img->appendChild($desc); + } + } + + /** + * Set date feed was created + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setDateCreated(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getDateCreated()) { + return; + } + if(!$this->getDataContainer()->getDateModified()) { + $this->getDataContainer()->setDateModified( + $this->getDataContainer()->getDateCreated() + ); + } + } + + /** + * Set date feed last build date + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setLastBuildDate(DOMDocument $dom, DOMElement $root) + { + if(!$this->getDataContainer()->getLastBuildDate()) { + return; + } + + $lastBuildDate = $dom->createElement('lastBuildDate'); + $root->appendChild($lastBuildDate); + $text = $dom->createTextNode( + $this->getDataContainer()->getLastBuildDate()->get(Zend_Date::RSS) + ); + $lastBuildDate->appendChild($text); + } + + /** + * Set base URL to feed links + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setBaseUrl(DOMDocument $dom, DOMElement $root) + { + $baseUrl = $this->getDataContainer()->getBaseUrl(); + if (!$baseUrl) { + return; + } + $root->setAttribute('xml:base', $baseUrl); + } + + /** + * Set feed categories + * + * @param DOMDocument $dom + * @param DOMElement $root + * @return void + */ + protected function _setCategories(DOMDocument $dom, DOMElement $root) + { + $categories = $this->getDataContainer()->getCategories(); + if (!$categories) { + return; + } + foreach ($categories as $cat) { + $category = $dom->createElement('category'); + if (isset($cat['scheme'])) { + $category->setAttribute('domain', $cat['scheme']); + } + $text = $dom->createTextNode($cat['term']); + $category->appendChild($text); + $root->appendChild($category); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/RendererAbstract.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/RendererAbstract.php new file mode 100644 index 00000000..b3457ed6 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/RendererAbstract.php @@ -0,0 +1,250 @@ +_container = $container; + $this->setType($container->getType()); + $this->_loadExtensions(); + } + + /** + * Save XML to string + * + * @return string + */ + public function saveXml() + { + return $this->getDomDocument()->saveXml(); + } + + /** + * Get DOM document + * + * @return DOMDocument + */ + public function getDomDocument() + { + return $this->_dom; + } + + /** + * Get document element from DOM + * + * @return DOMElement + */ + public function getElement() + { + return $this->getDomDocument()->documentElement; + } + + /** + * Get data container of items being rendered + * + * @return mixed + */ + public function getDataContainer() + { + return $this->_container; + } + + /** + * Set feed encoding + * + * @param string $enc + * @return Zend_Feed_Writer_Renderer_RendererAbstract + */ + public function setEncoding($enc) + { + $this->_encoding = $enc; + return $this; + } + + /** + * Get feed encoding + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Indicate whether or not to ignore exceptions + * + * @param bool $bool + * @return Zend_Feed_Writer_Renderer_RendererAbstract + */ + public function ignoreExceptions($bool = true) + { + if (!is_bool($bool)) { + // require_once 'Zend/Feed/Exception.php'; + throw new Zend_Feed_Exception('Invalid parameter: $bool. Should be TRUE or FALSE (defaults to TRUE if null)'); + } + $this->_ignoreExceptions = $bool; + return $this; + } + + /** + * Get exception list + * + * @return array + */ + public function getExceptions() + { + return $this->_exceptions; + } + + /** + * Set the current feed type being exported to "rss" or "atom". This allows + * other objects to gracefully choose whether to execute or not, depending + * on their appropriateness for the current type, e.g. renderers. + * + * @param string $type + */ + public function setType($type) + { + $this->_type = $type; + } + + /** + * Retrieve the current or last feed type exported. + * + * @return string Value will be "rss" or "atom" + */ + public function getType() + { + return $this->_type; + } + + /** + * Sets the absolute root element for the XML feed being generated. This + * helps simplify the appending of namespace declarations, but also ensures + * namespaces are added to the root element - not scattered across the entire + * XML file - may assist namespace unsafe parsers and looks pretty ;). + * + * @param DOMElement $root + */ + public function setRootElement(DOMElement $root) + { + $this->_rootElement = $root; + } + + /** + * Retrieve the absolute root element for the XML feed being generated. + * + * @return DOMElement + */ + public function getRootElement() + { + return $this->_rootElement; + } + + /** + * Load extensions from Zend_Feed_Writer + * + * @return void + */ + protected function _loadExtensions() + { + Zend_Feed_Writer::registerCoreExtensions(); + $all = Zend_Feed_Writer::getExtensions(); + if (stripos(get_class($this), 'entry')) { + $exts = $all['entryRenderer']; + } else { + $exts = $all['feedRenderer']; + } + foreach ($exts as $extension) { + $className = Zend_Feed_Writer::getPluginLoader()->getClassName($extension); + $this->_extensions[$extension] = new $className( + $this->getDataContainer() + ); + $this->_extensions[$extension]->setEncoding($this->getEncoding()); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/RendererInterface.php b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/RendererInterface.php new file mode 100644 index 00000000..89b42944 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Feed/Writer/Renderer/RendererInterface.php @@ -0,0 +1,111 @@ + 5, + 'strictredirects' => false, + 'useragent' => 'Zend_Http_Client', + 'timeout' => 10, + 'adapter' => 'Zend_Http_Client_Adapter_Socket', + 'httpversion' => self::HTTP_1, + 'keepalive' => false, + 'storeresponse' => true, + 'strict' => true, + 'output_stream' => false, + 'encodecookies' => true, + ); + + /** + * The adapter used to preform the actual connection to the server + * + * @var Zend_Http_Client_Adapter_Interface + */ + protected $adapter = null; + + /** + * Request URI + * + * @var Zend_Uri_Http + */ + protected $uri = null; + + /** + * Associative array of request headers + * + * @var array + */ + protected $headers = array(); + + /** + * HTTP request method + * + * @var string + */ + protected $method = self::GET; + + /** + * Associative array of GET parameters + * + * @var array + */ + protected $paramsGet = array(); + + /** + * Assiciative array of POST parameters + * + * @var array + */ + protected $paramsPost = array(); + + /** + * Request body content type (for POST requests) + * + * @var string + */ + protected $enctype = null; + + /** + * The raw post data to send. Could be set by setRawData($data, $enctype). + * + * @var string + */ + protected $raw_post_data = null; + + /** + * HTTP Authentication settings + * + * Expected to be an associative array with this structure: + * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic') + * Where 'type' should be one of the supported authentication types (see the AUTH_* + * constants), for example 'basic' or 'digest'. + * + * If null, no authentication will be used. + * + * @var array|null + */ + protected $auth; + + /** + * File upload arrays (used in POST requests) + * + * An associative array, where each element is of the format: + * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents') + * + * @var array + */ + protected $files = array(); + + /** + * The client's cookie jar + * + * @var Zend_Http_CookieJar + */ + protected $cookiejar = null; + + /** + * The last HTTP request sent by the client, as string + * + * @var string + */ + protected $last_request = null; + + /** + * The last HTTP response received by the client + * + * @var Zend_Http_Response + */ + protected $last_response = null; + + /** + * Redirection counter + * + * @var int + */ + protected $redirectCounter = 0; + + /** + * Fileinfo magic database resource + * + * This varaiable is populated the first time _detectFileMimeType is called + * and is then reused on every call to this method + * + * @var resource + */ + static protected $_fileInfoDb = null; + + /** + * Contructor method. Will create a new HTTP client. Accepts the target + * URL and optionally configuration array. + * + * @param Zend_Uri_Http|string $uri + * @param array $config Configuration key-value pairs. + */ + public function __construct($uri = null, $config = null) + { + if ($uri !== null) { + $this->setUri($uri); + } + if ($config !== null) { + $this->setConfig($config); + } + } + + /** + * Set the URI for the next request + * + * @param Zend_Uri_Http|string $uri + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setUri($uri) + { + if (is_string($uri)) { + $uri = Zend_Uri::factory($uri); + } + + if (!$uri instanceof Zend_Uri_Http) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Passed parameter is not a valid HTTP URI.'); + } + + // Set auth if username and password has been specified in the uri + if ($uri->getUsername() && $uri->getPassword()) { + $this->setAuth($uri->getUsername(), $uri->getPassword()); + } + + // We have no ports, set the defaults + if (! $uri->getPort()) { + $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80)); + } + + $this->uri = $uri; + + return $this; + } + + /** + * Get the URI for the next request + * + * @param boolean $as_string If true, will return the URI as a string + * @return Zend_Uri_Http|string + */ + public function getUri($as_string = false) + { + if ($as_string && $this->uri instanceof Zend_Uri_Http) { + return $this->uri->__toString(); + } else { + return $this->uri; + } + } + + /** + * Set configuration parameters for this HTTP client + * + * @param Zend_Config | array $config + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setConfig($config = array()) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + + } elseif (! is_array($config)) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Array or Zend_Config object expected, got ' . gettype($config)); + } + + foreach ($config as $k => $v) { + $this->config[strtolower($k)] = $v; + } + + // Pass configuration options to the adapter if it exists + if ($this->adapter instanceof Zend_Http_Client_Adapter_Interface) { + $this->adapter->setConfig($config); + } + + return $this; + } + + /** + * Set the next request's method + * + * Validated the passed method and sets it. If we have files set for + * POST requests, and the new method is not POST, the files are silently + * dropped. + * + * @param string $method + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setMethod($method = self::GET) + { + if (! preg_match('/^[^\x00-\x1f\x7f-\xff\(\)<>@,;:\\\\"\/\[\]\?={}\s]+$/', $method)) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method."); + } + + if ($method == self::POST && $this->enctype === null) { + $this->setEncType(self::ENC_URLENCODED); + } + + $this->method = $method; + + return $this; + } + + /** + * Set one or more request headers + * + * This function can be used in several ways to set the client's request + * headers: + * 1. By providing two parameters: $name as the header to set (eg. 'Host') + * and $value as it's value (eg. 'www.example.com'). + * 2. By providing a single header string as the only parameter + * eg. 'Host: www.example.com' + * 3. By providing an array of headers as the first parameter + * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case + * the function will call itself recursively for each array item. + * + * @param string|array $name Header name, full header string ('Header: value') + * or an array of headers + * @param mixed $value Header value or null + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setHeaders($name, $value = null) + { + // If we got an array, go recusive! + if (is_array($name)) { + foreach ($name as $k => $v) { + if (is_string($k)) { + $this->setHeaders($k, $v); + } else { + $this->setHeaders($v, null); + } + } + } else { + // Check if $name needs to be split + if ($value === null && (strpos($name, ':') > 0)) { + list($name, $value) = explode(':', $name, 2); + } + + // Make sure the name is valid if we are in strict mode + if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name"); + } + + $normalized_name = strtolower($name); + + // If $value is null or false, unset the header + if ($value === null || $value === false) { + unset($this->headers[$normalized_name]); + + // Else, set the header + } else { + // Header names are stored lowercase internally. + if (is_string($value)) { + $value = trim($value); + } + $this->headers[$normalized_name] = array($name, $value); + } + } + + return $this; + } + + /** + * Get the value of a specific header + * + * Note that if the header has more than one value, an array + * will be returned. + * + * @param string $key + * @return string|array|null The header value or null if it is not set + */ + public function getHeader($key) + { + $key = strtolower($key); + if (isset($this->headers[$key])) { + return $this->headers[$key][1]; + } else { + return null; + } + } + + /** + * Set a GET parameter for the request. Wrapper around _setParameter + * + * @param string|array $name + * @param string $value + * @return Zend_Http_Client + */ + public function setParameterGet($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $k => $v) + $this->_setParameter('GET', $k, $v); + } else { + $this->_setParameter('GET', $name, $value); + } + + return $this; + } + + /** + * Set a POST parameter for the request. Wrapper around _setParameter + * + * @param string|array $name + * @param string $value + * @return Zend_Http_Client + */ + public function setParameterPost($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $k => $v) + $this->_setParameter('POST', $k, $v); + } else { + $this->_setParameter('POST', $name, $value); + } + + return $this; + } + + /** + * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost + * + * @param string $type GET or POST + * @param string $name + * @param string $value + * @return null + */ + protected function _setParameter($type, $name, $value) + { + $parray = array(); + $type = strtolower($type); + switch ($type) { + case 'get': + $parray = &$this->paramsGet; + break; + case 'post': + $parray = &$this->paramsPost; + break; + } + + if ($value === null) { + if (isset($parray[$name])) unset($parray[$name]); + } else { + $parray[$name] = $value; + } + } + + /** + * Get the number of redirections done on the last request + * + * @return int + */ + public function getRedirectionsCount() + { + return $this->redirectCounter; + } + + /** + * Set HTTP authentication parameters + * + * $type should be one of the supported types - see the self::AUTH_* + * constants. + * + * To enable authentication: + * + * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC); + * + * + * To disable authentication: + * + * $this->setAuth(false); + * + * + * @see http://www.faqs.org/rfcs/rfc2617.html + * @param string|false $user User name or false disable authentication + * @param string $password Password + * @param string $type Authentication type + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setAuth($user, $password = '', $type = self::AUTH_BASIC) + { + // If we got false or null, disable authentication + if ($user === false || $user === null) { + $this->auth = null; + + // Clear the auth information in the uri instance as well + if ($this->uri instanceof Zend_Uri_Http) { + $this->getUri()->setUsername(''); + $this->getUri()->setPassword(''); + } + // Else, set up authentication + } else { + // Check we got a proper authentication type + if (! defined('self::AUTH_' . strtoupper($type))) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Invalid or not supported authentication type: '$type'"); + } + + $this->auth = array( + 'user' => (string) $user, + 'password' => (string) $password, + 'type' => $type + ); + } + + return $this; + } + + /** + * Set the HTTP client's cookie jar. + * + * A cookie jar is an object that holds and maintains cookies across HTTP requests + * and responses. + * + * @param Zend_Http_CookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setCookieJar($cookiejar = true) + { + if (! class_exists('Zend_Http_CookieJar')) { + // require_once 'Zend/Http/CookieJar.php'; + } + + if ($cookiejar instanceof Zend_Http_CookieJar) { + $this->cookiejar = $cookiejar; + } elseif ($cookiejar === true) { + $this->cookiejar = new Zend_Http_CookieJar(); + } elseif (! $cookiejar) { + $this->cookiejar = null; + } else { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Invalid parameter type passed as CookieJar'); + } + + return $this; + } + + /** + * Return the current cookie jar or null if none. + * + * @return Zend_Http_CookieJar|null + */ + public function getCookieJar() + { + return $this->cookiejar; + } + + /** + * Add a cookie to the request. If the client has no Cookie Jar, the cookies + * will be added directly to the headers array as "Cookie" headers. + * + * @param Zend_Http_Cookie|string $cookie + * @param string|null $value If "cookie" is a string, this is the cookie value. + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setCookie($cookie, $value = null) + { + if (! class_exists('Zend_Http_Cookie')) { + // require_once 'Zend/Http/Cookie.php'; + } + + if (is_array($cookie)) { + foreach ($cookie as $c => $v) { + if (is_string($c)) { + $this->setCookie($c, $v); + } else { + $this->setCookie($v); + } + } + + return $this; + } + + if ($value !== null && $this->config['encodecookies']) { + $value = urlencode($value); + } + + if (isset($this->cookiejar)) { + if ($cookie instanceof Zend_Http_Cookie) { + $this->cookiejar->addCookie($cookie); + } elseif (is_string($cookie) && $value !== null) { + $cookie = Zend_Http_Cookie::fromString("{$cookie}={$value}", + $this->uri, + $this->config['encodecookies']); + $this->cookiejar->addCookie($cookie); + } + } else { + if ($cookie instanceof Zend_Http_Cookie) { + $name = $cookie->getName(); + $value = $cookie->getValue(); + $cookie = $name; + } + + if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})"); + } + + $value = addslashes($value); + + if (! isset($this->headers['cookie'])) { + $this->headers['cookie'] = array('Cookie', ''); + } + $this->headers['cookie'][1] .= $cookie . '=' . $value . '; '; + } + + return $this; + } + + /** + * Set a file to upload (using a POST request) + * + * Can be used in two ways: + * + * 1. $data is null (default): $filename is treated as the name if a local file which + * will be read and sent. Will try to guess the content type using mime_content_type(). + * 2. $data is set - $filename is sent as the file name, but $data is sent as the file + * contents and no file is read from the file system. In this case, you need to + * manually set the Content-Type ($ctype) or it will default to + * application/octet-stream. + * + * @param string $filename Name of file to upload, or name to save as + * @param string $formname Name of form element to send as + * @param string $data Data to send (if null, $filename is read and sent) + * @param string $ctype Content type to use (if $data is set and $ctype is + * null, will be application/octet-stream) + * @return Zend_Http_Client + * @throws Zend_Http_Client_Exception + */ + public function setFileUpload($filename, $formname, $data = null, $ctype = null) + { + if ($data === null) { + if (($data = @file_get_contents($filename)) === false) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Unable to read file '{$filename}' for upload"); + } + + if (! $ctype) { + $ctype = $this->_detectFileMimeType($filename); + } + } + + // Force enctype to multipart/form-data + $this->setEncType(self::ENC_FORMDATA); + + $this->files[] = array( + 'formname' => $formname, + 'filename' => basename($filename), + 'ctype' => $ctype, + 'data' => $data + ); + + return $this; + } + + /** + * Set the encoding type for POST data + * + * @param string $enctype + * @return Zend_Http_Client + */ + public function setEncType($enctype = self::ENC_URLENCODED) + { + $this->enctype = $enctype; + + return $this; + } + + /** + * Set the raw (already encoded) POST data. + * + * This function is here for two reasons: + * 1. For advanced user who would like to set their own data, already encoded + * 2. For backwards compatibilty: If someone uses the old post($data) method. + * this method will be used to set the encoded data. + * + * $data can also be stream (such as file) from which the data will be read. + * + * @param string|resource $data + * @param string $enctype + * @return Zend_Http_Client + */ + public function setRawData($data, $enctype = null) + { + $this->raw_post_data = $data; + $this->setEncType($enctype); + if (is_resource($data)) { + // We've got stream data + $stat = @fstat($data); + if($stat) { + $this->setHeaders(self::CONTENT_LENGTH, $stat['size']); + } + } + return $this; + } + + /** + * Clear all GET and POST parameters + * + * Should be used to reset the request parameters if the client is + * used for several concurrent requests. + * + * clearAll parameter controls if we clean just parameters or also + * headers and last_* + * + * @param bool $clearAll Should all data be cleared? + * @return Zend_Http_Client + */ + public function resetParameters($clearAll = false) + { + // Reset parameter data + $this->paramsGet = array(); + $this->paramsPost = array(); + $this->files = array(); + $this->raw_post_data = null; + + if($clearAll) { + $this->headers = array(); + $this->last_request = null; + $this->last_response = null; + } else { + // Clear outdated headers + if (isset($this->headers[strtolower(self::CONTENT_TYPE)])) { + unset($this->headers[strtolower(self::CONTENT_TYPE)]); + } + if (isset($this->headers[strtolower(self::CONTENT_LENGTH)])) { + unset($this->headers[strtolower(self::CONTENT_LENGTH)]); + } + } + + return $this; + } + + /** + * Get the last HTTP request as string + * + * @return string + */ + public function getLastRequest() + { + return $this->last_request; + } + + /** + * Get the last HTTP response received by this client + * + * If $config['storeresponse'] is set to false, or no response was + * stored yet, will return null + * + * @return Zend_Http_Response or null if none + */ + public function getLastResponse() + { + return $this->last_response; + } + + /** + * Load the connection adapter + * + * While this method is not called more than one for a client, it is + * seperated from ->request() to preserve logic and readability + * + * @param Zend_Http_Client_Adapter_Interface|string $adapter + * @return null + * @throws Zend_Http_Client_Exception + */ + public function setAdapter($adapter) + { + if (is_string($adapter)) { + if (!class_exists($adapter)) { + try { + // require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($adapter); + } catch (Zend_Exception $e) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Unable to load adapter '$adapter': {$e->getMessage()}", 0, $e); + } + } + + $adapter = new $adapter; + } + + if (! $adapter instanceof Zend_Http_Client_Adapter_Interface) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Passed adapter is not a HTTP connection adapter'); + } + + $this->adapter = $adapter; + $config = $this->config; + unset($config['adapter']); + $this->adapter->setConfig($config); + } + + /** + * Load the connection adapter + * + * @return Zend_Http_Client_Adapter_Interface $adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Set streaming for received data + * + * @param string|boolean $streamfile Stream file, true for temp file, false/null for no streaming + * @return Zend_Http_Client + */ + public function setStream($streamfile = true) + { + $this->setConfig(array("output_stream" => $streamfile)); + return $this; + } + + /** + * Get status of streaming for received data + * @return boolean|string + */ + public function getStream() + { + return $this->config["output_stream"]; + } + + /** + * Create temporary stream + * + * @return resource + */ + protected function _openTempStream() + { + $this->_stream_name = $this->config['output_stream']; + if(!is_string($this->_stream_name)) { + // If name is not given, create temp name + $this->_stream_name = tempnam(isset($this->config['stream_tmp_dir'])?$this->config['stream_tmp_dir']:sys_get_temp_dir(), + 'Zend_Http_Client'); + } + + if (false === ($fp = @fopen($this->_stream_name, "w+b"))) { + if ($this->adapter instanceof Zend_Http_Client_Adapter_Interface) { + $this->adapter->close(); + } + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Could not open temp file {$this->_stream_name}"); + } + + return $fp; + } + + /** + * Send the HTTP request and return an HTTP response object + * + * @param string $method + * @return Zend_Http_Response + * @throws Zend_Http_Client_Exception + */ + public function request($method = null) + { + if (! $this->uri instanceof Zend_Uri_Http) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('No valid URI has been passed to the client'); + } + + if ($method) { + $this->setMethod($method); + } + $this->redirectCounter = 0; + $response = null; + + // Make sure the adapter is loaded + if ($this->adapter == null) { + $this->setAdapter($this->config['adapter']); + } + + // Send the first request. If redirected, continue. + do { + // Clone the URI and add the additional GET parameters to it + $uri = clone $this->uri; + if (! empty($this->paramsGet)) { + $query = $uri->getQuery(); + if (! empty($query)) { + $query .= '&'; + } + $query .= http_build_query($this->paramsGet, null, '&'); + + $uri->setQuery($query); + } + + $body = $this->_prepareBody(); + $headers = $this->_prepareHeaders(); + + // check that adapter supports streaming before using it + if(is_resource($body) && !($this->adapter instanceof Zend_Http_Client_Adapter_Stream)) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Adapter does not support streaming'); + } + + // Open the connection, send the request and read the response + $this->adapter->connect($uri->getHost(), $uri->getPort(), + ($uri->getScheme() == 'https' ? true : false)); + + if($this->config['output_stream']) { + if($this->adapter instanceof Zend_Http_Client_Adapter_Stream) { + $stream = $this->_openTempStream(); + $this->adapter->setOutputStream($stream); + } else { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Adapter does not support streaming'); + } + } + + $this->last_request = $this->adapter->write($this->method, + $uri, $this->config['httpversion'], $headers, $body); + + $response = $this->adapter->read(); + if (! $response) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception('Unable to read response, or response is empty'); + } + + if($this->config['output_stream']) { + rewind($stream); + // cleanup the adapter + $this->adapter->setOutputStream(null); + $response = Zend_Http_Response_Stream::fromStream($response, $stream); + $response->setStreamName($this->_stream_name); + if(!is_string($this->config['output_stream'])) { + // we used temp name, will need to clean up + $response->setCleanup(true); + } + } else { + $response = Zend_Http_Response::fromString($response); + } + + if ($this->config['storeresponse']) { + $this->last_response = $response; + } + + // Load cookies into cookie jar + if (isset($this->cookiejar)) { + $this->cookiejar->addCookiesFromResponse($response, $uri); + } + + // If we got redirected, look for the Location header + if ($response->isRedirect() && ($location = $response->getHeader('location'))) { + + // Check whether we send the exact same request again, or drop the parameters + // and send a GET request + if ($response->getStatus() == 303 || + ((! $this->config['strictredirects']) && ($response->getStatus() == 302 || + $response->getStatus() == 301))) { + + $this->resetParameters(); + $this->setMethod(self::GET); + } + + // If we got a well formed absolute URI + if (Zend_Uri_Http::check($location)) { + $this->setHeaders('host', null); + $this->setUri($location); + + } else { + + // Split into path and query and set the query + if (strpos($location, '?') !== false) { + list($location, $query) = explode('?', $location, 2); + } else { + $query = ''; + } + $this->uri->setQuery($query); + + // Else, if we got just an absolute path, set it + if(strpos($location, '/') === 0) { + $this->uri->setPath($location); + + // Else, assume we have a relative path + } else { + // Get the current path directory, removing any trailing slashes + $path = $this->uri->getPath(); + $path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); + $this->uri->setPath($path . '/' . $location); + } + } + ++$this->redirectCounter; + + } else { + // If we didn't get any location, stop redirecting + break; + } + + } while ($this->redirectCounter < $this->config['maxredirects']); + + return $response; + } + + /** + * Prepare the request headers + * + * @return array + */ + protected function _prepareHeaders() + { + $headers = array(); + + // Set the host header + if (! isset($this->headers['host'])) { + $host = $this->uri->getHost(); + + // If the port is not default, add it + if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) || + ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) { + $host .= ':' . $this->uri->getPort(); + } + + $headers[] = "Host: {$host}"; + } + + // Set the connection header + if (! isset($this->headers['connection'])) { + if (! $this->config['keepalive']) { + $headers[] = "Connection: close"; + } + } + + // Set the Accept-encoding header if not set - depending on whether + // zlib is available or not. + if (! isset($this->headers['accept-encoding'])) { + if (function_exists('gzinflate')) { + $headers[] = 'Accept-encoding: gzip, deflate'; + } else { + $headers[] = 'Accept-encoding: identity'; + } + } + + // Set the Content-Type header + if ($this->method == self::POST && + (! isset($this->headers[strtolower(self::CONTENT_TYPE)]) && isset($this->enctype))) { + + $headers[] = self::CONTENT_TYPE . ': ' . $this->enctype; + } + + // Set the user agent header + if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) { + $headers[] = "User-Agent: {$this->config['useragent']}"; + } + + // Set HTTP authentication if needed + if (is_array($this->auth)) { + $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']); + $headers[] = "Authorization: {$auth}"; + } + + // Load cookies from cookie jar + if (isset($this->cookiejar)) { + $cookstr = $this->cookiejar->getMatchingCookies($this->uri, + true, Zend_Http_CookieJar::COOKIE_STRING_CONCAT); + + if ($cookstr) { + $headers[] = "Cookie: {$cookstr}"; + } + } + + // Add all other user defined headers + foreach ($this->headers as $header) { + list($name, $value) = $header; + if (is_array($value)) { + $value = implode(', ', $value); + } + + $headers[] = "$name: $value"; + } + + return $headers; + } + + /** + * Prepare the request body (for POST and PUT requests) + * + * @return string + * @throws Zend_Http_Client_Exception + */ + protected function _prepareBody() + { + // According to RFC2616, a TRACE request should not have a body. + if ($this->method == self::TRACE) { + return ''; + } + + if (isset($this->raw_post_data) && is_resource($this->raw_post_data)) { + return $this->raw_post_data; + } + // If mbstring overloads substr and strlen functions, we have to + // override it's internal encoding + if (function_exists('mb_internal_encoding') && + ((int) ini_get('mbstring.func_overload')) & 2) { + + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + // If we have raw_post_data set, just use it as the body. + if (isset($this->raw_post_data)) { + $this->setHeaders(self::CONTENT_LENGTH, strlen($this->raw_post_data)); + if (isset($mbIntEnc)) { + mb_internal_encoding($mbIntEnc); + } + + return $this->raw_post_data; + } + + $body = ''; + + // If we have files to upload, force enctype to multipart/form-data + if (count ($this->files) > 0) { + $this->setEncType(self::ENC_FORMDATA); + } + + // If we have POST parameters or files, encode and add them to the body + if (count($this->paramsPost) > 0 || count($this->files) > 0) { + switch($this->enctype) { + case self::ENC_FORMDATA: + // Encode body as multipart/form-data + $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); + $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}"); + + // Get POST parameters and encode them + $params = self::_flattenParametersArray($this->paramsPost); + foreach ($params as $pp) { + $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); + } + + // Encode files + foreach ($this->files as $file) { + $fhead = array(self::CONTENT_TYPE => $file['ctype']); + $body .= self::encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); + } + + $body .= "--{$boundary}--\r\n"; + break; + + case self::ENC_URLENCODED: + // Encode body as application/x-www-form-urlencoded + $this->setHeaders(self::CONTENT_TYPE, self::ENC_URLENCODED); + $body = http_build_query($this->paramsPost, '', '&'); + break; + + default: + if (isset($mbIntEnc)) { + mb_internal_encoding($mbIntEnc); + } + + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Cannot handle content type '{$this->enctype}' automatically." . + " Please use Zend_Http_Client::setRawData to send this kind of content."); + break; + } + } + + // Set the Content-Length if we have a body or if request is POST/PUT + if ($body || $this->method == self::POST || $this->method == self::PUT) { + $this->setHeaders(self::CONTENT_LENGTH, strlen($body)); + } + + if (isset($mbIntEnc)) { + mb_internal_encoding($mbIntEnc); + } + + return $body; + } + + /** + * Helper method that gets a possibly multi-level parameters array (get or + * post) and flattens it. + * + * The method returns an array of (key, value) pairs (because keys are not + * necessarily unique. If one of the parameters in as array, it will also + * add a [] suffix to the key. + * + * This method is deprecated since Zend Framework 1.9 in favour of + * self::_flattenParametersArray() and will be dropped in 2.0 + * + * @deprecated since 1.9 + * + * @param array $parray The parameters array + * @param bool $urlencode Whether to urlencode the name and value + * @return array + */ + protected function _getParametersRecursive($parray, $urlencode = false) + { + // Issue a deprecated notice + trigger_error("The " . __METHOD__ . " method is deprecated and will be dropped in 2.0.", + E_USER_NOTICE); + + if (! is_array($parray)) { + return $parray; + } + $parameters = array(); + + foreach ($parray as $name => $value) { + if ($urlencode) { + $name = urlencode($name); + } + + // If $value is an array, iterate over it + if (is_array($value)) { + $name .= ($urlencode ? '%5B%5D' : '[]'); + foreach ($value as $subval) { + if ($urlencode) { + $subval = urlencode($subval); + } + $parameters[] = array($name, $subval); + } + } else { + if ($urlencode) { + $value = urlencode($value); + } + $parameters[] = array($name, $value); + } + } + + return $parameters; + } + + /** + * Attempt to detect the MIME type of a file using available extensions + * + * This method will try to detect the MIME type of a file. If the fileinfo + * extension is available, it will be used. If not, the mime_magic + * extension which is deprected but is still available in many PHP setups + * will be tried. + * + * If neither extension is available, the default application/octet-stream + * MIME type will be returned + * + * @param string $file File path + * @return string MIME type + */ + protected function _detectFileMimeType($file) + { + $type = null; + + // First try with fileinfo functions + if (function_exists('finfo_open')) { + if (self::$_fileInfoDb === null) { + self::$_fileInfoDb = @finfo_open(FILEINFO_MIME); + } + + if (self::$_fileInfoDb) { + $type = finfo_file(self::$_fileInfoDb, $file); + } + + } elseif (function_exists('mime_content_type')) { + $type = mime_content_type($file); + } + + // Fallback to the default application/octet-stream + if (! $type) { + $type = 'application/octet-stream'; + } + + return $type; + } + + /** + * Encode data to a multipart/form-data part suitable for a POST request. + * + * @param string $boundary + * @param string $name + * @param mixed $value + * @param string $filename + * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary") + * @return string + */ + public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) { + $ret = "--{$boundary}\r\n" . + 'Content-Disposition: form-data; name="' . $name .'"'; + + if ($filename) { + $ret .= '; filename="' . $filename . '"'; + } + $ret .= "\r\n"; + + foreach ($headers as $hname => $hvalue) { + $ret .= "{$hname}: {$hvalue}\r\n"; + } + $ret .= "\r\n"; + + $ret .= "{$value}\r\n"; + + return $ret; + } + + /** + * Create a HTTP authentication "Authorization:" header according to the + * specified user, password and authentication method. + * + * @see http://www.faqs.org/rfcs/rfc2617.html + * @param string $user + * @param string $password + * @param string $type + * @return string + * @throws Zend_Http_Client_Exception + */ + public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC) + { + $authHeader = null; + + switch ($type) { + case self::AUTH_BASIC: + // In basic authentication, the user name cannot contain ":" + if (strpos($user, ':') !== false) { + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("The user name cannot contain ':' in 'Basic' HTTP authentication"); + } + + $authHeader = 'Basic ' . base64_encode($user . ':' . $password); + break; + + //case self::AUTH_DIGEST: + /** + * @todo Implement digest authentication + */ + // break; + + default: + /** @see Zend_Http_Client_Exception */ + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Not a supported HTTP authentication type: '$type'"); + } + + return $authHeader; + } + + /** + * Convert an array of parameters into a flat array of (key, value) pairs + * + * Will flatten a potentially multi-dimentional array of parameters (such + * as POST parameters) into a flat array of (key, value) paris. In case + * of multi-dimentional arrays, square brackets ([]) will be added to the + * key to indicate an array. + * + * @since 1.9 + * + * @param array $parray + * @param string $prefix + * @return array + */ + static protected function _flattenParametersArray($parray, $prefix = null) + { + if (! is_array($parray)) { + return $parray; + } + + $parameters = array(); + + foreach($parray as $name => $value) { + + // Calculate array key + if ($prefix) { + if (is_int($name)) { + $key = $prefix . '[]'; + } else { + $key = $prefix . "[$name]"; + } + } else { + $key = $name; + } + + if (is_array($value)) { + $parameters = array_merge($parameters, self::_flattenParametersArray($value, $key)); + + } else { + $parameters[] = array($key, $value); + } + } + + return $parameters; + } + +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Curl.php b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Curl.php new file mode 100644 index 00000000..4747bd89 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Curl.php @@ -0,0 +1,507 @@ +_invalidOverwritableCurlOptions = array( + CURLOPT_HTTPGET, + CURLOPT_POST, + CURLOPT_PUT, + CURLOPT_CUSTOMREQUEST, + CURLOPT_HEADER, + CURLOPT_RETURNTRANSFER, + CURLOPT_HTTPHEADER, + CURLOPT_POSTFIELDS, + CURLOPT_INFILE, + CURLOPT_INFILESIZE, + CURLOPT_PORT, + CURLOPT_MAXREDIRS, + CURLOPT_CONNECTTIMEOUT, + CURL_HTTP_VERSION_1_1, + CURL_HTTP_VERSION_1_0, + ); + } + + /** + * Set the configuration array for the adapter + * + * @throws Zend_Http_Client_Adapter_Exception + * @param Zend_Config | array $config + * @return Zend_Http_Client_Adapter_Curl + */ + public function setConfig($config = array()) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + + } elseif (! is_array($config)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + 'Array or Zend_Config object expected, got ' . gettype($config) + ); + } + + if(isset($config['proxy_user']) && isset($config['proxy_pass'])) { + $this->setCurlOption(CURLOPT_PROXYUSERPWD, $config['proxy_user'].":".$config['proxy_pass']); + unset($config['proxy_user'], $config['proxy_pass']); + } + + foreach ($config as $k => $v) { + $option = strtolower($k); + switch($option) { + case 'proxy_host': + $this->setCurlOption(CURLOPT_PROXY, $v); + break; + case 'proxy_port': + $this->setCurlOption(CURLOPT_PROXYPORT, $v); + break; + default: + $this->_config[$option] = $v; + break; + } + } + + return $this; + } + + /** + * Retrieve the array of all configuration options + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Direct setter for cURL adapter related options. + * + * @param string|int $option + * @param mixed $value + * @return Zend_Http_Adapter_Curl + */ + public function setCurlOption($option, $value) + { + if (!isset($this->_config['curloptions'])) { + $this->_config['curloptions'] = array(); + } + $this->_config['curloptions'][$option] = $value; + return $this; + } + + /** + * Initialize curl + * + * @param string $host + * @param int $port + * @param boolean $secure + * @return void + * @throws Zend_Http_Client_Adapter_Exception if unable to connect + */ + public function connect($host, $port = 80, $secure = false) + { + // If we're already connected, disconnect first + if ($this->_curl) { + $this->close(); + } + + // If we are connected to a different server or port, disconnect first + if ($this->_curl + && is_array($this->_connected_to) + && ($this->_connected_to[0] != $host + || $this->_connected_to[1] != $port) + ) { + $this->close(); + } + + // Do the actual connection + $this->_curl = curl_init(); + if ($port != 80) { + curl_setopt($this->_curl, CURLOPT_PORT, intval($port)); + } + + // Set timeout + curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config['timeout']); + + // Set Max redirects + curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']); + + if (!$this->_curl) { + $this->close(); + + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' . $host . ':' . $port); + } + + if ($secure !== false) { + // Behave the same like Zend_Http_Adapter_Socket on SSL options. + if (isset($this->_config['sslcert'])) { + curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config['sslcert']); + } + if (isset($this->_config['sslpassphrase'])) { + curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']); + } + } + + // Update connected_to + $this->_connected_to = array($host, $port); + } + + /** + * Send request to the remote server + * + * @param string $method + * @param Zend_Uri_Http $uri + * @param float $http_ver + * @param array $headers + * @param string $body + * @return string $request + * @throws Zend_Http_Client_Adapter_Exception If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option + */ + public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '') + { + // Make sure we're properly connected + if (!$this->_curl) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected"); + } + + if ($this->_connected_to[0] != $uri->getHost() || $this->_connected_to[1] != $uri->getPort()) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong host"); + } + + // set URL + curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString()); + + // ensure correct curl call + $curlValue = true; + switch ($method) { + case Zend_Http_Client::GET: + $curlMethod = CURLOPT_HTTPGET; + break; + + case Zend_Http_Client::POST: + $curlMethod = CURLOPT_POST; + break; + + case Zend_Http_Client::PUT: + // There are two different types of PUT request, either a Raw Data string has been set + // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used. + if(is_resource($body)) { + $this->_config['curloptions'][CURLOPT_INFILE] = $body; + } + if (isset($this->_config['curloptions'][CURLOPT_INFILE])) { + // Now we will probably already have Content-Length set, so that we have to delete it + // from $headers at this point: + foreach ($headers AS $k => $header) { + if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) { + if(is_resource($body)) { + $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1]; + } + unset($headers[$k]); + } + } + + if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE."); + } + + if(is_resource($body)) { + $body = ''; + } + + $curlMethod = CURLOPT_PUT; + } else { + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "PUT"; + } + break; + + case Zend_Http_Client::DELETE: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "DELETE"; + break; + + case Zend_Http_Client::OPTIONS: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "OPTIONS"; + break; + + case Zend_Http_Client::TRACE: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "TRACE"; + break; + + case Zend_Http_Client::HEAD: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "HEAD"; + break; + + default: + // For now, through an exception for unsupported request methods + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Method currently not supported"); + } + + if(is_resource($body) && $curlMethod != CURLOPT_PUT) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Streaming requests are allowed only with PUT"); + } + + // get http version to use + $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0; + + // mark as HTTP request and set HTTP method + curl_setopt($this->_curl, $curlHttp, true); + curl_setopt($this->_curl, $curlMethod, $curlValue); + + if($this->out_stream) { + // headers will be read into the response + curl_setopt($this->_curl, CURLOPT_HEADER, false); + curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader")); + // and data will be written into the file + curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream); + } else { + // ensure headers are also returned + curl_setopt($this->_curl, CURLOPT_HEADER, true); + + // ensure actual response is returned + curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true); + } + + // set additional headers + $headers['Accept'] = ''; + curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers); + + /** + * Make sure POSTFIELDS is set after $curlMethod is set: + * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161 + */ + if ($method == Zend_Http_Client::POST) { + curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); + } elseif ($curlMethod == CURLOPT_PUT) { + // this covers a PUT by file-handle: + // Make the setting of this options explicit (rather than setting it through the loop following a bit lower) + // to group common functionality together. + curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config['curloptions'][CURLOPT_INFILE]); + curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config['curloptions'][CURLOPT_INFILESIZE]); + unset($this->_config['curloptions'][CURLOPT_INFILE]); + unset($this->_config['curloptions'][CURLOPT_INFILESIZE]); + } elseif ($method == Zend_Http_Client::PUT) { + // This is a PUT by a setRawData string, not by file-handle + curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); + } + + // set additional curl options + if (isset($this->_config['curloptions'])) { + foreach ((array)$this->_config['curloptions'] as $k => $v) { + if (!in_array($k, $this->_invalidOverwritableCurlOptions)) { + if (curl_setopt($this->_curl, $k, $v) == false) { + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception(sprintf("Unknown or erroreous cURL option '%s' set", $k)); + } + } + } + } + + // send the request + $response = curl_exec($this->_curl); + + // if we used streaming, headers are already there + if(!is_resource($this->out_stream)) { + $this->_response = $response; + } + + $request = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT); + $request .= $body; + + if (empty($this->_response)) { + // require_once 'Zend/Http/Client/Exception.php'; + throw new Zend_Http_Client_Exception("Error in cURL request: " . curl_error($this->_curl)); + } + + // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again + if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) { + $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->_response); + } + + // Eliminate multiple HTTP responses. + do { + $parts = preg_split('|(?:\r?\n){2}|m', $this->_response, 2); + $again = false; + + if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) { + $this->_response = $parts[1]; + $again = true; + } + } while ($again); + + // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string: + if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) { + $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->_response); + } + + return $request; + } + + /** + * Return read response from server + * + * @return string + */ + public function read() + { + return $this->_response; + } + + /** + * Close the connection to the server + * + */ + public function close() + { + if(is_resource($this->_curl)) { + curl_close($this->_curl); + } + $this->_curl = null; + $this->_connected_to = array(null, null); + } + + /** + * Get cUrl Handle + * + * @return resource + */ + public function getHandle() + { + return $this->_curl; + } + + /** + * Set output stream for the response + * + * @param resource $stream + * @return Zend_Http_Client_Adapter_Socket + */ + public function setOutputStream($stream) + { + $this->out_stream = $stream; + return $this; + } + + /** + * Header reader function for CURL + * + * @param resource $curl + * @param string $header + * @return int + */ + public function readHeader($curl, $header) + { + $this->_response .= $header; + return strlen($header); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Exception.php b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Exception.php new file mode 100644 index 00000000..082c8af3 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Exception.php @@ -0,0 +1,38 @@ + 'ssl', + 'sslcert' => null, + 'sslpassphrase' => null, + 'proxy_host' => '', + 'proxy_port' => 8080, + 'proxy_user' => '', + 'proxy_pass' => '', + 'proxy_auth' => Zend_Http_Client::AUTH_BASIC, + 'persistent' => false + ); + + /** + * Whether HTTPS CONNECT was already negotiated with the proxy or not + * + * @var boolean + */ + protected $negotiated = false; + + /** + * Connect to the remote server + * + * Will try to connect to the proxy server. If no proxy was set, will + * fall back to the target server (behave like regular Socket adapter) + * + * @param string $host + * @param int $port + * @param boolean $secure + */ + public function connect($host, $port = 80, $secure = false) + { + // If no proxy is set, fall back to Socket adapter + if (! $this->config['proxy_host']) { + return parent::connect($host, $port, $secure); + } + + /* Url might require stream context even if proxy connection doesn't */ + if ($secure) { + $this->config['sslusecontext'] = true; + } + + // Connect (a non-secure connection) to the proxy server + return parent::connect( + $this->config['proxy_host'], + $this->config['proxy_port'], + false + ); + } + + /** + * Send request to the proxy server + * + * @param string $method + * @param Zend_Uri_Http $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + // If no proxy is set, fall back to default Socket adapter + if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body); + + // Make sure we're properly connected + if (! $this->socket) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected"); + } + + $host = $this->config['proxy_host']; + $port = $this->config['proxy_port']; + + if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong proxy server"); + } + + // Add Proxy-Authorization header + if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) { + $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader( + $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth'] + ); + } + + // if we are proxying HTTPS, preform CONNECT handshake with the proxy + if ($uri->getScheme() == 'https' && (! $this->negotiated)) { + $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers); + $this->negotiated = true; + } + + // Save request method for later + $this->method = $method; + + // Build request headers + if ($this->negotiated) { + $path = $uri->getPath(); + if ($uri->getQuery()) { + $path .= '?' . $uri->getQuery(); + } + $request = "$method $path HTTP/$http_ver\r\n"; + } else { + $request = "$method $uri HTTP/$http_ver\r\n"; + } + + // Add all headers to the request string + foreach ($headers as $k => $v) { + if (is_string($k)) $v = "$k: $v"; + $request .= "$v\r\n"; + } + + // Add the request body + $request .= "\r\n" . $body; + + // Send the request + if (! @fwrite($this->socket, $request)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server"); + } + + return $request; + } + + /** + * Preform handshaking with HTTPS proxy using CONNECT method + * + * @param string $host + * @param integer $port + * @param string $http_ver + * @param array $headers + */ + protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array()) + { + $request = "CONNECT $host:$port HTTP/$http_ver\r\n" . + "Host: " . $this->config['proxy_host'] . "\r\n"; + + // Add the user-agent header + if (isset($this->config['useragent'])) { + $request .= "User-agent: " . $this->config['useragent'] . "\r\n"; + } + + // If the proxy-authorization header is set, send it to proxy but remove + // it from headers sent to target host + if (isset($headers['proxy-authorization'])) { + $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n"; + unset($headers['proxy-authorization']); + } + + $request .= "\r\n"; + + // Send the request + if (! @fwrite($this->socket, $request)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server"); + } + + // Read response headers only + $response = ''; + $gotStatus = false; + while ($line = @fgets($this->socket)) { + $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); + if ($gotStatus) { + $response .= $line; + if (!chop($line)) break; + } + } + + // Check that the response from the proxy is 200 + if (Zend_Http_Response::extractCode($response) != 200) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Unable to connect to HTTPS proxy. Server response: " . $response); + } + + // If all is good, switch socket to secure mode. We have to fall back + // through the different modes + $modes = array( + STREAM_CRYPTO_METHOD_TLS_CLIENT, + STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + STREAM_CRYPTO_METHOD_SSLv2_CLIENT + ); + + $success = false; + foreach($modes as $mode) { + $success = stream_socket_enable_crypto($this->socket, true, $mode); + if ($success) break; + } + + if (! $success) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception("Unable to connect to" . + " HTTPS server through proxy: could not negotiate secure connection."); + } + } + + /** + * Close the connection to the server + * + */ + public function close() + { + parent::close(); + $this->negotiated = false; + } + + /** + * Destructor: make sure the socket is disconnected + * + */ + public function __destruct() + { + if ($this->socket) $this->close(); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Socket.php b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Socket.php new file mode 100644 index 00000000..4f3d4172 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Socket.php @@ -0,0 +1,543 @@ + false, + 'ssltransport' => 'ssl', + 'sslcert' => null, + 'sslpassphrase' => null, + 'sslusecontext' => false + ); + + /** + * Request method - will be set by write() and might be used by read() + * + * @var string + */ + protected $method = null; + + /** + * Stream context + * + * @var resource + */ + protected $_context = null; + + /** + * Adapter constructor, currently empty. Config is set using setConfig() + * + */ + public function __construct() + { + } + + /** + * Set the configuration array for the adapter + * + * @param Zend_Config | array $config + */ + public function setConfig($config = array()) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + + } elseif (! is_array($config)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + 'Array or Zend_Config object expected, got ' . gettype($config) + ); + } + + foreach ($config as $k => $v) { + $this->config[strtolower($k)] = $v; + } + } + + /** + * Retrieve the array of all configuration options + * + * @return array + */ + public function getConfig() + { + return $this->config; + } + + /** + * Set the stream context for the TCP connection to the server + * + * Can accept either a pre-existing stream context resource, or an array + * of stream options, similar to the options array passed to the + * stream_context_create() PHP function. In such case a new stream context + * will be created using the passed options. + * + * @since Zend Framework 1.9 + * + * @param mixed $context Stream context or array of context options + * @return Zend_Http_Client_Adapter_Socket + */ + public function setStreamContext($context) + { + if (is_resource($context) && get_resource_type($context) == 'stream-context') { + $this->_context = $context; + + } elseif (is_array($context)) { + $this->_context = stream_context_create($context); + + } else { + // Invalid parameter + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + "Expecting either a stream context resource or array, got " . gettype($context) + ); + } + + return $this; + } + + /** + * Get the stream context for the TCP connection to the server. + * + * If no stream context is set, will create a default one. + * + * @return resource + */ + public function getStreamContext() + { + if (! $this->_context) { + $this->_context = stream_context_create(); + } + + return $this->_context; + } + + /** + * Connect to the remote server + * + * @param string $host + * @param int $port + * @param boolean $secure + */ + public function connect($host, $port = 80, $secure = false) + { + // If the URI should be accessed via SSL, prepend the Hostname with ssl:// + $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; + + // If we are connected to the wrong host, disconnect first + if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { + if (is_resource($this->socket)) $this->close(); + } + + // Now, if we are not connected, connect + if (! is_resource($this->socket) || ! $this->config['keepalive']) { + $context = $this->getStreamContext(); + if ($secure || $this->config['sslusecontext']) { + if ($this->config['sslcert'] !== null) { + if (! stream_context_set_option($context, 'ssl', 'local_cert', + $this->config['sslcert'])) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option'); + } + } + if ($this->config['sslpassphrase'] !== null) { + if (! stream_context_set_option($context, 'ssl', 'passphrase', + $this->config['sslpassphrase'])) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option'); + } + } + } + + $flags = STREAM_CLIENT_CONNECT; + if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT; + + $this->socket = @stream_socket_client($host . ':' . $port, + $errno, + $errstr, + (int) $this->config['timeout'], + $flags, + $context); + + if (! $this->socket) { + $this->close(); + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + 'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr); + } + + // Set the stream timeout + if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout'); + } + + // Update connected_to + $this->connected_to = array($host, $port); + } + } + + /** + * Send request to the remote server + * + * @param string $method + * @param Zend_Uri_Http $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + // Make sure we're properly connected + if (! $this->socket) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected'); + } + + $host = $uri->getHost(); + $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host; + if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host'); + } + + // Save request method for later + $this->method = $method; + + // Build request headers + $path = $uri->getPath(); + if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); + $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; + foreach ($headers as $k => $v) { + if (is_string($k)) $v = ucfirst($k) . ": $v"; + $request .= "$v\r\n"; + } + + if(is_resource($body)) { + $request .= "\r\n"; + } else { + // Add the request body + $request .= "\r\n" . $body; + } + + // Send the request + if (! @fwrite($this->socket, $request)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Error writing request to server'); + } + + if(is_resource($body)) { + if(stream_copy_to_stream($body, $this->socket) == 0) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Error writing request to server'); + } + } + + return $request; + } + + /** + * Read response from server + * + * @return string + */ + public function read() + { + // First, read headers only + $response = ''; + $gotStatus = false; + $stream = !empty($this->config['stream']); + + while (($line = @fgets($this->socket)) !== false) { + $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); + if ($gotStatus) { + $response .= $line; + if (rtrim($line) === '') break; + } + } + + $this->_checkSocketReadTimeout(); + + $statusCode = Zend_Http_Response::extractCode($response); + + // Handle 100 and 101 responses internally by restarting the read again + if ($statusCode == 100 || $statusCode == 101) return $this->read(); + + // Check headers to see what kind of connection / transfer encoding we have + $headers = Zend_Http_Response::extractHeaders($response); + + /** + * Responses to HEAD requests and 204 or 304 responses are not expected + * to have a body - stop reading here + */ + if ($statusCode == 304 || $statusCode == 204 || + $this->method == Zend_Http_Client::HEAD) { + + // Close the connection if requested to do so by the server + if (isset($headers['connection']) && $headers['connection'] == 'close') { + $this->close(); + } + return $response; + } + + // If we got a 'transfer-encoding: chunked' header + if (isset($headers['transfer-encoding'])) { + + if (strtolower($headers['transfer-encoding']) == 'chunked') { + + do { + $line = @fgets($this->socket); + $this->_checkSocketReadTimeout(); + + $chunk = $line; + + // Figure out the next chunk size + $chunksize = trim($line); + if (! ctype_xdigit($chunksize)) { + $this->close(); + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' . + $chunksize . '" unable to read chunked body'); + } + + // Convert the hexadecimal value to plain integer + $chunksize = hexdec($chunksize); + + // Read next chunk + $read_to = ftell($this->socket) + $chunksize; + + do { + $current_pos = ftell($this->socket); + if ($current_pos >= $read_to) break; + + if($this->out_stream) { + if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { + $this->_checkSocketReadTimeout(); + break; + } + } else { + $line = @fread($this->socket, $read_to - $current_pos); + if ($line === false || strlen($line) === 0) { + $this->_checkSocketReadTimeout(); + break; + } + $chunk .= $line; + } + } while (! feof($this->socket)); + + $chunk .= @fgets($this->socket); + $this->_checkSocketReadTimeout(); + + if(!$this->out_stream) { + $response .= $chunk; + } + } while ($chunksize > 0); + } else { + $this->close(); + throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' . + $headers['transfer-encoding'] . '" transfer encoding'); + } + + // We automatically decode chunked-messages when writing to a stream + // this means we have to disallow the Zend_Http_Response to do it again + if ($this->out_stream) { + $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response); + } + // Else, if we got the content-length header, read this number of bytes + } elseif (isset($headers['content-length'])) { + + // If we got more than one Content-Length header (see ZF-9404) use + // the last value sent + if (is_array($headers['content-length'])) { + $contentLength = $headers['content-length'][count($headers['content-length']) - 1]; + } else { + $contentLength = $headers['content-length']; + } + + $current_pos = ftell($this->socket); + $chunk = ''; + + for ($read_to = $current_pos + $contentLength; + $read_to > $current_pos; + $current_pos = ftell($this->socket)) { + + if($this->out_stream) { + if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { + $this->_checkSocketReadTimeout(); + break; + } + } else { + $chunk = @fread($this->socket, $read_to - $current_pos); + if ($chunk === false || strlen($chunk) === 0) { + $this->_checkSocketReadTimeout(); + break; + } + + $response .= $chunk; + } + + // Break if the connection ended prematurely + if (feof($this->socket)) break; + } + + // Fallback: just read the response until EOF + } else { + + do { + if($this->out_stream) { + if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) { + $this->_checkSocketReadTimeout(); + break; + } + } else { + $buff = @fread($this->socket, 8192); + if ($buff === false || strlen($buff) === 0) { + $this->_checkSocketReadTimeout(); + break; + } else { + $response .= $buff; + } + } + + } while (feof($this->socket) === false); + + $this->close(); + } + + // Close the connection if requested to do so by the server + if (isset($headers['connection']) && $headers['connection'] == 'close') { + $this->close(); + } + + return $response; + } + + /** + * Close the connection to the server + * + */ + public function close() + { + if (is_resource($this->socket)) @fclose($this->socket); + $this->socket = null; + $this->connected_to = array(null, null); + } + + /** + * Check if the socket has timed out - if so close connection and throw + * an exception + * + * @throws Zend_Http_Client_Adapter_Exception with READ_TIMEOUT code + */ + protected function _checkSocketReadTimeout() + { + if ($this->socket) { + $info = stream_get_meta_data($this->socket); + $timedout = $info['timed_out']; + if ($timedout) { + $this->close(); + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + "Read timed out after {$this->config['timeout']} seconds", + Zend_Http_Client_Adapter_Exception::READ_TIMEOUT + ); + } + } + } + + /** + * Set output stream for the response + * + * @param resource $stream + * @return Zend_Http_Client_Adapter_Socket + */ + public function setOutputStream($stream) + { + $this->out_stream = $stream; + return $this; + } + + /** + * Destructor: make sure the socket is disconnected + * + * If we are in persistent TCP mode, will not close the connection + * + */ + public function __destruct() + { + if (! $this->config['persistent']) { + if ($this->socket) $this->close(); + } + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Stream.php b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Stream.php new file mode 100644 index 00000000..58dd921f --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Client/Adapter/Stream.php @@ -0,0 +1,46 @@ +_nextRequestWillFail = (bool) $flag; + + return $this; + } + + /** + * Set the configuration array for the adapter + * + * @param Zend_Config | array $config + */ + public function setConfig($config = array()) + { + if ($config instanceof Zend_Config) { + $config = $config->toArray(); + + } elseif (! is_array($config)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + 'Array or Zend_Config object expected, got ' . gettype($config) + ); + } + + foreach ($config as $k => $v) { + $this->config[strtolower($k)] = $v; + } + } + + + /** + * Connect to the remote server + * + * @param string $host + * @param int $port + * @param boolean $secure + * @param int $timeout + * @throws Zend_Http_Client_Adapter_Exception + */ + public function connect($host, $port = 80, $secure = false) + { + if ($this->_nextRequestWillFail) { + $this->_nextRequestWillFail = false; + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception('Request failed'); + } + } + + /** + * Send request to the remote server + * + * @param string $method + * @param Zend_Uri_Http $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + $host = $uri->getHost(); + $host = (strtolower($uri->getScheme()) == 'https' ? 'sslv2://' . $host : $host); + + // Build request headers + $path = $uri->getPath(); + if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); + $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; + foreach ($headers as $k => $v) { + if (is_string($k)) $v = ucfirst($k) . ": $v"; + $request .= "$v\r\n"; + } + + // Add the request body + $request .= "\r\n" . $body; + + // Do nothing - just return the request as string + + return $request; + } + + /** + * Return the response set in $this->setResponse() + * + * @return string + */ + public function read() + { + if ($this->responseIndex >= count($this->responses)) { + $this->responseIndex = 0; + } + return $this->responses[$this->responseIndex++]; + } + + /** + * Close the connection (dummy) + * + */ + public function close() + { } + + /** + * Set the HTTP response(s) to be returned by this adapter + * + * @param Zend_Http_Response|array|string $response + */ + public function setResponse($response) + { + if ($response instanceof Zend_Http_Response) { + $response = $response->asString("\r\n"); + } + + $this->responses = (array)$response; + $this->responseIndex = 0; + } + + /** + * Add another response to the response buffer. + * + * @param string Zend_Http_Response|$response + */ + public function addResponse($response) + { + if ($response instanceof Zend_Http_Response) { + $response = $response->asString("\r\n"); + } + + $this->responses[] = $response; + } + + /** + * Sets the position of the response buffer. Selects which + * response will be returned on the next call to read(). + * + * @param integer $index + */ + public function setResponseIndex($index) + { + if ($index < 0 || $index >= count($this->responses)) { + // require_once 'Zend/Http/Client/Adapter/Exception.php'; + throw new Zend_Http_Client_Adapter_Exception( + 'Index out of range of response buffer size'); + } + $this->responseIndex = $index; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Client/Exception.php b/oss/piwik/trunk/libs/Zend/Http/Client/Exception.php new file mode 100644 index 00000000..dc35e3a0 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Client/Exception.php @@ -0,0 +1,36 @@ +name = (string) $name) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('Cookies must have a name'); + } + + if (! $this->domain = (string) $domain) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('Cookies must have a domain'); + } + + $this->value = (string) $value; + $this->expires = ($expires === null ? null : (int) $expires); + $this->path = ($path ? $path : '/'); + $this->secure = $secure; + } + + /** + * Get Cookie name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get cookie value + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Get cookie domain + * + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Get the cookie path + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get the expiry time of the cookie, or null if no expiry time is set + * + * @return int|null + */ + public function getExpiryTime() + { + return $this->expires; + } + + /** + * Check whether the cookie should only be sent over secure connections + * + * @return boolean + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Check whether the cookie has expired + * + * Always returns false if the cookie is a session cookie (has no expiry time) + * + * @param int $now Timestamp to consider as "now" + * @return boolean + */ + public function isExpired($now = null) + { + if ($now === null) $now = time(); + if (is_int($this->expires) && $this->expires < $now) { + return true; + } else { + return false; + } + } + + /** + * Check whether the cookie is a session cookie (has no expiry time set) + * + * @return boolean + */ + public function isSessionCookie() + { + return ($this->expires === null); + } + + /** + * Checks whether the cookie should be sent or not in a specific scenario + * + * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path) + * @param boolean $matchSessionCookies Whether to send session cookies + * @param int $now Override the current time when checking for expiry time + * @return boolean + */ + public function match($uri, $matchSessionCookies = true, $now = null) + { + if (is_string ($uri)) { + $uri = Zend_Uri_Http::factory($uri); + } + + // Make sure we have a valid Zend_Uri_Http object + if (! ($uri->valid() && ($uri->getScheme() == 'http' || $uri->getScheme() =='https'))) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('Passed URI is not a valid HTTP or HTTPS URI'); + } + + // Check that the cookie is secure (if required) and not expired + if ($this->secure && $uri->getScheme() != 'https') return false; + if ($this->isExpired($now)) return false; + if ($this->isSessionCookie() && ! $matchSessionCookies) return false; + + // Check if the domain matches + if (! self::matchCookieDomain($this->getDomain(), $uri->getHost())) { + return false; + } + + // Check that path matches using prefix match + if (! self::matchCookiePath($this->getPath(), $uri->getPath())) { + return false; + } + + // If we didn't die until now, return true. + return true; + } + + /** + * Get the cookie as a string, suitable for sending as a "Cookie" header in an + * HTTP request + * + * @return string + */ + public function __toString() + { + if ($this->encodeValue) { + return $this->name . '=' . urlencode($this->value) . ';'; + } + return $this->name . '=' . $this->value . ';'; + } + + /** + * Generate a new Cookie object from a cookie string + * (for example the value of the Set-Cookie HTTP header) + * + * @param string $cookieStr + * @param Zend_Uri_Http|string $refUri Reference URI for default values (domain, path) + * @param boolean $encodeValue Weither or not the cookie's value should be + * passed through urlencode/urldecode + * @return Zend_Http_Cookie A new Zend_Http_Cookie object or false on failure. + */ + public static function fromString($cookieStr, $refUri = null, $encodeValue = true) + { + // Set default values + if (is_string($refUri)) { + $refUri = Zend_Uri_Http::factory($refUri); + } + + $name = ''; + $value = ''; + $domain = ''; + $path = ''; + $expires = null; + $secure = false; + $parts = explode(';', $cookieStr); + + // If first part does not include '=', fail + if (strpos($parts[0], '=') === false) return false; + + // Get the name and value of the cookie + list($name, $value) = explode('=', trim(array_shift($parts)), 2); + $name = trim($name); + if ($encodeValue) { + $value = urldecode(trim($value)); + } + + // Set default domain and path + if ($refUri instanceof Zend_Uri_Http) { + $domain = $refUri->getHost(); + $path = $refUri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + } + + // Set other cookie parameters + foreach ($parts as $part) { + $part = trim($part); + if (strtolower($part) == 'secure') { + $secure = true; + continue; + } + + $keyValue = explode('=', $part, 2); + if (count($keyValue) == 2) { + list($k, $v) = $keyValue; + switch (strtolower($k)) { + case 'expires': + if(($expires = strtotime($v)) === false) { + /** + * The expiration is past Tue, 19 Jan 2038 03:14:07 UTC + * the maximum for 32-bit signed integer. Zend_Date + * can get around that limit. + * + * @see Zend_Date + */ + // require_once 'Zend/Date.php'; + + $expireDate = new Zend_Date($v); + $expires = $expireDate->getTimestamp(); + } + break; + + case 'path': + $path = $v; + break; + + case 'domain': + $domain = $v; + break; + + default: + break; + } + } + } + + if ($name !== '') { + $ret = new self($name, $value, $domain, $expires, $path, $secure); + $ret->encodeValue = ($encodeValue) ? true : false; + return $ret; + } else { + return false; + } + } + + /** + * Check if a cookie's domain matches a host name. + * + * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching + * + * @param string $cookieDomain + * @param string $host + * + * @return boolean + */ + public static function matchCookieDomain($cookieDomain, $host) + { + if (! $cookieDomain) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("\$cookieDomain is expected to be a cookie domain"); + } + + if (! $host) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("\$host is expected to be a host name"); + } + + $cookieDomain = strtolower($cookieDomain); + $host = strtolower($host); + + if ($cookieDomain[0] == '.') { + $cookieDomain = substr($cookieDomain, 1); + } + + // Check for either exact match or suffix match + return ($cookieDomain == $host || + preg_match("/\.$cookieDomain$/", $host)); + } + + /** + * Check if a cookie's path matches a URL path + * + * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching + * + * @param string $cookiePath + * @param string $path + * @return boolean + */ + public static function matchCookiePath($cookiePath, $path) + { + if (! $cookiePath) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("\$cookiePath is expected to be a cookie path"); + } + + if (! $path) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("\$path is expected to be a host name"); + } + + return (strpos($path, $cookiePath) === 0); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/CookieJar.php b/oss/piwik/trunk/libs/Zend/Http/CookieJar.php new file mode 100644 index 00000000..3d00a8e0 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/CookieJar.php @@ -0,0 +1,403 @@ +getDomain(); + $path = $cookie->getPath(); + if (! isset($this->cookies[$domain])) $this->cookies[$domain] = array(); + if (! isset($this->cookies[$domain][$path])) $this->cookies[$domain][$path] = array(); + $this->cookies[$domain][$path][$cookie->getName()] = $cookie; + $this->_rawCookies[] = $cookie; + } else { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('Supplient argument is not a valid cookie string or object'); + } + } + + /** + * Parse an HTTP response, adding all the cookies set in that response + * to the cookie jar. + * + * @param Zend_Http_Response $response + * @param Zend_Uri_Http|string $ref_uri Requested URI + */ + public function addCookiesFromResponse($response, $ref_uri) + { + if (! $response instanceof Zend_Http_Response) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('$response is expected to be a Response object, ' . + gettype($response) . ' was passed'); + } + + $cookie_hdrs = $response->getHeader('Set-Cookie'); + + if (is_array($cookie_hdrs)) { + foreach ($cookie_hdrs as $cookie) { + $this->addCookie($cookie, $ref_uri); + } + } elseif (is_string($cookie_hdrs)) { + $this->addCookie($cookie_hdrs, $ref_uri); + } + } + + /** + * Get all cookies in the cookie jar as an array + * + * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings + * @return array|string + */ + public function getAllCookies($ret_as = self::COOKIE_OBJECT) + { + $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as); + return $cookies; + } + + /** + * Return an array of all cookies matching a specific request according to the request URI, + * whether session cookies should be sent or not, and the time to consider as "now" when + * checking cookie expiry time. + * + * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path) + * @param boolean $matchSessionCookies Whether to send session cookies + * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings + * @param int $now Override the current time when checking for expiry time + * @return array|string + */ + public function getMatchingCookies($uri, $matchSessionCookies = true, + $ret_as = self::COOKIE_OBJECT, $now = null) + { + if (is_string($uri)) $uri = Zend_Uri::factory($uri); + if (! $uri instanceof Zend_Uri_Http) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("Invalid URI string or object passed"); + } + + // First, reduce the array of cookies to only those matching domain and path + $cookies = $this->_matchDomain($uri->getHost()); + $cookies = $this->_matchPath($cookies, $uri->getPath()); + $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT); + + // Next, run Cookie->match on all cookies to check secure, time and session mathcing + $ret = array(); + foreach ($cookies as $cookie) + if ($cookie->match($uri, $matchSessionCookies, $now)) + $ret[] = $cookie; + + // Now, use self::_flattenCookiesArray again - only to convert to the return format ;) + $ret = $this->_flattenCookiesArray($ret, $ret_as); + + return $ret; + } + + /** + * Get a specific cookie according to a URI and name + * + * @param Zend_Uri_Http|string $uri The uri (domain and path) to match + * @param string $cookie_name The cookie's name + * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings + * @return Zend_Http_Cookie|string + */ + public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT) + { + if (is_string($uri)) { + $uri = Zend_Uri::factory($uri); + } + + if (! $uri instanceof Zend_Uri_Http) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('Invalid URI specified'); + } + + // Get correct cookie path + $path = $uri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + if (! $path) $path = '/'; + + if (isset($this->cookies[$uri->getHost()][$path][$cookie_name])) { + $cookie = $this->cookies[$uri->getHost()][$path][$cookie_name]; + + switch ($ret_as) { + case self::COOKIE_OBJECT: + return $cookie; + break; + + case self::COOKIE_STRING_ARRAY: + case self::COOKIE_STRING_CONCAT: + return $cookie->__toString(); + break; + + default: + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("Invalid value passed for \$ret_as: {$ret_as}"); + break; + } + } else { + return false; + } + } + + /** + * Helper function to recursivly flatten an array. Shoud be used when exporting the + * cookies array (or parts of it) + * + * @param Zend_Http_Cookie|array $ptr + * @param int $ret_as What value to return + * @return array|string + */ + protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT) { + if (is_array($ptr)) { + $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? '' : array()); + foreach ($ptr as $item) { + if ($ret_as == self::COOKIE_STRING_CONCAT) { + $ret .= $this->_flattenCookiesArray($item, $ret_as); + } else { + $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as)); + } + } + return $ret; + } elseif ($ptr instanceof Zend_Http_Cookie) { + switch ($ret_as) { + case self::COOKIE_STRING_ARRAY: + return array($ptr->__toString()); + break; + + case self::COOKIE_STRING_CONCAT: + return $ptr->__toString(); + break; + + case self::COOKIE_OBJECT: + default: + return array($ptr); + break; + } + } + + return null; + } + + /** + * Return a subset of the cookies array matching a specific domain + * + * @param string $domain + * @return array + */ + protected function _matchDomain($domain) + { + $ret = array(); + + foreach (array_keys($this->cookies) as $cdom) { + if (Zend_Http_Cookie::matchCookieDomain($cdom, $domain)) { + $ret[$cdom] = $this->cookies[$cdom]; + } + } + + return $ret; + } + + /** + * Return a subset of a domain-matching cookies that also match a specified path + * + * @param array $dom_array + * @param string $path + * @return array + */ + protected function _matchPath($domains, $path) + { + $ret = array(); + + foreach ($domains as $dom => $paths_array) { + foreach (array_keys($paths_array) as $cpath) { + if (Zend_Http_Cookie::matchCookiePath($cpath, $path)) { + if (! isset($ret[$dom])) { + $ret[$dom] = array(); + } + + $ret[$dom][$cpath] = $paths_array[$cpath]; + } + } + } + + return $ret; + } + + /** + * Create a new CookieJar object and automatically load into it all the + * cookies set in an Http_Response object. If $uri is set, it will be + * considered as the requested URI for setting default domain and path + * of the cookie. + * + * @param Zend_Http_Response $response HTTP Response object + * @param Zend_Uri_Http|string $uri The requested URI + * @return Zend_Http_CookieJar + * @todo Add the $uri functionality. + */ + public static function fromResponse(Zend_Http_Response $response, $ref_uri) + { + $jar = new self(); + $jar->addCookiesFromResponse($response, $ref_uri); + return $jar; + } + + /** + * Required by Countable interface + * + * @return int + */ + public function count() + { + return count($this->_rawCookies); + } + + /** + * Required by IteratorAggregate interface + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->_rawCookies); + } + + /** + * Tells if the jar is empty of any cookie + * + * @return bool + */ + public function isEmpty() + { + return count($this) == 0; + } + + /** + * Empties the cookieJar of any cookie + * + * @return Zend_Http_CookieJar + */ + public function reset() + { + $this->cookies = $this->_rawCookies = array(); + return $this; + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Exception.php b/oss/piwik/trunk/libs/Zend/Http/Exception.php new file mode 100644 index 00000000..230c9c0a --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Exception.php @@ -0,0 +1,36 @@ + 'Continue', + 101 => 'Switching Protocols', + + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ); + + /** + * The HTTP version (1.0, 1.1) + * + * @var string + */ + protected $version; + + /** + * The HTTP response code + * + * @var int + */ + protected $code; + + /** + * The HTTP response code as string + * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500) + * + * @var string + */ + protected $message; + + /** + * The HTTP response headers array + * + * @var array + */ + protected $headers = array(); + + /** + * The HTTP response body + * + * @var string + */ + protected $body; + + /** + * HTTP response constructor + * + * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP + * response string and create a new Zend_Http_Response object. + * + * NOTE: The constructor no longer accepts nulls or empty values for the code and + * headers and will throw an exception if the passed values do not form a valid HTTP + * responses. + * + * If no message is passed, the message will be guessed according to the response code. + * + * @param int $code Response code (200, 404, ...) + * @param array $headers Headers array + * @param string $body Response body + * @param string $version HTTP version + * @param string $message Response code as text + * @throws Zend_Http_Exception + */ + public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) + { + // Make sure the response code is valid and set it + if (self::responseCodeAsText($code) === null) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("{$code} is not a valid HTTP response code"); + } + + $this->code = $code; + + // Make sure we got valid headers and set them + if (! is_array($headers)) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception('No valid headers were passed'); + } + + foreach ($headers as $name => $value) { + if (is_int($name)) + list($name, $value) = explode(": ", $value, 1); + + $this->headers[ucwords(strtolower($name))] = $value; + } + + // Set the body + $this->body = $body; + + // Set the HTTP version + if (! preg_match('|^\d\.\d$|', $version)) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("Invalid HTTP response version: $version"); + } + + $this->version = $version; + + // If we got the response message, set it. Else, set it according to + // the response code + if (is_string($message)) { + $this->message = $message; + } else { + $this->message = self::responseCodeAsText($code); + } + } + + /** + * Check whether the response is an error + * + * @return boolean + */ + public function isError() + { + $restype = floor($this->code / 100); + if ($restype == 4 || $restype == 5) { + return true; + } + + return false; + } + + /** + * Check whether the response in successful + * + * @return boolean + */ + public function isSuccessful() + { + $restype = floor($this->code / 100); + if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ??? + return true; + } + + return false; + } + + /** + * Check whether the response is a redirection + * + * @return boolean + */ + public function isRedirect() + { + $restype = floor($this->code / 100); + if ($restype == 3) { + return true; + } + + return false; + } + + /** + * Get the response body as string + * + * This method returns the body of the HTTP response (the content), as it + * should be in it's readable version - that is, after decoding it (if it + * was decoded), deflating it (if it was gzip compressed), etc. + * + * If you want to get the raw body (as transfered on wire) use + * $this->getRawBody() instead. + * + * @return string + */ + public function getBody() + { + $body = ''; + + // Decode the body if it was transfer-encoded + switch (strtolower($this->getHeader('transfer-encoding'))) { + + // Handle chunked body + case 'chunked': + $body = self::decodeChunkedBody($this->body); + break; + + // No transfer encoding, or unknown encoding extension: + // return body as is + default: + $body = $this->body; + break; + } + + // Decode any content-encoding (gzip or deflate) if needed + switch (strtolower($this->getHeader('content-encoding'))) { + + // Handle gzip encoding + case 'gzip': + $body = self::decodeGzip($body); + break; + + // Handle deflate encoding + case 'deflate': + $body = self::decodeDeflate($body); + break; + + default: + break; + } + + return $body; + } + + /** + * Get the raw response body (as transfered "on wire") as string + * + * If the body is encoded (with Transfer-Encoding, not content-encoding - + * IE "chunked" body), gzip compressed, etc. it will not be decoded. + * + * @return string + */ + public function getRawBody() + { + return $this->body; + } + + /** + * Get the HTTP version of the response + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Get the HTTP response status code + * + * @return int + */ + public function getStatus() + { + return $this->code; + } + + /** + * Return a message describing the HTTP response code + * (Eg. "OK", "Not Found", "Moved Permanently") + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Get the response headers + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Get a specific header as string, or null if it is not set + * + * @param string$header + * @return string|array|null + */ + public function getHeader($header) + { + $header = ucwords(strtolower($header)); + if (! is_string($header) || ! isset($this->headers[$header])) return null; + + return $this->headers[$header]; + } + + /** + * Get all headers as string + * + * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK") + * @param string $br Line breaks (eg. "\n", "\r\n", "
") + * @return string + */ + public function getHeadersAsString($status_line = true, $br = "\n") + { + $str = ''; + + if ($status_line) { + $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}"; + } + + // Iterate over the headers and stringify them + foreach ($this->headers as $name => $value) + { + if (is_string($value)) + $str .= "{$name}: {$value}{$br}"; + + elseif (is_array($value)) { + foreach ($value as $subval) { + $str .= "{$name}: {$subval}{$br}"; + } + } + } + + return $str; + } + + /** + * Get the entire response as string + * + * @param string $br Line breaks (eg. "\n", "\r\n", "
") + * @return string + */ + public function asString($br = "\n") + { + return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody(); + } + + /** + * Implements magic __toString() + * + * @return string + */ + public function __toString() + { + return $this->asString(); + } + + /** + * A convenience function that returns a text representation of + * HTTP response codes. Returns 'Unknown' for unknown codes. + * Returns array of all codes, if $code is not specified. + * + * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown') + * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference + * + * @param int $code HTTP response code + * @param boolean $http11 Use HTTP version 1.1 + * @return string + */ + public static function responseCodeAsText($code = null, $http11 = true) + { + $messages = self::$messages; + if (! $http11) $messages[302] = 'Moved Temporarily'; + + if ($code === null) { + return $messages; + } elseif (isset($messages[$code])) { + return $messages[$code]; + } else { + return 'Unknown'; + } + } + + /** + * Extract the response code from a response string + * + * @param string $response_str + * @return int + */ + public static function extractCode($response_str) + { + preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m); + + if (isset($m[1])) { + return (int) $m[1]; + } else { + return false; + } + } + + /** + * Extract the HTTP message from a response + * + * @param string $response_str + * @return string + */ + public static function extractMessage($response_str) + { + preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m); + + if (isset($m[1])) { + return $m[1]; + } else { + return false; + } + } + + /** + * Extract the HTTP version from a response + * + * @param string $response_str + * @return string + */ + public static function extractVersion($response_str) + { + preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m); + + if (isset($m[1])) { + return $m[1]; + } else { + return false; + } + } + + /** + * Extract the headers from a response string + * + * @param string $response_str + * @return array + */ + public static function extractHeaders($response_str) + { + $headers = array(); + + // First, split body and headers + $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2); + if (! $parts[0]) return $headers; + + // Split headers part to lines + $lines = explode("\n", $parts[0]); + unset($parts); + $last_header = null; + + foreach($lines as $line) { + $line = trim($line, "\r\n"); + if ($line == "") break; + + if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) { + unset($last_header); + $h_name = strtolower($m[1]); + $h_value = $m[2]; + + if (isset($headers[$h_name])) { + if (! is_array($headers[$h_name])) { + $headers[$h_name] = array($headers[$h_name]); + } + + $headers[$h_name][] = $h_value; + } else { + $headers[$h_name] = $h_value; + } + $last_header = $h_name; + } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) { + if (is_array($headers[$last_header])) { + end($headers[$last_header]); + $last_header_key = key($headers[$last_header]); + $headers[$last_header][$last_header_key] .= $m[1]; + } else { + $headers[$last_header] .= $m[1]; + } + } + } + + return $headers; + } + + /** + * Extract the body from a response string + * + * @param string $response_str + * @return string + */ + public static function extractBody($response_str) + { + $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2); + if (isset($parts[1])) { + return $parts[1]; + } + return ''; + } + + /** + * Decode a "chunked" transfer-encoded body and return the decoded text + * + * @param string $body + * @return string + */ + public static function decodeChunkedBody($body) + { + $decBody = ''; + + // If mbstring overloads substr and strlen functions, we have to + // override it's internal encoding + if (function_exists('mb_internal_encoding') && + ((int) ini_get('mbstring.func_overload')) & 2) { + + $mbIntEnc = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + while (trim($body)) { + if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message"); + } + + $length = hexdec(trim($m[1])); + $cut = strlen($m[0]); + $decBody .= substr($body, $cut, $length); + $body = substr($body, $cut + $length + 2); + } + + if (isset($mbIntEnc)) { + mb_internal_encoding($mbIntEnc); + } + + return $decBody; + } + + /** + * Decode a gzip encoded message (when Content-encoding = gzip) + * + * Currently requires PHP with zlib support + * + * @param string $body + * @return string + */ + public static function decodeGzip($body) + { + if (! function_exists('gzinflate')) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception( + 'zlib extension is required in order to decode "gzip" encoding' + ); + } + + return gzinflate(substr($body, 10)); + } + + /** + * Decode a zlib deflated message (when Content-encoding = deflate) + * + * Currently requires PHP with zlib support + * + * @param string $body + * @return string + */ + public static function decodeDeflate($body) + { + if (! function_exists('gzuncompress')) { + // require_once 'Zend/Http/Exception.php'; + throw new Zend_Http_Exception( + 'zlib extension is required in order to decode "deflate" encoding' + ); + } + + /** + * Some servers (IIS ?) send a broken deflate response, without the + * RFC-required zlib header. + * + * We try to detect the zlib header, and if it does not exsit we + * teat the body is plain DEFLATE content. + * + * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov + * + * @link http://framework.zend.com/issues/browse/ZF-6040 + */ + $zlibHeader = unpack('n', substr($body, 0, 2)); + if ($zlibHeader[1] % 31 == 0) { + return gzuncompress($body); + } else { + return gzinflate($body); + } + } + + /** + * Create a new Zend_Http_Response object from a string + * + * @param string $response_str + * @return Zend_Http_Response + */ + public static function fromString($response_str) + { + $code = self::extractCode($response_str); + $headers = self::extractHeaders($response_str); + $body = self::extractBody($response_str); + $version = self::extractVersion($response_str); + $message = self::extractMessage($response_str); + + return new Zend_Http_Response($code, $headers, $body, $version, $message); + } +} diff --git a/oss/piwik/trunk/libs/Zend/Http/Response/Stream.php b/oss/piwik/trunk/libs/Zend/Http/Response/Stream.php new file mode 100644 index 00000000..667ca321 --- /dev/null +++ b/oss/piwik/trunk/libs/Zend/Http/Response/Stream.php @@ -0,0 +1,235 @@ +stream; + } + + /** + * Set the response stream + * + * @param resourse $stream + * @return Zend_Http_Response_Stream + */ + public function setStream($stream) + { + $this->stream = $stream; + return $this; + } + + /** + * Get the cleanup trigger + * + * @return boolean + */ + public function getCleanup() { + return $this->_cleanup; + } + + /** + * Set the cleanup trigger + * + * @param $cleanup Set cleanup trigger + */ + public function setCleanup($cleanup = true) { + $this->_cleanup = $cleanup; + } + + /** + * Get file name associated with the stream + * + * @return string + */ + public function getStreamName() { + return $this->stream_name; + } + + /** + * Set file name associated with the stream + * + * @param string $stream_name Name to set + * @return Zend_Http_Response_Stream + */ + public function setStreamName($stream_name) { + $this->stream_name = $stream_name; + return $this; + } + + + /** + * HTTP response constructor + * + * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP + * response string and create a new Zend_Http_Response object. + * + * NOTE: The constructor no longer accepts nulls or empty values for the code and + * headers and will throw an exception if the passed values do not form a valid HTTP + * responses. + * + * If no message is passed, the message will be guessed according to the response code. + * + * @param int $code Response code (200, 404, ...) + * @param array $headers Headers array + * @param string $body Response body + * @param string $version HTTP version + * @param string $message Response code as text + * @throws Zend_Http_Exception + */ + public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) + { + + if(is_resource($body)) { + $this->setStream($body); + $body = ''; + } + parent::__construct($code, $headers, $body, $version, $message); + } + + /** + * Create a new Zend_Http_Response_Stream object from a string + * + * @param string $response_str + * @param resource $stream + * @return Zend_Http_Response_Stream + */ + public static function fromStream($response_str, $stream) + { + $code = self::extractCode($response_str); + $headers = self::extractHeaders($response_str); + $version = self::extractVersion($response_str); + $message = self::extractMessage($response_str); + + return new self($code, $headers, $stream, $version, $message); + } + + /** + * Get the response body as string + * + * This method returns the body of the HTTP response (the content), as it + * should be in it's readable version - that is, after decoding it (if it + * was decoded), deflating it (if it was gzip compressed), etc. + * + * If you want to get the raw body (as transfered on wire) use + * $this->getRawBody() instead. + * + * @return string + */ + public function getBody() + { + if($this->stream != null) { + $this->readStream(); + } + return parent::getBody(); + } + + /** + * Get the raw response body (as transfered "on wire") as string + * + * If the body is encoded (with Transfer-Encoding, not content-encoding - + * IE "chunked" body), gzip compressed, etc. it will not be decoded. + * + * @return string + */ + public function getRawBody() + { + if($this->stream) { + $this->readStream(); + } + return $this->body; + } + + /** + * Read stream content and return it as string + * + * Function reads the remainder of the body from the stream and closes the stream. + * + * @return string + */ + protected function readStream() + { + if(!is_resource($this->stream)) { + return ''; + } + + if(isset($headers['content-length'])) { + $this->body = stream_get_contents($this->stream, $headers['content-length']); + } else { + $this->body = stream_get_contents($this->stream); + } + fclose($this->stream); + $this->stream = null; + } + + public function __destruct() + { + if(is_resource($this->stream)) { + fclose($this->stream); + $this->stream = null; + } + if($this->_cleanup) { + @unlink($this->stream_name); + } + } + +}