* @git Joomla Component Builder * @copyright Copyright (C) 2015 Vast Development Method. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ // No direct access to this JCB template file (EVER) defined('_JCB_TEMPLATE') or die; ?> ###BOM### use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Filesystem\File; use Joomla\CMS\Installer\InstallerAdapter; use Joomla\CMS\Installer\InstallerScriptInterface; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Log\Log; use Joomla\CMS\Version; use Joomla\CMS\HTML\HTMLHelper as Html; use Joomla\Filesystem\Folder; use Joomla\Database\DatabaseInterface; // No direct access to this file defined('_JEXEC') or die; /** * Script File of ###Component### Component * * @since 3.6 */ class Com_###Component###InstallerScript implements InstallerScriptInterface { /** * The CMS Application. * * @var CMSApplication * @since 4.4.2 */ protected $app; /** * The database class. * * @since 4.4.2 */ protected $db; /** * The version number of the extension. * * @var string * @since 3.6 */ protected $release; /** * The table the parameters are stored in. * * @var string * @since 3.6 */ protected $paramTable; /** * The extension name. This should be set in the installer script. * * @var string * @since 3.6 */ protected $extension; /** * A list of files to be deleted * * @var array * @since 3.6 */ protected $deleteFiles = []; /** * A list of folders to be deleted * * @var array * @since 3.6 */ protected $deleteFolders = []; /** * A list of CLI script files to be copied to the cli directory * * @var array * @since 3.6 */ protected $cliScriptFiles = []; /** * Minimum PHP version required to install the extension * * @var string * @since 3.6 */ protected $minimumPhp; /** * Minimum Joomla! version required to install the extension * * @var string * @since 3.6 */ protected $minimumJoomla; /** * Extension script constructor. * * @since 3.0.0 */ public function __construct() { $this->minimumJoomla = '4.3'; $this->minimumPhp = JOOMLA_MINIMUM_PHP; $this->app ??= Factory::getApplication(); $this->db = Factory::getContainer()->get(DatabaseInterface::class); // check if the files exist if (is_file(JPATH_ROOT . '/administrator/components/com_###component###/###component###.php')) { // remove Joomla 3 files $this->deleteFiles = [ '/administrator/components/com_###component###/###component###.php', '/administrator/components/com_###component###/controller.php', '/components/com_###component###/###component###.php', '/components/com_###component###/controller.php', '/components/com_###component###/router.php', ]; } // check if the Folders exist if (is_dir(JPATH_ROOT . '/administrator/components/com_###component###/modules')) { // remove Joomla 3 folder $this->deleteFolders = [ '/administrator/components/com_###component###/controllers', '/administrator/components/com_###component###/helpers', '/administrator/components/com_###component###/modules', '/administrator/components/com_###component###/tables', '/administrator/components/com_###component###/views', '/components/com_###component###/controllers', '/components/com_###component###/helpers', '/components/com_###component###/modules', '/components/com_###component###/views', ]; } } /** * Function called after the extension is installed. * * @param InstallerAdapter $adapter The adapter calling this method * * @return boolean True on success * * @since 4.2.0 */ public function install(InstallerAdapter $adapter): bool {return true;} /** * Function called after the extension is updated. * * @param InstallerAdapter $adapter The adapter calling this method * * @return boolean True on success * * @since 4.2.0 */ public function update(InstallerAdapter $adapter): bool {return true;} /** * Function called after the extension is uninstalled. * * @param InstallerAdapter $adapter The adapter calling this method * * @return boolean True on success * * @since 4.2.0 */ public function uninstall(InstallerAdapter $adapter): bool {###UNINSTALLSCRIPT### // little notice as after service, in case of bad experience with component. echo '

Did something go wrong? Are you disappointed?

Please let me know at ###AUTHOREMAIL###.
We at ###COMPANYNAME### are committed to building extensions that performs proficiently! You can help us, really!
Send me your thoughts on improvements that is needed, trust me, I will be very grateful!
Visit us at ###AUTHORWEBSITE### today!

