* Added database connection port

* Fixed some UI errors
* Added "Info" tab in configuration section
This commit is contained in:
nuxsmin
2015-03-31 13:55:21 +02:00
parent 40acdb2ef8
commit f803601dd5
9 changed files with 301 additions and 146 deletions

View File

@@ -3,8 +3,8 @@
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2015 Rubén Domínguez nuxsmin@syspass.org
*
* This file is part of sysPass.
@@ -320,6 +320,7 @@ switch ($action) {
echo (SP_ACL::checkUserAccess("masterpass")) ? '<LI><A HREF="#tabs-2" TITLE="' . _('Encriptación') . '">' . _('Encriptación') . '</A></LI>' : '';
echo (SP_ACL::checkUserAccess("backup")) ? '<LI><A HREF="#tabs-3" TITLE="' . _('Copia de Seguridad') . '">' . _('Copia de Seguridad') . '</A></LI>' : '';
echo (SP_ACL::checkUserAccess("config")) ? '<LI><A HREF="#tabs-4" TITLE="' . _('Importar cuentas desde fuentes externas') . '">' . _('Importar Cuentas') . '</A></LI>' : '';
echo (SP_ACL::checkUserAccess("config")) ? '<LI><A HREF="#tabs-5" TITLE="' . _('Información de la aplicación') . '">' . _('Información') . '</A></LI>' : '';
echo '</UL>';
$tplvars['activeTab'] = 0;
@@ -351,22 +352,33 @@ switch ($action) {
$tplvars['activeTab']++;
echo '<DIV ID="tabs-4">';
SP_Html::getTemplate('migrate', $tplvars);
SP_Html::getTemplate('import', $tplvars);
echo '</DIV>';
}
if (SP_ACL::checkUserAccess("config")) {
$tplvars['activeTab']++;
echo '<DIV ID="tabs-5">';
SP_Html::getTemplate('info', $tplvars);
echo '</DIV>';
}
echo '</DIV>';
echo '<script>
?>
<script>
$("#tabs").tabs({
active: ' . $itemId . ',
create: function( event, ui ) {$("input:visible:first").focus();},
activate: function( event, ui ) {
active: <?php echo $itemId ?>,
create: function (event, ui) {
$("input:visible:first").focus();
},
activate: function (event, ui) {
setContentSize();
$("input:visible:first").focus();
}
});
</script>';
</script>
<?php
break;
case "eventlog":
SP_ACL::checkUserAccess($action) || SP_Html::showCommonError('unavailable');

View File

@@ -1753,7 +1753,7 @@ fieldset.warning a {
margin-bottom: 1em;
}
#actions.installer form p {
#actions.installer form fieldset p {
width: 300px;
margin: 0 auto;
}

View File

@@ -62,10 +62,11 @@ class DBConnectionFactory
// error_log('NEW DB_CONNECTION');
$isInstalled = SP_Config::getValue('installed');
$dbhost = SP_Config::getValue("dbhost");
$dbuser = SP_Config::getValue("dbuser");
$dbpass = SP_Config::getValue("dbpass");
$dbname = SP_Config::getValue("dbname");
$dbhost = SP_Config::getValue('dbhost');
$dbuser = SP_Config::getValue('dbuser');
$dbpass = SP_Config::getValue('dbpass');
$dbname = SP_Config::getValue('dbname');
$dbport = SP_Config::getValue('dbport', 3306);
if (empty($dbhost) || empty($dbuser) || empty($dbpass) || empty($dbname)) {
if ($isInstalled) {
@@ -76,7 +77,7 @@ class DBConnectionFactory
}
try {
$dsn = 'mysql:host=' . $dbhost . ';dbname=' . $dbname . ';charset=utf8';
$dsn = 'mysql:host=' . $dbhost . ';port=' . $dbport . ';dbname=' . $dbname . ';charset=utf8';
// $this->db = new PDO($dsn, $dbuser, $dbpass, array(PDO::ATTR_PERSISTENT => true));
$this->db = new PDO($dsn, $dbuser, $dbpass);
} catch (PDOException $e) {
@@ -85,7 +86,7 @@ class DBConnectionFactory
SP_Config::setValue('installed', '0');
}
SP_Init::initError(_('No es posible conectar con la BD'), 'Error ' . $this->db->errorCode() . ': ' . $this->db->errorInfo());
SP_Init::initError(_('No es posible conectar con la BD'), 'Error ' . $e->getCode() . ': ' . $e->getMessage());
} else {
throw new SPDatabaseException($e->getMessage(), $e->getCode());
}
@@ -204,7 +205,7 @@ class DB
*
* @param string $query con la consulta a realizar
* @param string $querySource con el nombre de la función que realiza la consulta
* @param array $data con los datos de la consulta
* @param array $data con los datos de la consulta
* @return bool|array devuelve bool si hay un error. Devuelve array con el array de registros devueltos
*/
public static function getResults($query, $querySource, &$data = null)
@@ -224,7 +225,7 @@ class DB
return false;
}
if (self::$unbuffered && is_object($doQuery) && get_class($doQuery) == "PDOStatement"){
if (self::$unbuffered && is_object($doQuery) && get_class($doQuery) == "PDOStatement") {
return $doQuery;
}
@@ -244,6 +245,13 @@ class DB
return $db->last_result;
}
private static function resetVars()
{
self::$unbuffered = false;
self::$fullRowCount = false;
self::$retArray = false;
}
/**
* Realizar una consulta a la BBDD.
*
@@ -270,7 +278,7 @@ class DB
if (!$unbuffered) {
$this->num_fields = $queryRes->columnCount();
$this->last_result = $queryRes->fetchAll(PDO::FETCH_OBJ);
} else{
} else {
return $queryRes;
}
@@ -318,7 +326,7 @@ class DB
continue;
}
if ($param == 'blobcontent'){
if ($param == 'blobcontent') {
$sth->bindValue($param, $value, PDO::PARAM_LOB);
} elseif (is_int($value)) {
//error_log("INT: " . $param . " -> " . $value);
@@ -347,50 +355,6 @@ class DB
return false;
}
/**
* Obtener el número de filas de una consulta realizada
*
* @return int Número de files de la consulta
* @throws SPDatabaseException
*/
private function getFullRowCount(&$query)
{
if (empty($query)) {
return 0;
}
$patterns = array(
'/(LIMIT|ORDER BY|GROUP BY).*/i',
'/SELECT DISTINCT\s([\w_]+),.* FROM/i',
'/SELECT [\w_]+,.* FROM/i'
);
$replace = array('','SELECT COUNT(DISTINCT \1) FROM', 'SELECT COUNT(*) FROM');
$query = preg_replace($patterns, $replace, $query);
try {
$db = DBConnectionFactory::getFactory()->getConnection();
if (!is_array($this->stData)) {
$queryRes = $db->query($query);
$num = intval($queryRes->fetchColumn());
} else {
if ($queryRes = $this->prepareQueryData($query, true)) {
$num = intval($queryRes->fetchColumn());
}
}
$queryRes->closeCursor();
return $num;
} catch (PDOException $e) {
error_log("Exception: " . $e->getMessage());
throw new SPDatabaseException($e->getMessage());
}
return 0;
}
/**
* Método para registar los eventos de BD en el log
*
@@ -425,6 +389,50 @@ class DB
}
}
/**
* Obtener el número de filas de una consulta realizada
*
* @return int Número de files de la consulta
* @throws SPDatabaseException
*/
private function getFullRowCount(&$query)
{
if (empty($query)) {
return 0;
}
$patterns = array(
'/(LIMIT|ORDER BY|GROUP BY).*/i',
'/SELECT DISTINCT\s([\w_]+),.* FROM/i',
'/SELECT [\w_]+,.* FROM/i'
);
$replace = array('', 'SELECT COUNT(DISTINCT \1) FROM', 'SELECT COUNT(*) FROM');
$query = preg_replace($patterns, $replace, $query);
try {
$db = DBConnectionFactory::getFactory()->getConnection();
if (!is_array($this->stData)) {
$queryRes = $db->query($query);
$num = intval($queryRes->fetchColumn());
} else {
if ($queryRes = $this->prepareQueryData($query, true)) {
$num = intval($queryRes->fetchColumn());
}
}
$queryRes->closeCursor();
return $num;
} catch (PDOException $e) {
error_log("Exception: " . $e->getMessage());
throw new SPDatabaseException($e->getMessage());
}
return 0;
}
/**
* Realizar una consulta y devolver el resultado sin datos
*
@@ -467,11 +475,34 @@ class DB
self::$fullRowCount = true;
}
private static function resetVars()
/**
* Obtener la información del servidor de base de datos
*
* @return array
*/
public static function getDBinfo()
{
self::$unbuffered = false;
self::$fullRowCount = false;
self::$retArray = false;
$dbinfo = array();
try {
$db = DBConnectionFactory::getFactory()->getConnection();
$attributes = array(
'SERVER_VERSION',
'CLIENT_VERSION',
'SERVER_INFO',
'CONNECTION_STATUS',
);
foreach ($attributes as $val) {
$dbinfo[$val] = $db->getAttribute(constant('PDO::ATTR_' . $val));
}
} catch (SPDatabaseException $e) {
return $dbinfo;
}
return $dbinfo;
}
/**

View File

@@ -156,7 +156,14 @@ class SP_Installer
$dbadmin = $options['dbuser'];
$dbpass = $options['dbpass'];
$dbhost = $options['dbhost'];
if (preg_match('/(.*):(\d{1,5})/', $options['dbhost'], $match)){
$dbhost = $match[1];
$dbport = $match[2];
} else {
$dbhost = $options['dbhost'];
$dbport = 3306;
}
self::$isHostingMode = (isset($options['hostingmode'])) ? 1 : 0;
@@ -168,7 +175,7 @@ class SP_Installer
SP_Config::setDefaultValues();
try {
self::checkDatabaseAdmin($dbhost, $dbadmin, $dbpass);
self::checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbport);
self::setupMySQLDatabase();
self::createAdminAccount();
} catch (InstallerException $e) {
@@ -194,18 +201,19 @@ class SP_Installer
* @param string $dbhost host de conexión
* @param string $dbadmin usuario de conexión
* @param string $dbpass clave de conexión
* @param string $dbport puerto de conexión
* @throws InstallerException
* @return none
*/
private static function checkDatabaseAdmin($dbhost, $dbadmin, $dbpass)
private static function checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbport)
{
try {
$dsn = 'mysql:host=' . $dbhost . ';charset=utf8';
$dsn = 'mysql:host=' . $dbhost . ';dbport=' . $dbport . ';charset=utf8';
self::$dbc = new PDO($dsn, $dbadmin, $dbpass);
} catch (PDOException $e){
throw new InstallerException('critical'
, _('El usuario/clave de MySQL no es correcto')
, _('Verifique el usuario de conexión con la Base de Datos'));
, _('No es posible conectar con la BD')
, _('Compruebe los datos de conexión') . '<br>' . $e->getMessage());
}
}

