├── .gitignore ├── FISResource.class.php ├── README.md ├── block.widget_inline.php ├── compiler.body.php ├── compiler.head.php ├── compiler.html.php ├── compiler.placeholder.php ├── compiler.require.php ├── compiler.script.php ├── compiler.style.php ├── compiler.uri.php ├── compiler.widget.php ├── function.http_header.php ├── modifier.f_escape_callback.php ├── modifier.f_escape_data.php ├── modifier.f_escape_event.php ├── modifier.f_escape_js.php ├── modifier.f_escape_path.php ├── modifier.f_escape_xml.php └── test ├── FISResource ├── FISResourceTest.php └── projects │ └── config │ ├── common-map.json │ ├── empty-map.json │ └── map.json ├── SmartyPluginTest.php ├── TestEnv.php ├── performance ├── change.class.php ├── changeall.class.php ├── config.php ├── index.php ├── product_code_ready.sh ├── release.sh ├── result_template.html └── start.sh └── project └── script_priority ├── fis-conf.js ├── page └── index.tpl └── widget ├── a.tpl ├── b.tpl ├── c.tpl └── d.tpl /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.settings 3 | /.project 4 | 5 | tmp 6 | ~* 7 | *.db 8 | *.bak 9 | *.tmp 10 | *.swp 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /FISResource.class.php: -------------------------------------------------------------------------------- 1 | '; 6 | const JS_SCRIPT_HOOK = ''; 7 | const FRAMEWORK_HOOK = ''; 8 | const CSS_STYLE_HOOK = ''; 9 | 10 | private static $arrMap = array(); 11 | private static $arrLoaded = array(); 12 | private static $arrAsyncDeleted = array(); 13 | 14 | private static $arrStaticCollection = array(); 15 | //收集require.async组件 16 | private static $arrRequireAsyncCollection = array(); 17 | private static $arrScriptPool = array(); 18 | private static $arrStylePool = array(); 19 | 20 | public static $framework = null; 21 | 22 | //记录{%script%}, {%style%}的id属性 23 | public static $cp = null; 24 | 25 | //{%script%} {%style%}去重 26 | public static $arrEmbeded = array(); 27 | 28 | public static function reset(){ 29 | self::$arrMap = array(); 30 | self::$arrLoaded = array(); 31 | self::$arrAsyncDeleted = array(); 32 | self::$arrStaticCollection = array(); 33 | // 常驻进程时,此变量需要销毁 34 | self::$arrRequireAsyncCollection = array(); 35 | self::$arrScriptPool = array(); 36 | self::$arrStylePool = array(); 37 | self::$framework = null; 38 | } 39 | 40 | public static function addStatic($src, $typ) { 41 | if (!$typ) { 42 | preg_match('/\.(\w+)(?:\?[\s\S]+)?$/', $src, $m); 43 | if (!$m) { 44 | return; 45 | } 46 | $typ = $m[1]; 47 | } 48 | 49 | $typ = trim($typ); 50 | 51 | // 变量写错 52 | if (!in_array($typ, array('js', 'css'))) { 53 | return; 54 | } 55 | 56 | if (!is_array(self::$arrStaticCollection[$typ])) { 57 | self::$arrStaticCollection[$typ] = array(); 58 | } 59 | if (!in_array($src, self::$arrStaticCollection[$typ])) { 60 | self::$arrStaticCollection[$typ][] = $src; 61 | } 62 | } 63 | 64 | public static function styleHook(){ 65 | return self::CSS_STYLE_HOOK; 66 | } 67 | 68 | public static function cssHook(){ 69 | return self::CSS_LINKS_HOOK; 70 | } 71 | 72 | public static function jsHook(){ 73 | return self::JS_SCRIPT_HOOK; 74 | } 75 | 76 | public static function placeHolder($mode){ 77 | $placeHolder = ''; 78 | switch ($mode) { 79 | case 'modjs': 80 | $placeHolder = self::FRAMEWORK_HOOK; 81 | break; 82 | default: 83 | break; 84 | } 85 | return $placeHolder; 86 | } 87 | 88 | //输出模板的最后,替换css hook为css标签集合,替换js hook为js代码 89 | public static function renderResponse($strContent){ 90 | $cssIntPos = strpos($strContent, self::CSS_LINKS_HOOK); 91 | if($cssIntPos !== false){ 92 | $strContent = substr_replace($strContent, self::render('css'), $cssIntPos, strlen(self::CSS_LINKS_HOOK)); 93 | } 94 | $styleIntPos = strpos($strContent, self::CSS_STYLE_HOOK); 95 | if($styleIntPos !== false){ 96 | $strContent = substr_replace($strContent, self::renderStylePool(), $styleIntPos, strlen(self::CSS_STYLE_HOOK)); 97 | } 98 | $frameworkIntPos = strpos($strContent, self::FRAMEWORK_HOOK); 99 | if($frameworkIntPos !== false){ 100 | $strContent = substr_replace($strContent, self::render('framework'), $frameworkIntPos, strlen(self::FRAMEWORK_HOOK)); 101 | } 102 | $jsIntPos = strpos($strContent, self::JS_SCRIPT_HOOK); 103 | if($jsIntPos !== false){ 104 | $jsContent = ($frameworkIntPos !== false) ? '' : self::getModJsHtml(); 105 | $jsContent .= self::render('js') . self::renderScriptPool(); 106 | $strContent = substr_replace($strContent, $jsContent, $jsIntPos, strlen(self::JS_SCRIPT_HOOK)); 107 | } 108 | self::reset(); 109 | return $strContent; 110 | } 111 | 112 | //设置framewok mod.js 113 | public static function setFramework($strFramework) { 114 | self::$framework = $strFramework; 115 | } 116 | 117 | //返回静态资源uri,有包的时候,返回包的uri 118 | public static function getUri($strName, $smarty) { 119 | $intPos = strpos($strName, ':'); 120 | if($intPos === false){ 121 | $strNamespace = '__global__'; 122 | } else { 123 | $strNamespace = substr($strName, 0, $intPos); 124 | } 125 | if(isset(self::$arrMap[$strNamespace]) || self::register($strNamespace, $smarty)) { 126 | $arrMap = &self::$arrMap[$strNamespace]; 127 | if (isset($arrMap['res'][$strName])) { 128 | $arrRes = &$arrMap['res'][$strName]; 129 | if (!array_key_exists('fis_debug', $_GET) && isset($arrRes['pkg'])) { 130 | $arrPkg = &$arrMap['pkg'][$arrRes['pkg']]; 131 | return $arrPkg['uri']; 132 | } else { 133 | return $arrRes['uri']; 134 | } 135 | } 136 | } 137 | } 138 | 139 | public static function getTemplate($strName, $smarty) { 140 | //绝对路径 141 | return $smarty->joined_template_dir . str_replace('/template', '', self::getUri($strName, $smarty)); 142 | } 143 | 144 | private static function getModJsHtml(){ 145 | $html = ''; 146 | $resourceMap = self::getResourceMap(); 147 | $loadModJs = (self::$framework && (isset(self::$arrStaticCollection['js']) || $resourceMap)); 148 | //require.resourceMap要在mod.js加载以后执行 149 | if ($loadModJs) { 150 | $html .= '' . PHP_EOL; 151 | } 152 | if ($resourceMap) { 153 | $html .= ''; 156 | } 157 | return $html; 158 | } 159 | 160 | //渲染资源,将收集到的js css,变为html标签,异步js资源变为resorce map。 161 | public static function render($type){ 162 | $html = ''; 163 | if ($type === 'js') { 164 | if (isset(self::$arrStaticCollection['js'])) { 165 | $arrURIs = &self::$arrStaticCollection['js']; 166 | foreach ($arrURIs as $uri) { 167 | if ($uri === self::$framework) { 168 | continue; 169 | } 170 | $html .= '' . PHP_EOL; 171 | } 172 | } 173 | } else if($type === 'css'){ 174 | if(isset(self::$arrStaticCollection['css'])){ 175 | $arrURIs = &self::$arrStaticCollection['css']; 176 | $html = ''; 177 | } 178 | } else if($type === 'framework'){ 179 | $html .= self::getModJsHtml(); 180 | } 181 | 182 | return $html; 183 | } 184 | 185 | // fix issues https://github.com/fex-team/fis-plus-smarty-plugin/issues/6 186 | public static function addStylePool($code) { 187 | self::$arrStylePool[] = $code; 188 | } 189 | 190 | //输出css,将页面的css源代码集合到pool,一起输出 191 | public static function renderStylePool(){ 192 | $style = ''; 195 | return $style; 196 | } 197 | 198 | public static function addScriptPool($str, $priority) { 199 | $priority = intval($priority); 200 | if (!isset(self::$arrScriptPool[$priority])) { 201 | self::$arrScriptPool[$priority] = array(); 202 | } 203 | self::$arrScriptPool[$priority][] = $str; 204 | } 205 | 206 | //输出js,将页面的js源代码集合到pool,一起输出 207 | public static function renderScriptPool(){ 208 | $html = ''; 209 | if(!empty(self::$arrScriptPool)) { 210 | $priorities = array_keys(self::$arrScriptPool); 211 | rsort($priorities); 212 | foreach ($priorities as $priority) { 213 | $html .= ''; 214 | } 215 | } 216 | return $html; 217 | } 218 | 219 | //获取异步js资源集合,变为json格式的resourcemap 220 | public static function getResourceMap() { 221 | $ret = ''; 222 | $arrResourceMap = array(); 223 | $needPkg = !array_key_exists('fis_debug', $_GET); 224 | if (isset(self::$arrRequireAsyncCollection['res'])) { 225 | foreach (self::$arrRequireAsyncCollection['res'] as $id => $arrRes) { 226 | $deps = array(); 227 | if (!empty($arrRes['deps'])) { 228 | foreach ($arrRes['deps'] as $strName) { 229 | if (preg_match('/\.(?:js|jsx|es|es6|es7|ts|tsx|coffee)$/i', $strName)) { 230 | $deps[] = $strName; 231 | } 232 | } 233 | } 234 | 235 | $arrResourceMap['res'][$id] = array( 236 | 'url' => $arrRes['uri'], 237 | ); 238 | 239 | if (!empty($arrRes['pkg']) && $needPkg) { 240 | $arrResourceMap['res'][$id]['pkg'] = $arrRes['pkg']; 241 | } 242 | 243 | if (!empty($deps)) { 244 | $arrResourceMap['res'][$id]['deps'] = $deps; 245 | } 246 | } 247 | } 248 | if (isset(self::$arrRequireAsyncCollection['pkg']) && $needPkg) { 249 | foreach (self::$arrRequireAsyncCollection['pkg'] as $id => $arrRes) { 250 | $arrResourceMap['pkg'][$id] = array( 251 | 'url'=> $arrRes['uri'] 252 | ); 253 | } 254 | } 255 | if (!empty($arrResourceMap)) { 256 | $ret = str_replace('\\/', '/', json_encode($arrResourceMap)); 257 | } 258 | return $ret; 259 | } 260 | 261 | //获取命名空间的map.json 262 | public static function register($strNamespace, $smarty){ 263 | if($strNamespace === '__global__'){ 264 | $strMapName = 'map.json'; 265 | } else { 266 | $strMapName = $strNamespace . '-map.json'; 267 | } 268 | $arrConfigDir = $smarty->getConfigDir(); 269 | foreach ($arrConfigDir as $strDir) { 270 | $strPath = preg_replace('/[\\/\\\\]+/', '/', $strDir . '/' . $strMapName); 271 | if(is_file($strPath)){ 272 | self::$arrMap[$strNamespace] = json_decode(file_get_contents($strPath), true); 273 | return true; 274 | } 275 | } 276 | return false; 277 | } 278 | 279 | /** 280 | * 分析组件依赖 281 | * @param array $arrRes 组件信息 282 | * @param Object $smarty smarty对象 283 | * @param bool $async 是否异步 284 | */ 285 | private static function loadDeps($arrRes, $smarty, $async) { 286 | //require.async 287 | if (isset($arrRes['extras']) && isset($arrRes['extras']['async'])) { 288 | foreach ($arrRes['extras']['async'] as $uri) { 289 | self::load($uri, $smarty, true); 290 | } 291 | } 292 | if(isset($arrRes['deps'])){ 293 | foreach ($arrRes['deps'] as $strDep) { 294 | self::load($strDep, $smarty, $async); 295 | } 296 | } 297 | } 298 | 299 | /** 300 | * 已经分析到的组件在后续被同步使用时在异步组里删除。 301 | * @param $strName 302 | */ 303 | private static function delAsyncDeps($strName, $onlyDeps = false) { 304 | if (isset(self::$arrAsyncDeleted[$strName])) { 305 | return true; 306 | } else { 307 | self::$arrAsyncDeleted[$strName] = true; 308 | 309 | $arrRes = self::$arrRequireAsyncCollection['res'][$strName]; 310 | 311 | //first deps 312 | if (isset($arrRes['deps'])) { 313 | foreach ($arrRes['deps'] as $strDep) { 314 | if (isset(self::$arrRequireAsyncCollection['res'][$strDep])) { 315 | self::delAsyncDeps($strDep); 316 | } 317 | } 318 | } 319 | 320 | if ($onlyDeps) { 321 | return true; 322 | } 323 | 324 | //second self 325 | if (isset($arrRes['pkg'])) { 326 | $arrPkg = self::$arrRequireAsyncCollection['pkg'][$arrRes['pkg']]; 327 | $syncJs = isset(self::$arrStaticCollection['js']) ? self::$arrStaticCollection['js'] : array(); 328 | if ($arrPkg && !in_array($arrPkg['uri'], $syncJs)) { 329 | //@TODO 330 | //unset(self::$arrRequireAsyncCollection['pkg'][$arrRes['pkg']]); 331 | foreach ($arrPkg['has'] as $strHas) { 332 | if (isset(self::$arrRequireAsyncCollection['res'][$strHas])) { 333 | self::$arrLoaded[$strName] = $arrPkg['uri']; 334 | self::delAsyncDeps($strHas, true); 335 | } 336 | } 337 | self::$arrStaticCollection['js'][] = $arrPkg['uri']; 338 | } else { 339 | //@TODO 340 | //unset(self::$arrRequireAsyncCollection['res'][$strName]); 341 | } 342 | } else { 343 | //已经分析过的并且在其他文件里同步加载的组件,重新收集在同步输出组 344 | self::$arrStaticCollection['js'][] = $arrRes['uri']; 345 | self::$arrLoaded[$strName] = $arrRes['uri']; 346 | //@TODO 347 | //unset(self::$arrRequireAsyncCollection['res'][$strName]); 348 | } 349 | } 350 | } 351 | 352 | /** 353 | * 加载组件以及组件依赖 354 | * @param $strName id 355 | * @param $smarty smarty对象 356 | * @param bool $async 是否为异步组件(only JS) 357 | * @return mixed 358 | */ 359 | public static function load($strName, $smarty, $async = false){ 360 | if(isset(self::$arrLoaded[$strName])) { 361 | //同步组件优先级比异步组件高 362 | if (!$async && isset(self::$arrRequireAsyncCollection['res'][$strName])) { 363 | self::delAsyncDeps($strName); 364 | } 365 | return self::$arrLoaded[$strName]; 366 | } else { 367 | $intPos = strpos($strName, ':'); 368 | if($intPos === false){ 369 | $strNamespace = '__global__'; 370 | } else { 371 | $strNamespace = substr($strName, 0, $intPos); 372 | } 373 | if(isset(self::$arrMap[$strNamespace]) || self::register($strNamespace, $smarty)){ 374 | $arrMap = &self::$arrMap[$strNamespace]; 375 | $arrPkg = null; 376 | $arrPkgHas = array(); 377 | if(isset($arrMap['res'][$strName])) { 378 | $arrRes = &$arrMap['res'][$strName]; 379 | 380 | if (array_key_exists('fis_debug', $_GET)) { 381 | echo ''."\n"; 382 | } 383 | 384 | if(!array_key_exists('fis_debug', $_GET) && isset($arrRes['pkg'])){ 385 | $arrPkg = &$arrMap['pkg'][$arrRes['pkg']]; 386 | $strURI = $arrPkg['uri']; 387 | 388 | foreach ($arrPkg['has'] as $strResId) { 389 | self::$arrLoaded[$strResId] = $strURI; 390 | } 391 | 392 | foreach ($arrPkg['has'] as $strResId) { 393 | $arrHasRes = &$arrMap['res'][$strResId]; 394 | $arrPkgHas[$strResId] = $arrHasRes; 395 | self::loadDeps($arrHasRes, $smarty, $async); 396 | 397 | } 398 | } else { 399 | $strURI = $arrRes['uri']; 400 | self::$arrLoaded[$strName] = $strURI; 401 | self::loadDeps($arrRes, $smarty, $async); 402 | } 403 | 404 | if ($async && $arrRes['type'] === 'js') { 405 | if ($arrPkg) { 406 | self::$arrRequireAsyncCollection['pkg'][$arrRes['pkg']] = $arrPkg; 407 | self::$arrRequireAsyncCollection['res'] = array_merge((array)self::$arrRequireAsyncCollection['res'], $arrPkgHas); 408 | } else { 409 | self::$arrRequireAsyncCollection['res'][$strName] = $arrRes; 410 | } 411 | } else { 412 | self::$arrStaticCollection[$arrRes['type']][] = $strURI; 413 | } 414 | return $strURI; 415 | } else { 416 | self::triggerError($strName, 'undefined resource "' . $strName . '"', E_USER_NOTICE); 417 | } 418 | } else { 419 | self::triggerError($strName, 'missing map file of "' . $strNamespace . '"', E_USER_NOTICE); 420 | } 421 | } 422 | self::triggerError($strName, 'unknown resource "' . $strName . '" load error', E_USER_NOTICE); 423 | } 424 | 425 | /** 426 | * 用户代码自定义js组件,其没有对应的文件 427 | * 只有有后缀的组件找不到时进行报错 428 | * @param $strName 组件ID 429 | * @param $strMessage 错误信息 430 | * @param $errorLevel 错误level 431 | */ 432 | private static function triggerError($strName, $strMessage, $errorLevel) { 433 | $arrExt = array( 434 | 'js', 435 | 'css', 436 | 'tpl', 437 | 'html', 438 | 'xhtml', 439 | ); 440 | if (preg_match('/\.('.implode('|', $arrExt).')$/', $strName)) { 441 | trigger_error(date('Y-m-d H:i:s') . ' ' . $strName . ' ' . $strMessage, $errorLevel); 442 | } 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fis-pc-plugin 2 | ============= 3 | 4 | fis-pc-plugin 5 | -------------------------------------------------------------------------------- /block.widget_inline.php: -------------------------------------------------------------------------------- 1 | $value){ 7 | if($value instanceof Smarty_Variable){ 8 | $tpl_vars[$key] = $value; 9 | } else { 10 | $tpl_vars[$key] = new Smarty_Variable($value); 11 | } 12 | } 13 | } 14 | public static function pop(&$tpl_vars){ 15 | $tpl_vars = array_pop(self::$_vars); 16 | } 17 | } 18 | 19 | function smarty_block_widget_inline($params, $content, Smarty_Internal_Template $template, &$repeat){ 20 | if(!$repeat){//block 定义结束 21 | FISBlockFisWidget::pop($template->tpl_vars); 22 | return $content; 23 | }else{//block 定义开始 24 | FISBlockFisWidget::push($params, $template->tpl_vars); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /compiler.body.php: -------------------------------------------------------------------------------- 1 | $_value) { 7 | if (is_numeric($_key)) { 8 | $strAttr .= ' '; 9 | } else { 10 | $strAttr .= ' ' . $_key . '=""'; 11 | } 12 | } 13 | 14 | return ''; 15 | } 16 | 17 | function smarty_compiler_bodyclose($arrParams, $smarty){ 18 | $strCode = ''; 23 | $strCode .= ''; 24 | return $strCode; 25 | } 26 | -------------------------------------------------------------------------------- /compiler.head.php: -------------------------------------------------------------------------------- 1 | $_value) { 6 | $strAttr .= ' ' . $_key . '=""'; 7 | } 8 | return ''; 9 | } 10 | 11 | function smarty_compiler_headclose($arrParams, $smarty){ 12 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/FISResource.class.php'); 13 | $strCode = ''; 18 | $strCode .= ''; 19 | return $strCode; 20 | } 21 | -------------------------------------------------------------------------------- /compiler.html.php: -------------------------------------------------------------------------------- 1 | smarty));'; 11 | } 12 | $strCode .= ' ?>'; 13 | 14 | foreach ($arrParams as $_key => $_value) { 15 | if (is_numeric($_key)) { 16 | $strAttr .= ' '; 17 | } else { 18 | $strAttr .= ' ' . $_key . '=""'; 19 | } 20 | } 21 | /** 22 | * 后端的服务器进程为常住进程,需要在页面头部取消上次 register 的事件,防止在 smarty.fetch 方法调用 ,导致清空临时资源数据 23 | * Date: 2016-07-21 24 | * Author: raisezhang@hotmail.com 25 | */ 26 | $strCode .= 'unregisterFilter(\'output\', array(\'FISResource\', \'renderResponse\'));'; 28 | $strCode .= '?>'; 29 | 30 | return $strCode . ""; 31 | } 32 | 33 | function smarty_compiler_htmlclose($arrParams, $smarty){ 34 | $strCode = 'registerFilter(\'output\', array(\'FISResource\', \'renderResponse\'));'; 36 | $strCode .= '?>'; 37 | $strCode .= ''; 38 | return $strCode; 39 | } 40 | -------------------------------------------------------------------------------- /compiler.placeholder.php: -------------------------------------------------------------------------------- 1 | '; 10 | return $strCode; 11 | } -------------------------------------------------------------------------------- /compiler.require.php: -------------------------------------------------------------------------------- 1 | \n";}'; 22 | $strCode .= 'FISResource::load(' . $strName . ',$_smarty_tpl->smarty, '.$async.');'; 23 | } else if (is_string($src)) { 24 | $strCode .= 'if (array_key_exists(\'fis_debug\', $_GET)) {echo "\n";}'; 25 | $strCode .= 'FISResource::addStatic(' . $src . ', ' . $type . ');'; 26 | } 27 | $strCode .= '?>'; 28 | } 29 | 30 | return $strCode; 31 | } 32 | -------------------------------------------------------------------------------- /compiler.script.php: -------------------------------------------------------------------------------- 1 | '; 13 | return $strCode; 14 | } 15 | 16 | function smarty_compiler_scriptclose($params, $smarty){ 17 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/FISResource.class.php'); 18 | $strCode = ''; 32 | return $strCode; 33 | } 34 | -------------------------------------------------------------------------------- /compiler.style.php: -------------------------------------------------------------------------------- 1 | '; 11 | return $strCode; 12 | } 13 | 14 | function smarty_compiler_styleclose($params, $smarty){ 15 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/FISResource.class.php'); 16 | $strCode = ''; 30 | return $strCode; 31 | } 32 | -------------------------------------------------------------------------------- /compiler.uri.php: -------------------------------------------------------------------------------- 1 | smarty);'; 10 | $strCode .= '?>'; 11 | } 12 | return $strCode; 13 | } 14 | -------------------------------------------------------------------------------- /compiler.widget.php: -------------------------------------------------------------------------------- 1 | getConfigDir(); 13 | foreach ($arrConfigDir as $strDir) { 14 | $strPath = preg_replace('/[\\/\\\\]+/', '/', $strDir . '/' . $strFilename); 15 | if(is_file($strPath)){ 16 | self::$arrCached[$strFilename] = $strPath; 17 | return $strPath; 18 | } 19 | } 20 | } 21 | trigger_error('missing map file "' . $strFilename . '"', E_USER_ERROR); 22 | } 23 | } 24 | 25 | function smarty_compiler_widget($arrParams, $smarty){ 26 | //支持1.X widget 通过path属性判断 同时判断是否有name属性 27 | if (isset($arrParams['path'])) { 28 | if(!isset($arrParams['name']) || strpos($arrParams['name'], ':') === false){ 29 | $path = $arrParams['path']; 30 | unset($arrParams['path']); 31 | return getWidgetStrCode($path, $arrParams); 32 | } 33 | } 34 | 35 | $strResourceApiPath = preg_replace('/[\\/\\\\]+/', '/', dirname(__FILE__) . '/FISResource.class.php'); 36 | $strCode = 'smarty);'; 56 | $strCode .= 'if(isset($_tpl_path)){'; 57 | 58 | $strCode .= 'if (array_key_exists(\'fis_debug\', $_GET)) {echo "\n";}'; 59 | 60 | if($bHasCall){ 61 | $strCode .= '$_smarty_tpl->getSubTemplate($_tpl_path, $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, $_smarty_tpl->caching, $_smarty_tpl->cache_lifetime, ' . $strFuncParams . ', Smarty::SCOPE_LOCAL);'; 62 | $strCode .= 'if(is_callable('. $strTplFuncName . ')){'; 63 | $strCode .= $strCallTplFunc; 64 | $strCode .= '}else{'; 65 | $strCode .= 'trigger_error(\'missing function define "\'.' . $strTplFuncName . '.\'" in tpl "\'.$_tpl_path.\'"\', E_USER_ERROR);'; 66 | $strCode .= '}'; 67 | } else { 68 | $strCode .= 'echo $_smarty_tpl->getSubTemplate($_tpl_path, $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, $_smarty_tpl->caching, $_smarty_tpl->cache_lifetime, ' . $strFuncParams . ', Smarty::SCOPE_LOCAL);'; 69 | } 70 | 71 | $strCode .= 'if (array_key_exists(\'fis_debug\', $_GET)) {echo "\n";}'; 72 | 73 | $strCode .= '}else{'; 74 | $strCode .= 'trigger_error(\'unable to locale resource "\'.' . $strName . '.\'"\', E_USER_ERROR);'; 75 | $strCode .= '}'; 76 | $strCode .= 'FISResource::load('.$strName.', $_smarty_tpl->smarty);'; 77 | } else { 78 | trigger_error('undefined widget name in file "' . $smarty->_current_file . '"', E_USER_ERROR); 79 | } 80 | if($bHasCall){ 81 | $strCode .= '}'; 82 | } 83 | $strCode .= '?>'; 84 | return $strCode; 85 | } 86 | 87 | 88 | function getWidgetStrCode($path, $arrParams){ 89 | $strFuncParams = getFuncParams($arrParams); 90 | $path = trim($path,"\""); 91 | $fn = '"smarty_template_function_fis_' . strtr(substr($path, 0, strrpos($path, '/')), '/', '_') . '"'; 92 | $strCode = '\n";}'; 95 | 96 | $strCode .= 'if(is_callable(' . $fn . ')){'; 97 | $strCode .= 'return call_user_func(' . $fn . ',$_smarty_tpl,' . $strFuncParams . ');'; 98 | $strCode .= '}else{'; 99 | $strCode .= '$fis_widget_output = $_smarty_tpl->getSubTemplate("' . $path . '", $_smarty_tpl->cache_id, $_smarty_tpl->compile_id, null, null, '. $strFuncParams.', Smarty::SCOPE_LOCAL);'; 100 | $strCode .= 'if(is_callable(' . $fn .')){'; 101 | $strCode .= 'return call_user_func('. $fn . ',$_smarty_tpl,' . $strFuncParams . ');'; 102 | $strCode .= '}else{'; 103 | $strCode .= 'echo $fis_widget_output;'; 104 | $strCode .= '}'; 105 | 106 | $strCode .= 'if (array_key_exists(\'fis_debug\', $_GET)) {echo "\n";}'; 107 | 108 | $strCode .= '}?>'; 109 | return $strCode; 110 | } 111 | 112 | function getFuncParams($arrParams){ 113 | $arrFuncParams = array(); 114 | foreach ($arrParams as $_key => $_value) { 115 | if (is_int($_key)) { 116 | $arrFuncParams[] = "$_key=>$_value"; 117 | } else { 118 | $arrFuncParams[] = "'$_key'=>$_value"; 119 | } 120 | } 121 | $strFuncParams = 'array(' . implode(',', $arrFuncParams) . ')'; 122 | return $strFuncParams; 123 | } 124 | -------------------------------------------------------------------------------- /function.http_header.php: -------------------------------------------------------------------------------- 1 | 'text/html', 26 | 'json' => 'application/json', 27 | 'javascript' => 'application/x-javascript', 28 | 'js' => 'application/x-javascript', 29 | 'xml' => 'text/xml', 30 | 'stream' => 'application/octet-stream' 31 | ); 32 | if (array_key_exists($type, $mimeTypes)) { 33 | $mime = $mimeTypes[$type]; 34 | } else { 35 | $mime = "text/plain"; 36 | } 37 | header("Content-Type:$mime; charset=$charset;"); 38 | } 39 | 40 | 41 | -------------------------------------------------------------------------------- /modifier.f_escape_callback.php: -------------------------------------------------------------------------------- 1 | ',"'",'"','\\',"\n","\r","/"); 5 | $fis_smarty_modifier_f_escape_data_js_char_values = array('<','>',"\\'",'\\"',"\\\\","\\n","\\r","\\/"); 6 | 7 | function smarty_modifier_f_escape_data($str) 8 | { 9 | 10 | global $fis_smarty_modifier_f_escape_data_js_char_keys; 11 | global $fis_smarty_modifier_f_escape_data_js_char_values; 12 | 13 | $str = strval($str); 14 | $ret = str_replace($fis_smarty_modifier_f_escape_data_js_char_keys, $fis_smarty_modifier_f_escape_data_js_char_values, $str); 15 | return $ret; 16 | } -------------------------------------------------------------------------------- /modifier.f_escape_event.php: -------------------------------------------------------------------------------- 1 | ',"\\","'",'"',"\n","\r","/"); 6 | $fis_smarty_modifier_f_escape_event_char_map_array_values = array('&','<','>',"\\\\","\\'","\\"","\\n","\\r","\\/"); 7 | 8 | function smarty_modifier_f_escape_event($str) 9 | { 10 | global $fis_smarty_modifier_f_escape_event_char_map_array_keys; 11 | global $fis_smarty_modifier_f_escape_event_char_map_array_values; 12 | 13 | $str = strval($str); 14 | return str_replace($fis_smarty_modifier_f_escape_event_char_map_array_keys, $fis_smarty_modifier_f_escape_event_char_map_array_values, $str); 15 | } 16 | -------------------------------------------------------------------------------- /modifier.f_escape_js.php: -------------------------------------------------------------------------------- 1 | ', '\'', '"'); 5 | $fis_smarty_modifier_f_escape_xml_value_array = array('&', '<', '>', ''', '"'); 6 | function smarty_modifier_f_escape_xml($str) 7 | { 8 | global $fis_smarty_modifier_f_escape_xml_search_array; 9 | global $fis_smarty_modifier_f_escape_xml_value_array; 10 | 11 | return str_replace( 12 | $fis_smarty_modifier_f_escape_xml_search_array, 13 | $fis_smarty_modifier_f_escape_xml_value_array, 14 | strval($str) 15 | ); 16 | } -------------------------------------------------------------------------------- /test/FISResource/FISResourceTest.php: -------------------------------------------------------------------------------- 1 | exceptionMessageTest( 24 | "FISResource::load('empty:static/common/ui/a/a.js', new Smarty());", 25 | 'undefined resource "empty:static/common/ui/a/a.js"' 26 | ); 27 | } 28 | 29 | public function testLoadAsync() { 30 | FISResource::load('common:static/common/index/index.js', new Smarty()); 31 | $this->assertEqualsIgnoreSeparator( 32 | FISResource::getResourceMap(), 33 | '{"res":{"common:static/common/ui/async/async.js":{"url":"/static/common/ui/async/async.js","deps":[]}}}' 34 | ); 35 | } 36 | 37 | public function testLoadCycle() { 38 | } 39 | } -------------------------------------------------------------------------------- /test/FISResource/projects/config/common-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "res": { 3 | "common:static/common/ui/a/a.js": { 4 | "uri": "/static/common/ui/a/a.js", 5 | "type": "js", 6 | "deps": [ 7 | "common:static/common/ui/b/b.js" 8 | ] 9 | }, 10 | "common:static/common/ui/b/b.js": { 11 | "uri": "/static/common/ui/b/b.js", 12 | "type": "js", 13 | "deps": [ 14 | "common:static/common/ui/c/c.js" 15 | ] 16 | }, 17 | "common:static/common/ui/c/c.js": { 18 | "uri": "/static/common/ui/b/b.js", 19 | "type": "js", 20 | "deps": [ 21 | "common:static/common/ui/a/a.js" 22 | ] 23 | }, 24 | "common:static/common/ui/async/async.js": { 25 | "uri": "/static/common/ui/async/async.js", 26 | "type": "js", 27 | "deps": [ 28 | ] 29 | }, 30 | "common:static/common/index/index.js": { 31 | "uri": "/static/common/index/index.js", 32 | "type": "js", 33 | "deps": [ 34 | "common:static/common/ui/a/a.js" 35 | ], 36 | "extras": { 37 | "async": [ 38 | "common:static/common/ui/async/async.js" 39 | ] 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /test/FISResource/projects/config/empty-map.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /test/FISResource/projects/config/map.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fex-team/fis-plus-smarty-plugin/f977c6ec8100bde760123c764bb10402b5f0ebf1/test/FISResource/projects/config/map.json -------------------------------------------------------------------------------- /test/SmartyPluginTest.php: -------------------------------------------------------------------------------- 1 | assertEqualsIgnoreSeparator($expect, $e->getMessage()); 11 | } 12 | } 13 | 14 | 15 | protected function assertFileEqualsIgnoreSeparator($expect, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) { 16 | $this->assertEquals( 17 | str_replace("\r\n", "\n", file_get_contents($expect)), 18 | str_replace("\r\n", "\n", file_get_contents($actual)), 19 | $message, 20 | $delta, 21 | $maxDepth, 22 | $canonicalize, 23 | $ignoreCase 24 | ); 25 | } 26 | 27 | protected function assertEqualsIgnoreSeparator($expect, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) { 28 | $this->assertEquals( 29 | str_replace("\r\n", "\n", $expect), 30 | str_replace("\r\n", "\n", $actual), 31 | $message, 32 | $delta, 33 | $maxDepth, 34 | $canonicalize, 35 | $ignoreCase 36 | ); 37 | } 38 | 39 | protected function assertContainsIgnoreSeparator($needle, $haystack, $message = '', $ignoreCase = false, $checkForObjectIdentity = TRUE) { 40 | $this->assertContains( 41 | str_replace("\r\n", "\n", $needle), 42 | str_replace("\r\n", "\n", $haystack), 43 | $message, 44 | $ignoreCase, 45 | $checkForObjectIdentity 46 | ); 47 | } 48 | 49 | protected function assertArrayEqualsIgnoreOrder($expected, $actual, $msg = ''){ 50 | $this->assertEquals(count($expected), count($actual), $msg); 51 | foreach($actual as $value){ 52 | $this->assertTrue(in_array($value, $expected), $msg); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/TestEnv.php: -------------------------------------------------------------------------------- 1 | ,配置数据必须是名为$config的变量 22 | * @return bool 23 | */ 24 | public static function loadConfig($path) { 25 | if(is_file($path)){ 26 | $config = array(); 27 | try { 28 | include $path; 29 | } catch (Exception $e) { 30 | Log::error("配置文件[{$path}]解析失败:" . $e->getMessage()); 31 | } 32 | self::merge($config); 33 | return self::$config; 34 | } else { 35 | Log::warning("配置文件[{$path}]不存在."); 36 | } 37 | return false; 38 | } 39 | 40 | /** 41 | * @static 42 | * @param $key 43 | * @param $key1 44 | * @return null 返回配置信息 45 | */ 46 | public static function getConfig($key,$key1=null){ 47 | if(!$key){ 48 | return null; 49 | }else if($key1){ 50 | return self::$config[$key][$key1]; 51 | }else{ 52 | return self::$config[$key]; 53 | } 54 | } 55 | 56 | /** 57 | * 将数据递归合并到当前配置数据上 58 | * @static 59 | * @param mixed $data 要合并的关联数组数据 60 | */ 61 | public static function merge($data){ 62 | self::_merge(self::$config, $data); 63 | } 64 | /** 65 | * 递归合并数据函数 66 | * @static 67 | * @param array $source 68 | * @param mixed $data 69 | */ 70 | private static function _merge(&$source, $data){ 71 | if(is_array($data)){ 72 | foreach($data as $key => $value){ 73 | if(array_key_exists($key, $source)){ 74 | self::_merge($source[$key], $value); 75 | } else { 76 | $source[$key] = $value; 77 | } 78 | } 79 | } else { 80 | $source = $data; 81 | } 82 | } 83 | 84 | 85 | /** 86 | * 修改index.php的内容 87 | */ 88 | public static function changeIndex(){ 89 | $index_path = self::$product['outputdir'].'index.php'; 90 | $root = self::$rootPath; 91 | $pro_name = self::$product['name']; 92 | foreach(self::$product['tpls'] as $tpl){ 93 | unlink($index_path); 94 | copy(self::$rootPath.'index.php', $index_path); 95 | $newdis="\$smarty->display(\$root.'$tpl');"; 96 | file_put_contents($index_path,str_replace('<*display*>',$newdis,file_get_contents($index_path))); 97 | exec("php -f $index_path $root $pro_name $tpl",$info, $ret); 98 | if($ret != 0) 99 | exit("display tpl error!"); 100 | } 101 | } 102 | } 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /test/performance/changeall.class.php: -------------------------------------------------------------------------------- 1 | $value){ 9 | $change = new Change($pro); 10 | $change->ChangeIndex(); 11 | } 12 | -------------------------------------------------------------------------------- /test/performance/config.php: -------------------------------------------------------------------------------- 1 | array( //产品线目录 9 | 'batman'=>array( 10 | 'name'=>'batman', 11 | 'outputdir' => BATMAN_PATH, //使用新版本编译后的产出 12 | "tpls" =>array( 13 | "/template/drive/widget/list/list.tpl", 14 | "/template/index/page/index.tpl" 15 | ) 16 | ) 17 | ) 18 | ); 19 | -------------------------------------------------------------------------------- /test/performance/index.php: -------------------------------------------------------------------------------- 1 | setTemplateDir($root . '/template'); 9 | $smarty->setConfigDir($root . '/config'); 10 | $smarty->addPluginsDir($root . '/plugin'); 11 | $smarty->setLeftDelimiter('{%'); 12 | $smarty->setRightDelimiter('%}'); 13 | 14 | xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); 15 | 16 | <*display*> 17 | 18 | // stop profiler 19 | $xhprof_data = xhprof_disable(); 20 | 21 | // display raw xhprof data for the profiler run 22 | #print_r($xhprof_data); 23 | 24 | 25 | $XHPROF_ROOT = realpath('/home/work/repos/xhprof-0.9.2'); 26 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php"; 27 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php"; 28 | 29 | // save raw data for this profiler run using default 30 | // implementation of iXHProfRuns. 31 | $xhprof_runs = new XHProfRuns_Default(); 32 | 33 | // save the run under a namespace "xhprof_foo" 34 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo"); 35 | $time = date('Y-m-d H:i:s', time()); 36 | $result = "$argv[2]$argv[3]detail$time"; 37 | file_put_contents($argv[1].'/result.html', str_replace("", $result, file_get_contents($argv[1].'/result.html'))); 38 | -------------------------------------------------------------------------------- /test/performance/product_code_ready.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TEST_PATH=/home/work/repos/fis-plus-smarty-plugin/test/performance 4 | cd ${TEST_PATH} 5 | 6 | BATMAN_SVN=https://svn.baidu.com/app/search/lbs-webapp/trunk/mmap/batman 7 | BATMAN_DIR=./product_code/batman 8 | 9 | svn co --username=tianlili --password=tianlili --no-auth-cache ${BATMAN_SVN} ${BATMAN_DIR} 10 | -------------------------------------------------------------------------------- /test/performance/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TEST_PATH=/home/work/repos/fis-plus-smarty-plugin/test/performance 4 | cd ${TEST_PATH} 5 | 6 | BATMAN_CODE_PATH=${TEST_PATH}/product_code/batman 7 | BATMAN_OUTPUT_PATH=${TEST_PATH}/product_output/batman 8 | BATMAN_MODULES=(transit place common index addr feedback drive walk) 9 | 10 | #batman 11 | rm -rf ${BATMAN_OUTPUT_PATH} 12 | for module in ${BATMAN_MODULES[@]} 13 | do 14 | cd ${BATMAN_CODE_PATH}/$module 15 | fisp release -d ${BATMAN_OUTPUT_PATH} --no-color 16 | done 17 | 18 | fisp server install pc --root ${BATMAN_OUTPUT_PATH} 19 | 20 | cd ${TEST_PATH} 21 | cp ../../*.*.php ${BATMAN_OUTPUT_PATH}/plugin 22 | 23 | -------------------------------------------------------------------------------- /test/performance/result_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | diff file list 6 | 24 | 25 | 26 | 27 |

Fis-plus Smarty Plugin Performance:

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
product name tpl path result time
36 | 37 | 38 | -------------------------------------------------------------------------------- /test/performance/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TEST_PATH=/home/work/repos/fis-plus-smarty-plugin/test/performance 4 | FISP_PATH=/home/work/lib/node_modules/fis-plus 5 | cd ${TEST_PATH} 6 | sh product_code_ready.sh 7 | npm cache clean 8 | npm update -g fis-plus 9 | cd ${FISP_PATH} 10 | npm install fis-preprocessor-inline 11 | cd ${TEST_PATH} 12 | sh release.sh 13 | sleep 2s 14 | cp result_template.html result.html 15 | php -f changeall.class.php 16 | -------------------------------------------------------------------------------- /test/project/script_priority/fis-conf.js: -------------------------------------------------------------------------------- 1 | fis.config.merge({ 2 | namespace: "priority" 3 | }); -------------------------------------------------------------------------------- /test/project/script_priority/page/index.tpl: -------------------------------------------------------------------------------- 1 | {%html%} 2 | 3 | {%head%} 4 | priority test 5 | {%/head%} 6 | {%body%} 7 | {%widget name="priority:widget/a.tpl"%} 8 | {%widget name="priority:widget/b.tpl"%} 9 | {%widget name="priority:widget/c.tpl"%} 10 | {%widget name="priority:widget/d.tpl"%} 11 | {%/body%} 12 | {%/html%} -------------------------------------------------------------------------------- /test/project/script_priority/widget/a.tpl: -------------------------------------------------------------------------------- 1 | {%script%} 2 | console.log('widget:a'); 3 | {%/script%} -------------------------------------------------------------------------------- /test/project/script_priority/widget/b.tpl: -------------------------------------------------------------------------------- 1 | {%script priority="1"%} 2 | console.log('widget:b'); 3 | {%/script%} -------------------------------------------------------------------------------- /test/project/script_priority/widget/c.tpl: -------------------------------------------------------------------------------- 1 | {%script priority="3"%} 2 | console.log('widget:c'); 3 | {%/script%} -------------------------------------------------------------------------------- /test/project/script_priority/widget/d.tpl: -------------------------------------------------------------------------------- 1 | {%script priority="4"%} 2 | console.log('widget:d'); 3 | {%/script%} --------------------------------------------------------------------------------