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

/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

Large files files are truncated, but you can click here to view the full file

  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']

Large files files are truncated, but you can click here to view the full file