forked from extern/egroupware
285 lines
11 KiB
PHP
285 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* EGroupware API - Bundle JS includes
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package api
|
|
* @subpackage framework
|
|
* @access public
|
|
* @version $Id$
|
|
*/
|
|
|
|
namespace EGroupware\Api\Framework;
|
|
|
|
use EGroupware\Api\Cache;
|
|
use EGroupware\Api\Header\UserAgent;
|
|
|
|
/**
|
|
* Bundle JS includes
|
|
*/
|
|
class Bundle
|
|
{
|
|
/**
|
|
* Url of minified version of bundle
|
|
*
|
|
* @var array
|
|
*/
|
|
static $bundle2minurl = array(
|
|
'api' => '/api/js/jsapi.min.js',
|
|
'et2' => '/api/js/etemplate/etemplate2.min.js',
|
|
'et21'=> '/api/js/etemplate/etemplate2.min.js',
|
|
'pixelegg' => '/pixelegg/js/fw_pixelegg.min.js',
|
|
'jdots' => '/jdots/js/fw_jdots.min.js',
|
|
'mobile' => '/pixelegg/js/fw_mobile.min.js',
|
|
);
|
|
|
|
/**
|
|
* Devide js-includes in bundles of javascript files to include eg. api or etemplate2, if minifying is enabled
|
|
*
|
|
* @param array $js_includes files to include with egw relative url
|
|
* @return array egw relative urls to include incl. bundels/minify urls, if enabled
|
|
*/
|
|
public static function js_includes(array $js_includes)
|
|
{
|
|
$file2bundle = array();
|
|
if ($GLOBALS['egw_info']['server']['debug_minify'] !== 'True')
|
|
{
|
|
// get used bundles and cache them on tree-level for 2h
|
|
//$bundles = self::all(); Cache::setTree(__CLASS__, 'bundles', $bundles, 7200);
|
|
$bundles = Cache::getTree(__CLASS__, 'bundles', array(__CLASS__, 'all'), array(), 7200);
|
|
$bundles_ts = $bundles['.ts'];
|
|
unset($bundles['.ts']);
|
|
foreach($bundles as $name => $files)
|
|
{
|
|
// to facilitate move to new api/et2 location, can be removed after 16.1 release
|
|
if ($name == 'et21' && !in_array('/api/js/etemplate/etemplate2.js', $files) ||
|
|
$name == 'api' && !in_array('/vendor/bower-asset/jquery/dist/jquery.js', $files))
|
|
{
|
|
Cache::unsetTree(__CLASS__, 'bundles');
|
|
return self::js_includes($js_includes);
|
|
}
|
|
// ignore bundles of not used templates, as they can contain identical files
|
|
if (in_array($name, array('api', 'et2', 'et21')) ||
|
|
$name == (UserAgent::mobile() ? 'mobile' : $GLOBALS['egw_info']['server']['template_set']) ||
|
|
isset($GLOBALS['egw_info']['apps'][$name]))
|
|
{
|
|
$file2bundle += array_combine($files, array_fill(0, count($files), $name));
|
|
}
|
|
}
|
|
//error_log(__METHOD__."() file2bundle=".array2string($file2bundle));
|
|
}
|
|
$to_include = $included_bundles = array();
|
|
$query = null;
|
|
foreach($js_includes as $file)
|
|
{
|
|
if ($file == '/api/js/jsapi/egw.js') continue; // loaded via own tag, and we must not load it twice!
|
|
|
|
if (!isset($to_include[$file]))
|
|
{
|
|
if (($bundle = $file2bundle[$file]))
|
|
{
|
|
//error_log(__METHOD__."() requiring bundle $bundle for $file");
|
|
if (!in_array($bundle, $included_bundles))
|
|
{
|
|
$included_bundles[] = $bundle;
|
|
$minurl = self::$bundle2minurl[$bundle];
|
|
if (!isset($minurl) && isset($GLOBALS['egw_info']['apps'][$bundle]))
|
|
{
|
|
$minurl = '/'.$bundle.'/js/app.min.js';
|
|
}
|
|
$max_modified = 0;
|
|
$to_include = array_merge($to_include, self::urls($bundles[$bundle], $max_modified, $minurl));
|
|
// check if bundle-config is more recent then
|
|
if ($max_modified > $bundles_ts)
|
|
{
|
|
// force new bundle Config by deleting cached one and call ourself again
|
|
Cache::unsetTree(__CLASS__, 'bundles');
|
|
return self::js_includes($js_includes);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unset($query);
|
|
list($path, $query) = explode('?', $file, 2);
|
|
$mod = filemtime(EGW_SERVER_ROOT.$path);
|
|
// check if we have a more recent minified version of the file and use it
|
|
if ($GLOBALS['egw_info']['server']['debug_minify'] !== 'True' &&
|
|
substr($path, -3) == '.js' && file_exists(EGW_SERVER_ROOT.($min_path = substr($path, 0, -3).'.min.js')) &&
|
|
(($min_mod = filemtime(EGW_SERVER_ROOT.$min_path)) >= $mod))
|
|
{
|
|
$path = $min_path;
|
|
$mod = $min_mod;
|
|
}
|
|
$to_include[$file] = $path.'?'.$mod.($query ? '&'.$query : '');
|
|
}
|
|
}
|
|
}
|
|
//error_log(__METHOD__."(".array2string($js_includes).') debug_minify='.array2string($GLOBALS['egw_info']['server']['debug_minify']).', include_bundels='.array2string($included_bundles).' returning '.array2string(array_values(array_unique($to_include))));
|
|
return array_values(array_unique($to_include));
|
|
}
|
|
|
|
/**
|
|
* Generate bundle url(s) for given js files
|
|
*
|
|
* @param array $js_includes
|
|
* @param int& $max_modified =null on return maximum modification time of bundle
|
|
* @param string $minurl =null url of minified bundle, to be used, if existing and recent
|
|
* @return array js-files (can be more then one, if one of given files can not be bundeled)
|
|
*/
|
|
protected static function urls(array $js_includes, &$max_modified=null, $minurl=null)
|
|
{
|
|
$debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
|
|
// ignore not existing minurl
|
|
if (!empty($minurl) && !file_exists(EGW_SERVER_ROOT.$minurl)) $minurl = null;
|
|
$to_include_first = $to_include = $to_minify = array();
|
|
$max_modified = 0;
|
|
$query = null;
|
|
foreach($js_includes as $path)
|
|
{
|
|
if ($path == '/api/js/jsapi/egw.js') continue; // Leave egw.js out of bundle
|
|
unset($query);
|
|
list($path,$query) = explode('?',$path,2);
|
|
$mod = filemtime(EGW_SERVER_ROOT.$path);
|
|
if ($mod > $max_modified) $max_modified = $mod;
|
|
|
|
// ckeditor must be included before bundled files, as they depend on it!
|
|
if (strpos($path,'/ckeditor/ckeditor.js') !== false)
|
|
{
|
|
$to_include_first[] = $path . '?' . $mod;
|
|
}
|
|
// for now minify does NOT support query parameters, nor php files generating javascript
|
|
elseif ($debug_minify || $query || substr($path, -3) != '.js' || empty($minurl))
|
|
{
|
|
$path .= '?'. $mod.($query ? '&'.$query : '');
|
|
$to_include[] = $path;
|
|
}
|
|
else
|
|
{
|
|
$to_minify[] = substr($path,1);
|
|
}
|
|
}
|
|
if (!$debug_minify && $to_minify)
|
|
{
|
|
$path = $minurl.'?'.filemtime(EGW_SERVER_ROOT.$minurl);
|
|
/* no more dynamic minifying
|
|
if (!empty($minurl) && file_exists(EGW_SERVER_ROOT.$minurl) &&
|
|
($mod=filemtime(EGW_SERVER_ROOT.$minurl)) >= $max_modified)
|
|
{
|
|
$path = $minurl.'?'.$mod;
|
|
}
|
|
else
|
|
{
|
|
$base_path = $GLOBALS['egw_info']['server']['webserver_url'];
|
|
if ($base_path[0] != '/') $base_path = parse_url($base_path, PHP_URL_PATH);
|
|
$path = '/phpgwapi/inc/min/?'.($base_path && $base_path != '/' ? 'b='.substr($base_path, 1).'&' : '').
|
|
'f='.implode(',', $to_minify) .
|
|
($GLOBALS['egw_info']['server']['debug_minify'] === 'debug' ? '&debug' : '').
|
|
'&'.$max_modified;
|
|
}*/
|
|
// need to include minified javascript before not minified stuff like jscalendar-setup, as it might depend on it
|
|
array_unshift($to_include, $path);
|
|
}
|
|
if ($to_include_first) $to_include = array_merge($to_include_first, $to_include);
|
|
//error_log(__METHOD__."("./*array2string($js_includes).*/", $max_modified, $minurl) returning ".array2string($to_include));
|
|
return $to_include;
|
|
}
|
|
|
|
/**
|
|
* Maximum number of files in a bundle
|
|
*
|
|
* We split bundles, if they contain more then these number of files,
|
|
* because IE silently stops caching them, if Content-Length get's too big.
|
|
*
|
|
* IE11 cached 142kb compressed api bundle, but not 190kb et2 bundle.
|
|
* Splitting et2 bundle in max 50 files chunks, got IE11 to cache both bundles.
|
|
*/
|
|
const MAX_BUNDLE_FILES = 50;
|
|
|
|
/**
|
|
* Return all bundels we use:
|
|
* - api stuff phpgwapi/js/jsapi/* and it's dependencies incl. jquery
|
|
* - etemplate2 stuff not including api bundle, but jquery-ui
|
|
*
|
|
* @return array bundle-url => array of contained files
|
|
*/
|
|
public static function all()
|
|
{
|
|
$inc_mgr = new IncludeMgr();
|
|
$bundles = array();
|
|
|
|
$max_mod = array();
|
|
|
|
// generate api bundle
|
|
$inc_mgr->include_js_file('/vendor/bower-asset/jquery/dist/jquery.js');
|
|
$inc_mgr->include_js_file('/vendor/bower-asset/jquery-ui/jquery-ui.js');
|
|
$inc_mgr->include_js_file('/api/js/jsapi/jsapi.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_json.js');
|
|
$inc_mgr->include_js_file('/api/js/jsapi/egw.js');
|
|
// dhtmlxTree (dhtmlxMenu get loaded via dependency in egw_menu_dhtmlx.js)
|
|
$inc_mgr->include_js_file('/api/js/dhtmlxtree/codebase/dhtmlxcommon.js');
|
|
$inc_mgr->include_js_file('/api/js/dhtmlxtree/sources/dhtmlxtree.js');
|
|
$inc_mgr->include_js_file('/api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js');
|
|
// actions
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_action.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_keymanager.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_action_popup.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_action_dragdrop.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_dragdrop_dhtmlx_tree.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_menu.js');
|
|
$inc_mgr->include_js_file('/api/js/egw_action/egw_menu_dhtmlx.js');
|
|
// include choosen in api, as old eTemplate uses it and fail if it pulls in half of et2
|
|
$inc_mgr->include_js_file('/api/js/jquery/chosen/chosen.jquery.js');
|
|
// include CKEditor in api, as old eTemplate uses it too
|
|
$inc_mgr->include_js_file('/api/js/ckeditor/ckeditor.js');
|
|
$inc_mgr->include_js_file('/api/js/ckeditor/config.js');
|
|
$bundles['api'] = $inc_mgr->get_included_files();
|
|
self::urls($bundles['api'], $max_mod['api']);
|
|
|
|
// generate et2 bundle (excluding files in api bundle)
|
|
$inc_mgr->include_js_file('/api/js/etemplate/etemplate2.js');
|
|
$bundles['et2'] = array_diff($inc_mgr->get_included_files(), $bundles['api']);
|
|
self::urls($bundles['et2'], $max_mod['et2']);
|
|
|
|
$stock_files = call_user_func_array('array_merge', $bundles);
|
|
|
|
// generate template and app bundles, if installed
|
|
foreach(array(
|
|
'jdots' => '/jdots/js/fw_jdots.js',
|
|
'mobile' => '/pixelegg/js/fw_mobile.js',
|
|
'pixelegg' => '/pixelegg/js/fw_pixelegg.js',
|
|
'calendar' => '/calendar/js/app.js',
|
|
'mail' => '/mail/js/app.js',
|
|
'projectmanager' => '/projectmanager/js/app.js',
|
|
) as $bundle => $file)
|
|
{
|
|
if (@file_exists(EGW_SERVER_ROOT.$file))
|
|
{
|
|
$inc_mgr = new IncludeMgr($stock_files); // reset loaded files to stock files
|
|
$inc_mgr->include_js_file($file);
|
|
$bundles[$bundle] = array_diff($inc_mgr->get_included_files(), $stock_files);
|
|
self::urls($bundles[$bundle], $max_mod[$bundle]);
|
|
}
|
|
}
|
|
|
|
// automatic split bundles with more then MAX_BUNDLE_FILES (=50) files
|
|
foreach($bundles as $name => $files)
|
|
{
|
|
$n = '';
|
|
while (count($files) > self::MAX_BUNDLE_FILES*(int)$n)
|
|
{
|
|
$files80 = array_slice($files, self::MAX_BUNDLE_FILES*(int)$n, self::MAX_BUNDLE_FILES, true);
|
|
$bundles[$name.$n++] = $files80;
|
|
}
|
|
}
|
|
|
|
// store max modification time of all files in all bundles
|
|
$bundles['.ts'] = max($max_mod);
|
|
|
|
//error_log(__METHOD__."() returning ".array2string($bundles));
|
|
return $bundles;
|
|
}
|
|
}
|