1 <?php
2 /**
3 * @package Joomla.Libraries
4 * @subpackage Installer
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 jimport('joomla.base.adapterinstance');
13
14 /**
15 * Abstract adapter for the installer.
16 *
17 * @method JInstaller getParent() Retrieves the parent object.
18 * @property-read JInstaller $parent Parent object
19 *
20 * @since 3.4
21 * @note As of 4.0, this class will no longer extend from JAdapterInstance
22 */
23 abstract class JInstallerAdapter extends JAdapterInstance
24 {
25 /**
26 * ID for the currently installed extension if present
27 *
28 * @var integer
29 * @since 3.4
30 */
31 protected $currentExtensionId = null;
32
33 /**
34 * The unique identifier for the extension (e.g. mod_login)
35 *
36 * @var string
37 * @since 3.4
38 * */
39 protected $element = null;
40
41 /**
42 * JTableExtension object.
43 *
44 * @var JTableExtension
45 * @since 3.4
46 * */
47 protected $extension = null;
48
49 /**
50 * Messages rendered by custom scripts
51 *
52 * @var string
53 * @since 3.4
54 */
55 protected $extensionMessage = '';
56
57 /**
58 * Copy of the XML manifest file.
59 *
60 * Making this object public allows extensions to customize the manifest in custom scripts.
61 *
62 * @var string
63 * @since 3.4
64 */
65 public $manifest = null;
66
67 /**
68 * A path to the PHP file that the scriptfile declaration in the manifest refers to.
69 *
70 * @var string
71 * @since 3.4
72 */
73 protected $manifest_script = null;
74
75 /**
76 * Name of the extension
77 *
78 * @var string
79 * @since 3.4
80 */
81 protected $name = null;
82
83 /**
84 * Install function routing
85 *
86 * @var string
87 * @since 3.4
88 */
89 protected $route = 'install';
90
91 /**
92 * Flag if the adapter supports discover installs
93 *
94 * Adapters should override this and set to false if discover install is unsupported
95 *
96 * @var boolean
97 * @since 3.4
98 */
99 protected $supportsDiscoverInstall = true;
100
101 /**
102 * The type of adapter in use
103 *
104 * @var string
105 * @since 3.4
106 */
107 protected $type;
108
109 /**
110 * Constructor
111 *
112 * @param JInstaller $parent Parent object
113 * @param JDatabaseDriver $db Database object
114 * @param array $options Configuration Options
115 *
116 * @since 3.4
117 */
118 public function __construct(JInstaller $parent, JDatabaseDriver $db, array $options = array())
119 {
120 parent::__construct($parent, $db, $options);
121
122 // Get a generic JTableExtension instance for use if not already loaded
123 if (!($this->extension instanceof JTableInterface))
124 {
125 $this->extension = JTable::getInstance('extension');
126 }
127
128 // Sanity check, make sure the type is set by taking the adapter name from the class name
129 if (!$this->type)
130 {
131 $this->type = strtolower(str_replace('JInstallerAdapter', '', get_called_class()));
132 }
133 }
134
135 /**
136 * Check if a package extension allows its child extensions to be uninstalled individually
137 *
138 * @param integer $packageId The extension ID of the package to check
139 *
140 * @return boolean
141 *
142 * @since 3.7.0
143 * @note This method defaults to true to emulate the behavior of 3.6 and earlier which did not support this lookup
144 */
145 protected function canUninstallPackageChild($packageId)
146 {
147 $package = JTable::getInstance('extension');
148
149 // If we can't load this package ID, we have a corrupt database
150 if (!$package->load((int) $packageId))
151 {
152 return true;
153 }
154
155 $manifestFile = JPATH_MANIFESTS . '/packages/' . $package->element . '.xml';
156
157 $xml = $this->parent->isManifest($manifestFile);
158
159 // If the manifest doesn't exist, we've got some major issues
160 if (!$xml)
161 {
162 return true;
163 }
164
165 $manifest = new JInstallerManifestPackage($manifestFile);
166
167 return $manifest->blockChildUninstall === false;
168 }
169
170 /**
171 * Method to check if the extension is already present in the database
172 *
173 * @return void
174 *
175 * @since 3.4
176 * @throws RuntimeException
177 */
178 protected function checkExistingExtension()
179 {
180 try
181 {
182 $this->currentExtensionId = $this->extension->find(
183 array('element' => $this->element, 'type' => $this->type)
184 );
185
186 // If it does exist, load it
187 if ($this->currentExtensionId)
188 {
189 $this->extension->load(array('element' => $this->element, 'type' => $this->type));
190 }
191 }
192 catch (RuntimeException $e)
193 {
194 // Install failed, roll back changes
195 throw new RuntimeException(
196 JText::sprintf(
197 'JLIB_INSTALLER_ABORT_ROLLBACK',
198 JText::_('JLIB_INSTALLER_' . $this->route),
199 $e->getMessage()
200 ),
201 $e->getCode(),
202 $e
203 );
204 }
205 }
206
207 /**
208 * Method to check if the extension is present in the filesystem, flags the route as update if so
209 *
210 * @return void
211 *
212 * @since 3.4
213 * @throws RuntimeException
214 */
215 protected function checkExtensionInFilesystem()
216 {
217 if (file_exists($this->parent->getPath('extension_root')) && (!$this->parent->isOverwrite() || $this->parent->isUpgrade()))
218 {
219 // Look for an update function or update tag
220 $updateElement = $this->getManifest()->update;
221
222 // Upgrade manually set or update function available or update tag detected
223 if ($updateElement || $this->parent->isUpgrade()
224 || ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update')))
225 {
226 // Force this one
227 $this->parent->setOverwrite(true);
228 $this->parent->setUpgrade(true);
229
230 if ($this->currentExtensionId)
231 {
232 // If there is a matching extension mark this as an update
233 $this->setRoute('update');
234 }
235 }
236 elseif (!$this->parent->isOverwrite())
237 {
238 // We didn't have overwrite set, find an update function or find an update tag so lets call it safe
239 throw new RuntimeException(
240 JText::sprintf(
241 'JLIB_INSTALLER_ABORT_DIRECTORY',
242 JText::_('JLIB_INSTALLER_' . $this->route),
243 $this->type,
244 $this->parent->getPath('extension_root')
245 )
246 );
247 }
248 }
249 }
250
251 /**
252 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
253 *
254 * @return void
255 *
256 * @since 3.4
257 * @throws RuntimeException
258 */
259 abstract protected function copyBaseFiles();
260
261 /**
262 * Method to create the extension root path if necessary
263 *
264 * @return void
265 *
266 * @since 3.4
267 * @throws RuntimeException
268 */
269 protected function createExtensionRoot()
270 {
271 // If the extension directory does not exist, lets create it
272 $created = false;
273
274 if (!file_exists($this->parent->getPath('extension_root')))
275 {
276 if (!$created = JFolder::create($this->parent->getPath('extension_root')))
277 {
278 throw new RuntimeException(
279 JText::sprintf(
280 'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
281 JText::_('JLIB_INSTALLER_' . $this->route),
282 $this->parent->getPath('extension_root')
283 )
284 );
285 }
286 }
287
288 /*
289 * Since we created the extension directory and will want to remove it if
290 * we have to roll back the installation, let's add it to the
291 * installation step stack
292 */
293
294 if ($created)
295 {
296 $this->parent->pushStep(
297 array(
298 'type' => 'folder',
299 'path' => $this->parent->getPath('extension_root'),
300 )
301 );
302 }
303 }
304
305 /**
306 * Generic discover_install method for extensions
307 *
308 * @return boolean True on success
309 *
310 * @since 3.4
311 */
312 public function discover_install()
313 {
314 // Get the extension's description
315 $description = (string) $this->getManifest()->description;
316
317 if ($description)
318 {
319 $this->parent->message = JText::_($description);
320 }
321 else
322 {
323 $this->parent->message = '';
324 }
325
326 // Set the extension's name and element
327 $this->name = $this->getName();
328 $this->element = $this->getElement();
329
330 /*
331 * ---------------------------------------------------------------------------------------------
332 * Extension Precheck and Setup Section
333 * ---------------------------------------------------------------------------------------------
334 */
335
336 // Setup the install paths and perform other prechecks as necessary
337 try
338 {
339 $this->setupInstallPaths();
340 }
341 catch (RuntimeException $e)
342 {
343 // Install failed, roll back changes
344 $this->parent->abort($e->getMessage());
345
346 return false;
347 }
348
349 /*
350 * ---------------------------------------------------------------------------------------------
351 * Installer Trigger Loading
352 * ---------------------------------------------------------------------------------------------
353 */
354
355 $this->setupScriptfile();
356
357 try
358 {
359 $this->triggerManifestScript('preflight');
360 }
361 catch (RuntimeException $e)
362 {
363 // Install failed, roll back changes
364 $this->parent->abort($e->getMessage());
365
366 return false;
367 }
368
369 /*
370 * ---------------------------------------------------------------------------------------------
371 * Database Processing Section
372 * ---------------------------------------------------------------------------------------------
373 */
374
375 try
376 {
377 $this->storeExtension();
378 }
379 catch (RuntimeException $e)
380 {
381 // Install failed, roll back changes
382 $this->parent->abort($e->getMessage());
383
384 return false;
385 }
386
387 try
388 {
389 $this->parseQueries();
390 }
391 catch (RuntimeException $e)
392 {
393 // Install failed, roll back changes
394 $this->parent->abort($e->getMessage());
395
396 return false;
397 }
398
399 // Run the custom install method
400 try
401 {
402 $this->triggerManifestScript('install');
403 }
404 catch (RuntimeException $e)
405 {
406 // Install failed, roll back changes
407 $this->parent->abort($e->getMessage());
408
409 return false;
410 }
411
412 /*
413 * ---------------------------------------------------------------------------------------------
414 * Finalization and Cleanup Section
415 * ---------------------------------------------------------------------------------------------
416 */
417
418 try
419 {
420 $this->finaliseInstall();
421 }
422 catch (RuntimeException $e)
423 {
424 // Install failed, roll back changes
425 $this->parent->abort($e->getMessage());
426
427 return false;
428 }
429
430 // And now we run the postflight
431 try
432 {
433 $this->triggerManifestScript('postflight');
434 }
435 catch (RuntimeException $e)
436 {
437 // Install failed, roll back changes
438 $this->parent->abort($e->getMessage());
439
440 return false;
441 }
442
443 return $this->extension->extension_id;
444 }
445
446 /**
447 * Method to handle database transactions for the installer
448 *
449 * @return boolean True on success
450 *
451 * @since 3.4
452 * @throws RuntimeException
453 */
454 protected function doDatabaseTransactions()
455 {
456 $route = $this->route === 'discover_install' ? 'install' : $this->route;
457
458 // Let's run the install queries for the component
459 if (isset($this->getManifest()->{$route}->sql))
460 {
461 $result = $this->parent->parseSQLFiles($this->getManifest()->{$route}->sql);
462
463 if ($result === false)
464 {
465 // Only rollback if installing
466 if ($route === 'install')
467 {
468 throw new RuntimeException(
469 JText::sprintf(
470 'JLIB_INSTALLER_ABORT_SQL_ERROR',
471 JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
472 $this->parent->getDbo()->stderr(true)
473 )
474 );
475 }
476
477 return false;
478 }
479
480 // If installing with success and there is an uninstall script, add a installer rollback step to rollback if needed
481 if ($route === 'install' && isset($this->getManifest()->uninstall->sql))
482 {
483 $this->parent->pushStep(array('type' => 'query', 'script' => $this->getManifest()->uninstall->sql));
484 }
485 }
486
487 return true;
488 }
489
490 /**
491 * Load language files
492 *
493 * @param string $extension The name of the extension
494 * @param string $source Path to the extension
495 * @param string $base Base path for the extension language
496 *
497 * @return void
498 *
499 * @since 3.4
500 */
501 protected function doLoadLanguage($extension, $source, $base = JPATH_ADMINISTRATOR)
502 {
503 $lang = JFactory::getLanguage();
504 $lang->load($extension . '.sys', $source, null, false, true) || $lang->load($extension . '.sys', $base, null, false, true);
505 }
506
507 /**
508 * Checks if the adapter supports discover_install
509 *
510 * @return boolean
511 *
512 * @since 3.4
513 */
514 public function getDiscoverInstallSupported()
515 {
516 return $this->supportsDiscoverInstall;
517 }
518
519 /**
520 * Get the filtered extension element from the manifest
521 *
522 * @param string $element Optional element name to be converted
523 *
524 * @return string The filtered element
525 *
526 * @since 3.4
527 */
528 public function getElement($element = null)
529 {
530 if (!$element)
531 {
532 // Ensure the element is a string
533 $element = (string) $this->getManifest()->element;
534 }
535
536 if (!$element)
537 {
538 $element = $this->getName();
539 }
540
541 // Filter the name for illegal characters
542 return strtolower(JFilterInput::getInstance()->clean($element, 'cmd'));
543 }
544
545 /**
546 * Get the manifest object.
547 *
548 * @return SimpleXMLElement Manifest object
549 *
550 * @since 3.4
551 */
552 public function getManifest()
553 {
554 return $this->manifest;
555 }
556
557 /**
558 * Get the filtered component name from the manifest
559 *
560 * @return string The filtered name
561 *
562 * @since 3.4
563 */
564 public function getName()
565 {
566 // Ensure the name is a string
567 $name = (string) $this->getManifest()->name;
568
569 // Filter the name for illegal characters
570 $name = JFilterInput::getInstance()->clean($name, 'string');
571
572 return $name;
573 }
574
575 /**
576 * Get the install route being followed
577 *
578 * @return string The install route
579 *
580 * @since 3.4
581 */
582 public function getRoute()
583 {
584 return $this->route;
585 }
586
587 /**
588 * Get the class name for the install adapter script.
589 *
590 * @return string The class name.
591 *
592 * @since 3.4
593 */
594 protected function getScriptClassName()
595 {
596 // Support element names like 'en-GB'
597 $className = JFilterInput::getInstance()->clean($this->element, 'cmd') . 'InstallerScript';
598
599 // Cannot have - in class names
600 $className = str_replace('-', '', $className);
601
602 return $className;
603 }
604
605 /**
606 * Generic install method for extensions
607 *
608 * @return boolean|integer The extension ID on success, boolean false on failure
609 *
610 * @since 3.4
611 */
612 public function install()
613 {
614 // Get the extension's description
615 $description = (string) $this->getManifest()->description;
616
617 if ($description)
618 {
619 $this->parent->message = JText::_($description);
620 }
621 else
622 {
623 $this->parent->message = '';
624 }
625
626 // Set the extension's name and element
627 $this->name = $this->getName();
628 $this->element = $this->getElement();
629
630 /*
631 * ---------------------------------------------------------------------------------------------
632 * Extension Precheck and Setup Section
633 * ---------------------------------------------------------------------------------------------
634 */
635
636 // Setup the install paths and perform other prechecks as necessary
637 try
638 {
639 $this->setupInstallPaths();
640 }
641 catch (RuntimeException $e)
642 {
643 // Install failed, roll back changes
644 $this->parent->abort($e->getMessage());
645
646 return false;
647 }
648
649 // Check to see if an extension by the same name is already installed.
650 try
651 {
652 $this->checkExistingExtension();
653 }
654 catch (RuntimeException $e)
655 {
656 // Install failed, roll back changes
657 $this->parent->abort($e->getMessage());
658
659 return false;
660 }
661
662 // Check if the extension is present in the filesystem
663 try
664 {
665 $this->checkExtensionInFilesystem();
666 }
667 catch (RuntimeException $e)
668 {
669 // Install failed, roll back changes
670 $this->parent->abort($e->getMessage());
671
672 return false;
673 }
674
675 // If we are on the update route, run any custom setup routines
676 if ($this->route === 'update')
677 {
678 try
679 {
680 $this->setupUpdates();
681 }
682 catch (RuntimeException $e)
683 {
684 // Install failed, roll back changes
685 $this->parent->abort($e->getMessage());
686
687 return false;
688 }
689 }
690
691 /*
692 * ---------------------------------------------------------------------------------------------
693 * Installer Trigger Loading
694 * ---------------------------------------------------------------------------------------------
695 */
696
697 $this->setupScriptfile();
698
699 try
700 {
701 $this->triggerManifestScript('preflight');
702 }
703 catch (RuntimeException $e)
704 {
705 // Install failed, roll back changes
706 $this->parent->abort($e->getMessage());
707
708 return false;
709 }
710
711 /*
712 * ---------------------------------------------------------------------------------------------
713 * Filesystem Processing Section
714 * ---------------------------------------------------------------------------------------------
715 */
716
717 // If the extension directory does not exist, lets create it
718 try
719 {
720 $this->createExtensionRoot();
721 }
722 catch (RuntimeException $e)
723 {
724 // Install failed, roll back changes
725 $this->parent->abort($e->getMessage());
726
727 return false;
728 }
729
730 // Copy all necessary files
731 try
732 {
733 $this->copyBaseFiles();
734 }
735 catch (RuntimeException $e)
736 {
737 // Install failed, roll back changes
738 $this->parent->abort($e->getMessage());
739
740 return false;
741 }
742
743 // Parse optional tags
744 $this->parseOptionalTags();
745
746 /*
747 * ---------------------------------------------------------------------------------------------
748 * Database Processing Section
749 * ---------------------------------------------------------------------------------------------
750 */
751
752 try
753 {
754 $this->storeExtension();
755 }
756 catch (RuntimeException $e)
757 {
758 // Install failed, roll back changes
759 $this->parent->abort($e->getMessage());
760
761 return false;
762 }
763
764 try
765 {
766 $this->parseQueries();
767 }
768 catch (RuntimeException $e)
769 {
770 // Install failed, roll back changes
771 $this->parent->abort($e->getMessage());
772
773 return false;
774 }
775
776 // Run the custom method based on the route
777 try
778 {
779 $this->triggerManifestScript($this->route);
780 }
781 catch (RuntimeException $e)
782 {
783 // Install failed, roll back changes
784 $this->parent->abort($e->getMessage());
785
786 return false;
787 }
788
789 /*
790 * ---------------------------------------------------------------------------------------------
791 * Finalization and Cleanup Section
792 * ---------------------------------------------------------------------------------------------
793 */
794
795 try
796 {
797 $this->finaliseInstall();
798 }
799 catch (RuntimeException $e)
800 {
801 // Install failed, roll back changes
802 $this->parent->abort($e->getMessage());
803
804 return false;
805 }
806
807 // And now we run the postflight
808 try
809 {
810 $this->triggerManifestScript('postflight');
811 }
812 catch (RuntimeException $e)
813 {
814 // Install failed, roll back changes
815 $this->parent->abort($e->getMessage());
816
817 return false;
818 }
819
820 return $this->extension->extension_id;
821 }
822
823 /**
824 * Method to parse the queries specified in the `<sql>` tags
825 *
826 * @return void
827 *
828 * @since 3.4
829 * @throws RuntimeException
830 */
831 protected function parseQueries()
832 {
833 // Let's run the queries for the extension
834 if (in_array($this->route, array('install', 'discover_install', 'uninstall')))
835 {
836 // This method may throw an exception, but it is caught by the parent caller
837 if (!$this->doDatabaseTransactions())
838 {
839 throw new RuntimeException(
840 JText::sprintf(
841 'JLIB_INSTALLER_ABORT_SQL_ERROR',
842 JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
843 $this->db->stderr(true)
844 )
845 );
846 }
847
848 // Set the schema version to be the latest update version
849 if ($this->getManifest()->update)
850 {
851 $this->parent->setSchemaVersion($this->getManifest()->update->schemas, $this->extension->extension_id);
852 }
853 }
854 elseif ($this->route === 'update')
855 {
856 if ($this->getManifest()->update)
857 {
858 $result = $this->parent->parseSchemaUpdates($this->getManifest()->update->schemas, $this->extension->extension_id);
859
860 if ($result === false)
861 {
862 // Install failed, rollback changes
863 throw new RuntimeException(
864 JText::sprintf(
865 'JLIB_INSTALLER_ABORT_SQL_ERROR',
866 JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
867 $this->db->stderr(true)
868 )
869 );
870 }
871 }
872 }
873 }
874
875 /**
876 * Method to parse optional tags in the manifest
877 *
878 * @return void
879 *
880 * @since 3.1
881 */
882 protected function parseOptionalTags()
883 {
884 // Some extensions may not have optional tags
885 }
886
887 /**
888 * Prepares the adapter for a discover_install task
889 *
890 * @return void
891 *
892 * @since 3.4
893 */
894 public function prepareDiscoverInstall()
895 {
896 // Adapters may not support discover install or may have overridden the default task and aren't using this
897 }
898
899 /**
900 * Set the manifest object.
901 *
902 * @param object $manifest The manifest object
903 *
904 * @return JInstallerAdapter Instance of this class to support chaining
905 *
906 * @since 3.4
907 */
908 public function setManifest($manifest)
909 {
910 $this->manifest = $manifest;
911
912 return $this;
913 }
914
915 /**
916 * Set the install route being followed
917 *
918 * @param string $route The install route being followed
919 *
920 * @return JInstallerAdapter Instance of this class to support chaining
921 *
922 * @since 3.4
923 */
924 public function setRoute($route)
925 {
926 $this->route = $route;
927
928 return $this;
929 }
930
931 /**
932 * Method to do any prechecks and setup the install paths for the extension
933 *
934 * @return void
935 *
936 * @since 3.4
937 */
938 abstract protected function setupInstallPaths();
939
940 /**
941 * Setup the manifest script file for those adapters that use it.
942 *
943 * @return void
944 *
945 * @since 3.4
946 */
947 protected function setupScriptfile()
948 {
949 // If there is an manifest class file, lets load it; we'll copy it later (don't have dest yet)
950 $manifestScript = (string) $this->getManifest()->scriptfile;
951
952 if ($manifestScript)
953 {
954 $manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;
955
956 $classname = $this->getScriptClassName();
957
958 JLoader::register($classname, $manifestScriptFile);
959
960 if (class_exists($classname))
961 {
962 // Create a new instance
963 $this->parent->manifestClass = new $classname($this);
964
965 // And set this so we can copy it later
966 $this->manifest_script = $manifestScript;
967 }
968 }
969 }
970
971 /**
972 * Method to setup the update routine for the adapter
973 *
974 * @return void
975 *
976 * @since 3.4
977 */
978 protected function setupUpdates()
979 {
980 // Some extensions may not have custom setup routines for updates
981 }
982
983 /**
984 * Method to store the extension to the database
985 *
986 * @return void
987 *
988 * @since 3.4
989 * @throws RuntimeException
990 */
991 abstract protected function storeExtension();
992
993 /**
994 * Executes a custom install script method
995 *
996 * @param string $method The install method to execute
997 *
998 * @return boolean True on success
999 *
1000 * @since 3.4
1001 * @throws RuntimeException
1002 */
1003 protected function triggerManifestScript($method)
1004 {
1005 ob_start();
1006 ob_implicit_flush(false);
1007
1008 if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $method))
1009 {
1010 switch ($method)
1011 {
1012 // The preflight and postflight take the route as a param
1013 case 'preflight' :
1014 case 'postflight' :
1015 if ($this->parent->manifestClass->$method($this->route, $this) === false)
1016 {
1017 if ($method !== 'postflight')
1018 {
1019 // Clean and close the output buffer
1020 ob_end_clean();
1021
1022 // The script failed, rollback changes
1023 throw new RuntimeException(
1024 JText::sprintf(
1025 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
1026 JText::_('JLIB_INSTALLER_' . $this->route)
1027 )
1028 );
1029 }
1030 }
1031 break;
1032
1033 // The install, uninstall, and update methods only pass this object as a param
1034 case 'install' :
1035 case 'uninstall' :
1036 case 'update' :
1037 if ($this->parent->manifestClass->$method($this) === false)
1038 {
1039 if ($method !== 'uninstall')
1040 {
1041 // Clean and close the output buffer
1042 ob_end_clean();
1043
1044 // The script failed, rollback changes
1045 throw new RuntimeException(
1046 JText::sprintf(
1047 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
1048 JText::_('JLIB_INSTALLER_' . $this->route)
1049 )
1050 );
1051 }
1052 }
1053 break;
1054 }
1055 }
1056
1057 // Append to the message object
1058 $this->extensionMessage .= ob_get_clean();
1059
1060 // If in postflight or uninstall, set the message for display
1061 if (($method === 'uninstall' || $method === 'postflight') && $this->extensionMessage !== '')
1062 {
1063 $this->parent->set('extension_message', $this->extensionMessage);
1064 }
1065
1066 return true;
1067 }
1068
1069 /**
1070 * Generic update method for extensions
1071 *
1072 * @return boolean|integer The extension ID on success, boolean false on failure
1073 *
1074 * @since 3.4
1075 */
1076 public function update()
1077 {
1078 // Set the overwrite setting
1079 $this->parent->setOverwrite(true);
1080 $this->parent->setUpgrade(true);
1081
1082 // And make sure the route is set correctly
1083 $this->setRoute('update');
1084
1085 // Now jump into the install method to run the update
1086 return $this->install();
1087 }
1088 }
1089