* @copyright Copyright (C) 2010, Muze * @license http://opensource.org/licenses/gpl-3.0.html GNU Public License * @version Ripcord 0.9 - PHP 5 */ /** * This interface defines the minimum methods any documentor needs to implement. * @package Ripcord */ interface Ripcord_Documentor_Interface { public function setMethodData( $methods ); public function handle( $rpcServer ); public function getIntrospectionXML(); } /** * This class implements the default documentor for the ripcord server. Any request to the server * without a request_xml is handled by the documentor. * @package Ripcord */ class Ripcord_Documentor implements Ripcord_Documentor_Interface { /** * The object to parse the docComments. */ private $docCommentParser = null; /** * The name of the rpc server, used as the title and heading of the default HTML page. */ public $name = 'Ripcord: Simple RPC Server'; /** * A url to an optional css file or a css string for an inline stylesheet. */ public $css = " html { font-family: georgia, times, serif; font-size: 79%; background-color: #EEEEEE; } h1 { font-family: 'arial black', helvetica, sans-serif; font-size: 2em; font-weight: normal; margin: -20px -21px 0.4em -20px; padding: 40px 20px 20px; background: #01648E; /* for non-css3 browsers */ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00486E', endColorstr='#09799E'); /* for IE */ background: -webkit-gradient(linear, left top, left bottom, from(#00486E), to(#09799E)); /* for webkit browsers */ background: -moz-linear-gradient(top, #00486E, #09799E); /* for firefox 3.6+ */ color: white; border-bottom: 4px solid black; text-shadow: black 0.1em 0.1em 0.2em; } h2 { font-family: arial, helvetica, sans-serif; font-weight: bold; font-size: 1.4em; color: #444444; text-shadow: #AAAAAA 0.1em 0.1em 0.2em; margin-top: 2.5em; border-bottom: 1px solid #09799E; } h3 { font-family: arial, helvetica, sans-serif; font-weight: normal; font-size: 1.4em; color: #555555; text-shadow: #AAAAAA 0.1em 0.1em 0.2em; margin-bottom: 0px; } div.signature { font-family: courier, monospace; margin-bottom: 1.4em; } ul, ol, li { margin: 0px; padding: 0px; } ul, ol { color: #09799E; margin-bottom: 1.4em; } ul li { list-style: square; } ul li, ol li { margin-left: 20px; } li span, li label { color: black; } li.param label { font-family: courier, monospace; padding-right: 1.4em; } a { text-decoration: none; } a:hover { text-decoration: underline; } body { background-color: white; width: 830px; margin: 10px auto; padding: 20px; -moz-box-shadow: 5px 5px 5px #ccc; -webkit-box-shadow: 5px 5px 5px #ccc; box-shadow: 5px 5px 5px #ccc; } code { display: block; background-color: #999999; padding: 10px; margin: 0.4em 0px 1.4em 0px; color: white; white-space: pre; font-family: courier, monospace; font-size: 1.2em; } .tag, .argName, .argType { margin-right: 10px; } .argument { margin-left: 20px; } .footer { font-family: helvetica, sans-serif; font-size: 0.9em; font-weight: normal; margin: 0px -21px -20px -20px; padding: 20px; background: #01648E; /* for non-css3 browsers */ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00486E', endColorstr='#09799E'); /* for IE */ background: -webkit-gradient(linear, left top, left bottom, from(#00486E), to(#09799E)); /* for webkit browsers */ background: -moz-linear-gradient(top, #00486E, #09799E); /* for firefox 3.6+ */ color: white; } .footer a { color: white; text-decoration: none; } "; /** * The wsdl 1.0 description. */ public $wsdl = false; /** * The wsdl 2.0 description */ public $wsdl2 = false; /** * Which version of the XML vocabulary the server implements. Either 'xmlrpc', 'soap 1.1', 'simple' or 'auto'. */ public $version = 'auto'; /** * The root URL of the rpc server. */ public $root = ''; /** * Optional header text for the online documentation. */ public $header = ''; /** * Optional footer text for the online documentation. */ public $footer = ''; /** * A list of method data, containing all the user supplied methods the rpc server implements. */ private $methods = null; /** * The constructor for the Ripcord_Documentor class. * @param array $options. Optional. Allows you to set the public properties of this class upon construction. */ public function __construct( $options = null, $docCommentParser = null ) { $check = array( 'name', 'css', 'wsdl', 'wsdl2', 'root', 'version', 'header', 'footer' ); foreach ( $check as $name ) { if ( isset($options[$name]) ) { $this->{$name} = $options[$name]; } } $this->docCommentParser = $docCommentParser; } /** * This method fills the list of method data with all the user supplied methods of the rpc server. * @param array $methodData A list of methods with name and callback information. */ public function setMethodData( $methodData ) { $this->methods = $methodData; } /** * This method handles any request which isn't a valid rpc request. * @param object $rpcServer A reference to the active rpc server. */ public function handle( $rpcServer ) { $methods = $rpcServer->call('system.listMethods'); echo ''; echo '' . $this->name . ''; if ( isset($this->css) ) { if (strpos($this->css, "\n")!==false) { echo ''; } else { echo ''; } } echo ''; echo '
'; echo '

