Link to home
Start Free TrialLog in
Avatar of tel2
tel2Flag for New Zealand

asked on

WordPress: Avoid losing function change due to theme updates

Hi WordPress Experts,

I'm wanting a way to retain a change to a theme function file, even if I update the theme.

I’m using WordPress 5.1 with the AccessPress Store 2.3.6 theme and the WooCommerce 3.5.5 plugin, and the (free) theme provider told me that if I want to increase the number of products which show on a page, I should edit wp-content/themes/accesspress-store/inc/accesspress-function.php, modifying line 624, as follows:
623: function accesspress_store_loop_shop_per_page($cols){
624:	    return 999;   # Changed from 12 to 999 to show 999 products per page
625: }

Open in new window

The change worked, but when I asked them how I can make it so that change is not overwritten every time I update the theme to a new version, they couldn't help.
I assume this involves making a child theme, and somehow including that function (or one like it) in it?
How could this be done, please?

Attached is the entire accesspress-function.php file, in case you need it.

BTW, what is the point in the theme coming with it's own functions.php file, and a inc/accesspress-function.php file?

I’m reasonably new to WordPress, but I’ve done programming and know a little PHP.

Thanks.
tel2
accesspress-function.php
ASKER CERTIFIED SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
On a side note, showing 999 products is likely a bad idea, especially if you have 999 products to show. It's likely to be a performance problem in the long run. Not only will it take longer to query the DB for 999 products instead of 12, but that's also likely a lot of HTML that has to be rendered, which slows down the whole page (especially on lower-end hardware like mobile devices). Just my two cents.
Avatar of tel2

ASKER

Thanks for your response, gr8gonzo.
Please ignore the 999, which I chose arbitrarily.  I am aware that it will take longer to load all in one go.  The maximum we currently have is 65 products in a category, and that is not likely to increase much.  I've been asked to make it so they all fit on a single page, so people can just scroll through them rather than scrolling and paging.  The graphics are small (about 50kb each for the full image, and about half that for the thumbnails which appear on these pages which list multiple products), and on my phone the HTML & images render in a few secs via WiFi.  I am aware that it could take longer if I was using mobile data, though.
But thanks for the 2c.  Can I keep the change?

Edit: Just saw your first post, so I'll study that now...
Avatar of tel2

ASKER

Hi again gr8gonzo.

Thanks for all that!  I haven't tried it yet, but I might do so soon.

So, we need this line:
    remove_filter('loop_shop_per_page', 'accesspress_store_loop_shop_per_page');
to prevent the original code version (with 12 items per page) from being used, right?
If so, that sounds fine.

After reading your post, I had a look at this, regarding add_filter():
  https://developer.wordpress.org/reference/functions/add_filter/
