From fa556facfb7e1a2365c0e3e660436b19d3c9e6fb Mon Sep 17 00:00:00 2001
From: d-skora <d.skora@clearcode.cc>
Date: Fri, 3 Oct 2014 16:12:35 +0200
Subject: [PATCH] hide annotation with tests

---
 core/API/DocumentationGenerator.php           | 117 ++++++++++-
 core/API/Proxy.php                            |  46 +++++
 plugins/CoreAdminHome/API.php                 |   1 +
 plugins/CoreAdminHome/CoreAdminHome.php       |  13 +-
 plugins/DBStats/API.php                       |   1 +
 plugins/DBStats/DBStats.php                   |  13 +-
 .../Core/API/DocumentationGeneratorTest.php   | 195 ++++++++++++++++++
 7 files changed, 375 insertions(+), 11 deletions(-)
 create mode 100644 tests/PHPUnit/Core/API/DocumentationGeneratorTest.php

diff --git a/core/API/DocumentationGenerator.php b/core/API/DocumentationGenerator.php
index 63ae943433..96cad6393d 100644
--- a/core/API/DocumentationGenerator.php
+++ b/core/API/DocumentationGenerator.php
@@ -12,10 +12,10 @@ use Exception;
 use Piwik\Common;
 use Piwik\Piwik;
 use Piwik\Url;
