Commits

dgea...@gmail.com <  committed 3cc3682

  • Participants

Comments (0)

Files changed (158)

File GemiModule.php

+<?php
+
+/**
+ * GeMI
+ * ----
+ * Gestión de Múltiples Idiomas
+ * 
+ * Créditos:
+ * ---------
+ * Para el desarrollo de este módulo se utilizaron los siguientes recursos:
+ * - LanguageSelector (http://www.yiiframework.com/wiki/293/manage-target-language-in-multilingual-applications-a-language-selector-widget-i18n)
+ * - MissingTranslation (http://www.yiiframework.com/extension/db-missing-translations)
+ * 
+ * Instalación:
+ * ------------
+ * - Copiar la carpeta 'gemi' dentro de "modules"
+ * - Copiar el archivo BeginRequest.php a la carpeta components
+ * - Copiar EUpdateDialog de modules/extensions a protected/extensions
+ * - Editar las siguientes secciones de config/main.php:
+ *  //Forzar que el idioma original nunca exista
+ *  'sourceLanguage' => '¡¡',
+ *  'language' => 'es',
+ * 
+ *    // Asocia una clase behavior con el evento onBeginRequest.
+ *    // Ponerlo en el arreglo primario, afecta a toda la aplicación
+ *    // En este caso, lo q hace es cambiar el idioma elegido
+ *   'behaviors' => array(
+ *        'onBeginRequest' => array(
+ *            'class' => 'application.components.BeginRequest'
+ *        ),
+ *    ),
+ *
+ *  'modules'=>array(
+ *    'gemi'=>array(
+ *      //mostrarTodos: listar todos los idiomas definidos o sólo los activos
+ *      //puede usarse: true, false, o una operación lógica a evaluar:
+ *      //"Yii::app()->user->checkAccess('Admin') || Yii::app()->user->checkAccess('UsuarioAvanzado')",
+ *      'mostrarTodos'=>true,
+ *      //mostrarInactivos: listar todos los idiomas no dados de baja
+ *      // Por ejemplo, para testear las traducciones antes de activar el idioma
+ *      //puede usarse: true, false, o una operación lógica a evaluar:
+ *      //!Yii::app()->user->checkAccess('Admin')  && !Yii::app()->user->checkAccess('UsuarioAvanzado')
+ *      'mostrarInactivos'=>true,
+ *    ), 
+ * 
+ *    'messages' => array(
+ *      'class' => 'CDbMessageSource',
+ *      'sourceMessageTable' => 'tbl_msgOriginal',
+ *      'translatedMessageTable' => 'tbl_msgTraducido',
+ *      'onMissingTranslation' => array('MensajeATraducir', 'load'),
+ *    ),
+ * 
+ * Implementación:
+ * - Agregar al menú deseado estos elementos:
+ *   array('label' => 'Idiomas', 'url' => array('/gemi/idioma/index')),
+ *   array('label' => 'Traducir', 'url' => array('/gemi/msgTraducido/index')),
+ * - En la vista que se desea mostrar el selector de idiomas, por ejemplo,
+ *   protectec/views/layout/main.php
+ *     <div  id="language-selector" style="float:right; margin:5px;">
+ *      <?php 
+ *       eval(Yii::app()->getModule('gemi')->mostrarSelector);
+ *      ?>
+ *     </div>
+ *
+ * Notas:
+ * * Un idioma está ACTIVO cuando su flag idiActivo es verdadero
+ * * Un idioma está INACTIVO cuando su flag idiActivo es falso
+ * * Un idioma está VIGENTE cuando su campo idiBaja es nulo
+ * * Un idioma está NO VIGENTE cuando su flag idiBaja es un dato DATETIME
+ * * Un idioma está ACTIVO cuando su flag idiActivo es verdadero
+ * * Al momento de esta versión se usa como identificador de idioma ISO 639 (2 letras)
+ * * El campo idiCodigo acepta 5 caracteres tal como indica ISO 3166-1 alfa-2 (es-ES, es-AR, etc)
+ *
+ * 
+ * @package GeMI
+ * @author DG Esteban A. Pérez <dgeaperez@gmail.com>
+ * @version 1.0
+ * @access public
+ * 
+ */
+class GemiModule extends CWebModule {
+
+  /**
+   * Mostrar o no todos los idiomas
+   * @property bool mostrarTodos true, false o una cadena que al evaluarse devuelve true o false 
+   * Mostrar o no los idomas inactivos. Por ejemplo, para editar traducciones.
+   * @property bool mostrarInactivos true, false o una cadena que al evaluarse devuelve true o false 
+   */
+  private $_mostrarTodos = true;
+  private $_mostrarInactivos = true;
+  private $_assetsUrl;
+
+  public function init() {
+    // this method is called when the module is being created
+    // you may place code here to customize the module or the application
+    // import the module-level models and components
+    $this->setImport(array(
+        'gemi.models.*',
+        'gemi.components.*',
+    ));
+
+    Idioma::model()->verificarTabla();
+    MsgOriginal::model()->verificarTabla();
+    MsgTraducido::model()->verificarTabla();
+  }
+
+  /**
+   * @return string La URL que contiene todos los archivos de personalización del módulo.
+   */
+  public function getAssetsUrl() {
+    if ($this->_assetsUrl === null)
+      $this->_assetsUrl = Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('gemi.assets'));
+    return $this->_assetsUrl;
+  }
+
+  /**
+   * @param string La URL que contiene todos los archivos de personalización del módulo.
+   */
+  public function setAssetsUrl($value) {
+    $this->_assetsUrl = $value;
+  }
+
+
+  public function beforeControllerAction($controller, $action) {
+    if (parent::beforeControllerAction($controller, $action)) {
+      // this method is called before any module controller action is performed
+      // you may place customized code here
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public function setMostrarTodos($mostrarTodos) {
+    $this->_mostrarTodos = eval("return $mostrarTodos;");
+  }
+
+  public function getMostrarTodos() {
+    return $this->_mostrarTodos;
+  }
+
+  public function setMostrarInactivos($mostrarInactivos) {
+    $this->_mostrarInactivos = eval("return $mostrarInactivos;");
+  }
+
+  public function getMostrarInactivos() {
+    return $this->_mostrarInactivos;
+  }
+
+  /**
+   * 
+   * @return string Devuelve una cadena que debe evaluarse para mostrar el selector de Idiomas
+   */
+  public function getMostrarSelector() {
+    return Idioma::model()->mostrarSelector;
+  }
+
+  /**
+   * @return array Devuelve un arreglo con la lista COMPLETA de idiomas definidos 'es'=>'Español'
+   */
+  public function getListarIdiomasCompleto() {
+    return Idioma::model()->listarIdiomas;
+  }
+
+  /**
+   * 
+   * @return array Devuelve un arreglo con el listado de idiomas activos 'es'=>'Español'
+   */
+  public function getListarIdiomasActivos() {
+    return Idioma::model()->getListarIdiomas(Idioma::ACTIVOS);
+  }
+
+  /**
+   * 
+   * @return array Devuelve un arreglo con el listado de idiomas vigentes 'es'=>'Español'
+   */
+  public function getListarIdiomasInActivos() {
+    return Idioma::model()->getListarIdiomas(Idioma::INACTIVOS);
+  }
+
+  /**
+   * 
+   * @return array Devuelve un arreglo con el listado de idiomas no vigentes 'es'=>'Español'
+   */
+  public function getListarIdiomasVigentes() {
+    return Idioma::model()->getListarIdiomas(Idioma::VIGENTES);
+  }
+
+  /**
+   * 
+   * @return array Devuelve un arreglo con el listado de idiomas inactivos 'es'=>'Español'
+   */
+  public function getListarIdiomasNoVigentes() {
+    return Idioma::model()->getListarIdiomas(Idioma::NO_VIGENTES);
+  }
+
+  /**
+   * @param string $sSQL cadena se adicionará al "WHERE". 
+   * Si tuviera un error o no se indica devolverá lo mismo que ListarIdiomasCompleto
+   * @return array Devuelve un arreglo con el listado de idiomas inactivos 'es'=>'Español'
+   * @example $aIdiomasExcepto_es=$gemi->ListarIdiomasPersonalizado("idiCodigo != 'es' and idiBaja IS NULL");
+   */
+  public function ListarIdiomasPersonalizado($sSQL) {
+    return Idioma::model()->getListarIdiomas(Idioma::NO_VIGENTES);
+  }
+
+  /**
+   * @return string Devuelve el idioma por defecto a utilizar.
+   * Se espera que indique idioma del navegador si está definido, activo y no dado de baja.
+   * Caso contrario, devolverá el idioma declarado en sourceLanguage de Yii. 
+   * Si se siguieron las instrucciones de GeMI, devolverá '!!'.
+   */
+  public function getIdiomaPorDefecto() {
+    return Idioma::model()->idiomaPorDefecto;
+  }
+
+  /**
+   * 
+   * @param  string $lng El idioma a saber si está disponible o no.
+   * @return bool true o false si el idioma está disponible o no. 
+   * null si no se $lang no se indica.
+   */
+  public function IdiomaEstaDisponible($lng = null) {
+    if ($lng === null)
+      return null;
+    return Idioma::model()->IdiomaEstaDisponible($lng);
+  }
+
+}

File assets/gridview/bg.gif

Added
New image

File assets/gridview/delete.png

Added
New image

File assets/gridview/down.gif

Added
New image

File assets/gridview/loading.gif

Added
New image

File assets/gridview/styles.css

+.grid-view-loading
+{
+	background:url(loading.gif) no-repeat;
+}
+
+.grid-view
+{
+	padding: 15px 0;
+}
+
+.grid-view table.items
+{
+	background: white;
+	border-collapse: collapse;
+	width: 100%;
+	border: 1px #D0E3EF solid;
+}
+
+.grid-view table.items th, .grid-view table.items td
+{
+	font-size: 0.9em;
+	border: 1px white solid;
+	padding: 0.3em;
+}
+
+.grid-view table.items th
+{
+	color: white;
+	background: url("bg.gif") repeat-x scroll left top white;
+	text-align: center;
+}
+
+.grid-view table.items th a
+{
+	color: #EEE;
+	font-weight: bold;
+	text-decoration: none;
+}
+
+.grid-view table.items th a:hover
+{
+	color: #FFF;
+}
+
+.grid-view table.items th a.asc
+{
+	background:url(up.gif) right center no-repeat;
+	padding-right: 10px;
+}
+
+.grid-view table.items th a.desc
+{
+	background:url(down.gif) right center no-repeat;
+	padding-right: 10px;
+}
+
+.grid-view table.items tr.even
+{
+	background: #F8F8F8;
+}
+
+.grid-view table.items tr.odd
+{
+	background: #E5F1F4;
+}
+
+.grid-view table.items tr.even_del
+{
+	background: #f8e2e2;
+}
+
+.grid-view table.items tr.odd_del
+{
+	background: #e5c8f4;
+}
+
+.grid-view table.items tr.selected
+{
+	background: #BCE774;
+}
+
+.grid-view table.items tr:hover
+{
+	background: #ECFBD4;
+}
+
+.grid-view .link-column img
+{
+	border: 0;
+}
+
+.grid-view .button-column
+{
+	text-align: center;
+	width: 60px;
+}
+
+.grid-view .button-column img
+{
+	border: 0;
+}
+
+.grid-view .checkbox-column
+{
+	width: 15px;
+}
+
+.grid-view .summary
+{
+	margin: 0 0 5px 0;
+	text-align: right;
+}
+
+.grid-view .pager
+{
+	margin: 5px 0 0 0;
+	text-align: right;
+}
+
+.grid-view .empty
+{
+	font-style: italic;
+}
+
+.grid-view .filters input,
+.grid-view .filters select
+{
+	width: 100%;
+	border: 1px solid #ccc;
+}

File assets/gridview/undelete.png

Added
New image

File assets/gridview/up.gif

Added
New image

File assets/gridview/update.png

Added
New image

File assets/gridview/view.png

Added
New image

File components/BeginRequest.php

+<?php
+
+/**
+ * Description of BeginRequest
+ *
+ * @author esteban
+ */
+class BeginRequest extends CBehavior {
+
+  // The attachEventHandler() mathod attaches an event handler to an event. 
+  // So: onBeginRequest, the handleBeginRequest() method will be called.
+  public function attach($owner) {
+    $owner->attachEventHandler('onBeginRequest', array($this, 'handleBeginRequest'));
+  }
+
+  public function handleBeginRequest($event) {
+    $app = Yii::app();
+    $user = $app->user;
+    $gemi = Yii::app()->getModule('gemi');
+
+    if (isset($_POST['_idioma']))
+      $lng = $_POST['_idioma'];
+    else if ($app->user->hasState('_idioma'))
+      $lng = $app->user->getState('_idioma');
+    else if (isset(Yii::app()->request->cookies['_idioma']))
+      $lng = Yii::app()->request->cookies['_idioma']->value;
+    else
+      $lng = Yii::app()->language;
+
+    if (!$gemi->IdiomaEstaDisponible($lng) && 
+        !$gemi->mostrarInactivos)
+      $lng = $gemi->IdiomaEstaDisponible('en') ? 'en' : $gemi->idiomaPorDefecto;
+
+    if (isset($_POST['_idioma'])) {
+      $app->user->setState('_idioma', $lng);
+      $cookie = new CHttpCookie('_idioma', $lng);
+      $cookie->expire = time() + (60 * 60 * 24 * 365); // (1 year)
+      Yii::app()->request->cookies['_idioma'] = $cookie;
+    }
+    $app->language = $lng;
+  }
+
+}
+
+?>

File components/MensajeATraducir.php

+<?php
+
+/**
+ * Maneja el evento onMissingTranslation
+ */
+class MensajeATraducir extends CApplicationComponent {
+
+  /**
+   * Agrega los textos explícitos del código para ser traducidos o modificados
+   * por el usuario.
+   * La lógica es la siguiente:
+   * - Se indica a Yii que el idioma original es "!!" que no existe
+   * - Conforme va ejecutando Yii::t(), verifica si existe esa cadena
+   * - Si no existe, la almacena y luego se podrá editar vía "Traducir Mensajes"
+   */
+  public function load($event) {
+    // Carga los mensajes		
+    $original = MsgOriginal::model()->find('message=:mensaje AND category=:categoria', array(':mensaje' => $event->message, ':categoria' => $event->category));
+
+    // Si no encuentra el mensaje, existe su categoría (que ahora es única)
+    if (!$original){
+      $original = MsgOriginal::model()->find('category=:categoria', array(':categoria' => $event->category));
+      if (($original instanceof MsgOriginal) &&
+           $original->message != $event->message){
+        //El mensaje cambió en el código, y hay que actualizarlo.
+        $original->message = $event->message;
+        $original->setScenario('update');
+        $original->save();
+      }
+    }
+    // Si aún no se encuentra el mensaje
+    if (!$original) {
+      // Lo agregamos
+      $original = new MsgOriginal;
+
+      $original->category = $event->category;
+      $original->message = $event->message;
+      $original->save();
+
+      $lastID = Yii::app()->db->lastInsertID;
+    }
+
+    if ($event->language != Yii::app()->sourceLanguage) {
+      // Hacemos lo mismo con las traducciones	
+      $traduccion = MsgTraducido::model()->find('language=:idioma AND id=:id', 
+                                               array(':idioma' => $event->language, 
+                                                     ':id' => $original->id)
+                                               );
+
+      // Si no la encontramos, la guardamos
+      if (!$traduccion) {
+        $original = MsgOriginal::model()->find('category=:categoria', array(':categoria' => $event->category));
+
+        // Agregando...
+        $traduccion = new MsgTraducido;
+
+        $traduccion->id = $original->id;
+        $traduccion->language = $event->language;
+        $traduccion->translation = $event->message;
+        $traduccion->save();
+      }
+    }
+  }
+
+}

File components/widgets/SelectorIdioma.php

+<?php
+/**
+ * Description of LanguageSelector
+ *
+ * @author esteban
+ */
+class SelectorIdioma extends CWidget
+{
+    public function run()
+    {
+        $idiomaActual = Yii::app()->language;
+        $gemi = Yii::app()->getModule('gemi');
+        //$mostrar=eval("return $gemi->mostrarTodos;");
+        $idiomas = $gemi->mostrarTodos ?
+                     $gemi->listarIdiomasVigentes:
+                     $gemi->listarIdiomasActivos;
+        if (is_array($idiomas) && count($idiomas) > 1)
+          $this->render('selectorIdioma', array('idiomaActual' => $idiomaActual, 'idiomas'=>$idiomas));
+    }
+}
+?>

File components/widgets/views/selectorIdioma.php

+<?php echo CHtml::form(); ?>
+    <div id="idioma-select">
+        <?php 
+            echo CHtml::dropDownList('_idioma', $idiomaActual, $idiomas,
+                array(
+                    'submit' => '',
+                    'csrf'=>true,
+                )
+            ); 
+        ?>
+    </div>
+<?php echo CHtml::endForm(); ?>

File controllers/DefaultController.php

+<?php
+
+class DefaultController extends Controller
+{
+	public function actionIndex()
+	{
+		$this->render('index');
+	}
+}

File controllers/IdiomaController.php

+<?php
+
+class IdiomaController extends Controller {
+
+  /**
+   * @var string the default layout for the views. Defaults to '//layouts/column2', meaning
+   * using two-column layout. See 'protected/views/layouts/column2.php'.
+   */
+  public $layout = '//layouts/column2';
+  /**
+   * @var CActiveRecord the currently loaded data model instance.
+   */
+  private $_model;
+
+  /**
+   * @return array action filters
+   */
+  public function filters() {
+    return array(
+        'accessControl', // perform access control for CRUD operations
+    );
+  }
+
+  /**
+   * Specifies the access control rules.
+   * This method is used by the 'accessControl' filter.
+   * @return array access control rules
+   */
+  public function accessRules() {
+    return array(
+        array('allow', // allow all users to perform 'index' and 'view' actions
+            'actions' => array('index', 'view'),
+            'users' => array('*'),
+        ),
+        array('allow', // allow authenticated user to perform 'create' and 'update' actions
+            'actions' => array('create', 'update'),
+            'users' => array('@'),
+        ),
+        array('allow', // allow admin user to perform 'admin' and 'delete' actions
+            'actions' => array('admin', 'delete', 'undelete'),
+            'users' => array('admin'),
+        ),
+        array('deny', // deny all users
+            'users' => array('*'),
+        ),
+    );
+  }
+
+  /**
+   * Displays a particular model.
+   */
+  public function actionView() {
+    $this->render('view', array(
+        'model' => $this->loadModel(),
+    ));
+  }
+
+  /**
+   * Creates a new model.
+   * If creation is successful, the browser will be redirected to the 'view' page.
+   */
+  public function actionCreate() {
+    $model = new Idioma;
+    $model->setAttributes($model->getAttributes(), false);
+    // Uncomment the following line if AJAX validation is needed
+    $this->performAjaxValidation($model);
+
+    if (isset($_POST['Idioma'])) {
+      $model->attributes = $_POST['Idioma'];
+      $model->idiCodigo = strtolower($model->idiCodigo);
+      if ($model->save())
+        $this->redirect(array('index'));
+    }
+
+    $this->render('create', array(
+        'model' => $model,
+    ));
+  }
+
+  /**
+   * Updates a particular model.
+   * If update is successful, the browser will be redirected to the 'view' page.
+   */
+  public function actionUpdate() {
+    $model = $this->loadModel();
+
+    // Uncomment the following line if AJAX validation is needed
+    $this->performAjaxValidation($model);
+
+    if (isset($_POST['Idioma'])) {
+      $model->attributes = $_POST['Idioma'];
+      $model->idiCodigo = strtolower($model->idiCodigo);
+      if ($model->save())
+        $this->redirect(array('index'));
+    }
+
+    $this->render('update', array(
+        'model' => $model,
+    ));
+  }
+
+  /**
+   * Deletes a particular model.
+   * If deletion is successful, the browser will be redirected to the 'index' page.
+   */
+  public function actionDeleteOriginal() {
+    if (Yii::app()->request->isPostRequest) {
+      // we only allow deletion via POST request
+      $this->loadModel()->delete();
+
+      // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
+      if (!isset($_GET['ajax']))
+        $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));
+    }
+    else
+      throw new CHttpException(400, Yii::t('err_CCONTROLLER_400', 'Solicitud Incorrecta. Por favor no la repita.'));
+  }
+
+  /**
+   * Deletes a particular model.
+   * If deletion is successful, the browser will be redirected to the 'index' page.
+   */
+  public function actionDelete() {
+    if (Yii::app()->request->isPostRequest) {
+      // we only allow deletion via POST request
+      $model = $this->loadModel();
+      $model->idiBaja = new CDbExpression('NOW()');
+      $model->save();
+
+      // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
+      if (!isset($_GET['ajax']))
+        $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('index'));
+    }
+    else
+      throw new CHttpException(400, Yii::t('err_CCONTROLLER_400', 'Solicitud Incorrecta. Por favor no la repita.'));
+  }
+
+  public function actionUndelete() {
+    if (Yii::app()->request->isPostRequest) {
+      // we only allow deletion via POST request
+      $model = $this->loadModel();
+      $model->idiBaja = new CDbExpression('NULL');
+      $model->save();
+
+      // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
+      if (!isset($_GET['ajax']))
+        $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('index'));
+    }
+    else
+      throw new CHttpException(400, Yii::t('err_CCONTROLLER_400', 'Solicitud Incorrecta. Por favor no la repita.'));
+  }
+
+  /**
+   * Lists all models.
+   */
+  public function actionIndex() {/*
+    $dataProvider = new CActiveDataProvider('Idioma');
+    $this->render('index', array(
+        'dataProvider' => $dataProvider,
+    )); */
+    $model = new Idioma('search');
+    $model->unsetAttributes();  // clear any default values
+    if (isset($_GET['Idioma']))
+      $model->attributes = $_GET['Idioma'];
+
+    $this->render('index', array(
+        'model' => $model,
+    ));
+  }
+
+  /**
+   * Manages all models.
+   */
+  public function actionAdmin() {
+    $model = new Idioma('search');
+    $model->unsetAttributes();  // clear any default values
+    if (isset($_GET['Idioma']))
+      $model->attributes = $_GET['Idioma'];
+
+    $this->render('admin', array(
+        'model' => $model,
+    ));
+  }
+
+  /**
+   * Returns the data model based on the primary key given in the GET variable.
+   * If the data model is not found, an HTTP exception will be raised.
+   */
+  public function loadModel() {
+    if ($this->_model === null) {
+      if (isset($_GET['id']))
+        $this->_model = Idioma::model()->findbyPk($_GET['id']);
+      if ($this->_model === null)
+        throw new CHttpException(404, Yii::t('err_CCONTROLLER_404', 'La página solicitada no existe.'));
+    }
+    return $this->_model;
+  }
+
+  /**
+   * Performs the AJAX validation.
+   * @param CModel the model to be validated
+   */
+  protected function performAjaxValidation($model) {
+    if (isset($_POST['ajax']) && $_POST['ajax'] === 'idioma-form') {
+      echo CActiveForm::validate($model);
+      Yii::app()->end();
+    }
+  }
+
+}