' . $this->name . '

'; echo $this->header; echo '

'; $showWSDL = false; switch ( $this->version ) { case 'xmlrpc': echo 'This server implements the XML-RPC specification'; break; case 'simple': echo 'This server implements the SimpleRPC 1.0 specification'; break; case 'auto'; echo 'This server implements the SOAP 1.1, XML-RPC and SimpleRPC 1.0 specification.'; $showWSDL = true; break; case 'soap 1.1': echo 'This server implements the SOAP 1.1 specification.'; $showWSDL = true; break; } echo '

'; if ( $showWSDL && ( $this->wsdl || $this->wsdl2 ) ) { echo ''; } $methods = $rpcServer->call( 'system.describeMethods' ); $allMethods = array(); $allFunctions = array(); foreach( $methods['methodList'] as $index => $method ) { if ( strpos( $method['name'], '.' ) !== false ) { $allMethods[ $method['name'] ] = $index; } else { $allFunctions[ $method['name'] ] = $index; } } ksort( $allMethods ); ksort( $allFunctions ); $allMethods = $allFunctions + $allMethods; echo '

Methods

    '; foreach ( $allMethods as $methodName => $methodIndex ) { echo '
  • ' . $methodName . '
  • '; } echo '
'; $currentClass = ''; $class = ''; echo '
'; foreach ( $allMethods as $methodName => $methodIndex ) { $method = $methods['methodList'][$methodIndex]; $pos = strpos( $methodName, '.'); if ( $pos !== false ) { $class = substr( $methodName, 0, $pos ); } if ( $currentClass != $class ) { echo '
'; echo '
'; $currentClass = $class; } echo '

' . $method['name'] . '

'; if ( isset( $method['signatures'] ) ) { foreach ( $method['signatures'] as $signature ) { echo '
'; if ( is_array( $signature['returns'] ) ) { $return = $signature['returns'][0]; echo '(' . $return['type'] . ') '; } echo $method['name'] . '('; $paramInfo = false; if ( is_array( $signature['params'] ) ) { $paramInfo = $signature['params']; $params = ''; foreach ( $signature['params'] as $param ) { $params .= ', (' . $param['type'] . ') ' . $param['name'] . ' '; } echo substr($params, 1); } echo ')
'; if ( is_array( $paramInfo ) ) { echo '

Parameters

    '; foreach ( $paramInfo as $param ) { echo '
  • '; echo ' '; echo '' . $param['description'] . ''; echo '
  • '; } echo '
'; } } } if ( $method['purpose'] ) { echo '
' . $method['purpose'] . '
'; } if ( isset( $method['notes'] ) && is_array( $method['notes'] ) ) { echo '

Notes

    '; foreach ( $method['notes'] as $note ) { echo '
  1. ' . $note. '
  2. '; } echo '
'; } if ( isset( $method['see'] ) && is_array( $method['see'] ) ) { echo '
'; echo '

See

'; echo '
    '; foreach ( $method['see'] as $link => $description) { echo '
  • '; if ( isset( $allMethods[$link] ) ) { echo '' . $link . ' ' . $description . ''; } else { echo '' . $link . ' ' . $description . ''; } echo '
  • '; } echo '
'; } } echo '
'; echo $this->footer; echo ''; echo '
'; } /** * This method returns an XML document in the introspection format expected by * xmlrpc_server_register_introspection_callback. It uses the php Reflection * classes to gather information from the registered methods. * Descriptions are added from phpdoc docblocks if found. * @return string XML string with the introspection data. */ function getIntrospectionXML() { $xml = ""; if ( isset($this->methods) && is_array( $this->methods ) ) { foreach ($this->methods as $method => $methodData ) { if ( is_array( $methodData['call'] ) ) { $reflection = new ReflectionMethod( $methodData['call'][0], $methodData['call'][1] ); } else { $reflection = new ReflectionFunction( $methodData['call'] ); } $description = $reflection->getDocComment(); if ( $description && $this->docCommentParser ) { $data = $this->docCommentParser->parse( $description ); if ($data['description']) { $description = $data['description']; } } if ($description) { $description = '

