├── .gitignore ├── README.md └── src └── cli └── update.php /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | ._* 4 | .Spotlight-V100 5 | .Trashes 6 | 7 | # Windows 8 | Thumbs.db 9 | Desktop.ini 10 | 11 | # PHPStorm 12 | .idea/ 13 | 14 | # Sublime Text 15 | *.sublime* 16 | 17 | # Eclipse 18 | .buildpath 19 | .project 20 | .settings 21 | 22 | # Temp files 23 | *.tmp 24 | *.bak 25 | *.swp 26 | *~.nib 27 | *~ 28 | 29 | # composer 30 | composer.phar 31 | vendor/* 32 | 33 | #php doc releated files 34 | output/ 35 | docs/phpqa/checkstyle.xml 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLI-Update 2 | 3 | Proof of concept on how to update/manage Joomla from the command line 4 | 5 | Parameter: 6 | 7 | php cli/update.php 8 | 9 | --core 10 | 11 | --extension[=ID_OF_THE_EXTENSION] 12 | 13 | --info 14 | 15 | --sitename 16 | 17 | --installpackage=[ARCHIVE_FILE_IN_TMP] 18 | 19 | --installurl=[ARCHIVE_FILE_URL] 20 | 21 | --remove=ID_OF_THE_EXTENSION 22 | 23 | # Description 24 | 25 | --core: 26 | 27 | Updates the Joomla! Core CMS 28 | 29 | --extension[=ID_OF_THE_EXTENSION]: 30 | 31 | Updates all or a single Extension 32 | 33 | --info 34 | 35 | Gives Information about all installed Extensions as a json 36 | 37 | --sitename 38 | 39 | Returns the sitename 40 | 41 | --installpackage=[ARCHIVE_FILE_IN_TMP] 42 | 43 | Installs an extension, provide the name of the the package file which is placed in the tmp folder 44 | 45 | --installurl=[URL] 46 | 47 | Installs an extension, provide the URL to the archive package 48 | 49 | --remove=ID_OF_THE_EXTENSION 50 | 51 | Removes an extension, provide the extension id 52 | -------------------------------------------------------------------------------- /src/cli/update.php: -------------------------------------------------------------------------------- 1 | app = JFactory::getApplication('site'); 114 | 115 | if ($this->input->get('sitename', false)) 116 | { 117 | $this->out($this->getSiteInfo()); 118 | 119 | return; 120 | } 121 | 122 | JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_installer/models'); 123 | $this->updater = JModelLegacy::getInstance('Update', 'InstallerModel'); 124 | 125 | if ($this->input->get('core', false)) 126 | { 127 | $this->out(json_encode(array('700' => $this->updateCore()))); 128 | 129 | return; 130 | } 131 | 132 | if ($this->input->get('info', false)) 133 | { 134 | $this->out(json_encode($this->infoInstalledVersions())); 135 | 136 | return; 137 | } 138 | 139 | if ($this->input->get('extensions', false)) 140 | { 141 | $this->out(json_encode($this->updateExtensions())); 142 | 143 | return; 144 | } 145 | 146 | $extension_id = $this->input->get('extension', false, 'INTEGER'); 147 | 148 | if (!empty($extension_id)) 149 | { 150 | $this->out(json_encode($this->updateExtension($extension_id))); 151 | 152 | return; 153 | } 154 | 155 | $installPackage = $this->input->get('installpackage', '', 'PATH'); 156 | 157 | if (!empty($installPackage)) 158 | { 159 | $this->out(json_encode(array('result' => $this->installExtension($installPackage, 'folder')))); 160 | 161 | return; 162 | } 163 | 164 | $installUrl = $this->input->get('installurl', '', 'STRING'); 165 | 166 | if (!empty($installUrl)) 167 | { 168 | $this->out(json_encode(array('result' => $this->installExtension($installUrl, 'url')))); 169 | 170 | return; 171 | } 172 | 173 | $remove = $this->input->get('remove', ''); 174 | 175 | if ($remove != '') 176 | { 177 | return $this->removeExtension($remove); 178 | } 179 | } 180 | 181 | /** 182 | * Remove an extension 183 | * 184 | * @param int $param Extension id 185 | * 186 | * @return bool 187 | */ 188 | protected function removeExtension($param) 189 | { 190 | $id = (int) $param; 191 | 192 | $result = true; 193 | 194 | $installer = JInstaller::getInstance(); 195 | $row = JTable::getInstance('extension'); 196 | 197 | $row->load($id); 198 | 199 | if ($row->type && $row->type != 'language') 200 | { 201 | $result = $installer->uninstall($row->type, $id); 202 | } 203 | 204 | return $result; 205 | } 206 | 207 | /** 208 | * Installs an extension (From tmp_path or URL) 209 | * 210 | * @param string $filename 211 | * @param string $method 212 | * 213 | * @return bool 214 | */ 215 | public function installExtension($filename, $method) 216 | { 217 | if ($method == 'url') 218 | { 219 | $filename = JInstallerHelper::downloadPackage($filename); 220 | } 221 | 222 | $tmp_path = $this->app->get('tmp_path'); 223 | $path = $tmp_path . '/' . basename($filename); 224 | $package = JInstallerHelper::unpack($path, true); 225 | 226 | if ($package['type'] === false) 227 | { 228 | return false; 229 | } 230 | 231 | $jInstaller = JInstaller::getInstance(); 232 | $result = $jInstaller->install($package['extractdir']); 233 | JInstallerHelper::cleanupInstall($path, $package['extractdir']); 234 | 235 | return $result; 236 | } 237 | 238 | /** 239 | * Gets the Information about all installed extensions from the database and checked the database. 240 | * Outputs to the cli as json string 241 | * 242 | * @return void 243 | */ 244 | public function infoInstalledVersions() 245 | { 246 | $lang = JFactory::getLanguage(); 247 | 248 | $langFiles = $this->getLanguageFiles(); 249 | foreach ($langFiles as $file) 250 | { 251 | $file = str_replace(array('en-GB.', '.ini'), '', $file); 252 | $lang->load($file, JPATH_ADMINISTRATOR, 'en-GB', true, false); 253 | } 254 | 255 | // Get All extensions 256 | $extensions = $this->getAllExtensions(); 257 | 258 | $this->updater->purge(); 259 | 260 | $this->findUpdates(0); 261 | 262 | $updates = $this->getUpdates(); 263 | 264 | $toUpdate = array(); 265 | $upToDate = array(); 266 | 267 | foreach ($extensions as &$extension) 268 | { 269 | $extension['name'] = JText::_($extension['name']); 270 | 271 | if (array_key_exists($extension['extension_id'], $updates)) 272 | { 273 | $tmp = $extension; 274 | $tmp['currentVersion'] = json_decode($tmp['manifest_cache'], true)['version']; 275 | $tmp['newVersion'] = $updates[$tmp['extension_id']]['version']; 276 | $tmp['needsUpdate'] = true; 277 | 278 | $toUpdate[] = $tmp; 279 | } 280 | else 281 | { 282 | $tmp = $extension; 283 | $tmp['currentVersion'] = json_decode($tmp['manifest_cache'], true)['version']; 284 | $tmp['newVersion'] = $tmp['currentVersion']; 285 | $tmp['needsUpdate'] = false; 286 | 287 | $upToDate[] = $tmp; 288 | } 289 | } 290 | 291 | return array_merge($toUpdate, $upToDate); 292 | } 293 | 294 | /** 295 | * Get the list of available language sys files 296 | * 297 | * @return array List of languages 298 | */ 299 | private function getLanguageFiles() 300 | { 301 | return JFolder::files(JPATH_ADMINISTRATOR . '/language/en-GB/', '\.sys\.ini'); 302 | } 303 | 304 | /** 305 | * Update Core Joomla 306 | * 307 | * @return bool success 308 | */ 309 | public function updateCore() 310 | { 311 | JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_joomlaupdate/models'); 312 | $jUpdate = JModelLegacy::getInstance('Default', 'JoomlaupdateModel'); 313 | 314 | $jUpdate->purge(); 315 | 316 | $jUpdate->refreshUpdates(true); 317 | 318 | $updateInformation = $jUpdate->getUpdateInformation(); 319 | 320 | if (!empty($updateInformation['hasUpdate'])) 321 | { 322 | $packagefile = JInstallerHelper::downloadPackage($updateInformation['object']->downloadurl->_data); 323 | $tmp_path = $this->app->get('tmp_path'); 324 | $packagefile = $tmp_path . '/' . $packagefile; 325 | $package = JInstallerHelper::unpack($packagefile, true); 326 | JFolder::copy($package['extractdir'], JPATH_BASE, '', true); 327 | 328 | $result = $jUpdate->finaliseUpgrade(); 329 | 330 | if ($result) 331 | { 332 | // Remove the xml 333 | if (file_exists(JPATH_BASE . '/joomla.xml')) 334 | { 335 | JFile::delete(JPATH_BASE . '/joomla.xml'); 336 | } 337 | 338 | JInstallerHelper::cleanupInstall($packagefile, $package['extractdir']); 339 | 340 | return true; 341 | } 342 | } 343 | 344 | return false; 345 | } 346 | 347 | /** 348 | * Update a single extension 349 | * 350 | * @param int $eid - The extension_id 351 | * 352 | * @return bool success 353 | */ 354 | public function updateExtension($eid) 355 | { 356 | $this->updater->purge(); 357 | 358 | $this->findUpdates($eid); 359 | 360 | $update_id = $this->getUpdateIds($eid); 361 | 362 | $this->updater->update($update_id); 363 | 364 | $result = $this->updater->getState('result'); 365 | 366 | return $result; 367 | } 368 | 369 | /** 370 | * Update Extensions 371 | * 372 | * @return array Array with success information for each extension 373 | */ 374 | public function updateExtensions() 375 | { 376 | $this->updater->purge(); 377 | 378 | $this->findUpdates(); 379 | 380 | // Get the objects 381 | $extensions = $this->getUpdateIds(); 382 | 383 | $result = array(); 384 | 385 | foreach ($extensions as $e) 386 | { 387 | // Check if ist core or extension 388 | if ($e->extension_id == CORE_EXTENSION_ID) 389 | { 390 | $result[$e->extension_id] = $this->updateCore(); 391 | } 392 | else 393 | { 394 | $this->updater->update([$e->update_id]); 395 | $result[$e->extension_id] = $this->updater->getState('result'); 396 | } 397 | } 398 | 399 | return $result; 400 | } 401 | 402 | /** 403 | * Find updates 404 | * 405 | * @param int $eid The extension id 406 | * 407 | * @return void 408 | */ 409 | private function findUpdates($eid = 0) 410 | { 411 | $updater = JUpdater::getInstance(); 412 | 413 | // Fills potential updates into the table '#__updates for ALL extensions 414 | $updater->findUpdates($eid); 415 | } 416 | 417 | /** 418 | * Get the update 419 | * 420 | * @param int|null $eid The extenion id or null for all 421 | * 422 | * @return object|array 423 | */ 424 | private function getUpdateIds($eid = null) 425 | { 426 | $db = JFactory::getDbo(); 427 | $query = $db->getQuery(true); 428 | 429 | $query->select('update_id, extension_id') 430 | ->from('#__updates') 431 | ->where($db->qn('extension_id') . ' <> 0'); 432 | 433 | if (!is_null($eid)) 434 | { 435 | $query->where($db->qn('extension_id') . ' = ' . $db->q($eid)); 436 | } 437 | 438 | $db->setQuery($query); 439 | 440 | $result = $db->loadObjectList(); 441 | 442 | if (!$result) 443 | { 444 | return array(); 445 | } 446 | 447 | if ($eid) 448 | { 449 | // Return only update id 450 | return (array) $result[0]->update_id; 451 | } 452 | 453 | return $result; 454 | } 455 | 456 | /** 457 | * Get available updates from #__updates 458 | * 459 | * @return array AssocList with available updates 460 | */ 461 | private function getUpdates() 462 | { 463 | $db = JFactory::getDbo(); 464 | $query = $db->getQuery(true); 465 | 466 | $query->select('*') 467 | ->from('#__updates') 468 | ->where($db->qn('extension_id') . ' <> 0'); 469 | 470 | $db->setQuery($query); 471 | 472 | return $db->loadAssocList('extension_id'); 473 | } 474 | 475 | /** 476 | * Get all extensions 477 | * 478 | * @return array AssocList with all extensions from #__extensions 479 | */ 480 | private function getAllExtensions() 481 | { 482 | $db = JFactory::getDbo(); 483 | $query = $db->getQuery(true); 484 | 485 | $query->select('*') 486 | ->from('#__extensions'); 487 | 488 | $db->setQuery($query); 489 | 490 | return $db->loadAssocList('extension_id'); 491 | } 492 | 493 | /** 494 | * Get the sitename json encoded out of the Joomla config 495 | * 496 | * @return string The json encoded result 497 | */ 498 | public function getSiteInfo() 499 | { 500 | $info = new stdClass(); 501 | 502 | $info->sitename = JFactory::getApplication()->getCfg('sitename'); 503 | 504 | return json_encode($info); 505 | } 506 | } 507 | 508 | JApplicationCli::getInstance('JoomlaCliUpdate')->execute(); 509 | --------------------------------------------------------------------------------