PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / API / Proxy.php
matomo / app / core / API Last commit date
DataTableManipulator 1 month ago ApiRenderer.php 3 months ago CORSHandler.php 1 year ago DataTableGenericFilter.php 1 month ago DataTableManipulator.php 1 month ago DataTablePostProcessor.php 1 month ago DocumentationGenerator.php 6 months ago Inconsistencies.php 2 years ago NoDefaultValue.php 2 years ago Proxy.php 1 month ago Request.php 1 month ago ResponseBuilder.php 1 month ago
Proxy.php
648 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\API;
10
11 use Exception;
12 use Piwik\Http\BadRequestException;
13 use Piwik\Common;
14 use Piwik\Container\StaticContainer;
15 use Piwik\Context;
16 use Piwik\Piwik;
17 use Piwik\Plugin\API;
18 use Piwik\Plugin\Manager;
19 use ReflectionClass;
20 use ReflectionMethod;
21 // prevent upgrade error eg from Matomo 3.x to Matomo 4.x. Refs https://github.com/matomo-org/matomo/pull/16468
22 // the `false` is important otherwise it would fail and try to load the proxy.php file again.
23 if (!class_exists('Piwik\\API\\NoDefaultValue', \false)) {
24 // phpcs:ignoreFile PSR1.Classes.ClassDeclaration.MultipleClasses
25 class NoDefaultValue
26 {
27 }
28 }
29 /**
30 * Proxy is a singleton that has the knowledge of every method available, their parameters
31 * and default values.
32 * Proxy receives all the API calls requests via call() and forwards them to the right
33 * object, with the parameters in the right order.
34 *
35 * It will also log the performance of API calls (time spent, parameter values, etc.) if logger available
36 */
37 class Proxy
38 {
39 // array of already registered plugins names
40 protected $alreadyRegistered = array();
41 protected $metadataArray = array();
42 private $hideIgnoredFunctions = \true;
43 // when a parameter doesn't have a default value we use this
44 private $noDefaultValue;
45 public function __construct()
46 {
47 $this->noDefaultValue = new \Piwik\API\NoDefaultValue();
48 }
49 public static function getInstance()
50 {
51 return StaticContainer::get(self::class);
52 }
53 /**
54 * Returns array containing reflection meta data for all the loaded classes
55 * eg. number of parameters, method names, etc.
56 *
57 * @return array
58 */
59 public function getMetadata()
60 {
61 ksort($this->metadataArray);
62 return $this->metadataArray;
63 }
64 /**
65 * Registers the API information of a given module.
66 *
67 * The module to be registered must be
68 * - a singleton (providing a getInstance() method)
69 * - the API file must be located in plugins/ModuleName/API.php
70 * for example plugins/Referrers/API.php
71 *
72 * The method will introspect the methods, their parameters, etc.
73 *
74 * @param string $className ModuleName eg. "API"
75 */
76 public function registerClass($className)
77 {
78 if (isset($this->alreadyRegistered[$className])) {
79 return;
80 }
81 $this->includeApiFile($className);
82 $this->checkClassIsSingleton($className);
83 $rClass = new ReflectionClass($className);
84 if (!$this->shouldHideAPIMethod($rClass->getDocComment())) {
85 foreach ($rClass->getMethods() as $method) {
86 $this->loadMethodMetadata($className, $method);
87 }
88 $this->setDocumentation($rClass, $className);
89 $this->alreadyRegistered[$className] = \true;
90 }
91 }
92 /**
93 * Will be displayed in the API page
94 *
95 * @param ReflectionClass $rClass Instance of ReflectionClass
96 * @param string $className Name of the class
97 */
98 private function setDocumentation($rClass, $className)
99 {
100 // Doc comment
101 $doc = $rClass->getDocComment();
102 $doc = $this->removeDocblockAnnotationBlocks($doc, 'phpstan');
103 $doc = str_replace(" * " . \PHP_EOL, "<br>", $doc);
104 // boldify the first line only if there is more than one line, otherwise too much bold
105 if (substr_count($doc, '<br>') > 1) {
106 $firstLineBreak = strpos($doc, "<br>");
107 $doc = "<div class='apiFirstLine'>" . substr($doc, 0, $firstLineBreak) . "</div>" . substr($doc, $firstLineBreak + strlen("<br>"));
108 }
109 $doc = preg_replace("/(@package)[a-z _A-Z]*/", "", $doc);
110 $doc = preg_replace("/(@method).*/", "", $doc);
111 $doc = str_replace(array("\t", "\n", "/**", "*/", " * ", " *", " ", "\t*", " * @package"), " ", $doc);
112 // replace 'foo' and `bar` and "foobar" with code blocks... much magic
113 $doc = preg_replace('/`(.*?)`/', '<code>$1</code>', $doc);
114 $this->metadataArray[$className]['__documentation'] = $doc;
115 }
116 /**
117 * Removes docblock annotations and their continuation lines.
118 *
119 * For example, this removes `@phpstan-type` and the following multiline shape definition.
120 */
121 private function removeDocblockAnnotationBlocks($doc, $annotationPrefix)
122 {
123 if (!is_string($doc) || $doc === '') {
124 return $doc;
125 }
126 $lines = preg_split('/\\R/', $doc);
127 $result = [];
128 $isSkipping = \false;
129 foreach ($lines as $line) {
130 if (preg_match('/^\\s*\\*\\s*@' . preg_quote($annotationPrefix, '/') . '\\S*/', $line)) {
131 $isSkipping = \true;
132 continue;
133 }
134 if ($isSkipping) {
135 // stop skipping once a new annotation starts
136 if (preg_match('/^\\s*\\*\\s*@\\S+/', $line)) {
137 $isSkipping = \false;
138 } elseif (preg_match('/^\\s*\\*\\/\\s*$/', $line)) {
139 $isSkipping = \false;
140 } else {
141 continue;
142 }
143 }
144 $result[] = $line;
145 }
146 return implode(\PHP_EOL, $result);
147 }
148 /**
149 * Returns number of classes already loaded
150 * @return int
151 */
152 public function getCountRegisteredClasses()
153 {
154 return count($this->alreadyRegistered);
155 }
156 /**
157 * Will execute $className->$methodName($parametersValues)
158 * If any error is detected (wrong number of parameters, method not found, class not found, etc.)
159 * it will throw an exception
160 *
161 * It also logs the API calls, with the parameters values, the returned value, the performance, etc.
162 * You can enable logging in config/global.ini.php (log_api_call)
163 *
164 * @param string $className The class name (eg. API)
165 * @param string $methodName The method name
166 * @param array $parametersRequest The parameters pairs (name=>value)
167 *
168 * @return mixed|null
169 * @throws Exception|\Piwik\NoAccessException
170 */
171 public function call($className, $methodName, $parametersRequest)
172 {
173 // Temporarily sets the Request array to this API call context
174 return Context::executeWithQueryParameters($parametersRequest, function () use($className, $methodName, $parametersRequest) {
175 $this->registerClass($className);
176 $request = new \Piwik\Request($parametersRequest);
177 /**
178 * instantiate the object
179 * @var API $object
180 */
181 $object = $className::getInstance();
182 // check method exists
183 $this->checkMethodExists($className, $methodName);
184 // get the list of parameters required by the method
185 $parameterNamesDefaultValuesAndTypes = $this->getParametersListWithTypes($className, $methodName);
186 // load parameters in the right order, etc.
187 if ($object->usesAutoSanitizeInputParams() && !$this->usesUnsanitizedInputParams($className, $methodName)) {
188 $finalParameters = $this->getSanitizedRequestParametersArray($parameterNamesDefaultValuesAndTypes, $request->getParameters());
189 } else {
190 $finalParameters = $this->getRequestParametersArray($parameterNamesDefaultValuesAndTypes, $request);
191 }
192 // allow plugins to manipulate the value
193 $pluginName = $this->getModuleNameFromClassName($className);
194 $returnedValue = null;
195 /**
196 * Triggered before an API request is dispatched.
197 *
198 * This event can be used to modify the arguments passed to one or more API methods.
199 *
200 * **Example**
201 *
202 * Piwik::addAction('API.Request.dispatch', function (&$parameters, $pluginName, $methodName) {
203 * if ($pluginName == 'Actions') {
204 * if ($methodName == 'getPageUrls') {
205 * // ... do something ...
206 * } else {
207 * // ... do something else ...
208 * }
209 * }
210 * });
211 *
212 * @param array &$finalParameters List of parameters that will be passed to the API method.
213 * @param string $pluginName The name of the plugin the API method belongs to.
214 * @param string $methodName The name of the API method that will be called.
215 */
216 Piwik::postEvent('API.Request.dispatch', array(&$finalParameters, $pluginName, $methodName));
217 /**
218 * Triggered before an API request is dispatched.
219 *
220 * This event exists for convenience and is triggered directly after the {@hook API.Request.dispatch}
221 * event is triggered. It can be used to modify the arguments passed to a **single** API method.
222 *
223 * _Note: This is can be accomplished with the {@hook API.Request.dispatch} event as well, however
224 * event handlers for that event will have to do more work._
225 *
226 * **Example**
227 *
228 * Piwik::addAction('API.Actions.getPageUrls', function (&$parameters) {
229 * // force use of a single website. for some reason.
230 * $parameters['idSite'] = 1;
231 * });
232 *
233 * @param array &$finalParameters List of parameters that will be passed to the API method.
234 */
235 Piwik::postEvent(sprintf('API.%s.%s', $pluginName, $methodName), array(&$finalParameters));
236 /**
237 * Triggered before an API request is dispatched.
238 *
239 * Use this event to intercept an API request and execute your own code instead. If you set
240 * `$returnedValue` in a handler for this event, the original API method will not be executed,
241 * and the result will be what you set in the event handler.
242 *
243 * @param mixed &$returnedValue Set this to set the result and preempt normal API invocation.
244 * @param array &$finalParameters List of parameters that will be passed to the API method.
245 * @param string $pluginName The name of the plugin the API method belongs to.
246 * @param string $methodName The name of the API method that will be called.
247 * @param array $parametersRequest The query parameters for this request.
248 */
249 Piwik::postEvent('API.Request.intercept', [&$returnedValue, $finalParameters, $pluginName, $methodName, $parametersRequest]);
250 $apiParametersInCorrectOrder = array();
251 foreach ($parameterNamesDefaultValuesAndTypes as $name => $parameter) {
252 if (isset($finalParameters[$name]) || array_key_exists($name, $finalParameters)) {
253 $apiParametersInCorrectOrder[] = $finalParameters[$name];
254 }
255 }
256 // call the method if a hook hasn't already set an output variable
257 if ($returnedValue === null) {
258 $returnedValue = call_user_func_array(array($object, $methodName), $apiParametersInCorrectOrder);
259 }
260 $endHookParams = array(&$returnedValue, array('className' => $className, 'module' => $pluginName, 'action' => $methodName, 'parameters' => $finalParameters));
261 /**
262 * Triggered directly after an API request is dispatched.
263 *
264 * This event exists for convenience and is triggered immediately before the
265 * {@hook API.Request.dispatch.end} event. It can be used to modify the output of a **single**
266 * API method.
267 *
268 * _Note: This can be accomplished with the {@hook API.Request.dispatch.end} event as well,
269 * however event handlers for that event will have to do more work._
270 *
271 * **Example**
272 *
273 * // append (0 hits) to the end of row labels whose row has 0 hits
274 * Piwik::addAction('API.Actions.getPageUrls', function (&$returnValue, $info)) {
275 * $returnValue->filter('ColumnCallbackReplace', 'label', function ($label, $hits) {
276 * if ($hits === 0) {
277 * return $label . " (0 hits)";
278 * } else {
279 * return $label;
280 * }
281 * }, null, array('nb_hits'));
282 * }
283 *
284 * @param mixed &$returnedValue The API method's return value. Can be an object, such as a
285 * {@link Piwik\DataTable DataTable} instance.
286 * could be a {@link Piwik\DataTable DataTable}.
287 * @param array $extraInfo An array holding information regarding the API request. Will
288 * contain the following data:
289 *
290 * - **className**: The namespace-d class name of the API instance
291 * that's being called.
292 * - **module**: The name of the plugin the API request was
293 * dispatched to.
294 * - **action**: The name of the API method that was executed.
295 * - **parameters**: The array of parameters passed to the API
296 * method.
297 */
298 Piwik::postEvent(sprintf('API.%s.%s.end', $pluginName, $methodName), $endHookParams);
299 /**
300 * Triggered directly after an API request is dispatched.
301 *
302 * This event can be used to modify the output of any API method.
303 *
304 * **Example**
305 *
306 * // append (0 hits) to the end of row labels whose row has 0 hits for any report that has the 'nb_hits' metric
307 * Piwik::addAction('API.Actions.getPageUrls.end', function (&$returnValue, $info)) {
308 * // don't process non-DataTable reports and reports that don't have the nb_hits column
309 * if (!($returnValue instanceof DataTableInterface)
310 * || in_array('nb_hits', $returnValue->getColumns())
311 * ) {
312 * return;
313 * }
314 *
315 * $returnValue->filter('ColumnCallbackReplace', 'label', function ($label, $hits) {
316 * if ($hits === 0) {
317 * return $label . " (0 hits)";
318 * } else {
319 * return $label;
320 * }
321 * }, null, array('nb_hits'));
322 * }
323 *
324 * @param mixed &$returnedValue The API method's return value. Can be an object, such as a
325 * {@link Piwik\DataTable DataTable} instance.
326 * @param array $extraInfo An array holding information regarding the API request. Will
327 * contain the following data:
328 *
329 * - **className**: The namespace-d class name of the API instance
330 * that's being called.
331 * - **module**: The name of the plugin the API request was
332 * dispatched to.
333 * - **action**: The name of the API method that was executed.
334 * - **parameters**: The array of parameters passed to the API
335 * method.
336 */
337 Piwik::postEvent('API.Request.dispatch.end', $endHookParams);
338 return $returnedValue;
339 });
340 }
341 /**
342 * Returns the parameters names and default values for the method $name
343 * of the class $class
344 *
345 * @param string $class The class name
346 * @param string $name The method name
347 * @return array Format array(
348 * 'testParameter' => null, // no default value
349 * 'life' => 42, // default value = 42
350 * 'date' => 'yesterday',
351 * );
352 */
353 public function getParametersList($class, $name)
354 {
355 return array_combine(array_keys($this->metadataArray[$class][$name]['parameters']), array_column($this->metadataArray[$class][$name]['parameters'], 'default'));
356 }
357 /**
358 * Returns the parameters names, default values and types for the method $name
359 * of the class $class
360 *
361 * @param string $class The class name
362 * @param string $name The method name
363 * @return array Format array(
364 * 'testParameter' => ['default' => null, 'type' => null], // no default value
365 * 'life' => ['default' => 42, 'type' => 'int'], // default value 42, type hint is int
366 * );
367 */
368 public function getParametersListWithTypes($class, $name)
369 {
370 return $this->metadataArray[$class][$name]['parameters'];
371 }
372 /**
373 * Check if given method name is deprecated or not.
374 */
375 public function isDeprecatedMethod($class, $methodName)
376 {
377 return $this->metadataArray[$class][$methodName]['isDeprecated'] ?? \false;
378 }
379 /**
380 * Check if given method uses unsanitized input parameters.
381 */
382 public function usesUnsanitizedInputParams($class, $methodName)
383 {
384 return $this->metadataArray[$class][$methodName]['unsanitizedInputParams'] ?? \false;
385 }
386 /**
387 * Returns the 'moduleName' part of '\\Piwik\\Plugins\\moduleName\\API'
388 *
389 * @param string $className "API"
390 * @return string "Referrers"
391 */
392 public function getModuleNameFromClassName($className)
393 {
394 return str_replace(array('\\Piwik\\Plugins\\', '\\API'), '', $className);
395 }
396 public function isExistingApiAction($pluginName, $apiAction)
397 {
398 $namespacedApiClassName = "\\Piwik\\Plugins\\{$pluginName}\\API";
399 $api = $namespacedApiClassName::getInstance();
400 return method_exists($api, $apiAction);
401 }
402 public function buildApiActionName($pluginName, $apiAction)
403 {
404 return sprintf("%s.%s", $pluginName, $apiAction);
405 }
406 /**
407 * Sets whether to hide '@ignore'd functions from method metadata or not.
408 *
409 * @param bool $hideIgnoredFunctions
410 */
411 public function setHideIgnoredFunctions($hideIgnoredFunctions)
412 {
413 $this->hideIgnoredFunctions = $hideIgnoredFunctions;
414 // make sure metadata gets reloaded
415 $this->alreadyRegistered = array();
416 $this->metadataArray = array();
417 }
418 /**
419 * Returns an array containing the *sanitized* values of the parameters to pass to the method to call
420 *
421 * @param array $requiredParameters array of (parameter name, default value)
422 * @param array $parametersRequest
423 * @throws Exception
424 * @return array values to pass to the function call
425 */
426 private function getSanitizedRequestParametersArray($requiredParameters, $parametersRequest)
427 {
428 $finalParameters = [];
429 foreach ($requiredParameters as $name => $parameter) {
430 try {
431 $defaultValue = $parameter['default'];
432 $type = $parameter['type'];
433 $request = new \Piwik\Request($parametersRequest);
434 if (in_array($name, ['segment', 'password', 'passwordConfirmation']) && !empty($parametersRequest[$name])) {
435 // special handling for some parameters:
436 // segment: we do not want to sanitize user input as it would break the segment encoding
437 // password / passwordConfirmation: sanitizing this parameters might change special chars in passwords, breaking login and confirmation boxes
438 $requestValue = $parametersRequest[$name];
439 } elseif ($defaultValue instanceof \Piwik\API\NoDefaultValue) {
440 if ($type === 'bool') {
441 $requestValue = $request->getBoolParameter($name);
442 } else {
443 $requestValue = Common::getRequestVar($name, null, $type, $parametersRequest);
444 }
445 } else {
446 try {
447 if ($type === 'bool') {
448 $requestValue = $request->getBoolParameter($name, $defaultValue);
449 } else {
450 $requestValue = Common::getRequestVar($name, $defaultValue, $type, $parametersRequest);
451 }
452 } catch (Exception $e) {
453 // Special case: empty parameter in the URL, should return the empty string, if no incompatible type is defined
454 if (isset($parametersRequest[$name]) && $parametersRequest[$name] === '' && (empty($type) || $type === 'string')) {
455 $requestValue = '';
456 } else {
457 $requestValue = $defaultValue;
458 }
459 }
460 }
461 } catch (Exception $e) {
462 throw new BadRequestException(Piwik::translate('General_PleaseSpecifyValue', [$name]));
463 }
464 $finalParameters[$name] = $requestValue;
465 }
466 return $finalParameters;
467 }
468 /**
469 * Returns an array containing the values of the parameters to pass to the method to call
470 *
471 * @param array $requiredParameters array of (parameter name, default value)
472 * @param \Piwik\Request $request
473 * @throws Exception
474 * @return array values to pass to the function call
475 */
476 private function getRequestParametersArray($requiredParameters, \Piwik\Request $request) : array
477 {
478 $finalParameters = [];
479 foreach ($requiredParameters as $name => $parameter) {
480 try {
481 $defaultValue = $parameter['default'];
482 $type = $parameter['type'] ?? '';
483 $requestValue = null;
484 switch (strtolower($type)) {
485 case 'bool':
486 $method = 'getBoolParameter';
487 break;
488 case 'int':
489 $method = 'getIntegerParameter';
490 break;
491 case 'string':
492 $method = 'getStringParameter';
493 break;
494 case 'float':
495 $method = 'getFloatParameter';
496 break;
497 case 'array':
498 $method = 'getArrayParameter';
499 break;
500 default:
501 $method = 'getParameter';
502 }
503 if ($defaultValue instanceof \Piwik\API\NoDefaultValue) {
504 $requestValue = $request->{$method}($name);
505 } elseif ($defaultValue === null) {
506 try {
507 $requestValue = $request->{$method}($name);
508 } catch (\InvalidArgumentException $e) {
509 $requestValue = null;
510 }
511 } else {
512 $requestValue = $request->{$method}($name, $defaultValue);
513 }
514 } catch (Exception $e) {
515 throw new BadRequestException(Piwik::translate('General_PleaseSpecifyValue', [$name]));
516 }
517 $finalParameters[$name] = $requestValue;
518 }
519 return $finalParameters;
520 }
521 /**
522 * Includes the class API by looking up plugins/xxx/API.php
523 *
524 * @param string $fileName api class name eg. "API"
525 * @throws Exception
526 */
527 private function includeApiFile($fileName)
528 {
529 $module = self::getModuleNameFromClassName($fileName);
530 $path = Manager::getPluginDirectory($module) . '/API.php';
531 if (is_readable($path)) {
532 require_once $path;
533 // prefixed by PIWIK_INCLUDE_PATH
534 } else {
535 throw new Exception("API module {$module} not found.");
536 }
537 }
538 /**
539 * @param string $class name of a class
540 * @param ReflectionMethod $method instance of ReflectionMethod
541 */
542 private function loadMethodMetadata($class, $method)
543 {
544 if (!$this->checkIfMethodIsAvailable($method)) {
545 return;
546 }
547 $name = $method->getName();
548 $parameters = $method->getParameters();
549 $docComment = $method->getDocComment();
550 $aParameters = array();
551 foreach ($parameters as $parameter) {
552 $nameVariable = $parameter->getName();
553 $defaultValue = $this->noDefaultValue;
554 if ($parameter->isDefaultValueAvailable()) {
555 $defaultValue = $parameter->getDefaultValue();
556 }
557 $type = $parameter->getType();
558 // In case no default value is defined in the method, but the type hint allows null, we assume null as default value
559 if ($type && $type->allowsNull() && $defaultValue === $this->noDefaultValue) {
560 $defaultValue = null;
561 }
562 $aParameters[$nameVariable] = ['default' => $defaultValue, 'type' => $type && $type->isBuiltin() ? $type->getName() : null, 'allowsNull' => $type ? $type->allowsNull() : $defaultValue === null];
563 }
564 $this->metadataArray[$class][$name]['parameters'] = $aParameters;
565 $this->metadataArray[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters();
566 $this->metadataArray[$class][$name]['isDeprecated'] = \false !== strstr($docComment, '@deprecated');
567 $this->metadataArray[$class][$name]['unsanitizedInputParams'] = \false !== strstr($docComment, '@unsanitized');
568 }
569 /**
570 * Checks that the method exists in the class
571 *
572 * @param string $className The class name
573 * @param string $methodName The method name
574 * @throws Exception If the method is not found
575 */
576 private function checkMethodExists($className, $methodName)
577 {
578 if (!$this->isMethodAvailable($className, $methodName)) {
579 throw new BadRequestException(Piwik::translate('General_ExceptionMethodNotFound', [$methodName, $className]));
580 }
581 }
582 /**
583 * @param $docComment
584 * @return bool
585 */
586 public function shouldHideAPIMethod($docComment)
587 {
588 $hideLine = strstr($docComment, '@hide');
589 if ($hideLine === \false) {
590 return \false;
591 }
592 $hideLine = trim($hideLine);
593 $hideLine .= ' ';
594 $token = trim(strtok($hideLine, " "), "\n");
595 $hide = \false;
596 if (!empty($token)) {
597 /**
598 * This event exists for checking whether a Plugin API class or a Plugin API method tagged
599 * with a `@hideXYZ` should be hidden in the API listing.
600 *
601 * @param bool &$hide whether to hide APIs tagged with $token should be displayed.
602 */
603 Piwik::postEvent(sprintf('API.DocumentationGenerator.%s', $token), array(&$hide));
604 }
605 return $hide;
606 }
607 /**
608 * @param ReflectionMethod $method
609 * @return bool
610 */
611 protected function checkIfMethodIsAvailable(ReflectionMethod $method)
612 {
613 if (!$method->isPublic() || $method->isConstructor() || $method->getName() === 'getInstance') {
614 return \false;
615 }
616 if ($this->hideIgnoredFunctions && \false !== strstr($method->getDocComment(), '@ignore')) {
617 return \false;
618 }
619 if ($this->shouldHideAPIMethod($method->getDocComment())) {
620 return \false;
621 }
622 return \true;
623 }
624 /**
625 * Returns true if the method is found in the API of the given class name.
626 *
627 * @param string $className The class name
628 * @param string $methodName The method name
629 * @return bool
630 */
631 private function isMethodAvailable($className, $methodName)
632 {
633 return isset($this->metadataArray[$className][$methodName]);
634 }
635 /**
636 * Checks that the class is a Singleton (presence of the getInstance() method)
637 *
638 * @param string $className The class name
639 * @throws Exception If the class is not a Singleton
640 */
641 private function checkClassIsSingleton($className)
642 {
643 if (!method_exists($className, "getInstance")) {
644 throw new Exception("{$className} that provide an API must be Singleton and have a 'public static function getInstance()' method.");
645 }
646 }
647 }
648