File controllers/MsgTraducidoController.php

+<?php
+
+class MsgTraducidoController extends Controller
+{
+	/**
+	 * @var string the default layout for the views. Defaults to '//layouts/column2', meaning
+	 * using two-column layout. See 'protected/views/layouts/column2.php'.
+	 */
+	public $layout='//layouts/column1';
+
+	/**
+	 * @return array action filters
+	 */
+	public function filters()
+	{
+		return array(
+			'accessControl', // perform access control for CRUD operations
+		);
+	}
+
+	/**
+	 * Specifies the access control rules.
+	 * This method is used by the 'accessControl' filter.
+	 * @return array access control rules
+	 */
+	public function accessRules()
+	{
+		return array(
+			array('allow',  // allow all users to perform 'index' and 'view' actions
+				'actions'=>array('index','view'),
+				'users'=>array('*'),
+			),
+			array('allow', // allow authenticated user to perform 'create' and 'update' actions
+				'actions'=>array('create','update'),
+				'users'=>array('@'),
+			),
+			array('allow', // allow admin user to perform 'admin' and 'delete' actions
+				'actions'=>array('admin','delete'),
+				'users'=>array('admin'),
+			),
+			array('deny',  // deny all users
+				'users'=>array('*'),
+			),
+		);
+	}
+
+	/**
+	 * Displays a particular model.
+	 * @param integer $id the ID of the model to be displayed
+	 */
+	public function actionView($id)
+	{
+		$this->render('view',array(
+			'model'=>$this->loadModel($id),
+		));
+	}
+
+	/**
+	 * Creates a new model.
+	 * If creation is successful, the browser will be redirected to the 'view' page.
+	 */
+	public function actionCreate()
+	{
+		$model=new MsgTraducido;
+
+		// Uncomment the following line if AJAX validation is needed
+		// $this->performAjaxValidation($model);
+
+		if(isset($_POST['MsgTraducido']))
+		{
+			$model->attributes=$_POST['MsgTraducido'];
+			if($model->save())
+				$this->redirect(array('view','id'=>$model->id));
+		}
+
+		$this->render('create',array(
+			'model'=>$model,
+		));
+	}
+
+	/**
+	 * Updates a particular model.
+	 * If update is successful, the browser will be redirected to the 'view' page.
+	 * @param integer $id the ID of the model to be updated
+	 */
+  /* Original
+	public function actionUpdate($id)
+	{
+		$model=$this->loadModel($id);
+
+		// Uncomment the following line if AJAX validation is needed
+		// $this->performAjaxValidation($model);
+
+		if(isset($_POST['MsgTraducido']))
+		{
+			$model->attributes=$_POST['MsgTraducido'];
+			if($model->save())
+				$this->redirect(array('view','id'=>$model->id));
+		}
+
+		$this->render('update',array(
+			'model'=>$model,
+		));
+	}
+  */
+  /* Para eupdatedialog*/
+  public function actionUpdate($id, $lng)
+{
+  $model = $this->loadModel($id, $lng);
+  $msgOriginal = MsgOriginal::model()->findByPk($model->id);
+  if( isset( $_POST['MsgTraducido'] ) )
+  {
+    $model->attributes = $_POST['MsgTraducido'];
+    if( $model->save() )
+    {
+      if( Yii::app()->request->isAjaxRequest )
+      {
+        // Stop jQuery from re-initialization
+        Yii::app()->clientScript->scriptMap['jquery.js'] = false;
+ 
+        echo CJSON::encode( array(
+          'status' => 'success',
+          'content' => Yii::t('int_ACTMENSAJE','Se actualizó el mensaje'),
+        ));
+        exit;
+      }
+      else
+        $this->redirect( array( 'admin') );
+    }
+  }
+ 
+  if( Yii::app()->request->isAjaxRequest )
+  {
+    // Stop jQuery from re-initialization
+    Yii::app()->clientScript->scriptMap['jquery.js'] = false;
+ 
+    echo CJSON::encode( array(
+      'status' => 'failure',
+      'content' => $this->renderPartial( '_form', array(
+        'model' => $model, 'txtOriginal' => $msgOriginal->message), true, true ),
+    ));
+    exit;
+  }
+  else
+    $this->render( 'update', array( 'model' => $model ) );
+}
+	/**
+	 * Deletes a particular model.
+	 * If deletion is successful, the browser will be redirected to the 'admin' page.
+	 * @param integer $id the ID of the model to be deleted
+	 */
+	public function actionDelete($id)
+	{
+		if(Yii::app()->request->isPostRequest)
+		{
+			// we only allow deletion via POST request
+			$this->loadModel($id)->delete();
+
+			// if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
+			if(!isset($_GET['ajax']))
+				$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));
+		}
+		else
+			throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
+	}
+
+	/**
+	 * Manages all models.
+	 */
+	public function actionIndex()
+	{
+		$model=new MsgTraducido('search');
+		$model->unsetAttributes();  // clear any default values
+    $gemi = Yii::app()->getModule('gemi');
+		if(isset($_GET['MsgTraducido']))
+			$model->attributes=$_GET['MsgTraducido'];
+
+		$this->render('index',array(
+			'model'=>$model,
+      'gemi' =>$gemi,
+		));
+	}
+
+	/**
+	 * Returns the data model based on the primary key given in the GET variable.
+	 * If the data model is not found, an HTTP exception will be raised.
+	 * @param integer the ID of the model to be loaded
+	 */
+	public function loadModel($id, $lng)
+	{
+		$model=MsgTraducido::model()->findByAttributes(array('id'=>$id, 'language'=>$lng));
+		if($model===null)
+			throw new CHttpException(404,'The requested page does not exist.');
+		return $model;
+	}
+
+	/**
+	 * Performs the AJAX validation.
+	 * @param CModel the model to be validated
+	 */
+	protected function performAjaxValidation($model)
+	{
+		if(isset($_POST['ajax']) && $_POST['ajax']==='msg-traducido-form')
+		{
+			echo CActiveForm::validate($model);
+			Yii::app()->end();
+		}
+	}
+}

