Jump to content

Theme Hook weirdness in 4.6


Go to solution Solved by CodingJungle,

Recommended Posts

On my localhost, with php 7.4 i have E_ALL enabled, and if i am not in_dev mode, my theme hooks for my toolbox app causes this error:

Cannot use “parent” when current class scope has no parent

i've tracked it down to the hookData() method:

    public static function hookData()
    { 
		return parent::hookData(); 
    }

i've been able to correct it with this code:

    public static function hookData()
    {
        if (is_callable('parent::hookData')) {
            return parent::hookData();
        }
        return [];
    }

now i am not entirely sure why i have to do this, it only seem to have started with 4.6 (maybe its a change i'm unware of, but i am not sure if it is happening in any of my other apps, as i don't often make theme hooks nor am i often not IN_DEV). now on a proper production server, E_DEPRECATED should be suppressed and this isn't a super priority, its more of a curiosity why it is happening, and why i had to make the change, was it a change in 4.6 theme hooks? is my code just bad? i mean i know some of it is clunky and nonsensical sometimes, but its happening even on the theme hooks that i don't add a theme hook, but i'm overloading a theme method, so the hookData() is just empty, returning the parent. 

it happens both on my mac and linux pc, so i'm not sure what to think. php version is the latest 7.4 on both. so any ideas?

Link to comment
Share on other sites

  • 2 weeks later...
On 7/7/2021 at 6:45 AM, Adriano Faria said:

Any clue?

yeah, after further debugging, that wasn't the solution, might've been a stale opcache on my end that made me think it was working. 

I really don't know now toolbox has a ton of hooks, its gonna take a lot of work to go thru each one and try to find the culprit. 

Link to comment
Share on other sites

Okay after a bit more digging into the code, i see why this can happen.

it is any theme hook, that has an override for a template in it or aka "theme hook in php mode"

try{
	if ( eval( "namespace IPS\\Theme;\n\n" . str_replace( array( ' extends _HOOK_CLASS_', 'parent::hookData()' ), array( '_tmp', 'array()' ), file_get_contents( $path . '/' . $data['file'] ) ) ) !== FALSE )
  {
      $class = "IPS\\Theme\\" . $data['class'] . "_tmp";
      $templateHooks = array_merge_recursive( $templateHooks, $class::hookData() );
  }
}
catch ( \ParseError $e ) { }

in php 8.0 it is now a fatal error to call a non existing parent class/method, in the latest php 7.4 it is a deprecated.

so since at this point, the hook in php mode isn't actually extending another class, it is blowing up. this is def an engineering issue on IPS side of things. so technically any theme hook in php mode, that eval and try to call a parent method that doesn't exist, will throw this fatal/warning in the current versions of php. we don't see it while IN_DEV cause templates are compiled differently there. 

i wonder now if it happens with class hooks, that get wrapped up by IPS try/catch block they place inside every method, even if the method doesn't exist in the parent.  

 

Link to comment
Share on other sites

I have a lot of plugins with theme hooks like that. 

12 minutes ago, CodingJungle said:

this is def an engineering issue on IPS side of things. so technically any theme hook in php mode, that eval and try to call a parent method that doesn't exist, will throw this fatal/warning in the current versions of php.

@Matt @Ryan Ashbrook @Andy Millne

Any word on this?

Link to comment
Share on other sites

[Thu Jul 08 19:00:59.697206 2021] [proxy_fcgi:error] [pid 3910:tid 6158315520] [client ::1:51242] AH01071: Got error 'PHP message: PHP Fatal error:  Cannot use "parent" when current class scope has no parent in /Users/michael/public_html/dev/system/Theme/Theme.php(2613) : eval()'d code on line 57', referer: http://localhost/~michael/dev/

this is the error that shows up in my apache log.

i've tried several different things, even checking if parent is callable on the overloaded templates in the hook, still causes this error to pop up in php 8, not sure about 7.4, but i'd imagine it will there too as well.  the line number might be a bit off for Theme.php as inserted some code to try to catch which theme hooks were erroring out. 

Link to comment
Share on other sites

 

test 1.0.0.tar

here is a proof of concept app for this issue, it is just one theme hook in php mode, overriding 'includeCss':

//<?php

/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
	exit;
}

class test_hook_globalTemplate extends _HOOK_CLASS_
{

/* !Hook Data - DO NOT REMOVE */
public static function hookData() {
 return parent::hookData();
}
/* End Hook Data */

    public function includeCSS(){
        return parent::includeCSS();
    }
}

parent::hookData() gets replaced with an empty array before you eval it, so the only thing that can be causing it is the parent call to includeCss()

Edited by CodingJungle
Link to comment
Share on other sites

