@propertunist opened this issue on July 16th 2015

as requested, here is the source code for this issue (https://github.com/piwik/piwik/issues/8333) that has persisted beyond the recent release of piwik - 2.14.0.

this code includes the workaround that appeared in the forum that i have mentioned several times already. without the workaround, there is a fatal error relating to a container not being available yet (or similar error) and with the workaround, i see a fatal error of this form:

renderer format 'json' not valid. try any of the following instead.

my code here worked fine before the 2.14.0 upgrade.

 define('PIWIK_INCLUDE_PATH', $piwik_local_path);

    define('PIWIK_ENABLE_DISPATCH', false);
    define('PIWIK_ENABLE_ERROR_HANDLER', false);
    define('PIWIK_ENABLE_SESSION_START', false);

    // if you prefer not to include 'index.php', you must also define here PIWIK_DOCUMENT_ROOT
    // and include "libs/upgradephp/upgrade.php" and "core/Loader.php"
    require_once PIWIK_INCLUDE_PATH . "/index.php";
    require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";

    // Workaround for 2.14.0 bug
    $environment = new \Piwik\Application\Environment('tracker');
    $environment->init();

    FrontController::getInstance()->init();
    $current_IP = \Spam\LoginFilter\get_ip();
    if (($current_IP) && ($piwik_secret))
    {
        // This inits the API Request with the specified parameters
        $request = new Request('
                                module=API
                                &method=UserCountry.getLocationFromIP
                                &ip=' . $current_IP . '
                                &format=Json
                                &token_auth=' . $piwik_secret
        );


        // Calls the API and fetch XML data back
        $result = $request->process();
@braekling commented on July 19th 2015

This is the PHP API call in WP-Piwik:

        private function call($id, $url, $params) {
            if (!defined('PIWIK_INCLUDE_PATH'))
                return false;
            if (PIWIK_INCLUDE_PATH === FALSE)
                return json_encode(array('result' => 'error', 'message' => __('Could not resolve','wp-piwik').' "'.htmlentities(self::$settings->getGlobalOption('piwik_path')).'": '.__('realpath() returns false','wp-piwik').'.'));
            if (file_exists(PIWIK_INCLUDE_PATH . "/index.php"))
                require_once PIWIK_INCLUDE_PATH . "/index.php";
            if (file_exists(PIWIK_INCLUDE_PATH . "/core/API/Request.php"))
                require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";
            if (class_exists('Piwik\FrontController'))
                \Piwik\FrontController::getInstance()->init();
            else json_encode(array('result' => 'error', 'message' => __('Class Piwik\FrontController does not exists.','wp-piwik')));
            if (class_exists('Piwik\API\Request'))
                $request = new \Piwik\API\Request($params.'&token_auth='.self::$settings->getGlobalOption('piwik_token'));
            else json_encode(array('result' => 'error', 'message' => __('Class Piwik\API\Request does not exists.','wp-piwik')));
            if (isset($request))
                $result = $request->process();
            else $result = null;
            if (!headers_sent())
                header("Content-Type: text/html", true);
            $result = $this->unserialize($result);
            if ($GLOBALS ['wp-piwik_debug'])
                self::$debug[$id] = array ( $params.'&token_auth=...' );
            return $result;
        }

This is the constants definition:

    public static function definePiwikConstants() {
        if (! defined ( 'PIWIK_INCLUDE_PATH' )) {
            @header ( 'Content-type: text/xml' );
            define ( 'PIWIK_INCLUDE_PATH', self::$settings->getGlobalOption ( 'piwik_path' ) );
            define ( 'PIWIK_USER_PATH', self::$settings->getGlobalOption ( 'piwik_path' ) );
            define ( 'PIWIK_ENABLE_DISPATCH', false );
            define ( 'PIWIK_ENABLE_ERROR_HANDLER', false );
            define ( 'PIWIK_ENABLE_SESSION_START', false );
        }
    }

If I add the workaround lines, the initial error changes to "renderer format not valid"... it does not care which renderer format I use (tested with PHP and JSON).

Hope this helps :-(

@tsteur commented on July 20th 2015

Can you maybe try to do a manual update see http://piwik.org/docs/update/#the-manual-three-step-update ? Maybe some files are missing somehow. It should work otherwise. I had a rough look through the code. If replacing some files doesn't work maybe you can provide us the content of the whole file you're using (not sure if the one above is everything within the file) and an example URL request. Then we can try to reproduce and debug

@propertunist commented on July 20th 2015

i have already performed a manual update - so that is not the issue here. the code i have already provided is the only relevant code i have. the IP address is grabbed by an external function and the piwik 'secret' is grabbed from my app in an earlier line - but other than that the code is all here. to link you to a page on my test server where the code is running would result in nothing other than you seeing an error page and would require my server to be offline, since the code in question runs on all pages and breaks the page.

@braekling commented on July 20th 2015

A manual update did not solve the issue.

Demo code 1 (using my real auth token & path, of course):

@header ( 'Content-type: text/xml' );
define ( 'PIWIK_INCLUDE_PATH', '/var/www/vhosts/mysite/httpdocs/piwik' );
define ( 'PIWIK_USER_PATH', '/var/www/vhosts/mysite/httpdocs/piwik' );
define ( 'PIWIK_ENABLE_DISPATCH', false );
define ( 'PIWIK_ENABLE_ERROR_HANDLER', false );
define ( 'PIWIK_ENABLE_SESSION_START', false );

if (file_exists(PIWIK_INCLUDE_PATH . "/index.php"))
        require_once PIWIK_INCLUDE_PATH . "/index.php";
if (file_exists(PIWIK_INCLUDE_PATH . "/core/API/Request.php"))
        require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";
if (class_exists('Piwik\FrontController'))
        \Piwik\FrontController::getInstance()->init();
if (class_exists('Piwik\API\Request'))
        $request = new \Piwik\API\Request('module=API&method=ExampleAPI.getPiwikVersion&format=JSON&token_auth=anonymous');
if (isset($request))
        $result = $request->process();
else $result = null;
var_dump($result);

This causes:

PHP Fatal error:  Uncaught exception 'Piwik\Container\ContainerDoesNotExistException' with message 'The root container has not been created yet.' in /var/www/vhosts/mysite/httpdocs/piwik/core/Container/StaticContainer.php:40
Stack trace:
#0 /var/www/vhosts/mysite/httpdocs/piwik/core/Container/StaticContainer.php(80): Piwik\Container\StaticContainer::getContainer()
#1 /var/www/vhosts/mysite/httpdocs/piwik/core/FrontController.php(206): Piwik\Container\StaticContainer::get('path.tmp')
#2 /var/www/vhosts/mysite/httpdocs/testlab/piwik.php(15): Piwik\FrontController->init()
#3 {main}
  thrown in /var/www/vhosts/mysite/httpdocs/piwik/core/Container/StaticContainer.php on line 40
PHP Fatal error:  Uncaught exception 'Piwik\Container\ContainerDoesNotExistException' with message 'The root container has not been created yet.' in /var/www/vhosts/mysite/httpdocs/piwik/core/Container/StaticContainer.php:40
Stack trace:
#0 /var/www/vhosts/mysite/httpdocs/piwik/core/Container/StaticContainer.php(80): Piwik\Container\StaticContainer::getContainer()
#1 /var/www/vhosts/mysite/httpdocs/piwik/core/Config.php(62): Piwik\Container\StaticContainer::get('Piwik\Config')
#2 /var/www/vhosts/mysite/httpdocs/piwik/core/Url.php(342): Piwik\Config::getInstance()
#3 /var/www/vhosts/mysite/httpdocs/piwik/core/Url.php(62): Piwik\Url::getCurrentHost()
#4 /var/www/vhosts/mysite/httpdocs/piwik/core/FrontController.php(87): Piwik\Url::getCurrentUrl()
#5 /var/www/vhosts/mysite/httpdocs/piwik/core/FrontController.php(181): Piwik\FrontController->dispatch('CorePluginsAdmi...', 'safemode', Array)
#6 [internal function]: Piwik\FrontController::triggerSafeModeWhenError()
#7 {main}
  thrown in /var/www/vhosts/mysite/httpdocs/piwik/core/Container/StaticContainer.php on line 40

If I add the "environment workaround" like this...

if (class_exists('Piwik\FrontController')) {
        $environment = new \Piwik\Application\Environment('tracker');
        $environment->init();
        \Piwik\FrontController::getInstance()->init();
}

... the test script works as expected.

But in WP-Piwik itself it still does not work. By adding these Environment-lines, it just switches to

Fatal error: Uncaught exception 'Exception' with message 'Renderer format 'php' not valid. Try any of the following instead: .' in /var/www/vhosts/mysite/httpdocs/piwik/core/API/ApiRenderer.php:122 Stack trace: #0 /var/www/vhosts/mysite/httpdocs/piwik/core/API/ResponseBuilder.php(40): Piwik\API\ApiRenderer::factory('php', Array) #1 /var/www/vhosts/mysite/httpdocs/piwik/core/API/Request.php(211): Piwik\API\ResponseBuilder->__construct('php', Array) #2 /var/www/vhosts/mysite/httpdocs/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Php.php(39): Piwik\API\Request->process() #3 /var/www/vhosts/mysite/httpdocs/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request/Php.php(14): WP_Piwik\Request\Php->call('method=VisitsSu...', 'http://www.brae...', 'module=API&form...') #4 /var/www/vhosts/mysite/httpdocs/wp-content/plugins/wp-piwik/classes/WP_Piwik/Request.php(63): WP_Piwik\Request\Php->request('method=VisitsSu...') #5 /var/www/vhosts/mysite/httpdocs/wp-content/plugins/wp-piwik/classes in /var/www/vhosts/mysite/httpdocs/piwik/core/API/ApiRenderer.php on line 122

I will try to figure out the difference between my WP-Piwik code and the one shown above (all lines above are copied and pasted from my WP-Piwik code, but changed to work standalone, of course). If I figure out something new, I will tell you asap.

(Updating manually did not change anything.)

@braekling commented on July 20th 2015

Ok, now I got it: If the environment init is performed a second time, the renderer error appears.

Try this code:

$urls = array(
        0 => "module=API&format=php&method=VisitsSummary.get&idSite=1&idSite=1&period=day&date=yesterday&description=today&token_auth=anonymous",
        1 => "module=API&format=php&method=VisitsSummary.getVisits&idSite=1&idSite=1&period=day&date=last30&limit=&token_auth=anonymous"
);

foreach ($urls as $url) {
        if (file_exists(PIWIK_INCLUDE_PATH . "/index.php"))
                require_once PIWIK_INCLUDE_PATH . "/index.php";
        if (file_exists(PIWIK_INCLUDE_PATH . "/core/API/Request.php"))
                require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";
        if (class_exists('Piwik\FrontController')) {
                $environment = new \Piwik\Application\Environment('tracker');
                $environment->init();
                \Piwik\FrontController::getInstance()->init();
        }
        if (class_exists('Piwik\API\Request'))
                $request = new \Piwik\API\Request($url);
        if (isset($request))
                $result = $request->process();
        else $result = null;
        var_dump($result);
}

The first request will perform as expected, the second one will throw the exception.

Of course, I can prevent my code to call the workaround twice... but there are two questions left: 1. Is there a way to check if the environment is already initialized? 2. Is this workaround backward compatible? I'm afraid performing an update with this workaround will break older setups.

@tsteur commented on July 21st 2015

Is there a way to check if the environment is already initialized?

@diosmosis do you maybe know this?

Is this workaround backward compatible? I'm afraid performing an update with this workaround will break older setups.

Using Piwik\Application\Environment in general is most likely not backwards compatible since this class is not marked as API and also not listed on http://developer.piwik.org/api-reference/classes

At the same time it is needed to do bootstrap Piwik as shown in our internal API call example code: https://github.com/piwik/piwik/blob/2.14.1-rc1/misc/others/api_internal_call.php#L16

@diosmosis Should we mark this class as API? Is it stable? Can be maybe find a way where it is not needed to create an environment? I'm not into this part so if not that's okay

@diosmosis commented on July 21st 2015

Re @braekling

Is there a way to check if the environment is already initialized?

You can use a try-catch w/ StaticContainer::getContainer(). It is, however, a better idea to store a single environment instance instead of recreating it each time you need to issue a local API request. The Environment should be created & initialized once per HTTP request that is sent to the app that is accessing Piwik locally.

Is this workaround backward compatible? I'm afraid performing an update with this workaround will break older setups.

For BC, you can separate the FrontController & Environment initialization, eg:

if (class_exists('Piwik\Application\Environment')) {
    $environment = new \Piwik\Application\Environment();
    $environment->init();
}

if (class_exists('Piwik\FrontController')) {
    \Piwik\FrontController::getInstance()->init();
}

Re @tsteur

Should we mark this class as API? Is it stable? Can be maybe find a way where it is not needed to create an environment? I'm not into this part so if not that's okay

The environment encapsulates the Piwik environment setup, so it's always needed (just as FrontController::init() or the random tracker functions were needed before).

Regarding API, it's mostly stable, but it likely doesn't need to be made API (though PiwikPRO will use it). Ideally, there would be none of this and just a WebApplication class, and "local requests" would be made like this:

$webApplication = new Piwik\Application\WebApplication();
$webApplication->init();
$result = $webApplication->dispatchApi(...);

However, I couldn't get that done within one release.

@tsteur commented on July 21st 2015

The environment encapsulates the Piwik environment setup, so it's always needed (just as FrontController::init() or the random tracker functions were needed before).

Sweet, was just wondering whether it could be done on FrontController::init() in case the environment was not initialized yet but I presume it wouldn't always be the same environment (although for test we could make sure it was loaded before I reckon). Nonetheless a FrontController shouldn't care about environment init so all good. As you say a WebApplication::init() that does both makes most likely more sense.

Regarding API, it's mostly stable, but it likely doesn't need to be made API (though PiwikPRO will use it). Ideally, there would be none of this and just a WebApplication class, and "local requests" would be made like this:

FYI: A while ago I created this issue: https://github.com/piwik/piwik/issues/7268 but that's for talking to the external API. I forgot why one would use the internal API at all and not connect to the external API. There shouldn't be a huge speed difference but I can imagine it's good if one eg doesn't want to have the token_auth in log entries etc. I think in the example of this issue it is to change the IP but that might work with the external API as well

@diosmosis commented on July 21st 2015

Sweet, was just wondering whether it could be done on FrontController::init() in case the environment was not initialized yet

Just FYI, FrontController::init() will be removed eventually, in favor of application encapsulation. (I guess this is what you meant by "FrontController shouldn't care about environment init"?).

FYI: A while ago I created this issue: #7268 but that's for talking to the external API.

In general I think sending actual web requests seems a better idea to me than loading Piwik's PHP files to access an instance.

@tsteur commented on July 21st 2015

(I guess this is what you meant by "FrontController shouldn't care about environment init"?).

Yep

@braekling I wonder if it was possible to achieve the same by calling the external HTTP API see eg http://developer.piwik.org/api-reference/reporting-api ?

@braekling commented on July 21st 2015

Ok, thank you both.

I will check if the environment class exists and store the instance in a static variable (I'm calling the API in a class context) - so I can check if the environment is already initialized. This should work for all Piwik versions. :-)

@tsteur WP-Piwik offers the users to choose between HTTP and PHP Reporting API. Some users are not able or don't want to connect to Piwik using the HTTP API for several reasons, e.g., because of mod_security restrictions. But as described before, it should work this way.

@tsteur commented on July 21st 2015

makes sense. Can we close this issue in this case? Please let me know if not and I reopen

@braekling commented on July 21st 2015

From my side this is solved - thanks! But I don't know if this also works for @propertunist ... ?

@propertunist commented on July 21st 2015

i am only calling one API call per page, so from what i have understood, i don't need to use a static variable for the environment. however, i have used the code that was provided in this thread (at least the code that i detected that was being offered as part of the solution here) and the problem of the api renderer format being invalid remains. this i the code i have used:

if (class_exists('Piwik\Application\Environment')) {
    $environment = new \Piwik\Application\Environment();
    $environment->init();
}

if (class_exists('Piwik\FrontController')) {
    \Piwik\FrontController::getInstance()->init();
}

did i miss something else?

@braekling commented on July 21st 2015

Environment's constructor requires a parameter, maybe just adding null will already help?

$environment = new \Piwik\Application\Environment(null);

@propertunist commented on July 21st 2015

oh, my mistake - the code i pasted was not the actual code i am using - i copied it from the thread here. my actual code had 'tracker' as a value passed into the constructor - so using null made no change.

@diosmosis commented on July 21st 2015

@propertunist Can you add an echo to the script before $request = new Request( and load a page in your website to see how many times it is being executed?

@propertunist commented on July 21st 2015

yes, it is being run twice. i did identify that and point it out in my original ticket - but don't know enough about the design of piwik to explain why or what to do.

@diosmosis commented on July 21st 2015

Piwik shouldn't be the cause of it running twice, Piwik won't execute the script that accesses Piwik, only your website will. Can you print out a stack trace immediately after the enviornment is initialized? eg, echo "<pre>";debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit = 20);echo "</pre>";? You have to figure out why your script is being called more than once and either eliminate the redundancy or move the Environment initialization to the scope that executes the script, and outside of any loop.

If you don't want to do this, however, you can, as stated above use a try-catch w/ StaticContainer::getContainer(), and only init an environment if it throws.

@propertunist commented on July 21st 2015

aha, i just found that the cause is that i am calling the piwik function twice per page (i forgot i put the other call in there). i have added a static variable to my function and now it all works ok. :)

This issue was closed on July 21st 2015
Powered by GitHub Issue Mirror