+use ReflectionClass;
 
 class DocumentationGenerator
 {
-    protected $modulesToHide = array('CoreAdminHome', 'DBStats');
     protected $countPluginsLoaded = 0;
 
     /**
@@ -49,21 +49,20 @@ class DocumentationGenerator
         }
 
         $str = $toc = '';
-        $parametersToSet = array(
-            'idSite' => Common::getRequestVar('idSite', 1, 'int'),
-            'period' => Common::getRequestVar('period', 'day', 'string'),
-            'date'   => Common::getRequestVar('date', 'today', 'string')
-        );
 
         foreach (Proxy::getInstance()->getMetadata() as $class => $info) {
             $moduleName = Proxy::getInstance()->getModuleNameFromClassName($class);
+            $rClass = new ReflectionClass($class);
 
-            if (in_array($moduleName, $this->modulesToHide)) {
+            if (!Piwik::hasUserSuperUserAccess() && $this->checkIfClassCommentContainsHideAnnotation($rClass)) {
                 continue;
             }
 
-            $toc .= "<a href='#$moduleName'>$moduleName</a><br/>";
-            $str .= $this->getInterfaceString($moduleName, $class, $info, $parametersToSet, $outputExampleUrls, $prefixUrls);
+            $toDisplay = $this->prepareModulesAndMethods($info, $moduleName);
+            foreach ($toDisplay as $moduleName => $methods) {
+                $toc .= $this->prepareModuleToDisplay($moduleName);
+                $str .= $this->prepareMethodToDisplay($moduleName, $info, $methods, $class, $outputExampleUrls, $prefixUrls);
+            }
         }
 
         $str = "<h2 id='topApiRef' name='topApiRef'>Quick access to APIs</h2>
@@ -73,6 +72,106 @@ class DocumentationGenerator
         return $str;
     }
 
+    public function prepareModuleToDisplay($moduleName)
+    {
+        return "<a href='#$moduleName'>$moduleName</a><br/>";
+    }
+
+    public function prepareMethodToDisplay($moduleName, $info, $methods, $class, $outputExampleUrls, $prefixUrls)
+    {
+        $str = '';
+        $str .= "\n<a name='$moduleName' id='$moduleName'></a><h2>Module " . $moduleName . "</h2>";
+        $info['__documentation'] = $this->checkDocumentation($info['__documentation']);
+        $str .= "<div class='apiDescription'> " . $info['__documentation'] . " </div>";
+        foreach ($methods as $methodName) {
+            $params = $this->getParametersString($class, $methodName);
+
+            $str .= "\n <div class='apiMethod'>- <b>$moduleName.$methodName </b>" . $params . "";
+            $str .= '<small>';
+            if ($outputExampleUrls) {
+                $str .= $this->addExamples($class, $methodName, $prefixUrls);
+            }
+            $str .= '</small>';
+            $str .= "</div>\n";
+        }
+
+        return $str;
+    }
+
+    public function prepareModulesAndMethods($info, $moduleName)
+    {
+        $toDisplay = array();
+
+        foreach ($info as $methodName => $infoMethod) {
+            if ($methodName == '__documentation') {
+                continue;
+            }
+            $toDisplay[$moduleName][] = $methodName;
+        }
+
+        return $toDisplay;
+    }
+
+    public function addExamples($class, $methodName, $prefixUrls)
+    {
+        $token_auth = "&token_auth=" . Piwik::getCurrentUserTokenAuth();
+        $parametersToSet = array(
+            'idSite' => Common::getRequestVar('idSite', 1, 'int'),
+            'period' => Common::getRequestVar('period', 'day', 'string'),
+            'date' => Common::getRequestVar('date', 'today', 'string')
+        );
+        $str = '';
+// used when we include this output in the Piwik official documentation for example
+        $str .= "<span class=\"example\">";
+        $exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet);
+        if ($exampleUrl !== false) {
+            $lastNUrls = '';
+            if (preg_match('/(&period)|(&date)/', $exampleUrl)) {
+                $exampleUrlRss = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10', 'period' => 'day') + $parametersToSet);
+                $lastNUrls = ", RSS of the last <a target=_blank href='$exampleUrlRss&format=rss$token_auth&translateColumnNames=1'>10 days</a>";
+            }
+            $exampleUrl = $prefixUrls . $exampleUrl;
+            $str .= " [ Example in
+                                                                    <a target=_blank href='$exampleUrl&format=xml$token_auth'>XML</a>,
+                                                                    <a target=_blank href='$exampleUrl&format=JSON$token_auth'>Json</a>,
+                                                                    <a target=_blank href='$exampleUrl&format=Tsv$token_auth&translateColumnNames=1'>Tsv (Excel)</a>
+                                                                    $lastNUrls
+                                                                    ]";
+        } else {
+            $str .= " [ No example available ]";
+        }
+        $str .= "</span>";
+        return $str;
+    }
+
+    /**
+     * Check if Class contains @hide
+     *
+     * @param ReflectionClass $rClass instance of ReflectionMethod
+     * @return bool
+     */
+    public function checkIfClassCommentContainsHideAnnotation(ReflectionClass $rClass)
+    {
+        if (strstr($rClass->getDocComment(), '@hide') === false) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Check if documentation contains @hide annotation and deletes it
+     *
+     * @param $moduleToCheck
+     * @return mixed
+     */
+    public function checkDocumentation($moduleToCheck)
+    {
+        if (strpos($moduleToCheck, '@hide') == true) {
+            $moduleToCheck = str_replace(strtok(strstr($moduleToCheck, '@hide'), "\n"), "", $moduleToCheck);
+        }
+        return $moduleToCheck;
+    }
+
     private function getInterfaceString($moduleName, $class, $info, $parametersToSet, $outputExampleUrls, $prefixUrls)
     {
         $str = '';
diff --git a/core/API/Proxy.php b/core/API/Proxy.php
index daaa5c9f42..1a87352abe 100644
--- a/core/API/Proxy.php
+++ b/core/API/Proxy.php
@@ -361,6 +361,51 @@ class Proxy extends Singleton
         $this->metadataArray = array();
     }
 
+    /**
+     * Check if method contains @hide
+     *
+     * @param ReflectionMethod $method instance of ReflectionMethod
+     * @return bool
+     */
+    public function checkIfMethodContainsHideAnnotation($method)
+    {
+        $docComment = $method->getDocComment();
+        $hideLine = strstr($docComment, '@hide');
+        if($hideLine) {
+            $hideString = trim(str_replace("@hide", "", strtok($hideLine, "\n")));
+            if($hideString) {
+                $response = false;
+                $hideArray = explode(" ", $hideString);
+                $hideString = $hideArray[0];
+                /**
+                 * Triggered to check if plugin should be hidden from the API for the current user.
+                 *
+                 * This event exists for checking if the user should be able to see the plugin API.
+                 * If &$response is set to false then the user will be able to see the plugin API.
+                 * If &$response is set to true then the plugin API will be hidden for the user.
+                 *
+                 * **Example**
+                 *
+                 *     public function checkIfNotSuperUser(&$response)
+                 *     {
+                 *          try {
+                 *                  Piwik::checkUserHasSuperUserAccess();
+                 *                  $response = false;
+                 *          } catch (\Exception $e) {
+                 *                  $response = true;
+                 *          }
+                 *      }
+                 *
+                 * @param bool &$response Boolean value containing information
+                 * if the plugin API should be hidden from the current user.
+                 */
+                Piwik::postEvent(sprintf('API.DocumentationGenerator.hide%s', $hideString), array(&$response));
+                return $response;
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns an array containing the values of the parameters to pass to the method to call
      *
@@ -433,6 +478,7 @@ class Proxy extends Singleton
             && $method->getName() != 'getInstance'
             && false === strstr($method->getDocComment(), '@deprecated')
             && (!$this->hideIgnoredFunctions || false === strstr($method->getDocComment(), '@ignore'))
+            && (Piwik::hasUserSuperUserAccess() || false === $this->checkIfMethodContainsHideAnnotation($method))
         ) {
             $name = $method->getName();
             $parameters = $method->getParameters();
diff --git a/plugins/CoreAdminHome/API.php b/plugins/CoreAdminHome/API.php
index a25a522cb4..0586d0b3b1 100644
--- a/plugins/CoreAdminHome/API.php
+++ b/plugins/CoreAdminHome/API.php
@@ -22,6 +22,7 @@ use Piwik\Site;
 use Piwik\TaskScheduler;
 
 /**
+ * @hide ExceptForSuperUser
  * @method static \Piwik\Plugins\CoreAdminHome\API getInstance()
  */
 class API extends \Piwik\Plugin\API
diff --git a/plugins/CoreAdminHome/CoreAdminHome.php b/plugins/CoreAdminHome/CoreAdminHome.php
index 86b5f26ec7..8057a9b93a 100644
--- a/plugins/CoreAdminHome/CoreAdminHome.php
+++ b/plugins/CoreAdminHome/CoreAdminHome.php
@@ -9,6 +9,7 @@
 namespace Piwik\Plugins\CoreAdminHome;
 
 use Piwik\Db;
+use Piwik\Piwik;
 use Piwik\Settings\UserSetting;
 
 /**
@@ -24,7 +25,8 @@ class CoreAdminHome extends \Piwik\Plugin
         return array(
             'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
             'AssetManager.getJavaScriptFiles' => 'getJsFiles',
-            'UsersManager.deleteUser'         => 'cleanupUser'
+            'UsersManager.deleteUser'         => 'cleanupUser',
+            'API.DocumentationGenerator.hideExceptForSuperUser' => 'checkIfNotSuperUser'
         );
     }
 
@@ -60,4 +62,13 @@ class CoreAdminHome extends \Piwik\Plugin
         $jsFiles[] = "plugins/CoreAdminHome/javascripts/pluginSettings.js";
     }
 
+    public function checkIfNotSuperUser(&$response)
+    {
+        try {
+            Piwik::checkUserHasSuperUserAccess();
+            $response = false;
+        } catch (\Exception $e) {
+            $response = true;
+        }
+    }
 }
diff --git a/plugins/DBStats/API.php b/plugins/DBStats/API.php
index 442c78752a..f6443b12da 100644
--- a/plugins/DBStats/API.php
+++ b/plugins/DBStats/API.php
@@ -13,6 +13,7 @@ use Piwik\DataTable;
 use Piwik\Piwik;
 
 /**
+ * @hide ExceptForSuperUser
  * @see plugins/DBStats/MySQLMetadataProvider.php
  */
 require_once PIWIK_INCLUDE_PATH . '/plugins/DBStats/MySQLMetadataProvider.php';
diff --git a/plugins/DBStats/DBStats.php b/plugins/DBStats/DBStats.php
index 1f5f9d7609..45269630b5 100644
--- a/plugins/DBStats/DBStats.php
+++ b/plugins/DBStats/DBStats.php
@@ -23,7 +23,8 @@ class DBStats extends \Piwik\Plugin
     {
         return array(
             'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
-            "TestingEnvironment.addHooks"     => 'setupTestEnvironment'
+            "TestingEnvironment.addHooks"     => 'setupTestEnvironment',
+            'API.DocumentationGenerator.hideExceptForSuperUser' => 'checkIfNotSuperUser'
         );
     }
 
@@ -39,4 +40,14 @@ class DBStats extends \Piwik\Plugin
             $dao = new Mocks\MockDataAccess();
         });
     }
+
+    public function checkIfNotSuperUser(&$response)
+    {
+        try {
+            Piwik::checkUserHasSuperUserAccess();
+            $response = false;
+        } catch (\Exception $e) {
+            $response = true;
+        }
+    }
 }
diff --git a/tests/PHPUnit/Core/API/DocumentationGeneratorTest.php b/tests/PHPUnit/Core/API/DocumentationGeneratorTest.php
new file mode 100644
index 0000000000..1179890171
--- /dev/null
+++ b/tests/PHPUnit/Core/API/DocumentationGeneratorTest.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Copyright (C) Piwik PRO - All rights reserved.
+ *
+ * Using this code requires that you first get a license from Piwik PRO.
+ * Unauthorized copying of this file, via any medium is strictly prohibited.
+ *
+ * @link http://piwik.pro
+ */
+use Piwik\API\DocumentationGenerator;
+use Piwik\API\Proxy;
+use Piwik\EventDispatcher;
+/**
+ * @group CoreD
+ */
+class DocumentationGeneratorTest extends PHPUnit_Framework_TestCase
+{
+    public function testCheckIfModuleContainsHideAnnotation()
+    {
+        $annotation = '@hide ExceptForSuperUser test test';
+        $mock = $this->getMockBuilder('ReflectionClass')
+            ->disableOriginalConstructor()
+            ->setMethods(array('getDocComment'))
+            ->getMock();
+        $mock->expects($this->once())->method('getDocComment')->willReturn($annotation);
+        $documentationGenerator = new DocumentationGenerator();
+        $this->assertTrue($documentationGenerator->checkIfClassCommentContainsHideAnnotation($mock));
+    }
+    public function testCheckDocumentation()
+    {
+        $moduleToCheck = 'this is documentation which contains @hide ExceptForSuperUser';
+        $documentationAfterCheck = 'this is documentation which contains ';
+        $documentationGenerator = new DocumentationGenerator();
+        $this->assertEquals($documentationGenerator->checkDocumentation($moduleToCheck), $documentationAfterCheck);
+    }
+    public function testCheckIfMethodCommentContainsHideAnnotation()
+    {
+        $annotation = '@hide ForAll test test';
+        $mock = $this->getMockBuilder('ReflectionMethod')
+            ->disableOriginalConstructor()
+            ->setMethods(array('getDocComment'))
+            ->getMock();
+        $mock->expects($this->once())->method('getDocComment')->willReturn($annotation);
+        EventDispatcher::getInstance()->addObserver('API.DocumentationGenerator.hideForAll',
+            function (&$response) {
+                $response = true;
+            });
+        $this->assertEquals(Proxy::getInstance()->checkIfMethodContainsHideAnnotation($mock), true);
+    }
+    public function testPrepareModuleToDisplay()
+    {
+        $moduleName = 'VisitTime';
+        $moduleToDisplay = "<a href='#VisitTime'>VisitTime</a><br/>";
+        $documentationGenerator = new DocumentationGenerator();
+        $this->assertEquals($documentationGenerator->prepareModuleToDisplay($moduleName), $moduleToDisplay);
+    }
+    /**
+     * @dataProvider providerPrepareModulesAndMethods
+     */
+    public function testPrepareModulesAndMethods($toDisplay, $actualModulesAndMethods)
+    {
+        $this->assertEquals($toDisplay, $actualModulesAndMethods);
+    }
+    public function providerPrepareModulesAndMethods()
+    {
+        $toDisplay = array(
+            'VisitTime'=>
+                array(
+                    'getVisitInformationPerLocalTime',
+                    'getVisitInformationPerServerTime',
+                    'getByDayOfWeek'
+                )
+        );
+        $info = array(
+            'getVisitInformationPerLocalTime' => array(
+                'idSite',
+                'period',
+                'date'
+            ),
+            'getVisitInformationPerServerTime' => array(
+                'idSite',
+                'period',
+                'date'
+            ),
+            'getByDayOfWeek' => array(
+                'idSite',
+                'period',
+                'date'
+            ),
+            '__documentation' =>
+                'VisitTime API lets you access reports by Hour (Server time), and by Hour Local Time of your visitors.',
+        );
+        $moduleName = 'VisitTime';
+        $documentationGenerator = New DocumentationGenerator();
+        $actualModulesAndMethods = $documentationGenerator->prepareModulesAndMethods($info, $moduleName);
+        return array(
+            array($toDisplay, $actualModulesAndMethods)
+        );
+    }
+    /**
+     * @dataProvider providerPrepareMethodToDisplay
+     */
+    public function testPrepareMethodToDisplay($elementShouldContainsInMethods, $methods)
+    {
+        $this->assertContains($elementShouldContainsInMethods, $methods);
+    }
+    public function providerPrepareMethodToDisplay()
+    {
+        $info = array(
+            'sendFeedbackForFeature' => array(
+                'featureName',
+                'like',
+            ),
+            '__documentation' => 'API for plugin Feedback',
+        );
+        $moduleName = 'Feedback';
+        $methods = array(
+            'sendFeedbackForFeature'
+        );
+        $class = '\Piwik\Plugins\Feedback\API';
+        $outputExampleUrls = true;
+        $prefixUrls = '';
+        $firstElementToAssert = "<a name='Feedback' id='Feedback'></a><h2>Module Feedback</h2>"
+            ."<div class='apiDescription'> API for plugin Feedback </div>";
+        $secondElementToAssert = "<div class='apiMethod'>- <b>Feedback.sendFeedbackForFeature </b>"
+            ."(featureName, like, message = '')"
+            ."<small><span class=\"example\"> [ No example available ]</span></small></div>";
+        $documentationGenerator = new DocumentationGenerator();
+        $preparedMethods = $documentationGenerator->prepareMethodToDisplay(
+            $moduleName,
+            $info,
+            $methods,
+            $class,
+            $outputExampleUrls,
+            $prefixUrls
+        );
+        return array(
+            array($firstElementToAssert, $preparedMethods),
+            array($secondElementToAssert, $preparedMethods)
+        );
+    }
+    /**
+     * @dataProvider providerAddExamples
+     */
+    public function testAddExamples($example, $examples)
+    {
+        $this->assertContains($example, $examples);
+    }
+    public function providerAddExamples()
+    {
+        $class = '\Piwik\Plugins\VisitTime\API';
+        $methodName = 'getVisitInformationPerLocalTime';
+        $prefixUrls = '';
+        $documentationGenerator = new DocumentationGenerator();
+        $xmlExample = "<a target=_blank href='?module=API&method=VisitTime.getVisitInformationPerLocalTime"
+            ."&idSite=1&period=day&date=today&format=xml&token_auth='>XML</a>";
+        $jsonExample = "<a target=_blank href='?module=API&method=VisitTime.getVisitInformationPerLocalTime"
+            ."&idSite=1&period=day&date=today&format=JSON&token_auth='>Json</a>";
+        $excelElement = "<a target=_blank href='?module=API&method=VisitTime.getVisitInformationPerLocalTime"
+            ."&idSite=1&period=day&date=today&format=Tsv&token_auth=&translateColumnNames=1'>Tsv (Excel)</a>";
+        $rss = "RSS of the last <a target=_blank href='?module=API&method=VisitTime.getVisitInformationPerLocalTime"
+            ."&idSite=1&period=day&date=last10&format=rss&token_auth=&translateColumnNames=1'>10 days</a>";
+        $examples = $documentationGenerator->addExamples($class, $methodName, $prefixUrls);
+        return array(
+            array($xmlExample, $examples),
+            array($jsonExample, $examples),
+            array($excelElement, $examples),
+            array($rss, $examples)
+        );
+    }
+    public function testGetExampleUrl()
+    {
+        $class = '\Piwik\Plugins\VisitTime\API';
+        $methodName = 'getVisitInformationPerLocalTime';
+        $parametersToSet = array(
+            'idSite' => 1,
+            'period' => 'day',
+            'date' => 'yesterday'
+        );
+        $expectedExampleUrl =
+            '?module=API&method=VisitTime.getVisitInformationPerLocalTime&idSite=1&period=day&date=yesterday';
+        $documentationGenerator = new DocumentationGenerator();
+        $this->assertEquals(
+            $expectedExampleUrl,
+            $documentationGenerator->getExampleUrl($class, $methodName, $parametersToSet));
+    }
+    public function testGetParametersString()
+    {
+        $class = '\Piwik\Plugins\VisitTime\API';
+        $name = 'getVisitInformationPerLocalTime';
+        $parameters = "(idSite, period, date, segment = '')";
+        $documentationGenerator = new DocumentationGenerator();
+        $this->assertEquals($parameters, $documentationGenerator->getParametersString($class, $name));
+    }
+}
\ No newline at end of file
-- 
GitLab