@Adriano Faria

if ( \is_callable('parent::includeCSS') )
{
	return \call_user_func_array( 'parent::' . __FUNCTION__, \func_get_args() );
}

something like this appears to work (change the if statement to the method you are overriding). i haven't tested it exhaustively, but it might be better than waiting for ips 4.12 for a fix 😛 

Link to comment
Share on other sites

10 hours ago, CodingJungle said:

let me know, it seems to have worked for the toolbox, now i possibly gotta figure out a clever way to automate this for the future 🙂 

Nope. Still got the error:

Quote

Fatal error: Cannot use "parent" when current class scope has no parent in C:\wamp64\www\46x\system\Theme\Theme.php(2612) : eval()'d code on line 39

Using;

/* !Hook Data - DO NOT REMOVE */
    public static function hookData()
    {
		if( \is_callable( 'parent::hookData' ) )
		{
			return \call_user_func_array( 'parent::' . __FUNCTION__, \func_get_args() );
		}

        return parent::hookData();
    }
/* End Hook Data */

 

Link to comment
Share on other sites

on the hookData() method, just do:

public static function hookData(){
	return [];
}

or

public static function hookData(){
	return parent::hookData();
}

it gets replaced by an empty array anyway before its eval'ed

the solution i came up with last night is to overridden templates methods, look at the test.tar hooks folder, you will see what i mean. its a theme hook in php mode.

the other thing you should do, is manually delete everything in the datastore folder, there would be times when i was testing that would be causing the issue. 

 

here is one of the them hooks i use in my toolbox apps, i have two template methods i'm overriding to be able to manipulate their parameters. 

//<?php

/* To prevent PHP errors (extending class does not exist) revealing path */

use IPS\Output;
use IPS\Request;
use IPS\Theme;

if (!defined('\IPS\SUITE_UNIQUE_KEY')) {
    exit;
}

class toolbox_hook_adminGlobalTemplate extends _HOOK_CLASS_
{

    /* !Hook Data - DO NOT REMOVE */
    public static function hookData()
    {
        if (\is_callable('parent::hookData')) {
            return array_merge_recursive(
                [
                    'globalTemplate' => [
                        0 => [
                            'selector' => '#ipsLayout_header',
                            'type'     => 'add_inside_start',
                            'content'  => '{{if $menu = \IPS\toolbox\Menu::i()->build()}}
	{$menu|raw}
{{endif}}',
                        ],
                        1 => [
                            'selector' => 'html > body',
                            'type'     => 'add_inside_end',
                            'content'  => '<!--ipsQueryLog-->',
                        ],
                    ],
                ],
                parent::hookData()
            );
        }
        return [];
    }

    /* End Hook Data */
    public function globalTemplate($title, $html, $location = [])
    {
            Output::i()->cssFiles = array_merge(
                Output::i()->cssFiles,
                Theme::i()->css('devbar.css', 'toolbox', 'admin')
            );

        if ( \is_callable('parent::globalTemplate') )
        {
            return \call_user_func_array( 'parent::' . __FUNCTION__, \func_get_args() );
        }
    }

    public function tabs(
        $tabNames,
        $activeId,
        $defaultContent,
        $url,
        $tabParam = 'tab',
        $tabClasses = '',
        $panelClasses = ''
    ) {
            if (Request::i()->app === 'core' && Request::i()->module === 'applications' && Request::i(
                )->controller === 'developer' && !Request::i()->do) {
             $tabNames['SchemaImports'] = 'dtdevplus_schema_imports';
            }
        if ( \is_callable('parent::tabs') )
        {
            return \call_user_func_array( 'parent::' . __FUNCTION__, [$tabNames, $activeId, $defaultContent, $url, $tabParam, $tabClasses, $panelClasses] );
        }

    }
}

 

 

Edited by CodingJungle
Link to comment
Share on other sites

No luck cleaning the datastore folder. That's what works in 4.3, 4.3 and 4.5:

	/* !Hook Data - DO NOT REMOVE */
	public static function hookData() {
	 return parent::hookData();
	}
	/* End Hook Data */

	public function searchResult( $indexData, $articles, $authorData, $itemData, $unread, $objectUrl, $itemUrl, $containerUrl, $containerTitle, $repCount, $showRepUrl, $snippet, $iPostedIn, $view, $canIgnoreComments=FALSE )
	{
		if( $indexData['index_class'] == 'IPS\forums\Topic\Post' AND ( !\in_array( $indexData['index_container_id'], explode( ',', \IPS\Settings::i()->banExcludeForums ) ) ) AND ( \IPS\Settings::i()->banGroups == '*' OR \IPS\Member::loggedIn()->inGroup( explode( ',', \IPS\Settings::i()->banGroups ) ) ) AND $view != 'condensed' )
		{
			$topic 		 = \IPS\forums\Topic::loadAndCheckPerms( $itemData['tid'] );
			$checkAccess = \IPS\banfromtopics\Manage::getBannedMemberStatus( $topic, \IPS\Member::loggedIn()->member_id );

			if( $checkAccess === 'BANNED' )
			{
				$snippet = \IPS\Theme::i()->getTemplate( 'global', 'banfromtopics', 'front' )->bftNoPermission();
			}
		}

		return parent::searchResult( $indexData, $articles, $authorData, $itemData, $unread, $objectUrl, $itemUrl, $containerUrl, $containerTitle, $repCount, $showRepUrl, $snippet, $iPostedIn, $view, $canIgnoreComments );
	}

 