File extensions/EUpdateDialog/EUpdateDialog.php

+<?php
+/**
+ * EUpdateDialog class file.
+ * 
+ * @author Andrius Marcinkevicius <andrew.web@ifdattic.com>
+ * @copyright Copyright &copy; 2011 Andrius Marcinkevicius
+ * @license Licensed under MIT license. http://ifdattic.com/MIT-license.txt
+ * @version 1.2
+ */
+
+/**
+ * EUpdateDialog allows to create/update/delete model entry from JUI Dialog.
+ * 
+ * @author Andrius Marcinkevicius <andrew.web@ifdattic.com>
+ */
+class EUpdateDialog extends CWidget
+{
+  /**
+   * @var int the height of the dialog.
+   */
+  public $height = 'auto';
+  
+  /**
+   * @var bool if set to true, the dialog will be resizable.
+   */
+  public $resizable = false;
+  
+  /**
+   * @var string the title of the dialog.
+   */
+  public $title = 'Dialog';
+  
+  /**
+   * @var int the width of the dialog.
+   */
+  public $width = 500;
+  
+  /**
+   * Add the update dialog to current page.
+   */
+  public function run()
+  {
+    $this->beginWidget( 'zii.widgets.jui.CJuiDialog', array(
+      'id' => 'update-dialog',
+      'options' => array(
+        'autoOpen' => false,
+        'height' => $this->height,
+        'modal' => true,
+        'resizable' => $this->resizable,
+        'title' => $this->title,
+        'width' => $this->width,
+        
+      ),
+    )); ?>
+    <div class="update-dialog-content"></div>
+    <?php $this->endWidget();
+    
+    // Publish extension assets
+    $assets = Yii::app()->getAssetManager()->publish( Yii::getPathOfAlias(
+      'ext.EUpdateDialog' ) . '/assets' );
+    $cs = Yii::app()->getClientScript();
+    $cs->registerScriptFile( $assets . '/EUpdateDialog.js' );
+    
+    // Add cookie script and csrf cookie if using CSRF validation.
+    if( Yii::app()->request->enableCsrfValidation )
+    {
+      $cs->registerScriptFile( $assets . '/jquery.cookie.js' );
+      Yii::app()->request->getCsrfToken();
+    }
+  }
+}
+?>

