PageRenderTime 92ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/system/expressionengine/third_party/stash/mod.stash.php

https://gitlab.com/sops21/mt-rubidoux
PHP | 5113 lines | 3338 code | 619 blank | 1156 comment | 455 complexity | 659280961a436c13b17490d1a9b9a167 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  2. require_once PATH_THIRD . 'stash/config.php';
  3. /**
  4. * Set and get template variables, EE snippets and persistent variables.
  5. *
  6. * @package Stash
  7. * @author Mark Croxton (mcroxton@hallmark-design.co.uk)
  8. * @copyright Copyright (c) 2014 Hallmark Design
  9. * @license http://creativecommons.org/licenses/by-nc-sa/3.0/
  10. * @link http://hallmark-design.co.uk
  11. */
  12. class Stash {
  13. public $EE;
  14. public $version = STASH_VER;
  15. public $site_id;
  16. public $path;
  17. public $file_sync;
  18. public $stash_cookie;
  19. public $stash_cookie_expire;
  20. public $default_scope;
  21. public $limit_bots;
  22. public static $context = NULL;
  23. protected $xss_clean;
  24. protected $replace;
  25. protected $type;
  26. protected $parse_tags = FALSE;
  27. protected $parse_vars = NULL;
  28. protected $parse_conditionals = FALSE;
  29. protected $parse_depth = 1;
  30. protected $parse_complete = FALSE;
  31. protected $bundle_id = 1;
  32. protected $process = 'inline';
  33. protected $priority = 1;
  34. protected static $bundles = array();
  35. private $_update = FALSE;
  36. private $_append = TRUE;
  37. private $_stash;
  38. private $_session_id;
  39. private $_ph = array();
  40. private $_list_delimiter = '|+|';
  41. private $_list_row_delimiter = '|&|';
  42. private $_list_row_glue = '|=|';
  43. private $_list_null = '__NULL__';
  44. private $_embed_nested = FALSE;
  45. private $_nocache_suffix = ':nocache';
  46. private static $_nocache = TRUE;
  47. private static $_nocache_prefixes = array('stash');
  48. private static $_is_human = TRUE;
  49. private static $_cache;
  50. /*
  51. * Constructor
  52. */
  53. public function __construct($calling_from_hook = FALSE)
  54. {
  55. $this->EE =& get_instance();
  56. // load dependencies - make sure the package path is available in case the class is being called statically
  57. $this->EE->load->add_package_path(PATH_THIRD.'stash/', TRUE);
  58. $this->EE->lang->loadfile('stash');
  59. $this->EE->load->model('stash_model');
  60. // default site id
  61. $this->site_id = $this->EE->config->item('site_id');
  62. // config defaults
  63. $this->path = $this->EE->config->item('stash_file_basepath') ? $this->EE->config->item('stash_file_basepath') : APPPATH . 'stash/';
  64. $this->file_sync = $this->_get_boolean_config_item('stash_file_sync', FALSE); // default = FALSE
  65. $this->stash_cookie = $this->EE->config->item('stash_cookie') ? $this->EE->config->item('stash_cookie') : 'stashid';
  66. $this->stash_cookie_expire = $this->EE->config->item('stash_cookie_expire') ? $this->EE->config->item('stash_cookie_expire') : 0;
  67. $this->stash_cookie_enabled = $this->_get_boolean_config_item('stash_cookie_enabled'); // default = TRUE
  68. $this->default_scope = $this->EE->config->item('stash_default_scope') ? $this->EE->config->item('stash_default_scope') : 'user';
  69. $this->default_refresh = $this->EE->config->item('stash_default_refresh') ? $this->EE->config->item('stash_default_refresh') : 0; // minutes
  70. $this->limit_bots = $this->_get_boolean_config_item('stash_limit_bots', FALSE); // default = FALSE
  71. // cache pruning can cache stampede mitigation defaults
  72. $this->prune = $this->_get_boolean_config_item('stash_prune_enabled'); // default = TRUE
  73. $this->prune_probability = $this->EE->config->item('stash_prune_probability') ? $this->EE->config->item('stash_prune_probability') : .4; // percent
  74. $this->invalidation_period = $this->EE->config->item('stash_invalidation_period') ? $this->EE->config->item('stash_invalidation_period') : 0; // seconds
  75. // permitted file extensions for Stash embeds
  76. $this->file_extensions = $this->EE->config->item('stash_file_extensions')
  77. ? (array) $this->EE->config->item('stash_file_extensions')
  78. : array('html', 'md', 'css', 'js', 'rss', 'xml');
  79. // Support {if var1 IN (var2) }...{/if} style conditionals in Stash templates / tagdata?
  80. $this->parse_if_in = $this->EE->config->item('stash_parse_if_in') ? $this->EE->config->item('stash_parse_if_in') : FALSE;
  81. // include query string when using the @URI context (full page caching)?
  82. $this->include_query_str = $this->EE->config->item('stash_query_strings') ? $this->EE->config->item('stash_query_strings') : FALSE;
  83. // initialise tag parameters
  84. if (FALSE === $calling_from_hook)
  85. {
  86. $this->init();
  87. }
  88. // fetch the stash session id
  89. if ($this->stash_cookie_enabled)
  90. {
  91. if ( ! isset($this->EE->session->cache['stash']['_session_id']) )
  92. {
  93. // do we have a stash cookie?
  94. if ($cookie_data = $this->_get_stash_cookie())
  95. {
  96. // YES - restore session
  97. $this->EE->session->cache['stash']['_session_id'] = $cookie_data['id'];
  98. // shall we prune expired variables?
  99. if ($this->prune)
  100. {
  101. // probability that pruning occurs
  102. $prune_chance = 100/$this->prune_probability;
  103. // trigger pruning every 1 chance out of $prune_chance
  104. if (mt_rand(0, ($prune_chance-1)) === 0)
  105. {
  106. // prune variables with expiry date older than right now
  107. $this->EE->stash_model->prune_keys();
  108. }
  109. }
  110. }
  111. else
  112. {
  113. if ($this->limit_bots)
  114. {
  115. // Is the user a human? Legitimate bots don't set cookies so will end up here every page load
  116. // Humans who accept cookies only get checked when the cookie is first set
  117. self::$_is_human = ($this->_is_bot() ? FALSE : TRUE);
  118. }
  119. // NO - let's generate a unique id
  120. $unique_id = $this->EE->functions->random();
  121. // add to stash array
  122. $this->EE->session->cache['stash']['_session_id'] = $unique_id;
  123. // create a cookie; store the creation date in the cookie itself
  124. $this->_set_stash_cookie($unique_id);
  125. }
  126. }
  127. // create a reference to the session id
  128. $this->_session_id =& $this->EE->session->cache['stash']['_session_id'];
  129. }
  130. else
  131. {
  132. $this->_session_id = '_global';
  133. }
  134. }
  135. // ---------------------------------------------------------
  136. /**
  137. * Initialise tag parameters
  138. *
  139. * @access public
  140. * @param bool $calling_from_hook Is method being called by an extension hook?
  141. * @return void
  142. */
  143. public function init($calling_from_hook = FALSE)
  144. {
  145. // make sure we have a Template object to work with, in case Stash is being invoked outside of a template
  146. if ( ! class_exists('EE_Template'))
  147. {
  148. $this->_load_EE_TMPL();
  149. }
  150. // initialise internal flags
  151. $this->parse_complete = FALSE;
  152. $this->_update = FALSE;
  153. $this->_append = TRUE;
  154. $this->_embed_nested = FALSE;
  155. $this->process = 'inline';
  156. // postpone the parsing of the called stash tag?
  157. if (FALSE === $calling_from_hook)
  158. {
  159. /* process stage:
  160. start = called prior to template parsing in the current template
  161. inline = process as a normal tag within the natural parse order of the template
  162. end = called after all tag parsing has completed
  163. */
  164. $this->process = $this->EE->TMPL->fetch_param('process', 'inline'); // start | inline | end
  165. $this->priority = $this->EE->TMPL->fetch_param('priority', '1'); // ensure a priority is set
  166. }
  167. // legacy: make 'final' the same as 'end'
  168. if ($this->process == "final")
  169. {
  170. $this->process = "end";
  171. }
  172. // tags can't be processed on start, only stash embeds
  173. if ($this->process == "start")
  174. {
  175. $this->process = "inline";
  176. }
  177. // allow the site_id to be overridden, for e.g. shared variables across mutliple sites
  178. $this->site_id = (integer) $this->EE->TMPL->fetch_param('site_id', $this->site_id);
  179. // selected bundle
  180. $bundle = $this->EE->TMPL->fetch_param('bundle', 'default');
  181. // lookup the id of an existing bundle, or map to one of the preset bundles
  182. if ( ! $this->bundle_id = $this->EE->stash_model->get_bundle_by_name($bundle))
  183. {
  184. // not found, fallback to the default
  185. $this->bundle_id = 1;
  186. }
  187. // xss scripting protection
  188. $this->xss_clean = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('xss_clean'));
  189. // if the variable is already set, do we want to replace it's value? Default = yes
  190. $this->replace = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('replace', 'yes'));
  191. // parse="yes"?
  192. $this->set_parse_params();
  193. // do we want to parse any tags and variables inside tagdata? Default = no
  194. $this->parse_tags = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('parse_tags'));
  195. $this->parse_vars = $this->EE->TMPL->fetch_param('parse_vars', NULL);
  196. // legacy behaviour: if parse_vars is null but parse tags is true, we should make sure vars are parsed too
  197. if ($this->parse_tags && $this->parse_vars == NULL)
  198. {
  199. $this->parse_vars = TRUE;
  200. }
  201. else
  202. {
  203. $this->parse_vars = (bool) preg_match('/1|on|yes|y/i', $this->parse_vars);
  204. }
  205. // parsing: how many passes of the template should we make? (more passes = more overhead). Default = 1
  206. $this->parse_depth = preg_replace('/[^0-9]/', '', $this->EE->TMPL->fetch_param('parse_depth', 1));
  207. // parsing: parse advanced conditionals. Default = no
  208. $this->parse_conditionals = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('parse_conditionals'));
  209. // stash type, default to 'variable'
  210. $this->type = strtolower( $this->EE->TMPL->fetch_param('type', 'variable') );
  211. // create a stash array in the session if we don't have one
  212. if ( ! array_key_exists('stash', $this->EE->session->cache) )
  213. {
  214. $this->EE->session->cache['stash'] = array();
  215. }
  216. // determine the memory storage location
  217. if ($this->type === 'variable')
  218. {
  219. // we're setting/getting a 'native' stash variable
  220. $this->_stash =& $this->EE->session->cache['stash'];
  221. }
  222. elseif ($this->type === 'snippet' || $this->type === 'global')
  223. {
  224. // we're setting/getting a global variable {snippet}
  225. $this->_stash =& $this->EE->config->_global_vars;
  226. }
  227. else
  228. {
  229. $this->EE->output->show_user_error('general', $this->EE->lang->line('unknown_stash_type') . $this->type);
  230. }
  231. }
  232. // ---------------------------------------------------------
  233. /**
  234. * Load the EE Template class and register the Stash module
  235. * Used when Stash is instantiated outside of an EE template
  236. *
  237. * @access private
  238. * @return void
  239. */
  240. private function _load_EE_TMPL()
  241. {
  242. // -------------------------------------
  243. // 'stash_load_template_class' hook
  244. // -------------------------------------
  245. if ($this->EE->extensions->active_hook('stash_load_template_class') === TRUE)
  246. {
  247. $this->EE->TMPL = $this->EE->extensions->call('stash_load_template_class');
  248. }
  249. else
  250. {
  251. require_once APPPATH.'libraries/Template.php';
  252. $this->EE->TMPL = new EE_Template();
  253. $this->EE->TMPL->modules = array('stash');
  254. }
  255. }
  256. /*
  257. ================================================================
  258. Template tags
  259. ================================================================
  260. */
  261. /**
  262. * Shortcut to stash:get or stash:set
  263. *
  264. * @param string $name The method name being called or context if third tagpart
  265. * @param array $arguments The method call arguments
  266. *
  267. * @return void
  268. */
  269. public function __call($name, $arguments)
  270. {
  271. /* Sample use
  272. ---------------------------------------------------------
  273. {exp:stash:foo}
  274. is equivalent to:
  275. {exp:stash:get name="foo"}
  276. ---------------------------------------------------------
  277. {exp:stash:foo}
  278. CONTENT
  279. {/exp:stash:foo}
  280. is equivalent to:
  281. {exp:stash:set name="foo"}
  282. CONTENT
  283. {/exp:stash:set}
  284. ---------------------------------------------------------
  285. {exp:stash:bar:foo}
  286. is equivalent to:
  287. {exp:stash:get name="bar:foo"}
  288. and
  289. {exp:stash:get context="bar" name="foo"}
  290. ---------------------------------------------------------
  291. {exp:stash:bar:foo}
  292. CONTENT
  293. {/exp:stash:bar:foo}
  294. is equivalent to:
  295. {exp:stash:set context="bar" name="foo"}
  296. CONTENT
  297. {/exp:stash:set}
  298. and
  299. {exp:stash:set name="bar:foo"}
  300. CONTENT
  301. {/exp:stash:set}
  302. --------------------------------------------------------- */
  303. switch($name)
  304. {
  305. case 'unset' :
  306. // make 'unset' - a reserved word - an alias of destroy()
  307. return call_user_func_array(array($this, 'destroy'), $arguments);
  308. break;
  309. case 'static' :
  310. // make 'static' - a reserved word - an alias of static_cache()
  311. return call_user_func_array(array($this, 'static_cache'), $arguments);
  312. break;
  313. default :
  314. // if there is an extra tagpart, then we have a context and a name
  315. if (isset($this->EE->TMPL->tagparts[2]))
  316. {
  317. $this->EE->TMPL->tagparams['context'] = $name;
  318. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->tagparts[2];
  319. }
  320. else
  321. {
  322. $this->EE->TMPL->tagparams['name'] = $name;
  323. }
  324. return $this->EE->TMPL->tagdata ? $this->set() : $this->get();
  325. }
  326. }
  327. /**
  328. * Set content in the current session, optionally save to the database
  329. *
  330. * @access public
  331. * @param mixed $params The name of the variable to retrieve, or an array of key => value pairs
  332. * @param string $value The value of the variable
  333. * @param string $type The type of variable
  334. * @param string $scope The scope of the variable
  335. * @return void
  336. */
  337. public function set($params=array(), $value='', $type='variable', $scope='user')
  338. {
  339. /* Sample use
  340. ---------------------------------------------------------
  341. {exp:stash:set name="title" type="snippet"}A title{/exp:stash:set}
  342. OR static call within PHP enabled templates or other add-on:
  343. <?php stash::set('title', 'My title') ?>
  344. --------------------------------------------------------- */
  345. // is this method being called directly?
  346. if ( func_num_args() > 0)
  347. {
  348. if ( !(isset($this) && get_class($this) == __CLASS__))
  349. {
  350. return self::_api_static_call(__FUNCTION__, $params, $type, $scope, $value);
  351. }
  352. else
  353. {
  354. return $this->_api_call(__FUNCTION__, $params, $type, $scope, $value);
  355. }
  356. }
  357. // do we want to set the variable?
  358. $set = TRUE;
  359. // var name
  360. $name = $this->EE->TMPL->fetch_param('name', FALSE);
  361. // context handling
  362. $context = $this->EE->TMPL->fetch_param('context', NULL);
  363. if ( !! $name)
  364. {
  365. if ($context !== NULL && count( explode(':', $name) == 1 ) )
  366. {
  367. $name = $context . ':' . $name;
  368. $this->EE->TMPL->tagparams['context'] = NULL;
  369. }
  370. }
  371. // replace '@' placeholders with the current context
  372. $stash_key = $this->_parse_context($name);
  373. // scope
  374. $scope = strtolower($this->EE->TMPL->fetch_param('scope', $this->default_scope)); // local|user|site
  375. // do we want this tag to return it's tagdata? (default: no)
  376. $output = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('output'));
  377. // do we want to parse early global variables in variables retrieved from the cache
  378. // append or prepend passed as parameters?
  379. if (preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('prepend')))
  380. {
  381. $this->_update = TRUE;
  382. $this->_append = FALSE;
  383. }
  384. elseif (preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('append')))
  385. {
  386. $this->_update = TRUE;
  387. $this->_append = TRUE;
  388. }
  389. // do we want to save this variable in a bundle?
  390. $bundle = $this->EE->TMPL->fetch_param('bundle', NULL); // save in a bundle?
  391. // do we want to replace an existing variable?
  392. if ( !! $name && ! $this->replace && ! $this->_update)
  393. {
  394. // try to get existing value
  395. $existing_value = FALSE;
  396. if ( array_key_exists($stash_key, $this->_stash))
  397. {
  398. $existing_value = $this->_stash[$name];
  399. }
  400. elseif ($scope !== 'local')
  401. {
  402. // narrow the scope to user?
  403. $session_id = $scope === 'user' ? $this->_session_id : '_global';
  404. $existing_value = $this->EE->stash_model->get_key(
  405. $stash_key,
  406. $this->bundle_id,
  407. $session_id,
  408. $this->site_id
  409. );
  410. }
  411. if ( $existing_value !== FALSE)
  412. {
  413. // yes, it's already been stashed
  414. $this->EE->TMPL->tagdata = $this->_stash[$name] = $existing_value;
  415. // don't overwrite existing value
  416. $set = FALSE;
  417. }
  418. unset($existing_value);
  419. }
  420. // do we want to ignore empty tagdata values?
  421. if ( $not_empty = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('not_empty')) )
  422. {
  423. if ( ! $this->not_empty())
  424. {
  425. $set = FALSE;
  426. }
  427. }
  428. if ($set)
  429. {
  430. // support for deprecated no_results_prefix parameter
  431. $no_results_prefix = $this->EE->TMPL->fetch_param('no_results_prefix');
  432. // check for an unprefix parameter to avoid variable name conflicts in nested tags
  433. if($unprefix = $this->EE->TMPL->fetch_param('unprefix', $no_results_prefix))
  434. {
  435. $this->EE->TMPL->tagdata = $this->_un_prefix($unprefix, $this->EE->TMPL->tagdata);
  436. }
  437. if ( ($this->parse_tags || $this->parse_vars || $this->parse_conditionals) && ! $this->parse_complete)
  438. {
  439. $this->_parse_sub_template($this->parse_tags, $this->parse_vars, $this->parse_conditionals, $this->parse_depth);
  440. $this->parse_complete = TRUE; // don't run again
  441. }
  442. // apply any string manipulations
  443. $this->EE->TMPL->tagdata = $this->_clean_string($this->EE->TMPL->tagdata);
  444. if ( !! $name )
  445. {
  446. // get params
  447. $label = $this->EE->TMPL->fetch_param('label', $name);
  448. $save = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('save'));
  449. $match = $this->EE->TMPL->fetch_param('match', NULL); // regular expression to test value against
  450. $against = $this->EE->TMPL->fetch_param('against', $this->EE->TMPL->tagdata); // text to apply test against
  451. $filter = $this->EE->TMPL->fetch_param('filter', NULL); // regex pattern to search for
  452. $default = $this->EE->TMPL->fetch_param('default', NULL); // default value
  453. $delimiter = $this->EE->TMPL->fetch_param('delimiter', '|'); // implode arrays using this delimiter
  454. // cache refresh time
  455. $refresh = (int) $this->EE->TMPL->fetch_param('refresh', $this->default_refresh);
  456. // do we want to set a placeholder somewhere in this template ?
  457. $set_placeholder = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('set_placeholder'));
  458. // make sure we have a value to fallback to for output in current template
  459. if ($set_placeholder && is_null($default))
  460. {
  461. $default = '';
  462. }
  463. // set refresh
  464. if ($refresh > 0)
  465. {
  466. $refresh = $this->EE->localize->now + ($refresh * 60);
  467. }
  468. // regex match
  469. if ( $match !== NULL && preg_match('/^#(.*)#$/', $match))
  470. {
  471. $is_match = $this->_matches($match, $against);
  472. // did it fail to match the filter?
  473. if ( ! $is_match )
  474. {
  475. // if a default has been specified fallback to it
  476. if (! is_null($default))
  477. {
  478. $this->EE->TMPL->tagdata = $default;
  479. }
  480. else
  481. {
  482. return;
  483. }
  484. }
  485. }
  486. // regex filter
  487. if ( $filter !== NULL && ! is_array($this->EE->TMPL->tagdata))
  488. {
  489. preg_match($filter, $this->EE->TMPL->tagdata, $found);
  490. if (isset($found[1]))
  491. {
  492. $this->EE->TMPL->tagdata = $found[1];
  493. }
  494. }
  495. // make sure we're working with a string
  496. // if we're setting a variable from a global ($_POST, $_GET etc), it could be an array
  497. if ( is_array($this->EE->TMPL->tagdata))
  498. {
  499. $this->EE->TMPL->tagdata = array_filter($this->EE->TMPL->tagdata, 'strlen');
  500. $this->EE->TMPL->tagdata = implode($delimiter, $this->EE->TMPL->tagdata);
  501. }
  502. if ( $this->_update )
  503. {
  504. // We're updating a variable, so lets see if it's in the session or db
  505. if ( ! array_key_exists($name, $this->_stash))
  506. {
  507. $this->_stash[$name] = $this->_run_tag('get', array('name', 'type', 'scope', 'context'));
  508. }
  509. // Append or prepend?
  510. if ( $this->_append )
  511. {
  512. $this->_stash[$name] .= $this->EE->TMPL->tagdata;
  513. }
  514. else
  515. {
  516. $this->_stash[$name] = $this->EE->TMPL->tagdata.$this->_stash[$name];
  517. }
  518. }
  519. else
  520. {
  521. $this->_stash[$name] = $this->EE->TMPL->tagdata;
  522. }
  523. // replace value into a {placeholder} anywhere in the current template?
  524. if ($set_placeholder)
  525. {
  526. $this->EE->TMPL->template = $this->EE->functions->var_swap(
  527. $this->EE->TMPL->template,
  528. array($name => $this->_stash[$name])
  529. );
  530. }
  531. // allow user- and site- scoped variables to be saved to the db
  532. // stop bots saving data to reduce unnecessary load on the server
  533. if ($save && $scope !== 'local' && self::$_is_human)
  534. {
  535. // optionally clean data before inserting
  536. $parameters = $this->_stash[$name];
  537. if ($this->xss_clean)
  538. {
  539. $this->EE->security->xss_clean($parameters);
  540. }
  541. // what's the intended variable scope?
  542. if ($scope === 'site')
  543. {
  544. $session_filter = '_global';
  545. }
  546. else
  547. {
  548. $session_filter =& $this->_session_id;
  549. }
  550. // let's check if there is an existing record
  551. $result = $this->EE->stash_model->get_key($stash_key, $this->bundle_id, $session_filter, $this->site_id);
  552. if ( $result !== FALSE)
  553. {
  554. // yes record exists, but do we want to update it?
  555. $update_key = FALSE;
  556. // is the new variable value identical to the value in the cache?
  557. // allow append/prepend if the stash key has been created *in this page load*
  558. $cache_key = $stash_key. '_'. $this->bundle_id .'_' .$this->site_id . '_' . $session_filter;
  559. if ( $result !== $parameters && ($this->replace || ($this->_update && $this->EE->stash_model->is_inserted_key($cache_key)) ))
  560. {
  561. $update_key = TRUE;
  562. }
  563. if ($update_key)
  564. {
  565. // update
  566. $this->EE->stash_model->update_key(
  567. $stash_key,
  568. $this->bundle_id,
  569. $session_filter,
  570. $this->site_id,
  571. $refresh,
  572. $parameters
  573. );
  574. }
  575. }
  576. else
  577. {
  578. // no record - insert one
  579. // Don't save if this template has a 404 header set from a redirect
  580. if ( $this->EE->output->out_type !== "404")
  581. {
  582. $this->EE->stash_model->insert_key(
  583. $stash_key,
  584. $this->bundle_id,
  585. $session_filter,
  586. $this->site_id,
  587. $refresh,
  588. $parameters,
  589. $label
  590. );
  591. }
  592. }
  593. }
  594. }
  595. else
  596. {
  597. // no name supplied, so let's assume we want to set sections of content within tag pairs
  598. // {stash:my_variable}...{/stash:my_variable}
  599. $vars = array();
  600. $tagdata = $this->EE->TMPL->tagdata;
  601. // context handling
  602. if ( $context !== NULL )
  603. {
  604. $prefix = $context . ':';
  605. $this->EE->TMPL->tagparams['context'] = NULL;
  606. }
  607. else
  608. {
  609. $prefix = '';
  610. }
  611. // if the tagdata has been parsed, we need to generate a new array of tag pairs
  612. // this permits dynamic tag pairs, e.g. {stash:{key}}{/stash:{key}}
  613. if ($this->parse_complete)
  614. {
  615. $tag_vars = $this->EE->functions->assign_variables($this->EE->TMPL->tagdata);
  616. $tag_pairs = $tag_vars['var_pair'];
  617. }
  618. else
  619. {
  620. $tag_pairs =& $this->EE->TMPL->var_pair;
  621. }
  622. foreach($tag_pairs as $key => $val)
  623. {
  624. if (strncmp($key, 'stash:', 6) == 0)
  625. {
  626. $pattern = '/'.LD.$key.RD.'(.*)'.LD.'\/'.$key.RD.'/Usi';
  627. preg_match($pattern, $tagdata, $matches);
  628. if (!empty($matches))
  629. {
  630. // set the variable, but cleanup first in case there are any nested tags
  631. $this->EE->TMPL->tagparams['name'] = $prefix . str_replace('stash:', '', $key);
  632. $this->EE->TMPL->tagdata = preg_replace('/'.LD.'stash:[a-zA-Z0-9\-_]+'.RD.'(.*)'.LD.'\/stash:[a-zA-Z0-9\-_]+'.RD.'/Usi', '', $matches[1]);
  633. $this->parse_complete = TRUE; // don't allow tagdata to be parsed
  634. $this->set();
  635. }
  636. }
  637. }
  638. // reset tagdata to original value
  639. $this->EE->TMPL->tagdata = $tagdata;
  640. unset($tagdata);
  641. }
  642. }
  643. if ( !! $name)
  644. {
  645. if ( $bundle !== NULL)
  646. {
  647. if ( ! isset(self::$bundles[$bundle]))
  648. {
  649. self::$bundles[$bundle] = array();
  650. }
  651. self::$bundles[$bundle][$name] = $this->_stash[$name];
  652. }
  653. $this->EE->TMPL->log_item('Stash: SET '. $name . ' to value ' . $this->_stash[$name]);
  654. }
  655. if ($output)
  656. {
  657. return $this->EE->TMPL->tagdata;
  658. }
  659. }
  660. // ---------------------------------------------------------
  661. /**
  662. * Get content from session, database, $_POST/$_GET superglobals or file
  663. *
  664. * @access public
  665. * @param mixed $params The name of the variable to retrieve, or an array of key => value pairs
  666. * @param string $type The type of variable
  667. * @param string $scope The scope of the variable
  668. * @return string
  669. */
  670. public function get($params='', $type='variable', $scope='user')
  671. {
  672. /* Sample use
  673. ---------------------------------------------------------
  674. {exp:stash:get name="title"}
  675. OR static call within PHP enabled templates or other add-on:
  676. <?php echo stash::get('title') ?>
  677. --------------------------------------------------------- */
  678. // is this method being called directly?
  679. if ( func_num_args() > 0)
  680. {
  681. if ( !(isset($this) && get_class($this) == __CLASS__))
  682. {
  683. return self::_api_static_call(__FUNCTION__, $params, $type, $scope);
  684. }
  685. else
  686. {
  687. return $this->_api_call(__FUNCTION__, $params, $type, $scope);
  688. }
  689. }
  690. if ( $this->process !== 'inline')
  691. {
  692. if ($out = $this->_post_parse(__FUNCTION__)) return $out;
  693. }
  694. $name = $this->EE->TMPL->fetch_param('name');
  695. $default = $this->EE->TMPL->fetch_param('default', NULL); // default value
  696. $dynamic = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('dynamic'));
  697. $save = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('save'));
  698. $scope = strtolower($this->EE->TMPL->fetch_param('scope', $this->default_scope)); // local|user|site
  699. $bundle = $this->EE->TMPL->fetch_param('bundle', NULL); // save in a bundle?
  700. $match = $this->EE->TMPL->fetch_param('match', NULL); // regular expression to test value against
  701. $filter = $this->EE->TMPL->fetch_param('filter', NULL); // regex pattern to search for
  702. // do we want this tag to return the value, or just set the variable quietly in the background?
  703. $output = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('output', 'yes'));
  704. // parse any vars in the $name parameter?
  705. if ($this->parse_vars)
  706. {
  707. $name = $this->_parse_template_vars($name);
  708. }
  709. // low search support - do we have a query string?
  710. $low_query = $this->EE->TMPL->fetch_param('low_query', NULL);
  711. // context handling
  712. $context = $this->EE->TMPL->fetch_param('context', NULL);
  713. $global_name = $name;
  714. if ($context !== NULL && count( explode(':', $name) == 1 ) )
  715. {
  716. $name = $context . ':' . $name;
  717. $this->EE->TMPL->tagparams['context'] = NULL;
  718. }
  719. // parse '@' context pointers
  720. $name_in_context = $this->_parse_context($name);
  721. // read from file?
  722. $file = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('file'));
  723. $file_name = $this->EE->TMPL->fetch_param('file_name', FALSE); // default value
  724. // when to parse the variable if reading from a file and saving:
  725. // before we save it to database (set) or when we retrieve it (get), or on set and get (both)
  726. $parse_stage = strtolower($this->EE->TMPL->fetch_param('parse_stage', 'set')); // set|get|both
  727. if ( !! $file_name)
  728. {
  729. $file = TRUE;
  730. }
  731. else
  732. {
  733. $file_name = $name;
  734. }
  735. // the variable value
  736. $value = NULL;
  737. // do we want to set the variable?
  738. $set = FALSE;
  739. // is it a segment? We need to support these in stash template files
  740. if (strncmp($name, 'segment_', 8) === 0)
  741. {
  742. $seg_index = substr($name, 8);
  743. $value = $this->EE->uri->segment($seg_index);
  744. }
  745. // let's see if it's been stashed before in this page load
  746. elseif ( is_string($name) && array_key_exists($name, $this->_stash))
  747. {
  748. $value = $this->_stash[$name];
  749. }
  750. // let's see if it exists in the current context
  751. elseif ( is_string($name_in_context) && array_key_exists($name_in_context, $this->_stash))
  752. {
  753. $value = $this->_stash[$name_in_context];
  754. $name = $name_in_context;
  755. }
  756. // not found in memory
  757. else
  758. {
  759. // has it been bundled?
  760. if ( ! is_null($bundle) && isset(self::$bundles[$bundle][$name]))
  761. {
  762. $value = $this->_stash[$name] = self::$bundles[$bundle][$name];
  763. }
  764. elseif ( ! $this->_update && ! ($dynamic && ! $save) && $scope !== 'local')
  765. {
  766. // let's look in the database table cache, but only if if we're not
  767. // appending/prepending or trying to register a global without saving it
  768. // narrow the scope to user?
  769. $session_id = $scope === 'user' ? $this->_session_id : '_global';
  770. // replace '@' placeholders with the current context
  771. $stash_key = $this->_parse_context($name);
  772. // look for our key
  773. if ( $parameters = $this->EE->stash_model->get_key(
  774. $stash_key,
  775. $this->bundle_id,
  776. $session_id,
  777. $this->site_id
  778. ))
  779. {
  780. // save to session
  781. $value = $this->_stash[$name] = $parameters;
  782. }
  783. }
  784. // Are we looking for a superglobal or uri segment?
  785. if ( ($dynamic && $value === NULL) || ($dynamic && $this->replace) )
  786. {
  787. $from_global = FALSE;
  788. // low search support
  789. if ($low_query !== NULL)
  790. {
  791. // has the query string been supplied or is it in POST?
  792. if (strncmp($low_query, 'stash:', 6) == 0)
  793. {
  794. $low_query = substr($low_query, 6);
  795. $low_query = $this->_stash[$low_query];
  796. }
  797. $low_query = @unserialize(base64_decode(str_replace('_', '/', $low_query)));
  798. if (isset( $low_query[$global_name] ))
  799. {
  800. $from_global = $low_query[$global_name];
  801. unset($low_query);
  802. }
  803. else
  804. {
  805. // set to empty value
  806. $from_global = '';
  807. }
  808. }
  809. // or is it in the $_POST or $_GET superglobals ( run through xss_clean() )?
  810. if ($from_global === FALSE)
  811. {
  812. $from_global = $this->EE->input->get_post($global_name, TRUE);
  813. }
  814. if ($from_global === FALSE)
  815. {
  816. // no, so let's check the uri segments
  817. $segs = $this->EE->uri->segment_array();
  818. foreach ( $segs as $index => $segment )
  819. {
  820. if ( $segment == $global_name && array_key_exists( ($index+1), $segs) )
  821. {
  822. $from_global = $segs[($index+1)];
  823. break;
  824. }
  825. }
  826. }
  827. if ($from_global !== FALSE)
  828. {
  829. // save to stash, and optionally to database, if save="yes"
  830. $value = $from_global;
  831. $set = TRUE;
  832. }
  833. }
  834. // Are we reading a file?
  835. if ( ($file && $value === NULL) || ($file && $this->replace) || ($file && $this->file_sync) )
  836. {
  837. // extract and remove the file extension, if provided
  838. $ext = 'html'; // default extension
  839. # PHP 5.3+ only
  840. # $file_ext = preg_filter('/^.*\./', '', $file_name);
  841. $file_ext = NULL;
  842. if ( preg_match('/^.*\./', $file_name) )
  843. {
  844. $file_ext = preg_replace('/^.*\./', '', $file_name);
  845. }
  846. // make sure the extension is allowed
  847. if ( ! is_null($file_ext))
  848. {
  849. if ( in_array($file_ext, $this->file_extensions))
  850. {
  851. $ext = $file_ext;
  852. }
  853. }
  854. // strip file ext (if any) and make sure we have a safe url encoded file path
  855. $file_path = preg_replace('/\.[^.]*$/', '', $file_name);
  856. #$file_path = explode(':', $file_path);
  857. $file_path = preg_split("/[:\/]+/", $file_path);
  858. foreach($file_path as &$part)
  859. {
  860. // make sure it's a valid url title
  861. $part = str_replace('.', '', $part);
  862. // insist upon alphanumeric characters and - or _
  863. $part = trim(preg_replace('/[^a-z0-9\-\_]+/', '-', strtolower($part)), '-');
  864. }
  865. unset($part); // remove reference
  866. // remove any empty url parts
  867. $file_path = array_filter($file_path);
  868. $file_path = $this->path . implode('/', $file_path) . '.' . $ext;
  869. if ( file_exists($file_path))
  870. {
  871. $this->EE->TMPL->log_item("Stash: reading file " . $file_path);
  872. $value = str_replace("\r\n", "\n", file_get_contents($file_path));
  873. $set = TRUE;
  874. // disable tag parsing on set when parse_stage is 'get'
  875. if ($parse_stage == 'get')
  876. {
  877. $this->parse_complete = TRUE;
  878. }
  879. }
  880. else
  881. {
  882. $this->EE->output->show_user_error('general', sprintf($this->EE->lang->line('stash_file_not_found'), $file_path));
  883. return;
  884. }
  885. }
  886. }
  887. // set to default value if it NULL or empty string (this permits '0' to be a valid value)
  888. if ( ($value === NULL || $value === '') && ! is_null($default))
  889. {
  890. $value = $default;
  891. $set = TRUE;
  892. }
  893. // create/update value of variable if required
  894. // note: don't save if we're updating a variable (to avoid recursion)
  895. if ( $set && ! $this->_update)
  896. {
  897. $this->EE->TMPL->tagparams['name'] = $name;
  898. $this->EE->TMPL->tagparams['output'] = 'yes';
  899. $this->EE->TMPL->tagdata = $value;
  900. $this->replace = TRUE;
  901. $value = $this->set();
  902. }
  903. $this->EE->TMPL->log_item('Stash: RETRIEVED '. $name . ' with value ' . $value);
  904. // save to bundle
  905. if ($bundle !== NULL)
  906. {
  907. if ( ! isset(self::$bundles[$bundle]))
  908. {
  909. self::$bundles[$bundle] = array();
  910. }
  911. self::$bundles[$bundle][$name] = $value;
  912. }
  913. // are we outputting the variable?
  914. if ($output)
  915. {
  916. if ( ! $file )
  917. {
  918. $value = $this->_parse_output($value, $match, $filter, $default);
  919. }
  920. else
  921. {
  922. // If this is a variable loaded originally from a file, parse if the desired parse stage is on retrieval (parse_stage="get|both")
  923. if ( ($parse_stage == 'get' || $parse_stage == 'both'))
  924. {
  925. $this->parse_complete = FALSE; // enable parsing
  926. $this->parse_vars = TRUE; // ensure early global and stash vars are always fully parsed
  927. $value = $this->_parse_output($value, $match, $filter, $default);
  928. }
  929. else
  930. {
  931. // ensure early global vars are always parsed
  932. $value = $this->_parse_template_vars($value);
  933. }
  934. }
  935. return $value;
  936. }
  937. }
  938. // ---------------------------------------------------------
  939. /**
  940. * Define default/fallback content for a stash variable from enclosed tagdata
  941. *
  942. * @access public
  943. * @return string
  944. */
  945. public function block()
  946. {
  947. /* Sample use
  948. ---------------------------------------------------------
  949. {exp:stash:block name="page_content"}
  950. default content
  951. {/exp:stash:block}
  952. {exp:stash:block:page_content}
  953. default content
  954. {/exp:stash:block:page_content}
  955. --------------------------------------------------------- */
  956. $tag_parts = $this->EE->TMPL->tagparts;
  957. if ( is_array( $tag_parts ) && isset( $tag_parts[2] ) )
  958. {
  959. if (isset($tag_parts[3]))
  960. {
  961. $this->EE->TMPL->tagparams['context'] = $this->EE->TMPL->fetch_param('context', $tag_parts[2]);
  962. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->fetch_param('name', $tag_parts[3]);
  963. }
  964. else
  965. {
  966. // no context or name provided?
  967. if ( ! isset($this->EE->TMPL->tagparams['name']) AND ! isset($this->EE->TMPL->tagparams['context']))
  968. {
  969. $this->EE->TMPL->tagparams['context'] = 'block';
  970. }
  971. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->fetch_param('name', $tag_parts[2]);
  972. }
  973. }
  974. // is this block dependent on one or more other stash variables *being set*?
  975. if ($requires = $this->EE->TMPL->fetch_param('requires', FALSE))
  976. {
  977. $requires = explode('|', $requires);
  978. foreach ($requires as $var)
  979. {
  980. if ( ! isset($this->_stash[$var]))
  981. {
  982. return '';
  983. }
  984. }
  985. }
  986. $this->EE->TMPL->tagparams['default'] = $this->EE->TMPL->tagdata;
  987. $this->EE->TMPL->tagdata = FALSE;
  988. return $this->get();
  989. }
  990. // ---------------------------------------------------------
  991. /**
  992. * Inject a stash embed into a variable or block
  993. *
  994. * @access public
  995. * @return string|void
  996. */
  997. public function extend()
  998. {
  999. /* Sample use
  1000. ---------------------------------------------------------
  1001. {exp:stash:extend name="content" with="views:my_embed" stash:my_var="value"}
  1002. Or as a tag pair with an arbitrary 4th tagpart:
  1003. {exp:stash:extend:block name="content" with="views:my_embed"}
  1004. {stash:my_var}value{/stash:my_var}
  1005. {/exp:stash:extend:block}
  1006. --------------------------------------------------------- */
  1007. if ( FALSE !== $with = $this->EE->TMPL->fetch_param('with', FALSE))
  1008. {
  1009. $embed_vars = array();
  1010. unset($this->EE->TMPL->tagparams['with']);
  1011. // embed vars passed as params
  1012. foreach ($this->EE->TMPL->tagparams as $key => $val)
  1013. {
  1014. if (strncmp($key, 'stash:', 6) == 0)
  1015. {
  1016. $embed_vars[substr($key, 6)] = $val;
  1017. unset($this->EE->TMPL->tagparams[$key]);
  1018. }
  1019. }
  1020. // if this is a tag pair, capture data enclosed by {stash:...} pairs
  1021. if ($this->EE->TMPL->tagdata)
  1022. {
  1023. foreach($this->EE->TMPL->var_pair as $key => $val)
  1024. {
  1025. if (strncmp($key, 'stash:', 6) == 0)
  1026. {
  1027. $pattern = '/'.LD.$key.RD.'(.*)'.LD.'\/'.$key.RD.'/Usi';
  1028. preg_match($pattern, $this->EE->TMPL->tagdata, $matches);
  1029. if ( ! empty($matches))
  1030. {
  1031. $embed_vars[substr($key, 6)] = $matches[1];
  1032. }
  1033. }
  1034. }
  1035. }
  1036. // add embed vars directly to the stash session cache
  1037. #$this->EE->session->cache['stash'] = array_merge($this->EE->session->cache['stash'], $embed_vars);
  1038. // add embed vars to the static cache for parsing at the point the extended embed is included into the page
  1039. if (count($embed_vars) > 0)
  1040. {
  1041. if ( ! isset(self::$_cache['embed_vars']))
  1042. {
  1043. self::$_cache['embed_vars'] = array();
  1044. }
  1045. // generate a unique id to identify this embed instance
  1046. $id = $this->EE->functions->random();
  1047. // cache the variables for later
  1048. self::$_cache['embed_vars'][$id] = $embed_vars;
  1049. // add id as a parameter
  1050. $with .= ' id="' . $id .'"';
  1051. }
  1052. // now inject the embed into the named block/variable
  1053. $this->EE->TMPL->tagdata = LD . 'exp:stash:embed:' . $with . RD;
  1054. return $this->set();
  1055. }
  1056. }
  1057. // ---------------------------------------------------------
  1058. /**
  1059. * Clone a variable / list
  1060. *
  1061. * @access public
  1062. * @return string
  1063. */
  1064. public function copy()
  1065. {
  1066. /* Sample use
  1067. ---------------------------------------------------------
  1068. {exp:stash:copy
  1069. name="original_name"
  1070. context="original_context"
  1071. scope="original_scope"
  1072. type="original_type"
  1073. copy_name="copy_name"
  1074. copy_context="copy_context"
  1075. copy_scope="copy_scope"
  1076. copy_type="copy_type"
  1077. }
  1078. --------------------------------------------------------- */
  1079. // get the original variable value, restricting which params are passed to a minimum
  1080. $this->EE->TMPL->tagdata = $this->_run_tag('get', array('name', 'type', 'scope', 'context'));
  1081. // prep the tagparams with the values for the clone
  1082. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->fetch_param('copy_name', FALSE);
  1083. $this->EE->TMPL->tagparams['context'] = $this->EE->TMPL->fetch_param('copy_context', NULL);
  1084. $this->EE->TMPL->tagparams['scope'] = strtolower($this->EE->TMPL->fetch_param('copy_scope', $this->default_scope));
  1085. $this->EE->TMPL->tagparams['type'] = $this->EE->TMPL->fetch_param('copy_type', 'variable');
  1086. // re-initialise Stash with the new params
  1087. $this->init();
  1088. // clone the bugger
  1089. return $this->set();
  1090. }
  1091. // ---------------------------------------------------------
  1092. /**
  1093. * Append the specified value to an already existing variable.
  1094. *
  1095. * @access public
  1096. * @return void
  1097. */
  1098. public function append()
  1099. {
  1100. $this->_update = TRUE;
  1101. $this->_append = TRUE;
  1102. return $this->set();
  1103. }
  1104. // ---------------------------------------------------------
  1105. /**
  1106. * Prepend the specified value to an already existing variable.
  1107. *
  1108. * @access public
  1109. * @return void
  1110. */
  1111. public function prepend()
  1112. {
  1113. $this->_update = TRUE;
  1114. $this->_append = FALSE;
  1115. return $this->set();
  1116. }
  1117. // ---------------------------------------------------------
  1118. /**
  1119. * Single tag version of set(), for when you need to use a
  1120. * plugin as a tag parameter (always use with parse="inward")
  1121. *
  1122. *
  1123. * @access public
  1124. * @param bool $update Update an existing stashed variable
  1125. * @return void
  1126. */
  1127. public function set_value()
  1128. {
  1129. /* Sample use
  1130. ---------------------------------------------------------
  1131. {exp:stash:set_value name="title" value="{exp:another:tag}" type="snippet" parse="inward"}
  1132. --------------------------------------------------------- */
  1133. $this->EE->TMPL->tagdata = $this->EE->TMPL->fetch_param('value', FALSE);
  1134. if ( $this->EE->TMPL->tagdata !== FALSE )
  1135. {
  1136. return $this->set();
  1137. }
  1138. }
  1139. // ---------------------------------------------------------
  1140. /**
  1141. * Single tag version of append()
  1142. *
  1143. * @access public
  1144. * @return void
  1145. */
  1146. public function append_value()
  1147. {
  1148. $this->_update = TRUE;
  1149. $this->_append = TRUE;
  1150. return $this->set_value();
  1151. }
  1152. // ---------------------------------------------------------
  1153. /**
  1154. * Single tag version of prepend()
  1155. *
  1156. * @access public
  1157. * @return void
  1158. */
  1159. public function prepend_value()
  1160. {
  1161. $this->_update = TRUE;
  1162. $this->_append = FALSE;
  1163. return $this->set_value();
  1164. }
  1165. // ---------------------------------------------------------
  1166. /**
  1167. * Set the current context
  1168. *
  1169. * @access public
  1170. * @return void
  1171. */
  1172. public function context()
  1173. {
  1174. if ( !! $name = $this->EE->TMPL->fetch_param('name', FALSE) )
  1175. {
  1176. self::$context = $name;
  1177. }
  1178. }
  1179. // ---------------------------------------------------------
  1180. /**
  1181. * Checks if a variable or string is empty or non-existent, handy for conditionals
  1182. *
  1183. * @access public
  1184. * @param $string a string to test
  1185. * @return integer
  1186. */
  1187. public function not_empty($string = NULL)
  1188. {
  1189. /* Sample use
  1190. ---------------------------------------------------------
  1191. Check a native stash variable, global variable or snippet is not empty:
  1192. {if {exp:stash:not_empty type="snippet" name="title"} }
  1193. Yes! {title} is not empty
  1194. {/if}
  1195. Check any string or variable is not empty even if it's not been Stashed:
  1196. {if {exp:stash:not_empty:string}{my_string}{/exp:stash:not_empty:string} }
  1197. Yes! {my_string} is not empty
  1198. {/if}
  1199. --------------------------------------------------------- */
  1200. if ( ! is_null($string))
  1201. {
  1202. $test = $string;
  1203. }
  1204. elseif ( $this->EE->TMPL->tagdata )
  1205. {
  1206. // parse any vars in the string we're testing
  1207. $this->_parse_sub_template(FALSE, TRUE);
  1208. $test = $this->EE->TMPL->tagdata;
  1209. }
  1210. else
  1211. {
  1212. $test = $this->_run_tag('get', array('name', 'type', 'scope', 'context'));
  1213. }
  1214. $value = str_replace( array("\t", "\n", "\r", "\0", "\x0B"), '', trim($test));
  1215. return empty( $value ) ? 0 : 1;
  1216. }
  1217. // ---------------------------------------------------------
  1218. /**
  1219. * Checks if a variable or string has any content, handy for conditionals
  1220. *
  1221. * @access public
  1222. * @param $string a string to test
  1223. * @return integer
  1224. */
  1225. public function is_empty($string = NULL)
  1226. {
  1227. /* Sample use
  1228. ---------------------------------------------------------
  1229. Check a native stash variable, global variable or snippet is empty:
  1230. {if {exp:stash:is_empty type="snippet" name="title"} }
  1231. Yes! {title} is empty
  1232. {/if}
  1233. Check any string or variable is not empty even if it's not been Stashed:
  1234. {if {exp:stash:is_empty:string}{my_string}{/exp:stash:is_empty:string} }
  1235. Yes! {my_string} is empty
  1236. {/if}
  1237. --------------------------------------------------------- */
  1238. return $this->not_empty($string) == 0 ? 1 : 0;
  1239. }
  1240. // ---------------------------------------------------------
  1241. /**
  1242. * Serialize a multidimenisional array and save as a variable
  1243. *
  1244. * @access public
  1245. * @return string
  1246. */
  1247. public function set_list()
  1248. {
  1249. /* Sample use
  1250. ---------------------------------------------------------
  1251. {exp:stash:set_list name="blog_entries"}
  1252. {stash:item_title}{title}{/stash:item_title}
  1253. {stash:item_img_url}{img_url}{/stash:item_img_url}
  1254. {stash:item_copy}{copy}{/stash:item_copy}
  1255. {/exp:stash:set_list}
  1256. --------------------------------------------------------- */
  1257. // name and context
  1258. $name = $this->EE->TMPL->fetch_param('name', FALSE);
  1259. $context = $this->EE->TMPL->fetch_param('context', NULL);
  1260. $scope = strtolower($this->EE->TMPL->fetch_param('scope', $this->default_scope)); // local|user|site
  1261. // are we trying to *overwrite* an existing list (replace it but not change the existing list if the new list is empty)?
  1262. $overwrite = $this->EE->TMPL->fetch_param('overwrite', FALSE);
  1263. if ($overwrite)
  1264. {
  1265. $this->replace = TRUE;
  1266. }
  1267. if ( !! $name)
  1268. {
  1269. if ($context !== NULL && count( explode(':', $name) == 1 ) )
  1270. {
  1271. $name = $context . ':' . $name;
  1272. }
  1273. }
  1274. // replace '@' placeholders with the current context
  1275. $stash_key = $this->_parse_context($name);
  1276. // no results prefix
  1277. $prefix = $this->EE->TMPL->fetch_param('prefix', NULL);
  1278. // check for prefixed no_results block
  1279. if ( ! is_null($prefix))
  1280. {
  1281. $this->_prep_no_results($prefix);
  1282. }
  1283. // Unprefix common variables in wrapped tags
  1284. if($unprefix = $this->EE->TMPL->fetch_param('unprefix'))
  1285. {
  1286. $this->EE->TMPL->tagdata = $this->_un_prefix($unprefix, $this->EE->TMPL->tagdata);
  1287. }
  1288. // do we want to replace an existing list variable?
  1289. $set = TRUE;
  1290. if ( ! $this->replace && ! $this->_update)
  1291. {
  1292. // try to get existing value
  1293. $existing_value = FALSE;
  1294. if ( array_key_exists($name, $this->_stash))
  1295. {
  1296. $existing_value = $this->_stash[$name];
  1297. }
  1298. elseif ($scope !== 'local')
  1299. {
  1300. // narrow the scope to user?
  1301. $session_id = $scope === 'user' ? $this->_session_id : '_global';
  1302. $existing_value = $this->EE->stash_model->get_key(
  1303. $stash_key,
  1304. $this->bundle_id,
  1305. $session_id,
  1306. $this->site_id
  1307. );
  1308. }
  1309. if ( $existing_value !== FALSE)
  1310. {
  1311. // yes, it's already been stashed, make sure it's in the stash memory cache
  1312. $this->EE->TMPL->tagdata = $this->_stash[$name] = $existing_value;
  1313. // don't overwrite existing value
  1314. $set = FALSE;
  1315. }
  1316. unset($existing_value);
  1317. }
  1318. if ($set)
  1319. {
  1320. // do any parsing and string transforms before making the list
  1321. $this->EE->TMPL->tagdata = $this->_parse_output($this->EE->TMPL->tagdata);
  1322. $this->parse_complete = TRUE; // make sure we don't run parsing again, if we're saving the list
  1323. // get stash variable pairs (note: picks up outer pairs, nested pairs and singles are ignored)
  1324. preg_match_all('#'.LD.'(stash:[a-z0-9\-_]+)'.RD.'.*?'.LD.'/\g{1}'.RD.'#ims', $this->EE->TMPL->tagdata, $matches);
  1325. if (isset($matches[1]))
  1326. {
  1327. $this->EE->TMPL->var_pair = array_flip(array_unique($matches[1]));
  1328. }
  1329. // get the first key and see if it repeats
  1330. $keys = array_keys($this->EE->TMPL->var_pair);
  1331. if ( ! empty($keys))
  1332. {
  1333. $first_key = $keys[0];
  1334. preg_match_all('/'. LD . $first_key . RD . '/', $this->EE->TMPL->tagdata, $matches);
  1335. if (count($matches[0]) > 1)
  1336. {
  1337. // yes we have repeating keys, so let's split the tagdata up into rows
  1338. $this->EE->TMPL->tagdata = str_replace(
  1339. LD . $first_key . RD,
  1340. $this->_list_delimiter . LD . $first_key . RD,
  1341. $this->EE->TMPL->tagdata
  1342. );
  1343. // get an array of rows, remove first element which will be empty
  1344. $rows = explode($this->_list_delimiter, $this->EE->TMPL->tagdata);
  1345. array_shift($rows);
  1346. // serialize each row and append
  1347. // bracket the serilaized string with delimiters
  1348. $tagdata = '';
  1349. foreach($rows as $row)
  1350. {
  1351. $this->EE->TMPL->tagdata = $row;
  1352. $this->_serialize_stash_tag_pairs();
  1353. if ( ! empty($this->EE->TMPL->tagdata))
  1354. {
  1355. $tagdata .= $this->_list_delimiter . $this->EE->TMPL->tagdata;
  1356. }
  1357. }
  1358. $this->EE->TMPL->tagdata = trim($tagdata, $this->_list_delimiter);
  1359. }
  1360. else
  1361. {
  1362. // get the stash var pairs values
  1363. $this->_serialize_stash_tag_pairs();
  1364. }
  1365. if ( $this->not_empty($this->EE->TMPL->tagdata))
  1366. {
  1367. // set the list, but do we need to disable match/against?
  1368. if ( FALSE !== $this->EE->TMPL->fetch_param('against', FALSE))
  1369. {
  1370. // already matched/against a specified column in the list, so disable match/against
  1371. unset($this->EE->TMPL->tagparams['match']);
  1372. unset($this->EE->TMPL->tagparams['against']);
  1373. }
  1374. return $this->set();
  1375. }
  1376. }
  1377. else
  1378. {
  1379. // make sure this variable is marked as empty, so subsquent get_list() calls return no_results
  1380. if (FALSE === $overwrite)
  1381. {
  1382. $this->_stash[$name] = '';
  1383. }
  1384. if ((bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('output'))) // default="no"
  1385. {
  1386. // optionally parse and return no_results tagdata
  1387. // note: output="yes" with set_list should only be used for debugging
  1388. return $this->_no_results();
  1389. }
  1390. else
  1391. {
  1392. // parse no_results tagdata, but don't output
  1393. // note: unless parse_tags="yes", no parsing would occur
  1394. $this->_no_results();
  1395. }
  1396. }
  1397. }
  1398. }
  1399. // ---------------------------------------------------------
  1400. /**
  1401. * Append to array
  1402. *
  1403. * @access public
  1404. * @return string
  1405. */
  1406. public function append_list()
  1407. {
  1408. return $this->_update_list(TRUE);
  1409. }
  1410. // ---------------------------------------------------------
  1411. /**
  1412. * Prepend to array
  1413. *
  1414. * @access public
  1415. * @return string
  1416. */
  1417. public function prepend_list()
  1418. {
  1419. return $this->_update_list(FALSE);
  1420. }
  1421. // ---------------------------------------------------------
  1422. /**
  1423. * Append / prepend to array
  1424. *
  1425. * @access private
  1426. * @param bool $append Append or prepend to an existing list?
  1427. * @return string
  1428. */
  1429. private function _update_list($append=TRUE)
  1430. {
  1431. $name = $this->EE->TMPL->fetch_param('name');
  1432. $context = $this->EE->TMPL->fetch_param('context', NULL);
  1433. $this->EE->TMPL->tagdata = $this->_parse_output($this->EE->TMPL->tagdata);
  1434. $this->parse_complete = TRUE; // make sure we don't run parsing again
  1435. // get stash variable pairs (note: picks up outer pairs, nested pairs and singles are ignored)
  1436. preg_match_all('#'.LD.'(stash:[a-z0-9\-_]+)'.RD.'.*?'.LD.'/\g{1}'.RD.'#ims', $this->EE->TMPL->tagdata, $matches);
  1437. if (isset($matches[1]))
  1438. {
  1439. $this->EE->TMPL->var_pair = array_flip(array_unique($matches[1]));
  1440. }
  1441. // format our list
  1442. $this->_serialize_stash_tag_pairs();
  1443. if ( $this->not_empty($this->EE->TMPL->tagdata))
  1444. {
  1445. // does the list really exist?
  1446. if ($context !== NULL && count( explode(':', $name) == 1 ) )
  1447. {
  1448. $name = $context . ':' . $name;
  1449. }
  1450. $name_in_context = $this->_parse_context($name);
  1451. // get the current value of the list
  1452. $current_value = '';
  1453. if (array_key_exists($name, $this->_stash))
  1454. {
  1455. $current_value = $this->_stash[$name];
  1456. }
  1457. elseif(array_key_exists($name_in_context, $this->_stash))
  1458. {
  1459. $current_value = $this->_stash[$name_in_context];
  1460. }
  1461. // check that the list has a value before appending/prepending
  1462. if ( $this->not_empty($current_value))
  1463. {
  1464. if ($append)
  1465. {
  1466. $this->EE->TMPL->tagdata = $this->_list_delimiter . $this->EE->TMPL->tagdata;
  1467. }
  1468. else
  1469. {
  1470. $this->EE->TMPL->tagdata = $this->EE->TMPL->tagdata . $this->_list_delimiter;
  1471. }
  1472. }
  1473. // update the list, but do we need to disable match/against?
  1474. if ( FALSE !== $this->EE->TMPL->fetch_param('against', FALSE))
  1475. {
  1476. // already matched/against a specified column in the list, so disable match/against
  1477. unset($this->EE->TMPL->tagparams['match']);
  1478. unset($this->EE->TMPL->tagparams['against']);
  1479. }
  1480. return $append ? $this->append() : $this->prepend();
  1481. }
  1482. }
  1483. // ---------------------------------------------------------
  1484. /**
  1485. * Create a union of two or more existing lists
  1486. * Lists *must* share the same keys and be *already in memory*
  1487. *
  1488. * @access public
  1489. * @return string
  1490. */
  1491. public function join_lists()
  1492. {
  1493. /* Sample use
  1494. ---------------------------------------------------------
  1495. {exp:stash:join_lists
  1496. name="my_combined_list"
  1497. lists="list_1,list_2,list3"
  1498. }
  1499. --------------------------------------------------------- */
  1500. // list names
  1501. $lists = $this->EE->TMPL->fetch_param('lists');
  1502. $lists = explode(',', $lists);
  1503. // create an array of values
  1504. $values = array();
  1505. foreach($lists as $name)
  1506. {
  1507. if (array_key_exists($name, $this->_stash))
  1508. {
  1509. // ignore empty lists
  1510. if ( ! empty($this->_stash[$name]))
  1511. {
  1512. $values[] = $this->_stash[$name];
  1513. }
  1514. }
  1515. }
  1516. // implode values into the format of a delimited list, and set as the tagdata
  1517. $this->EE->TMPL->tagdata = implode($this->_list_delimiter, $values);
  1518. // set as a new variable
  1519. return $this->set();
  1520. }
  1521. public function split_list()
  1522. {
  1523. /* Sample use
  1524. ---------------------------------------------------------
  1525. {exp:stash:split_list
  1526. name="my_list_fragment"
  1527. list="list_1"
  1528. match="#^blue$#"
  1529. against="colour"
  1530. }
  1531. --------------------------------------------------------- */
  1532. // the original list
  1533. $old_list = $this->EE->TMPL->fetch_param('list', FALSE);
  1534. // the new list
  1535. $new_list = $this->EE->TMPL->fetch_param('name', FALSE);
  1536. // limit the number of rows to copy?
  1537. $limit = $this->EE->TMPL->fetch_param('limit', FALSE);
  1538. if ($old_list && $new_list)
  1539. {
  1540. $this->EE->TMPL->tagparams['name'] = $old_list;
  1541. // apply filters to the original list and generate an array
  1542. $list = $this->rebuild_list();
  1543. // apply limit
  1544. if ($limit !== FALSE)
  1545. {
  1546. $list = array_slice($list, 0, $limit);
  1547. }
  1548. // flatten the list array into a string, ready for setting as a variable
  1549. $this->EE->TMPL->tagdata = $this->flatten_list($list);
  1550. // reset the name parameter
  1551. $this->EE->TMPL->tagparams['name'] = $new_list;
  1552. // unset params used for filtering
  1553. unset($this->EE->TMPL->tagparams['match']);
  1554. unset($this->EE->TMPL->tagparams['against']);
  1555. // set as a new variable
  1556. return $this->set();
  1557. }
  1558. }
  1559. // ---------------------------------------------------------
  1560. /**
  1561. * Retrieve a serialised array of items, explode and replace into tagdata
  1562. *
  1563. * @access public
  1564. * @return string
  1565. */
  1566. public function get_list($params=array(), $value='', $type='variable', $scope='user')
  1567. {
  1568. /* Sample use
  1569. ---------------------------------------------------------
  1570. {exp:stash:get_list name="page_items" orderby="item_title" sort="asc"}
  1571. <h2>{item_title}</h2>
  1572. <img src="{item_img_url}" />
  1573. {item_copy}
  1574. {/exp:stash:get_list}
  1575. --------------------------------------------------------- */
  1576. // is this method being called directly?
  1577. if ( func_num_args() > 0)
  1578. {
  1579. if ( !(isset($this) && get_class($this) == __CLASS__))
  1580. {
  1581. return self::_api_static_call(__FUNCTION__, $params, $type, $scope, $value);
  1582. }
  1583. else
  1584. {
  1585. return $this->_api_call(__FUNCTION__, $params, $type, $scope, $value);
  1586. }
  1587. }
  1588. if ( $this->process !== 'inline')
  1589. {
  1590. if ($out = $this->_post_parse(__FUNCTION__)) return $out;
  1591. }
  1592. $limit = $this->EE->TMPL->fetch_param('limit', FALSE);
  1593. $offset = $this->EE->TMPL->fetch_param('offset', 0);
  1594. $default = $this->EE->TMPL->fetch_param('default', ''); // default value
  1595. $filter = $this->EE->TMPL->fetch_param('filter', NULL); // regex pattern to search final output for
  1596. $prefix = $this->EE->TMPL->fetch_param('prefix', NULL); // optional namespace for common vars like {count}
  1597. $require_prefix = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('require_prefix', 'yes')); // if prefix="" is set, only placeholders using the prefix will be parsed
  1598. $paginate = $this->EE->TMPL->fetch_param('paginate', FALSE);
  1599. $paginate_param = $this->EE->TMPL->fetch_param('paginate_param', NULL); // if using query string style pagination
  1600. $track = $this->EE->TMPL->fetch_param('track', FALSE); // one or more column values to track as a static variable, e.g. entry_id|color
  1601. $list_html = '';
  1602. $list_markers = array();
  1603. // check for prefixed no_results block
  1604. if ( ! is_null($prefix))
  1605. {
  1606. $this->_prep_no_results($prefix);
  1607. }
  1608. // retrieve the list array
  1609. $list = $this->rebuild_list();
  1610. // return no results if this variable has no value
  1611. if ( empty($list))
  1612. {
  1613. return $this->_no_results();
  1614. }
  1615. // apply prefix
  1616. if ( ! is_null($prefix))
  1617. {
  1618. foreach ($list as $index => $array)
  1619. {
  1620. foreach ($array as $k => $v)
  1621. {
  1622. $list[$index][$prefix.':'.$k] = $v;
  1623. // do we want to stop un-prefixed variables being parsed?
  1624. if ($require_prefix)
  1625. {
  1626. unset($list[$index][$k]);
  1627. }
  1628. }
  1629. }
  1630. }
  1631. // absolute results total
  1632. $absolute_results = count($list);
  1633. // does limit contain a fraction?
  1634. if ($limit)
  1635. {
  1636. $limit = $this->_parse_fraction($limit, $offset, $absolute_results);
  1637. }
  1638. // does offset contain a fraction, e.g. '1/3' ?
  1639. if ($offset)
  1640. {
  1641. $offset = $this->_parse_fraction($offset, 0, $absolute_results);
  1642. }
  1643. // pagination
  1644. if ($paginate)
  1645. {
  1646. // remove prefix if used in the paginate tag pair
  1647. if ( ! is_null($prefix))
  1648. {
  1649. if (preg_match("/(".LD.$prefix.":paginate".RD.".+?".LD.'\/'.$prefix.":paginate".RD.")/s", $this->EE->TMPL->tagdata, $paginate_match))
  1650. {
  1651. $paginate_template = str_replace($prefix.':','', $paginate_match[1]);
  1652. $this->EE->TMPL->tagdata = str_replace($paginate_match[1], $paginate_template, $this->EE->TMPL->tagdata);
  1653. }
  1654. }
  1655. // pagination template
  1656. $this->EE->load->library('pagination');
  1657. // are we passing the offset in the query string?
  1658. if ( ! is_null($paginate_param))
  1659. {
  1660. // prep the base pagination object
  1661. $this->EE->pagination->query_string_segment = $paginate_param;
  1662. $this->EE->pagination->page_query_string = TRUE;
  1663. }
  1664. // create a pagination object instance
  1665. if (version_compare(APP_VER, '2.8', '>='))
  1666. {
  1667. $this->pagination = $this->EE->pagination->create();
  1668. }
  1669. else
  1670. {
  1671. $this->pagination = new Pagination_object(__CLASS__);
  1672. }
  1673. // pass the offset to the pagination object
  1674. if ( ! is_null($paginate_param))
  1675. {
  1676. // we only want the offset integer, ignore the 'P' prefix inserted by EE_Pagination
  1677. $this->pagination->offset = filter_var($this->EE->input->get($paginate_param, TRUE), FILTER_SANITIZE_NUMBER_INT);
  1678. if ( ! is_null($this->EE->TMPL->fetch_param('paginate_base', NULL)))
  1679. {
  1680. // make sure paginate_base ends with a '?', if specified
  1681. $base=$this->EE->TMPL->tagparams['paginate_base'];
  1682. $this->EE->TMPL->tagparams['paginate_base'] = $base.((!strpos($base, '?'))? '?': '');
  1683. }
  1684. }
  1685. else
  1686. {
  1687. $this->pagination->offset = 0;
  1688. }
  1689. // determine pagination limit & total rows
  1690. $page_limit = $limit ? $limit : 100; // same default limit as channel entries module
  1691. $page_total_rows = $absolute_results - $offset;
  1692. if (version_compare(APP_VER, '2.8', '>='))
  1693. {
  1694. // find and remove the pagination template from tagdata wrapped by get_list
  1695. $this->EE->TMPL->tagdata = $this->pagination->prepare(ee()->TMPL->tagdata);
  1696. // build
  1697. $this->pagination->build($page_total_rows, $page_limit);
  1698. }
  1699. else
  1700. {
  1701. $this->pagination->per_page = $page_limit;
  1702. $this->pagination->total_rows = $page_total_rows;
  1703. $this->pagination->get_template();
  1704. $this->pagination->build();
  1705. }
  1706. // update offset
  1707. $offset = $offset + $this->pagination->offset;
  1708. }
  1709. // {absolute_count} - absolute count to the ordered/sorted items
  1710. $i=0;
  1711. foreach($list as $key => &$value)
  1712. {
  1713. $i++;
  1714. $value['absolute_count'] = $i;
  1715. // {prefix:absolute_count}
  1716. if ( ! is_null($prefix))
  1717. {
  1718. $value[$prefix.':absolute_count'] = $i;
  1719. }
  1720. }
  1721. // {absolute_results} - record the total number of list rows
  1722. $list_markers['absolute_results'] = $absolute_results;
  1723. // slice array depending on limit/offset
  1724. if ($limit && $offset)
  1725. {
  1726. $list = array_slice($list, $offset, $limit);
  1727. }
  1728. elseif ($limit)
  1729. {
  1730. $list = array_slice($list, 0, $limit);
  1731. }
  1732. elseif ($offset)
  1733. {
  1734. $list = array_slice($list, $offset);
  1735. }
  1736. // prefixes
  1737. if ( ! is_null($prefix))
  1738. {
  1739. // {prefix:absolute_results}
  1740. $list_markers[$prefix.':absolute_results'] = $list_markers['absolute_results'];
  1741. // {prefix:total_results}
  1742. $list_markers[$prefix.':total_results'] = count($list);
  1743. }
  1744. if (count($list) > 0)
  1745. {
  1746. // track use of one or more elements
  1747. if ($track)
  1748. {
  1749. if ( ! isset(self::$_cache['track']))
  1750. {
  1751. self::$_cache['track'] = array();
  1752. }
  1753. $track = explode('|', $track);
  1754. foreach($track as $t)
  1755. {
  1756. if ( ! isset(self::$_cache['track'][$t]))
  1757. {
  1758. self::$_cache['track'][$t] = array();
  1759. }
  1760. foreach($list as $key => $v)
  1761. {
  1762. if (isset($v[$t]))
  1763. {
  1764. self::$_cache['track'][$t][] = $v[$t];
  1765. }
  1766. }
  1767. // ensure the tracked values are always unique
  1768. self::$_cache['track'][$t] = array_unique(self::$_cache['track'][$t]);
  1769. }
  1770. }
  1771. if ( ! is_null($prefix))
  1772. {
  1773. // {prefix:count}
  1774. $i=0;
  1775. foreach($list as $key => &$v)
  1776. {
  1777. $i++;
  1778. $v[$prefix.':count'] = $i;
  1779. }
  1780. // {prefix:switch = ""}
  1781. if (strpos($this->EE->TMPL->tagdata, LD.$prefix.':switch') !== FALSE)
  1782. {
  1783. $this->EE->TMPL->tagdata = str_replace(LD.$prefix.':switch', LD.'switch', $this->EE->TMPL->tagdata);
  1784. }
  1785. }
  1786. // disable backspace param to stop parse_variables() doing it automatically
  1787. // because it can potentially break unparsed conditionals / tags etc in the list
  1788. $backspace = $this->EE->TMPL->fetch_param('backspace', FALSE);
  1789. $this->EE->TMPL->tagparams['backspace'] = FALSE;
  1790. // prep {if IN ()}...{/if} conditionals
  1791. if ($this->parse_if_in)
  1792. {
  1793. // prefixed ifs? We have to hide them in EE 2.9+ if this tagdata is in the root template
  1794. if ( ! is_null($prefix))
  1795. {
  1796. $this->EE->TMPL->tagdata = str_replace(LD.$prefix.':if', LD.'if', $this->EE->TMPL->tagdata);
  1797. $this->EE->TMPL->tagdata = str_replace(LD.'/'.$prefix.':if'.RD, LD.'/if'.RD, $this->EE->TMPL->tagdata);
  1798. }
  1799. $this->EE->TMPL->tagdata = $this->_prep_in_conditionals($this->EE->TMPL->tagdata);
  1800. }
  1801. // Replace into template.
  1802. //
  1803. // KNOWN BUG:
  1804. // TMPL::parse_variables() runs functions::preps conditionals() which is buggy with advanced conditionals
  1805. // that reference *external* variables (such as global variables, segment variables).
  1806. // E.g. say you have a list var '{tel}' with value '123' and global var '{pg_tel}' with value '456'
  1807. // {if pg_tel OR pg_fax} is changed to {if pg_"123" OR pg_fax} and will throw an error :(
  1808. //
  1809. // WORKAROUND:
  1810. // use the prefix="" param for local list vars when you need to reference external
  1811. // variables inside the get_list tag pair which have names that could collide.
  1812. $list_html = $this->EE->TMPL->parse_variables($this->EE->TMPL->tagdata, $list);
  1813. // restore original backspace parameter
  1814. $this->EE->TMPL->tagparams['backspace'] = $backspace;
  1815. // parse other markers
  1816. $list_html = $this->EE->TMPL->parse_variables_row($list_html, $list_markers);
  1817. // render pagination
  1818. if ($paginate)
  1819. {
  1820. $list_html = $this->pagination->render($list_html);
  1821. }
  1822. // now apply final output transformations / parsing
  1823. return $this->_parse_output($list_html, NULL, $filter, $default);
  1824. }
  1825. else
  1826. {
  1827. return $this->_no_results();
  1828. }
  1829. }
  1830. // ---------------------------------------------------------
  1831. /**
  1832. * Retrieve the item count for a given list
  1833. *
  1834. * @access public
  1835. * @return string
  1836. */
  1837. public function list_count()
  1838. {
  1839. // retrieve the list array
  1840. $list = $this->rebuild_list();
  1841. // return 0 if this variable has no value
  1842. if ($list == '') return '0';
  1843. $count = count($list);
  1844. return "$count"; // make sure we return a string
  1845. }
  1846. // ---------------------------------------------------------
  1847. /**
  1848. * Restore values for a given bundle
  1849. *
  1850. * @access public
  1851. * @param bool set the bundled variables into Stash variables?
  1852. * @return string
  1853. */
  1854. public function get_bundle($set=TRUE)
  1855. {
  1856. /* Sample use
  1857. ---------------------------------------------------------
  1858. {exp:stash:get_bundle name="contact_form" context="@" limit="5"}
  1859. {contact_name}
  1860. {/exp:stash:get_bundle}
  1861. --------------------------------------------------------- */
  1862. $out = $this->EE->TMPL->tagdata;
  1863. if ( !! $bundle = $this->EE->TMPL->fetch_param('name', FALSE) )
  1864. {
  1865. // get the bundle id, cache to memory for efficient reuse later
  1866. $bundle_id = $this->EE->stash_model->get_bundle_by_name($bundle);
  1867. // does this bundle already exist?
  1868. if ( $bundle_id )
  1869. {
  1870. $bundle_array = array();
  1871. $tpl = $this->EE->TMPL->tagdata;
  1872. $this->bundle_id = $bundle_id;
  1873. // get params
  1874. $unique = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('unique', 'yes'));
  1875. $index = $this->EE->TMPL->fetch_param('index', NULL);
  1876. $context = $this->EE->TMPL->fetch_param('context', NULL);
  1877. $scope = strtolower($this->EE->TMPL->fetch_param('scope', 'user')); // user|site
  1878. // if this is a unique bundle, restore the bundled variables to static bundles array
  1879. if ($unique || ! is_null($index))
  1880. {
  1881. if ( $index !== NULL && $index > 0)
  1882. {
  1883. $bundle .= '_'.$index;
  1884. $this->EE->TMPL->tagparams['name'] = $bundle;
  1885. }
  1886. // get bundle var
  1887. $bundle_entry_key = $bundle;
  1888. if ($bundle !== NULL && count( explode(':', $bundle) == 1 ) )
  1889. {
  1890. $bundle_entry_key = $context . ':' . $bundle;
  1891. }
  1892. $session_id = $scope === 'user' ? $this->_session_id : '';
  1893. $bundle_entry_key = $this->_parse_context($bundle_entry_key);
  1894. // look for our key
  1895. if ( $bundle_value = $this->EE->stash_model->get_key(
  1896. $bundle_entry_key,
  1897. $this->bundle_id,
  1898. $session_id,
  1899. $this->site_id
  1900. ))
  1901. {
  1902. $bundle_array[0] = unserialize($bundle_value);
  1903. foreach ($bundle_array[0] as $key => $val)
  1904. {
  1905. self::$bundles[$bundle][$key] = $val;
  1906. }
  1907. }
  1908. }
  1909. else
  1910. {
  1911. // FUTURE FEATURE: get all entries for a bundle with *multiple* rows
  1912. }
  1913. // replace into template
  1914. if ( ! empty($tpl))
  1915. {
  1916. // take care of any unparsed current context pointers '@'
  1917. if ( ! is_null($context))
  1918. {
  1919. $tpl = str_replace(LD.'@:', LD.$context.':', $tpl);
  1920. }
  1921. if ( ! empty($bundle_array))
  1922. {
  1923. $out = '';
  1924. foreach($bundle_array as $vars)
  1925. {
  1926. $out .= $this->EE->functions->var_swap($tpl, $vars);
  1927. // set variables
  1928. if ($set)
  1929. {
  1930. foreach($vars as $name => $value)
  1931. {
  1932. $this->EE->TMPL->tagparams['name'] = $name;
  1933. $this->EE->TMPL->tagparams['type'] = 'variable';
  1934. $this->EE->TMPL->tagdata = $value;
  1935. $this->replace = TRUE;
  1936. $this->_run_tag('set', array('name', 'type', 'scope', 'context'));
  1937. }
  1938. }
  1939. }
  1940. }
  1941. // prep 'IN' conditionals if the retreived var is a delimited string
  1942. if ($this->parse_if_in)
  1943. {
  1944. $out = $this->_prep_in_conditionals($out);
  1945. }
  1946. }
  1947. $this->EE->TMPL->log_item("Stash: RETRIEVED bundle ".$bundle);
  1948. }
  1949. }
  1950. return $out;
  1951. }
  1952. // ---------------------------------------------------------
  1953. /**
  1954. * Set values into a bundle
  1955. *
  1956. * @access public
  1957. * @return void
  1958. */
  1959. public function set_bundle()
  1960. {
  1961. /* Sample use
  1962. ---------------------------------------------------------
  1963. {exp:stash:set_bundle name="contact_form"}
  1964. --------------------------------------------------------- */
  1965. if ( !! $bundle = $this->EE->TMPL->fetch_param('name', FALSE) )
  1966. {
  1967. if ( isset(self::$bundles[$bundle]))
  1968. {
  1969. // get params
  1970. $bundle_label = $this->EE->TMPL->fetch_param('label', $bundle);
  1971. $unique = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('unique', 'yes'));
  1972. $bundle_entry_key = $bundle_entry_label = $bundle;
  1973. // get the bundle id
  1974. $bundle_id = $this->EE->stash_model->get_bundle_by_name($bundle);
  1975. // does this bundle already exist? Let's try to get it's id
  1976. if ( ! $bundle_id )
  1977. {
  1978. // doesn't exist, let's create it
  1979. $bundle_id = $this->EE->stash_model->insert_bundle(
  1980. $bundle,
  1981. $bundle_label
  1982. );
  1983. }
  1984. elseif ( ! $unique)
  1985. {
  1986. // bundle exists, but do we want more than one entry per bundle?
  1987. $entry_count = $this->EE->stash_model->bundle_entry_count($bundle_id, $this->site_id);
  1988. if ($entry_count > 0)
  1989. {
  1990. $bundle_entry_key .= '_'.$entry_count;
  1991. $bundle_entry_label = $bundle_entry_key;
  1992. }
  1993. }
  1994. // stash the data under a single key
  1995. $this->EE->TMPL->tagparams['name'] = $bundle_entry_key;
  1996. $this->EE->TMPL->tagparams['label'] = $bundle_entry_label;
  1997. $this->EE->TMPL->tagparams['save'] = 'yes';
  1998. $this->EE->TMPL->tagparams['scope'] = 'user';
  1999. $this->EE->TMPL->tagdata = serialize(self::$bundles[$bundle]);
  2000. $this->bundle_id = $bundle_id;
  2001. unset(self::$bundles[$bundle]);
  2002. return $this->set();
  2003. }
  2004. }
  2005. }
  2006. // ---------------------------------------------------------
  2007. /**
  2008. * Bundle up a collection of variables and save in the database
  2009. *
  2010. * @access public
  2011. * @param array $params
  2012. * @param array $dynamic
  2013. * @return void
  2014. */
  2015. public function bundle($params = array(), $dynamic = array())
  2016. {
  2017. /* Sample use
  2018. ---------------------------------------------------------
  2019. {exp:stash:bundle name="contact_form" context="@" unique="no" type="snippet" refresh="10"}
  2020. {exp:stash:get dynamic="yes" name="orderby" output="no" default="persons_last_name" match="#^[a-zA-Z0-9_-]+$#"}
  2021. {exp:stash:get dynamic="yes" name="sort" output="no" default="asc" match="#^asc$|^desc$#"}
  2022. {exp:stash:get dynamic="yes" name="filter" output="no" default="" match="#^[a-zA-Z0-9_-]+$#"}
  2023. {exp:stash:get dynamic="yes" name="in" output="no" default="" match="#^[a-zA-Z0-9_-]+$#"}
  2024. {exp:stash:get dynamic="yes" name="field" output="no" match="#^[a-zA-Z0-9_-]+$#" default="persons_last_name"}
  2025. {/exp:stash:bundle}
  2026. --------------------------------------------------------- */
  2027. // is this method being called statically from PHP?
  2028. if ( func_num_args() > 0 && !(isset($this) && get_class($this) == __CLASS__))
  2029. {
  2030. // as this function is called statically,
  2031. // we need to get an instance of this object and run get()
  2032. $self = new self();
  2033. // set parameters
  2034. $this->EE->TMPL->tagparams = $params;
  2035. // convert tagdata array
  2036. if ( is_array($dynamic))
  2037. {
  2038. $this->EE->TMPL->tagdata = '';
  2039. foreach ($dynamic as $name => $options)
  2040. {
  2041. $this->EE->TMPL->tagdata .= LD.'exp:stash:get dynamic="yes" name="'.$name.'"';
  2042. foreach ($options as $option => $value)
  2043. {
  2044. $this->EE->TMPL->tagdata .= ' '.$option.'="'.$value.'"';
  2045. }
  2046. $this->EE->TMPL->tagdata .= RD;
  2047. }
  2048. }
  2049. else
  2050. {
  2051. $this->EE->TMPL->tagdata = $dynamic;
  2052. }
  2053. return $self->bundle();
  2054. }
  2055. if ( !! $bundle = $this->EE->TMPL->fetch_param('name', FALSE) )
  2056. {
  2057. // build a string of parameters to inject into nested stash tags
  2058. $context = $this->EE->TMPL->fetch_param('context', NULL);
  2059. $params = 'bundle="' . $bundle . '" scope="local"';
  2060. if ($context !== NULL )
  2061. {
  2062. $params .= ' context="'.$context.'"';
  2063. }
  2064. // add params to nested tags
  2065. $this->EE->TMPL->tagdata = preg_replace( '/('.LD.'exp:stash:get|'.LD.'exp:stash:set)/i', '$1 '.$params, $this->EE->TMPL->tagdata);
  2066. // get existing values from bundle
  2067. $this->get_bundle(FALSE);
  2068. // parse stash tags in the bundle
  2069. $this->_parse_sub_template();
  2070. // save the bundle values
  2071. $this->set_bundle();
  2072. }
  2073. }
  2074. // ----------------------------------------------------------
  2075. /**
  2076. * Embed a Stash template file in the current template
  2077. *
  2078. * @access public
  2079. * @return string
  2080. */
  2081. public function embed()
  2082. {
  2083. /* Sample use
  2084. ---------------------------------------------------------
  2085. {stash:embed name="my_template"
  2086. context="my_template_folder"
  2087. process="start"
  2088. stash:another_var1="value 1"
  2089. stash:another_var2="value 2"
  2090. }
  2091. Alternative sytax:
  2092. {stash:embed:name} or
  2093. {stash:embed:context:name}
  2094. --------------------------------------------------------- */
  2095. // mandatory parameter values for template files
  2096. $this->EE->TMPL->tagparams['file'] = 'yes';
  2097. $this->EE->TMPL->tagparams['embed_vars'] = array();
  2098. // parse="yes"?
  2099. $this->set_parse_params();
  2100. // default parameter values
  2101. $this->EE->TMPL->tagparams['save'] = $this->EE->TMPL->fetch_param('save', 'yes');
  2102. $this->EE->TMPL->tagparams['scope'] = $this->EE->TMPL->fetch_param('scope', 'site');
  2103. $this->EE->TMPL->tagparams['parse_tags'] = $this->EE->TMPL->fetch_param('parse_tags', 'yes');
  2104. $this->EE->TMPL->tagparams['parse_vars'] = $this->EE->TMPL->fetch_param('parse_vars', 'yes');
  2105. $this->EE->TMPL->tagparams['parse_conditionals'] = $this->EE->TMPL->fetch_param('parse_conditionals', 'yes');
  2106. // name and context passed in tagparts?
  2107. if (isset($this->EE->TMPL->tagparts[3]))
  2108. {
  2109. $this->EE->TMPL->tagparams['context'] = $this->EE->TMPL->tagparts[2];
  2110. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->tagparts[3];
  2111. }
  2112. elseif(isset($this->EE->TMPL->tagparts[2]))
  2113. {
  2114. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->tagparts[2];
  2115. }
  2116. // default to processing embeds at end
  2117. $this->EE->TMPL->tagparams['process'] = $this->EE->TMPL->fetch_param('process', 'end');
  2118. // is this a static template?
  2119. if ( $this->EE->TMPL->tagparams['process'] !== 'static')
  2120. {
  2121. // non-static templates are assigned to the template bundle by default
  2122. $this->EE->TMPL->tagparams['bundle'] = $this->EE->TMPL->fetch_param('bundle', 'template');
  2123. // by default, parse the template when it is retrieved from the database (like a standard EE embed)
  2124. $this->EE->TMPL->tagparams['parse_stage'] = $this->EE->TMPL->fetch_param('parse_stage', 'get');
  2125. }
  2126. else
  2127. {
  2128. // mandatory params for static templates
  2129. $this->EE->TMPL->tagparams['bundle'] = 'static'; // must be assigned to the static bundle
  2130. $this->EE->TMPL->tagparams['process'] = 'end';
  2131. $this->EE->TMPL->tagparams['context'] = "@URI"; // must be in the context of current URI
  2132. $this->EE->TMPL->tagparams['parse_stage'] = "set"; // static templates must be pre-parsed
  2133. $this->EE->TMPL->tagparams['refresh'] = "0"; // static templates can never expire
  2134. // as this is the full rendered output of a template, check that we should really be saving it
  2135. if ( ! $this->_is_cacheable())
  2136. {
  2137. $this->EE->TMPL->tagparams['save'] = 'no';
  2138. }
  2139. }
  2140. // set default parameter values for template files
  2141. // set a parse depth of 4
  2142. $this->EE->TMPL->tagparams['parse_depth'] = $this->EE->TMPL->fetch_param('parse_depth', 4);
  2143. // don't replace the variable by default (only read from file once)
  2144. // note: file syncing can be forced by setting stash_file_sync = TRUE in config
  2145. $this->EE->TMPL->tagparams['replace'] = $this->EE->TMPL->fetch_param('replace', 'no');
  2146. // set priority to 0 by default, so that embeds come before post-processed variables
  2147. $this->EE->TMPL->tagparams['priority'] = $this->EE->TMPL->fetch_param('priority', '0');
  2148. // initialise?
  2149. $init = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('init', 'yes'));
  2150. // re-initialise parameters, unless disabled by init parameter
  2151. if ($init)
  2152. {
  2153. $this->init();
  2154. }
  2155. else
  2156. {
  2157. $this->process = 'inline';
  2158. }
  2159. // save stash embed vars passed as parameters in the form stash:my_var which we'll
  2160. // inject later into the stash array for replacement, so remove the stash: prefix
  2161. $params = $this->EE->TMPL->tagparams;
  2162. foreach ($params as $key => $val)
  2163. {
  2164. if (strncmp($key, 'stash:', 6) == 0)
  2165. {
  2166. $this->EE->TMPL->tagparams['embed_vars'][substr($key, 6)] = $val;
  2167. }
  2168. }
  2169. // merge any cached embed vars if this embed was injected via an extend
  2170. $id = $this->EE->TMPL->fetch_param('id');
  2171. if ( $id && isset(self::$_cache['embed_vars']))
  2172. {
  2173. if (isset(self::$_cache['embed_vars'][$id]))
  2174. {
  2175. $this->EE->TMPL->tagparams['embed_vars'] = array_merge(self::$_cache['embed_vars'][$id], $this->EE->TMPL->tagparams['embed_vars']);
  2176. unset(self::$_cache['embed_vars'][$id]);
  2177. }
  2178. }
  2179. // permitted parameters for embeds
  2180. $reserved_vars = array(
  2181. 'name',
  2182. 'context',
  2183. 'scope',
  2184. 'file',
  2185. 'file_name',
  2186. 'parse_stage',
  2187. 'save',
  2188. 'refresh',
  2189. 'replace',
  2190. 'parse_tags',
  2191. 'parse_depth',
  2192. 'parse_vars',
  2193. 'parse_conditionals',
  2194. 'process',
  2195. 'priority',
  2196. 'output',
  2197. 'embed_vars',
  2198. 'bundle',
  2199. 'prefix',
  2200. 'trim',
  2201. 'strip_tags',
  2202. 'strip_curly_braces',
  2203. 'strip_unparsed',
  2204. 'compress',
  2205. 'backspace',
  2206. 'strip',
  2207. );
  2208. return $this->_run_tag('get', $reserved_vars);
  2209. }
  2210. // ----------------------------------------------------------
  2211. /**
  2212. * Cache tagdata to db and link it to the current URL context
  2213. *
  2214. * @access public
  2215. * @return string
  2216. */
  2217. public function cache()
  2218. {
  2219. /* Sample use
  2220. ---------------------------------------------------------
  2221. {exp:stash:cache}
  2222. ...
  2223. {/exp:stash:cache}
  2224. */
  2225. // Unprefix common variables in wrapped tags
  2226. if($unprefix = $this->EE->TMPL->fetch_param('unprefix'))
  2227. {
  2228. $this->EE->TMPL->tagdata = $this->_un_prefix($unprefix, $this->EE->TMPL->tagdata);
  2229. }
  2230. // process as a static cache?
  2231. if ( $this->EE->TMPL->fetch_param('process') == 'static')
  2232. {
  2233. return $this->static_cache($this->EE->TMPL->tagdata);
  2234. }
  2235. // default name for cached items is 'cache'
  2236. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->fetch_param('name', 'cache');
  2237. // cached items are saved to the template bundle by default, allow this to be overridden
  2238. $this->EE->TMPL->tagparams['bundle'] = $this->EE->TMPL->fetch_param('bundle', 'template');
  2239. // by default, parse on both set and get (i.e. so partial caching is possible)
  2240. $this->EE->TMPL->tagparams['parse_stage'] = $this->EE->TMPL->fetch_param('parse_stage', 'both');
  2241. // key_name format for cached items is @URI:context:name, where @URI is the current page URI
  2242. // thus context is always @URI, and name must be set to context:name
  2243. if ( $context = $this->EE->TMPL->fetch_param('context', FALSE))
  2244. {
  2245. $this->EE->TMPL->tagparams['name'] = $this->_parse_context($context . ':') . $this->EE->TMPL->tagparams['name'];
  2246. }
  2247. // context parameter MUST be set to the page URI pointer
  2248. $this->EE->TMPL->tagparams['context'] = '@URI';
  2249. // set a default parse depth of 4
  2250. $this->EE->TMPL->tagparams['parse_depth'] = $this->EE->TMPL->fetch_param('parse_depth', 4);
  2251. // don't replace the variable by default
  2252. $this->EE->TMPL->tagparams['replace'] = $this->EE->TMPL->fetch_param('replace', 'no');
  2253. // set a default refresh of 0 (never)
  2254. $this->EE->TMPL->tagparams['refresh'] = $this->EE->TMPL->fetch_param('refresh', 0);
  2255. // mandatory parameter values for cached items
  2256. $this->EE->TMPL->tagparams['scope'] = 'site';
  2257. $this->EE->TMPL->tagparams['save'] = 'yes';
  2258. $this->EE->TMPL->tagparams['parse_tags'] = 'yes';
  2259. $this->EE->TMPL->tagparams['parse_vars'] = 'yes';
  2260. $this->EE->TMPL->tagparams['parse_conditionals'] = 'yes';
  2261. $this->EE->TMPL->tagparams['output'] = 'yes';
  2262. $this->process = 'end';
  2263. // re-initialise Stash with the new params
  2264. $this->init();
  2265. // permitted parameters for cache
  2266. $reserved_vars = array(
  2267. 'name',
  2268. 'context',
  2269. 'scope',
  2270. 'parse_stage',
  2271. 'save',
  2272. 'refresh',
  2273. 'replace',
  2274. 'parse_tags',
  2275. 'parse_depth',
  2276. 'parse_vars',
  2277. 'parse_conditionals',
  2278. 'output',
  2279. 'bundle',
  2280. 'prefix',
  2281. 'trim',
  2282. 'strip_tags',
  2283. 'strip_curly_braces',
  2284. 'strip_unparsed',
  2285. 'compress',
  2286. 'backspace',
  2287. 'strip',
  2288. );
  2289. // cache / retreive the variables
  2290. $this->_run_tag('set', $reserved_vars);
  2291. // Is partially cached content possible? We'll need to make sure it's parsed before returning to the template
  2292. if ($this->EE->TMPL->tagparams['parse_stage'] == 'both' || $this->EE->TMPL->tagparams['parse_stage'] == 'get')
  2293. {
  2294. $this->_parse_sub_template($this->parse_tags, $this->parse_vars, $this->parse_conditionals, $this->parse_depth);
  2295. }
  2296. return $this->EE->TMPL->tagdata;
  2297. }
  2298. // ----------------------------------------------------------
  2299. /**
  2300. * Cache a template to file and link it to the current URL context
  2301. *
  2302. * @access public
  2303. * @param string $output additional tagdata to return to the template along with the placeholder
  2304. * @return string
  2305. */
  2306. public function static_cache($output='')
  2307. {
  2308. /* Sample use
  2309. ---------------------------------------------------------
  2310. {exp:stash:static} or {exp:stash:static_cache}
  2311. */
  2312. // default name for static cached items is 'static'
  2313. $this->EE->TMPL->tagparams['name'] = $this->EE->TMPL->fetch_param('name', 'static');
  2314. // format for key_name for cached items is @URI:context:name, where @URI is the current page URI
  2315. // thus context is always @URI, and name must be set to context:name
  2316. if ( $context = $this->EE->TMPL->fetch_param('context', FALSE))
  2317. {
  2318. if ($context !== '@URI')
  2319. {
  2320. $this->EE->TMPL->tagparams['name'] = $context . ':' . $this->EE->TMPL->tagparams['name'];
  2321. }
  2322. }
  2323. // parse cache key, making sure query strings are excluded from the @URI
  2324. $this->EE->TMPL->tagparams['name'] = $this->_parse_context('@URI:' . $this->EE->TMPL->tagparams['name'], TRUE);
  2325. $this->process = 'end';
  2326. $this->priority = '999999'; // should be the last thing post-processed (by Stash)
  2327. // has the tag been used as a tag pair? If so, just return to the template so it can be parsed naturally
  2328. if ($this->EE->TMPL->tagdata)
  2329. {
  2330. $output = $this->EE->TMPL->tagdata;
  2331. }
  2332. if ($out = $this->_post_parse('save_output')) return $out . $output;
  2333. }
  2334. // ----------------------------------------------------------
  2335. /**
  2336. * Save the final rendered template output to a static file
  2337. *
  2338. * @access public
  2339. * @param string $output the rendered template
  2340. * @return string
  2341. */
  2342. public function save_output($output='')
  2343. {
  2344. // mandatory parameter values for cached output
  2345. $this->EE->TMPL->tagparams['context'] = NULL;
  2346. $this->EE->TMPL->tagparams['scope'] = 'site';
  2347. $this->EE->TMPL->tagparams['save'] = 'yes';
  2348. $this->EE->TMPL->tagparams['refresh'] = "0"; // static cached items can't expire
  2349. $this->EE->TMPL->tagparams['replace'] = "no"; // static cached items cannot be replaced
  2350. $this->EE->TMPL->tagparams['bundle'] = 'static'; // cached pages in the static bundle are saved to file automatically by the model
  2351. // bundle determines the cache driver
  2352. $this->bundle_id = $this->EE->stash_model->get_bundle_by_name($this->EE->TMPL->tagparams['bundle']);
  2353. // set the entire template data as the tagdata, removing the placeholder for this tag from the output saved to file
  2354. // we need to parse remaining globals since unlike db cached pages, static pages won't pass through PHP/EE again
  2355. $this->EE->TMPL->tagdata = $this->EE->TMPL->parse_globals($output);
  2356. // parse ACTion id placeholders
  2357. $this->EE->TMPL->tagdata = $this->EE->functions->insert_action_ids($this->EE->TMPL->tagdata);
  2358. // as this is the full rendered output of a template, check that we should really be saving it
  2359. if ( ! $this->_is_cacheable())
  2360. {
  2361. $this->EE->TMPL->tagparams['save'] = 'no';
  2362. }
  2363. // permitted parameters for cached
  2364. $reserved_vars = array(
  2365. 'name',
  2366. 'context',
  2367. 'scope',
  2368. 'save',
  2369. 'refresh',
  2370. 'replace',
  2371. 'bundle',
  2372. 'trim',
  2373. 'strip_tags',
  2374. 'strip_curly_braces',
  2375. 'strip_unparsed',
  2376. 'compress',
  2377. 'backspace',
  2378. 'strip',
  2379. );
  2380. $this->_run_tag('set', $reserved_vars);
  2381. return $this->EE->TMPL->tagdata;
  2382. }
  2383. // ---------------------------------------------------------
  2384. /**
  2385. * Output the 404 template with the correct header and exit
  2386. *
  2387. * @access public
  2388. * @return string
  2389. */
  2390. public function not_found()
  2391. {
  2392. // try to prevent recursion
  2393. if ( $this->EE->output->out_type == "404")
  2394. {
  2395. return;
  2396. }
  2397. $url = FALSE;
  2398. $template = explode('/', $this->EE->config->item('site_404'));
  2399. if (isset($template[1]))
  2400. {
  2401. // build an absolute URL to the 404 template
  2402. $url = $this->EE->functions->create_url($template[0].'/'.$template[1]);
  2403. }
  2404. // We'll use cURL to grab the rendered 404 template
  2405. // The template MUST be publicly accessible without being logged in
  2406. if ($url
  2407. && $this->EE->config->item('is_system_on') !== 'n'
  2408. && is_callable('curl_init'))
  2409. {
  2410. // set header
  2411. $this->EE->config->set_item('send_headers', FALSE); // trick EE into not sending a 200
  2412. $this->EE->output->set_status_header('404');
  2413. // grab the rendered 404 page
  2414. $ch = curl_init();
  2415. // set the url
  2416. curl_setopt($ch, CURLOPT_URL, $url);
  2417. // return it direct, don't print it out
  2418. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  2419. // this connection will timeout in 10 seconds
  2420. curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  2421. $result = @curl_exec($ch);
  2422. if (curl_errno($ch))
  2423. {
  2424. log($ch);
  2425. curl_close($ch);
  2426. }
  2427. else
  2428. {
  2429. die($result);
  2430. }
  2431. }
  2432. // if cURL fails or system is off, fallback to a redirect
  2433. if ($url)
  2434. {
  2435. $this->EE->functions->redirect($url, FALSE, '404');
  2436. }
  2437. else
  2438. {
  2439. $this->EE->TMPL->log_item('Stash: 404 template is not configured. Please select a 404 template in Design > Templates > Global Preferences.');
  2440. }
  2441. }
  2442. // ---------------------------------------------------------
  2443. /**
  2444. * Check to see if a template (not a fragment) is suitable for caching
  2445. *
  2446. * @access public
  2447. * @return string
  2448. */
  2449. private function _is_cacheable()
  2450. {
  2451. // Check if we should cache this URI
  2452. if ($_SERVER['REQUEST_METHOD'] == 'POST' // … POST request
  2453. || $this->EE->input->get('ACT') // … ACT request
  2454. || $this->EE->input->get('css') // … css request
  2455. || $this->EE->input->get('URL') // … URL request
  2456. )
  2457. {
  2458. return FALSE;
  2459. }
  2460. return TRUE;
  2461. }
  2462. // ----------------------------------------------------------
  2463. /**
  2464. * Tagb for cleaning up specific placeholders before final output
  2465. *
  2466. * @access public
  2467. * @return string
  2468. */
  2469. public function finish()
  2470. {
  2471. /* Sample use
  2472. ---------------------------------------------------------
  2473. {exp:stash:finish nocache="no" compress="yes"}
  2474. */
  2475. // disable nocache for all template data parsed after this point?
  2476. self::$_nocache = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('nocache', 'y'));
  2477. $this->process = 'end';
  2478. $this->priority = '999998'; // should be the *second to last* thing post-processed (by Stash)
  2479. if ($out = $this->_post_parse('final_output')) return $out;
  2480. }
  2481. // ----------------------------------------------------------
  2482. /**
  2483. * Final parsing/cleanup of template tagdata before output
  2484. *
  2485. * @access public
  2486. * @return string
  2487. */
  2488. public function final_output($output='')
  2489. {
  2490. // is nocache disabled?
  2491. if ( ! self::$_nocache)
  2492. {
  2493. // yes - let's remove any {[prefix]:nocache} tags from the final output
  2494. $strip = $this->EE->TMPL->fetch_param('strip', FALSE);
  2495. if ($strip)
  2496. {
  2497. $strip = explode('|', $strip);
  2498. }
  2499. else
  2500. {
  2501. $strip = array();
  2502. }
  2503. foreach(self::$_nocache_prefixes as $prefix)
  2504. {
  2505. $strip[] = $prefix . $this->_nocache_suffix;
  2506. }
  2507. $this->EE->TMPL->tagparams['strip'] = implode('|', $strip);
  2508. }
  2509. // Do string transformations
  2510. $output = $this->_clean_string($output);
  2511. // set as template tagdata
  2512. $this->EE->TMPL->tagdata = $output;
  2513. // remove the placeholder from the output
  2514. return $this->EE->TMPL->tagdata;
  2515. }
  2516. // ---------------------------------------------------------
  2517. /**
  2518. * Parse tagdata
  2519. *
  2520. * @param array $params an array of key => value pairs representing tag parameters
  2521. * @param string $value string to parse, defaults to template tagdata
  2522. * @access public
  2523. * @return string
  2524. */
  2525. public function parse($params = array(), $value=NULL)
  2526. {
  2527. // is this method being called directly?
  2528. if ( func_num_args() > 0)
  2529. {
  2530. if ( !(isset($this) && get_class($this) == __CLASS__))
  2531. {
  2532. return self::_api_static_call(__FUNCTION__, $params, '', '', $value);
  2533. }
  2534. else
  2535. {
  2536. return $this->_api_call(__FUNCTION__, $params, '', '', $value);
  2537. }
  2538. }
  2539. // parse="yes"?
  2540. $this->set_parse_params();
  2541. // default parameter values
  2542. $this->EE->TMPL->tagparams['parse_tags'] = $this->EE->TMPL->fetch_param('parse_tags', 'yes');
  2543. $this->EE->TMPL->tagparams['parse_vars'] = $this->EE->TMPL->fetch_param('parse_vars', 'yes');
  2544. $this->EE->TMPL->tagparams['parse_conditionals'] = $this->EE->TMPL->fetch_param('parse_conditionals', 'yes');
  2545. $this->EE->TMPL->tagparams['parse_depth'] = $this->EE->TMPL->fetch_param('parse_depth', 3);
  2546. // postpone tag processing?
  2547. if ( $this->process !== 'inline')
  2548. {
  2549. if ($out = $this->_post_parse(__FUNCTION__)) return $out;
  2550. }
  2551. // re-initialise Stash with the new default params
  2552. $this->init();
  2553. // Unprefix common variables in wrapped tags
  2554. if($unprefix = $this->EE->TMPL->fetch_param('unprefix'))
  2555. {
  2556. $this->EE->TMPL->tagdata = $this->_un_prefix($unprefix, $this->EE->TMPL->tagdata);
  2557. }
  2558. // do the business
  2559. $this->_parse_sub_template($this->parse_tags, $this->parse_vars, $this->parse_conditionals, $this->parse_depth);
  2560. // output the parsed template data?
  2561. $output = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('output', 'yes'));
  2562. if ($output)
  2563. {
  2564. return $this->EE->TMPL->tagdata;
  2565. }
  2566. }
  2567. // ---------------------------------------------------------
  2568. /**
  2569. * Unset variable(s) in the current session, optionally flush from db
  2570. *
  2571. * @access public
  2572. * @param mixed $params The name of the variable to unset, or an array of key => value pairs
  2573. * @param string $type The type of variable
  2574. * @param string $scope The scope of the variable
  2575. * @return void
  2576. */
  2577. public function destroy($params=array(), $type='variable', $scope='user')
  2578. {
  2579. // is this method being called directly?
  2580. if ( func_num_args() > 0)
  2581. {
  2582. if ( !(isset($this) && get_class($this) == __CLASS__))
  2583. {
  2584. return self::_api_static_call(__FUNCTION__, $params, $type, $scope);
  2585. }
  2586. else
  2587. {
  2588. return $this->_api_call(__FUNCTION__, $params, $type, $scope);
  2589. }
  2590. }
  2591. // register params
  2592. $name = $this->EE->TMPL->fetch_param('name', FALSE);
  2593. $context = $this->EE->TMPL->fetch_param('context', NULL);
  2594. $scope = strtolower($this->EE->TMPL->fetch_param('scope', $this->default_scope));
  2595. $flush_cache = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('flush_cache', 'yes'));
  2596. $bundle = $this->EE->TMPL->fetch_param('bundle', NULL);
  2597. $bundle_id = $this->EE->TMPL->fetch_param('bundle_id', FALSE);
  2598. // narrow the scope to user session?
  2599. $session_id = NULL;
  2600. if ($scope === 'user')
  2601. {
  2602. $session_id = $this->_session_id;
  2603. }
  2604. elseif($scope === 'site')
  2605. {
  2606. $session_id = '_global';
  2607. }
  2608. // named bundle?
  2609. if ( ! is_null($bundle) && ! $bundle_id)
  2610. {
  2611. $bundle_id = $this->EE->stash_model->get_bundle_by_name($bundle);
  2612. }
  2613. // unset a single variable, or multiple variables that match a regex
  2614. if ($name)
  2615. {
  2616. if (preg_match('/^#(.*)#$/', $name))
  2617. {
  2618. // remove matching variable keys from the session
  2619. foreach($this->_stash as $var_key => $var_val)
  2620. {
  2621. if (preg_match($name, $var_key))
  2622. {
  2623. unset($this->_stash[$var_key]);
  2624. }
  2625. }
  2626. // remove from db cache?
  2627. if ($flush_cache && $scope !== 'local')
  2628. {
  2629. // delete variables with key_names that match the regex
  2630. $this->EE->stash_model->delete_matching_keys(
  2631. $bundle_id,
  2632. $session_id,
  2633. $this->site_id,
  2634. trim($name, '#'),
  2635. $this->invalidation_period
  2636. );
  2637. }
  2638. }
  2639. else
  2640. {
  2641. // a named variable
  2642. if ($context !== NULL && count( explode(':', $name) == 1 ))
  2643. {
  2644. $name = $context . ':' . $name;
  2645. }
  2646. // remove from session
  2647. if ( isset($this->_stash[$name]))
  2648. {
  2649. unset($this->_stash[$name]);
  2650. }
  2651. // remove from db cache?
  2652. if ($flush_cache && $scope !== 'local')
  2653. {
  2654. // replace '@' placeholders with the current context
  2655. $stash_key = $this->_parse_context($name);
  2656. // as we're deleting a specific key, the bundle_id is required
  2657. $bundle_id = $bundle_id ? $bundle_id : $this->bundle_id;
  2658. $this->EE->stash_model->delete_key(
  2659. $stash_key,
  2660. $bundle_id,
  2661. $session_id,
  2662. $this->site_id,
  2663. $this->invalidation_period
  2664. );
  2665. }
  2666. }
  2667. }
  2668. elseif($scope === 'user' || $scope === 'site' || $scope === 'all')
  2669. {
  2670. // unset ALL user-scoped variables in the current process
  2671. $this->_stash = array();
  2672. // remove from cache
  2673. if ($flush_cache)
  2674. {
  2675. $this->EE->stash_model->delete_matching_keys(
  2676. $bundle_id,
  2677. $session_id,
  2678. $this->site_id,
  2679. NULL,
  2680. $this->invalidation_period
  2681. );
  2682. }
  2683. }
  2684. }
  2685. // ---------------------------------------------------------
  2686. /**
  2687. * Flush the variables database cache for the current site (Super Admins only)
  2688. *
  2689. * @access public
  2690. * @return string
  2691. */
  2692. public function flush_cache()
  2693. {
  2694. if ($this->EE->session->userdata['group_title'] == "Super Admins")
  2695. {
  2696. if ( $this->EE->stash_model->flush_cache($this->site_id))
  2697. {
  2698. return $this->EE->lang->line('cache_flush_success');
  2699. }
  2700. }
  2701. else
  2702. {
  2703. // not authorised
  2704. $this->EE->output->show_user_error('general', $this->EE->lang->line('not_authorized'));
  2705. }
  2706. }
  2707. /*
  2708. ================================================================
  2709. Utility methods
  2710. ================================================================
  2711. */
  2712. /**
  2713. * Match a regex against a string or array of strings
  2714. *
  2715. * @access private
  2716. * @param string $match A regular expression
  2717. * @param string/array $against array of strings to match regex against
  2718. * @return bool
  2719. */
  2720. private function _matches($match, $against)
  2721. {
  2722. $is_match = TRUE;
  2723. $match = $this->EE->security->entity_decode($match);
  2724. if ( ! is_array($against))
  2725. {
  2726. $against = array($against);
  2727. }
  2728. else
  2729. {
  2730. // remove null values
  2731. $against = array_filter($against, 'strlen');
  2732. }
  2733. // check every value in the array matches
  2734. foreach($against as $part)
  2735. {
  2736. // convert placeholder null to an empty string before comparing
  2737. if ($part === $this->_list_null)
  2738. {
  2739. $part = '';
  2740. }
  2741. $this->EE->TMPL->log_item('Stash: MATCH '. $match . ' AGAINST ' . $part);
  2742. if ( ! preg_match($match, $part))
  2743. {
  2744. $is_match = FALSE;
  2745. break;
  2746. }
  2747. }
  2748. return $is_match;
  2749. }
  2750. // ---------------------------------------------------------
  2751. /**
  2752. * Retrieve and rebuild list, or optionally part of a list
  2753. *
  2754. * @access public
  2755. * @param mixed $params The name of the variable to retrieve, or an array of key => value pairs
  2756. * @param string $type The type of variable
  2757. * @param string $scope The scope of the variable
  2758. * @return array
  2759. */
  2760. public function rebuild_list($params='', $type='variable', $scope='user')
  2761. {
  2762. // is this method being called directly?
  2763. if ( func_num_args() > 0)
  2764. {
  2765. if ( !(isset($this) && get_class($this) == __CLASS__))
  2766. {
  2767. return self::_api_static_call(__FUNCTION__, $params, $type, $scope);
  2768. }
  2769. else
  2770. {
  2771. return $this->_api_call(__FUNCTION__, $params, $type, $scope);
  2772. }
  2773. }
  2774. $sort = strtolower($this->EE->TMPL->fetch_param('sort', FALSE));
  2775. $sort_type = strtolower($this->EE->TMPL->fetch_param('sort_type', FALSE)); // string || integer || lowercase
  2776. $orderby = $this->EE->TMPL->fetch_param('orderby', FALSE);
  2777. $match = $this->EE->TMPL->fetch_param('match', NULL); // regular expression to each list item against
  2778. $against = $this->EE->TMPL->fetch_param('against', NULL); // array key to test $match against
  2779. $unique = $this->EE->TMPL->fetch_param('unique', NULL);
  2780. $slice = $this->EE->TMPL->fetch_param('slice', NULL); // e.g. "0, 2" - slice the list array before order/sort/limit
  2781. $in = $this->EE->TMPL->fetch_param('in', FALSE); // compare column against a tracked value, e.g. list_column:tracked_column, and include if it matches
  2782. $not_in = $this->EE->TMPL->fetch_param('not_in', FALSE); // compare column against a tracked value, and exclude if it matches
  2783. // make sure any parsing is done AFTER the list has been replaced in to the template
  2784. // not when it's still a serialized array
  2785. $this->parse_complete = TRUE;
  2786. // run get() with a safe list of parameters
  2787. $list = $this->_run_tag('get', array('name', 'type', 'scope', 'context'));
  2788. // reenable parsing
  2789. $this->parse_complete = FALSE;
  2790. if ($list !== '' && $list !== NULL)
  2791. {
  2792. // explode the list
  2793. $list = explode( $this->_list_delimiter, $list);
  2794. foreach($list as $key => &$value)
  2795. {
  2796. $value = $this->_list_row_explode($value);
  2797. }
  2798. unset($value);
  2799. // apply order/sort
  2800. if ($orderby)
  2801. {
  2802. if ($orderby == 'random')
  2803. {
  2804. // shuffle list order
  2805. shuffle($list);
  2806. }
  2807. elseif (strncmp($orderby, 'random:', 7) == 0)
  2808. {
  2809. // shuffle one or more keys in the list, but leave the overall list order unchanged
  2810. $keys_to_shuffle = explode(',', substr($orderby, 7));
  2811. foreach($keys_to_shuffle as $key_shuffle)
  2812. {
  2813. $list = $this->shuffle_list_key($list, $key_shuffle);
  2814. }
  2815. }
  2816. else
  2817. {
  2818. // here be dragons (array_multisort)
  2819. $orderby = explode('|', preg_replace('#\s+#', '', $orderby));
  2820. $sort = explode('|', preg_replace('#\s+#', '', $sort));
  2821. $sort_type = explode('|', preg_replace('#\s+#', '', $sort_type));
  2822. // make columns out of rows needed for orderby
  2823. $columns = array();
  2824. foreach ($list as $key => $row)
  2825. {
  2826. foreach ($orderby as $name)
  2827. {
  2828. if ( isset($list[$key][$name]) )
  2829. {
  2830. $columns[$name][$key] =& $list[$key][$name];
  2831. }
  2832. else
  2833. {
  2834. $columns[$name][$key] = null;
  2835. }
  2836. }
  2837. }
  2838. // create function arguments for multisort
  2839. $args = array();
  2840. foreach ($orderby as $i => $name)
  2841. {
  2842. $args[] =& $columns[$name]; // column reference
  2843. // SORT_ASC is default, only change if desc
  2844. if (isset($sort[$i]) && $sort[$i]=="desc")
  2845. {
  2846. $args[] = SORT_DESC;
  2847. }
  2848. // types string, integer, lowercase
  2849. if (isset($sort_type[$i]))
  2850. {
  2851. switch ($sort_type[$i])
  2852. {
  2853. case 'string':
  2854. $args[] = SORT_STRING;
  2855. break;
  2856. case 'integer': case 'numeric':
  2857. $args[] = SORT_NUMERIC;
  2858. break;
  2859. case 'lowercase':
  2860. $columns[$name] = array_map('strtolower', $columns[$name]);
  2861. $args[] = SORT_STRING;
  2862. break;
  2863. case 'normalize':
  2864. $columns[$name] = array_map(array($this, '_normalize'), $columns[$name]);
  2865. $args[] = SORT_STRING;
  2866. break;
  2867. default:
  2868. // $args[] = SORT_REGULAR;
  2869. break;
  2870. }
  2871. }
  2872. }
  2873. // last argument, array to sort
  2874. $args[] =& $list;
  2875. // sorted
  2876. call_user_func_array('array_multisort', $args);
  2877. unset($columns);
  2878. }
  2879. }
  2880. // apply sort direction
  2881. if ( ! is_array($sort) && $sort == 'desc')
  2882. {
  2883. $list = array_values(array_reverse($list));
  2884. }
  2885. // slice before any filtering is applied
  2886. // note: offset/limit can be used to 'slice' after filters are applied
  2887. if ( ! is_null($slice))
  2888. {
  2889. $slice = array_map('intval', explode(',', $slice));
  2890. if (isset($slice[1]))
  2891. {
  2892. $list = array_slice($list, $slice[0], $slice[1]);
  2893. }
  2894. else
  2895. {
  2896. $list = array_slice($list, $slice[0]);
  2897. }
  2898. }
  2899. // compare column values against a statically tracked value, and *exclude* the row if the value matches
  2900. if ($not_in)
  2901. {
  2902. $col_local = $col_tracked = $not_in;
  2903. if (strstr($not_in, ':'))
  2904. {
  2905. $not_in = explode(':', $not_in);
  2906. $col_local = $not_in[0];
  2907. if (isset($not_in[1]))
  2908. {
  2909. $col_tracked = $not_in[1];
  2910. }
  2911. }
  2912. if (isset(self::$_cache['track'][$col_tracked]))
  2913. {
  2914. foreach($list as $key => $value)
  2915. {
  2916. if ( isset($value[$col_local]) && in_array($value[$col_local], self::$_cache['track'][$col_tracked]) )
  2917. {
  2918. unset($list[$key]);
  2919. }
  2920. }
  2921. }
  2922. }
  2923. // compare column values against a statically tracked value, and *include* the row only if the value matches
  2924. if ($in)
  2925. {
  2926. $new_list = array();
  2927. $col_local = $col_tracked = $not_in;
  2928. if (strstr($in, ':'))
  2929. {
  2930. $in = explode(':', $in);
  2931. $col_local = $in[0];
  2932. if (isset($in[1]))
  2933. {
  2934. $col_tracked = $in[1];
  2935. }
  2936. }
  2937. if (isset(self::$_cache['track'][$col_tracked]))
  2938. {
  2939. foreach($list as $key => $value)
  2940. {
  2941. if ( isset($value[$col_local]) && in_array($value[$col_local], self::$_cache['track'][$col_tracked]) )
  2942. {
  2943. $new_list[] = $value;
  2944. }
  2945. }
  2946. }
  2947. $list = $new_list;
  2948. }
  2949. // match/against: match the value of one of the list keys (specified by the against param) against a regex
  2950. if ( ! is_null($match) && preg_match('/^#(.*)#$/', $match) && ! is_null($against))
  2951. {
  2952. $new_list = array();
  2953. foreach($list as $key => $value)
  2954. {
  2955. if ( isset($value[$against]) )
  2956. {
  2957. if ($this->_matches($match, $value[$against]))
  2958. {
  2959. // match found
  2960. $new_list[] = $value;
  2961. }
  2962. }
  2963. }
  2964. $list = $new_list;
  2965. }
  2966. // re-index array
  2967. $list = array_values($list);
  2968. // ensure we have unique rows?
  2969. if ($unique !== NULL)
  2970. {
  2971. if ( FALSE === (bool) preg_match('/^(0|off|no|n)$/i', $unique))
  2972. {
  2973. if ( FALSE === (bool) preg_match('/^(1|on|yes|y)$/i', $unique))
  2974. {
  2975. // unique across a single column
  2976. $unique_list = array();
  2977. $index = 0;
  2978. foreach($list as $key => $value)
  2979. {
  2980. if ( isset($value[$unique]) )
  2981. {
  2982. $unique_list[$index] = array(
  2983. $unique => $value[$unique]
  2984. );
  2985. }
  2986. ++$index;
  2987. }
  2988. // make a unique list for the column
  2989. $unique_list = array_map('unserialize', array_unique(array_map('serialize', $unique_list)));
  2990. // restore original list values for the unique rows
  2991. $restored_list = array();
  2992. foreach($unique_list as $key => $value)
  2993. {
  2994. $restored_list[] = $list[$key];
  2995. }
  2996. $list = $restored_list;
  2997. }
  2998. else
  2999. {
  3000. // make a unique list
  3001. $list = array_map('unserialize', array_unique(array_map('serialize', $list)));
  3002. }
  3003. }
  3004. }
  3005. }
  3006. else
  3007. {
  3008. $list = array(); // make sure we always return an array
  3009. }
  3010. return $list;
  3011. }
  3012. // ---------------------------------------------------------
  3013. /**
  3014. * Retrieve {stash:var}{/stash:var} tag pairs and serialize
  3015. *
  3016. * @access private
  3017. * @return void
  3018. */
  3019. private function _serialize_stash_tag_pairs()
  3020. {
  3021. $match = $this->EE->TMPL->fetch_param('match', NULL); // regular expression to each list item against
  3022. $against = $this->EE->TMPL->fetch_param('against', NULL); // array key to test $match against
  3023. // get the stash var pairs values
  3024. $stash_vars = array();
  3025. foreach($this->EE->TMPL->var_pair as $key => $val)
  3026. {
  3027. // valid variable pair?
  3028. if (strncmp($key, 'stash:', 6) == 0)
  3029. {
  3030. // but does the pair exist for this row of the list?
  3031. $starts_at = strpos($this->EE->TMPL->tagdata, LD.$key.RD) + strlen(LD.$key.RD);
  3032. $ends_at = strpos($this->EE->TMPL->tagdata, LD."/".$key.RD, $starts_at);
  3033. if (FALSE !== $starts_at && FALSE !== $ends_at)
  3034. {
  3035. // extract value between the pair
  3036. $tag_value = substr($this->EE->TMPL->tagdata, $starts_at, $ends_at - $starts_at);
  3037. // don't save a string containing just white space, but be careful to preserve zeros
  3038. if ( $this->not_empty($tag_value) || $tag_value === '0')
  3039. {
  3040. $stash_vars[substr($key, 6)] = $tag_value;
  3041. }
  3042. else
  3043. {
  3044. // default key value: use a placeholder to represent a null/empty value
  3045. $stash_vars[substr($key, 6)] = $this->_list_null;
  3046. }
  3047. }
  3048. else
  3049. {
  3050. // no tag pair found in this row - use a placeholder to represent a null/empty value
  3051. $stash_vars[substr($key, 6)] = $this->_list_null;
  3052. }
  3053. }
  3054. }
  3055. // match/against: optionally match against the value of one of the list keys, rather than the whole serialized variable
  3056. if ( ! is_null($match)
  3057. && preg_match('/^#(.*)#$/', $match)
  3058. && ! is_null($against)
  3059. && isset($stash_vars[$against])
  3060. )
  3061. {
  3062. if ( ! $this->_matches($match, $stash_vars[$against]))
  3063. {
  3064. // match not found, end here
  3065. $this->EE->TMPL->tagdata = '';
  3066. return;
  3067. }
  3068. // disable match/against when setting the variable
  3069. #unset($this->EE->TMPL->tagparams['match']);
  3070. #unset($this->EE->TMPL->tagparams['against']);
  3071. }
  3072. // flatten the array into a string
  3073. $this->EE->TMPL->tagdata = $this->_list_row_implode($stash_vars);
  3074. }
  3075. // ---------------------------------------------------------
  3076. /**
  3077. * @param array $array The array to implode
  3078. * @return string The imploded array
  3079. */
  3080. private function _list_row_implode($array)
  3081. {
  3082. if ( ! is_array( $array ) ) return $array;
  3083. $string = array();
  3084. foreach ( $array as $key => $val )
  3085. {
  3086. if ( is_array( $val ) )
  3087. {
  3088. $val = implode( ',', $val );
  3089. }
  3090. $string[] = "{$key}{$this->_list_row_glue}{$val}";
  3091. }
  3092. return implode( $this->_list_row_delimiter, $string );
  3093. }
  3094. // ---------------------------------------------------------
  3095. /**
  3096. * @param string $string The string to explode
  3097. * @return array The exploded array
  3098. */
  3099. private function _list_row_explode($string)
  3100. {
  3101. $array = explode($this->_list_row_delimiter, $string);
  3102. $new_array = array();
  3103. foreach ( $array as $key => $val )
  3104. {
  3105. $val = explode($this->_list_row_glue, $val);
  3106. if (isset($val[1]))
  3107. {
  3108. // replace our null placeholder with an empty string
  3109. if ($val[1] === $this->_list_null)
  3110. {
  3111. $val[1] = '';
  3112. }
  3113. $new_array[$val[0]] = $val[1];
  3114. }
  3115. }
  3116. return $new_array;
  3117. }
  3118. // ---------------------------------------------------------
  3119. /**
  3120. * Flattens an array into a quasi-serialized format suitable for saving as a stash variable
  3121. *
  3122. * @param array $list The list array to flatten
  3123. * @return string The imploded string
  3124. */
  3125. static public function flatten_list($array)
  3126. {
  3127. $self = new self();
  3128. $new_list = array();
  3129. foreach($array as $value)
  3130. {
  3131. $new_list[] = $self->_list_row_implode($value);
  3132. }
  3133. return implode($self->_list_delimiter, $new_list);
  3134. }
  3135. // ---------------------------------------------------------
  3136. /**
  3137. * Shuffle a stash list key
  3138. *
  3139. * @access public
  3140. * @param array Multidimensional array to sort
  3141. * @param string Array key to sort on
  3142. * @param string Callback function
  3143. * @return void
  3144. */
  3145. public function shuffle_list_key($arr, $key)
  3146. {
  3147. $key_list = array();
  3148. // shuffle the key array
  3149. foreach($arr as $row)
  3150. {
  3151. if (isset($row[$key]))
  3152. {
  3153. $key_list[] = $row[$key];
  3154. }
  3155. }
  3156. shuffle($key_list);
  3157. // rebuld the list with the shuffled key
  3158. $i = 0;
  3159. foreach($arr as &$row)
  3160. {
  3161. if (isset($key_list[$i]))
  3162. {
  3163. $row[$key] = $key_list[$i];
  3164. }
  3165. ++$i;
  3166. }
  3167. unset($row);
  3168. return $arr;
  3169. }
  3170. // ---------------------------------------------------------
  3171. /**
  3172. * Normalize characters in a string (the dirty way)
  3173. *
  3174. * @access private
  3175. * @param string
  3176. * @return string
  3177. */
  3178. private function _normalize($str)
  3179. {
  3180. /* Character map courtesy of https://github.com/jbroadway/urlify */
  3181. $char_map = array(
  3182. /* German */
  3183. 'Ä' => 'Ae', 'Ö' => 'Oe', 'Ü' => 'Ue', 'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',
  3184. 'ẞ' => 'SS',
  3185. /* latin */
  3186. 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Ă' => 'A', 'Æ' => 'AE',
  3187. 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I',
  3188. 'Ï' => 'I', 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' =>
  3189. 'O', 'Ő' => 'O', 'Ø' => 'O','Ș' => 'S','Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U',
  3190. 'Ý' => 'Y', 'Þ' => 'TH', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' =>
  3191. 'a', 'å' => 'a', 'ă' => 'a', 'æ' => 'ae', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
  3192. 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' =>
  3193. 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', 'ø' => 'o', 'ș' => 's', 'ț' => 't', 'ù' => 'u', 'ú' => 'u',
  3194. 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', 'ÿ' => 'y',
  3195. /* latin_symbols */
  3196. '©' => '(c)',
  3197. /* Greek */
  3198. 'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8',
  3199. 'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p',
  3200. 'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w',
  3201. 'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's',
  3202. 'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i',
  3203. 'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8',
  3204. 'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P',
  3205. 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W',
  3206. 'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I',
  3207. 'Ϋ' => 'Y',
  3208. /* Turkish */
  3209. 'ş' => 's', 'Ş' => 'S', 'ı' => 'i', 'İ' => 'I', 'ç' => 'c', 'Ç' => 'C', 'ü' => 'u', 'Ü' => 'U',
  3210. 'ö' => 'o', 'Ö' => 'O', 'ğ' => 'g', 'Ğ' => 'G',
  3211. /* Russian */
  3212. 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh',
  3213. 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o',
  3214. 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c',
  3215. 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu',
  3216. 'я' => 'ya',
  3217. 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh',
  3218. 'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O',
  3219. 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C',
  3220. 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu',
  3221. 'Я' => 'Ya',
  3222. '№' => '',
  3223. /* Ukrainian */
  3224. 'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G', 'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g',
  3225. /* Czech */
  3226. 'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u',
  3227. 'ž' => 'z', 'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T',
  3228. 'Ů' => 'U', 'Ž' => 'Z',
  3229. /* Polish */
  3230. 'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z',
  3231. 'ż' => 'z', 'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'O', 'Ś' => 'S',
  3232. 'Ź' => 'Z', 'Ż' => 'Z',
  3233. /* Romanian */
  3234. 'ă' => 'a', 'â' => 'a', 'î' => 'i', 'ș' => 's', 'ț' => 't', 'Ţ' => 'T', 'ţ' => 't',
  3235. /* Latvian */
  3236. 'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n',
  3237. 'š' => 's', 'ū' => 'u', 'ž' => 'z', 'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i',
  3238. 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N', 'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z',
  3239. /* Lithuanian */
  3240. 'ą' => 'a', 'č' => 'c', 'ę' => 'e', 'ė' => 'e', 'į' => 'i', 'š' => 's', 'ų' => 'u', 'ū' => 'u', 'ž' => 'z',
  3241. 'Ą' => 'A', 'Č' => 'C', 'Ę' => 'E', 'Ė' => 'E', 'Į' => 'I', 'Š' => 'S', 'Ų' => 'U', 'Ū' => 'U', 'Ž' => 'Z',
  3242. /* Vietnamese */
  3243. 'Á' => 'A', 'À' => 'A', 'Ả' => 'A', 'Ã' => 'A', 'Ạ' => 'A', 'Ă' => 'A', 'Ắ' => 'A', 'Ằ' => 'A', 'Ẳ' => 'A', 'Ẵ' => 'A', 'Ặ' => 'A', 'Â' => 'A', 'Ấ' => 'A', 'Ầ' => 'A', 'Ẩ' => 'A', 'Ẫ' => 'A', 'Ậ' => 'A',
  3244. 'á' => 'a', 'à' => 'a', 'ả' => 'a', 'ã' => 'a', 'ạ' => 'a', 'ă' => 'a', 'ắ' => 'a', 'ằ' => 'a', 'ẳ' => 'a', 'ẵ' => 'a', 'ặ' => 'a', 'â' => 'a', 'ấ' => 'a', 'ầ' => 'a', 'ẩ' => 'a', 'ẫ' => 'a', 'ậ' => 'a',
  3245. 'É' => 'E', 'È' => 'E', 'Ẻ' => 'E', 'Ẽ' => 'E', 'Ẹ' => 'E', 'Ê' => 'E', 'Ế' => 'E', 'Ề' => 'E', 'Ể' => 'E', 'Ễ' => 'E', 'Ệ' => 'E',
  3246. 'é' => 'e', 'è' => 'e', 'ẻ' => 'e', 'ẽ' => 'e', 'ẹ' => 'e', 'ê' => 'e', 'ế' => 'e', 'ề' => 'e', 'ể' => 'e', 'ễ' => 'e', 'ệ' => 'e',
  3247. 'Í' => 'I', 'Ì' => 'I', 'Ỉ' => 'I', 'Ĩ' => 'I', 'Ị' => 'I', 'í' => 'i', 'ì' => 'i', 'ỉ' => 'i', 'ĩ' => 'i', 'ị' => 'i',
  3248. 'Ó' => 'O', 'Ò' => 'O', 'Ỏ' => 'O', 'Õ' => 'O', 'Ọ' => 'O', 'Ô' => 'O', 'Ố' => 'O', 'Ồ' => 'O', 'Ổ' => 'O', 'Ỗ' => 'O', 'Ộ' => 'O', 'Ơ' => 'O', 'Ớ' => 'O', 'Ờ' => 'O', 'Ở' => 'O', 'Ỡ' => 'O', 'Ợ' => 'O',
  3249. 'ó' => 'o', 'ò' => 'o', 'ỏ' => 'o', 'õ' => 'o', 'ọ' => 'o', 'ô' => 'o', 'ố' => 'o', 'ồ' => 'o', 'ổ' => 'o', 'ỗ' => 'o', 'ộ' => 'o', 'ơ' => 'o', 'ớ' => 'o', 'ờ' => 'o', 'ở' => 'o', 'ỡ' => 'o', 'ợ' => 'o',
  3250. 'Ú' => 'U', 'Ù' => 'U', 'Ủ' => 'U', 'Ũ' => 'U', 'Ụ' => 'U', 'Ư' => 'U', 'Ứ' => 'U', 'Ừ' => 'U', 'Ử' => 'U', 'Ữ' => 'U', 'Ự' => 'U',
  3251. 'ú' => 'u', 'ù' => 'u', 'ủ' => 'u', 'ũ' => 'u', 'ụ' => 'u', 'ư' => 'u', 'ứ' => 'u', 'ừ' => 'u', 'ử' => 'u', 'ữ' => 'u', 'ự' => 'u',
  3252. 'Ý' => 'Y', 'Ỳ' => 'Y', 'Ỷ' => 'Y', 'Ỹ' => 'Y', 'Ỵ' => 'Y', 'ý' => 'y', 'ỳ' => 'y', 'ỷ' => 'y', 'ỹ' => 'y', 'ỵ' => 'y',
  3253. 'Đ' => 'D', 'đ' => 'd',
  3254. /* Arabic */
  3255. 'أ' => 'a', 'ب' => 'b', 'ت' => 't', 'ث' => 'th', 'ج' => 'g', 'ح' => 'h', 'خ' => 'kh', 'د' => 'd',
  3256. 'ذ' => 'th', 'ر' => 'r', 'ز' => 'z', 'س' => 's', 'ش' => 'sh', 'ص' => 's', 'ض' => 'd', 'ط' => 't',
  3257. 'ظ' => 'th', 'ع' => 'aa', 'غ' => 'gh', 'ف' => 'f', 'ق' => 'k', 'ك' => 'k', 'ل' => 'l', 'م' => 'm',
  3258. 'ن' => 'n', 'ه' => 'h', 'و' => 'o', 'ي' => 'y',
  3259. /* Serbian */
  3260. 'ђ' => 'dj', 'ј' => 'j', 'љ' => 'lj', 'њ' => 'nj', 'ћ' => 'c', 'џ' => 'dz', 'đ' => 'dj',
  3261. 'Ђ' => 'Dj', 'Ј' => 'j', 'Љ' => 'Lj', 'Њ' => 'Nj', 'Ћ' => 'C', 'Џ' => 'Dz', 'Đ' => 'Dj',
  3262. /* Azerbaijani */
  3263. 'ç' => 'c', 'ə' => 'e', 'ğ' => 'g', 'ı' => 'i', 'ö' => 'o', 'ş' => 's', 'ü' => 'u',
  3264. 'Ç' => 'C', 'Ə' => 'E', 'Ğ' => 'G', 'İ' => 'I', 'Ö' => 'O', 'Ş' => 'S', 'Ü' => 'U',
  3265. );
  3266. return strtr($str, $char_map);
  3267. }
  3268. // ---------------------------------------------------------
  3269. /**
  3270. * Sort a multi-dimensional array by key
  3271. *
  3272. * @access public
  3273. * @param array Multidimensional array to sort
  3274. * @param string Array key to sort on
  3275. * @param string Callback function
  3276. * @return void
  3277. */
  3278. public function sort_by_key($arr, $key, $cmp='sort_by_integer')
  3279. {
  3280. $this->_key2sort = $key;
  3281. uasort($arr, array(__CLASS__, $cmp));
  3282. return ($arr);
  3283. }
  3284. // ---------------------------------------------------------
  3285. /**
  3286. * Sort callback function: sort by string
  3287. *
  3288. * @access protected
  3289. * @param array
  3290. * @param array
  3291. */
  3292. protected function sort_by_string($a, $b)
  3293. {
  3294. return (@strcasecmp($a[$this->_key2sort], $b[$this->_key2sort]));
  3295. }
  3296. // ---------------------------------------------------------
  3297. /**
  3298. * Sort callback function: sort by integer
  3299. *
  3300. * @access protected
  3301. * @param array
  3302. * @param array
  3303. */
  3304. protected function sort_by_integer($a, $b)
  3305. {
  3306. if ($a[$this->_key2sort] == $b[$this->_key2sort])
  3307. {
  3308. return 0;
  3309. }
  3310. return ($a[$this->_key2sort] < $b[$this->_key2sort]) ? -1 : 1;
  3311. }
  3312. // ---------------------------------------------------------
  3313. /**
  3314. * get a fraction from a parameter value
  3315. *
  3316. * @access private
  3317. * @param string
  3318. * @param string
  3319. * @param integer
  3320. * @return integer
  3321. */
  3322. private function _parse_fraction($fraction, $offset=0, $total)
  3323. {
  3324. if (strstr($fraction, '/'))
  3325. {
  3326. $fraction = explode('/', $fraction);
  3327. if (isset($fraction[1]) && $fraction[1] > 0)
  3328. {
  3329. $p = $fraction[1]; // the default number of partitions
  3330. $start = 0; // index of the first partition
  3331. $end = $fraction[0]; // index of the last partition
  3332. // do we have an offset?
  3333. if ($offset)
  3334. {
  3335. if (strstr($offset, '/'))
  3336. {
  3337. // we were passed a fraction
  3338. $offset = explode('/', $offset);
  3339. }
  3340. elseif( intval($offset) === $offset)
  3341. {
  3342. // we were passed an integer, convert to a fraction of the total
  3343. $offset = array($offset, $total);
  3344. }
  3345. if (isset($offset[1]) && $offset[1] > 0)
  3346. {
  3347. // do the denominators match?
  3348. if ($offset[1] !== $fraction[1])
  3349. {
  3350. // no, find the least common denominator for those numbers
  3351. $p = $this->_lcd(array($offset[1], $fraction[1]));
  3352. // multiply the numerators accordingly
  3353. $offset[0] = $p / $offset[1] * $offset[0];
  3354. $fraction[0] = $p / $fraction[1] * $fraction[0];
  3355. }
  3356. // update indexes of start/end partitions
  3357. $start = $offset[0];
  3358. $end = $start + $fraction[0];
  3359. }
  3360. else
  3361. {
  3362. $offset = 0;
  3363. }
  3364. }
  3365. // partition a temporary list
  3366. $partlen = floor($total / $p);
  3367. $partrem = $total % $p;
  3368. $partition = array();
  3369. $mark = 0;
  3370. $list = array_fill(0, $total, 0);
  3371. for($px = 0; $px < $p; $px++)
  3372. {
  3373. $incr = ($px < $partrem) ? $partlen + 1 : $partlen;
  3374. $partition[$px] = array_slice($list, $mark, $incr);
  3375. $mark += $incr;
  3376. }
  3377. unset($list);
  3378. $i = $start;
  3379. $index = 0;
  3380. while ($i < $end) {
  3381. if (isset($partition[$i]))
  3382. {
  3383. $index += count($partition[$i]);
  3384. }
  3385. else break;
  3386. $i++;
  3387. }
  3388. return $index;
  3389. }
  3390. }
  3391. else
  3392. {
  3393. return (int) $fraction;
  3394. }
  3395. }
  3396. // ---------------------------------------------------------
  3397. /**
  3398. * get the least common denominator for a given array of numbers
  3399. *
  3400. * @access private
  3401. * @param array numbers to compare
  3402. * @param integer the multiplication count
  3403. * @return integer
  3404. */
  3405. private function _lcd($array, $x=1)
  3406. {
  3407. $mod_sum = 0;
  3408. static $lcd = 0;
  3409. for($int=1; $int < count($array); $int++)
  3410. {
  3411. $modulus[$int] = ($array[0]*$x) % ($array[$int]);
  3412. $mod_sum = $mod_sum + $modulus[$int];
  3413. }
  3414. if (!$mod_sum)
  3415. {
  3416. $lcd = $array[0]*$x;
  3417. }
  3418. else
  3419. {
  3420. $this->_lcd($array, $x+1);
  3421. }
  3422. return $lcd;
  3423. }
  3424. // ---------------------------------------------------------
  3425. /**
  3426. * Replace the current context in a variable name
  3427. *
  3428. * @access private
  3429. * @param string $name The variable name
  3430. * @return string
  3431. */
  3432. private function _parse_context($name, $disable_query_str = FALSE)
  3433. {
  3434. // replace '@:' with current context name
  3435. if (strncmp($name, '@:', 2) == 0)
  3436. {
  3437. $name = str_replace('@', self::$context, $name);
  3438. }
  3439. // fetch the *unadulterated* URI of the current page
  3440. $ee_uri = new EE_URI;
  3441. // documented as a 'private' method, but not actually. Called in CI_Router so unlikely to ever be made private.
  3442. $ee_uri->_fetch_uri_string();
  3443. $ee_uri->_remove_url_suffix();
  3444. $ee_uri->_explode_segments();
  3445. // provide a fallback value for index pages
  3446. $uri = $ee_uri->uri_string();
  3447. $uri = empty($uri) ? $this->EE->stash_model->get_index_key() : $uri;
  3448. // append query string?
  3449. if ($this->include_query_str
  3450. && ! $disable_query_str
  3451. && $query_str = $this->EE->input->server('QUERY_STRING')
  3452. ){
  3453. $uri = $uri . '?' . $query_str;
  3454. }
  3455. // replace '@URI:' with the current URI
  3456. if (strncmp($name, '@URI:', 5) == 0)
  3457. {
  3458. $name = str_replace('@URI', $uri, $name);
  3459. }
  3460. // apply a global variable prefix, if set
  3461. if ( $prefix = $this->EE->config->item('stash_var_prefix'))
  3462. {
  3463. if (strstr($name, ':'))
  3464. {
  3465. $name = str_replace(':', ':' . $prefix, $name);
  3466. }
  3467. else
  3468. {
  3469. $name = $prefix . $name;
  3470. }
  3471. }
  3472. return $name;
  3473. }
  3474. // ---------------------------------------------------------
  3475. /**
  3476. * Parse template data
  3477. *
  3478. * @access private
  3479. * @param bool $tags Parse plugin/module tags
  3480. * @param bool $vars Parse globals (inc. snippets), native stash vars and segments
  3481. * @param bool $conditionals Parse advanced conditionals
  3482. * @param int $depth Number of passes to make of the template tagdata
  3483. * @return string
  3484. */
  3485. private function _parse_sub_template($tags = TRUE, $vars = TRUE, $conditionals = FALSE, $depth = 1, $nocache_id = FALSE)
  3486. {
  3487. $this->EE->TMPL->log_item("Stash: processing inner tags");
  3488. // optional prefix to use for nocache pairs
  3489. if ($nocache_prefix = $this->EE->TMPL->fetch_param('prefix', FALSE))
  3490. {
  3491. // add to the array for optional removal at the end of template parsing
  3492. if ( ! in_array($nocache_prefix, self::$_nocache_prefixes))
  3493. {
  3494. self::$_nocache_prefixes[] = $nocache_prefix;
  3495. }
  3496. }
  3497. else
  3498. {
  3499. $nocache_prefix = 'stash';
  3500. }
  3501. // nocache tags
  3502. if (FALSE === $nocache_id)
  3503. {
  3504. $this->nocache_id = $this->EE->functions->random();
  3505. }
  3506. $nocache = $nocache_prefix . $this->_nocache_suffix;
  3507. $nocache_pattern = '/'.LD.$nocache.RD.'(.*)'.LD.'\/'.$nocache.RD.'/Usi';
  3508. // save TMPL values for later
  3509. $tagparams = $this->EE->TMPL->tagparams;
  3510. $tagdata = $this->EE->TMPL->tagdata;
  3511. // call the template_fetch_template hook to prep nested stash embeds
  3512. if ($this->EE->extensions->active_hook('template_fetch_template') === TRUE && ! $this->_embed_nested)
  3513. {
  3514. // stash embed vars
  3515. $embed_vars = (array) $this->EE->TMPL->fetch_param('embed_vars', array());
  3516. $this->EE->session->cache['stash'] = array_merge($this->EE->session->cache['stash'], $embed_vars);
  3517. // important: we only want to call Stash's hook, not any other add-ons
  3518. // make a copy of the extensions for this hook
  3519. // we'll need to do this manually if extensions property visibility is ever changed to protected or private
  3520. $ext = $this->EE->extensions->extensions['template_fetch_template'];
  3521. // temporarily make Stash the only extension
  3522. $this->EE->extensions->extensions['template_fetch_template'] = array(
  3523. array('Stash_ext' => array(
  3524. 'template_fetch_template',
  3525. '',
  3526. $this->version
  3527. )));
  3528. // call the hook
  3529. $this->EE->extensions->call('template_fetch_template', array(
  3530. 'template_data' => $this->EE->TMPL->tagdata
  3531. ));
  3532. // restore original extensions
  3533. $this->EE->extensions->extensions['template_fetch_template'] = $ext;
  3534. unset($ext);
  3535. // don't run again for this template
  3536. $this->_embed_nested = TRUE;
  3537. }
  3538. // restore original TMPL values
  3539. $this->EE->TMPL->tagparams = $tagparams;
  3540. $this->EE->TMPL->tagdata = $tagdata;
  3541. if (self::$_nocache)
  3542. {
  3543. // protect content inside {stash:nocache} tags, or {[prefix]:nocache} tags
  3544. $this->EE->TMPL->tagdata = preg_replace_callback($nocache_pattern, array($this, '_placeholders'), $this->EE->TMPL->tagdata);
  3545. }
  3546. // parse variables
  3547. if ($vars)
  3548. {
  3549. // note: each pass can expose more variables to be parsed after tag processing
  3550. $this->EE->TMPL->tagdata = $this->_parse_template_vars($this->EE->TMPL->tagdata);
  3551. if (self::$_nocache)
  3552. {
  3553. // protect content inside {stash:nocache} tags that might have been exposed by parse_vars
  3554. $this->EE->TMPL->tagdata = preg_replace_callback($nocache_pattern, array($this, '_placeholders'), $this->EE->TMPL->tagdata);
  3555. }
  3556. }
  3557. // parse conditionals?
  3558. if ($conditionals && strpos($this->EE->TMPL->tagdata, LD.'if') !== FALSE)
  3559. {
  3560. // prep {If var1 IN (var2)}../if] style conditionals
  3561. if ($this->parse_if_in)
  3562. {
  3563. $this->EE->TMPL->tagdata = $this->_prep_in_conditionals($this->EE->TMPL->tagdata);
  3564. }
  3565. // parse conditionals
  3566. if (version_compare(APP_VER, '2.9', '<'))
  3567. {
  3568. // pre EE 2.9, we can only parse "simple" segment and global conditionals on each pass,
  3569. // leaving "advanced" ones until after tag parsing has completed
  3570. $this->EE->TMPL->tagdata = $this->EE->TMPL->parse_simple_segment_conditionals($this->EE->TMPL->tagdata);
  3571. $this->EE->TMPL->tagdata = $this->EE->TMPL->simple_conditionals($this->EE->TMPL->tagdata, $this->EE->config->_global_vars);
  3572. }
  3573. else
  3574. {
  3575. // with EE 2.9 and later we can parse conditionals when the variables referenced have a value ("when ready")
  3576. // populate user variables
  3577. $user_vars = $this->_get_users_vars();
  3578. $logged_in_user_cond = array();
  3579. foreach ($user_vars as $val)
  3580. {
  3581. if (isset($this->EE->session->userdata[$val]) AND ($val == 'group_description' OR strval($this->EE->session->userdata[$val]) != ''))
  3582. {
  3583. $logged_in_user_cond['logged_in_'.$val] = $this->EE->session->userdata[$val];
  3584. }
  3585. }
  3586. // Parse conditionals for known variables *without* converting unknown variables
  3587. // used in if/else statements to false or 'n'
  3588. $this->EE->TMPL->tagdata = $this->EE->functions->prep_conditionals(
  3589. $this->EE->TMPL->tagdata,
  3590. array_merge(
  3591. $this->EE->TMPL->segment_vars,
  3592. $this->EE->TMPL->template_route_vars,
  3593. $this->EE->TMPL->embed_vars,
  3594. $logged_in_user_cond,
  3595. $this->EE->config->_global_vars
  3596. )
  3597. );
  3598. }
  3599. }
  3600. // Remove any EE comments that might have been exposed before parsing tags
  3601. if (strpos($this->EE->TMPL->tagdata, '{!--') !== FALSE)
  3602. {
  3603. $this->EE->TMPL->tagdata = preg_replace("/\{!--.*?--\}/s", '', $this->EE->TMPL->tagdata);
  3604. }
  3605. // clone the template object
  3606. $TMPL2 = $this->EE->TMPL;
  3607. unset($this->EE->TMPL);
  3608. // parse tags, but check that there really are unparsed tags in the current shell
  3609. if ($tags && (strpos($TMPL2->tagdata, LD.'exp:') !== FALSE))
  3610. {
  3611. // copy object properties from original
  3612. $this->EE->TMPL = new EE_Template();
  3613. $this->EE->TMPL->start_microtime = $TMPL2->start_microtime;
  3614. $this->EE->TMPL->template = $TMPL2->tagdata;
  3615. $this->EE->TMPL->tag_data = array();
  3616. $this->EE->TMPL->var_single = array();
  3617. $this->EE->TMPL->var_cond = array();
  3618. $this->EE->TMPL->var_pair = array();
  3619. $this->EE->TMPL->plugins = $TMPL2->plugins;
  3620. $this->EE->TMPL->modules = $TMPL2->modules;
  3621. $this->EE->TMPL->module_data = $TMPL2->module_data;
  3622. // copy globals
  3623. $this->EE->TMPL->segment_vars = $TMPL2->segment_vars;
  3624. $this->EE->TMPL->embed_vars = $TMPL2->embed_vars;
  3625. $this->EE->TMPL->template_route_vars = array();
  3626. if ( isset($TMPL2->template_route_vars))
  3627. {
  3628. $this->EE->TMPL->template_route_vars = $TMPL2->template_route_vars;
  3629. }
  3630. $this->EE->TMPL->layout_conditionals = array();
  3631. if ( isset($TMPL2->layout_conditionals))
  3632. {
  3633. $this->EE->TMPL->layout_conditionals = $TMPL2->layout_conditionals;
  3634. }
  3635. // parse tags
  3636. $this->EE->TMPL->parse_tags();
  3637. $this->EE->TMPL->process_tags();
  3638. $this->EE->TMPL->loop_count = 0;
  3639. $TMPL2->tagdata = $this->EE->TMPL->template;
  3640. $TMPL2->log = array_merge($TMPL2->log, $this->EE->TMPL->log);
  3641. }
  3642. else
  3643. {
  3644. $depth = 1;
  3645. }
  3646. $this->EE->TMPL = $TMPL2;
  3647. unset($TMPL2);
  3648. // recursively parse?
  3649. if ( $depth > 1)
  3650. {
  3651. $depth --;
  3652. // the merry-go-round... parse the next shell of tags
  3653. $this->_parse_sub_template($tags, $vars, $conditionals, $depth, $this->nocache_id);
  3654. }
  3655. else
  3656. {
  3657. // recursive parsing complete
  3658. // parse advanced conditionals?
  3659. if ($conditionals && strpos($this->EE->TMPL->tagdata, LD.'if') !== FALSE)
  3660. {
  3661. // record if PHP is enabled for this template
  3662. $parse_php = $this->EE->TMPL->parse_php;
  3663. if ( ! isset($this->EE->TMPL->layout_conditionals))
  3664. {
  3665. $this->EE->TMPL->layout_conditionals = array();
  3666. }
  3667. // this will parse all remaining conditionals, with unknown variables used in if/else
  3668. // statements being converted to false or 'n' so they are parsed safely
  3669. $this->EE->TMPL->tagdata = $this->EE->TMPL->advanced_conditionals($this->EE->TMPL->tagdata);
  3670. // restore original parse_php flag for this template
  3671. $this->EE->TMPL->parse_php = $parse_php;
  3672. }
  3673. // call the 'template_post_parse' hook
  3674. if ($this->EE->extensions->active_hook('template_post_parse') === TRUE && $this->_embed_nested === TRUE)
  3675. {
  3676. // make a copy of the extensions for the 'template_fetch_template' hook
  3677. $ext = $this->EE->extensions->extensions['template_fetch_template'];
  3678. // temporarily make Stash the only extension on the 'template_fetch_template' hook
  3679. $this->EE->extensions->extensions['template_fetch_template'] = array(
  3680. array('Stash_ext' => array(
  3681. 'template_fetch_template',
  3682. '',
  3683. $this->version
  3684. )));
  3685. // call the 'template_post_parse' hook
  3686. $this->EE->TMPL->tagdata = $this->EE->extensions->call(
  3687. 'template_post_parse',
  3688. $this->EE->TMPL->tagdata,
  3689. FALSE,
  3690. $this->site_id,
  3691. TRUE
  3692. );
  3693. // restore original extensions on the 'template_fetch_template' hook
  3694. $this->EE->extensions->extensions['template_fetch_template'] = $ext;
  3695. unset($ext);
  3696. }
  3697. // restore content inside {stash:nocache} tags
  3698. // we must do this even if nocache has been disabled, since it may have been disabled after tags were escaped
  3699. foreach ($this->_ph as $index => $val)
  3700. {
  3701. $this->EE->TMPL->tagdata = str_replace('[_'.__CLASS__.'_'.($index+1).'_'.$this->nocache_id.']', $val, $this->EE->TMPL->tagdata);
  3702. }
  3703. // parse EE nocache placeholders {NOCACHE}
  3704. $this->EE->TMPL->tagdata = $this->EE->TMPL->parse_nocache($this->EE->TMPL->tagdata);
  3705. }
  3706. }
  3707. // ---------------------------------------------------------
  3708. /**
  3709. * Parse global vars inside a string
  3710. *
  3711. * @access private
  3712. * @param string $template String to parse
  3713. * @return string
  3714. */
  3715. private function _parse_template_vars($template = '')
  3716. {
  3717. // globals vars {name}
  3718. if (count($this->EE->config->_global_vars) > 0 && strpos($template, LD) !== FALSE)
  3719. {
  3720. foreach ($this->EE->config->_global_vars as $key => $val)
  3721. {
  3722. $template = str_replace(LD.$key.RD, $val, $template);
  3723. }
  3724. }
  3725. // stash vars {stash:var}
  3726. // note: due to the order we're doing this, global vars can themselves contain stash vars...
  3727. if (count($this->EE->session->cache['stash']) > 0 && strpos($template, LD.'stash:') !== FALSE)
  3728. {
  3729. // We only want to replace single stash placeholder tags,
  3730. // NOT tag pairs such as {stash:var}whatever{/stash:var}
  3731. $tag_vars = array();
  3732. preg_match_all('#'.LD.'(stash:[a-z0-9\-_]+)'.RD.'(?!.+\1'.RD.')#ims', $template, $matches);
  3733. if (isset($matches[1]))
  3734. {
  3735. $tag_vars = array_flip($matches[1]);
  3736. }
  3737. foreach($this->EE->session->cache['stash'] as $key => $val)
  3738. {
  3739. if (isset($tag_vars['stash:'.$key]))
  3740. {
  3741. $template = str_replace(LD.'stash:'.$key.RD, $val, $template);
  3742. }
  3743. }
  3744. }
  3745. // user variables, in the form {logged_in_[variable]}
  3746. if (strpos($template, LD.'logged_in_') !== FALSE)
  3747. {
  3748. $user_vars = $this->_get_users_vars();
  3749. foreach ($user_vars as $val)
  3750. {
  3751. if (isset($this->EE->session->userdata[$val]) AND ($val == 'group_description' OR strval($this->EE->session->userdata[$val]) != ''))
  3752. {
  3753. $template = str_replace(LD.'logged_in_'.$val.RD, $this->EE->session->userdata[$val], $template);
  3754. }
  3755. }
  3756. }
  3757. // Parse date format string "constants"
  3758. if (strpos($template, LD.'DATE_') !== FALSE)
  3759. {
  3760. $date_constants = array('DATE_ATOM' => '%Y-%m-%dT%H:%i:%s%Q',
  3761. 'DATE_COOKIE' => '%l, %d-%M-%y %H:%i:%s UTC',
  3762. 'DATE_ISO8601' => '%Y-%m-%dT%H:%i:%s%Q',
  3763. 'DATE_RFC822' => '%D, %d %M %y %H:%i:%s %O',
  3764. 'DATE_RFC850' => '%l, %d-%M-%y %H:%m:%i UTC',
  3765. 'DATE_RFC1036' => '%D, %d %M %y %H:%i:%s %O',
  3766. 'DATE_RFC1123' => '%D, %d %M %Y %H:%i:%s %O',
  3767. 'DATE_RFC2822' => '%D, %d %M %Y %H:%i:%s %O',
  3768. 'DATE_RSS' => '%D, %d %M %Y %H:%i:%s %O',
  3769. 'DATE_W3C' => '%Y-%m-%dT%H:%i:%s%Q'
  3770. );
  3771. foreach ($date_constants as $key => $val)
  3772. {
  3773. $template = str_replace(LD.$key.RD, $val, $template);
  3774. }
  3775. }
  3776. // Current time {current_time format="%Y %m %d %H:%i:%s"} - thanks @objectivehtml
  3777. if (strpos($template, LD.'current_time') !== FALSE)
  3778. {
  3779. if (preg_match_all("/".LD."current_time\s+format=([\"\'])([^\\1]*?)\\1".RD."/", $template, $matches))
  3780. {
  3781. for ($j = 0; $j < count($matches[0]); $j++)
  3782. {
  3783. if (version_compare(APP_VER, '2.6', '>='))
  3784. {
  3785. $template = str_replace($matches[0][$j], $this->EE->localize->format_date($matches[2][$j]), $template);
  3786. }
  3787. else
  3788. {
  3789. $template = str_replace($matches[0][$j], $this->EE->localize->decode_date($matches[2][$j], $this->EE->localize->now), $template);
  3790. }
  3791. }
  3792. }
  3793. }
  3794. // segment vars {segment_1} etc
  3795. if (strpos($template, LD.'segment_' ) !== FALSE )
  3796. {
  3797. for ($i = 1; $i < 10; $i++)
  3798. {
  3799. $template = str_replace(LD.'segment_'.$i.RD, $this->EE->uri->segment($i), $template);
  3800. }
  3801. }
  3802. return $template;
  3803. }
  3804. // ---------------------------------------------------------
  3805. /**
  3806. * Final parsing of the stash variable before output to the template
  3807. *
  3808. * @access private
  3809. * @param string $value the string to parse
  3810. * @param string $match A regular expression to match against
  3811. * @param string $filter A regular expression to filter by
  3812. * @param string $default fallback value
  3813. * @return string
  3814. */
  3815. private function _parse_output($value = NULL, $match = NULL, $filter = NULL, $default = NULL)
  3816. {
  3817. // parse tags?
  3818. if ( ($this->parse_tags || $this->parse_vars || $this->parse_conditionals) && ! $this->parse_complete)
  3819. {
  3820. // do parsing
  3821. $this->EE->TMPL->tagdata = $value;
  3822. $this->_parse_sub_template($this->parse_tags, $this->parse_vars, $this->parse_conditionals, $this->parse_depth);
  3823. $value = $this->EE->TMPL->tagdata;
  3824. unset($this->EE->TMPL->tagdata);
  3825. }
  3826. // regex match
  3827. if ( $match !== NULL && $value !== NULL )
  3828. {
  3829. $is_match = $this->_matches($match, $value);
  3830. if ( ! $is_match )
  3831. {
  3832. $value = $default;
  3833. }
  3834. }
  3835. // regex filter
  3836. if ( $filter !== NULL && $value !== NULL)
  3837. {
  3838. preg_match($filter, $value, $found);
  3839. if (isset($found[1]))
  3840. {
  3841. $value = $found[1];
  3842. }
  3843. }
  3844. // apply string manipulations
  3845. $value = $this->_clean_string($value);
  3846. return $value;
  3847. }
  3848. // ---------------------------------------------------------
  3849. /**
  3850. * String manipulations
  3851. *
  3852. * @access private
  3853. * @param string $value the string to parse
  3854. * @return string
  3855. */
  3856. private function _clean_string($value = NULL)
  3857. {
  3858. // register parameters
  3859. $trim = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('trim'));
  3860. $strip_tags = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('strip_tags'));
  3861. $strip_curly_braces = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('strip_curly_braces'));
  3862. $strip_unparsed = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('strip_unparsed'));
  3863. $compress = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('compress'));
  3864. $backspace = (int) $this->EE->TMPL->fetch_param('backspace', 0);
  3865. $strip_vars = $this->EE->TMPL->fetch_param('strip', FALSE);
  3866. // support legacy parameter name
  3867. if ( ! $strip_unparsed)
  3868. {
  3869. $strip_unparsed = (bool) preg_match('/1|on|yes|y/i', $this->EE->TMPL->fetch_param('remove_unparsed_vars'));
  3870. }
  3871. // trim?
  3872. if ($trim)
  3873. {
  3874. $value = str_replace( array("\t", "\n", "\r", "\0", "\x0B"), '', trim($value));
  3875. }
  3876. // remove whitespace between tags which are separated by line returns?
  3877. if ($compress)
  3878. {
  3879. // remove spaces between tags
  3880. $value = preg_replace('~>\s*\n\s*<~', '><', $value);
  3881. // double spaces, leading and trailing spaces
  3882. $value = trim(preg_replace('/\s\s+/', ' ', $value));
  3883. }
  3884. // strip tags?
  3885. if ($strip_tags)
  3886. {
  3887. $value = strip_tags($value);
  3888. }
  3889. // strip curly braces?
  3890. if ($strip_curly_braces)
  3891. {
  3892. $value = str_replace(array(LD, RD), '', $value);
  3893. }
  3894. // backspace?
  3895. if ($backspace)
  3896. {
  3897. // backspace can break unparsed conditionals and tags, so lets check for them
  3898. $remove_from_end = substr($value, -$backspace);
  3899. if (strrpos($remove_from_end, RD) !== false)
  3900. {
  3901. // unparsed var or tag within the backspace range, trim end as far as we safely can
  3902. $value = substr($value, 0, strrpos($value, RD)+1);
  3903. }
  3904. else
  3905. {
  3906. $value = substr($value, 0, -$backspace);
  3907. }
  3908. }
  3909. // xss clean?
  3910. if ($this->xss_clean)
  3911. {
  3912. $value = $this->EE->security->xss_clean($value);
  3913. }
  3914. // remove leftover placeholder variables {var} (leave stash: vars untouched)
  3915. if ($strip_unparsed)
  3916. {
  3917. $value = preg_replace('/\{\/?(?!\/?stash)[a-zA-Z0-9_\-:]+\}/', '', $value);
  3918. }
  3919. // cleanup specified single and pair variable placeholders
  3920. if ($strip_vars)
  3921. {
  3922. $strip_vars = explode("|", $strip_vars);
  3923. foreach($strip_vars as $var)
  3924. {
  3925. $value = str_replace(array(LD.$var.RD, LD.'/'.$var.RD), '', $value);
  3926. }
  3927. }
  3928. return $value;
  3929. }
  3930. // ---------------------------------------------------------
  3931. /**
  3932. * Run a Stash module tag with a known set of parameters
  3933. *
  3934. * @access private
  3935. * @param string $method the public Stash method to call
  3936. * @param array $params the tag parameters to use
  3937. * @return string
  3938. */
  3939. private function _run_tag($method, $params = array())
  3940. {
  3941. // make a copy of the original parameters
  3942. $original_params = $this->EE->TMPL->tagparams;
  3943. // array of permitted parameters
  3944. $allowed_params = array_flip($params);
  3945. // set permitted params for use
  3946. foreach($allowed_params as $key => &$value)
  3947. {
  3948. if ( isset($this->EE->TMPL->tagparams[$key]))
  3949. {
  3950. $value = $this->EE->TMPL->tagparams[$key];
  3951. }
  3952. else
  3953. {
  3954. unset($allowed_params[$key]);
  3955. }
  3956. }
  3957. // overwrite template params with our safe set
  3958. $this->EE->TMPL->tagparams = $allowed_params;
  3959. // run the tag if it is public
  3960. if (method_exists($this, $method))
  3961. {
  3962. $reflection = new ReflectionMethod($this, $method);
  3963. if ( ! $reflection->isPublic())
  3964. {
  3965. throw new RuntimeException("The called method is not public.");
  3966. }
  3967. $out = $this->$method();
  3968. }
  3969. // restore original parameters
  3970. $this->EE->TMPL->tagparams = $original_params;
  3971. unset($original_params);
  3972. return $out;
  3973. }
  3974. // ---------------------------------------------------------
  3975. /**
  3976. * Replaces nested tag content with placeholders
  3977. *
  3978. * @access private
  3979. * @param array $matches
  3980. * @return string
  3981. */
  3982. private function _placeholders($matches)
  3983. {
  3984. $this->_ph[] = $matches[1];
  3985. return '[_'.__CLASS__.'_'.count($this->_ph).'_'.$this->nocache_id.']';
  3986. }
  3987. // ---------------------------------------------------------
  3988. /**
  3989. * Delay processing a tag until template_post_parse hook
  3990. *
  3991. * @access private
  3992. * @param String Method name (e.g. display, link or embed)
  3993. * @return Mixed TRUE if delay, FALSE if not
  3994. */
  3995. private function _post_parse($method)
  3996. {
  3997. // base our needle off the calling tag
  3998. // add a random number to prevent EE caching the tag, if it is used more than once
  3999. $placeholder = md5($this->EE->TMPL->tagproper) . rand();
  4000. if ( ! isset($this->EE->session->cache['stash']['__template_post_parse__']))
  4001. {
  4002. $this->EE->session->cache['stash']['__template_post_parse__'] = array();
  4003. }
  4004. if ($this->process == 'end')
  4005. {
  4006. // postpone until end of tag processing
  4007. $cache =& $this->EE->session->cache['stash']['__template_post_parse__'];
  4008. }
  4009. else
  4010. {
  4011. // unknown or impossible post-process stage
  4012. $this->EE->output->show_user_error('general', sprintf($this->EE->lang->line('unknown_post_process'), $this->EE->TMPL->tagproper, $this->process));
  4013. return;
  4014. }
  4015. $this->EE->TMPL->log_item("Stash: this tag will be post-processed on {$this->process}: {$this->EE->TMPL->tagproper}");
  4016. $cache[$placeholder] = array(
  4017. 'method' => $method,
  4018. 'tagproper' => $this->EE->TMPL->tagproper,
  4019. 'tagparams' => $this->EE->TMPL->tagparams,
  4020. 'tagdata' => $this->EE->TMPL->tagdata,
  4021. 'priority' => $this->priority
  4022. );
  4023. // return needle so we can find it later
  4024. return LD.$placeholder.RD;
  4025. }
  4026. // ---------------------------------------------------------
  4027. /**
  4028. * Prep {if var IN (array)} conditionals
  4029. *
  4030. * Used with the permission of Lodewijk Schutte
  4031. * http://gotolow.com/addons/low-search
  4032. *
  4033. * @access private
  4034. * @param string $tagdata
  4035. * @return String
  4036. */
  4037. private function _prep_in_conditionals($tagdata = '')
  4038. {
  4039. if (preg_match_all('#'.LD.'if (([\w\-_]+)|((\'|")(.+)\\4)) (NOT)?\s?IN \((.*?)\)'.RD.'#', $tagdata, $matches))
  4040. {
  4041. foreach ($matches[0] as $key => $match)
  4042. {
  4043. $left = $matches[1][$key];
  4044. $operand = $matches[6][$key] ? '!=' : '==';
  4045. $andor = $matches[6][$key] ? ' AND ' : ' OR ';
  4046. $items = preg_replace('/(&(amp;)?)+/', '|', $matches[7][$key]);
  4047. $cond = array();
  4048. foreach (explode('|', $items) as $right)
  4049. {
  4050. $tmpl = preg_match('#^(\'|").+\\1$#', $right) ? '%s %s %s' : '%s %s "%s"';
  4051. $cond[] = sprintf($tmpl, $left, $operand, $right);
  4052. }
  4053. // replace {if var IN (1|2|3)} with {if var == '1' OR var == '2' OR var == '3'}
  4054. $tagdata = str_replace($match, LD.'if '.implode($andor, $cond).RD, $tagdata);
  4055. }
  4056. }
  4057. return $tagdata;
  4058. }
  4059. // ---------------------------------------------------------
  4060. /**
  4061. * prep a prefixed no_results block in current template tagdata
  4062. *
  4063. * @access public
  4064. * @param string $prefix
  4065. * @return String
  4066. */
  4067. function _prep_no_results($prefix)
  4068. {
  4069. if (strpos($this->EE->TMPL->tagdata, 'if '.$prefix.':no_results') !== FALSE
  4070. && preg_match("/".LD."if ".$prefix.":no_results".RD."(.*?)".LD.'\/'."if".RD."/s", $this->EE->TMPL->tagdata, $match))
  4071. {
  4072. if (stristr($match[1], LD.'if'))
  4073. {
  4074. $match[0] = $this->EE->functions->full_tag($match[0], $block, LD.'if', LD.'\/'."if".RD);
  4075. }
  4076. $no_results = substr($match[0], strlen(LD."if ".$prefix.":no_results".RD), -strlen(LD.'/'."if".RD));
  4077. $no_results_block = $match[0];
  4078. // remove {if prefix:no_results}..{/if} block from template
  4079. $this->EE->TMPL->tagdata = str_replace($no_results_block, '', $this->EE->TMPL->tagdata);
  4080. // set no_result variable in Template class
  4081. $this->EE->TMPL->no_results = $no_results;
  4082. }
  4083. }
  4084. // ---------------------------------------------------------
  4085. /**
  4086. * parse and return no_results content
  4087. *
  4088. * @access public
  4089. * @param string $prefix
  4090. * @return String
  4091. */
  4092. function _no_results()
  4093. {
  4094. if ( ! empty($this->EE->TMPL->no_results))
  4095. {
  4096. // parse the no_results block if it's got content
  4097. $this->EE->TMPL->no_results = $this->_parse_output($this->EE->TMPL->no_results);
  4098. }
  4099. return $this->EE->TMPL->no_results();
  4100. }
  4101. // ---------------------------------------------------------
  4102. /**
  4103. * remove a given prefix from common variables in the template tagdata
  4104. *
  4105. * @access private
  4106. * @param string $prefix
  4107. * @param string $template
  4108. * @return String
  4109. */
  4110. private function _un_prefix($prefix, $template)
  4111. {
  4112. // remove prefix
  4113. $common = array('count', 'absolute_count', 'total_results', 'absolute_results', 'switch', 'no_results');
  4114. foreach($common as $muck)
  4115. {
  4116. $template = str_replace($prefix.':'.$muck, $muck, $template);
  4117. }
  4118. return $template;
  4119. }
  4120. // ---------------------------------------------------------
  4121. /**
  4122. * set individual parse parameters if parse="yes"
  4123. *
  4124. * @access public
  4125. * @param string $prefix
  4126. * @return String
  4127. */
  4128. function set_parse_params()
  4129. {
  4130. $parse = $this->EE->TMPL->fetch_param('parse', NULL);
  4131. if ( NULL !== $parse)
  4132. {
  4133. if ( (bool) preg_match('/1|on|yes|y/i', $parse))
  4134. {
  4135. // parse="yes"
  4136. $this->EE->TMPL->tagparams['parse_tags'] = 'yes';
  4137. $this->EE->TMPL->tagparams['parse_vars'] = 'yes';
  4138. $this->EE->TMPL->tagparams['parse_conditionals'] = 'yes';
  4139. }
  4140. elseif ( (bool) preg_match('/^(0|off|no|n)$/i', $parse))
  4141. {
  4142. // parse="no"
  4143. $this->EE->TMPL->tagparams['parse_tags'] = 'no';
  4144. $this->EE->TMPL->tagparams['parse_vars'] = 'no';
  4145. $this->EE->TMPL->tagparams['parse_conditionals'] = 'no';
  4146. }
  4147. }
  4148. }
  4149. // ---------------------------------------------------------
  4150. /**
  4151. * API: call a Stash method directly
  4152. *
  4153. * @access public
  4154. * @param string $method
  4155. * @param mixed $params variable name or an array of parameters
  4156. * @param string $type
  4157. * @param string $scope
  4158. * @param string $value
  4159. * @return void
  4160. */
  4161. private function _api_call($method, $params, $type='variable', $scope='user', $value=NULL)
  4162. {
  4163. // make sure we have a Template object to work with, in case Stash is being invoked outside of a template
  4164. if ( ! class_exists('EE_Template'))
  4165. {
  4166. $this->_load_EE_TMPL();
  4167. }
  4168. // make a copy of the current tagparams and tagdata for later
  4169. $original_tagparams = array();
  4170. $original_tagdata = FALSE;
  4171. if ( isset($this->EE->TMPL->tagparams))
  4172. {
  4173. $original_tagparams = $this->EE->TMPL->tagparams;
  4174. }
  4175. if ( isset($this->EE->TMPL->tagdata))
  4176. {
  4177. $original_tagdata = $this->EE->TMPL->tagdata;
  4178. }
  4179. // make sure we have a slate to work with
  4180. $this->EE->TMPL->tagparams = array();
  4181. $this->EE->TMPL->tagdata = FALSE;
  4182. if ( is_array($params))
  4183. {
  4184. $this->EE->TMPL->tagparams = $params;
  4185. }
  4186. else
  4187. {
  4188. $this->EE->TMPL->tagparams['name'] = $params;
  4189. $this->EE->TMPL->tagparams['type'] = $type;
  4190. $this->EE->TMPL->tagparams['scope'] = $scope;
  4191. }
  4192. if ( ! is_null($value))
  4193. {
  4194. $this->EE->TMPL->tagdata = $value;
  4195. }
  4196. $result = $this->{$method}();
  4197. // restore original template params and tagdata
  4198. $this->EE->TMPL->tagparams = $original_tagparams;
  4199. $this->EE->TMPL->tagdata = $original_tagdata;
  4200. return $result;
  4201. }
  4202. // ---------------------------------------------------------
  4203. /**
  4204. * API: call a Stash method statically (DEPRECATED, PHP <5.6 only)
  4205. *
  4206. * @access public
  4207. * @param string $method
  4208. * @param mixed $params variable name or an array of parameters
  4209. * @param string $type
  4210. * @param string $scope
  4211. * @param string $value
  4212. * @return void
  4213. */
  4214. private function _api_static_call($method, $params, $type='variable', $scope='user', $value=NULL)
  4215. {
  4216. // make sure we have a Template object to work with, in case Stash is being invoked outside of a template
  4217. if ( ! class_exists('EE_Template'))
  4218. {
  4219. self::_load_EE_TMPL();
  4220. }
  4221. // make a copy of the current tagparams and tagdata for later
  4222. $original_tagparams = array();
  4223. $original_tagdata = FALSE;
  4224. if ( isset($this->EE->TMPL->tagparams))
  4225. {
  4226. $original_tagparams = $this->EE->TMPL->tagparams;
  4227. }
  4228. if ( isset($this->EE->TMPL->tagdata))
  4229. {
  4230. $original_tagdata = $this->EE->TMPL->tagdata;
  4231. }
  4232. // make sure we have a slate to work with
  4233. $this->EE->TMPL->tagparams = array();
  4234. $this->EE->TMPL->tagdata = FALSE;
  4235. if ( is_array($params))
  4236. {
  4237. $this->EE->TMPL->tagparams = $params;
  4238. }
  4239. else
  4240. {
  4241. $this->EE->TMPL->tagparams['name'] = $params;
  4242. $this->EE->TMPL->tagparams['type'] = $type;
  4243. $this->EE->TMPL->tagparams['scope'] = $scope;
  4244. }
  4245. if ( ! is_null($value))
  4246. {
  4247. $this->EE->TMPL->tagdata = $value;
  4248. }
  4249. // as this function is called statically, we need to get a Stash object instance and run the requested method
  4250. $self = new self();
  4251. $result = $self->{$method}();
  4252. // restore original template params and tagdata
  4253. $this->EE->TMPL->tagparams = $original_tagparams;
  4254. $this->EE->TMPL->tagdata = $original_tagdata;
  4255. return $result;
  4256. }
  4257. // ---------------------------------------------------------
  4258. /**
  4259. * Check if the user agent is a bot
  4260. *
  4261. * @access public
  4262. * @return void
  4263. */
  4264. private function _is_bot()
  4265. {
  4266. $bot_test = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : (php_sapi_name() === 'cli' ? 'cli' : 'other');
  4267. $is_bot = FALSE;
  4268. if (empty($bot_test))
  4269. {
  4270. $is_bot = TRUE; // no UA string, assume it's a bot
  4271. }
  4272. else
  4273. {
  4274. // Most active *legitimate* bots will contain one of these strings in the UA
  4275. $bot_list = $this->EE->config->item('stash_bots') ?
  4276. $this->EE->config->item('stash_bots') :
  4277. array('bot', 'crawl', 'spider', 'archive', 'search', 'java', 'yahoo', 'teoma');
  4278. foreach($bot_list as $bot)
  4279. {
  4280. if(strpos($bot_test, $bot) !== FALSE)
  4281. {
  4282. $is_bot = TRUE;
  4283. break; // stop right away to save processing
  4284. }
  4285. }
  4286. }
  4287. return $is_bot;
  4288. }
  4289. /**
  4290. * get the boolean value of a config item, with the desired fallback value
  4291. *
  4292. * @access private
  4293. * @param string $item config key
  4294. * @param boolean $default default value returned if config item doesn't exist
  4295. * @return boolean
  4296. */
  4297. private function _get_boolean_config_item($item, $default = TRUE)
  4298. {
  4299. if ( isset($this->EE->config->config[$item]))
  4300. {
  4301. if ($this->EE->config->config[$item] === FALSE)
  4302. {
  4303. return FALSE;
  4304. }
  4305. else
  4306. {
  4307. return TRUE;
  4308. }
  4309. }
  4310. else
  4311. {
  4312. return $default;
  4313. }
  4314. }
  4315. // ---------------------------------------------------------
  4316. /**
  4317. * set the stash cookie
  4318. *
  4319. * @access private
  4320. * @param string $unique_id the session ID
  4321. * @param integer $expire cookie duration in seconds
  4322. * @return void
  4323. */
  4324. private function _set_stash_cookie($unique_id)
  4325. {
  4326. $cookie_data = serialize(array(
  4327. 'id' => $unique_id,
  4328. 'dt' => $this->EE->localize->now
  4329. ));
  4330. if (version_compare(APP_VER, '2.8', '>='))
  4331. {
  4332. $this->EE->input->set_cookie($this->stash_cookie, $cookie_data, $this->stash_cookie_expire);
  4333. }
  4334. else
  4335. {
  4336. $this->EE->functions->set_cookie($this->stash_cookie, $cookie_data, $this->stash_cookie_expire);
  4337. }
  4338. }
  4339. /**
  4340. * get the stash cookie
  4341. *
  4342. * @access private
  4343. * @return boolean/array
  4344. */
  4345. private function _get_stash_cookie()
  4346. {
  4347. $cookie_data = @unserialize($this->EE->input->cookie($this->stash_cookie));
  4348. if ($cookie_data !== FALSE)
  4349. {
  4350. // make sure the cookie hasn't been monkeyed with
  4351. if ( isset($cookie_data['id']) && isset($cookie_data['dt']))
  4352. {
  4353. // make sure we have a valid 40-character SHA-1 hash
  4354. if ( (bool) preg_match('/^[0-9a-f]{40}$/i', $cookie_data['id']) )
  4355. {
  4356. // make sure we have a valid timestamp
  4357. if ( ((int) $cookie_data['dt'] === $cookie_data['dt'])
  4358. && ($cookie_data['dt'] <= PHP_INT_MAX)
  4359. && ($cookie_data['dt'] >= ~PHP_INT_MAX) )
  4360. {
  4361. return $cookie_data;
  4362. }
  4363. }
  4364. }
  4365. }
  4366. return FALSE;
  4367. }
  4368. /**
  4369. * return the standard set of user variables
  4370. *
  4371. * @access private
  4372. * @return array
  4373. */
  4374. private function _get_users_vars()
  4375. {
  4376. return array(
  4377. 'member_id', 'group_id', 'group_description',
  4378. 'group_title', 'username', 'screen_name',
  4379. 'email', 'ip_address', 'location', 'total_entries',
  4380. 'total_comments', 'private_messages', 'total_forum_posts',
  4381. 'total_forum_topics', 'total_forum_replies'
  4382. );
  4383. }
  4384. }
  4385. /* End of file mod.stash.php */
  4386. /* Location: ./system/expressionengine/third_party/stash/mod.stash.php */