Writing a plugin upgrade ######################## Every now and then there comes a time when a plugin needs to change the contents or the structure of the data it has stored either in the database or the dataroot. The motivation for this may be that the data structure needs to be converted to more efficient or flexible structure. Or perhaps due to a bug the data items have been saved in an invalid way, and they needs to be converted to the correct format. Migrations and convertions like this may take a long time if there is a lot of data to be processed. This is why Elgg provides the ``Elgg\Upgrade\AsynchronousUpgrade`` class that can be used for implementing long-running upgrades. Declaring a plugin upgrade -------------------------- Plugin can communicate the need for an upgrade under the ``upgrades`` key in ``elgg-plugin.php`` file. Each value of the array must be the fully qualified name of an upgrade class that extends the ``Elgg\Upgrade\AsynchronousUpgrade`` class. Example from ``mod/blog/elgg-plugin.php`` file: .. code-block:: php return [ 'upgrades' => [ Blog\Upgrades\AccessLevelFix::class, Blog\Upgrades\DraftStatusUpgrade::class, ] ]; The class names in the example refer to the classes: - ``mod/blog/classes/Blog/Upgrades/AccessLevelFix`` - ``mod/blog/classes/Blog/Upgrades/DraftStatusUpgrade`` .. note:: Elgg core upgrade classes can be declared in ``engine/lib/upgrades/async-upgrades.php``. The upgrade class ----------------- A class extending the ``Elgg\Upgrade\AsynchronousUpgrade`` class has a lot of freedom on how it wants to handle the actual processing of the data. It must however declare some constant variables and also take care of marking whether each processed item was upgraded successfully or not. The basic structure of the class is the following: .. code-block:: php addSuccesses()``: If the item was upgraded successfully - ``$result->addFailures()``: If it failed to upgrade the item Both methods default to one item, but you can optionally pass in the number of items. Additionally it can set as many error messages as it sees necessary in case something goes wrong: - ``$result->addError("Error message goes here")`` If ``countItems()`` returned ``Batch::UNKNOWN_COUNT``, then at some point ``run()`` must call ``$result->markComplete()`` to finish the upgrade. In most cases your ``run()`` method will want to pass the ``$offset`` parameter to one of the ``elgg_get_entities()`` functions: .. code-block:: php /** * Process blog posts * * @param Result $result The batch result (will be modified and returned) * @param int $offset Starting point of the batch * @return Result Instance of \Elgg\Upgrade\Result; */ public function run(Result $result, $offset) { $blogs = elgg_get_entitites([ 'type' => 'object' 'subtype' => 'blog' 'offset' => $offset, ]); foreach ($blogs as $blog) { if ($this->fixBlogPost($blog)) { $result->addSuccesses(); } else { $result->addFailures(); $result->addError("Failed to fix the blog {$blog->guid}."); } } return $result; } getUpgrade() ^^^^^^^^^^^^ Use this function to get the related ``ElggUpgrade`` entity that is related to this upgrade. Administration interface ------------------------ Each upgrade extending the ``Elgg\Upgrade\AsynchronousUpgrade`` class gets listed in the admin panel after triggering the site upgrade from the Administration dashboard. While running the upgrades Elgg provides: - Estimated duration of the upgrade - Count of processed items - Number of errors - Possible error messages