<commit_before><?php
/**
 * HUBzero CMS
 *
 * Copyright 2009-2011 Purdue University. All rights reserved.
 *
 * This file is part of: The HUBzero(R) Platform for Scientific Collaboration
 *
 * The HUBzero(R) Platform for Scientific Collaboration (HUBzero) is free
 * software: you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * HUBzero is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * HUBzero is a registered trademark of Purdue University.
 *
 * @package   hubzero-cms
 * @author    <NAME> <<EMAIL>>
 * @copyright Copyright 2009-2011 Purdue University. All rights reserved.
 * @license   http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
 */

namespace Hubzero\Content;

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

/**
* HUBzero Database migrations class
* 
* @TODO: add flag to ignore development scripts?
*/
class Migration
{
	/**
	 * Document root in which to search for migration scripts
	 *
	 * @var string
	 **/
	private $docroot = '';

	/**
	 * Array holding paths to migration scripts
	 *
	 * @var array
	 **/
	private $files = array();

	/**
	 * Variable holding database object
	 *
	 * @var string
	 **/
	private static $db = null;

	/**
	 * Log messages themselves (stored as array to return to browser, or other client)
	 *
	 * @var array
	 **/
	private $log = array();

	/**
	 * Date of last migrations run - implicit by last log entry
	 *
	 * @var array
	 **/
	private $last_run = array('up'=>null, 'down'=>null);

	/**
	 * Constructor
	 *
	 * @param $docroot - default null, which should then resolve to hub docroot
	 * @return void
	 **/
	public function __construct($docroot=null)
	{
		// Try to determine the document root if none provided
		if (is_null($docroot))
		{
			$this->docroot = JPATH_ROOT;
		}
		else
		{
			$this->docroot = rtrim($docroot, '/');
		}

		// Setup the database connection
		if (!self::$db = $this->getDBO())
		{
			$this->log('Error: database connection failed.', 'error');
			return false;
		}

		// Try to figure out the date of the last file run
		try
		{
			self::$db->setQuery('SELECT `file` FROM `migrations` WHERE `direction` = \'up\' ORDER BY `file` DESC LIMIT 1');
			$rowup = self::$db->loadAssoc();

			self::$db->setQuery('SELECT `file` FROM `migrations` WHERE `direction` = \'down\' ORDER BY `file` DESC LIMIT 1');
			$rowdown = self::$db->loadAssoc();

			if (count($rowup) > 0)
			{
				$this->last_run['up'] = substr($rowup['file'], 9, 14);
			}
			if (count($rowdown) > 0)
			{
				$this->last_run['down'] = substr($rowdown['file'], 9, 14);
			}
		}
		catch (\PDOException $e)
		{
			$this->log('Error: failed to look up last migrations log entry.', 'error');
			return false;
		}
	}

