<?php
/**
 * @author       Aicha Vack
 * @package      Joomla.Administrator
 * @subpackage   com_visforms
 * @link         https://www.vi-solutions.de
 * @license      GNU General Public License version 2 or later; see license.txt
 * @copyright    2021 vi-solutions
 * @since        Joomla 1.6
 */

// no direct access
defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Factory;
use Joomla\CMS\Version;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Date\Date;

class pkg_vfbaseInstallerScript {
	// common members for all installation script files
	private $release;
	private $oldRelease;
	private $minimum_joomla_release= 4;
	private $maximum_joomla_release = 5;
	private $min_visforms_version;
	private $name;
	private $loggerName;
	// special members
	private $vfsubminversion;
	private $last_modified_view_files_version;
	private $languageFoldersBackend;
	private $languageFoldersFrontend;
    // list of visforms beta versions; used to display extra text
    // if visforms is beta, subscription is also
    private $visformsBetaVersions = array('4.3.0');

	// construction

	public function __construct($adapter) {
		$this->initializeLogger($adapter);
		$date = new Date('now');
		$this->addLogEntry('', Log::INFO);
		$this->addLogEntry("************ package installation: visforms ( $date ) ************", Log::INFO);
		$this->addLogEntry('Joomla ' . (new Version())->getShortVersion(), Log::INFO);
		$this->addLogEntry('PHP    ' . PHP_VERSION . ' (' . PHP_OS . ') ', Log::INFO);
		$this->addLogEntry('running in: ' . preg_replace('#[\\\/]#i', '|', dirname(__FILE__)), Log::INFO);
		$this->addLogEntry('****** script file loaded: base package - administrator/manifests/packages/vfbase/script.php ******', Log::INFO);
		$this->languageFoldersBackend = array(
			'/administrator/components/com_visforms/language'        => 'com_visforms',
			'/plugins/actionlog/visforms/language'                   => 'plg_actionlog_visforms',
			'/plugins/editors-xtd/visformfields/language'            => 'plg_editors-xtd_visformfields',
			'/plugins/privacy/visforms/language'                     => 'plg_privacy_visforms',
			'/plugins/visforms/spambotcheck/language'                => 'plg_visforms_spambotcheck',
			'/plugins/visforms/visforms/language'                    => 'plg_visforms_visforms',
		);
		$this->languageFoldersFrontend = array(
			'/components/com_visforms/language'                      => 'com_visforms',
			'/modules/mod_visforms/language'                         => 'mod_visforms',
		);

        // set global error handler to get all PHP errors during installation
		$old_error_handler = set_error_handler(array($this, 'errorHandler'));
	}

	// interface

