<?php
/**
* @module Q
*/
/**
* Plugins manager
* @class Q_Plugin
*/
class Q_Plugin
{
/**
* Connect Qbix platform if it's not already connected and set up constants
* @method prepare
* @static
* @private
* @throws {Exception} If cannot find file Q.php
* or APP_DIR is not defined or does not exists or is not directory
* or APP_WEB_DIR, APP_LOCAL_DIR not defined
*/
static private function prepare() {
// Connect Qbix platform if it's not already connected
if (!class_exists('Q', false)) {
if (!file_exists($Q_file = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Q.php')) {
throw new Exception("$Q_file not found");
} else {
include($Q_file);
}
}
if (!class_exists('Q', false)) {
throw new Exception("Could not load Qbix Platform");
}
// Is APP_DIR defined and does it exist?
if (!defined('APP_DIR')) {
throw new Exception("APP_DIR is not defined");
}
if (!is_dir(APP_DIR)) {
throw new Exception(APP_DIR . " doesn't exist or is not a directory");
}
if (!defined('APP_WEB_DIR')) {
throw new Exception("APP_WEB_DIR is not defined");
}
if (!defined('APP_LOCAL_DIR')) {
throw new Exception("APP_LOCAL_DIR is not defined");
}
}
/**
* Get or set extra column from [plugin_name]_Q_plugin or [app_name]_Q_app tables
* Also check whether this column exist, and create if not.
* @method handleExtra
* @static
* @param {string} $name The name of application or plugin
* @param {string} $type One of 'app' or 'plugin'
* @param {string} $conn_name The name of the connection to affect
* @param {array} $options Contain data parsed from command line
* @param {array} $options.extra
* @throws {Exception} If cannot connect to database
* @return array List of installed stream names
*/
static function handleExtra($name, $type, $conn_name, $options = array())
{
// Get SQL connection for currently installed schema
// Is schema connection information provided?
if (($dbconf = Db::getConnection($conn_name)) == null) {
throw new Exception("Could not get info for database connection '$conn_name'. Check " . APP_LOCAL_DIR . "/app.json");
}
$tempname = $conn_name . '_' . time();
Db::setConnection($tempname, $dbconf);
// Try connecting
try {
$db = Db::connect($tempname);
$prefix = $dbconf['prefix'];
} catch (Exception $e) {
throw new Exception("Could not connect to DB connection '$conn_name': " . $e->getMessage(), $e->getCode(), $e);
}
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$tableName = "{$prefix}Q_{$type}";
if ($db->dbms() === 'mysql') {
$cols = false;
// check if table exist
try {
$cols = $db->rawQuery("SHOW COLUMNS FROM $tableName")
->execute()->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
// table not exist
throw new Exception("Table '$tableName': " . $e->getMessage(), $e->getCode(), $e);
}
// check if column "extra" exist, if not - create one
if ($cols) {
$found = false;
foreach ($cols as $col) {
if ($col['Field'] === 'extra') {
$found = true;
break;
}
}
if (!$found) {
echo "Adding 'extra' column to '$tableName'" . PHP_EOL;
$db->rawQuery("ALTER TABLE `$tableName`
ADD COLUMN `extra` VARCHAR (2047) DEFAULT '{}' COMMENT 'json encoded';")
->execute();
}
}
}
// if defined $options['streamsList'] - add these streams to extra
if (is_array($options['extra'])) {
// update extra
$db->update($tableName)
->set(array('extra' => Q::json_encode($options['extra'])))
->where(array($type => $name))
->execute();
}
// get current extra
$res = $db->select('extra', $tableName)
->where(array($type => $name))
->fetchAll(PDO::FETCH_ASSOC);
if (!empty($res)) {
$extra = Q::json_decode($res[0]['extra'], true) ?: array();
} else {
$extra = array();
}
return $extra;
}
/**
* Get list of users specified streams (Streams/onInsert/Users_User) installed.
* To compare with config data and decide whether need to install new streams.
* @method getUsersStreams
* @static
* @throws {Exception} If cannot connect to database
* @return array
*/
static function getUsersStreams()
{
// under this key we save streams names
$key = "Streams/User/onInsert";
$extra = self::handleExtra('Streams', 'plugin', 'Streams');
$extra[$key] = is_array($extra[$key]) ? $extra[$key] : array();
return $extra[$key];
}
/**
* Set list of users specified streams (Streams/onInsert/Users_User) installed.
* @method setUsersStreams
* @static
* @param {array} $streamsList array of streams list installed.
* @throws {Exception} If cannot connect to database
* @return array Result extra
*/
static function setUsersStreams($streamsList)
{
// under this key we save streams names
$key = "Streams/User/onInsert";
$extra = self::handleExtra('Streams', 'plugin', 'Streams');
$extra[$key] = is_array($extra[$key]) ? $extra[$key] : array();
$extra[$key] = array_values(array_unique(array_merge($extra[$key], $streamsList)));
return self::handleExtra('Streams', 'plugin', 'Streams', compact('extra'));
}
/**
* Install or update schema for app or plugin
* @method installSchema
* @static
* @param {string} $base_dir The directory where application or plugin is located
* @param {string} $name The name of application or plugin
* @param {string} $type One of 'app' or 'plugin'
* @param {string} $conn_name The name of the connection to affect
* @param {array} $options Contain data parsed from command line
* @throws {Exception} If cannot connect to database
*/
static function installSchema($base_dir, $name, $type, $conn_name, $options)
{
// is schema installation requested?
if (!isset($options['sql'])
|| empty($options['sql'][$conn_name])
|| !$options['sql'][$conn_name]['enabled']) {
return;
}
$config = $type === 'app'
? Q_Config::get('Q', "{$type}Info", null)
: Q_Config::get('Q', "{$type}Info", $name, null);
// version to install or update
$version = $config['version'];
// Get SQL connection for currently installed schema
// Is schema connection information provided?
if (($dbconf = Db::getConnection($conn_name)) == null)
throw new Exception("Could not get info for database connection '$conn_name'. Check ".APP_LOCAL_DIR."/app.json");
// If option -sql-user-pass was used, override config's username and password
// TODO: set pasword per shard
if (isset($options['sql'][$conn_name]['username'])) {
$dbconf['username'] = $options['sql'][$conn_name]['username'];
$dbconf['password'] = self::getPassword($conn_name);
}
$shards = array('' => $dbconf);
if (isset($dbconf['shards']))
$shards = array_merge($shards, $dbconf['shards']);
foreach ($shards as $shard => $data) {
$shard_text = ($shard === '' ? "" : " shard '$shard'");
$tempname = $conn_name . '_' . time();
$shard_data = array_merge($dbconf, $data);
Db::setConnection($tempname, $shard_data);
// Try connecting
try {
$db = Db::connect($tempname);
$pdo = $db->reallyConnect($shard);
list($dbms) = explode(':', $shard_data['dsn']);
$prefix = $shard_data['prefix'];
} catch (Exception $e) {
throw new Exception("Could not connect to DB connection '$conn_name'$shard_text: " . $e->getMessage(), $e->getCode(), $e);
}
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
if ($db->dbms() === 'mysql') {
// Do we already have $name installed?
// Checking SCHEMA plugin version in the DB.
$tableName = "{$prefix}Q_{$type}";
$cols = false;
try {
$cols = $db->rawQuery("SHOW COLUMNS FROM $tableName")
->execute()->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
$db->rawQuery("CREATE TABLE IF NOT EXISTS `$tableName` (
`{$type}` VARCHAR(255) NOT NULL,
`version` VARCHAR( 255 ) NOT NULL,
`versionPHP` VARCHAR (255) NOT NULL,
PRIMARY KEY (`{$type}`)) ENGINE = InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
")->execute();
}
if ($cols) {
$found = false;
foreach ($cols as $col) {
if ($col['Field'] === 'versionPHP') {
$found = true;
break;
}
}
if (!$found) {
$db->rawQuery("ALTER TABLE `$tableName`
ADD COLUMN `versionPHP` VARCHAR (255) NOT NULL AFTER `version`;
")->execute();
$db->update($tableName)->set(array(
'versionPHP' => new Db_Expression('version')
))->execute();
}
}
}
$res = $db->select('version, versionPHP', "{$prefix}Q_{$type}")
->where(array($type => $name))
->fetchAll(PDO::FETCH_ASSOC);
// If we have version in the db then this is upgrade
if (!empty($res)) {
$current_version = $res[0]['version'];
$current_versionPHP = $res[0]['versionPHP'];
echo ucfirst($type)." '$name' schema on '$conn_name'$shard_text (SQL $current_version, PHP $current_versionPHP) is already installed" . PHP_EOL;
if (Q::compareVersion($current_version, $version) < 0
or Q::compareVersion($current_versionPHP, $version) < 0) {
echo "Upgrading '$name' on '$conn_name'$shard_text schema to version: SQL $version" . PHP_EOL;
}
} else {
// Otherwise considering that plugin has version '0' to override it for getSqlScripts()
$current_version = $current_versionPHP = 0;
}
// collect script files for upgrade
$scriptsdir = $base_dir.DS.'scripts'.DS.$name;
$scriptsSQL = array();
$scriptsPHP = array();
if (!is_dir($scriptsdir)) return;
$dir = opendir($scriptsdir);
// Find all scripts for this dbms
while (($entry = readdir($dir)) !== false) {
$parts = preg_split('/(-|__)/', $entry, 2);
// wrong filename format
if (count($parts) < 2) continue;
list($sqlver, $tail) = $parts;
if ($tail !== "$conn_name.$dbms"
and $tail !== "$conn_name.$dbms.php") {
continue; // not schema file or php script
}
// If this sql file is for later plugin version than we are installing - skip it
if (Q::compareVersion($sqlver, $version) > 0) {
continue;
}
// we shall install this script!
if ($tail === "$conn_name.$dbms"
and Q::compareVersion($sqlver, $current_version) > 0) {
$scriptsSQL["$sqlver"] = $entry;
} else if ($tail === "$conn_name.$dbms.php"
and Q::compareVersion($sqlver, $current_versionPHP) > 0) {
$scriptsPHP["$sqlver"] = $entry;
}
}
closedir($dir);
// Sort scripts according to version
uksort($scriptsSQL, array('Q', 'compareVersion'));
uksort($scriptsPHP, array('Q', 'compareVersion'));
if (!empty($scripts)) {
echo "Running SQL scripts for $type $name on $conn_name ($dbms)".PHP_EOL;
}
$scripts = array();
foreach ($scriptsSQL as $s) {
$scripts[] = $s;
}
foreach ($scriptsPHP as $s) {
$scripts[] = $s;
}
// echo "Begin transaction".PHP_EOL;
// $query = $db->rawQuery('')->begin()->execute();
$original_version = $current_version;
// Process script files
foreach ($scripts as $script) {
try {
list($new_version) = preg_split('/(-|__)/', $script, 2);
if (substr($script, -4) === '.php') {
echo "Processing PHP file: $script " . PHP_EOL;
Q::includeFile($scriptsdir.DS.$script);
$db->update("{$prefix}Q_{$type}")->set(array(
'versionPHP' => $new_version
))->where(array(
$type => $name
))->execute();
continue;
}
echo "Processing SQL file: $script ";
$sqltext = file_get_contents($scriptsdir.DS.$script);
$sqltext = str_replace('{$prefix}', $prefix, $sqltext);
$sqltext = str_replace('{$dbname}', $db->dbname, $sqltext);
/**
* @event Q/Plugin/installSchema {before}
* @param {string} $base_dir The directory where application or plugin is located
* @param {string} $name The name of application or plugin
* @param {string} $type One of 'app' or 'plugin'
* @param {string} $conn_name The name of the connection to affect
* @param {array} $options Contain data parsed from command line
* @param {string} $shard
* @param {array} $shard_data
* @param {string} $new_version
* @param {string} $current_version
* @param {string} $script The script to execute
*/
$ret = Q::event("Q/Plugin/installSchema", compact(
'base_dir', 'name', 'type', 'options',
'conn_name', 'connection',
'shard', 'shard_data',
'script', 'newver', 'current_version'
), 'before');
if ($ret === false) {
continue;
}
$queries = $db->scriptToQueries($sqltext);
// Process each query
foreach ($queries as $q) {
$db->rawQuery($q)->execute();
echo ".";
}
// Update plugin db version
if ($dbms === 'mysql') {
$fields = array(
$type => $name,
'version' => $new_version,
'versionPHP' => 0
);
$db->insert("{$prefix}Q_{$type}", $fields)
->onDuplicateKeyUpdate(array('version' => $new_version))
->execute();
$current_version = $new_version;
}
echo PHP_EOL;
} catch (Exception $e) {
$errorCode = $pdo->errorCode();
if ($errorCode != '00000') {
$info = $pdo->errorInfo();
$message = $info[2];
if (isset($e->params['sql'])) {
$message .= PHP_EOL . "Query was: " . $e->params['sql'];
}
$err = new Q_Exception(
$message, array(), $errorCode,
$e->getFile(), $e->getLine(),
$e->getTrace(), $e->getTraceAsString()
);
} else {
$err = $e;
}
throw $err;
// echo PHP_EOL;
// echo "Rollback".PHP_EOL;
// try {
// $query = $db->rawQuery('')->rollback()->execute();
// } catch (Exception $e) {
// throw $err;
// }
}
}
try {
if (Q::compareVersion($version, $current_version) > 0
or Q::compareVersion($version, $current_versionPHP) > 0) {
echo '+ ' . ucfirst($type) . " '$name' schema on '$conn_name'$shard_text (v. $original_version -> $version) installed".PHP_EOL;
$db->insert("{$prefix}Q_{$type}", array(
$type => $name,
'version' => $version,
'versionPHP' => $version
))->onDuplicateKeyUpdate(array(
'version' => $version,
'versionPHP' => $version
))->execute();
}
} catch (Exception $e) {
if ($pdo->errorCode() != '00000') {
// echo "Rollback".PHP_EOL;
// $query = $db->rawQuery('')->rollback()->execute();
$info = $pdo->errorInfo();
$message = $info[2];
if (isset($e->params['sql'])) {
$message .= PHP_EOL . "Query was: " . $e->params['sql'];
}
throw new Q_Exception(
$message, array(), $pdo->errorCode(),
$e->getFile(), $e->getLine(),
$e->getTrace(), $e->getTraceAsString()
);
}
}
// echo "Commit transaction".PHP_EOL;
// $query = $db->rawQuery('')->commit()->execute();
}
}
/**
* @method checkTree
* @static
* @private
* @param {string} $root
* @param {integer} $filemode
* @param {integer} $dirmode
* @param {string|integer|false} [$gid=false]
* @param {boolean} [$ask=true]
* @return {boolean}
*/
static private function checkTree($root, $filemode, $dirmode, $gid = false, $ask = true) {
// fix permissions for current folder
$ask = self::fixPermissions($root, $dirmode, $gid, $ask);
foreach (glob($root.'*', GLOB_MARK) as $path) {
if ($path[strlen($path)-1] == DS) {
$ask = self::checkTree($path, $filemode, $dirmode, $gid, $ask);
} else {
$ask = self::fixPermissions($path, $filemode, $gid, $ask);
}
}
return $ask;
}
/**
* @method fixPermissions
* @static
* @private
* @param {string} $file
* @param {integer} $mode
* @param {string|integer|false} [$gid=false]
* @param {boolean} [$ask=true]
* @return {boolean}
*/
static private function fixPermissions($file, $mode, $gid = false, $ask = true) {
$line = '';
$modefix = $groupfix = false;
if(($modefix = (fileperms($file) & 0777) != $mode)
|| ($groupfix = $gid !== false && filegroup($file) !== $gid)) {
if ($ask) {
echo("Fix permissions for '$file' [y/n/all]? ");
$line = trim(fgets(STDIN));
}
switch ($line) {
case 'all':
$ask = false;
case '':
case 'y':
case 'Y':
if ($groupfix and !chgrp($file, $gid)) {
echo Q_Utils::colored("[WARN] Couldn't change group for $file", 'red', 'yellow').PHP_EOL;
}
if ($modefix and !chmod($file, $mode)) {
echo Q_Utils::colored("[WARN] Couldn't fix permissions for $file", 'red', 'yellow').PHP_EOL;
}
break;
default:
break;
}
}
return $ask;
}
/**
* @method checkPermissions
* @static
* @private
* @param {string} $files_dir
* @param {array} $options
*/
static function checkPermissions($files_dir, $options) {
// Check and fix permissions
if(!file_exists($files_dir)) {
$mask = umask(Q_Config::get('Q', 'internal', 'umask', 0000));
mkdir($files_dir, $options['dirmode'], true);
umask($mask);
}
// if group is supplied, convert name to gid
if (isset($options['group'])) {
$group = $options['group'];
if (!is_numeric($group)) {
$posix = posix_getgrnam($group);
$group = $posix['gid'];
}
} else {
$group = false;
}
if (isset($options['deep'])) {
self::checkTree($files_dir, $options['filemode'], $options['dirmode'], $group);
} else {
self::fixPermissions($files_dir, $options['dirmode'], $group);
}
}
/**
* @method installApp
* @static
* @param {array} $options
* @throws {Exception}
*/
static function installApp($options) {
// Connect Qbix platform if it's not already connected
self::prepare();
set_time_limit(Q_Config::expect('Q', 'install', 'timeLimit'));
// application data shall be defined in it's config
$APP_NAME = Q_Config::get('Q', 'app', null);
$APP_CONF = Q_Config::get('Q', 'appInfo', null);
$APP_VERSION = $APP_CONF['version'];
$app_dir = APP_DIR;
echo "Installing app '$APP_NAME' (version: $APP_VERSION) into '$app_dir'" . PHP_EOL;
// Ensure that the app has config/app.json
if (!file_exists($app_conf_file = $app_dir . DS . 'config' . DS . 'app.json'))
throw new Exception("Could not load apps's config. Check $app_conf_file");
$files_dir = APP_FILES_DIR;
$app_installed_file = APP_LOCAL_DIR.DS.'installed.json';
$app_plugins_file = APP_LOCAL_DIR.DS.'plugins.json';
if (file_exists($app_plugins_file)) {
Q_Config::load($app_plugins_file);
}
// Check requirements for app (will throw exceptions if they aren't met)
if(!isset($options['noreq']) || !$options['noreq']) {
echo "Checking requirements".PHP_EOL;
Q_Bootstrap::checkRequirementsApp();
}
// Check access to $app_installed_file
if(file_exists($app_installed_file) && !is_writable($app_installed_file))
throw new Exception("Can not write to $app_installed_file");
elseif(!file_exists($app_installed_file) && !is_writable(dirname($app_installed_file)))
throw new Exception("Can not write to ".dirname($app_installed_file));
if (file_exists($app_installed_file)) {
Q_Config::load($app_installed_file);
}
// Check access to $files_dir
if(!file_exists($files_dir)) {
if(!@mkdir($files_dir, $options['dirmode'], true)) {
throw new Exception("Could not create $files_dir");
}
}
// Do we now have app's config?
if (Q_Config::get('Q', 'app', null) == null) {
throw new Exception("Could not identify app name. Check $app_conf_file");
}
// Do we now have app's config?
if (Q_Config::get('Q', 'appInfo', 'version', null) == null) {
throw new Exception("Could not identify app version. Check $app_conf_file");
}
// Check and fix permissions
self::checkPermissions(APP_FILES_DIR, $options);
self::npmInstall(APP_DIR, !empty($options['npm']));
self::composerInstall(APP_DIR, !empty($options['composer']));
// install or update application schema
$connections = Q_Config::get('Q', 'appInfo', 'connections', array());
foreach ($connections as $connection) {
self::installSchema($app_dir, $APP_NAME, 'app', $connection, $options);
}
// Save info about app
echo 'Registering app'.PHP_EOL;
Q_Config::set('Q', 'appLocal', $APP_CONF);
Q_Config::save($app_installed_file, array('Q', 'appLocal'));
// Create .htaccess file if it doesn't exist
if (!file_exists(APP_WEB_DIR.DS.'.htaccess')) {
$htaccess = <<<EOT
RewriteEngine on
# uncomment the following line and modify it, if you're having problems:
# You will need to do it on Windows:
#RewriteBase /your_app_local_url/
# we can check if the .html version is here (caching)
#RewriteRule ^$ index.html [QSA]
#RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
#RewriteCond %{REQUEST_FILENAME} !-d
# no, so we redirect to our front web controller
RewriteRule ^(.*)$ index.php [QSA,L]
EOT;
file_put_contents(APP_WEB_DIR.DS.'.htaccess', $htaccess);
}
echo Q_Utils::colored("App '$APP_NAME' successfully installed".PHP_EOL, 'green');
}
/**
* @method installPlugin
* @static
* @param {string} $plugin_name
* @param {array} $options
* @throws {Exception}
*/
static function installPlugin($plugin_name, $options)
{
set_time_limit(Q_Config::expect('Q', 'install', 'timeLimit'));
// Connect Qbix platform if it's not already connected
self::prepare();
$app_dir = APP_DIR;
$plugin_dir = Q_PLUGINS_DIR.DS.$plugin_name;
$plugin_text_dir = $plugin_dir.DS.'text'.DS.$plugin_name;
$app_web_plugins_dir = APP_WEB_DIR.DS.'Q'.DS.'plugins';
$app_web_text_dir = APP_WEB_DIR.DS.'Q'.DS.'text';
$app_text_plugin_dir = APP_TEXT_DIR.DS.$plugin_name;
/**
* @event Q/Plugin/install {before}
* @param {string} $app_dir the directory where the app is installed
* @param {string} $plugin_name the name of the plugin
* @param {array} $options options passed to the installPlugin method
*/
Q::event("Q/Plugin/install", compact('app_dir', 'plugin_name', 'options'), 'before');
echo "Installing plugin '$plugin_name' into '$app_dir'" . PHP_EOL;
// Do we even have such a plugin?
if (!is_dir($plugin_dir))
throw new Exception("Plugin '$plugin_name' not found in " . Q_PLUGINS_DIR);
// Ensure that the plugin has config.json
if (!file_exists($plugin_conf_file = $plugin_dir . DS . 'config' . DS . 'plugin.json'))
throw new Exception("Could not load plugin's config. Check $plugin_conf_file");
$files_dir = $plugin_dir.DS.'files';
$app_plugins_file = APP_LOCAL_DIR.DS.'plugins.json';
// Check access to $app_web_plugins_dir
$dirs = array($app_web_plugins_dir, $app_web_text_dir);
foreach ($dirs as $dir) {
if(!file_exists($dir)) {
if(!@mkdir($dir, 0755, true)) {
throw new Exception("Could not create $dir");
}
}
if (!is_dir($dir)) {
throw new Exception("$dir exists, but is not a directory");
} else if(!is_writable($dir)) {
throw new Exception("Can not write to $dir");
}
}
// Check access to $app_plugins_file
if(file_exists($app_plugins_file) && !is_writable($app_plugins_file))
throw new Exception("Can not write to $app_plugins_file");
elseif(!file_exists($app_plugins_file) && !is_writable(dirname($app_plugins_file)))
throw new Exception("Can not write to ".dirname($app_plugins_file));
// Check access to $files_dir
if(!file_exists($files_dir))
if(!@mkdir($files_dir, $options['dirmode'], true))
throw new Exception("Could not create $files_dir");
// Do we now have plugin's config?
if (Q_Config::get('Q', 'pluginInfo', $plugin_name, 'version', null) == null)
throw new Exception("Could not identify plugin version. Check $plugin_conf_file");
$plugin_conf = Q_Config::get('Q', 'pluginInfo', $plugin_name, null);
$plugin_version = $plugin_conf['version'];
if (file_exists($app_plugins_file)) {
Q_Config::load($app_plugins_file, true);
}
// Do we already have this plugin installed for this app?
// Check requirements for plugin (will throw exceptions if they aren't met)
if(!isset($options['noreq']) || !$options['noreq']) {
echo "Checking requirements".PHP_EOL;
Q_Bootstrap::checkRequirements(array($plugin_name));
}
// Checking LOCAL plugin version in plugins.json file
if (($version_installed = Q_Config::get('Q', 'pluginLocal', $plugin_name, 'version', null)) != null) {
// We have this plugin installed
echo "Plugin '$plugin_name' (version: $version_installed) is already installed" . PHP_EOL;
if (Q::compareVersion($version_installed, $plugin_version) < 0) {
echo "Upgrading '$plugin_name' to version: $plugin_version" . PHP_EOL;
}
}
// Check and fix permissions
self::checkPermissions($files_dir, $options);
if (isset($plugin_conf['permissions'])) {
foreach ($plugin_conf['permissions'] as $perm) {
self::checkPermissions($files_dir.DS.$perm, $options);
}
}
self::npmInstall($plugin_dir, !empty($options['npm']));
self::composerInstall($plugin_dir, !empty($options['composer']));
// Symbolic links
echo 'Creating symbolic links'.PHP_EOL;
if (!file_exists($app_web_plugins_dir.DS.$plugin_name)) {
$p = $app_web_plugins_dir.DS.$plugin_name;
echo ' '.$p.PHP_EOL;
Q_Utils::symlink($plugin_dir.DS.'web', $p);
}
if (!file_exists($app_text_plugin_dir) and file_exists($plugin_text_dir)) {
$p = $app_text_plugin_dir;
echo ' '.$p.PHP_EOL;
Q_Utils::symlink($plugin_text_dir, $app_text_plugin_dir);
}
// Checking if schema update is requested and updating database version
$connections = Q_Config::get('Q', 'pluginInfo', $plugin_name, 'connections', array());
foreach ($connections as $connection) {
self::installSchema(Q_PLUGINS_DIR.DS.$plugin_name, $plugin_name, 'plugin', $connection, $options);
}
// Push plugin name into Q/plugins array
if (!in_array($plugin_name, $current_plugins = Q_Config::get('Q', 'plugins', array()))) {
$current_plugins[] = $plugin_name;
Q_Config::set('Q', 'plugins', $current_plugins); //TODO: When do we save Q/plugins to disk?
}
// Save info about plugin
echo 'Registering plugin'.PHP_EOL;
Q_Config::set('Q', 'pluginLocal', $plugin_name, $plugin_conf);
Q_Config::save($app_plugins_file, array('Q', 'pluginLocal'));
/**
* @event Q/Plugin/install {after}
* @param {string} $app_dir the directory where the app is installed
* @param {string} $plugin_name the name of the plugin
* @param {array} $options options passed to the installPlugin method
*/
Q::event("Q/Plugin/install", compact('app_dir', 'plugin_name', 'options'), 'after');
echo Q_Utils::colored("Plugin '$plugin_name' successfully installed".PHP_EOL, 'green');
}
static function npmInstall($dir, $exists = false)
{
$exists = $exists || self::commandExists('npm');
if (!file_exists($dir . DS . 'package.json') or !$exists) {
return false;
}
echo "Installing npm modules into $dir".DS."node_modules\n";
$cwd = getcwd();
chdir($dir);
shell_exec("npm install");
chdir($cwd);
return true;
}
static function composerInstall($dir, $exists = false)
{
return true;
$exists = $exists || self::commandExists('composer');
if (!file_exists($dir . DS . 'composer.json') or !$exists) {
return false;
}
echo "Installing composer packages into $dir".DS."vendor\n";
$cwd = getcwd();
chdir($dir);
shell_exec("composer install");
chdir($cwd);
return true;
}
static function commandExists($cmd) {
$return = shell_exec(sprintf("which %s", escapeshellarg($cmd)));
return !empty($return);
}
/**
* Get password for connection
* @method getPasword
* @static
* @private
* @param {string} $conn_name
* @param {boolean} [$stars=false]
* @return {string}
*/
private static function getPassword($conn_name, $stars = false)
{
echo "Enter password for '$conn_name' connection: ";
// Get current style
$oldStyle = shell_exec('stty -g');
if ($stars === false) {
shell_exec('stty -echo');
$password = rtrim(fgets(STDIN), "\n");
} else {
shell_exec('stty -icanon -echo min 1 time 0');
$password = '';
while (true) {
$char = fgetc(STDIN);
if ($char === "\n") {
break;
} else if (ord($char) === 127) {
if (strlen($password) > 0) {
fwrite(STDOUT, "\x08 \x08");
$password = substr($password, 0, -1);
}
} else {
fwrite(STDOUT, "*");
$password .= $char;
}
}
}
// Reset old style
shell_exec('stty ' . $oldStyle);
echo PHP_EOL;
// Return the password
return $password;
}
}