View File

@@ -63,8 +63,6 @@ class MigrateException extends Exception
class SP_Migrate
{
// private static $dbuser;
private static $dbname;
private static $dbhost;
private static $dbc; // Database connection
private static $customersByName;
private static $currentQuery;
@@ -85,15 +83,22 @@ class SP_Migrate
return $result;
}
self::$dbname = $dbname = $options['dbname'];
self::$dbhost = $dbhost = $options['dbhost'];
$dbname = $options['dbname'];
if (preg_match('/(.*):(\d{1,5})/', $options['dbhost'], $match)){
$dbhost = $match[1];
$dbport = $match[2];
} else {
$dbhost = $options['dbhost'];
$dbport = 3306;
}
$dbadmin = $options['dbuser'];
$dbpass = $options['dbpass'];
try {
self::checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbname);
self::checkDatabaseExist();
self::checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbname, $dbport);
self::checkDatabaseExist($dbname);
self::checkSourceVersion();
self::cleanCurrentDB();
self::migrateCustomers();
@@ -106,7 +111,11 @@ class SP_Migrate
self::migrateUsersGroups();
self::migrateConfig();
} catch (MigrateException $e) {
self::$result['error'][] = array('type' => $e->getType(), 'description' => $e->getMessage(), 'hint' => $e->getHint());
self::$result['error'][] = array(
'type' => $e->getType(),
'description' => $e->getMessage(),
'hint' => $e->getHint()
);
return (self::$result);
}
@@ -123,31 +132,33 @@ class SP_Migrate
* @param string $dbadmin usuario de conexión
* @param string $dbpass clave de conexión
* @param string $dbname nombre de la base de datos
* @param string $dbport puerto de conexión
* @throws MigrateException
* @return none
*/
private static function checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbname)
private static function checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbname, $dbport)
{
try {
$dsn = 'mysql:host=' . $dbhost . ';dbname=' . $dbname . ';charset=utf8';
$dsn = 'mysql:host=' . $dbhost . ';dbname=' . $dbname . ';dbport=' . $dbport . ';charset=utf8';
self::$dbc = new PDO($dsn, $dbadmin, $dbpass);
} catch (PDOException $e) {
throw new MigrateException('critical'
, _('El usuario/clave de MySQL no es correcto')
, _('Verifique el usuario de conexión con la Base de Datos'));
, _('No es posible conectar con la BD')
, _('Compruebe los datos de conexión') . '<br>' . $e->getMessage());
}
}
/**
* Comprobar si la BBDD existe.
*
* @return int
* @param string $dbname nombre de la base de datos
* @return bool
*/
private static function checkDatabaseExist()
private static function checkDatabaseExist($dbname)
{
$query = 'SELECT COUNT(*) '
. 'FROM information_schema.tables '
. 'WHERE table_schema = \'' . self::$dbname . '\' '
. 'WHERE table_schema = \'' . $dbname . '\' '
. 'AND table_name = \'usrData\' LIMIT 1';
return (intval(self::$dbc->query($query)->fetchColumn()) === 0);

View File

@@ -111,7 +111,7 @@ $onCloseAction = $data['onCloseAction'];
<?php echo SP_Common::printHelpButton("config", 23); ?>
</td>
<td class="valField">
<form method="post" enctypr="multipart/form-data" name="upload_form" id="fileUpload">
<form method="post" enctype="multipart/form-data" name="upload_form" id="fileUpload">
<input type="file" id="inFile" name="inFile" />
</form>
<div id="dropzone" class="round" title="<?php echo _('Soltar archivo aquí o click para seleccionar'); ?>">

78
inc/tpl/info.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2015 Rubén Domínguez nuxsmin@syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*
*/
defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo'));
$activeTab = $data['activeTab'];
$onCloseAction = $data['onCloseAction'];
?>
<div id="title" class="midroundup titleNormal">
<?php echo _('Información de la Aplicación'); ?>
</div>
<table class="data round">
<tr>
<td class="descField">
<?php echo _('Versión sysPass'); ?>
</td>
<td class="valField">
<?php echo implode('.', SP_Util::getVersion(true)); ?>
</td>
</tr>
<tr>
<td class="descField">
<?php echo _('Base de Datos'); ?>
</td>
<td class="valField">
<?php
foreach (DB::getDBinfo() as $infoattr => $infoval) {
echo $infoattr, ': ', $infoval, '<br><br>';
}
?>
</td>
</tr>
<tr>
<td class="descField">
<?php echo _('PHP'); ?>
</td>
<td class="valField">
<?php
echo _('Versión'), ': ', phpversion(), '<br><br>';
echo _('Extensiones'), ': ', wordwrap(implode(', ', get_loaded_extensions()), 75, '<br>'), '<br><br>';
echo _('Memoria'), ': ', (memory_get_usage(true) / 1024), ' KB<br><br>';
echo _('Usuario'), ': ', get_current_user(), '<br><br>';
?>
</td>
</tr>
<tr>
<td class="descField">
<?php echo _('Servidor'); ?>
</td>
<td class="valField">
<?php echo $_SERVER['SERVER_SOFTWARE']; ?>
</td>
</tr>
</table>

View File

@@ -2,8 +2,8 @@
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012-2015 Rubén Domínguez nuxsmin@syspass.org
*
* This file is part of sysPass.
@@ -25,16 +25,18 @@
$modulesErrors = SP_Util::checkModules();
$versionErrors = SP_Util::checkPhpVersion();
$resInstall = array();
$isCompleted = 0;
$isCompleted = false;
if (isset($_POST['install']) AND $_POST['install'] == 'true') {
if (isset($_POST['install']) && $_POST['install'] == 'true') {
$resInstall = SP_Installer::install($_POST);
if (count($resInstall) == 0) {
$resInstall[] = array('type' => 'ok',
$resInstall[] = array(
'type' => 'ok',
'description' => _('Instalación finalizada'),
'hint' => _('Pulse <a href="index.php" title="Acceder">aquí</a> para acceder'));
$isCompleted = 1;
'hint' => _('Pulse <a href="index.php" title="Acceder">aquí</a> para acceder')
);
$isCompleted = true;
}
}
?>
@@ -46,40 +48,42 @@ if (isset($_POST['install']) AND $_POST['install'] == 'true') {
<span ID="pageDesc"><?php echo _('Instalación ') . ' ' . SP_Util::getVersionString(); ?></span>
</div>
<?php
$securityErrors = array();
if (@file_exists(__FILE__ . "\0Nullbyte")) {
$securityErrors[] = array('type' => 'warning',
'description' => _('La version de PHP es vulnerable al ataque NULL Byte (CVE-2006-7243)'),
'hint' => _('Actualice la versión de PHP para usar sysPass de forma segura'));
}
if (!SP_Util::secureRNG_available()) {
$securityErrors[] = array('type' => 'warning',
'description' => _('No se encuentra el generador de números aleatorios.'),
'hint' => _('Sin esta función un atacante puede utilizar su cuenta al resetear la clave'));
}
$errors = array_merge($modulesErrors, $securityErrors, $resInstall);
if (count($errors) > 0) {
echo '<ul class="errors round">';
foreach ($errors as $err) {
if (is_array($err)) {
echo '<li class="err_' . $err["type"] . '">';
echo '<strong>' . $err['description'] . '</strong>';
echo ($err['hint']) ? '<p class="hint">' . $err['hint'] . '</p>' : '';
echo '</li>';
}
}
echo '</ul>';
}
?>
<form id="frmInstall" action="index.php" method="post">
<input type="hidden" name="install" value="true"/>
<?php
$securityErrors = array();
if (@file_exists(__FILE__ . "\0Nullbyte")) {
$securityErrors[] = array('type' => 'warning',
'description' => _('La version de PHP es vulnerable al ataque NULL Byte (CVE-2006-7243)'),
'hint' => _('Actualice la versión de PHP para usar sysPass de forma segura'));
}
if (!SP_Util::secureRNG_available()) {
$securityErrors[] = array('type' => 'warning',
'description' => _('No se encuentra el generador de números aleatorios.'),
'hint' => _('Sin esta función un atacante puede utilizar su cuenta al resetear la clave'));
}
$errors = array_merge($modulesErrors, $securityErrors, $resInstall);
if (count($errors) > 0) {
echo '<ul class="errors round">';
foreach ($errors as $err) {
if (is_array($err)) {
echo '<li class="err_' . $err["type"] . '">';
echo '<strong>' . $err['description'] . '</strong>';
echo ($err['hint']) ? '<p class="hint">' . $err['hint'] . '</p>' : '';
echo '</li>';
}
}
echo '</ul>';
}
if ($isCompleted === 0):
if ($isCompleted === false):
?>
<fieldset id="adminaccount">
<legend><?php echo _('Crear cuenta de admin de sysPass'); ?></legend>
@@ -173,17 +177,19 @@ if (isset($_POST['install']) AND $_POST['install'] == 'true') {
<div class="buttons"><input type="submit" class="button" value="<?php echo _('Instalar'); ?>"/></div>
</form>
<?php endif; ?>
<?php endif; ?>
</div>
<script>
var hostingmode = <?php echo SP_Util::init_var('hostingmode', 0); ?>;
$('#frmInstall').find('.checkbox').button();
$('#frmInstall').find('.ui-button').click(function () {
// El cambio de clase se produce durante el evento de click
// Si tiene la clase significa que el estado anterior era ON y ahora es OFF
if ($(this).hasClass('ui-state-active')) {
if (hostingmode == 1) {
hostingmode = 0;
$(this).children().html('<?php echo _('Modo Hosting');?> OFF');
} else {
hostingmode = 1;
$(this).children().html('<?php echo _('Modo Hosting');?> ON');
}
});

View File

@@ -2,8 +2,8 @@
/**
* sysPass
*
* @author nuxsmin
* @link http://syspass.org
* @author nuxsmin
* @link http://syspass.org
* @copyright 2012 Rubén Domínguez nuxsmin@syspass.org
*
* This file is part of sysPass.
@@ -32,21 +32,30 @@ defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo'
<div id="boxData">
<form method="post" name="frmLogin" id="frmLogin" action="" OnSubmit="return doLogin();">
<?php if (SP_Util::demoIsEnabled()): ?>
<input type="text" name="user" id="user" placeholder="<?php echo _('Usuario'); ?>" value=""
<input type="text" name="user" id="user" class="round5" placeholder="<?php echo _('Usuario'); ?>"
value=""
title="> demo <"/><br/>
<input type="password" name="pass" id="pass" placeholder="<?php echo _('Clave'); ?>" value=""
<input type="password" name="pass" id="pass" class="round5" placeholder="<?php echo _('Clave'); ?>"
value=""
title="> syspass <"/><br/>
<span id="smpass" style="display: none"><input type="password" name="mpass" id="mpass"
placeholder="<?php echo _('Clave Maestra'); ?>"
value="" title="> 01234567890 <"
disabled/><br/></span>
<span id="smpass" style="display: none">
<input type="password" name="mpass" id="mpass" class="round5"
placeholder="<?php echo _('Clave Maestra'); ?>"
value="" title="> 01234567890 <" disabled/>
<br/>
</span>
<?php else: ?>
<input type="text" name="user" id="user" placeholder="<?php echo _('Usuario'); ?>" value=""/><br/>
<input type="password" name="pass" id="pass" placeholder="<?php echo _('Clave'); ?>" value=""
<input type="text" name="user" id="user" class="round5" placeholder="<?php echo _('Usuario'); ?>"
value=""/><br/>
<input type="password" name="pass" id="pass" class="round5" placeholder="<?php echo _('Clave'); ?>"
value=""
autocomplete="off"/><br/>
<span id="smpass" style="display: none"><input type="password" name="mpass" id="mpass"
placeholder="<?php echo _('Clave Maestra'); ?>"
value="" autocomplete="off" disabled/><br/></span>
<span id="smpass" style="display: none">
<input type="password" name="mpass" id="mpass" class="round5"
placeholder="<?php echo _('Clave Maestra'); ?>"
value="" autocomplete="off" disabled/>
<br/>
</span>
<?php endif; ?>
<input type="image" id="btnLogin" src="imgs/login.png" title="<?php echo _('Acceder') ?>"/>
<input type="hidden" name="login" value="1"/>
@@ -79,7 +88,7 @@ defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo'
<?php endif; ?>
<?php
if ( SP_Util::demoIsEnabled() ) {
if (SP_Util::demoIsEnabled()) {
$newFeatures = array(
_('Nuevo interface de búsqueda con estilo de lista o tipo tarjeta'),
_('Selección de grupos y usuarios de acceso a cuentas'),
@@ -98,13 +107,13 @@ if ( SP_Util::demoIsEnabled() ) {
_('Mejoras de seguridad en XSS e inyección SQL')
);
echo '<div id="whatsNewIcon">';
echo '<img src="imgs/gearscolorful.png" title="' . _('Nuevas Características') . '" alt="'. _('Nuevas Características').'" onclick="$(\'#whatsNew\').show(500);"/>';
echo '<img src="imgs/gearscolorful.png" title="' . _('Nuevas Características') . '" alt="' . _('Nuevas Características') . '" onclick="$(\'#whatsNew\').show(500);"/>';
echo '<h2>' . _('Nuevas Características') . '</h2>';
echo '</div>';
echo '<div id="whatsNew" class="round5 shadow">';
echo '<ul>';
foreach ( $newFeatures as $feature) {
foreach ($newFeatures as $feature) {
echo '<li>' . $feature . '</li>';
}
echo '</ul>';