Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renderer format is not valid - fatal php error after 2.14.0 #8365

Closed
propertunist opened this issue Jul 16, 2015 · 20 comments
Closed

renderer format is not valid - fatal php error after 2.14.0 #8365

propertunist opened this issue Jul 16, 2015 · 20 comments
Milestone

Comments

@propertunist
Copy link

as requested, here is the source code for this issue (#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
Copy link

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
Copy link
Member

tsteur commented Jul 20, 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
Copy link
Author

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
Copy link

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
Copy link

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
Copy link
Member

tsteur commented Jul 21, 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
Copy link
Member

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
Copy link
Member

tsteur commented Jul 21, 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: #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
Copy link
Member

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
Copy link
Member

tsteur commented Jul 21, 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
Copy link

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
Copy link
Member

tsteur commented Jul 21, 2015

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

@tsteur tsteur closed this as completed Jul 21, 2015
@braekling
Copy link

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

@propertunist
Copy link
Author

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
Copy link

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

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

@propertunist
Copy link
Author

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
Copy link
Member

@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
Copy link
Author

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
Copy link
Member

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
Copy link
Author

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. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants