PageRenderTime 70ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/system/core/core.template.php

https://github.com/danboy/Croissierd
PHP | 3422 lines | 3219 code | 99 blank | 104 comment | 47 complexity | 1df5302c798edffb0e6a4a0cd94a61e7 MD5 | raw file

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

  1. <?php
  2. /*
  3. =====================================================
  4. ExpressionEngine - by EllisLab
  5. -----------------------------------------------------
  6. http://expressionengine.com/
  7. -----------------------------------------------------
  8. Copyright (c) 2003 - 2010 EllisLab, Inc.
  9. =====================================================
  10. THIS IS COPYRIGHTED SOFTWARE
  11. PLEASE READ THE LICENSE AGREEMENT
  12. http://expressionengine.com/docs/license.html
  13. =====================================================
  14. File: core.template.php
  15. -----------------------------------------------------
  16. Purpose: Template parsing class.
  17. =====================================================
  18. */
  19. if ( ! defined('EXT'))
  20. {
  21. exit('Invalid file request');
  22. }
  23. class Template {
  24. var $loop_count = 0; // Main loop counter.
  25. var $depth = 0; // Sub-template loop depth
  26. var $in_point = ''; // String position of matched opening tag
  27. var $template = ''; // The requested template (page)
  28. var $final_template = ''; // The finalized template
  29. var $fl_tmpl = ''; // 'Floating' copy of the template. Used as a temporary "work area".
  30. var $cache_hash = ''; // md5 checksum of the template name. Used as title of cache file.
  31. var $cache_status = ''; // Status of page cache (NO_CACHE, CURRENT, EXPIRED)
  32. var $cache_timestamp = '';
  33. var $template_type = ''; // Type of template (webpage, rss)
  34. var $embed_type = ''; // Type of template for embedded template
  35. var $template_hits = 0;
  36. var $php_parse_location = 'output'; // Where in the chain the PHP gets parsed
  37. var $template_edit_date = ''; // Template edit date
  38. var $encode_email = TRUE; // Whether to use the email encoder. This is set automatically
  39. var $hit_lock_override = FALSE; // Set to TRUE if you want hits tracked on sub-templates
  40. var $hit_lock = FALSE; // Lets us lock the hit counter if sub-templates are contained in a template
  41. var $parse_php = FALSE; // Whether to parse PHP or not
  42. var $protect_javascript = TRUE; // Protect javascript in conditionals
  43. var $templates_sofar = ''; // Templates processed so far, subtemplate tracker
  44. var $tag_data = array(); // Data contained in tags
  45. var $modules = array(); // List of installed modules
  46. var $module_data = array(); // Data for modules from exp_weblogs
  47. var $plugins = array(); // List of installed plug-ins
  48. var $native_modules = array(); // List of native modules with EE
  49. var $var_single = array(); // "Single" variables
  50. var $var_cond = array(); // "Conditional" variables
  51. var $var_pair = array(); // "Paired" variables
  52. var $global_vars = array(); // This array can be set via the path.php file
  53. var $embed_vars = array(); // This array can be set via the {embed} tag
  54. var $segment_vars = array(); // Array of segment variables
  55. var $tagparts = array(); // The parts of the tag: {exp:comment:form}
  56. var $tagdata = ''; // The chunk between tag pairs. This is what modules will utilize
  57. var $tagproper = ''; // The full opening tag
  58. var $no_results = ''; // The contents of the {if no_results}{/if} conditionals
  59. var $no_results_block = ''; // The {if no_results}{/if} chunk
  60. var $search_fields = array(); // Tag parameters that begin with 'search:'
  61. var $related_data = array(); // A multi-dimensional array containing any related tags
  62. var $related_id = ''; // Used temporarily for the related ID number
  63. var $related_markers = array(); // Used temporarily
  64. var $site_ids = array(); // Site IDs for the Sites Request for a Tag
  65. var $sites = array(); // Array of sites with site_id as key and site_name as value, used to determine site_ids for tag, above.
  66. var $site_prefs_cache = array(); // Array of cached site prefs, to allow fetching of another site's template files
  67. var $reverse_related_data = array(); // A multi-dimensional array containing any reverse related tags
  68. var $t_cache_path = 'tag_cache/'; // Location of the tag cache file
  69. var $p_cache_path = 'page_cache/'; // Location of the page cache file
  70. var $disable_caching = FALSE;
  71. var $debugging = FALSE; // Template parser debugging on?
  72. var $cease_processing = FALSE; // Used with no_results() method.
  73. var $log = array(); // Log of Template processing
  74. var $start_microtime = 0; // For Logging (= microtime())
  75. var $strict_urls = FALSE; // Whether to make URLs operate strictly or not. This is set via a template global pref
  76. var $realm = 'ExpressionEngine Template'; // Localize?
  77. var $marker = '0o93H7pQ09L8X1t49cHY01Z5j4TT91fGfr'; // Temporary marker used as a place-holder for template data
  78. /** -------------------------------------
  79. /** Constructor
  80. /** -------------------------------------*/
  81. function Template()
  82. {
  83. global $IN, $PREFS;
  84. $this->native_modules = array('blacklist', 'comment', 'email', 'forum',
  85. 'gallery', 'mailinglist', 'member', 'query',
  86. 'referrer', 'rss', 'search', 'stats',
  87. 'trackback', 'updated_sites', 'weblog',
  88. 'simple_commerce', 'commerce');
  89. $this->global_vars = $IN->global_vars;
  90. if ($PREFS->ini('multiple_sites_enabled') != 'y')
  91. {
  92. $this->sites[$PREFS->ini('site_id')] = $PREFS->ini('site_short_name');
  93. }
  94. if ($PREFS->ini('template_debugging') === 'y' && $this->start_microtime == 0)
  95. {
  96. $this->debugging = TRUE;
  97. if (phpversion() < 5)
  98. {
  99. list($usec, $sec) = explode(" ", microtime());
  100. $this->start_microtime = ((float)$usec + (float)$sec);
  101. }
  102. else
  103. {
  104. $this->start_microtime = microtime(TRUE);
  105. }
  106. }
  107. }
  108. /* END */
  109. /** -------------------------------------
  110. /** Run the template engine
  111. /** -------------------------------------*/
  112. function run_template_engine($template_group = '', $template = '')
  113. {
  114. global $OUT, $IN, $FNS, $PREFS;
  115. $this->log_item(" - Begin Template Processing - ");
  116. // Set the name of the cache folder for both tag and page caching
  117. if ($IN->URI != '')
  118. {
  119. $this->t_cache_path .= md5($FNS->fetch_site_index().$IN->URI).'/';
  120. $this->p_cache_path .= md5($FNS->fetch_site_index().$IN->URI).'/';
  121. }
  122. else
  123. {
  124. $this->t_cache_path .= md5($PREFS->ini('site_url').'index'.$IN->QSTR).'/';
  125. $this->p_cache_path .= md5($PREFS->ini('site_url').'index'.$IN->QSTR).'/';
  126. }
  127. // We limit the total number of cache files in order to
  128. // keep some sanity with large sites or ones that get
  129. // hit by over-ambitious crawlers.
  130. if ($this->disable_caching == FALSE)
  131. {
  132. if ($dh = @opendir(PATH_CACHE.'page_cache'))
  133. {
  134. $i = 0;
  135. while (false !== (readdir($dh)))
  136. {
  137. $i++;
  138. }
  139. $max = ( ! $PREFS->ini('max_caches') OR ! is_numeric($PREFS->ini('max_caches')) OR $PREFS->ini('max_caches') > 1000) ? 1000 : $PREFS->ini('max_caches');
  140. if ($i > $max)
  141. {
  142. $FNS->clear_caching('page');
  143. }
  144. }
  145. }
  146. $this->log_item("URI: ".$IN->URI);
  147. $this->log_item("Path.php Template: {$template_group}/{$template}");
  148. $this->process_template($template_group, $template, FALSE);
  149. $this->log_item(" - End Template Processing - ");
  150. $this->log_item("Parse Global Variables");
  151. if ($this->template_type == 'static')
  152. {
  153. $this->final_template = $this->restore_xml_declaration($this->final_template);
  154. }
  155. else
  156. {
  157. $this->final_template = $this->parse_globals($this->final_template);
  158. }
  159. $this->log_item("Template Parsing Finished");
  160. $OUT->out_type = $this->template_type;
  161. $OUT->build_queue($this->final_template);
  162. }
  163. /* END */
  164. /** -------------------------------------
  165. /** Process Template
  166. /** -------------------------------------*/
  167. function process_template($template_group = '', $template = '', $sub = FALSE, $site_id = '')
  168. {
  169. global $LOC, $PREFS, $REGX, $LANG, $IN, $FNS;
  170. // add this template to our subtemplate tracker
  171. $this->templates_sofar = $this->templates_sofar.'|'.$site_id.':'.$template_group.'/'.$template.'|';
  172. /** -------------------------------------
  173. /** Fetch the requested template
  174. /** -------------------------------------*/
  175. // The template can either come from the DB or a cache file
  176. // Do not use a reference!
  177. $this->cache_status = 'NO_CACHE';
  178. $this->log_item("Retrieving Template");
  179. $this->template = ($template_group != '' AND $template != '') ? $this->fetch_template($template_group, $template, FALSE, $site_id) : $this->parse_template_uri();
  180. $this->log_item("Template Type: ".$this->template_type);
  181. /** -------------------------------------
  182. /** Static Content, No Parsing
  183. /** -------------------------------------*/
  184. if ($this->template_type == 'static' OR $this->embed_type == 'static')
  185. {
  186. if ($sub == FALSE)
  187. {
  188. $this->final_template = $this->template;
  189. }
  190. return;
  191. }
  192. /* -------------------------------------
  193. /* "Smart" Static Parsing
  194. /*
  195. /* Performed on embedded webpage templates only that do not have
  196. /* ExpressionEngine tags or PHP in them.
  197. /*
  198. /* Hidden Configuration Variable
  199. /* - smart_static_parsing => Bypass parsing of templates that could be
  200. /* of the type 'static' but aren't? (y/n)
  201. /* -------------------------------------*/
  202. if ($PREFS->ini('smart_static_parsing') !== 'n' && $this->embed_type == 'webpage' && ! stristr($this->template, LD) && ! stristr($this->template, '<?'))
  203. {
  204. $this->log_item("Smart Static Parsing Triggered");
  205. if ($sub == FALSE)
  206. {
  207. $this->final_template = $this->template;
  208. }
  209. return;
  210. }
  211. /** -------------------------------------
  212. /** Replace "logged_out" variables
  213. /** -------------------------------------*/
  214. // We do this for backward compatibility
  215. // Note: My plan is to deprecate this, but we need to update
  216. // every template in an installation with the new syntax.
  217. // I would have done it for 1.2 but I added this late, after the
  218. // beta testers already got a copy, so we'll do it in a future update.
  219. $logvars = array('NOT_LOGGED_IN' => 'logged_out', 'not_logged_in' => 'logged_out', 'LOGGED_IN' => 'logged_in');
  220. foreach($logvars as $key => $val)
  221. {
  222. $this->template = str_replace(LD.'if '.$key.RD, LD.'if '.$val.RD, $this->template);
  223. }
  224. /** -------------------------------------
  225. /** Parse URI segments
  226. /** -------------------------------------*/
  227. // This code lets admins fetch URI segments which become
  228. // available as: {segment_1} {segment_2}
  229. for ($i = 1; $i < 10; $i++)
  230. {
  231. $this->template = str_replace(LD.'segment_'.$i.RD, $IN->fetch_uri_segment($i), $this->template);
  232. $this->segment_vars['segment_'.$i] = $IN->fetch_uri_segment($i);
  233. }
  234. /** -------------------------------------
  235. /** Parse {embed} tag variables
  236. /** -------------------------------------*/
  237. if ($sub === TRUE && count($this->embed_vars) > 0)
  238. {
  239. $this->log_item("Embed Variables (Keys): ".implode('|', array_keys($this->embed_vars)));
  240. $this->log_item("Embed Variables (Values): ".trim(implode('|', $this->embed_vars)));
  241. foreach ($this->embed_vars as $key => $val)
  242. {
  243. // add 'embed:' to the key for replacement and so these variables work in conditionals
  244. $this->embed_vars['embed:'.$key] = $val;
  245. unset($this->embed_vars[$key]);
  246. $this->template = str_replace(LD.'embed:'.$key.RD, $val, $this->template);
  247. }
  248. }
  249. // cleanup of leftover/undeclared embed variables
  250. // don't worry with undeclared embed: vars in conditionals as the conditionals processor will handle that adequately
  251. if (strpos($this->template, LD.'embed:') !== FALSE)
  252. {
  253. $this->template = preg_replace('/'.LD.'embed:(.+?)'.RD.'/', '', $this->template);
  254. }
  255. /** --------------------------------------------------
  256. /** Parse 'Site' variables
  257. /** --------------------------------------------------*/
  258. $this->log_item("Parsing Site Variables");
  259. // load site variables into the global_vars array
  260. foreach (array('site_id', 'site_label', 'site_short_name') as $site_var)
  261. {
  262. $this->global_vars[$site_var] = stripslashes($PREFS->ini($site_var));
  263. }
  264. /** -------------------------------------
  265. /** Parse manual variables
  266. /** -------------------------------------*/
  267. // These are variables that can be set in the path.php file
  268. if (count($this->global_vars) > 0)
  269. {
  270. $this->log_item("Global Path.php Variables (Keys): ".implode('|', array_keys($this->global_vars)));
  271. $this->log_item("Global Path.php Variables (Values): ".trim(implode('|', $this->global_vars)));
  272. foreach ($this->global_vars as $key => $val)
  273. {
  274. $this->template = str_replace(LD.$key.RD, $val, $this->template);
  275. }
  276. }
  277. /** -------------------------------------
  278. /** Parse date format string "constants"
  279. /** -------------------------------------*/
  280. $date_constants = array('DATE_ATOM' => '%Y-%m-%dT%H:%i:%s%Q',
  281. 'DATE_COOKIE' => '%l, %d-%M-%y %H:%i:%s UTC',
  282. 'DATE_ISO8601' => '%Y-%m-%dT%H:%i:%s%O',
  283. 'DATE_RFC822' => '%D, %d %M %y %H:%i:%s %O',
  284. 'DATE_RFC850' => '%l, %d-%M-%y %H:%m:%i UTC',
  285. 'DATE_RFC1036' => '%D, %d %M %y %H:%i:%s %O',
  286. 'DATE_RFC1123' => '%D, %d %M %Y %H:%i:%s %O',
  287. 'DATE_RFC2822' => '%D, %d %M %Y %H:%i:%s %O',
  288. 'DATE_RSS' => '%D, %d %M %Y %H:%i:%s %O',
  289. 'DATE_W3C' => '%Y-%m-%dT%H:%i:%s%Q'
  290. );
  291. foreach ($date_constants as $key => $val)
  292. {
  293. $this->template = str_replace(LD.$key.RD, $val, $this->template);
  294. }
  295. $this->log_item("Parse Date Format String Constants");
  296. /** --------------------------------------------------
  297. /** Template's Last Edit time {template_edit_date format="%Y %m %d %H:%i:%s"}
  298. /** --------------------------------------------------*/
  299. if (strpos($this->template, LD.'template_edit_date') !== FALSE && preg_match_all("/".LD."template_edit_date\s+format=([\"\'])([^\\1]*?)\\1".RD."/", $this->template, $matches))
  300. {
  301. for ($j = 0; $j < count($matches['0']); $j++)
  302. {
  303. $this->template = preg_replace("/".$matches['0'][$j]."/", $LOC->decode_date($matches['2'][$j], $this->template_edit_date), $this->template, 1);
  304. }
  305. }
  306. /** --------------------------------------------------
  307. /** Current time {current_time format="%Y %m %d %H:%i:%s"}
  308. /** --------------------------------------------------*/
  309. if (strpos($this->template, LD.'current_time') !== FALSE && preg_match_all("/".LD."current_time\s+format=([\"\'])([^\\1]*?)\\1".RD."/", $this->template, $matches))
  310. {
  311. for ($j = 0; $j < count($matches['0']); $j++)
  312. {
  313. $this->template = preg_replace("/".preg_quote($matches['0'][$j], '/')."/", $LOC->decode_date($matches['2'][$j], $LOC->now), $this->template, 1);
  314. }
  315. }
  316. $this->template = str_replace(LD.'current_time'.RD, $LOC->now, $this->template);
  317. $this->log_item("Parse Current Time Variables");
  318. /** -------------------------------------
  319. /** Is the main template cached?
  320. /** -------------------------------------*/
  321. // If a cache file exists for the primary template
  322. // there is no reason to go further.
  323. // However we do need to fetch any subtemplates
  324. if ($this->cache_status == 'CURRENT' AND $sub == FALSE)
  325. {
  326. $this->log_item("Cached Template Used");
  327. $this->template = $this->parse_nocache($this->template);
  328. /** -------------------------------------
  329. /** Smite Our Enemies: Advanced Conditionals
  330. /** -------------------------------------*/
  331. if (stristr($this->template, LD.'if'))
  332. {
  333. $this->template = $this->advanced_conditionals($this->template);
  334. }
  335. $this->log_item("Conditionals Parsed, Processing Sub Templates");
  336. $this->final_template = $this->template;
  337. $this->process_sub_templates($this->template);
  338. return;
  339. }
  340. // Remove whitespace from variables.
  341. // This helps prevent errors, particularly if PHP is used in a template
  342. $this->template = preg_replace("/".LD."\s*(\S+)\s*".RD."/U", LD."\\1".RD, $this->template);
  343. /** -------------------------------------
  344. /** Parse Input Stage PHP
  345. /** -------------------------------------*/
  346. if ($this->parse_php == TRUE AND $this->php_parse_location == 'input' AND $this->cache_status != 'CURRENT')
  347. {
  348. $this->log_item("Parsing PHP on Input");
  349. $this->template = $this->parse_template_php($this->template);
  350. }
  351. /** -------------------------------------
  352. /** Smite Our Enemies: Conditionals
  353. /** -------------------------------------*/
  354. $this->log_item("Parsing Segment, Embed, and Global Vars Conditionals");
  355. $this->template = $this->segment_conditionals($this->template);
  356. $this->template = $this->array_conditionals($this->template, $this->embed_vars);
  357. $this->template = $this->array_conditionals($this->template, $this->global_vars);
  358. /** -------------------------------------
  359. /** Set global variable assignment
  360. /** -------------------------------------*/
  361. if (preg_match_all("/".LD."assign_variable:(.+?)=([\"\'])([^\\2]*?)\\2".RD."/i", $this->template, $matches))
  362. {
  363. $this->log_item("Processing Assigned Variables: ".trim(implode('|', $matches['1'])));
  364. for ($j = 0; $j < count($matches['0']); $j++)
  365. {
  366. $this->template = str_replace($matches['0'][$j], "", $this->template);
  367. $this->template = str_replace(LD.$matches['1'][$j].RD, $matches['3'][$j], $this->template);
  368. }
  369. }
  370. /** -------------------------------------
  371. /** Process the template
  372. /** -------------------------------------*/
  373. // Replace forward slashes with entity to prevent preg_replace errors.
  374. $this->template = str_replace('/', SLASH, $this->template);
  375. // Fetch installed modules and plugins if needed
  376. if (count($this->modules) == 0)
  377. {
  378. $this->fetch_modules();
  379. }
  380. if (count($this->plugins) == 0)
  381. {
  382. $this->fetch_plugins();
  383. }
  384. // Parse the template.
  385. $this->log_item(" - Beginning Tag Processing - ");
  386. while (is_int(strpos($this->template, LD.'exp:')))
  387. {
  388. // Initialize values between loops
  389. $this->tag_data = array();
  390. $this->var_single = array();
  391. $this->var_cond = array();
  392. $this->var_pair = array();
  393. $this->loop_count = 0;
  394. $this->log_item("Parsing Tags in Template");
  395. // Run the template parser
  396. $this->parse_template();
  397. $this->log_item("Processing Tags");
  398. // Run the class/method handler
  399. $this->class_handler();
  400. if ($this->cease_processing === TRUE)
  401. {
  402. return;
  403. }
  404. }
  405. $this->log_item(" - End Tag Processing - ");
  406. // Decode forward slash entities back to ascii
  407. $this->template = str_replace(SLASH, '/', $this->template);
  408. /** -------------------------------------
  409. /** Parse Output Stage PHP
  410. /** -------------------------------------*/
  411. if ($this->parse_php == TRUE AND $this->php_parse_location == 'output' AND $this->cache_status != 'CURRENT')
  412. {
  413. $this->log_item("Parsing PHP on Output");
  414. $this->template = $this->parse_template_php($this->template);
  415. }
  416. /** -------------------------------------
  417. /** Write the cache file if needed
  418. /** -------------------------------------*/
  419. if ($this->cache_status == 'EXPIRED')
  420. {
  421. $this->template = $FNS->insert_action_ids($this->template);
  422. $this->write_cache_file($this->cache_hash, $this->template, 'template');
  423. }
  424. /** -------------------------------------
  425. /** Parse Our Uncacheable Forms
  426. /** -------------------------------------*/
  427. $this->template = $this->parse_nocache($this->template);
  428. /** -------------------------------------
  429. /** Smite Our Enemies: Advanced Conditionals
  430. /** -------------------------------------*/
  431. if (stristr($this->template, LD.'if'))
  432. {
  433. $this->log_item("Processing Advanced Conditionals");
  434. $this->template = $this->advanced_conditionals($this->template);
  435. }
  436. // <?php This fixes a BBEdit bug that makes the list of function not work right. Seems related to the PHP declarations above.
  437. /** -------------------------------------
  438. /** Build finalized template
  439. /** -------------------------------------*/
  440. // We only do this on the first pass.
  441. // The sub-template routine will insert embedded
  442. // templates into the master template
  443. if ($sub == FALSE)
  444. {
  445. $this->final_template = $this->template;
  446. $this->process_sub_templates($this->template);
  447. }
  448. }
  449. /* END */
  450. /** -------------------------------------
  451. /** Parse embedded sub-templates
  452. /** -------------------------------------*/
  453. function process_sub_templates($template)
  454. {
  455. global $REGX, $FNS, $LANG, $PREFS, $DB;
  456. /** -------------------------------------
  457. /** Match all {embed=bla/bla} tags
  458. /** -------------------------------------*/
  459. $matches = array();
  460. if ( ! preg_match_all("/(".LD."embed\s*=)(.*?)".RD."/s", $template, $matches))
  461. {
  462. return;
  463. }
  464. /** -------------------------------------
  465. /** Loop until we have parsed all sub-templates
  466. /** -------------------------------------*/
  467. // For each embedded tag we encounter we'll run the template parsing
  468. // function - AND - through the beauty of recursive functions we
  469. // will also call THIS function as well, allowing us to parse
  470. // infinitely nested sub-templates in one giant loop o' love
  471. $this->log_item(" - Processing Sub Templates (Depth: ".($this->depth+1).") - ");
  472. $i = 0;
  473. $this->depth++;
  474. $this->log_item("List of Embeds: ".str_replace(array('"', "'"), '', trim(implode(',', $matches['2']))));
  475. // re-match the full tag of each if necessary before we start processing
  476. // necessary evil in case template globals are used inside the embed tag,
  477. // doing this within the processing loop will result in leaving unparsed
  478. // embed tags e.g. {embed="foo/bar" var="{global_var}/{custom_field}"}
  479. $temp = $template;
  480. foreach ($matches[2] as $key => $val)
  481. {
  482. if (strpos($val, LD) !== FALSE)
  483. {
  484. $matches[0][$key] = $FNS->full_tag($matches[0][$key], $temp);
  485. $matches[2][$key] = substr(str_replace($matches[1][$key], '', $matches[0][$key]), 0, -1);
  486. $temp = str_replace($matches[0][$key], '', $temp);
  487. }
  488. }
  489. foreach($matches['2'] as $key => $val)
  490. {
  491. $parts = preg_split("/\s+/", $val, 2);
  492. $this->embed_vars = (isset($parts['1'])) ? $FNS->assign_parameters($parts['1']) : array();
  493. if ($this->embed_vars === FALSE)
  494. {
  495. $this->embed_vars = array();
  496. }
  497. $val = $REGX->trim_slashes($REGX->strip_quotes($parts['0']));
  498. if ( ! stristr($val, '/'))
  499. {
  500. continue;
  501. }
  502. $ex = explode("/", trim($val));
  503. if (count($ex) != 2)
  504. {
  505. continue;
  506. }
  507. /** ----------------------------------
  508. /** Determine Site
  509. /** ----------------------------------*/
  510. $site_id = $PREFS->ini('site_id');
  511. if (stristr($ex[0], ':'))
  512. {
  513. $name = substr($ex[0], 0, strpos($ex[0], ':'));
  514. if ($PREFS->ini('multiple_sites_enabled') == 'y')
  515. {
  516. if (sizeof($this->sites) == 0)
  517. {
  518. $sites_query = $DB->query("SELECT site_id, site_name FROM exp_sites");
  519. foreach($sites_query->result as $row)
  520. {
  521. $this->sites[$row['site_id']] = $row['site_name'];
  522. }
  523. }
  524. $site_id = array_search($name, $this->sites);
  525. if (empty($site_id))
  526. {
  527. $site_id = $PREFS->ini('site_id');
  528. }
  529. }
  530. $ex[0] = str_replace($name.':', '', $ex[0]);
  531. }
  532. /** ----------------------------------
  533. /** Loop Prevention
  534. /** ----------------------------------*/
  535. /* -------------------------------------------
  536. /* Hidden Configuration Variable
  537. /* - template_loop_prevention => 'n'
  538. Whether or not loop prevention is enabled - y/n
  539. /* -------------------------------------------*/
  540. if (substr_count($this->templates_sofar, '|'.$site_id.':'.$ex['0'].'/'.$ex['1'].'|') > 1 && $PREFS->ini('template_loop_prevention') != 'n')
  541. {
  542. $this->final_template = ($PREFS->ini('debug') >= 1) ? str_replace('%s', $ex['0'].'/'.$ex['1'], $LANG->line('template_loop')) : "";
  543. return;
  544. }
  545. /** ----------------------------------
  546. /** Process Subtemplate
  547. /** ----------------------------------*/
  548. $this->log_item("Processing Sub Template: ".$ex['0']."/".$ex['1']);
  549. $this->process_template($ex['0'], $ex['1'], TRUE, $site_id);
  550. $this->final_template = str_replace($matches['0'][$key], $this->template, $this->final_template);
  551. $this->embed_type = '';
  552. // Here we go again! Wheeeeeee.....
  553. $this->process_sub_templates($this->template);
  554. // pull the subtemplate tracker back a level to the parent template
  555. $this->templates_sofar = substr($this->templates_sofar, 0, - strlen('|'.$site_id.':'.$ex[0].'/'.$ex[1].'|'));
  556. }
  557. $this->depth--;
  558. if ($this->depth == 0)
  559. {
  560. $this->templates_sofar = '';
  561. }
  562. }
  563. /* END */
  564. /** -------------------------------------
  565. /** Parse the template
  566. /** -------------------------------------*/
  567. function parse_template()
  568. {
  569. global $FNS;
  570. while (TRUE)
  571. {
  572. // Make a "floating" copy of the template which we'll progressively slice into pieces with each loop
  573. $this->fl_tmpl = $this->template;
  574. // Identify the string position of the first occurence of a matched tag
  575. $this->in_point = strpos($this->fl_tmpl, LD.'exp:');
  576. // If the above variable returns false we are done looking for tags
  577. // This single conditional keeps the template engine from spiraling
  578. // out of control in an infinite loop.
  579. if (FALSE === $this->in_point)
  580. {
  581. break;
  582. }
  583. else
  584. {
  585. /** ------------------------------------------
  586. /** Process the tag data
  587. /** ------------------------------------------*/
  588. // These REGEXs parse out the various components contained in any given tag.
  589. // Grab the opening portion of the tag: {exp:some:tag param="value" param="value"}
  590. if ( ! preg_match("/".LD.'exp:'.".*?".RD."/s", $this->fl_tmpl, $matches))
  591. {
  592. $this->template = preg_replace("/".LD.'exp:'.".*?$/", '', $this->template);
  593. break;
  594. }
  595. $this->log_item("Tag: ".$matches['0']);
  596. // Checking for variables/tags embedded within tags
  597. // {exp:weblog:entries weblog="{master_weblog_name}"}
  598. if (stristr(substr($matches['0'], 1), LD) !== false)
  599. {
  600. $matches['0'] = $FNS->full_tag($matches['0']);
  601. }
  602. $raw_tag = preg_replace("/(\r\n)|(\r)|(\n)|(\t)/", ' ', $matches['0']);
  603. $tag_length = strlen($raw_tag);
  604. $data_start = $this->in_point + $tag_length;
  605. $tag = trim(substr($raw_tag, 1, -1));
  606. $args = trim((preg_match("/\s+.*/", $tag, $matches))) ? $matches['0'] : '';
  607. $tag = trim(str_replace($args, '', $tag));
  608. $cur_tag_close = LD.SLASH.$tag.RD;
  609. // -----------------------------------------
  610. // Assign the class name/method name and any parameters
  611. $class = $this->assign_class(substr($tag, strlen('exp') + 1));
  612. $args = $FNS->assign_parameters($args);
  613. // standardized mechanism for "search" type parameters get some extra lovin'
  614. $search_fields = array();
  615. if ($args !== FALSE)
  616. {
  617. foreach ($args as $key => $val)
  618. {
  619. if (strncmp($key, 'search:', 7) == 0)
  620. {
  621. $search_fields[substr($key, 7)] = str_replace(SLASH, '/', $val);
  622. }
  623. }
  624. }
  625. // Trim the floating template, removing the tag we just parsed.
  626. $this->fl_tmpl = substr($this->fl_tmpl, $this->in_point + $tag_length);
  627. $out_point = strpos($this->fl_tmpl, $cur_tag_close);
  628. // Do we have a tag pair?
  629. if (FALSE !== $out_point)
  630. {
  631. // Assign the data contained between the opening/closing tag pair
  632. $this->log_item("Closing Tag Found");
  633. $block = substr($this->template, $data_start, $out_point);
  634. // Fetch the "no_results" data
  635. $no_results = '';
  636. $no_results_block = '';
  637. if (preg_match("/".LD."if no_results".RD."(.*?)".LD.SLASH."if".RD."/s", $block, $match))
  638. {
  639. // Match the entirety of the conditional, dude. Bad Rick!
  640. if (stristr($match['1'], LD.'if'))
  641. {
  642. $match['0'] = $FNS->full_tag($match['0'], $block, LD.'if', LD.SLASH."if".RD);
  643. }
  644. $no_results = substr($match['0'], strlen(LD."if no_results".RD), -strlen(LD.SLASH."if".RD));
  645. $no_results_block = $match['0'];
  646. }
  647. // Define the entire "chunk" - from the left edge of the opening tag
  648. // to the right edge of closing tag.
  649. $out_point = $out_point + $tag_length + strlen($cur_tag_close);
  650. $chunk = substr($this->template, $this->in_point, $out_point);
  651. }
  652. else
  653. {
  654. // Single tag...
  655. $this->log_item("No Closing Tag");
  656. $block = ''; // Single tags don't contain data blocks
  657. $no_results = '';
  658. $no_results_block = '';
  659. // Define the entire opening tag as a "chunk"
  660. $chunk = substr($this->template, $this->in_point, $tag_length);
  661. }
  662. // Strip the "chunk" from the template, replacing it with a unique marker.
  663. if (stristr($raw_tag, 'random'))
  664. {
  665. $this->template = preg_replace("|".preg_quote($chunk)."|s", 'M'.$this->loop_count.$this->marker, $this->template, 1);
  666. }
  667. else
  668. {
  669. $this->template = str_replace($chunk, 'M'.$this->loop_count.$this->marker, $this->template);
  670. }
  671. $cfile = md5($chunk); // This becomes the name of the cache file
  672. // Build a multi-dimensional array containing all of the tag data we've assembled
  673. $this->tag_data[$this->loop_count]['tag'] = $raw_tag;
  674. $this->tag_data[$this->loop_count]['class'] = $class['0'];
  675. $this->tag_data[$this->loop_count]['method'] = $class['1'];
  676. $this->tag_data[$this->loop_count]['tagparts'] = $class;
  677. $this->tag_data[$this->loop_count]['params'] = $args;
  678. $this->tag_data[$this->loop_count]['chunk'] = $chunk; // Matched data block - including opening/closing tags
  679. $this->tag_data[$this->loop_count]['block'] = $block; // Matched data block - no tags
  680. $this->tag_data[$this->loop_count]['cache'] = $this->cache_status($cfile, $args);
  681. $this->tag_data[$this->loop_count]['cfile'] = $cfile;
  682. $this->tag_data[$this->loop_count]['no_results'] = $no_results;
  683. $this->tag_data[$this->loop_count]['no_results_block'] = $no_results_block;
  684. $this->tag_data[$this->loop_count]['search_fields'] = $search_fields;
  685. } // END IF
  686. // Increment counter
  687. $this->loop_count++;
  688. } // END WHILE
  689. }
  690. /* END */
  691. /** -------------------------------------
  692. /** Class/Method handler
  693. /** -------------------------------------*/
  694. function class_handler()
  695. {
  696. global $FNS, $TMPL, $DB, $PREFS;
  697. $classes = array();
  698. // Fill an array with the names of all the classes that we previously extracted from the tags
  699. for ($i = 0; $i < count($this->tag_data); $i++)
  700. {
  701. // Should we use the tag cache file?
  702. if ($this->tag_data[$i]['cache'] == 'CURRENT')
  703. {
  704. // If so, replace the marker in the tag with the cache data
  705. $this->log_item("Tag Cached and Cache is Current");
  706. $this->replace_marker($i, $this->get_cache_file($this->tag_data[$i]['cfile']));
  707. }
  708. else
  709. {
  710. // Is a module or plug-in being requested?
  711. if ( ! in_array($this->tag_data[$i]['class'] , $this->modules))
  712. {
  713. if ( ! in_array($this->tag_data[$i]['class'] , $this->plugins))
  714. {
  715. global $LANG, $PREFS, $OUT;
  716. $this->log_item("Invalid Tag");
  717. if ($PREFS->ini('debug') >= 1)
  718. {
  719. if ($this->tag_data[$i]['tagparts']['0'] == $this->tag_data[$i]['tagparts']['1'] &&
  720. ! isset($this->tag_data[$i]['tagparts']['2']))
  721. {
  722. unset($this->tag_data[$i]['tagparts']['1']);
  723. }
  724. $error = $LANG->line('error_tag_syntax');
  725. $error .= '<br /><br />';
  726. $error .= htmlspecialchars(LD);
  727. $error .= 'exp:'.implode(':', $this->tag_data[$i]['tagparts']);
  728. $error .= htmlspecialchars(RD);
  729. $error .= '<br /><br />';
  730. $error .= $LANG->line('error_fix_syntax');
  731. $OUT->fatal_error($error);
  732. }
  733. else
  734. return false;
  735. }
  736. else
  737. {
  738. $classes[] = 'pi.'.$this->tag_data[$i]['class'];
  739. $this->log_item("Plugin Tag: ".ucfirst($this->tag_data[$i]['class']).'/'.$this->tag_data[$i]['method']);
  740. }
  741. }
  742. else
  743. {
  744. $classes[] = $this->tag_data[$i]['class'];
  745. $this->log_item("Module Tag: ".ucfirst($this->tag_data[$i]['class']).'/'.$this->tag_data[$i]['method']);
  746. }
  747. }
  748. }
  749. // Remove duplicate class names and re-order the array
  750. $classes = array_values(array_unique($classes));
  751. // Dynamically require the file that contains each class
  752. $this->log_item("Including Files for Tag and Modules");
  753. for ($i = 0; $i < count($classes); $i++)
  754. {
  755. // But before we do, make sure it hasn't already been included...
  756. if ( ! class_exists($classes[$i]))
  757. {
  758. if (substr($classes[$i], 0, 3) == 'pi.')
  759. {
  760. require_once PATH_PI.$classes[$i].EXT;
  761. }
  762. else
  763. {
  764. require_once PATH_MOD.$classes[$i].'/mod.'.$classes[$i].EXT;
  765. }
  766. }
  767. }
  768. /** -----------------------------------
  769. /** Only Retrieve Data if Not Done Before and Modules Being Called
  770. /** -----------------------------------*/
  771. if (sizeof($this->module_data) == 0 && sizeof(array_intersect($this->modules, $classes)) > 0)
  772. {
  773. $query = $DB->query("SELECT module_version, module_name FROM exp_modules");
  774. foreach($query->result as $row)
  775. {
  776. $this->module_data[$row['module_name']] = array('version' => $row['module_version']);
  777. }
  778. }
  779. // Final data processing
  780. // Loop through the master array containing our extracted template data
  781. $this->log_item("Beginning Final Tag Data Processing");
  782. reset($this->tag_data);
  783. for ($i = 0; $i < count($this->tag_data); $i++)
  784. {
  785. if ($this->tag_data[$i]['cache'] != 'CURRENT')
  786. {
  787. $this->log_item("Calling Class/Method: ".ucfirst($this->tag_data[$i]['class'])."/".$this->tag_data[$i]['method']);
  788. /* ---------------------------------
  789. /* Plugin as Parameter
  790. /*
  791. /* - Example: weblog="{exp:some_plugin}"
  792. /* - A bit of a hidden feature. Has been tested but not quite
  793. /* ready to say it is ready for prime time as I might want to
  794. /* move it to earlier in processing so that if there are
  795. /* multiple plugins being used as parameters it is only called
  796. /* once instead of for every single parameter. - Paul
  797. /* ---------------------------------*/
  798. if (substr_count($this->tag_data[$i]['tag'], LD.'exp') > 1 && isset($this->tag_data[$i]['params']['parse']) && $this->tag_data[$i]['params']['parse'] == 'inward')
  799. {
  800. foreach($this->tag_data[$i]['params'] as $name => $param)
  801. {
  802. if (stristr($this->tag_data[$i]['params'][$name], LD.'exp'))
  803. {
  804. $this->log_item("Plugin in Parameter, Processing Plugin First");
  805. $TMPL2 = $FNS->clone_object($this);
  806. while (is_int(strpos($TMPL2->tag_data[$i]['params'][$name], LD.'exp:')))
  807. {
  808. $TMPL = new Template();
  809. $TMPL->start_microtime = $this->start_microtime;
  810. $TMPL->template = $TMPL2->tag_data[$i]['params'][$name];
  811. $TMPL->tag_data = array();
  812. $TMPL->var_single = array();
  813. $TMPL->var_cond = array();
  814. $TMPL->var_pair = array();
  815. $TMPL->plugins = $TMPL2->plugins;
  816. $TMPL->modules = $TMPL2->modules;
  817. $TMPL->parse_template();
  818. $TMPL->class_handler();
  819. $TMPL->loop_count = 0;
  820. $TMPL2->tag_data[$i]['params'][$name] = $TMPL->template;
  821. $TMPL2->log = array_merge($TMPL2->log, $TMPL->log);
  822. }
  823. foreach (get_object_vars($TMPL2) as $key => $value)
  824. {
  825. $this->$key = $value;
  826. }
  827. unset($TMPL2);
  828. $TMPL = $this;
  829. }
  830. }
  831. }
  832. /** ---------------------------------
  833. /** Nested Plugins...
  834. /** ---------------------------------*/
  835. if (in_array($this->tag_data[$i]['class'] , $this->plugins) && strpos($this->tag_data[$i]['block'], LD.'exp:') !== false)
  836. {
  837. if ( ! isset($this->tag_data[$i]['params']['parse']) OR $this->tag_data[$i]['params']['parse'] != 'inward')
  838. {
  839. $this->log_item("Nested Plugins in Tag, Parsing Outward First");
  840. $TMPL2 = $FNS->clone_object($this);
  841. while (is_int(strpos($TMPL2->tag_data[$i]['block'], LD.'exp:')))
  842. {
  843. $TMPL = new Template();
  844. $TMPL->start_microtime = $this->start_microtime;
  845. $TMPL->template = $TMPL2->tag_data[$i]['block'];
  846. $TMPL->tag_data = array();
  847. $TMPL->var_single = array();
  848. $TMPL->var_cond = array();
  849. $TMPL->var_pair = array();
  850. $TMPL->plugins = $TMPL2->plugins;
  851. $TMPL->modules = $TMPL2->modules;
  852. $TMPL->parse_template();
  853. $TMPL->class_handler();
  854. $TMPL->loop_count = 0;
  855. $TMPL2->tag_data[$i]['block'] = $TMPL->template;
  856. $TMPL2->log = array_merge($TMPL2->log, $TMPL->log);
  857. }
  858. foreach (get_object_vars($TMPL2) as $key => $value)
  859. {
  860. $this->$key = $value;
  861. }
  862. unset($TMPL2);
  863. $TMPL = $this;
  864. }
  865. }
  866. // Assign the data chunk, parameters
  867. // We moved the no_results_block here because of nested tags. The first
  868. // parsed tag has priority for that conditional.
  869. $this->tagdata = str_replace($this->tag_data[$i]['no_results_block'], '', $this->tag_data[$i]['block']);
  870. $this->tagparams = $this->tag_data[$i]['params'];
  871. $this->tagchunk = $this->tag_data[$i]['chunk'];
  872. $this->tagproper = $this->tag_data[$i]['tag'];
  873. $this->tagparts = $this->tag_data[$i]['tagparts'];
  874. $this->no_results = $this->tag_data[$i]['no_results'];
  875. $this->search_fields = $this->tag_data[$i]['search_fields'];
  876. /** -------------------------------------
  877. /** Assign Sites for Tag
  878. /** -------------------------------------*/
  879. $this->_fetch_site_ids();
  880. /** -------------------------------------
  881. /** Relationship Data Pulled Out
  882. /** -------------------------------------*/
  883. // If the weblog:entries tag or search:search_results is being called
  884. // we need to extract any relationship data that might be present.
  885. // Note: This needs to happen before extracting the variables
  886. // in the tag so it doesn't get confused as to which entry the
  887. // variables belong to.
  888. if (($this->tag_data[$i]['class'] == 'weblog' AND $this->tag_data[$i]['method'] == 'entries')
  889. OR ($this->tag_data[$i]['class'] == 'search' AND $this->tag_data[$i]['method'] == 'search_results'))
  890. {
  891. $this->tagdata = $this->assign_relationship_data($this->tagdata);
  892. }
  893. // Fetch the variables for this particular tag
  894. $vars = $FNS->assign_variables($this->tag_data[$i]['block']);
  895. if (count($this->related_markers) > 0)
  896. {
  897. foreach ($this->related_markers as $mkr)
  898. {
  899. if ( ! isset($vars['var_single'][$mkr]))
  900. {
  901. $vars['var_single'][$mkr] = $mkr;
  902. }
  903. }
  904. $this->related_markers = array();
  905. }
  906. $this->var_single = $vars['var_single'];
  907. $this->var_pair = $vars['var_pair'];
  908. // Redundant see above loop for related_markers - R.S.
  909. //if ($this->related_id != '')
  910. //{
  911. // $this->var_single[$this->related_id] = $this->related_id;
  912. // $this->related_id = '';
  913. //}
  914. // Assign Conditional Variables
  915. if ( ! in_array($this->tag_data[$i]['class'],$this->native_modules))
  916. {
  917. $this->var_cond = $FNS->assign_conditional_variables($this->tag_data[$i]['block'], SLASH, LD, RD);
  918. }
  919. // Assign the class name and method name
  920. $class_name = ucfirst($this->tag_data[$i]['class']);
  921. if ($class_name == 'Commerce')
  922. {
  923. // The Commerce module is special in that it has its own modules and its
  924. // constructor handles everything for us
  925. $meth_name = 'commerce';
  926. }
  927. else
  928. {
  929. $meth_name = $this->tag_data[$i]['method'];
  930. }
  931. // Dynamically instantiate the class.
  932. // If module, only if it is installed...
  933. if (in_array($this->tag_data[$i]['class'], $this->modules) && ! isset($this->module_data[$class_name]))
  934. {
  935. $this->log_item("Problem Processing Module: Module Not Installed");
  936. }
  937. else
  938. {
  939. $this->log_item(" -> Class Called: ".$class_name);
  940. $EE = new $class_name();
  941. }
  942. /** ----------------------------------
  943. /** Does method exist? Is This A Module and Is It Installed?
  944. /** ----------------------------------*/
  945. if ((in_array($this->tag_data[$i]['class'], $this->modules) && ! isset($this->module_data[$class_name])) OR ! method_exists($EE, $meth_name))
  946. {
  947. global $LANG, $PREFS, $OUT;
  948. $this->log_item("Tag Not Processed: Method Inexistent or Module Not Installed");
  949. if ($PREFS->ini('debug') >= 1)
  950. {
  951. if ($this->tag_data[$i]['tagparts']['0'] == $this->tag_data[$i]['tagparts']['1'] &&
  952. ! isset($this->tag_data[$i]['tagparts']['2']))
  953. {
  954. unset($this->tag_data[$i]['tagparts']['1']);
  955. }
  956. $error = $LANG->line('error_tag_module_processing');
  957. $error .= '<br /><br />';
  958. $error .= htmlspecialchars(LD);
  959. $error .= 'exp:'.implode(':', $this->tag_data[$i]['tagparts']);
  960. $error .= htmlspecialchars(RD);
  961. $error .= '<br /><br />';
  962. $error .= str_replace('%x', $this->tag_data[$i]['class'], str_replace('%y', $meth_name, $LANG->line('error_fix_module_processing')));
  963. $OUT->fatal_error($error);
  964. }
  965. else
  966. return;
  967. }
  968. /*
  969. OK, lets grab the data returned from the class.
  970. First, however, lets determine if the tag has one or two segments.
  971. If it only has one, we don't want to call the constructor again since
  972. it was already called during instantiation.
  973. Note: If it only has one segment, only the object constructor will be called.
  974. Since constructors can't return a value just by initialializing the object
  975. the output of the class must be assigned to a variable called $this->return_data
  976. */
  977. $this->log_item(" -> Method Called: ".$meth_name);
  978. if (strtolower($class_name) == $meth_name)
  979. {
  980. $return_data = (isset($EE->return_data)) ? $EE->return_data : '';
  981. }
  982. else
  983. {
  984. $return_data = $EE->$meth_name();
  985. }
  986. /** ----------------------------------
  987. /** 404 Page Triggered, Cease All Processing of Tags From Now On
  988. /** ----------------------------------*/
  989. if ($this->cease_processing === TRUE)
  990. {
  991. return;
  992. }
  993. $this->log_item(" -> Data Returned");
  994. // Write cache file if needed
  995. if ($this->tag_data[$i]['cache'] == 'EXPIRED')
  996. {
  997. $this->write_cache_file($this->tag_data[$i]['cfile'], $return_data);
  998. }
  999. // Replace the temporary markers we added earlier with the fully parsed data
  1000. $this->replace_marker($i, $return_data);
  1001. // Initialize data in case there are susequent loops
  1002. $this->var_single = array();
  1003. $this->var_cond = array();
  1004. $this->var_pair = array();
  1005. unset($return_data);
  1006. unset($class_name);
  1007. unset($meth_name);
  1008. unset($EE);
  1009. }
  1010. }
  1011. }
  1012. /* END */
  1013. /** -------------------------------------
  1014. /** Assign the related data
  1015. /** -------------------------------------*/
  1016. // Weblog entries can have related entries embedded within them.
  1017. // We'll extract the related tag data, stash it away in a…

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