1<?php 2/** 3 * Implementation of the Yadis Specification 1.0 protocol for service 4 * discovery from an Identity URI/XRI or other. 5 * 6 * PHP version 5 7 * 8 * LICENSE: 9 * 10 * Copyright (c) 2007 P�draic Brady <padraic.brady@yahoo.com> 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * * Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * * Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * * The name of the author may not be used to endorse or promote products 23 * derived from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 26 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 33 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @category Services 38 * @package Services_Yadis 39 * @author P�draic Brady (http://blog.astrumfutura.com) 40 * @license http://opensource.org/licenses/bsd-license.php New BSD License 41 * @version $Id$ 42 */ 43 44/** Services_Yadis_Xrds_Namespace */ 45require_once 'Services/Yadis/Xrds/Namespace.php'; 46 47/** 48 * The Services_Yadis_Xrds class is a wrapper for elements of an 49 * XRD document which is parsed using SimpleXML, and contains methods for 50 * retrieving data about the document. The concrete aspects of retrieving 51 * specific data elements is left to a concrete subclass. 52 * 53 * @category Services 54 * @package Services_Yadis 55 * @subpackage Yadis 56 * @author P�draic Brady (http://blog.astrumfutura.com) 57 * @license http://opensource.org/licenses/bsd-license.php New BSD License 58 */ 59class Services_Yadis_Xrds 60{ 61 62 /** 63 * Current key/pointer for the Iterator 64 * 65 * @var integer 66 */ 67 protected $_currentKey = 0; 68 69 /** 70 * Contains the valid xrd:XRD nodes parsed from the XRD document. 71 * 72 * @var SimpleXMLElement 73 */ 74 protected $_xrdNodes = null; 75 76 /** 77 * Instance of Services_Yadis_Xrds_Namespace for managing namespaces 78 * associated with an XRDS document. 79 * 80 * @var Services_Yadis_Xrds_Namespace 81 */ 82 protected $_namespace = null; 83 84 /** 85 * Constructor; parses and validates an XRD document. All access to 86 * the data held in the XML is left to a concrete subclass specific to 87 * expected XRD format and data types. 88 * Cannot be directly instantiated; must call from subclass. 89 * 90 * @param SimpleXMLElement $xrds 91 * @param Services_Yadis_Xrds_Namespace $namespace 92 */ 93 protected function __construct(SimpleXMLElement $xrds, Services_Yadis_Xrds_Namespace $namespace) 94 { 95 $this->_namespace = $namespace; 96 $xrdNodes = $this->_getValidXrdNodes($xrds); 97 if (!$xrdNodes) { 98 require_once 'Services/Yadis/Exception.php'; 99 throw new Services_Yadis_Exception('The XRD document was found to be invalid'); 100 } 101 $this->_xrdNodes = $xrdNodes; 102 } 103 104 /** 105 * Add a list (array) of additional namespaces to be utilised by the XML 106 * parser when it receives a valid XRD document. 107 * 108 * @param array $namespaces 109 * @return Services_Yadis 110 */ 111 public function addNamespaces(array $namespaces) 112 { 113 $this->_namespace->addNamespaces($namespaces); 114 return $this; 115 } 116 117 /** 118 * Add a single namespace to be utilised by the XML parser when it receives 119 * a valid XRD document. 120 * 121 * @param string $namespace 122 * @param string $namespaceUrl 123 * @return Services_Yadis 124 */ 125 public function addNamespace($namespace, $namespaceUrl) 126 { 127 $this->_namespace->addNamespace($namespace, $namespaceUrl); 128 return $this; 129 } 130 131 /** 132 * Return the value of a specific namespace. 133 * 134 * @return string|boolean 135 */ 136 public function getNamespace($namespace) 137 { 138 return $this->_namespace->getNamespace($namespace); 139 } 140 141 /** 142 * Returns an array of all currently set namespaces. 143 * 144 * @return array 145 */ 146 public function getNamespaces() 147 { 148 return $this->_namespace->getNamespaces(); 149 } 150 151 /** 152 * Returns an array of all xrd elements located in the XRD document. 153 * 154 * @param SimpleXMLElement 155 * @return array 156 */ 157 protected function _getValidXrdNodes(SimpleXMLElement $xrds) 158 { 159 /** 160 * Register all namespaces to this SimpleXMLElement. 161 */ 162 $this->_namespace->registerXpathNamespaces($xrds); 163 164 /** 165 * Verify the XRDS resource has a root element called "xrds:XRDS". 166 */ 167 $root = $xrds->xpath('/xrds:XRDS[1]'); 168 if (count($root) == 0) { 169 return null; 170 } 171 172 /** 173 * Check namespace urls of standard xmlns (no suffix) or xmlns:xrd 174 * (if present and of priority) for validity. 175 * No loss if neither exists, but they really should be. 176 */ 177 $namespaces = $xrds->getDocNamespaces(); 178 if (array_key_exists('xrd', $namespaces) && $namespaces['xrd'] != 'xri://$xrd*($v*2.0)') { 179 return null; 180 } elseif (array_key_exists('', $namespaces) && $namespaces[''] != 'xri://$xrd*($v*2.0)') { 181 // Hack for the namespace declaration in the XRD node, which SimpleXML misses 182 $xrdHack = false; 183 if (!isset($xrds->XRD)) { 184 return null; 185 } 186 187 foreach ($xrds->XRD as $xrd) { 188 $namespaces = $xrd->getNamespaces(); 189 if (array_key_exists('', $namespaces) 190 && $namespaces[''] == 'xri://$xrd*($v*2.0)') { 191 192 $xrdHack = true; 193 break; 194 } 195 } 196 197 if (!$xrdHack) { 198 return null; 199 } 200 } 201 202 /** 203 * Grab the XRD elements which contains details of the service provider's 204 * Server url, service types, and other details. Concrete subclass may 205 * have additional requirements concerning node priority or valid position 206 * in relation to other nodes. E.g. Yadis requires only using the *last* 207 * node. 208 */ 209 $xrdNodes = $xrds->xpath('/xrds:XRDS[1]/xrd:XRD'); 210 if (!$xrdNodes) { 211 return null; 212 } 213 return $xrdNodes; 214 } 215 216 /** 217 * Order an array of elements by priority. This assumes an array form of: 218 * $array[$priority] = <array of elements with equal priority> 219 * Where multiple elements are assigned to a priority, their order in the 220 * priority array should be made random. After ordering, the array is 221 * flattened to a single array of elements for iteration. 222 * 223 * @param array $unsorted 224 * @return array 225 */ 226 public static function sortByPriority(array $unsorted) 227 { 228 ksort($unsorted); 229 $flattened = array(); 230 foreach ($unsorted as $priority) { 231 if (count($priority) > 1) { 232 shuffle($priority); 233 $flattened = array_merge($flattened, $priority); 234 } else { 235 $flattened[] = $priority[0]; 236 } 237 } 238 return $flattened; 239 } 240 241} 242