Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(upgrades): Introduces a new upgrading feature
- Loading branch information
1 parent
d9cee3f
commit 6e221f0
Showing
20 changed files
with
943 additions
and
207 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php | ||
/** | ||
* Runs batch upgrades | ||
*/ | ||
|
||
$guid = get_input('guid'); | ||
|
||
$upgrade = get_entity($guid); | ||
|
||
if (!$upgrade instanceof \ElggUpgrade) { | ||
register_error(elgg_echo('admin:upgrades:error:invalid_upgrade', array($entity->title, $guid))); | ||
exit; | ||
} | ||
|
||
$upgrader = _elgg_services()->batchUpgrader; | ||
$upgrader->setUpgrade($upgrade); | ||
$result = $upgrader->run(); | ||
|
||
echo json_encode($result); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
Upgrading plugin data | ||
##################### | ||
|
||
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\Batch`` interface | ||
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 implements the ``Elgg\Upgrade\Batch`` interface. | ||
|
||
Example from ``mod/blog/elgg-plugin.php`` file: | ||
|
||
.. code:: php | ||
return [ | ||
'upgrades' => [ | ||
'Blog\Upgrades\AccessLevelFix', | ||
'Blog\Upgrades\DraftStatusUpgrade', | ||
] | ||
]; | ||
The class names in the example refer to the classes: | ||
- `mod/blog/classes/Blog/Upgrades/AccessLevelFix` | ||
- `mod/blog/classes/Blog/Upgrades/DraftStatusUpgrade` | ||
|
||
The upgrade class | ||
----------------- | ||
|
||
A class implemening the ``Elgg\Upgrade\Batch`` interface 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:: php | ||
<?php | ||
namespace Blog\Upgrades; | ||
use Elgg\Upgrade\Batch; | ||
use Elgg\Upgrade\Result; | ||
/** | ||
* Fixes invalid blog access values | ||
*/ | ||
class AccessLevelFix implements BatchUpgrade { | ||
const INCREMENT_OFFSET = true; | ||
const VERSION = 2016120300; | ||
/** | ||
* Run the upgrade | ||
* | ||
* @param Result $result | ||
* @param int $offset | ||
* @return Result result | ||
*/ | ||
public function run(Result $result, $offset) { | ||
} | ||
} | ||
Class constants | ||
~~~~~~~~~~~~~~~ | ||
|
||
The class must declare the following constant variables: | ||
|
||
INCREMENT_OFFSET | ||
^^^^^^^^^^^^^^^^ | ||
|
||
This is a boolean value that tells Elgg core whether it should increment | ||
the offset of the upgrade after each run. If the upgrade leaves the data | ||
itself intact and simply modifies it in some way, the value should be | ||
set to ``true``. If the upgrade either moves or completely deletes the | ||
items within the data, the value should be ``false``. | ||
|
||
VERSION | ||
^^^^^^^ | ||
|
||
The version constant tells the date when the upgrade was added. It consists | ||
of eight digits and is in format ``yyyymmddnn`` where: | ||
|
||
- ``yyyy`` is the year | ||
- ``mm`` is the month (with leading zero) | ||
- ``dd`` is the day (with leading zero) | ||
- ``nn`` is an incrementing number (starting from ``00``) that is used in case | ||
two separate upgrades have been added during the same day | ||
|
||
Class methods | ||
~~~~~~~~~~~~~ | ||
|
||
countItems() | ||
^^^^^^^^^^^^ | ||
|
||
Counts and returns the total amount of items that need to be processed | ||
by the upgrade. | ||
|
||
run() | ||
^^^^^ | ||
|
||
Takes care of the actual upgrade. It takes two parameters: | ||
|
||
- ``$result``: An instance of ``Elgg\Upgrade\Result`` object | ||
- ``$offset``: The offset where the next upgrade batch should start | ||
|
||
For each item the method processes, it must call either: | ||
|
||
- ``$result->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")`` | ||
|
||
In most cases the ``$offset`` parameter is passed directly to one of the | ||
``elgg_get_entities*()`` functions: | ||
|
||
.. code:: php | ||
/** | ||
* Process blog posts | ||
* | ||
* @param Result $result Object that holds results of the batch | ||
* @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) { | ||
// Do something to the blog objects here | ||
if (do_something($blog)) { | ||
$result->addSuccesses() | ||
} else { | ||
$result->addFailures(); | ||
$result->addError("Failed to fix the blog {$blog->guid}."); | ||
} | ||
} | ||
return $result; | ||
} | ||
Administration interface | ||
------------------------ | ||
|
||
Each upgrade implementing the ``Elgg\Upgrade\Batch`` interface 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<?php | ||
|
||
namespace Elgg; | ||
|
||
use ElggUpgrade; | ||
use Elgg\Config; | ||
use Elgg\Timer; | ||
use Elgg\Upgrade\Result; | ||
|
||
/** | ||
* Runs long running upgrades and gives feedback to UI after each batch. | ||
* | ||
* WARNING: API IN FLUX. DO NOT USE DIRECTLY. | ||
* | ||
* @access private | ||
* | ||
* @since 3.0.0 | ||
*/ | ||
class BatchUpgrader { | ||
|
||
/** | ||
* @var $upgrade ElggUpgrade | ||
*/ | ||
private $upgrade; | ||
|
||
/** | ||
* @var $config Config | ||
*/ | ||
private $config; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param Config $config Site configuration | ||
*/ | ||
public function __construct(Config $config) { | ||
$this->config = $config; | ||
|
||
// Custom limit can be defined in settings.php if necessary | ||
if (empty($this->config->get('batch_run_time_in_secs'))) { | ||
$this->config->set('batch_run_time_in_secs', 4); | ||
} | ||
} | ||
|
||
/** | ||
* Set ElggUpgrade object | ||
* | ||
* @param ElggUpgrade $upgrade ElggEntity representing the upgrade | ||
* @return void | ||
*/ | ||
public function setUpgrade(ElggUpgrade $upgrade) { | ||
$this->upgrade = $upgrade; | ||
} | ||
|
||
/** | ||
* Run single upgrade batch | ||
* | ||
* @return void | ||
*/ | ||
public function run() { | ||
// Upgrade also disabled data, so the compatibility is | ||
// preserved in case the data ever gets enabled again | ||
global $ENTITY_SHOW_HIDDEN_OVERRIDE; | ||
$ENTITY_SHOW_HIDDEN_OVERRIDE = true; | ||
|
||
// Defined in Elgg\Application | ||
global $START_MICROTIME; | ||
|
||
$result = new Result; | ||
|
||
// Get the class taking care of the actual upgrading | ||
$upgrade = $this->upgrade->getUpgrade(); | ||
|
||
do { | ||
$upgrade->run($result, $this->upgrade->offset); | ||
|
||
$failure_count = $result->getFailureCount(); | ||
$success_count = $result->getSuccessCount(); | ||
|
||
$total = $failure_count + $success_count; | ||
|
||
if ($upgrade::INCREMENT_OFFSET) { | ||
// Offset needs to incremented by the total amount of processed | ||
// items so the upgrade we won't get stuck upgrading the same | ||
// items over and over. | ||
$this->upgrade->offset += $total; | ||
} else { | ||
// Offset doesn't need to be incremented, so we mark only | ||
// the items that caused a failure. | ||
$this->upgrade->offset += $failure_count; | ||
} | ||
|
||
if ($failure_count > 0) { | ||
$this->upgrade->has_errors = true; | ||
} | ||
|
||
$this->upgrade->processed += $total; | ||
} while ((microtime(true) - $START_MICROTIME) < $this->config->get('batch_run_time_in_secs')); | ||
|
||
if ($this->upgrade->processed >= $this->upgrade->total) { | ||
// Upgrade is finished | ||
if ($this->upgrade->has_errors) { | ||
// The upgrade was finished with errors. Reset offset | ||
// and errors so the upgrade can start from a scratch | ||
// if attempted to run again. | ||
$this->upgrade->offset = 0; | ||
$this->upgrade->has_errors = false; | ||
|
||
// TODO Should $this->upgrade->count be updated again? | ||
} else { | ||
// Everything has been processed without errors | ||
// so the upgrade can be marked as completed. | ||
$this->upgrade->setCompleted(); | ||
} | ||
} | ||
|
||
// Give feedback to the user interface about the current batch. | ||
return array( | ||
'errors' => $result->getErrors(), | ||
'numErrors' => $failure_count, | ||
'numSuccess' => $success_count, | ||
); | ||
} | ||
} |
Oops, something went wrong.