Link to comment
Share on other sites

  • Solution

@Adriano Faria its not 4.6 that is at fault per se, IPS is at some fault here due to the way they engineered how the theme hooks work, but it's the latest php 7.4 and 8.0 that is the issue.

example:

<?php

class myclass {

	public function myfunction(){
		return parent::myfunction();
	}
}

this will error out on the new versions of 7.4 and 8.0, cause myclass isn't a child class of anything, so there is no parent to call. 

the theme hooks get eval'ed as stand alone classes, they replaced the " extends _HOOK_FILE" with "_tmp" (same with parent::hookData() gets replaced with array() ), so the class would be something like class my_theme_hook_temp {}. during the eval process, it it validating the code and that validation is failing on anything with parent::myfunction() that is being called in the code, that is why the call_user_func_array is working for toolbox, cause it passes the validation, so the template will build. 

so its not the parent::hookData() that i original thought it was, cause i just took a cursory look at the code, patched my files, it seemed to work cause my datastore wasn't cleared, but as soon as it cleared, i got that error. 

so in any template overrides you are doing, you need to wrap it with a if(\is_callable('parent::myfunction')){} and then inside the if statement, call the parent method with:

return \call_user_func_array( 'parent::' . __FUNCTION__, \func_get_args() );

like i have done in the example hook i posted a few post up. if you need further help, send me the app, and i will make the changes so you can see within your code what i'm talking about 🙂

Link to comment
Share on other sites

yeah i originally thought it was the hookData, cause when i looked at the IPS code in the section the error was referencing i thought that was the only one that it could be, cause hookData() use to do something and use to be required to call it 🙂 , so i'm not entirely sure when they changed that, cause it is almost identical to the 4.5 code and my sister has my laptop so i can't look up previous versions atm. 

anyway, if you are overloading a template, you need to put that container code in it, to check to see if it is callable, then use call_user_func_array to call the method instead of parent. 

Link to comment
Share on other sites

  • 2 months later...

Hello to both of you (and anyone reading),

I am sorry, but I cannot seem to fix this issue I ran into, with the exact same error message that you encountered, which is:

Quote

PHP Fatal error:  Cannot use "parent" when current class scope has no parent in /var/www/html/system/Theme/Theme.php(2612) : eval()'d code on line 39

This happens after I upgraded my PHP version from 7.4 to 8.0, only if I activate a hook that I downloaded on a third-party website. This hook had been available on the marketplace for a few years (I do not know why it is not anymore) and should be compatible with IPB 4.6.

With no hook activated, I have no issue at all ; whenever I activate this hook (the only one that I am using), I am getting a 500 error code on any front-end page.

I cannot understand what I should be updating in order to get things working. Is it the XML file for the hook? where I can seem to find

Quote

return call_user_func_array( 'parent::' . __FUNCTION__, func_get_args() );

but surrounding it with the is_callable function gives no result? Or somewhere else?

Thank you a lot for your help!

Link to comment
Share on other sites

On 9/30/2021 at 12:42 PM, HelloWorld said:

but surrounding it with the is_callable function gives no result? Or somewhere else?

Just tested this in another app and works fine. Example:

public function methodName( $param1, $param2 )
{
	... do your stuff ...

	if( \is_callable( 'parent::methodName' ) )
	{
		return \call_user_func_array( 'parent::' . __FUNCTION__, array( $param1, $param2 ) );
	}
}

 

Link to comment
Share on other sites

  • 2 weeks later...

Thank you @Adriano Faria, I will give it a go and let you know.

Edit: I finally managed to get things to work thanks to you and to the clear explanation provided by @CodingJungle. Many thanks to you.

 I had to look for all

parent::

calls, something that I probably did not do very well in the first place, and replace them with conditional sets/returns based on the fact that the parent class exists (which should never be the case, right?).

Now this works with PHP 8 ! 🎉

Edited by HelloWorld
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...