	public function preflight($route,  $adapter) {
		$this->addLogEntry('start preflight', Log::INFO);
		// filesystem: test for write access
		if ($route != 'uninstall') {
			if($this->testWriteAccessDenied()) {
				return false;
			}
		}

		$this->release = $adapter->getManifest()->version;
		$this->minimum_joomla_release = $adapter->getManifest()->attributes()->version;
		$this->oldRelease = "";
		$this->min_visforms_version = $adapter->getManifest()->vfminversion;
		$this->vfsubminversion = $adapter->getManifest()->vfsubminversion;
		$this->last_modified_view_files_version = $adapter->getManifest()->last_modified_view_files_version;
		$this->name = $adapter->getManifest()->name;
		$jversion = new Version;
		$date = new Date('now');
		$app = Factory::getApplication();
		$this->addLogEntry('*** start ' . $route . ' of extension ' . $this->name . ' ' . $this->release . ': ' . $date . ' ***', Log::INFO);

		// all version tests go here
		if ($route != 'uninstall') {
			// abort if the current Joomla release is too old or too new
			if (version_compare($jversion->getShortVersion(), $this->minimum_joomla_release, 'lt')) {
			    $msg = Text::sprintf('PKG_VFBASE_WRONG_JOOMLA_VERSION', $this->name, $this->minimum_joomla_release);
				$app->enqueueMessage($msg, 'ERROR');
				$this->addLogEntry($msg, Log::ERROR);
				return false;
			}
            if ($jversion::MAJOR_VERSION > $this->maximum_joomla_release) {
			    $msg = Text::sprintf('PKG_VFBASE_WRONG_MAX_JOOMLA_VERSION', $this->name, $this->maximum_joomla_release);
				$app->enqueueMessage($msg, 'ERROR');
				$this->addLogEntry($msg, Log::ERROR);
				return false;
			}
			// tests for update route only
			if ($route == 'update') {
				// abort if this package version is lower than the installed version
				$this->oldRelease = $this->getParam('version', $this->name);
				if (version_compare($this->release, $this->oldRelease, 'lt')) {
					$msg = Text::sprintf('PKG_VFBASE_WRONG_VF_VERSION', $this->oldRelease, $this->release);
					$app->enqueueMessage($msg, 'ERROR');
					$this->addLogEntry($msg, Log::ERROR);
					return false;
				}
				// finally, log that we are on update route
				$this->addLogEntry('trying to update from version ' . $this->oldRelease . ' to ' . $this->release, Log::INFO);
			}
		}

		// test for downloading PHP fonts: is it necessary and possible
		if ($route != 'uninstall') {
			if ( !File::exists(Path::clean(JPATH_ROOT . '/media/com_visforms/tcpdf/fonts/helvetica.php'))) {
				// no fonts installed
				$this->addLogEntry('PDF fonts not installed', Log::INFO);
				// test for PHP directive
				$directive = 'allow_url_fopen';
				$value = ini_get($directive);
				$this->addLogEntry("PHP directive '$directive' set to: $value", Log::INFO);
				// case (false === $value) means: could not read PHP directive: we ignore this for now
				if ('0' === $value) {
					$text = Text::_('PKG_VFBASE_WRONG_PHP_SETTING_ALLOW_URL_FOPEN');
					$app  = Factory::getApplication();
					$app->enqueueMessage($text, 'warning');
					$this->addLogEntry('PDF fonts can not be installed due to PHP settings', Log::INFO);
					// we do not return false: simply show installation message to user and continue
				}
				else {
					$this->addLogEntry('PDF fonts can be installed due to PHP settings', Log::INFO);
				}
			}
		}

		// handle custom translation files
		if ($route != 'uninstall') {
			$this->copyCustomTranslationFiles();
		}

		$this->addLogEntry('end preflight', Log::INFO);
		return true;
	}

	public function postflight($route,  $adapter) {
		$this->addLogEntry('start postflight', Log::INFO);

		if ($route == 'update') {
			$this->deleteOldFiles();
		}
		// todo: clarify difference to: ($route !== 'uninstall')
		if ($route == 'install' || $route == 'update') {
            $manifest = $adapter->getParent()->manifest;
            $packages = $manifest->xpath('files/file');
			if (!empty($packages)) {
			    $this->deleteUpdateSites($packages);
            }
		}
		if ($route !== 'uninstall') {
			// delete possible visforms language folders
			$this->deleteTranslationFolders();
			// enable plugins per default
		    $this->enableExtension('plg_editors-xtd_visformfields', 'plugin', 'visformfields', 'editors-xtd');
            $this->enableExtension('plg_visforms_visforms', 'plugin', 'visforms', 'visforms');
            $this->enableExtension('plg_visforms_spambotcheck', 'plugin', 'spambotcheck', 'visforms');
            $this->showInstallUpdateMessage($route);
            $this->addLogEntry($route . ' of ' . $this->name . ' successful', Log::INFO);
		}

		$this->addLogEntry('end postflight', Log::INFO);
		return true;
	}