	/**
	 * Getter for class private variables
	 *
	 * @param $var - var to retrieve
	 * @return class var
	 **/
	public function get($var)
	{
		if (property_exists($this, $var))
		{
			return $this->$var;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Setup database connect, test, return object
	 *
	 * @return database object
	 **/
	public function getDBO()
	{
		// Include the joomla configuration file
		if (file_exists($this->docroot . '/configuration.php'))
		{
			// If there's one in the provided doc root, use that
			require_once $this->docroot . '/configuration.php';
		}
		else
		{
			$this->log('Error: document root does not contain a configuration file', 'error');
			return false;
		}

		// Instantiate a config object
		$config = new \JConfig();

		$db = \JDatabase::getInstance(
			array(
				'driver'   => 'pdo',
				'host'     => $config->host,
				'user'     => $config->user,
				'password' => $config->password,
				'database' => $config->db,
				'prefix'   => 'jos_'
			)
		);

		// Test the connection
		if (!$db->connected())
		{
			$this->log('PDO connection failed', 'error');
			return false;
		}

		// Check for the existance of the migrations table
		try
		{
			$db->setQuery("SELECT `id` FROM `migrations` LIMIT 1");
			$db->query();
		}
		catch (\PDOException $e)
		{
			if ($this->createMigrationsTable($db) === false)
			{
				return false;
			}
		}

		return $db;
	}

	/**
	 * Find all migration scripts
	 *
	 * @param $extension - only look for migrations for this extension
	 * @param $file      - specific file to run
	 * @return array of file paths
	 **/
	public function find($extension=null, $file=null)
	{
		// Exclude certain thiings from our search
		$exclude = array(".", "..", "__migration_template.php");

		$files = array_diff(scandir($this->docroot . DS . 'migrations'), $exclude);
		$ext   = '';

		if (!is_null($file))
		{
			if (in_array($file, $files))
			{
				$this->files[] = $file;
				return true;
			}
			else
			{
				$this->log("Provided file ({$file}) could not be found.", 'error');
				return false;
			}
		}

		if (!is_null($extension))
		{
			$parts = explode('_', $extension);
			foreach ($parts as $part)
			{
				$ext .= ucfirst($part);
			}
		}

		foreach ($files as $file)
		{
			// Make sure they have a php extension and proper filename format
			if (preg_match('/^Migration[0-9]{14}[[:alnum:]]+\.php$/', $file))
			{
				// If an extension was provided...match against it...
				if (empty($ext) || (!empty($ext) && preg_match('/Migration[0-9]{14}'.$ext.'\.php/', $file)))
				{
					$this->files[] = $file;
				}
			}
		}

		return true;
	}

	/**
	 * Migrate up/down on all files gathered via 'find'
	 *
	 * @param $direction   - direction to migrate (up or down)
	 * @param $force       - run the update, even if the database says it's already been run
	 * @param $dryrun      - run the udpate, but only display what would be changed, wihthout actually doing anything
	 * @param $ignoreDates - run the update on all files found lacking entries in the db, not just those after the last run date
	 * @param $logOnly     - run the update, and mark as run, but don't actually run sql (usefully to mark changes that had already been made manually)
	 * @return bool - success
	 **/
	public function migrate($direction='up', $force=false, $dryrun=false, $ignoreDates=false, $logOnly=false)
	{
		// Make sure we have files
		if (empty($this->files))
		{
			$this->log("There were no migrations to run");
			return true;
		}

		// Notify if we're making a dry run
		if ($dryrun)
		{
			$this->log("Dry run: no changes will be made!");
		}

		// Notify if we're ignoring dates
		if ($ignoreDates)
		{
			$this->log("Ignore dates: all eligible files will be included!");
		}

		// Loop through files and run their '$direction' method
		foreach ($this->files as $file)
		{
			// Create a hash of the file (not using this at the moment)
			$hash = hash('md5', $file);

			// Don't compare dates if we're doing a full scan
			if (!$ignoreDates)
			{
				// Get date from file (would use last modified date, but git changes that)
				// Running up, down, and up again won't do anything, because the file is now older than the last run.
				// Currently, one should run $force or $ignoreDates and include an extension to override that behavior
				$date = substr($file, 9, 14);
				if (is_numeric($date))
				{
					if (is_numeric($this->last_run[$direction]) && $date <= $this->last_run[$direction] && !$force)
					{
						// This migration is older than the current, but let's see if we should inform that it should still be run
						self::$db->setQuery("SELECT `direction` FROM migrations WHERE `file` = " . self::$db->Quote($file) . " ORDER BY `date` DESC LIMIT 1");
						$row = self::$db->loadResult();

						// Check if last run was either not in the current direction we're going,
						// or if it hasn't been run at all and we're not going down
						if ($row != $direction || (!$row && $direction != 'down'))
						{
							$this->log("Migration {$direction}() in {$file} has not been run and should be (by using the -i option)", 'warning');
						}

						continue;
					}
				}
				else
				{
					// Filename did not contain a valid date
					$this->log("File did not contain a valid date ({$file}).", 'warning');
					continue;
				}
			}

			// Initialize ignore
			$ignore = false;

			// Check to see if this file has already been run
			try
			{
				// Look to the database log to see the last run on this file
				self::$db->setQuery("SELECT `direction` FROM migrations WHERE `file` = " . self::$db->Quote($file) . " ORDER BY `date` DESC LIMIT 1");
				$row = self::$db->loadResult();

				// If the last migration for this file doesn't exist, or, it was the opposite of $direction, we can go ahead and run it.
				// But, if the last run was the same direction as is currently being run, we shouldn't run it again
				// Also, don't run down first...it's should come after up
				if ($row == $direction || (!$row && $direction == 'down'))
				{
					$ignore = true;
				}
			}
			catch (\PDOException $e) 
			{
				$ignore = false;
			}

			// Ignore this file, if we've already run it (unless it's being forced)
			if ($ignore && !$force)
			{
				if ($dryrun)
				{
					$this->log("Would ignore {$direction}() {$file}");
					continue;
				}
				elseif (!$row)
				{
					$this->log("Ignoring {$direction}() - you should run up first ({$file})");
					continue;
				}
				else
				{
					$this->log("Ignoring {$direction}() {$file}");
					continue;
				}
			}

			// Get the file name
			$info = pathinfo($file);

			$fullpath = $this->docroot . DS . 'migrations' . DS . $file;

			// Include the file
			if (!is_file($fullpath))
			{
				$this->log("{$fullpath} is not a valid file", 'warning');
				continue;
			}
			else
			{
				require_once $fullpath;
			}

			// Set classname
			$class = $info['filename'];

			// Make sure file and classname match
			if (!class_exists($class))
			{
				$this->log("{$info['filename']} does not have a class of the same name", 'warning');
				continue;
			}

			// Check if we're making a dry run, or only logging changes
			if ($dryrun || $logOnly)
			{
				if ($dryrun)
				{
					$this->log("Would run {$direction}() {$file}", 'success');
				}
				elseif ($logOnly)
				{
					$this->recordMigration($file, $hash, $direction);
					$this->log("Marking as run: {$direction}() in {$file}", 'success');
				}
			}
			else
			{
				// Check and run 'pre' if necessary
				if (method_exists($class, 'pre'))
				{
					try
					{
						if ($class::pre(self::$db) === false)
						{
							$this->log("Running pre() returned false {$file}", 'warning');
							continue;
						}

						$this->log("Running pre() {$file}");
					}
					catch (\PDOException $e)
					{
						$this->log("Error: running pre() resulted in\n\n{$e}\n\nin {$file}", 'error');
						return false;
					}
				}

				// Try running the '$direction' SQL
				if(method_exists($class, $direction))
				{
					try
					{
						$result = $class::$direction(self::$db);

						if (is_object($result) && isset($result->error))
						{
							if (isset($result->error->type) && $result->error->type == 'fatal')
							{
								// Completely failed...stop immediately
								$message = (isset($result->error->message) && !empty($result->error->message)) ? $result->error->message : '[no message provided]';
								$this->log("Error: running {$direction}() resulted in a fatal error in {$file}: {$message}", 'error');
								return false;
							}
							else if (isset($result->error->type) && $result->error->type == 'warning')
							{
								// Just a warning...display message and carry on (my wayward son)
								$message = (isset($result->error->message) && !empty($result->error->message)) ? $result->error->message : '[no message provided]';
								$this->log("Warning: running {$direction}() resulted in a non-fatal error in {$file}: {$message}", 'warning');
								// Continue...i.e. don't log that this migration was run, so it shows up again on the next run
								continue;
							}
						}

						$this->recordMigration($file, $hash, $direction);
						$this->log("Running {$direction}() in {$file}", 'success');
					}
					catch (\PDOException $e)
					{
						$this->log("Error: running {$direction}() resulted in\n\n{$e}\n\nin {$file}", 'error');
						return false;
					}
				}

				// Check and run 'post'
				if (method_exists($class, 'post'))
				{
					try
					{
						if ($class::post(self::$db) === false)
						{
							$this->log("Running post() returned false {$file}", 'warning');
							continue;
						}

						$this->log("Running post() {$file}");
					}
					catch (\PDOException $e)
					{
						$this->log("Error: running post() resulted in\n\n{$e}\n\nin {$file}", 'error');
						return false;
					}
				}
			}
		}

		return true;
	}

	/**
	 * Record migration in migrations table
	 *
	 * @param $file - path to file being recorded
	 * @param $hash - hash of file
	 * @param $direction - up or down
	 * @return void
	 **/
	protected function recordMigration($file, $hash, $direction)
	{
		// Try inserting a migration record into the database
		try
		{
			$date = new \JDate();

			// Craete our object to insert
			$obj = (object) array(
					'file'      => $file,
					'hash'      => $hash,
					'direction' => $direction,
					'date'      => $date->toSql(),
					'action_by' => (php_sapi_name() == 'cli') ? exec("whoami") : \JFactory::getUser()->get('id')
				);

			self::$db->insertObject('migrations', $obj);
		}
		catch (\PDOException $e)
		{
			$this->log("Failed inserting migration record: {$e}", 'error');
			return false;
		}
	}

	/**
	 * Logging mechanism
	 *
	 * @param $message - message to log
	 * @param $type    - message type, can be one predefined values from output class (not specified will default to 'normal' text)
	 * @return log messages
	 **/
	public function log($message, $type=null)
	{
		$this->log[] = array('message' => $message, 'type' => $type);
	}

	/**
	 * Attempt to create needed migrations table
	 *
	 * @return bool
	 **/
	private function createMigrationsTable(&$db)
	{
		$this->log('Migrations table did not exist...attempting to create it now');

		$query = "CREATE TABLE `migrations` (
					`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
					`file` varchar(255) NOT NULL DEFAULT '',
					`hash` char(32) NOT NULL DEFAULT '',
					`direction` varchar(10) NOT NULL DEFAULT '',
					`date` datetime NOT NULL,
					`action_by` varchar(255) NOT NULL DEFAULT '',
					PRIMARY KEY (`id`)
				) ENGINE=InnoDB DEFAULT CHARSET=utf8;";

		// Try creating the migrations table now
		try
		{
			$db->setQuery($query);
			$db->query();
			$this->log('Migrations table successfully created');
			return true;
		}
		catch (\PDOException $e)
		{
			$this->log('Unable to create needed migrations table', 'error');
			return false;
		}
	}

	/**
	 * Add, as needed, the component to the appropriate table, depending on the Joomla version
	 *
	 * @param $name           - (string) component name
	 * @param $option         - (string) com_xyz
	 * @param $enabled        - (int)    whether or not the component should be enabled
	 * @param $params         - (string) component params (if already known)
	 * @param $createMenuItem - (bool)   create an admin menu item for this component
	 * @return bool
	 **/
	public static function addComponentEntry($name, $option=NULL, $enabled=1, $params='', $createMenuItem=true)
	{
		$db = self::$db;

		if ($db->tableExists('#__components'))
		{
			// First, make sure it isn't already there
			$query = "SELECT `id` FROM `#__components` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			if (is_null($option))
			{
				$option = 'com_' . strtolower($name);
			}

			$ordering = 0;

			if (!empty($params) && is_array($params))
			{
				$p = '';
				foreach ($params as $k => $v)
				{
					$p .= "{$k}={$v}\n";
				}

				$params = $p;
			}

			$query = "INSERT INTO `#__components` (`name`, `link`, `menuid`, `parent`, `admin_menu_link`, `admin_menu_alt`, `option`, `ordering`, `admin_menu_img`, `iscore`, `params`, `enabled`)";
			$query .= " VALUES ('{$name}', 'option={$option}', 0, 0, 'option={$option}', '{$name}', '{$option}', {$ordering}, '', 0, ".$db->quote($params).", {$enabled})";
			$db->setQuery($query);
			$db->query();
		}
		else
		{
			if (is_null($option))
			{
				$option = 'com_' . strtolower($name);
			}
			$name = $option;

			// First, make sure it isn't already there
			$query = "SELECT `extension_id` FROM `#__extensions` WHERE `name` = " . $db->quote($option);
			$db->setQuery($query);
			if ($db->loadResult())
			{
				$component_id = $db->loadResult();
			}
			else
			{
				$ordering = 0;

				if (!empty($params) && is_array($params))
				{
					$params = json_encode($params);
				}

				$query = "INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`)";
				$query .= " VALUES ('{$name}', 'component', '{$option}', '', 1, {$enabled}, 1, 0, '', ".$db->quote($params).", '', '', 0, '0000-00-00 00:00:00', {$ordering}, 0)";
				$db->setQuery($query);
				$db->query();
				$component_id = $db->insertId();
			}

			// Secondly, add asset entry if not yet created
			$query = "SELECT `id` FROM `#__assets` WHERE `name` = " . $db->quote($option);
			$db->setQuery($query);
			if (!$db->loadResult())
			{
				// Build default ruleset
				$defaulRules = array(
					"core.admin"      => array(
						"7" => 1
						),
					"core.manage"     => array(
						"6" => 1
						),
					"core.create"     => array(),
					"core.delete"     => array(),
					"core.edit"       => array(),
					"core.edit.state" => array()
					);

				// Register the component container just under root in the assets table
				$asset = JTable::getInstance('Asset');
				$asset->name = $option;
				$asset->parent_id = 1;
				$asset->rules = json_encode($defaulRules);
				$asset->title = $option;
				$asset->setLocation(1, 'last-child');
				$asset->store();
			}

			if ($createMenuItem)
			{
				// Check for an admin menu entry...if it's not there, create it
				$query = "SELECT `id` FROM `#__menu` WHERE `menutype` = 'main' AND `title` = " . $db->quote($option);
				$db->setQuery($query);
				if ($db->loadResult())
				{
					return true;
				}

				$alias = substr($option, 4);

				$query = "INSERT INTO `#__menu` (`menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `ordering`, `checked_out`, `checked_out_time`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`)";
				$query .= " VALUES ('main', '{$option}', '{$alias}', '', '{$alias}', 'index.php?option={$option}', 'component', {$enabled}, 1, 1, {$component_id}, 0, 0, '0000-00-00 00:00:00', 0, 0, '', 0, '', 0, 0, 0, '*', 1)";
				$db->setQuery($query);
				$db->query();

				// If we have the nested set class available, use it to rebuild lft/rgt
				if (class_exists('JTableNested') && method_exists('JTableNested', 'rebuild'))
				{
					// Use the MySQL driver for this
					$config = \JFactory::getConfig();
					$database = \JDatabase::getInstance(
						array(
							'driver'   => 'mysql',
							'host'     => $config->getValue('host'),
							'user'     => $config->getValue('user'),
							'password' => $<PASSWORD>'),
							'database' => $config->getValue('db')
						) 
					);

					$table = new \JTableMenu($database);
					$table->rebuild();
				}
			}
		}
	}

	/**
	 * Add, as needed, the plugin entry to the appropriate table, depending on the Joomla version
	 *
	 * @param $folder  - (string) plugin folder
	 * @param $element - (string) plugin element
	 * @param $enabled - (int)    whether or not the plugin should be enabled
	 * @param $params  - (array)  plugin params (if already known)
	 * @return bool
	 **/
	public static function addPluginEntry($folder, $element, $enabled=1, $params='')
	{
		$db = self::$db;

		if ($db->tableExists('#__plugins'))
		{
			$folder  = strtolower($folder);
			$element = strtolower($element);
			$name    = ucfirst($folder) . ' - ' . ucfirst($element);

			// First, make sure it isn't already there
			$query = "SELECT `id` FROM `#__plugins` WHERE `folder` = '{$folder}' AND `element` = '{$element}'";
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			// Get ordering
			$query = "SELECT MAX(ordering) FROM `#__plugins` WHERE `folder` = " . $db->quote($folder);
			$db->setQuery($query);
			$ordering = (is_numeric($db->loadResult())) ? $db->loadResult()+1 : 1;

			if (!empty($params) && is_array($params))
			{
				$p = '';
				foreach ($params as $k => $v)
				{
					$p .= "{$k}={$v}\n";
				}

				$params = $p;
			}

			$query = "INSERT INTO `#__plugins` (`name`, `element`, `folder`, `access`, `ordering`, `published`, `iscore`, `client_id`, `checked_out`, `checked_out_time`, `params`)";
			$query .= " VALUES ('{$name}', '{$element}', '{$folder}', 0, {$ordering}, {$enabled}, 0, 0, 0, '0000-00-00 00:00:00', ".$db->quote($params).")";
			$db->setQuery($query);
			$db->query();
		}
		else
		{
			$folder  = strtolower($folder);
			$element = strtolower($element);
			$name    = 'plg_' . $folder . '_' . $element;

			// First, make sure it isn't already there
			$query = "SELECT `extension_id` FROM `#__extensions` WHERE `folder` = '{$folder}' AND `element` = '{$element}'";
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			// Get ordering
			$query = "SELECT MAX(ordering) FROM `#__extensions` WHERE `folder` = " . $db->quote($folder);
			$db->setQuery($query);
			$ordering = (is_numeric($db->loadResult())) ? $db->loadResult()+1 : 1;

			if (!empty($params) && is_array($params))
			{
				$params = json_encode($params);
			}

			$query = "INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`)";
			$query .= " VALUES ('{$name}', 'plugin', '{$element}', '{$folder}', 0, {$enabled}, 1, 0, '', ".$db->quote($params).", '', '', 0, '0000-00-00 00:00:00', {$ordering}, 0)";
			$db->setQuery($query);
			$db->query();
		}
	}

	/**
	 * Add, as needed, the module entry to the appropriate table, depending on the Joomla version
	 *
	 * @param $element - (string) plugin element
	 * @param $enabled - (int)    whether or not the plugin should be enabled
	 * @param $params  - (array)  plugin params (if already known)
	 * @return bool
	 **/
	public static function addModuleEntry($element, $enabled=1, $params='')
	{
		$db = self::$db;

		if ($db->tableExists('#__extensions'))
		{
			$name = $element;

			// First, make sure it isn't already there
			$query = "SELECT `extension_id` FROM `#__extensions` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			$ordering = 0;

			if (!empty($params) && is_array($params))
			{
				$params = json_encode($params);
			}

			$query = "INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`)";
			$query .= " VALUES ('{$name}', 'module', '{$element}', '', 0, {$enabled}, 1, 0, '', ".$db->quote($params).", '', '', 0, '0000-00-00 00:00:00', {$ordering}, 0)";
			$db->setQuery($query);
			$db->query();
		}
	}

	/**
	 * Remove component entries from the appropriate table, depending on the Joomla version
	 *
	 * @param $name - (string) component name
	 * @return bool
	 **/
	public static function deleteComponentEntry($name)
	{
		$db = self::$db;

		if ($db->tableExists('#__components'))
		{
			// Delete component entry
			$query = "DELETE FROM `#__components` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			$db->query();
		}
		else
		{
			$name = 'com_' . strtolower($name);
			// Delete component entry
			$query = "DELETE FROM `#__extensions` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			$db->query();

			// Remove the component container in the assets table
			$asset = JTable::getInstance('Asset');
			if ($asset->loadByName($name))
			{
				$asset->delete();
			}

			// Check for an admin menu entry...if it's not there, create it<commit_msg>[LIB_HUBZERO] fix another namespacing issue in migration class<commit_after><?php
/**
 * HUBzero CMS
 *
 * Copyright 2009-2011 Purdue University. All rights reserved.
 *
 * This file is part of: The HUBzero(R) Platform for Scientific Collaboration
 *
 * The HUBzero(R) Platform for Scientific Collaboration (HUBzero) is free
 * software: you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * HUBzero is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * HUBzero is a registered trademark of Purdue University.
 *
 * @package   hubzero-cms
 * @author    <NAME> <<EMAIL>>
 * @copyright Copyright 2009-2011 Purdue University. All rights reserved.
 * @license   http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
 */

namespace Hubzero\Content;

// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

/**
* HUBzero Database migrations class
* 
* @TODO: add flag to ignore development scripts?
*/
class Migration
{
	/**
	 * Document root in which to search for migration scripts
	 *
	 * @var string
	 **/
	private $docroot = '';

	/**
	 * Array holding paths to migration scripts
	 *
	 * @var array
	 **/
	private $files = array();

	/**
	 * Variable holding database object
	 *
	 * @var string
	 **/
	private static $db = null;

	/**
	 * Log messages themselves (stored as array to return to browser, or other client)
	 *
	 * @var array
	 **/
	private $log = array();

	/**
	 * Date of last migrations run - implicit by last log entry
	 *
	 * @var array
	 **/
	private $last_run = array('up'=>null, 'down'=>null);

	/**
	 * Constructor
	 *
	 * @param $docroot - default null, which should then resolve to hub docroot
	 * @return void
	 **/
	public function __construct($docroot=null)
	{
		// Try to determine the document root if none provided
		if (is_null($docroot))
		{
			$this->docroot = JPATH_ROOT;
		}
		else
		{
			$this->docroot = rtrim($docroot, '/');
		}

		// Setup the database connection
		if (!self::$db = $this->getDBO())
		{
			$this->log('Error: database connection failed.', 'error');
			return false;
		}

		// Try to figure out the date of the last file run
		try
		{
			self::$db->setQuery('SELECT `file` FROM `migrations` WHERE `direction` = \'up\' ORDER BY `file` DESC LIMIT 1');
			$rowup = self::$db->loadAssoc();

			self::$db->setQuery('SELECT `file` FROM `migrations` WHERE `direction` = \'down\' ORDER BY `file` DESC LIMIT 1');
			$rowdown = self::$db->loadAssoc();

			if (count($rowup) > 0)
			{
				$this->last_run['up'] = substr($rowup['file'], 9, 14);
			}
			if (count($rowdown) > 0)
			{
				$this->last_run['down'] = substr($rowdown['file'], 9, 14);
			}
		}
		catch (\PDOException $e)
		{
			$this->log('Error: failed to look up last migrations log entry.', 'error');
			return false;
		}
	}

	/**
	 * Getter for class private variables
	 *
	 * @param $var - var to retrieve
	 * @return class var
	 **/
	public function get($var)
	{
		if (property_exists($this, $var))
		{
			return $this->$var;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Setup database connect, test, return object
	 *
	 * @return database object
	 **/
	public function getDBO()
	{
		// Include the joomla configuration file
		if (file_exists($this->docroot . '/configuration.php'))
		{
			// If there's one in the provided doc root, use that
			require_once $this->docroot . '/configuration.php';
		}
		else
		{
			$this->log('Error: document root does not contain a configuration file', 'error');
			return false;
		}

		// Instantiate a config object
		$config = new \JConfig();

		$db = \JDatabase::getInstance(
			array(
				'driver'   => 'pdo',
				'host'     => $config->host,
				'user'     => $config->user,
				'password' => $<PASSWORD>,
				'database' => $config->db,
				'prefix'   => 'jos_'
			)
		);

		// Test the connection
		if (!$db->connected())
		{
			$this->log('PDO connection failed', 'error');
			return false;
		}

		// Check for the existance of the migrations table
		try
		{
			$db->setQuery("SELECT `id` FROM `migrations` LIMIT 1");
			$db->query();
		}
		catch (\PDOException $e)
		{
			if ($this->createMigrationsTable($db) === false)
			{
				return false;
			}
		}

		return $db;
	}

	/**
	 * Find all migration scripts
	 *
	 * @param $extension - only look for migrations for this extension
	 * @param $file      - specific file to run
	 * @return array of file paths
	 **/
	public function find($extension=null, $file=null)
	{
		// Exclude certain thiings from our search
		$exclude = array(".", "..", "__migration_template.php");

		$files = array_diff(scandir($this->docroot . DS . 'migrations'), $exclude);
		$ext   = '';

		if (!is_null($file))
		{
			if (in_array($file, $files))
			{
				$this->files[] = $file;
				return true;
			}
			else
			{
				$this->log("Provided file ({$file}) could not be found.", 'error');
				return false;
			}
		}

		if (!is_null($extension))
		{
			$parts = explode('_', $extension);
			foreach ($parts as $part)
			{
				$ext .= ucfirst($part);
			}
		}

		foreach ($files as $file)
		{
			// Make sure they have a php extension and proper filename format
			if (preg_match('/^Migration[0-9]{14}[[:alnum:]]+\.php$/', $file))
			{
				// If an extension was provided...match against it...
				if (empty($ext) || (!empty($ext) && preg_match('/Migration[0-9]{14}'.$ext.'\.php/', $file)))
				{
					$this->files[] = $file;
				}
			}
		}

		return true;
	}

	/**
	 * Migrate up/down on all files gathered via 'find'
	 *
	 * @param $direction   - direction to migrate (up or down)
	 * @param $force       - run the update, even if the database says it's already been run
	 * @param $dryrun      - run the udpate, but only display what would be changed, wihthout actually doing anything
	 * @param $ignoreDates - run the update on all files found lacking entries in the db, not just those after the last run date
	 * @param $logOnly     - run the update, and mark as run, but don't actually run sql (usefully to mark changes that had already been made manually)
	 * @return bool - success
	 **/
	public function migrate($direction='up', $force=false, $dryrun=false, $ignoreDates=false, $logOnly=false)
	{
		// Make sure we have files
		if (empty($this->files))
		{
			$this->log("There were no migrations to run");
			return true;
		}

		// Notify if we're making a dry run
		if ($dryrun)
		{
			$this->log("Dry run: no changes will be made!");
		}

		// Notify if we're ignoring dates
		if ($ignoreDates)
		{
			$this->log("Ignore dates: all eligible files will be included!");
		}

		// Loop through files and run their '$direction' method
		foreach ($this->files as $file)
		{
			// Create a hash of the file (not using this at the moment)
			$hash = hash('md5', $file);

			// Don't compare dates if we're doing a full scan
			if (!$ignoreDates)
			{
				// Get date from file (would use last modified date, but git changes that)
				// Running up, down, and up again won't do anything, because the file is now older than the last run.
				// Currently, one should run $force or $ignoreDates and include an extension to override that behavior
				$date = substr($file, 9, 14);
				if (is_numeric($date))
				{
					if (is_numeric($this->last_run[$direction]) && $date <= $this->last_run[$direction] && !$force)
					{
						// This migration is older than the current, but let's see if we should inform that it should still be run
						self::$db->setQuery("SELECT `direction` FROM migrations WHERE `file` = " . self::$db->Quote($file) . " ORDER BY `date` DESC LIMIT 1");
						$row = self::$db->loadResult();

						// Check if last run was either not in the current direction we're going,
						// or if it hasn't been run at all and we're not going down
						if ($row != $direction || (!$row && $direction != 'down'))
						{
							$this->log("Migration {$direction}() in {$file} has not been run and should be (by using the -i option)", 'warning');
						}

						continue;
					}
				}
				else
				{
					// Filename did not contain a valid date
					$this->log("File did not contain a valid date ({$file}).", 'warning');
					continue;
				}
			}

			// Initialize ignore
			$ignore = false;

			// Check to see if this file has already been run
			try
			{
				// Look to the database log to see the last run on this file
				self::$db->setQuery("SELECT `direction` FROM migrations WHERE `file` = " . self::$db->Quote($file) . " ORDER BY `date` DESC LIMIT 1");
				$row = self::$db->loadResult();

				// If the last migration for this file doesn't exist, or, it was the opposite of $direction, we can go ahead and run it.
				// But, if the last run was the same direction as is currently being run, we shouldn't run it again
				// Also, don't run down first...it's should come after up
				if ($row == $direction || (!$row && $direction == 'down'))
				{
					$ignore = true;
				}
			}
			catch (\PDOException $e) 
			{
				$ignore = false;
			}

			// Ignore this file, if we've already run it (unless it's being forced)
			if ($ignore && !$force)
			{
				if ($dryrun)
				{
					$this->log("Would ignore {$direction}() {$file}");
					continue;
				}
				elseif (!$row)
				{
					$this->log("Ignoring {$direction}() - you should run up first ({$file})");
					continue;
				}
				else
				{
					$this->log("Ignoring {$direction}() {$file}");
					continue;
				}
			}

			// Get the file name
			$info = pathinfo($file);

			$fullpath = $this->docroot . DS . 'migrations' . DS . $file;

			// Include the file
			if (!is_file($fullpath))
			{
				$this->log("{$fullpath} is not a valid file", 'warning');
				continue;
			}
			else
			{
				require_once $fullpath;
			}

			// Set classname
			$class = $info['filename'];

			// Make sure file and classname match
			if (!class_exists($class))
			{
				$this->log("{$info['filename']} does not have a class of the same name", 'warning');
				continue;
			}

			// Check if we're making a dry run, or only logging changes
			if ($dryrun || $logOnly)
			{
				if ($dryrun)
				{
					$this->log("Would run {$direction}() {$file}", 'success');
				}
				elseif ($logOnly)
				{
					$this->recordMigration($file, $hash, $direction);
					$this->log("Marking as run: {$direction}() in {$file}", 'success');
				}
			}
			else
			{
				// Check and run 'pre' if necessary
				if (method_exists($class, 'pre'))
				{
					try
					{
						if ($class::pre(self::$db) === false)
						{
							$this->log("Running pre() returned false {$file}", 'warning');
							continue;
						}

						$this->log("Running pre() {$file}");
					}
					catch (\PDOException $e)
					{
						$this->log("Error: running pre() resulted in\n\n{$e}\n\nin {$file}", 'error');
						return false;
					}
				}

				// Try running the '$direction' SQL
				if(method_exists($class, $direction))
				{
					try
					{
						$result = $class::$direction(self::$db);

						if (is_object($result) && isset($result->error))
						{
							if (isset($result->error->type) && $result->error->type == 'fatal')
							{
								// Completely failed...stop immediately
								$message = (isset($result->error->message) && !empty($result->error->message)) ? $result->error->message : '[no message provided]';
								$this->log("Error: running {$direction}() resulted in a fatal error in {$file}: {$message}", 'error');
								return false;
							}
							else if (isset($result->error->type) && $result->error->type == 'warning')
							{
								// Just a warning...display message and carry on (my wayward son)
								$message = (isset($result->error->message) && !empty($result->error->message)) ? $result->error->message : '[no message provided]';
								$this->log("Warning: running {$direction}() resulted in a non-fatal error in {$file}: {$message}", 'warning');
								// Continue...i.e. don't log that this migration was run, so it shows up again on the next run
								continue;
							}
						}

						$this->recordMigration($file, $hash, $direction);
						$this->log("Running {$direction}() in {$file}", 'success');
					}
					catch (\PDOException $e)
					{
						$this->log("Error: running {$direction}() resulted in\n\n{$e}\n\nin {$file}", 'error');
						return false;
					}
				}

				// Check and run 'post'
				if (method_exists($class, 'post'))
				{
					try
					{
						if ($class::post(self::$db) === false)
						{
							$this->log("Running post() returned false {$file}", 'warning');
							continue;
						}

						$this->log("Running post() {$file}");
					}
					catch (\PDOException $e)
					{
						$this->log("Error: running post() resulted in\n\n{$e}\n\nin {$file}", 'error');
						return false;
					}
				}
			}
		}

		return true;
	}

	/**
	 * Record migration in migrations table
	 *
	 * @param $file - path to file being recorded
	 * @param $hash - hash of file
	 * @param $direction - up or down
	 * @return void
	 **/
	protected function recordMigration($file, $hash, $direction)
	{
		// Try inserting a migration record into the database
		try
		{
			$date = new \JDate();

			// Craete our object to insert
			$obj = (object) array(
					'file'      => $file,
					'hash'      => $hash,
					'direction' => $direction,
					'date'      => $date->toSql(),
					'action_by' => (php_sapi_name() == 'cli') ? exec("whoami") : \JFactory::getUser()->get('id')
				);

			self::$db->insertObject('migrations', $obj);
		}
		catch (\PDOException $e)
		{
			$this->log("Failed inserting migration record: {$e}", 'error');
			return false;
		}
	}

	/**
	 * Logging mechanism
	 *
	 * @param $message - message to log
	 * @param $type    - message type, can be one predefined values from output class (not specified will default to 'normal' text)
	 * @return log messages
	 **/
	public function log($message, $type=null)
	{
		$this->log[] = array('message' => $message, 'type' => $type);
	}

	/**
	 * Attempt to create needed migrations table
	 *
	 * @return bool
	 **/
	private function createMigrationsTable(&$db)
	{
		$this->log('Migrations table did not exist...attempting to create it now');

		$query = "CREATE TABLE `migrations` (
					`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
					`file` varchar(255) NOT NULL DEFAULT '',
					`hash` char(32) NOT NULL DEFAULT '',
					`direction` varchar(10) NOT NULL DEFAULT '',
					`date` datetime NOT NULL,
					`action_by` varchar(255) NOT NULL DEFAULT '',
					PRIMARY KEY (`id`)
				) ENGINE=InnoDB DEFAULT CHARSET=utf8;";

		// Try creating the migrations table now
		try
		{
			$db->setQuery($query);
			$db->query();
			$this->log('Migrations table successfully created');
			return true;
		}
		catch (\PDOException $e)
		{
			$this->log('Unable to create needed migrations table', 'error');
			return false;
		}
	}

	/**
	 * Add, as needed, the component to the appropriate table, depending on the Joomla version
	 *
	 * @param $name           - (string) component name
	 * @param $option         - (string) com_xyz
	 * @param $enabled        - (int)    whether or not the component should be enabled
	 * @param $params         - (string) component params (if already known)
	 * @param $createMenuItem - (bool)   create an admin menu item for this component
	 * @return bool
	 **/
	public static function addComponentEntry($name, $option=NULL, $enabled=1, $params='', $createMenuItem=true)
	{
		$db = self::$db;

		if ($db->tableExists('#__components'))
		{
			// First, make sure it isn't already there
			$query = "SELECT `id` FROM `#__components` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			if (is_null($option))
			{
				$option = 'com_' . strtolower($name);
			}

			$ordering = 0;

			if (!empty($params) && is_array($params))
			{
				$p = '';
				foreach ($params as $k => $v)
				{
					$p .= "{$k}={$v}\n";
				}

				$params = $p;
			}

			$query = "INSERT INTO `#__components` (`name`, `link`, `menuid`, `parent`, `admin_menu_link`, `admin_menu_alt`, `option`, `ordering`, `admin_menu_img`, `iscore`, `params`, `enabled`)";
			$query .= " VALUES ('{$name}', 'option={$option}', 0, 0, 'option={$option}', '{$name}', '{$option}', {$ordering}, '', 0, ".$db->quote($params).", {$enabled})";
			$db->setQuery($query);
			$db->query();
		}
		else
		{
			if (is_null($option))
			{
				$option = 'com_' . strtolower($name);
			}
			$name = $option;

			// First, make sure it isn't already there
			$query = "SELECT `extension_id` FROM `#__extensions` WHERE `name` = " . $db->quote($option);
			$db->setQuery($query);
			if ($db->loadResult())
			{
				$component_id = $db->loadResult();
			}
			else
			{
				$ordering = 0;

				if (!empty($params) && is_array($params))
				{
					$params = json_encode($params);
				}

				$query = "INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`)";
				$query .= " VALUES ('{$name}', 'component', '{$option}', '', 1, {$enabled}, 1, 0, '', ".$db->quote($params).", '', '', 0, '0000-00-00 00:00:00', {$ordering}, 0)";
				$db->setQuery($query);
				$db->query();
				$component_id = $db->insertId();
			}

			// Secondly, add asset entry if not yet created
			$query = "SELECT `id` FROM `#__assets` WHERE `name` = " . $db->quote($option);
			$db->setQuery($query);
			if (!$db->loadResult())
			{
				// Build default ruleset
				$defaulRules = array(
					"core.admin"      => array(
						"7" => 1
						),
					"core.manage"     => array(
						"6" => 1
						),
					"core.create"     => array(),
					"core.delete"     => array(),
					"core.edit"       => array(),
					"core.edit.state" => array()
					);

				// Register the component container just under root in the assets table
				$asset = \JTable::getInstance('Asset');
				$asset->name = $option;
				$asset->parent_id = 1;
				$asset->rules = json_encode($defaulRules);
				$asset->title = $option;
				$asset->setLocation(1, 'last-child');
				$asset->store();
			}

			if ($createMenuItem)
			{
				// Check for an admin menu entry...if it's not there, create it
				$query = "SELECT `id` FROM `#__menu` WHERE `menutype` = 'main' AND `title` = " . $db->quote($option);
				$db->setQuery($query);
				if ($db->loadResult())
				{
					return true;
				}

				$alias = substr($option, 4);

				$query = "INSERT INTO `#__menu` (`menutype`, `title`, `alias`, `note`, `path`, `link`, `type`, `published`, `parent_id`, `level`, `component_id`, `ordering`, `checked_out`, `checked_out_time`, `browserNav`, `access`, `img`, `template_style_id`, `params`, `lft`, `rgt`, `home`, `language`, `client_id`)";
				$query .= " VALUES ('main', '{$option}', '{$alias}', '', '{$alias}', 'index.php?option={$option}', 'component', {$enabled}, 1, 1, {$component_id}, 0, 0, '0000-00-00 00:00:00', 0, 0, '', 0, '', 0, 0, 0, '*', 1)";
				$db->setQuery($query);
				$db->query();

				// If we have the nested set class available, use it to rebuild lft/rgt
				if (class_exists('JTableNested') && method_exists('JTableNested', 'rebuild'))
				{
					// Use the MySQL driver for this
					$config = \JFactory::getConfig();
					$database = \JDatabase::getInstance(
						array(
							'driver'   => 'mysql',
							'host'     => $config->getValue('host'),
							'user'     => $config->getValue('user'),
							'password' => $config-><PASSWORD>('password'),
							'database' => $config->getValue('db')
						) 
					);

					$table = new \JTableMenu($database);
					$table->rebuild();
				}
			}
		}
	}

	/**
	 * Add, as needed, the plugin entry to the appropriate table, depending on the Joomla version
	 *
	 * @param $folder  - (string) plugin folder
	 * @param $element - (string) plugin element
	 * @param $enabled - (int)    whether or not the plugin should be enabled
	 * @param $params  - (array)  plugin params (if already known)
	 * @return bool
	 **/
	public static function addPluginEntry($folder, $element, $enabled=1, $params='')
	{
		$db = self::$db;

		if ($db->tableExists('#__plugins'))
		{
			$folder  = strtolower($folder);
			$element = strtolower($element);
			$name    = ucfirst($folder) . ' - ' . ucfirst($element);

			// First, make sure it isn't already there
			$query = "SELECT `id` FROM `#__plugins` WHERE `folder` = '{$folder}' AND `element` = '{$element}'";
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			// Get ordering
			$query = "SELECT MAX(ordering) FROM `#__plugins` WHERE `folder` = " . $db->quote($folder);
			$db->setQuery($query);
			$ordering = (is_numeric($db->loadResult())) ? $db->loadResult()+1 : 1;

			if (!empty($params) && is_array($params))
			{
				$p = '';
				foreach ($params as $k => $v)
				{
					$p .= "{$k}={$v}\n";
				}

				$params = $p;
			}

			$query = "INSERT INTO `#__plugins` (`name`, `element`, `folder`, `access`, `ordering`, `published`, `iscore`, `client_id`, `checked_out`, `checked_out_time`, `params`)";
			$query .= " VALUES ('{$name}', '{$element}', '{$folder}', 0, {$ordering}, {$enabled}, 0, 0, 0, '0000-00-00 00:00:00', ".$db->quote($params).")";
			$db->setQuery($query);
			$db->query();
		}
		else
		{
			$folder  = strtolower($folder);
			$element = strtolower($element);
			$name    = 'plg_' . $folder . '_' . $element;

			// First, make sure it isn't already there
			$query = "SELECT `extension_id` FROM `#__extensions` WHERE `folder` = '{$folder}' AND `element` = '{$element}'";
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			// Get ordering
			$query = "SELECT MAX(ordering) FROM `#__extensions` WHERE `folder` = " . $db->quote($folder);
			$db->setQuery($query);
			$ordering = (is_numeric($db->loadResult())) ? $db->loadResult()+1 : 1;

			if (!empty($params) && is_array($params))
			{
				$params = json_encode($params);
			}

			$query = "INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`)";
			$query .= " VALUES ('{$name}', 'plugin', '{$element}', '{$folder}', 0, {$enabled}, 1, 0, '', ".$db->quote($params).", '', '', 0, '0000-00-00 00:00:00', {$ordering}, 0)";
			$db->setQuery($query);
			$db->query();
		}
	}

	/**
	 * Add, as needed, the module entry to the appropriate table, depending on the Joomla version
	 *
	 * @param $element - (string) plugin element
	 * @param $enabled - (int)    whether or not the plugin should be enabled
	 * @param $params  - (array)  plugin params (if already known)
	 * @return bool
	 **/
	public static function addModuleEntry($element, $enabled=1, $params='')
	{
		$db = self::$db;

		if ($db->tableExists('#__extensions'))
		{
			$name = $element;

			// First, make sure it isn't already there
			$query = "SELECT `extension_id` FROM `#__extensions` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			if ($db->loadResult())
			{
				return true;
			}

			$ordering = 0;

			if (!empty($params) && is_array($params))
			{
				$params = json_encode($params);
			}

			$query = "INSERT INTO `#__extensions` (`name`, `type`, `element`, `folder`, `client_id`, `enabled`, `access`, `protected`, `manifest_cache`, `params`, `custom_data`, `system_data`, `checked_out`, `checked_out_time`, `ordering`, `state`)";
			$query .= " VALUES ('{$name}', 'module', '{$element}', '', 0, {$enabled}, 1, 0, '', ".$db->quote($params).", '', '', 0, '0000-00-00 00:00:00', {$ordering}, 0)";
			$db->setQuery($query);
			$db->query();
		}
	}

	/**
	 * Remove component entries from the appropriate table, depending on the Joomla version
	 *
	 * @param $name - (string) component name
	 * @return bool
	 **/
	public static function deleteComponentEntry($name)
	{
		$db = self::$db;

		if ($db->tableExists('#__components'))
		{
			// Delete component entry
			$query = "DELETE FROM `#__components` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			$db->query();
		}
		else
		{
			$name = 'com_' . strtolower($name);
			// Delete component entry
			$query = "DELETE FROM `#__extensions` WHERE `name` = " . $db->quote($name);
			$db->setQuery($query);
			$db->query();

			// Remove the component container in the assets table
			$asset = \JTable::getInstance('Asset');
			if ($asset->loadByName($name))
			{
				$asset->delete();
			}

			// Check for an admin menu entry...if it's not there, create it
{'fraction_non_alphanumeric': 0.17874871877323795, 'fraction_numerical': 0.007154772192857287, 'mean_word_length': 2.9553259141494435, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 402, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '512066', 'n_tokens_mistral': 19730, 'n_tokens_neox': 15989, 'n_words': 4804}