19
19
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
21
21
namespace Xibo\Controller;
22
use Xibo\Exception\AccessDeniedException;
23
use Xibo\Exception\ConfigurationException;
24
use Xibo\Factory\UpgradeFactory;
25
use Xibo\Helper\Environment;
26
use Xibo\Service\ConfigServiceInterface;
27
use Xibo\Service\DateServiceInterface;
28
use Xibo\Service\LogServiceInterface;
29
use Xibo\Service\SanitizerServiceInterface;
30
use Xibo\Storage\StorageServiceInterface;
24
use Xibo\Helper\Config;
26
use Xibo\Helper\Install;
27
use Xibo\Helper\Theme;
34
* @package Xibo\Controller
36
29
class Upgrade extends Base
38
/** @var StorageServiceInterface */
41
/** @var UpgradeFactory */
42
private $upgradeFactory;
45
31
public $errorMessage;
48
* Set common dependencies.
49
* @param LogServiceInterface $log
50
* @param SanitizerServiceInterface $sanitizerService
51
* @param \Xibo\Helper\ApplicationState $state
52
* @param \Xibo\Entity\User $user
53
* @param \Xibo\Service\HelpServiceInterface $help
54
* @param DateServiceInterface $date
55
* @param ConfigServiceInterface $config
57
* @param UpgradeFactory $upgradeFactory
59
public function __construct($log, $sanitizerService, $state, $user, $help, $date, $config, $store, $upgradeFactory)
61
$this->setCommonDependencies($log, $sanitizerService, $state, $user, $help, $date, $config);
63
$this->store = $store;
64
$this->upgradeFactory = $upgradeFactory;
70
33
public function displayPage()
72
// Assume we will show the upgrade page
35
if (DBVERSION === WEBSITE_VERSION) {
36
$this->getState()->template = 'upgrade-not-required-page';
40
if ($this->getUser()->userTypeId != 1) {
41
$this->getState()->template = 'upgrade-in-progress-page';
73
45
$this->getState()->template = 'upgrade-page';
75
// Is there a pending upgrade (i.e. are there any pending upgrade steps).
76
$steps = $this->upgradeFactory->getIncomplete();
78
if (count($steps) <= 0) {
79
// No pending steps, check to see if we need to insert them
80
if (!$this->getConfig()->isUpgradePending()) {
81
$this->getState()->template = 'upgrade-not-required-page';
85
if ($this->getUser()->userTypeId != 1) {
86
$this->getState()->template = 'upgrade-in-progress-page';
90
// Insert pending upgrade steps.
91
$steps = $this->upgradeFactory->createSteps(DBVERSION, Environment::$WEBSITE_VERSION);
93
foreach ($steps as $step) {
94
/* @var \Xibo\Entity\Upgrade $step */
100
// We have pending steps to process, show them in a list
101
$this->getState()->setData([
111
public function doStep($stepId)
113
// Check we are a super admin
114
if (!$this->getUser()->userTypeId == 1)
115
throw new AccessDeniedException();
118
$upgradeStep = $this->upgradeFactory->getByStepId($stepId);
120
if ($upgradeStep->complete == 1)
121
throw new \InvalidArgumentException(__('Upgrade step already complete'));
125
$stepFailedMessage = null;
48
// What step are we on
49
$xibo_step = \Kit::GetParam('step', _REQUEST, _INT, 1);
57
$content = $this->Step1();
61
// Collect upgrade details
62
$content = $this->Step2();
68
$content = $this->Step3();
69
} catch (Exception $e) {
70
$this->errorMessage = $e->getMessage();
73
$content = $this->Step2();
79
public function Step1()
81
Theme::Set('form_action', 'index.php?p=upgrade');
83
$config = new Config();
85
$environment = $config->CheckEnvironment();
87
$formFields = array();
88
$formButtons = array();
89
$formFields[] = Form::AddMessage(sprintf(__('First we need to re-check if your server meets %s\'s requirements. The CMS requirements may change from release to release. If this is the case there will be further information in the release notes.'), Theme::getConfig('app_name')));
91
$formFields[] = Form::AddRaw($environment);
93
if ($config->EnvironmentFault()) {
94
$formFields[] = Form::AddHidden('step', 1);
95
$formButtons[] = Form::AddButton(__('Retest'));
96
} else if ($config->EnvironmentWarning()) {
97
$formFields[] = Form::AddHidden('step', 2);
98
$formButtons[] = Form::AddButton(__('Retest'), 'link', 'index.php?p=upgrade&step=1');
99
$formButtons[] = Form::AddButton(__('Next'));
101
$formFields[] = Form::AddHidden('step', 2);
102
$formButtons[] = Form::AddButton(__('Next'));
105
// Return a rendered form
106
Theme::Set('form_fields', $formFields);
107
Theme::Set('form_buttons', $formButtons);
108
return Theme::RenderReturn('form_render');
111
public function Step2()
115
// Work out what is involved in this upgrade
116
$_SESSION['upgradeFrom'] = Config::Version('DBVersion');
118
if ($_SESSION['upgradeFrom'] < 1) {
119
$_SESSION['upgradeFrom'] = 1;
122
// Get a list of .sql and .php files for the upgrade
123
$sql_files = Install::ls('*.sql', 'install/database', false, array('return_files'));
124
$php_files = Install::ls('*.php', 'install/database', false, array('return_files'));
126
// Sort by natural filename (eg 10 is bigger than 2)
127
natcasesort($sql_files);
128
natcasesort($php_files);
130
$_SESSION['phpFiles'] = $php_files;
131
$_SESSION['sqlFiles'] = $sql_files;
133
$max_sql = \Kit::ValidateParam(substr(end($sql_files), 0, -4), _INT);
134
$max_php = \Kit::ValidateParam(substr(end($php_files), 0, -4), _INT);
135
$_SESSION['upgradeTo'] = max($max_sql, $max_php);
137
if (!$_SESSION['upgradeTo'])
138
throw new Exception(__('Unable to calculate the upgradeTo value. Check for non-numeric SQL and PHP files in the "install / database" directory.'));
140
if ($_SESSION['upgradeTo'] < $_SESSION['upgradeFrom'])
141
$_SESSION['upgradeTo'] = $_SESSION['upgradeFrom'];
143
// Form to collect some information.
144
$formFields = array();
145
$formButtons = array();
147
// Put up an error message if one has been set (and then unset it)
148
if ($this->errorMessage != '') {
149
Theme::Set('message', $this->errorMessage);
150
Theme::Set('prepend', Theme::RenderReturn('message_box'));
151
$this->errorMessage == '';
154
$formFields[] = Form::AddHidden('step', 3);
155
$formFields[] = Form::AddHidden('upgradeFrom', $_SESSION['upgradeFrom']);
156
$formFields[] = Form::AddHidden('upgradeTo', $_SESSION['upgradeTo']);
157
$formFields[] = Form::AddHidden('includes', true);
159
$formFields[] = Form::AddMessage(sprintf(__('Upgrading from database version %d to %d'), $_SESSION['upgradeFrom'], $_SESSION['upgradeTo']));
161
// Loop for $i between upgradeFrom + 1 and upgradeTo.
162
// If a php file exists for that upgrade, make an instance of it and call Questions so we can
163
// Ask the user for input.
164
for ($i = $_SESSION['upgradeFrom'] + 1; $i <= $_SESSION['upgradeTo']; $i++) {
165
if (file_exists('install/database/' . $i . '.php')) {
166
include_once('install/database/' . $i . '.php');
167
$stepName = 'Step' . $i;
169
// Check that a class called Step$i exists
170
if (class_exists($stepName)) {
171
$_SESSION['Step' . $i] = new $stepName($this->db);
172
// Call Questions on the object and send the resulting hash to createQuestions routine
173
$questionFields = $this->createQuestions($i, $_SESSION['Step' . $i]->Questions());
174
$formFields = array_merge($formFields, $questionFields);
176
$formFields[] = Form::AddMessage(sprintf(__('Warning: We included %s.php, but it did not include a class of appropriate name.'), $i));
181
$formFields[] = Form::AddCheckbox('doBackup', 'I agree I have a valid database backup and can restore it should the upgrade process fail', 0, __('It is important to take a database backup before running the upgrade wizard. A backup is essential for recovering your CMS should there be a problem with the upgrade.'), 'b');
183
// Return a rendered form
184
Theme::Set('form_action', 'index.php?p=upgrade');
185
Theme::Set('form_fields', $formFields);
186
Theme::Set('form_buttons', array(Form::AddButton(__('Next'))));
187
return Theme::RenderReturn('form_render');
190
public function Step3()
197
foreach ($_POST as $key => $post) {
198
// $key should be like 1-2, 1-3 etc
199
// Split $key on - character.
201
$parts = explode('-', $key);
202
if (count($parts) == 2) {
203
$step_num = 'Step' . $parts[0];
204
include_once('install/database/' . $parts[0] . '.php');
206
$response = $_SESSION[$step_num]->ValidateQuestion($parts[1], $post);
207
if (!$response == true) {
208
// The upgrade routine for this step wasn't happy.
210
$fault_string .= $response . "<br />\n";
216
throw new Exception($fault_string);
218
$doBackup = \Xibo\Helper\Sanitize::getCheckbox('doBackup');
221
throw new Exception(__('You MUST have a valid database backup to continue. Please take and verify a backup and upgrade again.'));
227
// Now loop over the entire upgrade. Run the SQLs and PHP interleaved.
128
$upgradeStep->doStep();
129
$upgradeStep->complete = 1;
132
$this->store->commitIfNecessary('upgrade');
134
} catch (\Exception $e) {
135
// Failed to run upgrade step
136
$this->getLog()->error('Unable to run upgrade stepId ' . $upgradeStep->stepId . '. Message = ' . $e->getMessage());
137
$this->getLog()->error($e->getTraceAsString());
138
$stepFailedMessage = $e->getMessage();
141
$this->store->getConnection('upgrade')->rollBack();
142
} catch (\Exception $exception) {
143
$this->getLog()->error('Unable to rollback. E = ' . $e->getMessage());
229
$dbh = \Xibo\Storage\PDOConnect::init();
230
//$dbh->beginTransaction();
232
for ($i = $_SESSION['upgradeFrom'] + 1; $i <= $_SESSION['upgradeTo']; $i++) {
233
if (file_exists('install/database/' . $i . '.sql')) {
236
$sql_file = @file_get_contents('install/database/' . $i . '.sql');
237
$sql_file = Install::remove_remarks($sql_file);
238
$sql_file = Install::split_sql_file($sql_file, $delimiter);
240
foreach ($sql_file as $sql) {
245
if (file_exists('install/database/' . $i . '.php')) {
246
$stepName = 'Step' . $i;
248
if (!$_SESSION[$stepName]->Boot())
249
throw new Exception(__('Failed with %s', $stepName));
149
$upgradeStep->lastTryDate = $this->getDate()->parse()->format('U');
150
$upgradeStep->save();
153
// Commit the default connection before we raise an error.
154
// the framework won't commit if we don't.
155
$this->store->commitIfNecessary();
157
// Throw the exception
158
throw new ConfigurationException($stepFailedMessage);
161
// Are we on the last step?
162
if (count($this->upgradeFactory->getIncomplete()) <= 0) {
163
// Clear all Task statuses
164
$this->store->update('UPDATE `task` SET `status` = 0 WHERE `status` = 1;', []);
166
// Install all module files if we are on the last step
167
$this->getApp()->container->get('\Xibo\Controller\Library')->installAllModuleFiles();
169
// Attempt to delete the install/index.php file
170
if (file_exists(PROJECT_ROOT . '/web/install/index.php') && !unlink(PROJECT_ROOT . '/web/install/index.php'))
171
$this->getLog()->critical('Unable to delete install.php file after upgrade');
175
$this->getState()->hydrate([
177
'message' => __('Complete')
254
} catch (Exception $e) {
256
throw new Exception(sprintf(__('An error occurred running the upgrade. Please take a screen shot of this page and seek help. Statement number: %d. Error Message = [%s]. File = [%s]. SQL = [%s].'), $i, $e->getMessage(), $sql_file, $sql));
260
Media::installAllModuleFiles();
263
if (!unlink('install.php'))
264
$formFields[] = Form::AddMessage(__("Unable to delete install.php. Please ensure the webserver has permission to unlink this file and retry"));
266
$formFields[] = Form::AddMessage(__('The upgrade was a success!'));
268
// Return a rendered form
269
Theme::Set('form_fields', $formFields);
270
return Theme::RenderReturn('form_render');
185
public function skipStep($stepId)
273
private function createQuestions($step, $questions)
187
// Check we are a super admin
188
if (!$this->getUser()->userTypeId == 1)
189
throw new AccessDeniedException();
192
$upgradeStep = $this->upgradeFactory->getByStepId($stepId);
194
if ($upgradeStep->complete == 1)
195
throw new \InvalidArgumentException(__('Upgrade step already complete'));
197
$this->getLog()->critical('Upgrade step skipped. id = ' . $stepId);
199
$upgradeStep->complete = 2;
200
$upgradeStep->save();
275
// Takes a multi-dimensional array eg:
276
// $q[0]['question'] = "May we collect anonymous usage statistics?";
277
// $q[0]['type'] = _CHECKBOX;
278
// $q[0]['default'] = true;
279
$formFields = array();
281
foreach ($questions as $qnum => $question) {
283
$title = ($step < 80) ? __('Question %d of Step %s', $qnum + 1, $step) : $question['title'];
285
if ($question['type'] == _INPUTBOX) {
286
$formFields[] = Form::AddText($step . '-' . $qnum, $title, $question['default'],
287
$question['question'], 'q');
288
} elseif ($question['type'] == _PASSWORD) {
289
$formFields[] = Form::AddPassword($step . '-' . $qnum, $title, $question['default'],
290
$question['question'], 'q');
291
} elseif ($question['type'] == _CHECKBOX) {
292
$formFields[] = Form::AddCheckbox($step . '-' . $qnum, $title, (($question['default']) ? 1 : 0),
293
$question['question'], 'q');
b'\\ No newline at end of file'