	public function uninstall($adapter) {
		$this->addLogEntry('start uninstall', Log::INFO);

		$manifestFile = JPATH_MANIFESTS . '/packages/pkg_vfbase.xml';
		if (!file_exists($manifestFile)) {
		    return;
        }
		$xml = simplexml_load_file($manifestFile);
		if (!$xml) {
		    return;
        }
		$release = $xml->version;
		if (empty($release)) {
		    return;
        }
		$language = Factory::getApplication()->getLanguage();
		$language->load('pkg_vfbase', JPATH_ROOT);
        // show user final end message
        $this->showUninstallMessage($release);

		$this->addLogEntry('end uninstall', Log::INFO);
	}

	// implementation

	private function getParam($pname, $name) {
		// get a variable from the manifest cache in database
		$db = Factory::getDbo();
		$query = $db->getQuery(true);
		$query->select($db->qn('manifest_cache'))
			->from($db->qn('#__extensions'))
			->where($db->qn('name') . ' = ' . $db->q($name));
		try {
			$db->setQuery($query);
			$result = $db->loadResult();
			if (empty($result)) {
				return '';
			}
			$manifest = json_decode($result, true);
			if (isset($manifest[$pname])) {
				return $manifest[$pname];
			}
			return '';
		}
		catch (Exception $e) {
			$this->addLogEntry('unable to get ' . $name . ' ' . $pname . ' from manifest cache in database, ' . $e->getMessage(), Log::ERROR);
			return false;
		}
	}

	private function getExtensionId($type, $name, $group = '', $client_id = 0) {
		$db = Factory::getDbo();
		$where = $db->quoteName('type') . ' = ' . $db->quote($type) . ' AND ' . $db->quoteName('element') . ' = ' . $db->quote($name);
		$query = $db->getQuery(true)
			->select($db->quoteName('extension_id'))
			->from($db->quoteName('#__extensions'))
			->where($where);
		try {
			$db->setQuery($query);
			$id = $db->loadResult();
		}
		catch (RuntimeException $e) {
			$this->addLogEntry('unable to get extension_id: ' . $name . ', ' . $e->getMessage(), Log::INFO);
			return false;
		}
		return $id;
	}