' . str_replace( array( "\r\n\r\n", "\n\n") , '

', $description) . '

'; } if ( is_array( $data ) ) { foreach( $data as $key => $value ) { switch( $key ) { case 'category' : case 'deprecated' : case 'package' : $description .= '
' . $key . '' . $value .'
'; break; default : break; } } } $xml .= ''; if ( is_array( $data ) && ( isset( $data['arguments'] ) || isset( $data['return'] ) ) ) { $xml .= ''; if ( isset( $data['arguments'] ) && is_array($data['arguments']) ) { $xml .= ''; foreach ( $data['arguments'] as $name => $argument ) { if ( $name[0] == '$' ) { $name = substr( $name, 1 ); } $xml .= ''; } $xml .= ''; } if ( isset( $data['return'] ) && is_array( $data['return'] ) ) { $xml .= ''; } $xml .= ''; } $xml .= ''; } } $xml .= "
"; return $xml; } } /** * This interface describes the minimum interface needed for a comment parser object used by the * Ripcord_Documentor * @package Ripcord */ interface Ripcord_Documentor_Parser { /** * This method parses a given docComment block and returns an array with information. * @param string $commentBlock The docComment block. * @return array The parsed information. */ public function parse( $commentBlock ); } /** * This class implements the Ripcord_Documentor_Parser interface, parsing the docComment * as a phpdoc style docComment. * @package Ripcord */ class Ripcord_Documentor_Parser_phpdoc implements Ripcord_Documentor_Parser { /** * This method parses a given docComment block and returns an array with information. * @param string $commentBlock The docComment block. * @return array The parsed information. */ public function parse( $commentBlock) { $this->currentTag = 'description'; $description = preg_replace('/^(\s*(\/\*\*|\*\/|\*))/m', '', $commentBlock); $info = array(); $lines = explode( "\n", $description ); foreach ( $lines as $line ) { $info = $this->parseLine( $line, $info ); } return $info; //array( 'description' => $description ); } /** * This method parses a single line from the comment block. */ private function parseLine( $line, $info ) { $handled = false; if (preg_match('/^\s*(@[a-z]+)\s(.*)$/i', $line, $matches)) { $this->currentTag = substr($matches[1], 1); $line = trim( substr($line, strlen($this->currentTag)+2 ) ); switch( $this->currentTag ) { case 'param' : if ( preg_match('/^\s*([[:alpha:]|]+)\s([[:alnum:]$_]+)(.*)$/i', $line, $matches) ) { if ( !isset($info['arguments']) ) { $info['arguments'] = array(); } if ( !isset($info['arguments'][$matches[2]]) ) { $info['arguments'][$matches[2]] = array('description' => ''); } $info['arguments'][$matches[2]]['type'] = $matches[1]; $info['arguments'][$matches[2]]['description'] .= $this->parseDescription($matches[3]); } $handled = true; break; case 'return' : if ( preg_match('/^\s*([[:alpha:]|]+)\s(.*)$/i', $line, $matches) ) { if ( !isset($info['return']) ) { $info['return'] = array( 'description' => '' ); } $info['return']['type'] = $matches[1]; $info['return']['description'] .= $this->parseDescription($matches[2]); } $handled = true; break; } } if (!$handled) { switch( $this->currentTag) { case 'param' : case 'return' : if ( !isset( $info[$this->currentTag] ) ) { $info[$this->currentTag] = array(); } $info[$this->currentTag]['description'] .= $this->parseDescription($line); break; default: if ( !isset( $info[$this->currentTag] ) ) { $info[$this->currentTag] = ''; } $info[$this->currentTag] .= $this->parseDescription($line); break; } } return $info; } /** * This method parses only the text description part of a line of the comment block. */ private function parseDescription( $line ) { while ( preg_match('/{@([^}]*)}/', $line, $matches) ) { switch( $matches[1] ) { case 'internal' : $line = str_replace( $matches[0], '', $line ); break; default : $line = str_replace( $matches[0], $matches[1], $line ); break; } } $line = str_replace( array( '\@', '{@*}' ), array( '@', '*/' ), $line ); return $line; } } ?>