File extensions/EUpdateDialog/assets/EUpdateDialog.js

+/*!
+ * EUpdateDialog allows to create/update/delete model entry from JUI Dialog.
+ * 
+ * Copyright 2011, Andrius Marcinkevicius
+ * Licensed under MIT license. http://ifdattic.com/MIT-license.txt
+ */
+
+/**
+ * Close and clean contents of update dialog.
+ */
+function closeUpdateDialog(){
+  $( '#update-dialog' ).dialog( 'close' ).children( ':eq(0)' ).empty();
+}
+
+/**
+ * Update contents of update dialog.
+ * @param url mixed url of the page to display, or false when submitting form.
+ * @param act mixed name of clicked button value [important for delete] 
+ */
+function updateDialog( url, act ){
+  // Set action
+  var action = '';
+  
+  // Get form contained inside update dialog
+  var form = $( '#update-dialog div.update-dialog-content form' );
+  
+  // Set CSRF token if CSRF validation is enabled. 
+  var csrfToken = '';
+  if( jQuery.cookie )
+    csrfToken = '&YII_CSRF_TOKEN=' + $.cookie( 'YII_CSRF_TOKEN' );
+    
+  // When submitting form set variables to ajax call
+  if( url === false )
+  {
+    action = '&action=' + act;
+    url = form.attr( 'action' );
+  }
+  
+  // Make ajax call
+  $.ajax({
+    'url': url,
+    'data': form.serialize() + action + csrfToken,
+    'type': 'post',
+    'dataType': 'json',
+    'success': function( data ){
+      if( data.status == 'failure' )
+      {
+        $( '#update-dialog div.update-dialog-content' ).html( data.content );
+        $( '#update-dialog div.update-dialog-content form input[type=submit]' )
+          .die() // Stop from re-binding event handlers
+          .live( 'click', function( e ){ // Send clicked button value
+            e.preventDefault();
+            updateDialog( false, $( this ).attr( 'name' ) );
+        });
+      }
+      else
+      {
+        $( '#update-dialog div.update-dialog-content' ).html( data.content );
+        if( data.status == 'success' ) // Update all grid views on success
+        {
+          $( 'div.grid-view' ).each( function(){
+            $.fn.yiiGridView.update( $( this ).attr( 'id' ) );
+          });
+        }
+        setTimeout( closeUpdateDialog, 1000 );
+      }
+    },
+    'cache': false
+  });
+}
+
+/**
+ * Open update dialog.
+ * @param url string url of the page to display.
+ * @param dialogTitle string the title of the dialog.
+ */
+function updateDialogActionBase( url, dialogTitle ){
+  // Clean the contents, just in case there is something left
+  $( '#update-dialog' ).children( ':eq(0)' ).empty();
+  
+  // Add content to update dialog
+  updateDialog( url );
+  
+  // Open the dialog
+  $( '#update-dialog' )
+    //.dialog( { title: dialogTitle } )
+    .dialog( 'open' );
+}
+
+/**
+ * Open dialog for delete action.
+ * @param e Event
+ */
+function updateDialogDelete( e ){
+  e.preventDefault();
+  updateDialogActionBase( $( this ).attr( 'href' ), 'Delete confirmation' );
+}
+
+/**
+ * Open dialog for update action.
+ * @param e Event
+ */
+function updateDialogUpdate( e ){
+  e.preventDefault();
+  updateDialogActionBase( $( this ).attr( 'href' ), 'Update' );
+}
+
+/**
+ * Open dialog for create action.
+ * @param e Event
+ */
+function updateDialogCreate( e ){
+  e.preventDefault();
+  updateDialogActionBase( $( this ).attr( 'href' ), 'Create' );
+}
+
+jQuery( function($){
+  $( 'a.update-dialog-create' ).bind( 'click', updateDialogCreate );
+});

