XF does not loop through all possible folders inside '_output' on import/upgrade

nocte

Well-known member
Affected version
2.3.6
In certain situations on xf-addon:upgrade or xf-dev:import XF does not remove orphaned data from the database (data, that was removed from the _output folder).

According to my tests we need at least 1 folder inside _output (e.g. phrases). If another folder (e.g. widget_definitions) is deleted, XF does completely ignore that fact on upgrade or import and just skips removing any (in this case) widget definitions from the database.

Result: If you build a release, if will contain orphaned data.

In what situations does this happen?

If you work with other developers (e.g. on Github) and you pull a commit, where a folder inside _output has been removed, you may face this problem.

The removal may happen, if there used to be 1 widget definition (or other thing, like a route, ..) and this was removed via admin cp. The only file left in the folder is then _metadata.json, which is often ignored via .gitignore. And empty folders are not pushed to Github.

How to reproduce:
  1. Create a dummy add-on: php cmd.php xf-addon:create.
  2. Create a widget definition (just use XF\Widget\Hmtl as class for testing purpose). You will now have a phrases and a widget_definitions folder in your _output folder.
  3. Delete the widget_positions folder.
  4. Run php cmd.php xf-addon:upgrade Test/Addon --force.
Result: The widget definition is still in the database and visible in the admin cp.

Expected behavior: The widget definition should be removed.



TL;DR: It seems that XF only loops through exiting folders inside _output, when doing an import/upgrade. If a folder is missing, XF does not remove the data belonging to the missing folder. Only exception: If there's not a single folders inside _output, everything seems to be removed and this problem does not occur.

Solution: XF should loop through all possible folders inside _output (not only the existing ones) on import/upgrade and if one does not exist remove all data connected to that folder.
 
Last edited:
The only file left in the folder is then _metadata.json, which is often ignored via .gitignore.
We'll give this some thought but ultimately the _metadata.json file is not supposed to be ignored, partially for this reason.

At the very least, that's the current workaround to this situation.
 
Committing _metadata.json causes endless merge conflicts, the format is practically made to cause issues. Plus it causes endless pointless churn which makes git change logs harder to work with

IMO it just isn't needed if you include a few extra CLI commands.

I've got a /usr/local/bin/xf-addon-import script which has this contents:
Code:
#!/bin/bash

if [[ "$#" -ne 1 ]]; then
    echo "Require an add-on name"
    exit 1;
fi

if [[ ! -d "src/addons/$1" ]]; then
    echo "Expect src/addons/$1  to exist"
    exit 1;
fi

php cmd.php xf-dev:metadata-clean -v || exit 1
php cmd.php xf-dev:import --addon="$1" -v || exit 1
php cmd.php xf-dev:rebuild-caches -v || exit 1

This forces XF to discard missing files, and then it does the import. For some reason the actions in xf-dev:rebuild-caches aren't done in the xf-dev:import command.


I have an xf-addon-import-export which does the import, and then generate-finders and then export actions. This round-tripping is useful as it catches some error cases.
 
Last edited:
Thank you for your feedback, Chris!

the _metadata.json file is not supposed to be ignored, partially for this reason.
Well, but that's what most XF devs are doing. ;)

Here are just 2 examples:


I found this issue, that I described above, because I built an add-on, that I pulled from Github. In the latest commit a widget was removed. On my dev environment it still had a widget definition, that was not removed, because of the missing folder in the latest commit. In the end I was not able to upgrade the add-on with the created build, because it contained the orphaned widget definition, but not the needed widget class. The upgrade stopped with an error and the add-on was in an "inconsistent state". Neither the developer nor me knew why there was that orphaned piece of data and it took quite a while to find out what I posted above.
 
There is only one feature that _metadata.json enables that isn't possible otherwise (vs clearing dangling references & importing _output); and that is tracking if the "cache global" flag has been set for a phrase.

Except phrase groups makes the "cache global" flag redundant. Phrase groups consume less memory, and have a lower runtime memory and time-cost.
 
Back
Top Bottom