PageRenderTime 63ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pkp/classes/template/PKPTemplateManager.inc.php

https://github.com/lib-uoguelph-ca/ocs
PHP | 1201 lines | 733 code | 154 blank | 314 comment | 181 complexity | 6e6935eeb0fde84eb94b0809076f61ab MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * @defgroup template
  4. */
  5. /**
  6. * @file classes/template/PKPTemplateManager.inc.php
  7. *
  8. * Copyright (c) 2000-2012 John Willinsky
  9. * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
  10. *
  11. * @class TemplateManager
  12. * @ingroup template
  13. *
  14. * @brief Class for accessing the underlying template engine.
  15. * Currently integrated with Smarty (from http://smarty.php.net/).
  16. */
  17. // $Id$
  18. /* This definition is required by Smarty */
  19. define('SMARTY_DIR', Core::getBaseDir() . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'pkp' . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'smarty' . DIRECTORY_SEPARATOR);
  20. require_once('Smarty.class.php');
  21. require_once('plugins/modifier.escape.php'); // Seems to be needed?
  22. define('CACHEABILITY_NO_CACHE', 'no-cache');
  23. define('CACHEABILITY_NO_STORE', 'no-store');
  24. define('CACHEABILITY_PUBLIC', 'public');
  25. define('CACHEABILITY_MUST_REVALIDATE', 'must-revalidate');
  26. define('CACHEABILITY_PROXY_REVALIDATE', 'proxy-revalidate');
  27. class PKPTemplateManager extends Smarty {
  28. /** @var $styleSheets array of URLs to stylesheets */
  29. var $styleSheets;
  30. /** @var $initialized Kludge because of reference problems with
  31. TemplateManager::getManager() invoked during constructor process */
  32. var $initialized;
  33. /** @var $cacheability string Type of cacheability (Cache-Control). */
  34. var $cacheability;
  35. /**
  36. * Constructor.
  37. * Initialize template engine and assign basic template variables.
  38. * @param $request PKPRequest FIXME: is optional for backwards compatibility only - make mandatory
  39. */
  40. function PKPTemplateManager($request = null) {
  41. // FIXME: for backwards compatibility only - remove
  42. if (!isset($request)) {
  43. if (Config::getVar('debug', 'deprecation_warnings')) trigger_error('Deprecated function call.');
  44. $request =& Registry::get('request');
  45. }
  46. assert(is_a($request, 'PKPRequest'));
  47. // Retrieve the router
  48. $router =& $request->getRouter();
  49. assert(is_a($router, 'PKPRouter'));
  50. parent::Smarty();
  51. // Set up Smarty configuration
  52. $baseDir = Core::getBaseDir();
  53. $cachePath = CacheManager::getFileCachePath();
  54. // Set the default template dir (app's template dir)
  55. $this->app_template_dir = $baseDir . DIRECTORY_SEPARATOR . 'templates';
  56. // Set fallback template dir (core's template dir)
  57. $this->core_template_dir = $baseDir . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'pkp' . DIRECTORY_SEPARATOR . 'templates';
  58. $this->template_dir = array($this->app_template_dir, $this->core_template_dir);
  59. $this->compile_dir = $cachePath . DIRECTORY_SEPARATOR . 't_compile';
  60. $this->config_dir = $cachePath . DIRECTORY_SEPARATOR . 't_config';
  61. $this->cache_dir = $cachePath . DIRECTORY_SEPARATOR . 't_cache';
  62. // Assign common variables
  63. $this->styleSheets = array();
  64. $this->assign_by_ref('stylesheets', $this->styleSheets);
  65. $this->cacheability = CACHEABILITY_NO_STORE; // Safe default
  66. $this->assign('defaultCharset', Config::getVar('i18n', 'client_charset'));
  67. $this->assign('baseUrl', $request->getBaseUrl());
  68. $this->assign('requiresFormRequest', $request->isPost());
  69. if (is_a($router, 'PKPPageRouter')) $this->assign('requestedPage', $router->getRequestedPage($request));
  70. $this->assign('currentUrl', $request->getCompleteUrl());
  71. $this->assign('dateFormatTrunc', Config::getVar('general', 'date_format_trunc'));
  72. $this->assign('dateFormatShort', Config::getVar('general', 'date_format_short'));
  73. $this->assign('dateFormatLong', Config::getVar('general', 'date_format_long'));
  74. $this->assign('datetimeFormatShort', Config::getVar('general', 'datetime_format_short'));
  75. $this->assign('datetimeFormatLong', Config::getVar('general', 'datetime_format_long'));
  76. $this->assign('timeFormat', Config::getVar('general', 'time_format'));
  77. $this->assign('allowCDN', Config::getVar('general', 'enable_cdn'));
  78. $locale = AppLocale::getLocale();
  79. $this->assign('currentLocale', $locale);
  80. // If there's a locale-specific stylesheet, add it.
  81. if (($localeStyleSheet = AppLocale::getLocaleStyleSheet($locale)) != null) $this->addStyleSheet($request->getBaseUrl() . '/' . $localeStyleSheet);
  82. $application =& PKPApplication::getApplication();
  83. $this->assign('pageTitle', $application->getNameKey());
  84. // Register custom functions
  85. $this->register_modifier('translate', array('AppLocale', 'translate'));
  86. $this->register_modifier('get_value', array(&$this, 'smartyGetValue'));
  87. $this->register_modifier('strip_unsafe_html', array('String', 'stripUnsafeHtml'));
  88. $this->register_modifier('String_substr', array('String', 'substr'));
  89. $this->register_modifier('to_array', array(&$this, 'smartyToArray'));
  90. $this->register_modifier('concat', array(&$this, 'smartyConcat'));
  91. $this->register_modifier('escape', array(&$this, 'smartyEscape'));
  92. $this->register_modifier('strtotime', array(&$this, 'smartyStrtotime'));
  93. $this->register_modifier('explode', array(&$this, 'smartyExplode'));
  94. $this->register_modifier('assign', array(&$this, 'smartyAssign'));
  95. $this->register_function('translate', array(&$this, 'smartyTranslate'));
  96. $this->register_function('flush', array(&$this, 'smartyFlush'));
  97. $this->register_function('call_hook', array(&$this, 'smartyCallHook'));
  98. $this->register_function('html_options_translate', array(&$this, 'smartyHtmlOptionsTranslate'));
  99. $this->register_block('iterate', array(&$this, 'smartyIterate'));
  100. $this->register_function('call_progress_function', array(&$this, 'smartyCallProgressFunction'));
  101. $this->register_function('page_links', array(&$this, 'smartyPageLinks'));
  102. $this->register_function('page_info', array(&$this, 'smartyPageInfo'));
  103. $this->register_function('get_help_id', array(&$this, 'smartyGetHelpId'));
  104. $this->register_function('icon', array(&$this, 'smartyIcon'));
  105. $this->register_function('help_topic', array(&$this, 'smartyHelpTopic'));
  106. $this->register_function('sort_heading', array(&$this, 'smartySortHeading'));
  107. $this->register_function('sort_search', array(&$this, 'smartySortSearch'));
  108. $this->register_function('get_debug_info', array(&$this, 'smartyGetDebugInfo'));
  109. $this->register_function('assign_mailto', array(&$this, 'smartyAssignMailto'));
  110. $this->register_function('display_template', array(&$this, 'smartyDisplayTemplate'));
  111. $this->register_modifier('truncate', array(&$this, 'smartyTruncate'));
  112. // JS UI components
  113. $this->register_function('load_div', array(&$this, 'smartyLoadUrlInDiv'));
  114. $this->register_function('modal', array(&$this, 'smartyModal'));
  115. $this->register_function('confirm', array(&$this, 'smartyConfirm'));
  116. $this->register_function('ajax_upload', array(&$this, 'smartyAjaxUpload'));
  117. $this->register_function('init_tabs', array(&$this, 'smartyInitTabs'));
  118. // register the resource name "core"
  119. $this->register_resource("core", array(array(&$this, 'smartyResourceCoreGetTemplate'),
  120. array(&$this, 'smartyResourceCoreGetTimestamp'),
  121. array(&$this, 'smartyResourceCoreGetSecure'),
  122. array(&$this, 'smartyResourceCoreGetTrusted')));
  123. $this->register_function('url', array(&$this, 'smartyUrl'));
  124. // ajax load into a div
  125. $this->register_function('load_url_in_div', array(&$this, 'smartyLoadUrlInDiv'));
  126. if (!defined('SESSION_DISABLE_INIT')) {
  127. /**
  128. * Kludge to make sure no code that tries to connect to
  129. * the database is executed (e.g., when loading
  130. * installer pages).
  131. */
  132. $this->assign('isUserLoggedIn', Validation::isLoggedIn());
  133. $versionDAO =& DAORegistry::getDAO('VersionDAO');
  134. $currentVersion = $versionDAO->getCurrentVersion();
  135. $this->assign('currentVersionString', $currentVersion->getVersionString());
  136. $this->assign('itemsPerPage', Config::getVar('interface', 'items_per_page'));
  137. $this->assign('numPageLinks', Config::getVar('interface', 'page_links'));
  138. }
  139. $this->initialized = false;
  140. }
  141. /**
  142. * Override the Smarty {include ...} function to allow hooks to be
  143. * called.
  144. */
  145. function _smarty_include($params) {
  146. if (!HookRegistry::call('TemplateManager::include', array(&$this, &$params))) {
  147. return parent::_smarty_include($params);
  148. }
  149. return false;
  150. }
  151. /**
  152. * Flag the page as cacheable (or not).
  153. * @param $cacheability boolean optional
  154. */
  155. function setCacheability($cacheability = CACHEABILITY_PUBLIC) {
  156. $this->cacheability = $cacheability;
  157. }
  158. function initialize() {
  159. // This code cannot be called in the constructor because of
  160. // reference problems, i.e. callers that need getManager fail.
  161. // Load the block plugins.
  162. $plugins =& PluginRegistry::loadCategory('blocks');
  163. $this->initialized = true;
  164. }
  165. function addStyleSheet($url) {
  166. array_push($this->styleSheets, $url);
  167. }
  168. /**
  169. * Display the template.
  170. */
  171. function display($template, $sendContentType = 'text/html', $hookName = 'TemplateManager::display') {
  172. if (!$this->initialized) {
  173. $this->initialize();
  174. }
  175. $charset = Config::getVar('i18n', 'client_charset');
  176. // Give any hooks registered against the TemplateManager
  177. // the opportunity to modify behavior; otherwise, display
  178. // the template as usual.
  179. $output = null;
  180. if (!HookRegistry::call($hookName, array(&$this, &$template, &$sendContentType, &$charset, &$output))) {
  181. // If this is the main display call, send headers.
  182. if ($hookName == 'TemplateManager::display') {
  183. // Explicitly set the character encoding
  184. // Required in case server is using Apache's
  185. // AddDefaultCharset directive (which can
  186. // prevent browser auto-detection of the proper
  187. // character set)
  188. header('Content-Type: ' . $sendContentType . '; charset=' . $charset);
  189. // Send caching info
  190. header('Cache-Control: ' . $this->cacheability);
  191. }
  192. // Actually display the template.
  193. parent::display($template);
  194. } else {
  195. // Display the results of the plugin.
  196. echo $output;
  197. }
  198. }
  199. /**
  200. * Display templates from Smarty and allow hook overrides
  201. *
  202. * Smarty usage: {display_template template="name.tpl" hookname="My::Hook::Name"}
  203. */
  204. function smartyDisplayTemplate($params, &$smarty) {
  205. $templateMgr =& TemplateManager::getManager();
  206. // This is basically a wrapper for display()
  207. if (isset($params['template'])) {
  208. $templateMgr->display($params['template'], "", $params['hookname']);
  209. }
  210. }
  211. /**
  212. * Clear template compile and cache directories.
  213. */
  214. function clearTemplateCache() {
  215. $this->clear_compiled_tpl();
  216. $this->clear_all_cache();
  217. }
  218. /**
  219. * Return an instance of the template manager.
  220. * @param $request PKPRequest FIXME: is optional for backwards compatibility only - make mandatory
  221. * @return TemplateManager the template manager object
  222. */
  223. function &getManager($request = null) {
  224. $instance =& Registry::get('templateManager', true, null);
  225. if ($instance === null) {
  226. $instance = new TemplateManager($request);
  227. }
  228. return $instance;
  229. }
  230. //
  231. // Custom Template Resource "Core"
  232. // The Core Template Resource is points to the fallback template_dir in the core
  233. //
  234. function smartyResourceCoreGetTemplate($template, &$templateSource, &$smarty) {
  235. $templateSource = file_get_contents($this->core_template_dir . DIRECTORY_SEPARATOR . $template);
  236. return true;
  237. }
  238. function smartyResourceCoreGetTimestamp($template, &$templateTimestamp, &$smarty) {
  239. $templateSource = $this->core_template_dir . DIRECTORY_SEPARATOR . $template;
  240. if (!file_exists($templateSource)) return false;
  241. $templateTimestamp = filemtime($templateSource);
  242. return true;
  243. }
  244. function smartyResourceCoreGetTecure($template, &$smarty) {
  245. return true;
  246. }
  247. function smartyResourceCoreGetTrusted($template, &$smarty) {}
  248. //
  249. // Custom template functions, modifiers, etc.
  250. //
  251. /**
  252. * Smarty usage: {translate key="localization.key.name" [paramName="paramValue" ...]}
  253. *
  254. * Custom Smarty function for translating localization keys.
  255. * Substitution works by replacing tokens like "{$foo}" with the value of the parameter named "foo" (if supplied).
  256. * @params $params array associative array, must contain "key" parameter for string to translate plus zero or more named parameters for substitution.
  257. * Translation variables can be specified also as an optional
  258. * associative array named "params".
  259. * @params $smarty Smarty
  260. * @return string the localized string, including any parameter substitutions
  261. */
  262. function smartyTranslate($params, &$smarty) {
  263. if (isset($params) && !empty($params)) {
  264. if (!isset($params['key'])) return __('');
  265. $key = $params['key'];
  266. unset($params['key']);
  267. if (isset($params['params']) && is_array($params['params'])) {
  268. $paramsArray = $params['params'];
  269. unset($params['params']);
  270. $params = array_merge($params, $paramsArray);
  271. }
  272. return __($key, $params);
  273. }
  274. }
  275. /**
  276. * Smarty usage: {assign_mailto var="varName" address="email@address.com" ...]}
  277. *
  278. * Generates a hex-encoded mailto address and assigns it to the variable name specified..
  279. */
  280. function smartyAssignMailto($params, &$smarty) {
  281. if (isset($params['var']) && isset($params['address'])) {
  282. // Password encoding code taken from Smarty's mailto
  283. // function.
  284. $address = $params['address'];
  285. $address_encode = '';
  286. for ($x=0; $x < strlen($address); $x++) {
  287. if(preg_match('!\w!',$address[$x])) {
  288. $address_encode .= '%' . bin2hex($address[$x]);
  289. } else {
  290. $address_encode .= $address[$x];
  291. }
  292. }
  293. $text_encode = '';
  294. for ($x=0; $x < strlen($text); $x++) {
  295. $text_encode .= '&#x' . bin2hex($text[$x]).';';
  296. }
  297. $mailto = "&#109;&#97;&#105;&#108;&#116;&#111;&#58;";
  298. $smarty->assign($params['var'], $mailto . $address_encode);
  299. }
  300. }
  301. /**
  302. * Smarty usage: {html_options_translate ...}
  303. * For parameter usage, see http://smarty.php.net/manual/en/language.function.html.options.php
  304. *
  305. * Identical to Smarty's "html_options" function except option values are translated from i18n keys.
  306. * @params $params array
  307. * @params $smarty Smarty
  308. */
  309. function smartyHtmlOptionsTranslate($params, &$smarty) {
  310. if (isset($params['options'])) {
  311. if (isset($params['translateValues'])) {
  312. // Translate values AND output
  313. $newOptions = array();
  314. foreach ($params['options'] as $k => $v) {
  315. $newOptions[__($k)] = __($v);
  316. }
  317. $params['options'] = $newOptions;
  318. } else {
  319. // Just translate output
  320. $params['options'] = array_map(array('AppLocale', 'translate'), $params['options']);
  321. }
  322. }
  323. if (isset($params['output'])) {
  324. $params['output'] = array_map(array('AppLocale', 'translate'), $params['output']);
  325. }
  326. if (isset($params['values']) && isset($params['translateValues'])) {
  327. $params['values'] = array_map(array('AppLocale', 'translate'), $params['values']);
  328. }
  329. require_once($this->_get_plugin_filepath('function','html_options'));
  330. return smarty_function_html_options($params, $smarty);
  331. }
  332. /**
  333. * Iterator function for looping through objects extending the
  334. * ItemIterator class.
  335. * Parameters:
  336. * - from: Name of template variable containing iterator
  337. * - item: Name of template variable to receive each item
  338. * - key: (optional) Name of variable to receive index of current item
  339. */
  340. function smartyIterate($params, $content, &$smarty, &$repeat) {
  341. $iterator =& $smarty->get_template_vars($params['from']);
  342. if (isset($params['key'])) {
  343. if (empty($content)) $smarty->assign($params['key'], 1);
  344. else $smarty->assign($params['key'], $smarty->get_template_vars($params['key'])+1);
  345. }
  346. // If the iterator is empty, we're finished.
  347. if (!$iterator || $iterator->eof()) {
  348. if (!$repeat) return $content;
  349. $repeat = false;
  350. return '';
  351. }
  352. $repeat = true;
  353. if (isset($params['key'])) {
  354. list($key, $value) = $iterator->nextWithKey();
  355. $smarty->assign_by_ref($params['item'], $value);
  356. $smarty->assign_by_ref($params['key'], $key);
  357. } else {
  358. $smarty->assign_by_ref($params['item'], $iterator->next());
  359. }
  360. return $content;
  361. }
  362. /**
  363. * Smarty usage: {icon name="image name" alt="alternative name" url="url path"}
  364. *
  365. * Custom Smarty function for generating anchor tag with optional url
  366. * @params $params array associative array, must contain "name" paramater to create image anchor tag
  367. * @return string <a href="url"><img src="path to image/image name" ... /></a>
  368. */
  369. function smartyIcon($params, &$smarty) {
  370. if (isset($params) && !empty($params)) {
  371. $iconHtml = '';
  372. if (isset($params['name'])) {
  373. // build image tag with standarized size of 16x16
  374. $disabled = (isset($params['disabled']) && !empty($params['disabled']));
  375. if (!isset($params['path'])) $params['path'] = 'lib/pkp/templates/images/icons/';
  376. $iconHtml = '<img src="' . $smarty->get_template_vars('baseUrl') . '/' . $params['path'];
  377. $iconHtml .= $params['name'] . ($disabled ? '_disabled' : '') . '.gif" width="16" height="14" alt="';
  378. // if alt parameter specified use it, otherwise use localization version
  379. if (isset($params['alt'])) {
  380. $iconHtml .= $params['alt'];
  381. } else {
  382. $iconHtml .= __('icon.'.$params['name'].'.alt');
  383. }
  384. $iconHtml .= '" ';
  385. // if onclick parameter specified use it
  386. if (isset($params['onclick'])) {
  387. $iconHtml .= 'onclick="' . $params['onclick'] . '" ';
  388. }
  389. $iconHtml .= '/>';
  390. // build anchor with url if specified as a parameter
  391. if (!$disabled && isset($params['url'])) {
  392. $iconHtml = '<a href="' . $params['url'] . '" class="icon">' . $iconHtml . '</a>';
  393. }
  394. }
  395. return $iconHtml;
  396. }
  397. }
  398. /**
  399. * Display page information for a listing of items that has been
  400. * divided onto multiple pages.
  401. * Usage:
  402. * {page_info from=$myIterator}
  403. */
  404. function smartyPageInfo($params, &$smarty) {
  405. $iterator = $params['iterator'];
  406. $itemsPerPage = $smarty->get_template_vars('itemsPerPage');
  407. if (!is_numeric($itemsPerPage)) $itemsPerPage=25;
  408. $page = $iterator->getPage();
  409. $pageCount = $iterator->getPageCount();
  410. $itemTotal = $iterator->getCount();
  411. if ($pageCount<1) return '';
  412. $from = (($page - 1) * $itemsPerPage) + 1;
  413. $to = min($itemTotal, $page * $itemsPerPage);
  414. return __('navigation.items', array(
  415. 'from' => ($to===0?0:$from),
  416. 'to' => $to,
  417. 'total' => $itemTotal
  418. ));
  419. }
  420. /**
  421. * Flush the output buffer. This is useful in cases where Smarty templates
  422. * are calling functions that take a while to execute so that they can display
  423. * a progress indicator or a message stating that the operation may take a while.
  424. */
  425. function smartyFlush($params, &$smarty) {
  426. $smarty->flush();
  427. }
  428. function flush() {
  429. while (ob_get_level()) {
  430. ob_end_flush();
  431. }
  432. flush();
  433. }
  434. /**
  435. * Call hooks from a template.
  436. */
  437. function smartyCallHook($params, &$smarty) {
  438. $output = null;
  439. HookRegistry::call($params['name'], array(&$params, &$smarty, &$output));
  440. return $output;
  441. }
  442. /**
  443. * Get debugging information and assign it to the template.
  444. */
  445. function smartyGetDebugInfo($params, &$smarty) {
  446. if (Config::getVar('debug', 'show_stats')) {
  447. $smarty->assign('enableDebugStats', true);
  448. // provide information from the PKPProfiler class
  449. $pkpProfiler =& Registry::get('system.debug.profiler');
  450. foreach ($pkpProfiler->getData() as $output => $value) {
  451. $smarty->assign($output, $value);
  452. }
  453. $smarty->assign('pqpCss', Request::getBaseUrl() . '/lib/pkp/lib/pqp/css/pQp.css');
  454. $smarty->assign('pqpTemplate', BASE_SYS_DIR . '/lib/pkp/lib/pqp/pqp.tpl');
  455. }
  456. }
  457. /**
  458. * Generate a URL into a PKPApp. (This is a wrapper around Dispatcher::url() to make it available to Smarty templates.)
  459. */
  460. function smartyUrl($params, &$smarty) {
  461. if ( !isset($params['context']) ) {
  462. // Extract the variables named in $paramList, and remove them
  463. // from the params array. Variables remaining in params will be
  464. // passed along to Request::url as extra parameters.
  465. $context = array();
  466. $contextList = Application::getContextList();
  467. foreach ($contextList as $contextName) {
  468. if (isset($params[$contextName])) {
  469. $context[$contextName] = $params[$contextName];
  470. unset($params[$contextName]);
  471. } else {
  472. $context[$contextName] = null;
  473. }
  474. }
  475. $params['context'] = $context;
  476. }
  477. // Extract the variables named in $paramList, and remove them
  478. // from the params array. Variables remaining in params will be
  479. // passed along to Request::url as extra parameters.
  480. $paramList = array('router', 'context', 'page', 'component', 'op', 'path', 'anchor', 'escape');
  481. foreach ($paramList as $param) {
  482. if (isset($params[$param])) {
  483. $$param = $params[$param];
  484. unset($params[$param]);
  485. } else {
  486. $$param = null;
  487. }
  488. }
  489. // Set the default router
  490. $request =& PKPApplication::getRequest();
  491. if (is_null($router)) {
  492. if (is_a($request->getRouter(), 'PKPComponentRouter')) {
  493. $router = ROUTE_COMPONENT;
  494. } else {
  495. $router = ROUTE_PAGE;
  496. }
  497. }
  498. // Check the router
  499. $dispatcher =& PKPApplication::getDispatcher();
  500. $routerShortcuts = array_keys($dispatcher->getRouterNames());
  501. assert(in_array($router, $routerShortcuts));
  502. // Identify the handler
  503. switch($router) {
  504. case ROUTE_PAGE:
  505. $handler = $page;
  506. break;
  507. case ROUTE_COMPONENT:
  508. $handler = $component;
  509. break;
  510. default:
  511. // Unknown router type
  512. assert(false);
  513. }
  514. // Let the dispatcher create the url
  515. return $dispatcher->url($request, $router, $context, $handler, $op, $path, $params, $anchor, !isset($escape) || $escape);
  516. }
  517. function setProgressFunction($progressFunction) {
  518. Registry::set('progressFunctionCallback', $progressFunction);
  519. }
  520. function smartyCallProgressFunction($params, &$smarty) {
  521. $progressFunctionCallback =& Registry::get('progressFunctionCallback');
  522. if ($progressFunctionCallback) {
  523. call_user_func($progressFunctionCallback);
  524. }
  525. }
  526. function updateProgressBar($progress, $total) {
  527. static $lastPercent;
  528. $percent = round($progress * 100 / $total);
  529. if (!isset($lastPercent) || $lastPercent != $percent) {
  530. for($i=1; $i <= $percent-$lastPercent; $i++) {
  531. echo '<img src="' . Request::getBaseUrl() . '/templates/images/progbar.gif" width="5" height="15">';
  532. }
  533. }
  534. $lastPercent = $percent;
  535. $templateMgr =& TemplateManager::getManager();
  536. $templateMgr->flush();
  537. }
  538. /**
  539. * Display page links for a listing of items that has been
  540. * divided onto multiple pages.
  541. * Usage:
  542. * {page_links
  543. * name="nameMustMatchGetRangeInfoCall"
  544. * iterator=$myIterator
  545. * additional_param=myAdditionalParameterValue
  546. * }
  547. */
  548. function smartyPageLinks($params, &$smarty) {
  549. $iterator = $params['iterator'];
  550. $name = $params['name'];
  551. if (isset($params['params']) && is_array($params['params'])) {
  552. $extraParams = $params['params'];
  553. unset($params['params']);
  554. $params = array_merge($params, $extraParams);
  555. }
  556. if (isset($params['anchor'])) {
  557. $anchor = $params['anchor'];
  558. unset($params['anchor']);
  559. } else {
  560. $anchor = null;
  561. }
  562. if (isset($params['all_extra'])) {
  563. $allExtra = ' ' . $params['all_extra'];
  564. unset($params['all_extra']);
  565. } else {
  566. $allExtra = '';
  567. }
  568. unset($params['iterator']);
  569. unset($params['name']);
  570. $numPageLinks = $smarty->get_template_vars('numPageLinks');
  571. if (!is_numeric($numPageLinks)) $numPageLinks=10;
  572. $page = $iterator->getPage();
  573. $pageCount = $iterator->getPageCount();
  574. $itemTotal = $iterator->getCount();
  575. $pageBase = max($page - floor($numPageLinks / 2), 1);
  576. $paramName = $name . 'Page';
  577. if ($pageCount<=1) return '';
  578. $value = '';
  579. if ($page>1) {
  580. $params[$paramName] = 1;
  581. $value .= '<a href="' . Request::url(null, null, null, Request::getRequestedArgs(), $params, $anchor) . '"' . $allExtra . '>&lt;&lt;</a>&nbsp;';
  582. $params[$paramName] = $page - 1;
  583. $value .= '<a href="' . Request::url(null, null, null, Request::getRequestedArgs(), $params, $anchor) . '"' . $allExtra . '>&lt;</a>&nbsp;';
  584. }
  585. for ($i=$pageBase; $i<min($pageBase+$numPageLinks, $pageCount+1); $i++) {
  586. if ($i == $page) {
  587. $value .= "<strong>$i</strong>&nbsp;";
  588. } else {
  589. $params[$paramName] = $i;
  590. $value .= '<a href="' . Request::url(null, null, null, Request::getRequestedArgs(), $params, $anchor) . '"' . $allExtra . '>' . $i . '</a>&nbsp;';
  591. }
  592. }
  593. if ($page < $pageCount) {
  594. $params[$paramName] = $page + 1;
  595. $value .= '<a href="' . Request::url(null, null, null, Request::getRequestedArgs(), $params, $anchor) . '"' . $allExtra . '>&gt;</a>&nbsp;';
  596. $params[$paramName] = $pageCount;
  597. $value .= '<a href="' . Request::url(null, null, null, Request::getRequestedArgs(), $params, $anchor) . '"' . $allExtra . '>&gt;&gt;</a>&nbsp;';
  598. }
  599. return $value;
  600. }
  601. /**
  602. * Convert the parameters of a function to an array.
  603. */
  604. function smartyToArray() {
  605. return func_get_args();
  606. }
  607. /**
  608. * Concatenate the parameters and return the result.
  609. */
  610. function smartyConcat() {
  611. $args = func_get_args();
  612. return implode('', $args);
  613. }
  614. /**
  615. * Convert a string to a numeric time.
  616. */
  617. function smartyStrtotime($string) {
  618. return strtotime($string);
  619. }
  620. /**
  621. * Get the value of a template variable.
  622. */
  623. function smartyGetValue($name) {
  624. $templateMgr =& TemplateManager::getManager();
  625. return $templateMgr->get_template_vars($name);
  626. }
  627. /**
  628. * Override the built-in smarty escape modifier to set the charset
  629. * properly; also add the jsparam escaping method.
  630. */
  631. function smartyEscape($string, $esc_type = 'html', $char_set = null) {
  632. if ($char_set === null) $char_set = LOCALE_ENCODING;
  633. switch ($esc_type) {
  634. case 'jsparam':
  635. // When including a value in a Javascript parameter,
  636. // quotes need to be specially handled on top of
  637. // the usual escaping, as Firefox (and probably others)
  638. // decodes &#039; as a quote before interpereting
  639. // the javascript.
  640. $value = smarty_modifier_escape($string, 'html', $char_set);
  641. return str_replace('&#039;', '\\\'', $value);
  642. default:
  643. return smarty_modifier_escape($string, $esc_type, $char_set);
  644. }
  645. }
  646. /**
  647. * Override the built-in smarty truncate modifier to support mbstring and HTML tags
  648. * text properly, if possible.
  649. */
  650. function smartyTruncate($string, $length = 80, $etc = '...', $break_words = false, $middle = false, $skip_tags = true) {
  651. if ($length == 0) return '';
  652. if (String::strlen($string) > $length) {
  653. $originalLength = String::strlen($string);
  654. if ($skip_tags) {
  655. if ($middle) {
  656. $tagsReverse = array();
  657. $this->_removeTags($string, $tagsReverse, true, $length);
  658. }
  659. $tags = array();
  660. $string = $this->_removeTags($string, $tags, false, $length);
  661. }
  662. $length -= min($length, String::strlen($etc));
  663. if (!$middle) {
  664. if(!$break_words) {
  665. $string = String::regexp_replace('/\s+?(\S+)?$/', '', String::substr($string, 0, $length+1));
  666. } else $string = String::substr($string, 0, $length+1);
  667. if ($skip_tags) $string = $this->_reinsertTags($string, $tags);
  668. return $this->_closeTags($string) . $etc;
  669. } else {
  670. $firstHalf = String::substr($string, 0, $length/2);
  671. $secondHalf = String::substr($string, -$length/2);
  672. if($break_words) {
  673. if($skip_tags) {
  674. $firstHalf = $this->_reinsertTags($firstHalf, $tags);
  675. $secondHalf = $this->reinsertTags($secondHalf, $tagsReverse, true);
  676. return $this->_closeTags($firstHalf) . $etc . $this->_closeTags($secondHalf, true);
  677. } else {
  678. return $firstHalf . $etc . $secondHalf;
  679. }
  680. } else {
  681. for($i=$length/2; $string[$i] != ' '; $i++) {
  682. $firstHalf = String::substr($string, 0, $i+1);
  683. }
  684. for($i=$length/2; String::substr($string, -$i, 1) != ' '; $i++) {
  685. $secondHalf = String::substr($string, -$i-1);
  686. }
  687. if ($skip_tags) {
  688. $firstHalf = $this->_reinsertTags($firstHalf, $tags);
  689. $secondHalf = $this->reinsertTags($secondHalf, $tagsReverse, strlen($string));
  690. return $this->_closeTags($firstHalf) . $etc . $this->_closeTags($secondHalf, true);
  691. } else {
  692. return $firstHalf . $etc . $secondHalf;
  693. }
  694. }
  695. }
  696. } else {
  697. return $string;
  698. }
  699. }
  700. /**
  701. * Helper function: Remove XHTML tags and insert them into a global array along with their position
  702. * @author Matt Crider
  703. * @param string
  704. * @param array
  705. * @param boolean
  706. * @param int
  707. * @return string
  708. */
  709. function _removeTags($string, &$tags, $reverse = false, $length) {
  710. if($reverse) {
  711. return $this->_removeTagsAuxReverse($string, 0, $tags, $length);
  712. } else {
  713. return $this->_removeTagsAux($string, 0, $tags, $length);
  714. }
  715. }
  716. /**
  717. * Helper function: Recursive function called by _removeTags
  718. * @author Matt Crider
  719. * @param string
  720. * @param int
  721. * @param array
  722. * @param int
  723. * @return string
  724. */
  725. function _removeTagsAux($string, $loc, &$tags, $length) {
  726. if(strlen($string) > 0 && $length > 0) {
  727. $length--;
  728. if(String::substr($string, 0, 1) == '<') {
  729. $closeBrack = String::strpos($string, '>')+1;
  730. if($closeBrack) {
  731. $tags[] = array(String::substr($string, 0, $closeBrack), $loc);
  732. return $this->_removeTagsAux(String::substr($string, $closeBrack), $loc+$closeBrack, $tags, $length);
  733. }
  734. }
  735. return String::substr($string, 0, 1) . $this->_removeTagsAux(String::substr($string, 1), $loc+1, $tags, $length);
  736. }
  737. }
  738. /**
  739. * Helper function: Recursive function called by _removeTags
  740. * Removes tags from the back of the string and keeps a record of their position from the back
  741. * @author Matt Crider
  742. * @param string
  743. * @param int loc Keeps track of position from the back of original string
  744. * @param array
  745. * @param int
  746. * @return string
  747. */
  748. function _removeTagsAuxReverse($string, $loc, &$tags, $length) {
  749. $backLoc = String::strlen($string)-1;
  750. if($backLoc >= 0 && $length > 0) {
  751. $length--;
  752. if(String::substr($string, $backLoc, 1) == '>') {
  753. $tag = '>';
  754. $openBrack = 1;
  755. while (String::substr($string, $backLoc-$openBrack, 1) != '<') {
  756. $tag = String::substr($string, $backLoc-$openBrack, 1) . $tag;
  757. $openBrack++;
  758. }
  759. $tag = '<' . $tag;
  760. $openBrack++;
  761. $tags[] = array($tag, $loc);
  762. return $this->_removeTagsAuxReverse(String::substr($string, 0, -$openBrack), $loc+$openBrack, $tags, $length);
  763. }
  764. return $this->_removeTagsAuxReverse(String::substr($string, 0, -1), $loc+1, $tags, $length) . String::substr($string, $backLoc, 1);
  765. }
  766. }
  767. /**
  768. * Helper function: Reinsert tags from the tag array into their original position in the string
  769. * @author Matt Crider
  770. * @param string
  771. * @param array
  772. * @param boolean Set to true to reinsert tags starting at the back of the string
  773. * @return string
  774. */
  775. function _reinsertTags($string, &$tags, $reverse = false) {
  776. if(empty($tags)) return $string;
  777. for($i = 0; $i < count($tags); $i++) {
  778. $length = String::strlen($string);
  779. if ($tags[$i][1] < String::strlen($string)) {
  780. if ($reverse) {
  781. if ($tags[$i][1] == 0) { // Cannot use -0 as the start index (its same as +0)
  782. $string = String::substr_replace($string, $tags[$i][0], $length, 0);
  783. } else {
  784. $string = String::substr_replace($string, $tags[$i][0], -$tags[$i][1], 0);
  785. }
  786. } else {
  787. $string = String::substr_replace($string, $tags[$i][0], $tags[$i][1], 0);
  788. }
  789. }
  790. }
  791. return $string;
  792. }
  793. /**
  794. * Helper function: Closes all dangling XHTML tags in a string
  795. * Modified from http://milianw.de/code-snippets/close-html-tags
  796. * by Milian Wolff <mail@milianw.de>
  797. * @param string
  798. * @return string
  799. */
  800. function _closeTags($string, $open = false){
  801. // Put all opened tags into an array
  802. String::regexp_match_all("#<([a-z]+)( .*)?(?!/)>#iU", $string, $result);
  803. $openedtags = $result[1];
  804. // Put all closed tags into an array
  805. String::regexp_match_all("#</([a-z]+)>#iU", $string, $result);
  806. $closedtags = $result[1];
  807. $len_opened = count($openedtags);
  808. $len_closed = count($closedtags);
  809. // All tags are closed
  810. if(count($closedtags) == $len_opened){
  811. return $string;
  812. }
  813. $openedtags = array_reverse($openedtags);
  814. $closedtags = array_reverse($closedtags);
  815. if ($open) {
  816. // Open tags
  817. for($i=0; $i < $len_closed; $i++) {
  818. if (!in_array($closedtags[$i],$openedtags)){
  819. $string = '<'.$closedtags[$i].'>' . $string;
  820. } else {
  821. unset($openedtags[array_search($closedtags[$i],$openedtags)]);
  822. }
  823. }
  824. return $string;
  825. } else {
  826. // Close tags
  827. for($i=0; $i < $len_opened; $i++) {
  828. if (!in_array($openedtags[$i],$closedtags)){
  829. $string .= '</'.$openedtags[$i].'>';
  830. } else {
  831. unset($closedtags[array_search($openedtags[$i],$closedtags)]);
  832. }
  833. }
  834. return $string;
  835. }
  836. }
  837. /**
  838. * Split the supplied string by the supplied separator.
  839. */
  840. function smartyExplode($string, $separator) {
  841. return explode($separator, $string);
  842. }
  843. /**
  844. * Assign a value to a template variable.
  845. */
  846. function smartyAssign($value, $varName, $passThru = false) {
  847. if (isset($varName)) {
  848. // NOTE: CANNOT use $this, as it's actually
  849. // a COPY of the real template manager for some PHPs!
  850. // FIXME: Track this bug down. (Smarty?)
  851. $templateMgr =& TemplateManager::getManager();
  852. $templateMgr->assign($varName, $value);
  853. }
  854. if ($passThru) return $value;
  855. }
  856. /**
  857. * Smarty usage: {sort_heading key="localization.key.name" sort="foo"}
  858. *
  859. * Custom Smarty function for creating heading links to sort tables by
  860. * @params $params array associative array
  861. * @params $smarty Smarty
  862. * @return string heading link to sort table by
  863. */
  864. function smartySortHeading($params, &$smarty) {
  865. if (isset($params) && !empty($params)) {
  866. $sortParams = Request::getQueryArray();
  867. isset($params['sort'])? ($sortParams['sort'] = $params['sort']) : null;
  868. $sortDirection = $smarty->get_template_vars('sortDirection');
  869. $sort = $smarty->get_template_vars('sort');
  870. // Invert sort direction
  871. if($params['sort'] == $sort) {
  872. if ($sortDirection == SORT_DIRECTION_ASC) {
  873. $sortParams['sortDirection'] = SORT_DIRECTION_DESC;
  874. } else {
  875. $sortParams['sortDirection'] = SORT_DIRECTION_ASC;
  876. }
  877. } else {
  878. $sortParams['sortDirection'] = SORT_DIRECTION_ASC;
  879. }
  880. $link = PKPRequest::url(null, null, null, Request::getRequestedArgs(), $sortParams, null, true);
  881. $text = isset($params['key']) ? __($params['key']) : '';
  882. $style = (isset($sort) && isset($params['sort']) && ($sort == $params['sort'])) ? ' style="font-weight:bold"' : '';
  883. return "<a href=\"$link\"$style>$text</a>";
  884. }
  885. }
  886. /**
  887. * Smarty usage: {sort_search key="localization.key.name" sort="foo"}
  888. *
  889. * Custom Smarty function for creating heading links to sort search-generated tables
  890. * @params $params array associative array
  891. * @params $smarty Smarty
  892. * @return string heading link to sort table by
  893. */
  894. function smartySortSearch($params, &$smarty) {
  895. if (isset($params) && !empty($params)) {
  896. $sort = $smarty->get_template_vars('sort');
  897. $sortDirection = $smarty->get_template_vars('sortDirection');
  898. // Invert sort direction
  899. if($params['sort'] == $sort) {
  900. if ($sortDirection == SORT_DIRECTION_ASC) {
  901. $direction = SORT_DIRECTION_DESC;
  902. } else {
  903. $direction = SORT_DIRECTION_ASC;
  904. }
  905. } else {
  906. $direction = SORT_DIRECTION_ASC;
  907. }
  908. $heading = isset($params['sort']) ? $params['sort'] : $sort;
  909. $text = isset($params['key']) ? __($params['key']) : '';
  910. $style = (isset($sort) && isset($params['sort']) && ($sort == $params['sort'])) ? ' style="font-weight:bold"' : '';
  911. return "<a href=\"javascript:sortSearch('$heading','$direction')\"$style>$text</a>";
  912. }
  913. }
  914. /**
  915. * Smarty usage: {load_div id="someHtmlId" url="http://the.url.to.be.loaded.into.the.grid"}
  916. *
  917. * Custom Smarty function for loading a URL via AJAX into a DIV
  918. * @params $params array associative array
  919. * @params $smarty Smarty
  920. * @return string of HTML/Javascript
  921. */
  922. function smartyLoadUrlInDiv($params, &$smarty) {
  923. // Required Params
  924. if (!isset($params['url'])) {
  925. $smarty->trigger_error("URL parameter is missing from load_div");
  926. }
  927. if (!isset($params['id'])) {
  928. $smarty->trigger_error("id parameter is missing from load_div");
  929. }
  930. $url = $params['url'];
  931. $id = $params['id'];
  932. if (isset($params['loadMessageId'])) {
  933. $loadMessageId = $params['loadMessageId'];
  934. unset($params['url'], $params['id'], $params['loadMessageId']);
  935. $translatedLoadMessage = __($loadMessageId, $params);
  936. } else {
  937. $translatedLoadMessage = '';
  938. }
  939. echo "<div id=\"$id\">$translatedLoadMessage</div>
  940. <script type='text/javascript'>
  941. $(\"#$id\").load(\"$url\");
  942. </script>";
  943. }
  944. /**
  945. * Smarty usage: {modal url=$dialogUrl actOnId="#gridName" button="#dialogButton"}
  946. *
  947. * Custom Smarty function for creating jQuery-based modals
  948. * @params $params array associative array
  949. * @params $smarty Smarty
  950. * @return string Call to modal function with specified parameters
  951. */
  952. function smartyModal($params, &$smarty) {
  953. // Required Params
  954. if (!isset($params['url'])) {
  955. $smarty->trigger_error("URL parameter is missing from modal");
  956. } elseif (!isset($params['actOnId'])) {
  957. $smarty->trigger_error("actOnId parameter is missing from modal");
  958. } elseif (!isset($params['button'])) {
  959. $smarty->trigger_error("Button parameter is missing from modal");
  960. } else {
  961. $url = $params['url'];
  962. $actOnType = isset($params['actOnType'])?$params['actOnType']:'';
  963. $actOnId = $params['actOnId'];
  964. $button = $params['button'];
  965. }
  966. // Translate modal submit/cancel buttons
  967. $submitButton = __('common.ok');
  968. $cancelButton = __('common.cancel');
  969. // Add the modal javascript to the header
  970. $modalCode = "<script type='text/javascript'>
  971. var localizedButtons = ['$submitButton', '$cancelButton'];
  972. modal('$url', '$actOnType', '$actOnId', localizedButtons, '$button');
  973. </script>\n";
  974. echo $modalCode;
  975. }
  976. /**
  977. * Smarty usage: {confirm url=$dialogUrl dialogText="example.locale.key" button="#dialogButton"}
  978. * Custom Smarty function for creating simple yes/no dialogs (or to just send an AJAX post)
  979. * NB: -Leave out 'url' parameter to just display a message
  980. * -Leave out 'dialogText' parameter to immediately submit an AJAX request
  981. * @params $params array associative array
  982. * @params $smarty Smarty
  983. * @return string Call to modal function with specified parameters
  984. */
  985. function smartyConfirm($params, &$smarty) {
  986. // Required params
  987. if (!isset($params['button'])) {
  988. $smarty->trigger_error("Button parameter is missing from confirm");
  989. } else {
  990. $button = $params['button'];
  991. }
  992. // Non-required params
  993. $url = isset($params['url']) ? $params['url'] : null;
  994. $actOnType = isset($params['actOnType']) ? $params['actOnType'] : '';
  995. $actOnId = isset($params['actOnId'])?$params['actOnId']:'';
  996. if (isset($params['dialogText'])) {
  997. $showDialog = true;
  998. $dialogText = __($params['dialogText']);
  999. } else {
  1000. $showDialog = false;
  1001. }
  1002. if (!$showDialog && !$url) {
  1003. $smarty->trigger_error("Both URL and dialogText parameters are missing from confirm");
  1004. }
  1005. // Translate modal submit/cancel buttons
  1006. $submitButton = __('common.ok');
  1007. $cancelButton = __('common.cancel');
  1008. if ($showDialog) {
  1009. $confirmCode = "<script type='text/javascript'>
  1010. var localizedButtons = ['$submitButton', '$cancelButton'];
  1011. modalConfirm('$url', '$actOnType', '$actOnId', '$dialogText', localizedButtons, '$button');
  1012. </script>\n";
  1013. } else {
  1014. $confirmCode = "<script type='text/javascript'>
  1015. buttonPost('$url', '$button');
  1016. </script>";
  1017. }
  1018. echo $confirmCode;
  1019. }
  1020. function smartyAjaxUpload($params, &$smarty) {
  1021. // Required params
  1022. if (!isset($params['form'])) {
  1023. $smarty->trigger_error("Form parameter is missing from ajax upload");
  1024. } else {
  1025. $form = $params['form'];
  1026. }
  1027. // Required params
  1028. if (!isset($params['url'])) {
  1029. $smarty->trigger_error("URL parameter is missing from ajax upload");
  1030. } else {
  1031. $url = $params['url'];
  1032. }
  1033. echo "<script type='text/javascript'>ajaxUpload('$url', '$form');</script>";
  1034. }
  1035. function smartyInitTabs($params, &$smarty) {
  1036. // Required params
  1037. if (!isset($params['id'])) {
  1038. $smarty->trigger_error("Selector missing for tab initialization");
  1039. } else {
  1040. $id = $params['id'];
  1041. }
  1042. echo "<script type='text/javascript'>$(function() {
  1043. $('#$id').tabs();
  1044. });</script>";
  1045. }
  1046. }
  1047. ?>