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 / DocumentationGenerator.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
DocumentationGenerator.php
353 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\Common;
13 use Piwik\Container\StaticContainer;
14 use Piwik\Piwik;
15 use Piwik\Url;
16 use ReflectionClass;
17 /**
18 * Possible tags to use in APIs
19 *
20 * @hide -> Won't be shown in list of all APIs but is also not possible to be called via HTTP API
21 * @hideForAll Same as @hide
22 * @hideExceptForSuperUser Same as @hide but still shown and possible to be called by a user with super user access
23 * @internal -> Won't be shown in list of all APIs but is possible to be called via HTTP API
24 */
25 class DocumentationGenerator
26 {
27 protected $countPluginsLoaded = 0;
28 /**
29 * trigger loading all plugins with an API.php file in the Proxy
30 */
31 public function __construct()
32 {
33 $plugins = \Piwik\Plugin\Manager::getInstance()->getLoadedPluginsName();
34 foreach ($plugins as $plugin) {
35 try {
36 $className = \Piwik\API\Request::getClassNameAPI($plugin);
37 \Piwik\API\Proxy::getInstance()->registerClass($className);
38 } catch (Exception $e) {
39 }
40 }
41 }
42 /**
43 * Returns a HTML page containing help for all the successfully loaded APIs.
44 *
45 * @param bool $outputExampleUrls
46 * @return string
47 */
48 public function getApiDocumentationAsString($outputExampleUrls = \true)
49 {
50 list($toc, $str) = $this->generateDocumentation($outputExampleUrls, $prefixUrls = '', $displayTitlesAsEnrichedHeadline = \true);
51 return "<div vue-entry=\"CoreHome.ContentBlock\" content-title='Quick access to APIs' id='topApiRef' name='topApiRef'>\n\t\t\t\t{$toc}</div>\n\t\t\t\t{$str}";
52 }
53 /**
54 * Used on developer.matomo.org
55 *
56 * @param bool|true $outputExampleUrls
57 * @param string $prefixUrls
58 * @return string
59 */
60 public function getApiDocumentationAsStringForDeveloperReference($outputExampleUrls = \true, $prefixUrls = '')
61 {
62 list($toc, $str) = $this->generateDocumentation($outputExampleUrls, $prefixUrls, $displayTitlesAsEnrichedHeadline = \false);
63 return "<h2 id='topApiRef' name='topApiRef'>Quick access to APIs</h2>\n\t\t\t\t{$toc}\n\t\t\t\t{$str}";
64 }
65 protected function prepareModuleToDisplay($moduleName)
66 {
67 return "<a href='#{$moduleName}'>{$moduleName}</a><br/>";
68 }
69 protected function prepareMethodToDisplay($moduleName, $info, $methods, $class, $outputExampleUrls, $prefixUrls, $displayTitlesAsEnrichedHeadline)
70 {
71 $str = '';
72 $str .= "\n<a name='{$moduleName}' id='{$moduleName}'></a>";
73 if ($displayTitlesAsEnrichedHeadline) {
74 $str .= "<div vue-entry=\"CoreHome.ContentBlock\" content-title='Module " . $moduleName . "'>";
75 } else {
76 $str .= "<h2>Module " . $moduleName . "</h2>";
77 }
78 $info['__documentation'] = $this->checkDocumentation($info['__documentation']);
79 $str .= "<div class='apiDescription'> " . $info['__documentation'] . " </div>";
80 foreach ($methods as $methodName) {
81 if (\Piwik\API\Proxy::getInstance()->isDeprecatedMethod($class, $methodName)) {
82 continue;
83 }
84 $params = $this->getParametersString($class, $methodName);
85 $str .= "\n <div class='apiMethod'>- <b>{$moduleName}.{$methodName} </b>" . $params . "";
86 $str .= '<small>';
87 if ($outputExampleUrls) {
88 $str .= $this->addExamples($class, $methodName, $prefixUrls);
89 }
90 $str .= '</small>';
91 $str .= "</div>\n";
92 }
93 if ($displayTitlesAsEnrichedHeadline) {
94 $str .= "</div>";
95 }
96 return $str;
97 }
98 protected function prepareModulesAndMethods($info, $moduleName)
99 {
100 $toDisplay = array();
101 foreach ($info as $methodName => $infoMethod) {
102 if ($methodName == '__documentation') {
103 continue;
104 }
105 $toDisplay[$moduleName][] = $methodName;
106 }
107 return $toDisplay;
108 }
109 protected function addExamples($class, $methodName, $prefixUrls)
110 {
111 $token = Piwik::getCurrentUserTokenAuth();
112 $token_auth_url = "&token_auth=" . $token;
113 if ($token !== 'anonymous') {
114 $token_auth_url .= "&force_api_session=1";
115 }
116 $parametersToSet = array('idSite' => Common::getRequestVar('idSite', 1, 'int'), 'period' => Common::getRequestVar('period', 'day', 'string'), 'date' => Common::getRequestVar('date', 'today', 'string'));
117 $str = '';
118 $str .= "<span class=\"example\">";
119 $exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet);
120 if ($exampleUrl !== \false) {
121 $lastNUrls = '';
122 if (preg_match('/(&period)|(&date)/', $exampleUrl)) {
123 $exampleUrlRss = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10', 'period' => 'day') + $parametersToSet);
124 $lastNUrls = ", RSS of the last <a target='_blank' href='{$exampleUrlRss}&format=rss{$token_auth_url}&translateColumnNames=1'>10 days</a>";
125 }
126 $exampleUrl = $prefixUrls . $exampleUrl;
127 $str .= " [ Example in\n <a target='_blank' href='{$exampleUrl}&format=xml{$token_auth_url}'>XML</a>,\n <a target='_blank' href='{$exampleUrl}&format=JSON{$token_auth_url}'>Json</a>,\n <a target='_blank' href='{$exampleUrl}&format=Tsv{$token_auth_url}&translateColumnNames=1'>Tsv (Excel)</a>\n {$lastNUrls}\n ]";
128 } else {
129 $str .= " [ No example available ]";
130 }
131 $str .= "</span>";
132 return $str;
133 }
134 /**
135 * Check if Class contains @hide
136 *
137 * @param ReflectionClass $rClass instance of ReflectionMethod
138 * @return bool
139 */
140 public function checkIfClassCommentContainsHideAnnotation(ReflectionClass $rClass)
141 {
142 return \false !== strstr($rClass->getDocComment(), '@hide');
143 }
144 /**
145 * Check if Class contains @internal
146 *
147 * @param ReflectionClass|\ReflectionMethod $rClass instance of ReflectionMethod
148 * @return bool
149 */
150 private function checkIfCommentContainsInternalAnnotation($rClass)
151 {
152 return \false !== strstr($rClass->getDocComment(), '@internal');
153 }
154 /**
155 * Check if documentation contains @hide annotation and deletes it
156 *
157 * @param $moduleToCheck
158 * @return mixed
159 */
160 public function checkDocumentation($moduleToCheck)
161 {
162 if (strpos($moduleToCheck, '@hide') == \true) {
163 $moduleToCheck = str_replace(strtok(strstr($moduleToCheck, '@hide'), "\n"), "", $moduleToCheck);
164 }
165 return $moduleToCheck;
166 }
167 /**
168 * Returns a string containing links to examples on how to call a given method on a given API
169 * It will export links to XML, CSV, HTML, JSON, PHP, etc.
170 * It will not export links for methods such as deleteSite or deleteUser
171 *
172 * @param string $class the class
173 * @param string $methodName the method
174 * @param array $parametersToSet parameters to set
175 * @return string|bool when not possible
176 */
177 public function getExampleUrl($class, $methodName, $parametersToSet = array())
178 {
179 $knowExampleDefaultParametersValues = array('access' => 'view', 'userLogin' => 'test', 'passwordMd5ied' => 'passwordExample', 'email' => 'test@example.org', 'languageCode' => 'fr', 'url' => 'https://divezone.net/', 'pageUrl' => 'https://divezone.net/', 'apiModule' => 'UserCountry', 'apiAction' => 'getCountry', 'lastMinutes' => '30', 'abandonedCarts' => '0', 'segmentName' => 'pageTitle', 'ip' => '194.57.91.215', 'idSites' => '1,2', 'idAlert' => '1', 'seconds' => '3600');
180 foreach ($parametersToSet as $name => $value) {
181 $knowExampleDefaultParametersValues[$name] = $value;
182 }
183 // no links for these method names
184 $doNotPrintExampleForTheseMethods = array(
185 //Sites
186 'deleteSite',
187 'addSite',
188 'updateSite',
189 'addSiteAliasUrls',
190 //Users
191 'deleteUser',
192 'addUser',
193 'updateUser',
194 'setUserAccess',
195 //Goals
196 'addGoal',
197 'updateGoal',
198 'deleteGoal',
199 //Marketplace
200 'deleteLicenseKey',
201 );
202 if (in_array($methodName, $doNotPrintExampleForTheseMethods)) {
203 return \false;
204 }
205 // we try to give an URL example to call the API
206 $aParameters = \Piwik\API\Proxy::getInstance()->getParametersList($class, $methodName);
207 $aParameters['format'] = \false;
208 $aParameters['hideIdSubDatable'] = \false;
209 $aParameters['serialize'] = \false;
210 $aParameters['language'] = \false;
211 $aParameters['translateColumnNames'] = \false;
212 $aParameters['label'] = \false;
213 $aParameters['labelSeries'] = \false;
214 $aParameters['flat'] = \false;
215 $aParameters['show_dimensions'] = \false;
216 $aParameters['include_aggregate_rows'] = \false;
217 $aParameters['filter_offset'] = \false;
218 $aParameters['filter_limit'] = \false;
219 $aParameters['filter_sort_column'] = \false;
220 $aParameters['filter_sort_order'] = \false;
221 $aParameters['filter_excludelowpop'] = \false;
222 $aParameters['filter_excludelowpop_value'] = \false;
223 $aParameters['filter_column_recursive'] = \false;
224 $aParameters['filter_pattern'] = \false;
225 $aParameters['filter_pattern_recursive'] = \false;
226 $aParameters['filter_truncate'] = \false;
227 $aParameters['hideColumns'] = \false;
228 $aParameters['hideColumnsRecursively'] = \false;
229 $aParameters['showColumns'] = \false;
230 $aParameters['pivotBy'] = \false;
231 $aParameters['pivotByColumn'] = \false;
232 $aParameters['pivotByColumnLimit'] = \false;
233 $aParameters['disable_queued_filters'] = \false;
234 $aParameters['disable_generic_filters'] = \false;
235 $aParameters['expanded'] = \false;
236 $aParameters['idDimenson'] = \false;
237 $aParameters['format_metrics'] = \false;
238 $aParameters['compare'] = \false;
239 $aParameters['compareDates'] = \false;
240 $aParameters['comparePeriods'] = \false;
241 $aParameters['compareSegments'] = \false;
242 $aParameters['comparisonIdSubtables'] = \false;
243 $aParameters['invert_compare_change_compute'] = \false;
244 $aParameters['filter_update_columns_when_show_all_goals'] = \false;
245 $aParameters['filter_show_goal_columns_process_goals'] = \false;
246 $aParameters['showMetadata'] = \false;
247 $extraParameters = StaticContainer::get('entities.idNames');
248 $extraParameters = array_merge($extraParameters, StaticContainer::get('DocumentationGenerator.customParameters'));
249 foreach ($extraParameters as $paramName) {
250 if (isset($aParameters[$paramName])) {
251 continue;
252 }
253 $aParameters[$paramName] = \false;
254 }
255 $moduleName = \Piwik\API\Proxy::getInstance()->getModuleNameFromClassName($class);
256 $aParameters = array_merge(array('module' => 'API', 'method' => $moduleName . '.' . $methodName), $aParameters);
257 foreach ($aParameters as $nameVariable => &$defaultValue) {
258 if (isset($knowExampleDefaultParametersValues[$nameVariable])) {
259 $defaultValue = $knowExampleDefaultParametersValues[$nameVariable];
260 } elseif ($defaultValue instanceof \Piwik\API\NoDefaultValue) {
261 // if there isn't a default value for a given parameter,
262 // we need a 'know default value' or we can't generate the link
263 return \false;
264 }
265 }
266 return '?' . Url::getQueryStringFromParameters($aParameters);
267 }
268 /**
269 * Returns the methods $class.$name parameters (and default value if provided) as a string.
270 *
271 * @param string $class The class name
272 * @param string $name The method name
273 * @return string For example "(idSite, period, date = 'today')"
274 */
275 protected function getParametersString($class, $name)
276 {
277 $aParameters = \Piwik\API\Proxy::getInstance()->getParametersListWithTypes($class, $name);
278 $asParameters = array();
279 foreach ($aParameters as $nameVariable => $parameter) {
280 // Do not show API parameters starting with _
281 // They are supposed to be used only in internal API calls
282 if (strpos($nameVariable, '_') === 0) {
283 continue;
284 }
285 $str = '';
286 if (!empty($parameter['type'])) {
287 $prefix = $parameter['allowsNull'] ? '?' : '';
288 $str = '<i>' . $prefix . $parameter['type'] . '</i> ';
289 }
290 $str .= $nameVariable;
291 $defaultValue = $parameter['default'];
292 if (!$defaultValue instanceof \Piwik\API\NoDefaultValue) {
293 if (is_array($defaultValue)) {
294 $str .= " = 'Array'";
295 } elseif (!empty($parameter['type']) && $parameter['allowsNull']) {
296 $str .= "";
297 // don't display default value, as the ? before the type hint indicates it's optional
298 } elseif ($parameter['type'] === 'bool' && $defaultValue === \true) {
299 $str .= " = true";
300 } elseif ($parameter['type'] === 'bool' && $defaultValue === \false) {
301 $str .= " = false";
302 } else {
303 $str .= " = '{$defaultValue}'";
304 }
305 }
306 $asParameters[] = $str;
307 }
308 $sParameters = implode(", ", $asParameters);
309 return "({$sParameters})";
310 }
311 /**
312 * @param $outputExampleUrls
313 * @param $prefixUrls
314 * @param $displayTitlesAsEnrichedHeadline
315 * @return array
316 */
317 protected function generateDocumentation($outputExampleUrls, $prefixUrls, $displayTitlesAsEnrichedHeadline)
318 {
319 $str = $toc = '';
320 foreach (\Piwik\API\Proxy::getInstance()->getMetadata() as $class => $info) {
321 $moduleName = \Piwik\API\Proxy::getInstance()->getModuleNameFromClassName($class);
322 $rClass = new ReflectionClass($class);
323 if (!Piwik::hasUserSuperUserAccess() && $this->checkIfClassCommentContainsHideAnnotation($rClass)) {
324 continue;
325 }
326 if ($this->checkIfCommentContainsInternalAnnotation($rClass)) {
327 continue;
328 }
329 $toDisplay = $this->prepareModulesAndMethods($info, $moduleName);
330 foreach ($toDisplay as $moduleName => $methods) {
331 foreach ($methods as $index => $method) {
332 if (!method_exists($class, $method)) {
333 // method is handled through API.Request.intercept event
334 continue;
335 }
336 $reflectionMethod = new \ReflectionMethod($class, $method);
337 if ($this->checkIfCommentContainsInternalAnnotation($reflectionMethod)) {
338 unset($toDisplay[$moduleName][$index]);
339 }
340 }
341 if (empty($toDisplay[$moduleName])) {
342 unset($toDisplay[$moduleName]);
343 }
344 }
345 foreach ($toDisplay as $moduleName => $methods) {
346 $toc .= $this->prepareModuleToDisplay($moduleName);
347 $str .= $this->prepareMethodToDisplay($moduleName, $info, $methods, $class, $outputExampleUrls, $prefixUrls, $displayTitlesAsEnrichedHeadline);
348 }
349 }
350 return array($toc, $str);
351 }
352 }
353