It seems the 3rd parameter is priority.
Would an alternative method (not that I'm planning to use it in this case) be, instead of using remove_filter(), we just add_filter() with a priority like 21 so the 12 gets set but then replaced with 999?

And what would be the pros and cons of making the change in a child theme instead of a plugin?  And if I did it in a child them, what would be the file name and path to put the code in?

Thanks again.
tel2
You asked, "I'm wanting a way to retain a change to a theme function file, even if I update the theme."

You can't.

This is why the https://codex.wordpress.org/Child_Themes mechanism was developed.

You'll create a child theme with a functions.php file which inherits from your AccessPress Store theme. Then any changes you make to your child theme functions.php will survive any theme updates.

You might also use a zero config plugin to achieve the same goal.
Avatar of tel2

ASKER

Thanks for that, David.

In my original post I wrote:
"I assume this involves making a child theme, and somehow including that function (or one like it) in it?
How could this be done, please?
...
BTW, what is the point in the theme coming with it's own functions.php file, and a inc/accesspress-function.php file?"


As also indicated in my original post, the function I'm dealing with is in the theme's inc/accesspress-function.php file, not the theme's functions.php file.

Questions:
Q1. So, will I be putting the function in the child theme's functions.php file, as you have just said, or will I put it in a the child theme's inc/accesspress-function.php?
Q2. Wouldn't it cause some kind of conflict to have 2 functions by the same name in 2 places?
Q3. Are you able to answer my "BTW..." question, above, please?

> "You might also use a zero config plugin to achieve the same goal."
Q4. What do you mean by "zero config" in the context of plugins?
Q5. What are the pros and cons of going the child theme way vs the plugin way?

Thanks.
tel2
Both Q1 and Q2 presume there is something special about the child theme. Ultimately, it's just doing the same thing as the plugin concept I suggested - it's introducing code that overrides the filter callback with your own function. It's just a different way of doing it. Personally, I prefer the simplicity and organization of plugins, but that's just my own opinion.

Either way, yes, you cannot have two functions with the exact same name (unless you're doing an object-oriented approach, but that's a more complex topic). As I provided in my example, the function name is different - it just tells the filter to look at the different function.

Zero-config plugin just means there's no separate configuration. The example plugin I gave is a zero-config plugin.

For the BTW question - that's just code organization. Whomever developed the theme decided to separate out certain parts of their code into separate files.

The pros/cons for child theme vs. plugin - I'd say it's mostly personal preference for something like this. If you had a LOT of changes / overrides to make, it might be easier and more efficient to put them into a child theme instead of having a lot of plugins. But when it's just one or two changes like this, I think the plugin is simpler.

To answer your question about priority of filters, you are correct in your understanding. However, unless there's some benefit to allowing the original code to run (when you're just about to completely override it anyway), why run the original function at all? That's just wasting processor time for no reason, which is why I have the line to remove the existing callback. Plus, you can't anticipate what that function might do later - a future update might put some queries and heavy code into that function. Then when you upgrade, your site is suddenly slower (because it's running the updated, heavy code) but still looking the same (because your function's results are overriding the other function's results).
Avatar of tel2

ASKER

Well explained, thanks gr8gonzo!  No wonder you are gonzo the gr8.  Take a pay rise.

I had expected much (though not all) of what you said before I asked it, but I'd rather hear it from someone with experience, than go through life assuming it.

I plan to experiment with your code when I have time.
Avatar of tel2

ASKER

Hi gr8gonzo,

I've got good news and bad news.
First, the good news:
I disabled the hack of the theme's accesspress-function.php file, then I put your code into a file, FTP'd it to the plugins folder, found it under Dashboard > Plugins, and successfully activated it.

Now, the bad news:
It's not working.  We're back to 12 products per page, after I refresh the browser.

Here's the contents of my plugins directory from the Linux command prompt:
$ ls
akismet
classic-editor
contact-form-7
hello.php
index.php
limit-login-attempts-reloaded
phoenix-media-rename
poli-payments
query-monitor
show-me-everything.php
woo-advanced-discounts
woocommerce
woocommerce-display-country-code-in-currency.php
woocommerce-easy-table-rate-shipping
woocommerce-gateway-paypal-express-checkout
wordfence
wordpress-seo
wp-super-cache

$ cat -vet show-me-everything.php
<?php^M$
   /*^M$
   Plugin Name: Show Me Everything^M$
   Plugin URI: http://www.tel2.com^M$
   Description: Replaces the loop_shop_per_page filter with a custom function that just returns 999.^M$
   Version: 1.0^M$
   Author: tel2^M$
   */^M$
^M$
function show_me_EVERYTHING($x)^M$
{^M$
    return 999;^M$
}^M$
^M$
// Swap out the loop_shop_per_page filter functions^M$
remove_filter('loop_shop_per_page', 'accesspress_store_loop_shop_per_page');^M$
add_filter( 'loop_shop_per_page', 'show_me_EVERYTHING', 20 );

I used "cat -vet" instead of "cat" just to show line endings.

Any ideas?
From the WordPress Docs:

Important: To remove a hook, the $function_to_remove and $priority arguments must match when the hook was added. This goes for both filters and actions. No warning will be given on removal failure.

The key to this is that the function name AND the priority arguments must match. In your plugin file, you're not including the same priority that was set in the original functions file. You need:

remove_filter( 'loop_shop_per_page', 'accesspress_store_loop_shop_per_page', 20 );

And regarding the plugin vs the functions file. The functions file is specific to the theme. A plugin is specific to your WordPress installation. If you want some functionality to work regardless of whether you switch themes, use a plugin. If the functionality is specific to a theme, use the functions file.

In your case, the functionality is specific to the theme (it's removing a theme specific filter function) so it would make more sense to put it in the child theme's functions file. It won't hurt leaving it in a Plugin, but if you switch themes, the plugin will still be active but entirely pointless.
Avatar of tel2

ASKER

Hi Chris,

Well explained, thank you!

Your point about this being more suited to a child theme change makes sense, but I'd like to see if I can get it working as a plugin, in this case.  For starters, I think I'm almost there, and the plugin experience will be good for me.

Good spotting with the missing priority parameter.  I've added that, and now it looks like this:
$ cat -vet show-me-everything.php
<?php^M$
   /*^M$
   Plugin Name: Show Me Everything^M$
   Plugin URI: http://www.tel2.com^M$
   Description: Replaces the loop_shop_per_page filter with a custom function that just returns 999.^M$
   Version: 1.0^M$
   Author: tel2^M$
   */^M$
^M$
function show_me_EVERYTHING($x)^M$
{^M$
    return 999;^M$
}^M$
^M$
// Swap out the loop_shop_per_page filter functions^M$
remove_filter( 'loop_shop_per_page', 'accesspress_store_loop_shop_per_page', 20 );^M$
add_filter( 'loop_shop_per_page', 'show_me_EVERYTHING', 20 );

Open in new window

I re-activated the plugin, and it seems to now be active (because Dashboard > Plugins shows "Deactivate" under it's name), and I refreshed the 1st page of products, but it still just shows 12.

Questions:
Q6. Why is show_me_EVERYTHING($x) defined with a $x parameter?  Does it need to have a parameter at all?  The original function used $cols (see line 623 in the 3 line code snippet of my original post).
Q7. Any more ideas on what might be wrong here?

Thanks.
tel2
When you define a function, you can call the parameters whatever you want because that's the name used to refer to the parameter within the local scope. When that function is called by the system, it will pass in the current number of products. If you need to, you can access that value by using whatever parameter name is passed in. In your case, you're not using it, so it doesn't really matter.

I don't know why it's not working. Everything else looks fine
Avatar of tel2

ASKER

Thanks for that, Chris.

In an attempt to debug this issue, I've added 4 error_log(...) commands, as follows:

$ cat wp-content/plugins/show-me-everything.php
<?php
...etc...
function show_me_EVERYTHING($x)
{
	error_log("Returning 999 in show-me-everything.php plugin");
	return 999;
}

// Swap out the loop_shop_per_page filter functions
remove_filter( 'loop_shop_per_page', 'accesspress_store_loop_shop_per_page', 20 );
add_filter( 'loop_shop_per_page', 'show_me_EVERYTHING', 20 );
error_log("Added filter in show-me-everything.php plugin");

Open in new window


$ cat wp-content/themes/accesspress-store/inc/accesspress-function.php
	...etc...
	add_filter( 'loop_shop_per_page', 'accesspress_store_loop_shop_per_page', 20 );
	error_log("Added filter in accesspress-function.php");
	function accesspress_store_loop_shop_per_page($cols){
		error_log("Returning 12 in accesspress-function.php");
		return 12;
	}
	...etc...

Open in new window


And here's what went to the error_log file when I visited a page of products:
[Mon Mar 04 15:31:49 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Added filter in show-me-everything.php plugin
[Mon Mar 04 15:31:49 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Added filter in accesspress-function.php
[Mon Mar 04 15:31:49 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Returning 999 in show-me-everything.php plugin
[Mon Mar 04 15:31:49 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Returning 12 in accesspress-function.php

Open in new window


Questions:
Q8. Would you agree that the plugin is trying to remove the filter before it's created by the theme's accesspress-function.php, therefore (silently) fails to do so?
Q9. How can this be fixed?  Somehow make the plugin run after the theme's accesspress-function.php?
Q10. Or do I need to do all this via a child theme?
Q11. If so, would that ensure the sequence is correct (i.e. run parent theme's accesspress-function.php before running child theme's functions.php?)
Q6: It's simply good practice to maintain the same function definition that you're replacing/overriding. If the core product is trying to pass in a parameter of some sort, then just have the placeholder for it, which is $x, even if you don't use it. The variable name doesn't matter.

Q8 - Q11: Yes, it could be that the theme's code is loading up accesspress-function.php separately, so it's loaded after all the core pieces and plugins have loaded up. So if that's the case, then you'd either use a higher number priority (which makes it run later, but that has the issues I mentioned before), or you might be able to make yours run sooner (lower number, like 10) and then in your callback code, do the remove_filter in there. The only catch is that I don't know if the list of filters in core WP has been loaded into memory at that point, so removing the filter during execution might not work. Alternatively, use the child theme, or find out when the accesspress-function.php file is loading in, and find the nearest filter afterwards, and use that filter callback to unload the default function.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of tel2

ASKER

Thank you gonzo for some more gr8 answers!  I don't fully understand them, but mainly.

And thank you Chris for the solution of changing the plugin's add_filter() priority to 21, which worked!
Here's the new error_log output:
[Tue Mar 05 13:39:00 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Added filter in show-me-everything.php plugin, referer: https://...
[Tue Mar 05 13:39:00 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Added filter in accesspress-function.php, referer: https://...
[Tue Mar 05 13:39:00 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Returning 12 in accesspress-function.php, referer: https://...
[Tue Mar 05 13:39:00 2019] [warn] [client XXX.XX.XXX.XXX] mod_fcgid: stderr: Returning 999 in show-me-everything.php plugin, referer: https://...

Open in new window

So I think that's it, but any ideas why "referer"s are now appearing in the error_log, but they weren't yesterday?

And would it be true to say that that this line of the plugin:
    remove_filter( 'loop_shop_per_page', 'accesspress_store_loop_shop_per_page', 20 );
is achieving nothing in this case, because it's trying to remove a filter before it's been added by the theme's accesspress-function.php?
And would it be true to say that that this line of the plugin ... is acheiving nothing?

Yes - it would be true. Plugins are loaded before the functions files, and the child function file is loaded before the parent function file, so you are trying to remove a filter that hasn't been added yet.

Your particular case is straight forward because you're overriding a filter with a higher priority, so your filter is added first, and then the main theme's filter is added, but yours kicks in because it has a highy priority.

If you wanted to remove the filter and not override it with your own, then you would need to hook into the after_setup_theme action hook and remove the filter there. By that time, all the filters from plugings, child theme and parent theme would have loaded, so you could safely remove them.
Avatar of tel2

ASKER

OK, thanks again Chris.