'; return true; } /** * Function called before extension installation/update/removal procedure commences. * * @param string $type The type of change (install or discover_install, update, uninstall) * @param InstallerAdapter $adapter The adapter calling this method * * @return boolean True on success * * @since 4.2.0 */ public function preflight(string $type, InstallerAdapter $adapter): bool { // Check for the minimum PHP version before continuing if (!empty($this->minimumPhp) && version_compare(PHP_VERSION, $this->minimumPhp, '<')) { Log::add(Text::sprintf('JLIB_INSTALLER_MINIMUM_PHP', $this->minimumPhp), Log::WARNING, 'jerror'); return false; } // Check for the minimum Joomla version before continuing if (!empty($this->minimumJoomla) && version_compare(JVERSION, $this->minimumJoomla, '<')) { Log::add(Text::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA', $this->minimumJoomla), Log::WARNING, 'jerror'); return false; } // Extension manifest file version $this->extension = $adapter->getName(); $this->release = $adapter->getManifest()->version; // do any updates needed if ($type === 'update') {###PREUPDATESCRIPT### } // do any install needed if ($type === 'install') {###PREINSTALLSCRIPT### } return true; } /** * Function called after extension installation/update/removal procedure commences. * * @param string $type The type of change (install or discover_install, update, uninstall) * @param InstallerAdapter $adapter The adapter calling this method * * @return boolean True on success * * @since 4.2.0 */ public function postflight(string $type, InstallerAdapter $adapter): bool {###MOVEFOLDERSSCRIPT### // set the default component settings if ($type === 'install') {###POSTINSTALLSCRIPT### } // do any updates needed if ($type === 'update') {###POSTUPDATESCRIPT### } // move CLI files $this->moveCliFiles(); // remove old files and folders $this->removeFiles(); return true; } /** * Remove the files and folders in the given array from * * @return void * * @since 3.6 */ protected function removeFiles() { if (!empty($this->deleteFiles)) { foreach ($this->deleteFiles as $file) { if (is_file(JPATH_ROOT . $file) && !File::delete(JPATH_ROOT . $file)) { echo Text::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER', $file) . '
'; } } } if (!empty($this->deleteFolders)) { foreach ($this->deleteFolders as $folder) { if (is_dir(JPATH_ROOT . $folder) && !Folder::delete(JPATH_ROOT . $folder)) { echo Text::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER', $folder) . '
'; } } } } /** * Moves the CLI scripts into the CLI folder in the CMS * * @return void * * @since 3.6 */ protected function moveCliFiles() { if (!empty($this->cliScriptFiles)) { foreach ($this->cliScriptFiles as $file) { $name = basename($file); if (file_exists(JPATH_ROOT . $file) && !File::move(JPATH_ROOT . $file, JPATH_ROOT . '/cli/' . $name)) { echo Text::sprintf('JLIB_INSTALLER_FILE_ERROR_MOVE', $name); } } } } /** * Set content type integration * * @param string $typeTitle * @param string $typeAlias * @param string $table * @param string $rules * @param string $fieldMappings * @param string $router * @param string $contentHistoryOptions * * @return void * @since 4.4.2 */ protected function setContentType( string $typeTitle, string $typeAlias, string $table, string $rules, string $fieldMappings, string $router, string $contentHistoryOptions): void { // Create the content type object. $content = new stdClass(); $content->type_title = $typeTitle; $content->type_alias = $typeAlias; $content->table = $table; $content->rules = $rules; $content->field_mappings = $fieldMappings; $content->router = $router; $content->content_history_options = $contentHistoryOptions; // Check if content type is already in content_type DB. $query = $this->db->getQuery(true); $query->select($this->db->quoteName(array('type_id'))); $query->from($this->db->quoteName('#__content_types')); $query->where($this->db->quoteName('type_alias') . ' LIKE '. $this->db->quote($content->type_alias)); $this->db->setQuery($query); $this->db->execute(); // Check if the type alias is already in the content types table. if ($this->db->getNumRows()) { $content->type_id = $this->db->loadResult(); if ($this->db->updateObject('#__content_types', $content, 'type_id')) { // If its successfully update. $this->app->enqueueMessage( Text::sprintf('The (%s) was found in the #__content_types table, and updated.', $content->type_alias) ); } } elseif ($this->db->insertObject('#__content_types', $content)) { // If its successfully added. $this->app->enqueueMessage( Text::sprintf('The (%s) was added to the #__content_types table.', $content->type_alias) ); } } /** * Set action log config integration * * @param string $typeTitle * @param string $typeAlias * @param string $idHolder * @param string $titleHolder * @param string $tableName * @param string $textPrefix * * @return void * @since 4.4.2 */ protected function setActionLogConfig( string $typeTitle, string $typeAlias, string $idHolder, string $titleHolder, string $tableName, string $textPrefix): void { // Create the content action log config object. $content = new stdClass(); $content->type_title = $typeTitle; $content->type_alias = $typeAlias; $content->id_holder = $idHolder; $content->title_holder = $titleHolder; $content->table_name = $tableName; $content->text_prefix = $textPrefix; // Check if the action log config is already in action_log_config DB. $query = $this->db->getQuery(true); $query->select($this->db->quoteName(['id'])); $query->from($this->db->quoteName('#__action_log_config')); $query->where($this->db->quoteName('type_alias') . ' LIKE '. $this->db->quote($content->type_alias)); $this->db->setQuery($query); $this->db->execute(); // Check if the type alias is already in the action log config table. if ($this->db->getNumRows()) { $content->id = $this->db->loadResult(); if ($this->db->updateObject('#__action_log_config', $content, 'id')) { // If its successfully update. $this->app->enqueueMessage( Text::sprintf('The (%s) was found in the #__action_log_config table, and updated.', $content->type_alias) ); } } elseif ($this->db->insertObject('#__action_log_config', $content)) { // If its successfully added. $this->app->enqueueMessage( Text::sprintf('The (%s) was added to the #__action_log_config table.', $content->type_alias) ); } } /** * Set action logs extensions integration * * @return void * @since 4.4.2 */ protected function setActionLogsExtensions(): void { // Create the extension action logs object. $data = new stdClass(); $data->extension = 'com_###component###'; // Check if ###component### action log extension is already in action logs extensions DB. $query = $this->db->getQuery(true); $query->select($this->db->quoteName(['id'])); $query->from($this->db->quoteName('#__action_logs_extensions')); $query->where($this->db->quoteName('extension') . ' = '. $this->db->quote($data->extension)); $this->db->setQuery($query); $this->db->execute(); // Set the object into the action logs extensions table if not found. if ($this->db->getNumRows()) { // If its already set don't set it again. $this->app->enqueueMessage( Text::_('The (com_###component###) is already in the #__action_logs_extensions table.') ); } elseif ($this->db->insertObject('#__action_logs_extensions', $data)) { // give a success message $this->app->enqueueMessage( Text::_('The (com_###component###) was successfully added to the #__action_logs_extensions table.') ); } } /** * Set global extension assets permission of this component * (on install only) * * @param string $rules The component rules * * @return void * @since 4.4.2 */ protected function setAssetsRules(string $rules): void { // Condition. $conditions = [ $this->db->quoteName('name') . ' = ' . $this->db->quote('com_###component###') ]; // Field to update. $fields = [ $this->db->quoteName('rules') . ' = ' . $this->db->quote($rules), ]; $query = $this->db->getQuery(true); $query->update( $this->db->quoteName('#__assets') )->set($fields)->where($conditions); $this->db->setQuery($query); $done = $this->db->execute(); if ($done) { // give a success message $this->app->enqueueMessage( Text::_('The (com_###component###) rules was successfully added to the #__assets table.') ); } } /** * Set global extension params of this component * (on install only) * * @param string $params The component rules * * @return void * @since 4.4.2 */ protected function setExtensionsParams(string $params): void { // Condition. $conditions = [ $this->db->quoteName('element') . ' = ' . $this->db->quote('com_###component###') ]; // Field to update. $fields = [ $this->db->quoteName('params') . ' = ' . $this->db->quote($params), ]; $query = $this->db->getQuery(true); $query->update( $this->db->quoteName('#__extensions') )->set($fields)->where($conditions); $this->db->setQuery($query); $done = $this->db->execute(); if ($done) { // give a success message $this->app->enqueueMessage( Text::_('The (com_###component###) params was successfully added to the #__extensions table.') ); } } /** * Set database fix (if needed) * => WHY DO WE NEED AN ASSET TABLE FIX? * https://git.vdm.dev/joomla/Component-Builder/issues/616#issuecomment-12085 * https://www.mysqltutorial.org/mysql-varchar/ * https://stackoverflow.com/a/15227917/1429677 * https://forums.mysql.com/read.php?24,105964,105964 * * @param int $accessWorseCase This is the max rules column size com_###component### would needs. * @param string $dataType This datatype we will change the rules column to if it to small. * * @return void * @since 4.4.2 */ protected function setDatabaseAssetsRulesFix(int $accessWorseCase, string $dataType): void { // Get the biggest rule column in the assets table at this point. $length = "SELECT CHAR_LENGTH(`rules`) as rule_size FROM #__assets ORDER BY rule_size DESC LIMIT 1"; $this->db->setQuery($length); if ($this->db->execute()) { $rule_length = $this->db->loadResult(); // Check the size of the rules column if ($rule_length <= $accessWorseCase) { // Fix the assets table rules column size $fix = "ALTER TABLE `#__assets` CHANGE `rules` `rules` {$dataType} NOT NULL COMMENT 'JSON encoded access control. Enlarged to {$dataType} by ###Component###';"; $this->db->setQuery($fix); $done = $this->db->execute(); if ($done) { $this->app->enqueueMessage( Text::sprintf('The #__assets table rules column was resized to the %s datatype for the components possible large permission rules.', $dataType) ); } } } } /** * Remove remnant data related to this view * * @param string $context The view context * @param bool $fields The switch to also remove related field data * * @return void * @since 4.4.2 */ protected function removeViewData(string $context, bool $fields = false): void { $this->removeContentTypes($context); $this->removeViewHistory($context); $this->removeUcmContent($context); // this might be obsolete... $this->removeContentItemTagMap($context); $this->removeActionLogConfig($context); if ($fields) { $this->removeFields($context); $this->removeFieldsGroups($context); } } /** * Remove content types related to this view * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeContentTypes(string $context): void { // Create a new query object. $query = $this->db->getQuery(true); // Select id from content type table $query->select($this->db->quoteName('type_id')); $query->from($this->db->quoteName('#__content_types')); // Where Item alias is found $query->where($this->db->quoteName('type_alias') . ' = '. $this->db->quote($context)); $this->db->setQuery($query); // Execute query to see if alias is found $this->db->execute(); $found = $this->db->getNumRows(); // Now check if there were any rows if ($found) { // Since there are load the needed item type ids $ids = $this->db->loadColumn(); // Remove Item from the content type table $condition = [ $this->db->quoteName('type_alias') . ' = '. $this->db->quote($context) ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__content_types')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove Item items $done = $this->db->execute(); if ($done) { // If successfully remove Item add queued success message. $this->app->enqueueMessage( Text::sprintf('The (%s) type alias was removed from the #__content_type table.', $context) ); } // Make sure that all the items are cleared from DB $this->removeUcmBase($ids); } } /** * Remove fields related to this view * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeFields(string $context): void { // Create a new query object. $query = $this->db->getQuery(true); // Select ids from fields $query->select($this->db->quoteName('id')); $query->from($this->db->quoteName('#__fields')); // Where context is found $query->where( $this->db->quoteName('context') . ' = '. $this->db->quote($context) ); $this->db->setQuery($query); // Execute query to see if context is found $this->db->execute(); $found = $this->db->getNumRows(); // Now check if there were any rows if ($found) { // Since there are load the needed release_check field ids $ids = $this->db->loadColumn(); // Create a new query object. $query = $this->db->getQuery(true); // Remove context from the field table $condition = [ $this->db->quoteName('context') . ' = '. $this->db->quote($context) ]; $query->delete($this->db->quoteName('#__fields')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove release_check items $done = $this->db->execute(); if ($done) { // If successfully remove context add queued success message. $this->app->enqueueMessage( Text::sprintf('The fields with context (%s) was removed from the #__fields table.', $context) ); } // Make sure that all the field values are cleared from DB $this->removeFieldsValues($context, $ids); } } /** * Remove fields values related to fields * * @param string $context The view context * @param array $ids The view context * * @return void * @since 4.4.2 */ protected function removeFieldsValues(string $context, array $ids): void { $condition = [ $this->db->quoteName('field_id') . ' IN ('. implode(',', $ids) .')' ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__fields_values')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove field values $done = $this->db->execute(); if ($done) { // If successfully remove release_check add queued success message. $this->app->enqueueMessage( Text::sprintf('The fields values for (%s) was removed from the #__fields_values table.', $context) ); } } /** * Remove fields groups related to fields * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeFieldsGroups(string $context): void { // Create a new query object. $query = $this->db->getQuery(true); // Select ids from fields $query->select($this->db->quoteName('id')); $query->from($this->db->quoteName('#__fields_groups')); // Where context is found $query->where( $this->db->quoteName('context') . ' = '. $this->db->quote($context) ); $this->db->setQuery($query); // Execute query to see if context is found $this->db->execute(); $found = $this->db->getNumRows(); // Now check if there were any rows if ($found) { // Create a new query object. $query = $this->db->getQuery(true); // Remove context from the field table $condition = [ $this->db->quoteName('context') . ' = '. $this->db->quote($context) ]; $query->delete($this->db->quoteName('#__fields_groups')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove release_check items $done = $this->db->execute(); if ($done) { // If successfully remove context add queued success message. $this->app->enqueueMessage( Text::sprintf('The fields with context (%s) was removed from the #__fields_groups table.', $context) ); } } } /** * Remove history related to this view * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeViewHistory(string $context): void { // Remove Item items from the ucm content table $condition = [ $this->db->quoteName('item_id') . ' LIKE ' . $this->db->quote($context . '.%') ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__history')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove Item items $done = $this->db->execute(); if ($done) { // If successfully removed Items add queued success message. $this->app->enqueueMessage( Text::sprintf('The (%s) items were removed from the #__history table.', $context) ); } } /** * Remove ucm base values related to these IDs * * @param array $ids The type ids * * @return void * @since 4.4.2 */ protected function removeUcmBase(array $ids): void { // Make sure that all the items are cleared from DB foreach ($ids as $type_id) { // Remove Item items from the ucm base table $condition = [ $this->db->quoteName('ucm_type_id') . ' = ' . $type_id ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__ucm_base')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove Item items $this->db->execute(); } $this->app->enqueueMessage( Text::_('All related items was removed from the #__ucm_base table.') ); } /** * Remove ucm content values related to this view * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeUcmContent(string $context): void { // Remove Item items from the ucm content table $condition = [ $this->db->quoteName('core_type_alias') . ' = ' . $this->db->quote($context) ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__ucm_content')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove Item items $done = $this->db->execute(); if ($done) { // If successfully removed Item add queued success message. $this->app->enqueueMessage( Text::sprintf('The (%s) type alias was removed from the #__ucm_content table.', $context) ); } } /** * Remove content item tag map related to this view * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeContentItemTagMap(string $context): void { // Create a new query object. $query = $this->db->getQuery(true); // Remove Item items from the contentitem tag map table $condition = [ $this->db->quoteName('type_alias') . ' = '. $this->db->quote($context) ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__contentitem_tag_map')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove Item items $done = $this->db->execute(); if ($done) { // If successfully remove Item add queued success message. $this->app->enqueueMessage( Text::sprintf('The (%s) type alias was removed from the #__contentitem_tag_map table.', $context) ); } } /** * Remove action log config related to this view * * @param string $context The view context * * @return void * @since 4.4.2 */ protected function removeActionLogConfig(string $context): void { // Remove ###component### view from the action_log_config table $condition = [ $this->db->quoteName('type_alias') . ' = '. $this->db->quote($context) ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__action_log_config')); $query->where($condition); $this->db->setQuery($query); // Execute the query to remove com_###component###.view $done = $this->db->execute(); if ($done) { // If successfully removed ###component### view add queued success message. $this->app->enqueueMessage( Text::sprintf('The (%s) type alias was removed from the #__action_log_config table.', $context) ); } } /** * Remove Asset Table Integrated * * @return void * @since 4.4.2 */ protected function removeAssetData(): void { // Remove ###component### assets from the assets table $condition = [ $this->db->quoteName('name') . ' LIKE ' . $this->db->quote('com_###component###.%') ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__assets')); $query->where($condition); $this->db->setQuery($query); $done = $this->db->execute(); if ($done) { // If successfully removed ###component### add queued success message. $this->app->enqueueMessage( Text::_('All related (com_###component###) items was removed from the #__assets table.') ); } } /** * Remove action logs extensions integrated * * @return void * @since 4.4.2 */ protected function removeActionLogsExtensions(): void { // Remove ###component### from the action_logs_extensions table $extension = [ $this->db->quoteName('extension') . ' = ' . $this->db->quote('com_###component###') ]; // Create a new query object. $query = $this->db->getQuery(true); $query->delete($this->db->quoteName('#__action_logs_extensions')); $query->where($extension); $this->db->setQuery($query); // Execute the query to remove ###component### $done = $this->db->execute(); if ($done) { // If successfully remove ###component### add queued success message. $this->app->enqueueMessage( Text::_('The (com_###component###) extension was removed from the #__action_logs_extensions table.') ); } } /** * Remove remove database fix (if possible) * * @return void * @since 4.4.2 */ protected function removeDatabaseAssetsRulesFix(): void { // Get the biggest rule column in the assets table at this point. $length = "SELECT CHAR_LENGTH(`rules`) as rule_size FROM #__assets ORDER BY rule_size DESC LIMIT 1"; $this->db->setQuery($length); if ($this->db->execute()) { $rule_length = $this->db->loadResult(); // Check the size of the rules column if ($rule_length < 5120) { // Revert the assets table rules column back to the default $revert_rule = "ALTER TABLE `#__assets` CHANGE `rules` `rules` varchar(5120) NOT NULL COMMENT 'JSON encoded access control.';"; $this->db->setQuery($revert_rule); $this->db->execute(); $this->app->enqueueMessage( Text::_('Reverted the #__assets table rules column back to its default size of varchar(5120).') ); } else { $this->app->enqueueMessage( Text::_('Could not revert the #__assets table rules column back to its default size of varchar(5120), since there is still one or more components that still requires the column to be larger.') ); } } }###INSTALLERMETHODS### }