TYPO3 API  SVNRelease
XRDS.php
Go to the documentation of this file.
00001 <?php
00002 
00003 /**
00004  * This module contains the XRDS parsing code.
00005  *
00006  * PHP versions 4 and 5
00007  *
00008  * LICENSE: See the COPYING file included in this distribution.
00009  *
00010  * @package OpenID
00011  * @author JanRain, Inc. <openid@janrain.com>
00012  * @copyright 2005-2008 Janrain, Inc.
00013  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
00014  */
00015 
00016 /**
00017  * Require the XPath implementation.
00018  */
00019 require_once 'Auth/Yadis/XML.php';
00020 
00021 /**
00022  * This match mode means a given service must match ALL filters passed
00023  * to the Auth_Yadis_XRDS::services() call.
00024  */
00025 define('SERVICES_YADIS_MATCH_ALL', 101);
00026 
00027 /**
00028  * This match mode means a given service must match ANY filters (at
00029  * least one) passed to the Auth_Yadis_XRDS::services() call.
00030  */
00031 define('SERVICES_YADIS_MATCH_ANY', 102);
00032 
00033 /**
00034  * The priority value used for service elements with no priority
00035  * specified.
00036  */
00037 define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
00038 
00039 /**
00040  * XRD XML namespace
00041  */
00042 define('Auth_Yadis_XMLNS_XRD_2_0', 'xri://$xrd*($v*2.0)');
00043 
00044 /**
00045  * XRDS XML namespace
00046  */
00047 define('Auth_Yadis_XMLNS_XRDS', 'xri://$xrds');
00048 
00049 function Auth_Yadis_getNSMap()
00050 {
00051     return array('xrds' => Auth_Yadis_XMLNS_XRDS,
00052                  'xrd' => Auth_Yadis_XMLNS_XRD_2_0);
00053 }
00054 
00055 /**
00056  * @access private
00057  */
00058 function Auth_Yadis_array_scramble($arr)
00059 {
00060     $result = array();
00061 
00062     while (count($arr)) {
00063         $index = array_rand($arr, 1);
00064         $result[] = $arr[$index];
00065         unset($arr[$index]);
00066     }
00067 
00068     return $result;
00069 }
00070 
00071 /**
00072  * This class represents a <Service> element in an XRDS document.
00073  * Objects of this type are returned by
00074  * Auth_Yadis_XRDS::services() and
00075  * Auth_Yadis_Yadis::services().  Each object corresponds directly
00076  * to a <Service> element in the XRDS and supplies a
00077  * getElements($name) method which you should use to inspect the
00078  * element's contents.  See {@link Auth_Yadis_Yadis} for more
00079  * information on the role this class plays in Yadis discovery.
00080  *
00081  * @package OpenID
00082  */
00083 class Auth_Yadis_Service {
00084 
00085     /**
00086      * Creates an empty service object.
00087      */
00088     function Auth_Yadis_Service()
00089     {
00090         $this->element = null;
00091         $this->parser = null;
00092     }
00093 
00094     /**
00095      * Return the URIs in the "Type" elements, if any, of this Service
00096      * element.
00097      *
00098      * @return array $type_uris An array of Type URI strings.
00099      */
00100     function getTypes()
00101     {
00102         $t = array();
00103         foreach ($this->getElements('xrd:Type') as $elem) {
00104             $c = $this->parser->content($elem);
00105             if ($c) {
00106                 $t[] = $c;
00107             }
00108         }
00109         return $t;
00110     }
00111 
00112     function matchTypes($type_uris)
00113     {
00114         $result = array();
00115 
00116         foreach ($this->getTypes() as $typ) {
00117             if (in_array($typ, $type_uris)) {
00118                 $result[] = $typ;
00119             }
00120         }
00121 
00122         return $result;
00123     }
00124 
00125     /**
00126      * Return the URIs in the "URI" elements, if any, of this Service
00127      * element.  The URIs are returned sorted in priority order.
00128      *
00129      * @return array $uris An array of URI strings.
00130      */
00131     function getURIs()
00132     {
00133         $uris = array();
00134         $last = array();
00135 
00136         foreach ($this->getElements('xrd:URI') as $elem) {
00137             $uri_string = $this->parser->content($elem);
00138             $attrs = $this->parser->attributes($elem);
00139             if ($attrs &&
00140                 array_key_exists('priority', $attrs)) {
00141                 $priority = intval($attrs['priority']);
00142                 if (!array_key_exists($priority, $uris)) {
00143                     $uris[$priority] = array();
00144                 }
00145 
00146                 $uris[$priority][] = $uri_string;
00147             } else {
00148                 $last[] = $uri_string;
00149             }
00150         }
00151 
00152         $keys = array_keys($uris);
00153         sort($keys);
00154 
00155         // Rebuild array of URIs.
00156         $result = array();
00157         foreach ($keys as $k) {
00158             $new_uris = Auth_Yadis_array_scramble($uris[$k]);
00159             $result = array_merge($result, $new_uris);
00160         }
00161 
00162         $result = array_merge($result,
00163                               Auth_Yadis_array_scramble($last));
00164 
00165         return $result;
00166     }
00167 
00168     /**
00169      * Returns the "priority" attribute value of this <Service>
00170      * element, if the attribute is present.  Returns null if not.
00171      *
00172      * @return mixed $result Null or integer, depending on whether
00173      * this Service element has a 'priority' attribute.
00174      */
00175     function getPriority()
00176     {
00177         $attributes = $this->parser->attributes($this->element);
00178 
00179         if (array_key_exists('priority', $attributes)) {
00180             return intval($attributes['priority']);
00181         }
00182 
00183         return null;
00184     }
00185 
00186     /**
00187      * Used to get XML elements from this object's <Service> element.
00188      *
00189      * This is what you should use to get all custom information out
00190      * of this element. This is used by service filter functions to
00191      * determine whether a service element contains specific tags,
00192      * etc.  NOTE: this only considers elements which are direct
00193      * children of the <Service> element for this object.
00194      *
00195      * @param string $name The name of the element to look for
00196      * @return array $list An array of elements with the specified
00197      * name which are direct children of the <Service> element.  The
00198      * nodes returned by this function can be passed to $this->parser
00199      * methods (see {@link Auth_Yadis_XMLParser}).
00200      */
00201     function getElements($name)
00202     {
00203         return $this->parser->evalXPath($name, $this->element);
00204     }
00205 }
00206 
00207 /*
00208  * Return the expiration date of this XRD element, or None if no
00209  * expiration was specified.
00210  *
00211  * @param $default The value to use as the expiration if no expiration
00212  * was specified in the XRD.
00213  */
00214 function Auth_Yadis_getXRDExpiration($xrd_element, $default=null)
00215 {
00216     $expires_element = $xrd_element->$parser->evalXPath('/xrd:Expires');
00217     if ($expires_element === null) {
00218         return $default;
00219     } else {
00220         $expires_string = $expires_element->text;
00221 
00222         // Will raise ValueError if the string is not the expected
00223         // format
00224         $t = strptime($expires_string, "%Y-%m-%dT%H:%M:%SZ");
00225 
00226         if ($t === false) {
00227             return false;
00228         }
00229 
00230         // [int $hour [, int $minute [, int $second [,
00231         //  int $month [, int $day [, int $year ]]]]]]
00232         return mktime($t['tm_hour'], $t['tm_min'], $t['tm_sec'],
00233                       $t['tm_mon'], $t['tm_day'], $t['tm_year']);
00234     }
00235 }
00236 
00237 /**
00238  * This class performs parsing of XRDS documents.
00239  *
00240  * You should not instantiate this class directly; rather, call
00241  * parseXRDS statically:
00242  *
00243  * <pre>  $xrds = Auth_Yadis_XRDS::parseXRDS($xml_string);</pre>
00244  *
00245  * If the XRDS can be parsed and is valid, an instance of
00246  * Auth_Yadis_XRDS will be returned.  Otherwise, null will be
00247  * returned.  This class is used by the Auth_Yadis_Yadis::discover
00248  * method.
00249  *
00250  * @package OpenID
00251  */
00252 class Auth_Yadis_XRDS {
00253 
00254     /**
00255      * Instantiate a Auth_Yadis_XRDS object.  Requires an XPath
00256      * instance which has been used to parse a valid XRDS document.
00257      */
00258     function Auth_Yadis_XRDS($xmlParser, $xrdNodes)
00259     {
00260         $this->parser =& $xmlParser;
00261         $this->xrdNode = $xrdNodes[count($xrdNodes) - 1];
00262         $this->allXrdNodes =& $xrdNodes;
00263         $this->serviceList = array();
00264         $this->_parse();
00265     }
00266 
00267     /**
00268      * Parse an XML string (XRDS document) and return either a
00269      * Auth_Yadis_XRDS object or null, depending on whether the
00270      * XRDS XML is valid.
00271      *
00272      * @param string $xml_string An XRDS XML string.
00273      * @return mixed $xrds An instance of Auth_Yadis_XRDS or null,
00274      * depending on the validity of $xml_string
00275      */
00276     function &parseXRDS($xml_string, $extra_ns_map = null)
00277     {
00278         $_null = null;
00279 
00280         if (!$xml_string) {
00281             return $_null;
00282         }
00283 
00284         $parser = Auth_Yadis_getXMLParser();
00285 
00286         $ns_map = Auth_Yadis_getNSMap();
00287 
00288         if ($extra_ns_map && is_array($extra_ns_map)) {
00289             $ns_map = array_merge($ns_map, $extra_ns_map);
00290         }
00291 
00292         if (!($parser && $parser->init($xml_string, $ns_map))) {
00293             return $_null;
00294         }
00295 
00296         // Try to get root element.
00297         $root = $parser->evalXPath('/xrds:XRDS[1]');
00298         if (!$root) {
00299             return $_null;
00300         }
00301 
00302         if (is_array($root)) {
00303             $root = $root[0];
00304         }
00305 
00306         $attrs = $parser->attributes($root);
00307 
00308         if (array_key_exists('xmlns:xrd', $attrs) &&
00309             $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
00310             return $_null;
00311         } else if (array_key_exists('xmlns', $attrs) &&
00312                    preg_match('/xri/', $attrs['xmlns']) &&
00313                    $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
00314             return $_null;
00315         }
00316 
00317         // Get the last XRD node.
00318         $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
00319 
00320         if (!$xrd_nodes) {
00321             return $_null;
00322         }
00323 
00324         $xrds = new Auth_Yadis_XRDS($parser, $xrd_nodes);
00325         return $xrds;
00326     }
00327 
00328     /**
00329      * @access private
00330      */
00331     function _addService($priority, $service)
00332     {
00333         $priority = intval($priority);
00334 
00335         if (!array_key_exists($priority, $this->serviceList)) {
00336             $this->serviceList[$priority] = array();
00337         }
00338 
00339         $this->serviceList[$priority][] = $service;
00340     }
00341 
00342     /**
00343      * Creates the service list using nodes from the XRDS XML
00344      * document.
00345      *
00346      * @access private
00347      */
00348     function _parse()
00349     {
00350         $this->serviceList = array();
00351 
00352         $services = $this->parser->evalXPath('xrd:Service', $this->xrdNode);
00353 
00354         foreach ($services as $node) {
00355             $s = new Auth_Yadis_Service();
00356             $s->element = $node;
00357             $s->parser =& $this->parser;
00358 
00359             $priority = $s->getPriority();
00360 
00361             if ($priority === null) {
00362                 $priority = SERVICES_YADIS_MAX_PRIORITY;
00363             }
00364 
00365             $this->_addService($priority, $s);
00366         }
00367     }
00368 
00369     /**
00370      * Returns a list of service objects which correspond to <Service>
00371      * elements in the XRDS XML document for this object.
00372      *
00373      * Optionally, an array of filter callbacks may be given to limit
00374      * the list of returned service objects.  Furthermore, the default
00375      * mode is to return all service objects which match ANY of the
00376      * specified filters, but $filter_mode may be
00377      * SERVICES_YADIS_MATCH_ALL if you want to be sure that the
00378      * returned services match all the given filters.  See {@link
00379      * Auth_Yadis_Yadis} for detailed usage information on filter
00380      * functions.
00381      *
00382      * @param mixed $filters An array of callbacks to filter the
00383      * returned services, or null if all services are to be returned.
00384      * @param integer $filter_mode SERVICES_YADIS_MATCH_ALL or
00385      * SERVICES_YADIS_MATCH_ANY, depending on whether the returned
00386      * services should match ALL or ANY of the specified filters,
00387      * respectively.
00388      * @return mixed $services An array of {@link
00389      * Auth_Yadis_Service} objects if $filter_mode is a valid
00390      * mode; null if $filter_mode is an invalid mode (i.e., not
00391      * SERVICES_YADIS_MATCH_ANY or SERVICES_YADIS_MATCH_ALL).
00392      */
00393     function services($filters = null,
00394                       $filter_mode = SERVICES_YADIS_MATCH_ANY)
00395     {
00396 
00397         $pri_keys = array_keys($this->serviceList);
00398         sort($pri_keys, SORT_NUMERIC);
00399 
00400         // If no filters are specified, return the entire service
00401         // list, ordered by priority.
00402         if (!$filters ||
00403             (!is_array($filters))) {
00404 
00405             $result = array();
00406             foreach ($pri_keys as $pri) {
00407                 $result = array_merge($result, $this->serviceList[$pri]);
00408             }
00409 
00410             return $result;
00411         }
00412 
00413         // If a bad filter mode is specified, return null.
00414         if (!in_array($filter_mode, array(SERVICES_YADIS_MATCH_ANY,
00415                                           SERVICES_YADIS_MATCH_ALL))) {
00416             return null;
00417         }
00418 
00419         // Otherwise, use the callbacks in the filter list to
00420         // determine which services are returned.
00421         $filtered = array();
00422 
00423         foreach ($pri_keys as $priority_value) {
00424             $service_obj_list = $this->serviceList[$priority_value];
00425 
00426             foreach ($service_obj_list as $service) {
00427 
00428                 $matches = 0;
00429 
00430                 foreach ($filters as $filter) {
00431                     if (call_user_func_array($filter, array(&$service))) {
00432                         $matches++;
00433 
00434                         if ($filter_mode == SERVICES_YADIS_MATCH_ANY) {
00435                             $pri = $service->getPriority();
00436                             if ($pri === null) {
00437                                 $pri = SERVICES_YADIS_MAX_PRIORITY;
00438                             }
00439 
00440                             if (!array_key_exists($pri, $filtered)) {
00441                                 $filtered[$pri] = array();
00442                             }
00443 
00444                             $filtered[$pri][] = $service;
00445                             break;
00446                         }
00447                     }
00448                 }
00449 
00450                 if (($filter_mode == SERVICES_YADIS_MATCH_ALL) &&
00451                     ($matches == count($filters))) {
00452 
00453                     $pri = $service->getPriority();
00454                     if ($pri === null) {
00455                         $pri = SERVICES_YADIS_MAX_PRIORITY;
00456                     }
00457 
00458                     if (!array_key_exists($pri, $filtered)) {
00459                         $filtered[$pri] = array();
00460                     }
00461                     $filtered[$pri][] = $service;
00462                 }
00463             }
00464         }
00465 
00466         $pri_keys = array_keys($filtered);
00467         sort($pri_keys, SORT_NUMERIC);
00468 
00469         $result = array();
00470         foreach ($pri_keys as $pri) {
00471             $result = array_merge($result, $filtered[$pri]);
00472         }
00473 
00474         return $result;
00475     }
00476 }
00477 
00478 ?>