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;
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
73
$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;
36
if (DBVERSION == WEBSITE_VERSION) {
37
Theme::Set('message', sprintf(__('Sorry you have arrived at this page in error, please try to navigate away.'), Theme::GetConfig('app_name')));
39
$this->getState()->html .= Theme::RenderReturn('message_box');
43
if ($this->getUser()->userTypeId != 1) {
44
// Make sure we actually need to do an upgrade
45
Theme::Set('message', sprintf(__('The CMS is temporarily off-line as an upgrade is in progress. Please check with your system administrator for updates or refresh your page in a few minutes.'), Theme::GetConfig('app_name')));
47
$this->getState()->html .= Theme::RenderReturn('message_box');
50
// We want a static form (traditional rather than ajax)
51
Theme::Set('form_class', 'StaticForm');
53
// What step are we on
54
$xibo_step = \Kit::GetParam('step', _REQUEST, _INT, 1);
62
$content = $this->Step1();
66
// Collect upgrade details
67
$content = $this->Step2();
73
$content = $this->Step3();
74
} catch (Exception $e) {
75
$this->errorMessage = $e->getMessage();
78
$content = $this->Step2();
83
Theme::Set('step', $xibo_step);
84
Theme::Set('page_content', $content);
85
$this->getState()->html .= Theme::RenderReturn('upgrade_page');
89
public function Step1()
91
Theme::Set('form_action', 'index.php?p=upgrade');
93
$config = new Config();
95
$environment = $config->CheckEnvironment();
97
$formFields = array();
98
$formButtons = array();
99
$formFields[] = FormManager::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')));
101
$formFields[] = FormManager::AddRaw($environment);
103
if ($config->EnvironmentFault()) {
104
$formFields[] = FormManager::AddHidden('step', 1);
105
$formButtons[] = FormManager::AddButton(__('Retest'));
106
} else if ($config->EnvironmentWarning()) {
107
$formFields[] = FormManager::AddHidden('step', 2);
108
$formButtons[] = FormManager::AddButton(__('Retest'), 'link', 'index.php?p=upgrade&step=1');
109
$formButtons[] = FormManager::AddButton(__('Next'));
111
$formFields[] = FormManager::AddHidden('step', 2);
112
$formButtons[] = FormManager::AddButton(__('Next'));
115
// Return a rendered form
116
Theme::Set('form_fields', $formFields);
117
Theme::Set('form_buttons', $formButtons);
118
return Theme::RenderReturn('form_render');
121
public function Step2()
125
// Work out what is involved in this upgrade
126
$_SESSION['upgradeFrom'] = Config::Version('DBVersion');
128
if ($_SESSION['upgradeFrom'] < 1) {
129
$_SESSION['upgradeFrom'] = 1;
132
// Get a list of .sql and .php files for the upgrade
133
$sql_files = Install::ls('*.sql', 'install/database', false, array('return_files'));
134
$php_files = Install::ls('*.php', 'install/database', false, array('return_files'));
136
// Sort by natural filename (eg 10 is bigger than 2)
137
natcasesort($sql_files);
138
natcasesort($php_files);
140
$_SESSION['phpFiles'] = $php_files;
141
$_SESSION['sqlFiles'] = $sql_files;
143
$max_sql = \Kit::ValidateParam(substr(end($sql_files), 0, -4), _INT);
144
$max_php = \Kit::ValidateParam(substr(end($php_files), 0, -4), _INT);
145
$_SESSION['upgradeTo'] = max($max_sql, $max_php);
147
if (!$_SESSION['upgradeTo'])
148
throw new Exception(__('Unable to calculate the upgradeTo value. Check for non-numeric SQL and PHP files in the "install / database" directory.'));
150
if ($_SESSION['upgradeTo'] < $_SESSION['upgradeFrom'])
151
$_SESSION['upgradeTo'] = $_SESSION['upgradeFrom'];
153
// Form to collect some information.
154
$formFields = array();
155
$formButtons = array();
157
// Put up an error message if one has been set (and then unset it)
158
if ($this->errorMessage != '') {
159
Theme::Set('message', $this->errorMessage);
160
Theme::Set('prepend', Theme::RenderReturn('message_box'));
161
$this->errorMessage == '';
164
$formFields[] = FormManager::AddHidden('step', 3);
165
$formFields[] = FormManager::AddHidden('upgradeFrom', $_SESSION['upgradeFrom']);
166
$formFields[] = FormManager::AddHidden('upgradeTo', $_SESSION['upgradeTo']);
167
$formFields[] = FormManager::AddHidden('includes', true);
169
$formFields[] = FormManager::AddMessage(sprintf(__('Upgrading from database version %d to %d'), $_SESSION['upgradeFrom'], $_SESSION['upgradeTo']));
171
// Loop for $i between upgradeFrom + 1 and upgradeTo.
172
// If a php file exists for that upgrade, make an instance of it and call Questions so we can
173
// Ask the user for input.
174
for ($i = $_SESSION['upgradeFrom'] + 1; $i <= $_SESSION['upgradeTo']; $i++) {
175
if (file_exists('install/database/' . $i . '.php')) {
176
include_once('install/database/' . $i . '.php');
177
$stepName = 'Step' . $i;
179
// Check that a class called Step$i exists
180
if (class_exists($stepName)) {
181
$_SESSION['Step' . $i] = new $stepName($this->db);
182
// Call Questions on the object and send the resulting hash to createQuestions routine
183
$questionFields = $this->createQuestions($i, $_SESSION['Step' . $i]->Questions());
184
$formFields = array_merge($formFields, $questionFields);
186
$formFields[] = FormManager::AddMessage(sprintf(__('Warning: We included %s.php, but it did not include a class of appropriate name.'), $i));
191
$formFields[] = FormManager::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');
193
// Return a rendered form
194
Theme::Set('form_action', 'index.php?p=upgrade');
195
Theme::Set('form_fields', $formFields);
196
Theme::Set('form_buttons', array(FormManager::AddButton(__('Next'))));
197
return Theme::RenderReturn('form_render');
200
public function Step3()
207
foreach ($_POST as $key => $post) {
208
// $key should be like 1-2, 1-3 etc
209
// Split $key on - character.
211
$parts = explode('-', $key);
212
if (count($parts) == 2) {
213
$step_num = 'Step' . $parts[0];
214
include_once('install/database/' . $parts[0] . '.php');
216
$response = $_SESSION[$step_num]->ValidateQuestion($parts[1], $post);
217
if (!$response == true) {
218
// The upgrade routine for this step wasn't happy.
220
$fault_string .= $response . "<br />\n";
226
throw new Exception($fault_string);
228
$doBackup = \Xibo\Helper\Sanitize::getCheckbox('doBackup');
231
throw new Exception(__('You MUST have a valid database backup to continue. Please take and verify a backup and upgrade again.'));
237
// 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());
239
$dbh = \Xibo\Storage\PDOConnect::init();
240
//$dbh->beginTransaction();
242
for ($i = $_SESSION['upgradeFrom'] + 1; $i <= $_SESSION['upgradeTo']; $i++) {
243
if (file_exists('install/database/' . $i . '.sql')) {
246
$sql_file = @file_get_contents('install/database/' . $i . '.sql');
247
$sql_file = Install::remove_remarks($sql_file);
248
$sql_file = Install::split_sql_file($sql_file, $delimiter);
250
foreach ($sql_file as $sql) {
255
if (file_exists('install/database/' . $i . '.php')) {
256
$stepName = 'Step' . $i;
258
if (!$_SESSION[$stepName]->Boot())
259
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')
264
} catch (Exception $e) {
266
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));
270
Media::installAllModuleFiles();
273
if (!unlink('install.php'))
274
$formFields[] = FormManager::AddMessage(__("Unable to delete install.php. Please ensure the webserver has permission to unlink this file and retry"));
276
$formFields[] = FormManager::AddMessage(__('The upgrade was a success!'));
278
// Return a rendered form
279
Theme::Set('form_fields', $formFields);
280
return Theme::RenderReturn('form_render');
185
public function skipStep($stepId)
283
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();
285
// Takes a multi-dimensional array eg:
286
// $q[0]['question'] = "May we collect anonymous usage statistics?";
287
// $q[0]['type'] = _CHECKBOX;
288
// $q[0]['default'] = true;
289
$formFields = array();
291
foreach ($questions as $qnum => $question) {
293
$title = ($step < 80) ? __('Question %d of Step %s', $qnum + 1, $step) : $question['title'];
295
if ($question['type'] == _INPUTBOX) {
296
$formFields[] = FormManager::AddText($step . '-' . $qnum, $title, $question['default'],
297
$question['question'], 'q');
298
} elseif ($question['type'] == _PASSWORD) {
299
$formFields[] = FormManager::AddPassword($step . '-' . $qnum, $title, $question['default'],
300
$question['question'], 'q');
301
} elseif ($question['type'] == _CHECKBOX) {
302
$formFields[] = FormManager::AddCheckbox($step . '-' . $qnum, $title, (($question['default']) ? 1 : 0),
303
$question['question'], 'q');
b'\\ No newline at end of file'