Skip to content

Commit

Permalink
Dev: Assets hash solution (#1057)
Browse files Browse the repository at this point in the history
Fixed issue #13606 : Unable to reset manually single asset
Dev: hash function use for whole path, then global var set here
Dev: generatePath is used path by path

Fixed issue #13497 : Editor screen can be loaded without css after save
Dev: moving global reset to only this template
Dev: less time to generate assets

Fixed issue #13596: template assets is not resetted when update config
Dev: need to do it if user add a file in TemplateConfiguration_files_*
Dev: do it only for global theme (?)

Dev: move injectglobalsettings to LSYii_Application
Dev: maybe create a now function to set all config ?

Dev: Fix installmer and move all set configs part to one function

Dev: usage of DB for asset version number
Dev: hash solution for indexed pk in DB

Dev: oups on phpdoc
Dev: same other improvment than other pull request

Dev: broken update … remove test line ;)
  • Loading branch information
Shnoulle authored and LouisGac committed Jun 4, 2018
1 parent ac5c685 commit 0d99224
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 69 deletions.
2 changes: 1 addition & 1 deletion application/config/version.php
Expand Up @@ -13,7 +13,7 @@


$config['versionnumber'] = '3.9.0';
$config['dbversionnumber'] = 349;
$config['dbversionnumber'] = 350;
$config['buildnumber'] = '';
$config['updatable'] = true;
$config['assetsversionnumber'] = '30036';
Expand Down
3 changes: 2 additions & 1 deletion application/controllers/admin/themeoptions.php
Expand Up @@ -80,11 +80,12 @@ public function update($id)
{
if (Permission::model()->hasGlobalPermission('templates', 'update')) {
$model = $this->loadModel($id);

if (isset($_POST['TemplateConfiguration'])) {
$model->attributes = $_POST['TemplateConfiguration'];
if ($model->save()) {
Yii::app()->user->setFlash('success', gT('Theme options saved.'));
/* If one TemplateConfiguration_files_* updated, we need to update asset */
Template::model()->findByPk($model->template_name)->resetAsset();
$this->getController()->redirect(array('admin/themeoptions/sa/update/id/'.$model->id));
}
}
Expand Down
3 changes: 2 additions & 1 deletion application/controllers/admin/themes.php
Expand Up @@ -698,7 +698,8 @@ public function templatesavechanges()

// If the file is an asset file, we refresh asset number
if (in_array($relativePathEditfile, $cssfiles) || in_array($relativePathEditfile, $jsfiles)){
SettingGlobal::increaseCustomAssetsversionnumber();
//SettingGlobal::increaseCustomAssetsversionnumber();
Template::model()->findByPk($sTemplateName)->resetAsset();
}

fclose($handle);
Expand Down
63 changes: 47 additions & 16 deletions application/core/LSYii_Application.php
Expand Up @@ -75,29 +75,19 @@ public function __construct($aApplicationConfig = null)
parent::__construct($aApplicationConfig);

/* Because we have app now : we have to call again the config (usage of Yii::app() for publicurl) */
$coreConfig = require(__DIR__.'/../config/config-defaults.php');
$emailConfig = require(__DIR__.'/../config/email.php');
$versionConfig = require(__DIR__.'/../config/version.php');
$updaterVersionConfig = require(__DIR__.'/../config/updater_version.php');
$lsConfig = array_merge($coreConfig, $emailConfig, $versionConfig, $updaterVersionConfig);
if (file_exists(__DIR__.'/../config/config.php')) {
$userConfigs = require(__DIR__.'/../config/config.php');
if (is_array($userConfigs['config'])) {
$lsConfig = array_merge($lsConfig, $userConfigs['config']);
}
}
$this->setConfigs();

/* Update asset manager path and url only if not directly set in aApplicationConfig (from config.php),
* must do after reloading to have valid publicurl (the tempurl) */
if (!isset($aApplicationConfig['components']['assetManager']['baseUrl'])) {
App()->getAssetManager()->setBaseUrl($lsConfig['tempurl'].'/assets');
App()->getAssetManager()->setBaseUrl($this->config['tempurl'].'/assets');
}
if (!isset($aApplicationConfig['components']['assetManager']['basePath'])) {
App()->getAssetManager()->setBasePath($lsConfig['tempdir'].'/assets');
App()->getAssetManager()->setBasePath($this->config['tempdir'].'/assets');
}

$this->config = array_merge($this->config, $lsConfig);
}

/* @inheritdoc */
public function init()
{
parent::init();
Expand All @@ -108,16 +98,57 @@ public function init()
ClassFactory::registerClass('Response_', 'Response');
}

/* @inheritdoc */
public function initLanguage()
{
// Set language to use.
if ($this->request->getParam('lang') !== null) {
$this->setLanguage($this->request->getParam('lang'));
} elseif (isset(App()->session['_lang'])) {
// See: http://www.yiiframework.com/wiki/26/setting-and-maintaining-the-language-in-application-i18n/
// See: http://www.yiiframework.com/wiki/26/setting-and-maintaining-the-language-in-application-i18n/
$this->setLanguage(App()->session['_lang']);
}
}

/**
* Set the LimeSUrvey config array according to files and DB
* @return void
*/
public function setConfigs() {
/* Default config */
$coreConfig = require(__DIR__.'/../config/config-defaults.php');
$emailConfig = require(__DIR__.'/../config/email.php');
$versionConfig = require(__DIR__.'/../config/version.php');
$updaterVersionConfig = require(__DIR__.'/../config/updater_version.php');
$this->config = array_merge($this->config,$coreConfig, $emailConfig, $versionConfig, $updaterVersionConfig);
if(!file_exists(__DIR__.'/../config/config.php')) {
/* Set up not done : then no other part to update */
return;
}
/* User file config */
$userConfigs = require(__DIR__.'/../config/config.php');
if (is_array($userConfigs['config'])) {
$this->config = array_merge($this->config, $userConfigs['config']);
}
/* Database config */
try {
$settingsTableExist = Yii::app()->db->schema->getTable('{{settings_global}}');
if (is_object($settingsTableExist)) {
$dbConfig = CHtml::listData(SettingGlobal::model()->findAll(), 'stg_name', 'stg_value');
$this->config = array_merge($this->config, $dbConfig);
}
} catch (Exception $exception) {
/* Even if database can exist : don't throw exception, */
/* @todo : find when settings_global was created with stg_name and stg_value, maybe can Throw Exception ? */
Yii::log("Table settings_global not found");// Log it as LEVEL_INFO , application category
}
/* Add some specific config using existing other configs */
$this->setConfig('globalAssetsVersion', /* Or create a new var ? */
$this->getConfig('assetsversionnumber',0).
$this->getConfig('versionnumber',0).
$this->getConfig('dbversionnumber',0).
$this->getConfig('customassetversionnumber',1)
);
}
/**
* Loads a helper
Expand Down
48 changes: 13 additions & 35 deletions application/core/LSYii_AssetManager.php
@@ -1,10 +1,9 @@
<?php

/**
* LimeSurvey
* Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
* Copyright (C) 2007-2018 The LimeSurvey Project Team / Carsten Schmitz
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* License: GNU/GPL License v3 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
Expand All @@ -14,44 +13,23 @@

class LSYii_AssetManager extends CAssetManager
{
/* @inheritdoc */
protected function hash($path)
{
return sprintf('%x',crc32($path.Yii::app()->getConfig('globalAssetsVersion')));
}

/**
* Generates path segments relative to basePath.
*
* This method is used instead of the original, so the hash is taken
* from LS version number instead of folder/file last modified time.
* Using file/folder causes a lot of problems due to FTP and other file
* transfers not updating the time stamp, forcing LS to use touch()
* in a lot of places instead. touch() can now be removed - the assets
* will be updated every time a version number is changed.
*
* @param string $file for which public path will be created.
* @param bool $hashByName whether the published directory should be named as the hashed basename.
* @return string path segments without basePath.
* @since 1.1.13
* @inheritdoc
* With db asset version used
*/
protected function generatePath($file, $hashByName = false)
protected function generatePath($file,$hashByName=false)
{
$assetsVersionNumber = Yii::app()->getConfig('assetsversionnumber');
$versionNumber = Yii::app()->getConfig('versionnumber');
$dbVersion = Yii::app()->getConfig('dbversionnumber');
$iCustomassetversionnumber = (function_exists('getGlobalSetting') ) ? getGlobalSetting('customassetversionnumber'):1; // When called from installer, function getGlobalSetting() is not available

if (empty($assetsVersionNumber)
|| empty($versionNumber)
|| empty($dbVersion)) {
throw new Exception(
'Could not create asset manager path hash: One of these configs are empty: assetsversionnumber/versionnumber/dbversionnumber.'
);
}

$lsVersion = $assetsVersionNumber.$versionNumber.$dbVersion.$iCustomassetversionnumber;

if (is_file($file)) {
$pathForHashing = $hashByName ? dirname($file) : dirname($file).$lsVersion;
$pathForHashing=$hashByName ? dirname($file) : dirname($file).filemtime($file).AssetVersion::getAssetVersion($file);
} else {
$pathForHashing = $hashByName ? $file : $file.$lsVersion;
$pathForHashing=$hashByName ? $file : $file.filemtime($file).AssetVersion::getAssetVersion($file);
}

return $this->hash($pathForHashing);
}
}
14 changes: 0 additions & 14 deletions application/helpers/globalsettings_helper.php
Expand Up @@ -15,20 +15,6 @@
*/
//Ensure script is not run directly, avoid path disclosure
//if (!isset($homedir) || isset($_REQUEST['$homedir'])) {die("Cannot run this script directly");}
injectglobalsettings();