File extensions/EUpdateDialog/assets/jquery.cookie.js

+/**
+ * jQuery Cookie plugin
+ *
+ * Copyright (c) 2010 Klaus Hartl (stilbuero.de)
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+jQuery.cookie = function (key, value, options) {
+
+    // key and at least value given, set cookie...
+    if (arguments.length > 1 && String(value) !== "[object Object]") {
+        options = jQuery.extend({}, options);
+
+        if (value === null || value === undefined) {
+            options.expires = -1;
+        }
+
+        if (typeof options.expires === 'number') {
+            var days = options.expires, t = options.expires = new Date();
+            t.setDate(t.getDate() + days);
+        }
+
+        value = String(value);
+
+        return (document.cookie = [
+            encodeURIComponent(key), '=',
+            options.raw ? value : encodeURIComponent(value),
+            options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+            options.path ? '; path=' + options.path : '',
+            options.domain ? '; domain=' + options.domain : '',
+            options.secure ? '; secure' : ''
+        ].join(''));
+    }
+
+    // key and possibly options given, get cookie...
+    options = value || {};
+    var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
+    return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
+};

File extensions/emenu/EMenu.php

+<?php
+
+ Yii::import('zii.widgets.CMenu');
+ 
+class EMenu extends CMenu
+{
+    // to make menu vertical
+    public $vertical = false; 
+    // to make menu right to left vertical, just will be considered if $vertical set to true
+    public $rtl = false; 
+    // to make menu upward
+    public $upward = false;
+
+    public $firstItemCssClass = 'first';
+    public $lastItemCssClass = 'last';
+    public $dirCssClass = 'dir';
+    
+    //to use a provided theme
+    public $theme = 'default';
+    
+    //to use a personal theme. if set, $theme will be ignored
+    public $themeCssFile = '';
+
+    public function init()
+    {
+        $class=array('dropdown');
+        $cssFile;
+        if($this->vertical){
+            $class[] = 'dropdown-vertical';
+            if($this->rtl){
+                $class[] = 'dropdown-vertical-rtl';
+                $cssFile = 'dropdown.vertical.rtl.css';
+            }
+            else{
+                $cssFile = 'dropdown.vertical.css';
+            }
+        }
+        else if($this->upward){
+            $class[] = 'dropdown-upward';
+            $cssFile = 'dropdown.upward.css';
+        }
+        else{
+            $class[] = 'dropdown-horizontal';
+            $cssFile = 'dropdown.css';
+        }
+        
+        $this->htmlOptions['class']=implode(' ',$class);
+                        
+        $basedir = dirname(__FILE__). '/free-css-drop-down-menu';
+        $baseUrl = Yii::app()->getAssetManager()->publish($basedir);
+
+        if($this->themeCssFile == ''){
+            switch ($this->theme) {
+                case 'adobe':
+                    $this->themeCssFile = 'adobe.com/default.css';
+                    break;
+                case 'flikr':
+                    $this->themeCssFile = 'flikr.com/default.css';
+                    break;
+                case 'lwis':
+                    $this->themeCssFile = 'lwis.celebrity/default.css';
+                    break;
+                case 'mtv':
+                    $this->themeCssFile = 'mtv.com/default.css';
+                    break;
+                case 'nvidia':
+                    $this->themeCssFile = 'nvidia.com/default.css';
+                    break;
+                case 'vimeo':
+                    $this->themeCssFile = 'vimeo.com/default.css';
+                    break;
+                case 'default':
+                default:
+                    $this->themeCssFile = 'default/default.css';
+                    break;
+            }
+        }
+
+        Yii::app()->getClientScript()->registerCSSFile($baseUrl.'/css/dropdown/'.$cssFile)
+                                    ->registerCSSFile($this->themeCssFile);
+
+          //ToDo: these should added just for IE7, i don't know how to do this
+//            Yii::app()->getClientScript()->registerCoreScript('jquery')
+//                                            ->registerScriptFile($baseUrl.'/js/jquery.dropdown.js');
+        parent::init();
+    }
+
+    /**
+     * Recursively renders the menu items.
+     * @param array $items the menu items to be rendered recursively
+     */
+    protected function renderMenuRecursive($items)
+    {
+        $count=0;
+        $n=count($items);
+        foreach($items as $item)
+        {
+            if($item == array())
+                continue;
+            $count++;
+            $options=isset($item['itemOptions']) ? $item['itemOptions'] : array();
+            $class=array();
+//            if($item['active'] && $this->activeCssClass!='')
+//                    $class[]=$this->activeCssClass;
+            if($count===1 && $this->firstItemCssClass!='')
+                $class[]=$this->firstItemCssClass;
+            if($count===$n && $this->lastItemCssClass!='')
+                $class[]=$this->lastItemCssClass;
+            if($class!==array())
+            {
+                if(empty($options['class']))
+                    $options['class']=implode(' ',$class);
+                else
+                    $options['class'].=' '.implode(' ',$class);
+            }
+
+            if(isset($item['items']) && count($item['items']))
+                if(empty($options['class']))
+                    $options['class']=' '.$this->dirCssClass;
+                else
+                    $options['class'].=' '.$this->dirCssClass;
+
+            echo CHtml::openTag('li', $options);
+
+            $menu=$this->renderMenuItem($item);
+            if(isset($this->itemTemplate) || isset($item['template']))
+            {
+                $template=isset($item['template']) ? $item['template'] : $this->itemTemplate;
+                echo strtr($template,array('{menu}'=>$menu));
+            }
+            else
+                echo $menu;
+
+            if(isset($item['items']) && count($item['items']))
+            {
+                echo "\n".CHtml::openTag('ul',isset($item['submenuOptions']) ? $item['submenuOptions'] : $this->submenuHtmlOptions)."\n";
+                $this->renderMenuRecursive($item['items']);
+                echo CHtml::closeTag('ul')."\n";
+            }
+
+            echo CHtml::closeTag('li')."\n";
+        }
+    }
+}

File extensions/emenu/changelog.txt

+1.0
+
+initial version

File extensions/emenu/free-css-drop-down-menu/GPL-LICENSE.txt

+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.