alreadyRegistered[$fileName])) { return; } $potentialPaths = array( PIWIK_INCLUDE_PATH . "/plugins/". $fileName ."/API.php", ); $found = false; foreach($potentialPaths as $path) { if(is_file($path)) { require_once $path; $found = true; break; } } if(!$found) { throw new Exception("API module $fileName not found."); } $class= $this->getClassNameFromModule($fileName); $rClass = new ReflectionClass($class); // check that it is a subclass of Piwik_APIable if(!$rClass->isSubclassOf(new ReflectionClass("Piwik_Apiable"))) { throw new Exception("To publish its public methods in the API, the class '$class' must be a subclass of 'Piwik_Apiable'."); } // check that is is singleton $this->checkClassIsSingleton($class); $rMethods = $rClass->getMethods(); foreach($rMethods as $method) { // use this trick to read the static attribute of the class // $class::$methodsNotToPublish doesn't work $variablesClass = get_class_vars($class); $variablesClass['methodsNotToPublish'][] = 'getInstance'; if($method->isPublic() && !$method->isConstructor() && !in_array($method->getName(), $variablesClass['methodsNotToPublish'] ) ) { $name = $method->getName(); $parameters = $method->getParameters(); $aParameters = array(); foreach($parameters as $parameter) { $nameVariable = $parameter->getName(); $defaultValue = Piwik_API_Proxy::NO_DEFAULT_VALUE; if($parameter->isDefaultValueAvailable()) { $defaultValue = $parameter->getDefaultValue(); } $aParameters[$nameVariable] = $defaultValue; } $this->api[$class][$name]['parameters'] = $aParameters; $this->api[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters(); } } $this->alreadyRegistered[$fileName] = true; } /** * Returns the 'moduleName' part of 'Piwik_moduleName_API' classname * * @param string moduleName * @return string className */ protected function getModuleNameFromClassName( $className ) { $start = strpos($className, '_') + 1; return substr($className, $start , strrpos($className, '_') - $start); } /** * Returns a string containing links to examples on how to call a given method on a given API * It will export links to XML, CSV, HTML, JSON, PHP, etc. * It will not export links for methods such as deleteSite or deleteUser * * @param string the class * @param methodName the method * @return string|false when not possible */ public function getExampleUrl($class, $methodName, $parametersToSet = array()) { $knowExampleDefaultParametersValues = array( 'access' => 'view', 'idSite' => '1', 'userLogin' => 'test', 'password' => 'passwordExample', 'passwordMd5ied' => 'passwordExample', 'email' => 'test@example.org', 'siteName' => 'new example website', 'urls' => 'http://example.org', // used in addSite, updateSite 'period' => 'day', 'date' => 'today', ); foreach($parametersToSet as $name => $value) { $knowExampleDefaultParametersValues[$name] = $value; } // no links for these method names $doNotPrintExampleForTheseMethods = array( 'deleteSite', 'deleteUser', ); if(in_array($methodName,$doNotPrintExampleForTheseMethods)) { return false; } // we try to give an URL example to call the API $aParameters = $this->getParametersList($class, $methodName); $moduleName = $this->getModuleNameFromClassName($class); $urlExample = '?module=API&method='.$moduleName.'.'.$methodName.'&'; foreach($aParameters as $nameVariable=> $defaultValue) { // if there isn't a default value for a given parameter, // we need a 'know default value' or we can't generate the link if($defaultValue === Piwik_API_Proxy::NO_DEFAULT_VALUE) { if(isset($knowExampleDefaultParametersValues[$nameVariable])) { $exampleValue = $knowExampleDefaultParametersValues[$nameVariable]; $urlExample .= $nameVariable . '=' . $exampleValue . '&'; } else { return false; } } } return substr($urlExample,0,-1); } /** * Returns a HTML page containing help for all the successfully loaded APIs. * * For each module it will return a mini help with the method names, parameters to give, * links to get the result in Xml/Csv/etc * * @return string */ public function getAllInterfaceString( $outputExampleUrls = true, $prefixUrls = '' ) { $str = ''; foreach($this->api as $class => $info) { $moduleName = $this->getModuleNameFromClassName($class); $str .= "\n

Module ".$moduleName."