	private function getUpdateSites($extension) {
		$db = Factory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('update_site_id'))
			->from($db->quoteName('#__update_sites_extensions'))
			->where($db->quoteName('extension_id') . ' = ' . $extension);
		try {
			$db->setQuery($query);
			$update_site_ids = $db->loadColumn();
		}
		catch (RuntimeException $e) {
			$this->addLogEntry('unable to get update sites id: ' . $extension . ', ' . $e->getMessage(), Log::INFO);
			return false;
		}
		return $update_site_ids;
	}

    // show installation messages

    private function showInstallUpdateMessage($route) {
        $image = ($route == 'update') ? 'logo-banner-u.png' : 'logo-banner.png';
        $src = "https://www.vi-solutions.de/images/f/$this->release/$image";
        echo '<h2><img src="' . $src . '" alt="visforms" style="margin-right: 0.5rem;"/>' . (($route == 'update') ? Text::_('PKG_VFBASE_PACKAGE_UPDATE_STATE') : Text::_('PKG_VFBASE_PACKAGE_INSTALLATION_STATUS')) . '</h2>';
        if (in_array($this->release, $this->visformsBetaVersions)) {
            echo '<p><strong style="color: #761817;">' . Text::_('PKG_VFBASE_BETA_WARNING') . '</strong></p>';
        }
        if ($route == 'update') {
            echo $this->getSubUpdateRequiredMsg($route);
            if (version_compare($this->oldRelease, $this->last_modified_view_files_version, 'lt')) {
                echo '<p><strong style="color: #761817;">' . Text::_('PKG_VFBASE_DELETE_TEMPLATE_OVERRIDES') . '</strong></p>';
            }
        }
    }

    private function showUninstallMessage($release) {
        $src = "https://www.vi-solutions.de/images/f/$release/logo-banner-d.png"; ?>
        <h2><img src="<?php echo $src; ?>" alt="visforms" style="margin-right: 0.5rem;"/><?php echo Text::_('PKG_VFBASE_PACKAGE_REMOVAL_SUCESSFUL'); ?></h2><?php
    }

    private function getSubUpdateRequiredMsg ($route){
        $this->addLogEntry('checking if Subscription update is necessary', Log::INFO);
        $db = Factory::getDbo();
        $query = $db->getQuery(true);
        $query->select($db->qn('manifest_cache'))
            ->from($db->qn('#__extensions'))
            ->where($db->qn('element') . ' = ' . $db->q('pkg_vfsubscription'))
            ->where($db->qn('type') . ' = ' . $db->q('package'));
        try {
            $db->setQuery($query);
            $result = $db->loadResult();
            if (empty($result)) {
                return '';
            }
            $manifest = json_decode($result, true);
            $version = $manifest['version'];
            if (!empty($version) && version_compare($version, $this->vfsubminversion, 'lt')) {
                return '<p><strong style="color: #761817;">' . Text::sprintf('PKG_VFBASE_SUBSCRIPTION_UPDATE_REQUIRED', $this->vfsubminversion) . '</strong></p>';
            }
        }
        catch (Exception $e) {
            return '';
        }
        return '';
    }

	// translation files and directories

	private function copyCustomTranslationFiles() {
		// get installed languages for backend and frontend
		$list       = LanguageHelper::getInstalledLanguages();
		$frontend   = array();
		$backend    = array();
		foreach ($list as $k => $v) {
			foreach ($v as $name => $language) {
				if(is_array($language)) {
					// however, during testing the structure of one additional language nl-NL was doubled
					// like this: $language changed from 'single stdClass' to 'array with two entries'
					// after temporarily manually copied the folder ('nl_NL' --> '__nl_NL') and reinstalled the language in the Joomla Extension Manager
					// Array (
					//[0] => stdClass Object (
					//	[element] => nl-NL
					//	[name] => Dutch (nl-NL)
					//	[client_id] => 1
					//	[extension_id] => 253 )
					//[1] => stdClass Object (
					//	[element] => nl-NL
					//	[name] => Dutch (nl-NL)
					//	[client_id] => 1
					//	[extension_id] => 407)
					//)
					// we just use the first array entry:
					$language = $language[0];
				}
				if('de-DE' == $language->element || 'en-GB' == $language->element) {
					// ignore DE German and GB English
					continue;
				}
				// client_Id = 1 means admin access client_Id = 0 means frontend access
				if($language->client_id) {
					array_push($backend, $language->element);
				}
				else {
					array_push($frontend, $language->element);
				}
			}
		}

		// copy possible language files
		$this->copyCustomTranslationFiles_helper($backend, $this->languageFoldersBackend, '/administrator/language');
		$this->copyCustomTranslationFiles_helper($frontend, $this->languageFoldersFrontend, '/language');
	}

	private function copyCustomTranslationFiles_helper($languages, $folders, $destPathRoot) {
		// copy custom language files of installed languages like:
		// administrator/components/com_visforms/language/de-DE/de-DE.com_visforms.ini
		// administrator/components/com_visforms/language/de-DE/de-DE.com_visforms.sys.ini
		// administrator/components/com_visforms/language/de-DE/com_visforms.ini
		// administrator/components/com_visforms/language/de-DE/com_visforms.sys.ini
		foreach ($languages as $language) {
			// for each backend installed language
			$destPath = JPATH_ROOT . "$destPathRoot/$language";
			// create destination language folder if missing (mkdir gives warning if folder exists)
			if ( !Folder::exists($destPath)) {
				$created = Folder::create($destPath, 0777);
			}
			foreach ($folders as $folder => $name) {
				// for each possible visforms extension language folder
				for($i = 1; $i <= 2; $i++) {
					for($j = 1; $j <= 2; $j++) {
						// twice: with and without '.sys' in file name
						$sys      = (1 == $i ? '' : '.sys');
						// twice: with and without language shortcut decorated in file name
						$fileDest   = "$name$sys.ini";
						$fileSource = (1 == $j ? "$language.$name$sys.ini" : $fileDest);
						$from       = JPATH_ROOT . "$folder/$language/$fileSource";
						$to         = "$destPath/$fileDest";
						$this->copyFile($from, $to);
					}
				}
			}
		}
	}

	private function deleteTranslationFolders() {
		$this->deleteFolders(array_keys($this->languageFoldersBackend));
		$this->deleteFolders(array_keys($this->languageFoldersFrontend));
	}

	private function copyFile($from, $to) {
		// copy if file exists
		if (File::exists($from)) {
			try {
				if(File::copy($from, $to)) {
					$this->addLogEntry("file $from copied to: $to", Log::INFO);
				}
				else {
					$this->addLogEntry("file $from not copied to: $to", Log::INFO);
				}
			}
			catch (RuntimeException $e) {
				$this->addLogEntry('unable to copy ' . $from . ': ' . $e->getMessage(), Log::INFO);
			}
		}
	}

	private function deleteFiles($files = array()) {
		foreach ($files as $file) {
			$oldFile = Path::clean(JPATH_ROOT . $file);
			if (File::exists($oldFile)) {
				try {
					File::delete($oldFile);
					$this->addLogEntry($oldFile . ' successfully deleted', Log::INFO);
				}
				catch (RuntimeException $e) {
					$this->addLogEntry('deleting ' . $oldFile . ' failed: ' . $e->getMessage(), Log::INFO);
				}
			}
		}
	}

	private function deleteFolders($folders = array()) {
		foreach ($folders as $folder) {
			$oldFolder = Path::clean(JPATH_ROOT . $folder);
			if (Folder::exists($oldFolder)) {
				try {
					Folder::delete($oldFolder);
					$this->addLogEntry($oldFolder . ' successfully deleted', Log::INFO);
				}
				catch (RuntimeException $e) {
					$this->addLogEntry('deleting ' . $oldFolder . ' failed: ' . $e->getMessage(), Log::INFO);
				}
			}
		}
	}

	private function deleteOldFiles() {
		// list all files and folder which have to be removed on update!
		$files = array(
			'/administrator/components/com_visforms/css/visforms_min.css',
			'/administrator/components/com_visforms/images/icon-16-visforms.png',
			'/administrator/components/com_visforms/js/jquery-ui.js',
			'/administrator/components/com_visforms/js/jquery-ui.min.js',
			'/administrator/components/com_visforms/layouts/td/terminating_line.php',
			'/administrator/components/com_visforms/src/Controller/VisdebuggerController.php',
			'/components/com_visforms/captcha/images/audio_icon.gif',
			'/libraries/visolutions/tcpdf/encodings_maps.php',
			'/libraries/visolutions/tcpdf/htmlcolors.php',
			'/libraries/visolutions/tcpdf/pdf417.php',
			'/libraries/visolutions/tcpdf/spotcolors.php',
			'/libraries/visolutions/tcpdf/tcpdf_filters.php',
			'/libraries/visolutions/tcpdf/unicode_data.php',
			'/media/com_visforms/js/visforms.min.js',
			'/modules/mod_visforms/helper.php',
			'/language/de-DE/de-DE.pkg_vfbase.ini',
			'/language/de-DE/de-DE.pkg_vfbase.sys.ini',
			'/language/de-DE/de-DE.pkg_vfsubscription.ini',
			'/language/de-DE/de-DE.pkg_vfsubscription.sys.ini',
			'/language/de-DE/de-DE.files_vfsubsfiles.sys.ini',
		);
		$folders = array(
			'/administrator/components/com_visforms/helpers/html',
			'/administrator/components/com_visforms/controllers',
			'/administrator/components/com_visforms/Controller',
			'/administrator/components/com_visforms/Extension',
			'/administrator/components/com_visforms/Field',
			'/administrator/components/com_visforms/models',
			'/administrator/components/com_visforms/Model',
			'/administrator/components/com_visforms/Service',
			'/administrator/components/com_visforms/tables',
			'/administrator/components/com_visforms/Table',
			'/administrator/components/com_visforms/views',
			'/administrator/components/com_visforms/View',
			'/administrator/components/com_visforms/lib/placeholder',
			'/administrator/components/com_visforms/src/View/Visdebugger',
			'/administrator/components/com_visforms/tmpl/visdebugger',
			'/components/com_visforms/controllers',
			'/components/com_visforms/Controller',
			'/components/com_visforms/Field',
			'/components/com_visforms/helpers/route',
			'/components/com_visforms/models',
			'/components/com_visforms/Model',
			'/components/com_visforms/Service',
			'/components/com_visforms/views',
			'/components/com_visforms/View',
			'/modules/mod_visforms/helper',
			'/modules/mod_visforms/Helper',
		);
		$this->addLogEntry('*** trying to delete old files and folders ***', Log::INFO);
		$this->deleteFiles($files);
		$this->deleteFolders($folders);
	}

	protected function deleteFilesAndFolders($files, $folders) {
		foreach ($files as $file) {
			$oldFile = Path::clean(JPATH_ROOT . $file);
			if (File::exists($oldFile)) {
				try {
					File::delete($oldFile);
					$this->addLogEntry($oldFile . " deleted", Log::INFO);
				}
				catch (RuntimeException $e) {
					$this->addLogEntry('unable to delete ' . $oldFile . ': ' . $e->getMessage(), Log::INFO);
				}
			} else {
				$this->addLogEntry($oldFile . " does not exist.", Log::INFO);
			}

		}
		foreach ($folders as $folder) {
			$oldFolder = Path::clean(JPATH_ROOT . $folder);
			if (Folder::exists($oldFolder)) {
				try {
					Folder::delete($oldFolder);
					$this->addLogEntry($oldFolder . "deleted", Log::INFO);
				} catch (RuntimeException $e) {
					$this->addLogEntry('unable to delete ' . $oldFolder . ': ' . $e->getMessage(), Log::INFO);
				}
			} else {
				$this->addLogEntry($oldFolder . " does not exist.", Log::INFO);
			}

		}
	}

	// miscellaneous

	private function testWriteAccessDenied() {
		$accessDenied = false;
		$app = Factory::getApplication();
		$msg = '';
		$txtDenied   = 'tested write access to filesystem in install directory: denied';
		$txtPossible = 'tested write access to filesystem in install directory: possible';

		// get joomla temporary path setting
		// $config = JFactory::getConfig();
		// $path = $config->get('tmp_path');

		// get script file folder path setting
		$path = dirname(__FILE__);

		$name = "$path/edit.txt";

		try {
			// create the file or open and clear content
			$file = fopen($name, "w");
			if(false === $file) {
				$this->addLogEntry($txtDenied, Log::ERROR);
				$accessDenied = true;
				$msg = "filesystem test failed: $txtDenied";
			}
			else {
				fwrite($file, 'added new line');
				fclose($file);
			}
		}
		catch (Exception $e) {
			$accessDenied = true;
			$txtDenied .= ' ' . $e->getMessage();
			$msg = "filesystem exception: $txtDenied: " . $e->getMessage();
		}
		catch (Error $e) {
			$accessDenied = true;
			$txtDenied .= ' ' . $e->getMessage();
			$msg = "filesystem error: $txtDenied: " . $e->getMessage();
		}

		// in case of fopen function returned 'false'
		if($accessDenied) {
			$this->addLogEntry("$txtDenied", Log::ERROR);
			$app->enqueueMessage('The Visforms installation is unable to continue', 'WARNING');
			$app->enqueueMessage('Make sure the filesystem access rights and file ownership allow for write access', 'WARNING');
			// avoid the message entry when returning 'true': Extension Update: Custom install routine failure.
			// $app->enqueueMessage("Visforms installation: $msg", 'WARNING');
			throw new RuntimeException("Visforms installation: $msg");
		}
		else {
			$this->addLogEntry("$txtPossible", Log::INFO);
		}

		return $accessDenied;
	}

	private function deleteUpdateSites($packages) {
        $db = Factory::getDbo();
        // remove upload site information for all extensions from database
        foreach ($packages as $package) {
            $type = (string) $package->attributes()->type;
            $name = (string) $package->attributes()->id;
            $group = (!empty($package->attributes()->group)) ? (string) $package->attributes()->group : '';
            $id = $this->getExtensionId($type, $name, $group, 0);
            if (!empty($id)) {
                $update_site_ids = $this->getUpdateSites($id);
                if (!empty($update_site_ids)) {
                    $update_sites_ids_a = implode(',', $update_site_ids);
                    $query = $db->getQuery(true);
                    $query->delete($db->quoteName('#__update_sites'));
                    $query->where($db->quoteName('update_site_id') . ' IN (' . $update_sites_ids_a . ')');
                    try {
                        $db->setQuery($query);
                        $db->execute();
                    }
                    catch (RuntimeException $e) {
                        $this->addLogEntry("problems deleting record sets in #__update_sites : " . $e->getMessage(), Log::INFO);
                    }
                    $query = $db->getQuery(true);
                    $query->delete($db->quoteName('#__update_sites_extensions'));
                    $query->where($db->quoteName('extension_id') . ' = ' . $id);
                    try {
                        $db->setQuery($query);
                        $db->execute();
                    }
                    catch (RuntimeException $e) {
                        $this->addLogEntry("problems deleting record sets in #__update_sites_extensions : " . $e->getMessage(), Log::INFO);
                    }
                }
            }
        }
    }

	private function enableExtension($name, $type, $element, $folder = '') {
		$db = Factory::getDbo();
		$query = $db->getQuery(true);
		$query->update($db->quoteName('#__extensions'))
			->set($db->quoteName('enabled') . " = 1")
			->where($db->quoteName('name') . ' = ' . $db->quote($name))
			->where($db->quoteName('type') . ' = ' . $db->quote($type))
			->where($db->quoteName('element') . ' = ' . $db->quote($element));
		if (!empty($folder)) {
			$query->where($db->quoteName('folder') . ' = ' . $db->quote($folder));
		}
		try {
			$db->setQuery($query);
			$db->execute();
			$this->addLogEntry("extension successfully enabled", Log::INFO);
		}
		catch (RuntimeException $e) {
			$this->addLogEntry("unable to enable extension " . $e->getMessage(), Log::ERROR);
		}
	}

	// logging

	private function initializeLogger($adapter) {
		$this->loggerName               = 'visforms';
		$options['format']              = "{CODE}\t{MESSAGE}";
		$options['text_entry_format']   = "{PRIORITY}\t{MESSAGE}";
		$options['text_file']           = 'visforms_update.php';
		try {
			Log::addLogger($options, Log::ALL, array($this->loggerName, 'jerror'));
		}
		catch (RuntimeException $e) {}
	}

	private function addLogEntry($message, $code = Log::ERROR) {
		try {
			Log::add($message, $code, $this->loggerName);
		}
		catch (RuntimeException $exception)
		{
			// prevent installation routine from failing due to problems with logger
		}
	}

	public function errorHandler($no, $msg, $file, $line) {
		switch ($no) {
			case E_USER_ERROR:
			case E_RECOVERABLE_ERROR:
			case E_ERROR:
				$this->errorHandlerHelper($no, $msg, $file, $line, Log::ERROR);
				break;
			case E_USER_WARNING:
			case E_WARNING:
				$this->errorHandlerHelper($no, $msg, $file, $line, Log::WARNING);
				break;

			case E_DEPRECATED:
			case E_USER_DEPRECATED:
			case E_NOTICE:
			case E_USER_NOTICE:
			    break;

			default:
				$this->errorHandlerHelper($no, "error $no: $msg", $file, $line, Log::WARNING);
				break;
		}

		/* do execute PHP internal error handler */
		return false;
	}

	private function errorHandlerHelper($no, $msg, $file, $line, $level) {
		$this->addLogEntry("message: $msg", $level);
		$this->addLogEntry("   file: $file, line: $line", $level);
	}
}