function injectglobalsettings()
{
$settings = SettingGlobal::model()->findAll();

//if ($dbvaluearray!==false)
if (count($settings) > 0) {
foreach ($settings as $setting) {
Yii::app()->setConfig($setting->getAttribute('stg_name'), $setting->getAttribute('stg_value'));
}
}
}

This comment has been minimized.

Copy link
@lacrioque

lacrioque Jun 14, 2018

Contributor

This basically destroyed the globalsetting and we had to remove it!
Globalsetting were saved but not reloaded because of this.

This comment has been minimized.

Copy link
@Shnoulle

Shnoulle Jun 14, 2018

Author Collaborator

When ? When start : get global seetings here : 0d99224#diff-f5392477bcb7ace25c65e8bd7abd39e9R117

When update, we currently use

function setGlobalSetting($settingname, $settingvalue)
then : reload config at end
Yii::app()->setConfig($settingname, $settingvalue);

This part must be moved to model, and this fix all.

And best when there are an issue related to a commit : inform the dev of the issue … it's not like i never fix my issue …

We can fix and fix and fix again …


/**
* Returns a global setting
Expand Down
19 changes: 19 additions & 0 deletions application/helpers/update/updatedb_helper.php
Expand Up @@ -2202,6 +2202,9 @@ function db_upgrade_all($iOldDBVersion, $bSilent = false)
$oDB->createCommand()->update('{{settings_global}}', ['stg_value'=>348], "stg_name='DBVersion'");
$oTransaction->commit();
}



if ($iOldDBVersion < 349) {
$oTransaction = $oDB->beginTransaction();
dropColumn('{{users}}','one_time_pw');
Expand All @@ -2210,6 +2213,22 @@ function db_upgrade_all($iOldDBVersion, $bSilent = false)
$oTransaction->commit();
}

/**
* Adding asset version to allow to reset asset without write inside
*/
if ($iOldDBVersion < 350) {
$oTransaction = $oDB->beginTransaction();
$oDB->createCommand()->createTable('{{asset_version}}',array(
'hash' => 'string(64)',
'path' => 'text',
'version' => 'integer NOT NULL',
));
/* Create index on hash */
$oDB->createCommand()->addPrimaryKey('{{asset_version_pk}}', '{{asset_version}}', ['hash']);
$oDB->createCommand()->update('{{settings_global}}', ['stg_value'=>349], "stg_name='DBVersion'");
$oTransaction->commit();
}

} catch (Exception $e) {
Yii::app()->setConfig('Updating', false);
$oTransaction->rollback();
Expand Down
103 changes: 103 additions & 0 deletions application/models/AssetVersion.php
@@ -0,0 +1,103 @@
<?php if (!defined('BASEPATH')) {exit('No direct script access allowed');}
/*
* LimeSurvey
* Copyright (C) 2018 The LimeSurvey Project Team / Carsten Schmitz
* All rights reserved.
* License: GNU/GPL License v3 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*
*/

/**
* Class AssetVersion
*
* @property string $hash identifier of path
* @property string $path for reminder
* @property integer $version number
*/
class AssetVersion extends LSActiveRecord
{
/**
* @inheritdoc
*/
public static function model($class = __CLASS__)
{
return parent::model($class);
}

/**
* @inheritdoc
*/
public function init()
{
$this->path = "";
$this->version = 0;
}
/** @inheritdoc */
public function tableName()
{
return '{{asset_version}}';
}

/** @inheritdoc */
public function primaryKey()
{
return array('path');
}

/** @inheritdoc */
public function rules()
{
return array(
array('hash', 'required'),
array('path', 'required'),
array('version', 'required'),
array('version', 'numerical', 'integerOnly'=>true),
);
}

/**
* get current assetVersion
* @param string $path
* @return integer
*/
public static function getAssetVersion($path)
{
if(Yii::app()->getConfig('DBVersion') < 349) {
return 0;
}
$hash = hash('sha256', $path);
$oAssetVersion = self::model()->findByPk($hash);
if(!$oAssetVersion) {
return 0;
}
return $oAssetVersion->version;
}

/**
* increment (and create if needed) asset version number
* @param string $path
* @return integer (current version)
*/
public static function incrementAssetVersion($path)
{
if(Yii::app()->getConfig('DBVersion') < 349) {
return 0;
}
$hash = hash('sha256', $path);
$oAssetVersion = self::model()->findByPk($hash);
if(!$oAssetVersion) {
$oAssetVersion = new self;
$oAssetVersion->hash = $hash;
$oAssetVersion->path = $path;
$oAssetVersion->version = 0;
}
$oAssetVersion->version++;
$oAssetVersion->save(); // Not need to test : can break rules. DB error can happen ?
return $oAssetVersion->version;
}
}
11 changes: 10 additions & 1 deletion application/models/Template.php
Expand Up @@ -509,14 +509,23 @@ public static function resetInstance()
self::$instance = null;
}

/**
* Reset assets for this template
* Using DB only
* @return void
*/
public function resetAsset()
{
AssetVersion::incrementAssetVersion(self::getTemplatePath($this->name));
}

/**
* Return the standard template list
* @return string[]
* @throws Exception
*/
public static function getStandardTemplateList()
{

$standardTemplates = array('vanilla', 'bootswatch', 'fruity');
return $standardTemplates;
}
Expand Down
7 changes: 7 additions & 0 deletions installer/create-database.php
Expand Up @@ -809,6 +809,13 @@ function createDatabase($oDB){

$oDB->createCommand()->createIndex('{{idx1_user_groups}}', '{{user_groups}}', 'name', true);

// asset version
$oDB->createCommand()->createTable('{{asset_version}}',array(
'hash' => 'string(64)',
'path' => 'text',
'version' => 'integer NOT NULL',
));
$oDB->createCommand()->addPrimaryKey('{{asset_version_pk}}', '{{asset_version}}', ['hash']);

// Set database version
$oDB->createCommand()->insert("{{settings_global}}", ['stg_name'=> 'DBVersion' , 'stg_value' => $databaseCurrentVersion]);
Expand Down

0 comments on commit 0d99224

Please sign in to comment.