"; foreach($info as $methodName => $infoMethod) { $params = $this->getStrListParameters($class, $methodName); $str .= "\n" . "- $moduleName.$methodName " . $params . ""; $str .= ''; if($outputExampleUrls) { // we prefix all URLs with $prefixUrls // used when we include this output in the Piwik official documentation for example $exampleUrl = $this->getExampleUrl($class, $methodName); if($exampleUrl !== false) { $lastNUrls = ''; if( ereg('(&period)|(&date)',$exampleUrl)) { $exampleUrlRss1 = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10')) ; $exampleUrlRss2 = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last5','period' => 'week',)); $lastNUrls = ", RSS of the last 10 days, 5 weeks, XML of the last 10 days"; } $exampleUrl = $prefixUrls . $exampleUrl ; $str .= " [ Example in XML, PHP, Json, Csv, Basic html $lastNUrls ]"; } else { $str .= " [ No example available ]"; } } $str .= ''; $str .= "\n
"; } } return $str; } /** * Returns the methods $class.$name parameters (and default value if provided) as a string. * * @param string The class name * @param string The method name * @return string For example "(idSite, period, date = 'today')" */ private function getStrListParameters($class, $name) { $aParameters = $this->getParametersList($class, $name); $asParameters = array(); foreach($aParameters as $nameVariable=> $defaultValue) { $str = $nameVariable; if($defaultValue !== Piwik_API_Proxy::NO_DEFAULT_VALUE) { $str .= " = '$defaultValue'"; } $asParameters[] = $str; } $sParameters = implode(", ", $asParameters); return "($sParameters)"; } /** * Returns the parameters names and default values for the method $name * of the class $class * * @param string The class name * @param string The method name * @return array Format array( * 'testParameter' => null, // no default value * 'life' => 42, // default value = 42 * 'date' => 'yesterday', * ); */ public function getParametersList($class, $name) { return $this->api[$class][$name]['parameters']; } /** * Returns the number of required parameters (parameters without default values). * * @param string The class name * @param string The method name * @return int The number of required parameters */ private function getNumberOfRequiredParameters($class, $name) { return $this->api[$class][$name]['numberOfRequiredParameters']; } /** * Returns true if the method is found in the API of the given class name. * * @param string The class name * @param string The method name * @return bool */ private function isMethodAvailable( $className, $methodName) { return isset($this->api[$className][$methodName]); } /** * Checks that the count of the given parameters do match with the count of the required ones * * @param string The class name * @param string The method name * @param array * @throws exception If less parameters than required were given */ private function checkNumberOfParametersMatch($className, $methodName, $parameters) { $nbParamsGiven = count($parameters); $nbParamsRequired = $this->getNumberOfRequiredParameters($className, $methodName); if($nbParamsGiven < $nbParamsRequired) { throw new Exception("The number of parameters provided ($nbParamsGiven) is less than the number of required parameters ($nbParamsRequired) for this method. Please check the method API."); } } /** * Checks that the class is a Singleton (presence of the getInstance() method) * * @param string The class name * @throws exception If the class is not a Singleton */ private function checkClassIsSingleton($className) { if(!method_exists($className, "getInstance")) { throw new Exception("Objects that provide an API must be Singleton and have a 'static public function getInstance()' method."); } } /** * Checks that the method exists in the class * * @param string The class name * @param string The method name * @throws exception If the method is not found */ public function checkMethodExists($className, $methodName) { if(!$this->isMethodAvailable($className, $methodName)) { throw new Exception("The method '$methodName' does not exist or is not available in the module '".$className."'."); } } /** * Returns the API class name given the module name. * * For exemple for $module = 'Referers' it returns 'Piwik_Referers_API' * Piwik_Referers_API is the class that extends Piwik_Apiable * and that contains the methods to be published in the API. * * @param string module name * @return string class name */ protected function getClassNameFromModule($module) { $class = Piwik::prefixClass($module ."_API"); return $class; } /** * Magic method used to set a flag telling the module named currently being called * */ public function __get($name) { self::$classCalled = $name; return $this; } /** * Method always called when making an API request. * It checks several things before actually calling the real method on the given module. * * It also logs the API calls, with the parameters values, the returned value, the performance, etc. * You can enable logging in config/global.ini.php (log_api_call) * * @param string The method name * @param array The parameters * * @throws Piwik_Access_NoAccessException */ public function __call($methodName, $parameterValues ) { $returnedValue = null; try { $this->registerClass(self::$classCalled); $className = $this->getClassNameFromModule(self::$classCalled); // instanciate the object $object = call_user_func(array($className, "getInstance")); // check method exists $this->checkMethodExists($className, $methodName); // first check number of parameters do match $this->checkNumberOfParametersMatch($className, $methodName, $parameterValues); // start the timer $timer = new Piwik_Timer; // call the method $returnedValue = call_user_func_array(array($object, $methodName), $parameterValues); // log the API Call $parameterNamesDefaultValues = $this->getParametersList($className, $methodName); Zend_Registry::get('logger_api_call')->log( self::$classCalled, $methodName, $parameterNamesDefaultValues, $parameterValues, $timer->getTimeMs(), $returnedValue ); } catch( Piwik_Access_NoAccessException $e) { throw $e; } self::$classCalled = null; return $returnedValue; } }