PageRenderTime 52ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

/system/expressionengine/libraries/Functions.php

https://bitbucket.org/studiobreakfast/sync
PHP | 2978 lines | 1902 code | 433 blank | 643 comment | 379 complexity | c3d150b95d4358ee3c721df715806c5c MD5 | raw file
  1. <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3. * ExpressionEngine - by EllisLab
  4. *
  5. * @package ExpressionEngine
  6. * @author EllisLab Dev Team
  7. * @copyright Copyright (c) 2003 - 2012, EllisLab, Inc.
  8. * @license http://expressionengine.com/user_guide/license.html
  9. * @link http://expressionengine.com
  10. * @since Version 2.0
  11. * @filesource
  12. */
  13. // ------------------------------------------------------------------------
  14. /**
  15. * ExpressionEngine Core Functions Class
  16. *
  17. * @package ExpressionEngine
  18. * @subpackage Core
  19. * @category Core
  20. * @author EllisLab Dev Team
  21. * @link http://expressionengine.com
  22. */
  23. class EE_Functions {
  24. var $seed = FALSE; // Whether we've seeded our rand() function. We only seed once per script execution
  25. var $cached_url = array();
  26. var $cached_path = array();
  27. var $cached_index = array();
  28. var $cached_captcha = '';
  29. var $template_map = array();
  30. var $template_type = '';
  31. var $action_ids = array();
  32. var $file_paths = array();
  33. var $conditional_debug = FALSE;
  34. var $catfields = array();
  35. /**
  36. * Constructor
  37. */
  38. function __construct()
  39. {
  40. // Make a local reference to the ExpressionEngine super object
  41. $this->EE =& get_instance();
  42. }
  43. // --------------------------------------------------------------------
  44. /**
  45. * Fetch base site index
  46. *
  47. * @access public
  48. * @param bool
  49. * @param bool
  50. * @return string
  51. */
  52. function fetch_site_index($add_slash = FALSE, $sess_id = TRUE)
  53. {
  54. if (isset($this->cached_index[$add_slash.$sess_id.$this->template_type]))
  55. {
  56. return $this->cached_index[$add_slash.$sess_id.$this->template_type];
  57. }
  58. $url = $this->EE->config->slash_item('site_url');
  59. $url .= $this->EE->config->item('site_index');
  60. if ($this->EE->config->item('force_query_string') == 'y')
  61. {
  62. $url .= '?';
  63. }
  64. if (is_object($this->EE->session) && $this->EE->session->userdata('session_id') != '' && REQ != 'CP' && $sess_id == TRUE &&
  65. $this->EE->config->item('user_session_type') != 'c' && $this->template_type == 'webpage')
  66. {
  67. $url .= "/S=".$this->EE->session->userdata('session_id')."/";
  68. }
  69. if ($add_slash == TRUE)
  70. {
  71. if (substr($url, -1) != '/')
  72. {
  73. $url .= "/";
  74. }
  75. }
  76. $this->cached_index[$add_slash.$sess_id.$this->template_type] = $url;
  77. return $url;
  78. }
  79. // --------------------------------------------------------------------
  80. /**
  81. * Create a custom URL
  82. *
  83. * The input to this function is parsed and added to the
  84. * full site URL to create a full URL/URI
  85. *
  86. * @access public
  87. * @param string
  88. * @param bool
  89. * @return string
  90. */
  91. function create_url($segment, $sess_id = TRUE)
  92. {
  93. // Since this function can be used via a callback
  94. // we'll fetch the segment if it's an array
  95. if (is_array($segment))
  96. {
  97. $segment = $segment[1];
  98. }
  99. if (isset($this->cached_url[$segment]))
  100. {
  101. return $this->cached_url[$segment];
  102. }
  103. $full_segment = $segment;
  104. $segment = str_replace(array("'", '"'), '', $segment);
  105. $segment = preg_replace("/(.+?(\/))index(\/)(.*?)/", "\\1\\2", $segment);
  106. $segment = preg_replace("/(.+?(\/))index$/", "\\1", $segment);
  107. // These are exceptions to the normal path rules
  108. if ($segment == '' OR strtolower($segment) == 'site_index')
  109. {
  110. return $this->fetch_site_index();
  111. }
  112. if (strtolower($segment) == 'logout')
  113. {
  114. $qs = ($this->EE->config->item('force_query_string') == 'y') ? '' : '?';
  115. return $this->fetch_site_index(0, 0).$qs.'ACT='.$this->fetch_action_id('Member', 'member_logout');
  116. }
  117. // END Specials
  118. // Load the string helper
  119. $this->EE->load->helper('string');
  120. $base = $this->fetch_site_index(0, $sess_id).'/'.trim_slashes($segment);
  121. $out = $this->remove_double_slashes($base);
  122. $this->cached_url[$full_segment] = $out;
  123. return $out;
  124. }
  125. // --------------------------------------------------------------------
  126. /**
  127. * Creates a url for Pages links
  128. *
  129. * @access public
  130. * @return string
  131. */
  132. function create_page_url($base_url, $segment, $trailing_slash = FALSE)
  133. {
  134. // Load the string helper
  135. $this->EE->load->helper('string');
  136. if ($this->EE->config->item('force_query_string') == 'y')
  137. {
  138. if (strpos($base_url, $this->EE->config->item('index_page') . '/') !== FALSE)
  139. {
  140. $base_url = rtrim($base_url, '/');
  141. }
  142. $base_url .= '?';
  143. }
  144. $base = $base_url.'/'.trim_slashes($segment);
  145. if (substr($base, -1) != '/' && $trailing_slash == TRUE)
  146. {
  147. $base .= '/';
  148. }
  149. $out = $this->remove_double_slashes($base);
  150. return $out;
  151. }
  152. // --------------------------------------------------------------------
  153. /**
  154. * Fetch site index with URI query string
  155. *
  156. * @access public
  157. * @return string
  158. */
  159. function fetch_current_uri()
  160. {
  161. return rtrim($this->remove_double_slashes($this->fetch_site_index(1).$this->EE->uri->uri_string), '/');
  162. }
  163. // --------------------------------------------------------------------
  164. /**
  165. * Prep Query String
  166. *
  167. * This function checks to see if "Force Query Strings" is on.
  168. * If so it adds a question mark to the URL if needed
  169. *
  170. * @access public
  171. * @param string
  172. * @return string
  173. */
  174. function prep_query_string($str)
  175. {
  176. if (stristr($str, '.php') && substr($str, -7) == '/index/')
  177. {
  178. $str = substr($str, 0, -6);
  179. }
  180. if (strpos($str, '?') === FALSE && $this->EE->config->item('force_query_string') == 'y')
  181. {
  182. if (stristr($str, '.php'))
  183. {
  184. $str = preg_replace("#(.+?)\.php(.*?)#", "\\1.php?\\2", $str);
  185. }
  186. else
  187. {
  188. $str .= "?";
  189. }
  190. }
  191. return $str;
  192. }
  193. // --------------------------------------------------------------------
  194. /**
  195. * Convert EE Tags to Entities
  196. *
  197. * @access public
  198. * @param string
  199. * @param bool
  200. * @return string
  201. */
  202. function encode_ee_tags($str, $convert_curly = FALSE)
  203. {
  204. if ($str != '' && strpos($str, '{') !== FALSE)
  205. {
  206. if ($convert_curly === TRUE)
  207. {
  208. $str = str_replace(array('{', '}'), array('&#123;', '&#125;'), $str);
  209. }
  210. else
  211. {
  212. $str = preg_replace("/\{(\/){0,1}exp:(.+?)\}/", "&#123;\\1exp:\\2&#125;", $str);
  213. $str = str_replace(array('{exp:', '{/exp'), array('&#123;exp:', '&#123;\exp'), $str);
  214. $str = preg_replace("/\{embed=(.+?)\}/", "&#123;embed=\\1&#125;", $str);
  215. $str = preg_replace("/\{path:(.+?)\}/", "&#123;path:\\1&#125;", $str);
  216. $str = preg_replace("/\{redirect=(.+?)\}/", "&#123;redirect=\\1&#125;", $str);
  217. }
  218. }
  219. return $str;
  220. }
  221. // --------------------------------------------------------------------
  222. /**
  223. * Remove duplicate slashes from URL
  224. *
  225. * With all the URL/URI parsing/building, there is the potential
  226. * to end up with double slashes. This is a clean-up function.
  227. *
  228. * @access public
  229. * @param string
  230. * @return string
  231. */
  232. function remove_double_slashes($str)
  233. {
  234. return preg_replace("#(^|[^:])//+#", "\\1/", $str);
  235. }
  236. // --------------------------------------------------------------------
  237. /**
  238. * Extract path info
  239. *
  240. * We use this to extract the template group/template name
  241. * from path variables, like {some_var path="channel/index"}
  242. *
  243. * @access public
  244. * @param string
  245. * @return string
  246. */
  247. function extract_path($str)
  248. {
  249. if (preg_match("#=(.*)#", $str, $match))
  250. {
  251. $match[1] = trim($match[1], '}');
  252. if (isset($this->cached_path[$match[1]]))
  253. {
  254. return $this->cached_path[$match[1]];
  255. }
  256. // Load the string helper
  257. $this->EE->load->helper('string');
  258. $path = trim_slashes(str_replace(array("'",'"'), "", $match[1]));
  259. if (substr($path, -6) == 'index/')
  260. {
  261. $path = str_replace('/index', '', $path);
  262. }
  263. if (substr($path, -5) == 'index')
  264. {
  265. $path = str_replace('/index', '', $path);
  266. }
  267. $this->cached_path[$match[1]] = $path;
  268. return $path;
  269. }
  270. else
  271. {
  272. return 'SITE_INDEX';
  273. }
  274. }
  275. // --------------------------------------------------------------------
  276. /**
  277. * Replace variables
  278. *
  279. * @access public
  280. * @param string
  281. * @param string
  282. * @return string
  283. */
  284. function var_swap($str, $data)
  285. {
  286. if ( ! is_array($data))
  287. {
  288. return FALSE;
  289. }
  290. foreach ($data as $key => $val)
  291. {
  292. $str = str_replace('{'.$key.'}', $val, $str);
  293. }
  294. return $str;
  295. }
  296. // --------------------------------------------------------------------
  297. /**
  298. * Redirect
  299. *
  300. * @access public
  301. * @param string
  302. * @return void
  303. */
  304. function redirect($location, $method = FALSE)
  305. {
  306. // Remove hard line breaks and carriage returns
  307. $location = str_replace(array("\n", "\r"), '', $location);
  308. // Remove any and all line breaks
  309. while (stripos($location, '%0d') !== FALSE OR stripos($location, '%0a') !== FALSE)
  310. {
  311. $location = str_ireplace(array('%0d', '%0a'), '', $location);
  312. }
  313. $location = str_replace('&amp;', '&', $this->insert_action_ids($location));
  314. if (count($this->EE->session->flashdata))
  315. {
  316. // Ajax requests don't redirect - serve the flashdata
  317. if ($this->EE->input->is_ajax_request())
  318. {
  319. // We want the data that would be available for the next request
  320. $this->EE->session->_age_flashdata();
  321. $this->EE->load->library('javascript');
  322. die($this->EE->javascript->generate_json(
  323. $this->EE->session->flashdata));
  324. }
  325. }
  326. if ($method === FALSE)
  327. {
  328. $method = $this->EE->config->item('redirect_method');
  329. }
  330. switch($method)
  331. {
  332. case 'refresh' : header("Refresh: 0;url=$location");
  333. break;
  334. default : header("Location: $location");
  335. break;
  336. }
  337. exit;
  338. }
  339. // --------------------------------------------------------------------
  340. /**
  341. * Convert a string into an encrypted hash
  342. * DEPRECATED 2.0
  343. *
  344. * @access public
  345. * @param string
  346. * @return string
  347. */
  348. function hash($str)
  349. {
  350. $this->EE->load->library('logger');
  351. $this->EE->logger->deprecated('2.0', 'Security_helper::do_hash');
  352. $this->EE->load->helper('security');
  353. return do_hash($str);
  354. }
  355. // --------------------------------------------------------------------
  356. /**
  357. * Random number/password generator
  358. *
  359. * @access public
  360. * @param string
  361. * @param int
  362. * @return string
  363. */
  364. function random($type = 'encrypt', $len = 8)
  365. {
  366. $this->EE->load->helper('string');
  367. return random_string($type, $len);
  368. }
  369. // --------------------------------------------------------------------
  370. /**
  371. * Form declaration
  372. *
  373. * This function is used by modules when they need to create forms
  374. *
  375. * @access public
  376. * @param string
  377. * @return string
  378. */
  379. function form_declaration($data)
  380. {
  381. // Load the form helper
  382. $this->EE->load->helper('form');
  383. $deft = array(
  384. 'hidden_fields' => array(),
  385. 'action' => '',
  386. 'id' => '',
  387. 'class' => '',
  388. 'secure' => TRUE,
  389. 'enctype' => '',
  390. 'onsubmit' => '',
  391. );
  392. foreach ($deft as $key => $val)
  393. {
  394. if ( ! isset($data[$key]))
  395. {
  396. $data[$key] = $val;
  397. }
  398. }
  399. if (is_array($data['hidden_fields']) && ! isset($data['hidden_fields']['site_id']))
  400. {
  401. $data['hidden_fields']['site_id'] = $this->EE->config->item('site_id');
  402. }
  403. // Add the CSRF Protection Hash
  404. if ($this->EE->config->item('csrf_protection') == TRUE )
  405. {
  406. $data['hidden_fields'][$this->EE->security->get_csrf_token_name()] = $this->EE->security->get_csrf_hash();
  407. }
  408. // -------------------------------------------
  409. // 'form_declaration_modify_data' hook.
  410. // - Modify the $data parameters before they are processed
  411. // - Added EE 1.4.0
  412. //
  413. if ($this->EE->extensions->active_hook('form_declaration_modify_data') === TRUE)
  414. {
  415. $data = $this->EE->extensions->call('form_declaration_modify_data', $data);
  416. }
  417. //
  418. // -------------------------------------------
  419. // -------------------------------------------
  420. // 'form_declaration_return' hook.
  421. // - Take control of the form_declaration function
  422. // - Added EE 1.4.0
  423. //
  424. if ($this->EE->extensions->active_hook('form_declaration_return') === TRUE)
  425. {
  426. $form = $this->EE->extensions->call('form_declaration_return', $data);
  427. if ($this->EE->extensions->end_script === TRUE) return $form;
  428. }
  429. //
  430. // -------------------------------------------
  431. if ($data['action'] == '')
  432. {
  433. $data['action'] = $this->fetch_site_index();
  434. }
  435. if ($data['onsubmit'] != '')
  436. {
  437. $data['onsubmit'] = 'onsubmit="'.trim($data['onsubmit']).'"';
  438. }
  439. if (substr($data['action'], -1) == '?')
  440. {
  441. $data['action'] = substr($data['action'], 0, -1);
  442. }
  443. $data['name'] = (isset($data['name']) && $data['name'] != '') ? 'name="'.$data['name'].'" ' : '';
  444. $data['id'] = ($data['id'] != '') ? 'id="'.$data['id'].'" ' : '';
  445. $data['class'] = ($data['class'] != '') ? 'class="'.$data['class'].'" ' : '';
  446. if ($data['enctype'] == 'multi' OR strtolower($data['enctype']) == 'multipart/form-data')
  447. {
  448. $data['enctype'] = 'enctype="multipart/form-data" ';
  449. }
  450. $form = '<form '.$data['id'].$data['class'].$data['name'].'method="post" action="'.$data['action'].'" '.$data['onsubmit'].' '.$data['enctype'].">\n";
  451. if ($data['secure'] == TRUE)
  452. {
  453. if ($this->EE->config->item('secure_forms') == 'y')
  454. {
  455. if ( ! isset($data['hidden_fields']['XID']))
  456. {
  457. $data['hidden_fields'] = array_merge(array('XID' => '{XID_HASH}'), $data['hidden_fields']);
  458. }
  459. elseif ($data['hidden_fields']['XID'] == '')
  460. {
  461. $data['hidden_fields']['XID'] = '{XID_HASH}';
  462. }
  463. }
  464. }
  465. if (is_array($data['hidden_fields']))
  466. {
  467. $form .= "<div class='hiddenFields'>\n";
  468. foreach ($data['hidden_fields'] as $key => $val)
  469. {
  470. $form .= '<input type="hidden" name="'.$key.'" value="'.form_prep($val).'" />'."\n";
  471. }
  472. $form .= "</div>\n\n";
  473. }
  474. return $form;
  475. }
  476. // --------------------------------------------------------------------
  477. /**
  478. * Form backtrack
  479. *
  480. * This function lets us return a user to a previously
  481. * visited page after submitting a form. The page
  482. * is determined by the offset that the admin
  483. * places in each form
  484. *
  485. * @access public
  486. * @param string
  487. * @return string
  488. */
  489. function form_backtrack($offset = '')
  490. {
  491. $ret = $this->fetch_site_index();
  492. if ($offset != '')
  493. {
  494. if (isset($this->EE->session->tracker[$offset]))
  495. {
  496. if ($this->EE->session->tracker[$offset] != 'index')
  497. {
  498. return $this->remove_double_slashes($this->fetch_site_index().'/'.$this->EE->session->tracker[$offset]);
  499. }
  500. }
  501. }
  502. if (isset($_POST['RET']))
  503. {
  504. if (strncmp($_POST['RET'], '-', 1) == 0)
  505. {
  506. $return = str_replace("-", "", $_POST['RET']);
  507. if (isset($this->EE->session->tracker[$return]))
  508. {
  509. if ($this->EE->session->tracker[$return] != 'index')
  510. {
  511. $ret = $this->fetch_site_index().'/'.$this->EE->session->tracker[$return];
  512. }
  513. }
  514. }
  515. else
  516. {
  517. if (strpos($_POST['RET'], '/') !== FALSE)
  518. {
  519. if (strncasecmp($_POST['RET'], 'http://', 7) == 0 OR
  520. strncasecmp($_POST['RET'], 'https://', 8) == 0 OR
  521. strncasecmp($_POST['RET'], 'www.', 4) == 0)
  522. {
  523. $ret = $_POST['RET'];
  524. }
  525. else
  526. {
  527. $ret = $this->create_url($_POST['RET']);
  528. }
  529. }
  530. else
  531. {
  532. $ret = $_POST['RET'];
  533. }
  534. }
  535. // We need to slug in the session ID if the admin is running
  536. // their site using sessions only. Normally the $this->EE->functions->fetch_site_index()
  537. // function adds the session ID automatically, except in cases when the
  538. // $_POST['RET'] variable is set. Since the login routine relies on the RET
  539. // info to know where to redirect back to we need to sandwich in the session ID.
  540. if ($this->EE->config->item('user_session_type') != 'c')
  541. {
  542. if ($this->EE->session->userdata['session_id'] != '' && ! stristr($ret, $this->EE->session->userdata['session_id']))
  543. {
  544. $url = $this->EE->config->slash_item('site_url');
  545. $url .= $this->EE->config->item('site_index');
  546. if ($this->EE->config->item('force_query_string') == 'y')
  547. {
  548. $url .= '?';
  549. }
  550. $sess_id = "/S=".$this->EE->session->userdata['session_id']."/";
  551. $ret = str_replace($url, $url.$sess_id, $ret);
  552. }
  553. }
  554. }
  555. return $this->remove_double_slashes($ret);
  556. }
  557. // --------------------------------------------------------------------
  558. /**
  559. * eval()
  560. *
  561. * Evaluates a string as PHP
  562. *
  563. * @access public
  564. * @param string
  565. * @return mixed
  566. */
  567. function evaluate($str)
  568. {
  569. return eval('?'.'>'.$str.'<?php ');
  570. }
  571. // --------------------------------------------------------------------
  572. /**
  573. * Encode email from template callback
  574. *
  575. * @access public
  576. * @param string
  577. * @return string
  578. */
  579. function encode_email($str)
  580. {
  581. if (isset($this->EE->session->cache['functions']['emails'][$str]))
  582. {
  583. return preg_replace("/(eeEncEmail_)\w+/", '\\1'.$this->EE->functions->random('alpha', 10), $this->EE->session->cache['functions']['emails'][$str]);
  584. }
  585. $email = (is_array($str)) ? trim($str[1]) : trim($str);
  586. $title = '';
  587. $email = str_replace(array('"', "'"), '', $email);
  588. if ($p = strpos($email, "title="))
  589. {
  590. $title = substr($email, $p + 6);
  591. $email = trim(substr($email, 0, $p));
  592. }
  593. $this->EE->load->library('typography');
  594. $this->EE->typography->initialize();
  595. $encoded = $this->EE->typography->encode_email($email, $title, TRUE);
  596. $this->EE->session->cache['functions']['emails'][$str] = $encoded;
  597. return $encoded;
  598. }
  599. // --------------------------------------------------------------------
  600. /**
  601. * Delete spam prevention hashes
  602. *
  603. * @access public
  604. * @return void
  605. */
  606. function clear_spam_hashes()
  607. {
  608. if ($this->EE->config->item('secure_forms') == 'y')
  609. {
  610. $this->EE->db->query("DELETE FROM exp_security_hashes WHERE date < UNIX_TIMESTAMP()-7200");
  611. }
  612. }
  613. // --------------------------------------------------------------------
  614. /**
  615. * Set Cookie
  616. *
  617. * @access public
  618. * @param string
  619. * @param string
  620. * @param string
  621. * @return void
  622. */
  623. function set_cookie($name = '', $value = '', $expire = '')
  624. {
  625. $data['name'] = $name;
  626. if ( ! is_numeric($expire))
  627. {
  628. $data['expire'] = time() - 86500;
  629. }
  630. else
  631. {
  632. if ($expire > 0)
  633. {
  634. $data['expire'] = time() + $expire;
  635. }
  636. else
  637. {
  638. $data['expire'] = 0;
  639. }
  640. }
  641. $data['prefix'] = ( ! $this->EE->config->item('cookie_prefix')) ? 'exp_' : $this->EE->config->item('cookie_prefix').'_';
  642. $data['path'] = ( ! $this->EE->config->item('cookie_path')) ? '/' : $this->EE->config->item('cookie_path');
  643. if (REQ == 'CP' && $this->EE->config->item('multiple_sites_enabled') == 'y')
  644. {
  645. $data['domain'] = $this->EE->config->cp_cookie_domain;
  646. }
  647. else
  648. {
  649. $data['domain'] = ( ! $this->EE->config->item('cookie_domain')) ? '' : $this->EE->config->item('cookie_domain');
  650. }
  651. $data['value'] = stripslashes($value);
  652. $data['secure_cookie'] = ($this->EE->config->item('cookie_secure') === TRUE) ? 1 : 0;
  653. if ($data['secure_cookie'])
  654. {
  655. $req = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : FALSE;
  656. if ( ! $req OR $req == 'off')
  657. {
  658. return FALSE;
  659. }
  660. }
  661. /* -------------------------------------------
  662. /* 'set_cookie_end' hook.
  663. /* - Take control of Cookie setting routine
  664. /* - Added EE 2.5.0
  665. */
  666. $this->EE->extensions->call('set_cookie_end', $data);
  667. if ($this->EE->extensions->end_script === TRUE) return;
  668. /*
  669. /* -------------------------------------------*/
  670. setcookie($data['prefix'].$data['name'], $data['value'], $data['expire'],
  671. $data['path'], $data['domain'], $data['secure_cookie']);
  672. }
  673. // --------------------------------------------------------------------
  674. /**
  675. * Character limiter
  676. *
  677. * @access public
  678. * @param string
  679. * @return string
  680. */
  681. function char_limiter($str, $num = 500)
  682. {
  683. if (strlen($str) < $num)
  684. {
  685. return $str;
  686. }
  687. $str = str_replace("\n", " ", $str);
  688. $str = preg_replace("/\s+/", " ", $str);
  689. if (strlen($str) <= $num)
  690. {
  691. return $str;
  692. }
  693. $str = trim($str);
  694. $out = "";
  695. foreach (explode(" ", trim($str)) as $val)
  696. {
  697. $out .= $val;
  698. if (strlen($out) >= $num)
  699. {
  700. return (strlen($out) == strlen($str)) ? $out : $out.'&#8230;';
  701. }
  702. $out .= ' ';
  703. }
  704. }
  705. // --------------------------------------------------------------------
  706. /**
  707. * Word limiter
  708. *
  709. * @access public
  710. * @param string
  711. * @return string
  712. */
  713. function word_limiter($str, $num = 100)
  714. {
  715. if (strlen($str) < $num)
  716. {
  717. return $str;
  718. }
  719. // allows the split to work properly with multi-byte Unicode characters
  720. if (is_php('4.3.2') === TRUE)
  721. {
  722. $word = preg_split('/\s/u', $str, -1, PREG_SPLIT_NO_EMPTY);
  723. }
  724. else
  725. {
  726. $word = preg_split('/\s/', $str, -1, PREG_SPLIT_NO_EMPTY);
  727. }
  728. if (count($word) <= $num)
  729. {
  730. return $str;
  731. }
  732. $str = "";
  733. for ($i = 0; $i < $num; $i++)
  734. {
  735. $str .= $word[$i]." ";
  736. }
  737. return trim($str).'&#8230;';
  738. }
  739. // --------------------------------------------------------------------
  740. /**
  741. * Fetch Email Template
  742. *
  743. * @access public
  744. * @param string
  745. * @return string
  746. */
  747. function fetch_email_template($name)
  748. {
  749. $query = $this->EE->db->query("SELECT template_name, data_title, template_data, enable_template FROM exp_specialty_templates WHERE site_id = '".$this->EE->db->escape_str($this->EE->config->item('site_id'))."' AND template_name = '".$this->EE->db->escape_str($name)."'");
  750. // Unlikely that this is necessary but it's possible a bad template request could
  751. // happen if a user hasn't run the update script.
  752. if ($query->num_rows() == 0)
  753. {
  754. return array('title' => '', 'data' => '');
  755. }
  756. if ($query->row('enable_template') == 'y')
  757. {
  758. return array('title' => $query->row('data_title') , 'data' => $query->row('template_data') );
  759. }
  760. if ($this->EE->session->userdata['language'] != '')
  761. {
  762. $user_lang = $this->EE->session->userdata['language'];
  763. }
  764. else
  765. {
  766. if ($this->EE->input->cookie('language'))
  767. {
  768. $user_lang = $this->EE->input->cookie('language');
  769. }
  770. elseif ($this->EE->config->item('deft_lang') != '')
  771. {
  772. $user_lang = $this->EE->config->item('deft_lang');
  773. }
  774. else
  775. {
  776. $user_lang = 'english';
  777. }
  778. }
  779. $user_lang = $this->EE->security->sanitize_filename($user_lang);
  780. if ( function_exists($name))
  781. {
  782. $title = $name.'_title';
  783. return array('title' => $title(), 'data' => $name());
  784. }
  785. else
  786. {
  787. if ( ! @include(APPPATH.'language/'.$user_lang.'/email_data.php'))
  788. {
  789. return array('title' => $query->row('data_title') , 'data' => $query->row('template_data') );
  790. }
  791. if (function_exists($name))
  792. {
  793. $title = $name.'_title';
  794. return array('title' => $title(), 'data' => $name());
  795. }
  796. else
  797. {
  798. return array('title' => $query->row('data_title') , 'data' => $query->row('template_data') );
  799. }
  800. }
  801. }
  802. // --------------------------------------------------------------------
  803. /**
  804. * Create character encoding menu
  805. *
  806. * DEPRECATED IN 2.0
  807. *
  808. * @access public
  809. * @param string
  810. * @param string
  811. * @param string
  812. * @return string
  813. */
  814. function encoding_menu($name, $selected = '')
  815. {
  816. $this->EE->load->library('logger');
  817. $this->EE->logger->deprecated('2.0');
  818. $file = APPPATH.'config/languages.php';
  819. if ( ! file_exists($file))
  820. {
  821. return FALSE;
  822. }
  823. require_once $file;
  824. $languages = array_flip($languages);
  825. $this->EE->load->helper('form');
  826. return form_dropdown($name, $languages, $selected);
  827. }
  828. // --------------------------------------------------------------------
  829. /**
  830. * Create Directory Map
  831. *
  832. * DEPRECATED IN 2.2
  833. *
  834. * @access public
  835. * @param string
  836. * @param bool
  837. * @return array
  838. */
  839. function create_directory_map($source_dir, $top_level_only = FALSE)
  840. {
  841. $this->EE->load->library('logger');
  842. $this->EE->logger->deprecated('2.2');
  843. if ( ! isset($filedata))
  844. $filedata = array();
  845. if ($fp = @opendir($source_dir))
  846. {
  847. while (FALSE !== ($file = readdir($fp)))
  848. {
  849. if (@is_dir($source_dir.$file) && substr($file, 0, 1) != '.' AND $top_level_only == FALSE)
  850. {
  851. $temp_array = array();
  852. $temp_array = $this->create_directory_map($source_dir.$file."/");
  853. $filedata[$file] = $temp_array;
  854. }
  855. elseif (substr($file, 0, 1) != "." && $file != 'index.html')
  856. {
  857. $filedata[] = $file;
  858. }
  859. }
  860. return $filedata;
  861. }
  862. }
  863. // --------------------------------------------------------------------
  864. /**
  865. * Create pull-down optios from dirctory map
  866. *
  867. * @access public
  868. * @param array
  869. * @param string
  870. * @return string
  871. */
  872. function render_map_as_select_options($zarray, $array_name = '')
  873. {
  874. foreach ($zarray as $key => $val)
  875. {
  876. if ( is_array($val))
  877. {
  878. if ($array_name != '')
  879. {
  880. $key = $array_name.'/'.$key;
  881. }
  882. $this->render_map_as_select_options($val, $key);
  883. }
  884. else
  885. {
  886. if ($array_name != '')
  887. {
  888. $val = $array_name.'/'.$val;
  889. }
  890. if (substr($val, -4) == '.php')
  891. {
  892. if ($val != 'theme_master.php')
  893. {
  894. $this->template_map[] = $val;
  895. }
  896. }
  897. }
  898. }
  899. }
  900. // --------------------------------------------------------------------
  901. /**
  902. * Fetch names of installed language packs
  903. *
  904. * DEPRECATED IN 2.0
  905. *
  906. * @access public
  907. * @param string
  908. * @return string
  909. */
  910. function language_pack_names($default)
  911. {
  912. $source_dir = APPPATH.'language/';
  913. $dirs = array();
  914. if ($fp = @opendir($source_dir))
  915. {
  916. while (FALSE !== ($file = readdir($fp)))
  917. {
  918. if (is_dir($source_dir.$file) && substr($file, 0, 1) != ".")
  919. {
  920. $dirs[] = $file;
  921. }
  922. }
  923. closedir($fp);
  924. }
  925. sort($dirs);
  926. $r = "<div class='default'>";
  927. $r .= "<select name='deft_lang' class='select'>\n";
  928. foreach ($dirs as $dir)
  929. {
  930. $selected = ($dir == $default) ? " selected='selected'" : '';
  931. $r .= "<option value='{$dir}'{$selected}>".ucfirst($dir)."</option>\n";
  932. }
  933. $r .= "</select>";
  934. $r .= "</div>";
  935. return $r;
  936. }
  937. // --------------------------------------------------------------------
  938. /**
  939. * Delete cache files
  940. *
  941. * @access public
  942. * @param string
  943. * @return string
  944. */
  945. function clear_caching($which, $sub_dir = '', $relationships=FALSE)
  946. {
  947. $actions = array('page', 'tag', 'db', 'sql', 'relationships', 'all');
  948. if ( ! in_array($which, $actions))
  949. {
  950. return;
  951. }
  952. /* -------------------------------------
  953. /* Disable Tag Caching
  954. /*
  955. /* All for you, Nevin! Disables tag caching, which if used unwisely
  956. /* on a high traffic site can lead to disastrous disk i/o
  957. /* This setting allows quick thinking admins to temporarily disable
  958. /* it without hacking or modifying folder permissions
  959. /*
  960. /* Hidden Configuration Variable
  961. /* - disable_tag_caching => Disable tag caching? (y/n)
  962. /* -------------------------------------*/
  963. if ($which == 'tag' && $this->EE->config->item('disable_tag_caching') == 'y')
  964. {
  965. return;
  966. }
  967. $db_path = '';
  968. if ($sub_dir != '')
  969. {
  970. if ($which == 'all' OR $which == 'db')
  971. {
  972. $segs = explode('/', str_replace($this->fetch_site_index(), '', $sub_dir));
  973. $segment_one = (isset($segs['0'])) ? $segs['0'] : 'default';
  974. $segment_two = (isset($segs['1'])) ? $segs['1'] : 'index';
  975. $db_path = '/'.$segment_one.'+'.$segment_two.'/';
  976. }
  977. $sub_dir = '/'.md5($sub_dir).'/';
  978. }
  979. switch ($which)
  980. {
  981. case 'page' : $this->delete_directory(APPPATH.'cache/page_cache'.$sub_dir);
  982. break;
  983. case 'db' : $this->delete_directory(APPPATH.'cache/db_cache_'.$this->EE->config->item('site_id').$db_path);
  984. break;
  985. case 'tag' : $this->delete_directory(APPPATH.'cache/tag_cache'.$sub_dir);
  986. break;
  987. case 'sql' : $this->delete_directory(APPPATH.'cache/sql_cache'.$sub_dir);
  988. break;
  989. case 'relationships' : $this->EE->db->query("UPDATE exp_relationships SET rel_data = '', reverse_rel_data = ''");
  990. break;
  991. case 'all' :
  992. $this->delete_directory(APPPATH.'cache/page_cache'.$sub_dir);
  993. $this->delete_directory(APPPATH.'cache/db_cache_'.$this->EE->config->item('site_id').$db_path);
  994. $this->delete_directory(APPPATH.'cache/sql_cache'.$sub_dir);
  995. if ($this->EE->config->item('disable_tag_caching') != 'y')
  996. {
  997. $this->delete_directory(APPPATH.'cache/tag_cache'.$sub_dir);
  998. }
  999. if ($relationships === TRUE)
  1000. {
  1001. $this->EE->db->query("UPDATE exp_relationships SET rel_data = '', reverse_rel_data = ''");
  1002. }
  1003. break;
  1004. }
  1005. }
  1006. // --------------------------------------------------------------------
  1007. /**
  1008. * Delete Direcories
  1009. *
  1010. * @access public
  1011. * @param string
  1012. * @param bool
  1013. * @return void
  1014. */
  1015. function delete_directory($path, $del_root = FALSE)
  1016. {
  1017. $path = rtrim($path, '/');
  1018. $path_delete = $path.'_delete';
  1019. if ( ! is_dir($path))
  1020. {
  1021. return FALSE;
  1022. }
  1023. // Delete temporary directory if it happens to exist from a previous attempt
  1024. if (is_dir($path_delete))
  1025. {
  1026. @exec("rm -r -f {$path_delete}");
  1027. }
  1028. // let's try this the sane way first
  1029. @exec("mv {$path} {$path_delete}", $out, $ret);
  1030. if (isset($ret) && $ret == 0)
  1031. {
  1032. if ($del_root === FALSE)
  1033. {
  1034. @mkdir($path, 0777);
  1035. if ($fp = @fopen($path.'/index.html', FOPEN_WRITE_CREATE_DESTRUCTIVE))
  1036. {
  1037. fclose($fp);
  1038. }
  1039. }
  1040. @exec("rm -r -f {$path_delete}");
  1041. }
  1042. else
  1043. {
  1044. if ( ! $current_dir = @opendir($path))
  1045. {
  1046. return;
  1047. }
  1048. while($filename = @readdir($current_dir))
  1049. {
  1050. if (@is_dir($path.'/'.$filename) and ($filename != "." and $filename != ".."))
  1051. {
  1052. $this->delete_directory($path.'/'.$filename, TRUE);
  1053. }
  1054. elseif($filename != "." and $filename != "..")
  1055. {
  1056. @unlink($path.'/'.$filename);
  1057. }
  1058. }
  1059. @closedir($current_dir);
  1060. if (substr($path, -6) == '_cache' && $fp = @fopen($path.'/index.html', FOPEN_WRITE_CREATE_DESTRUCTIVE))
  1061. {
  1062. fclose($fp);
  1063. }
  1064. if ($del_root == TRUE)
  1065. {
  1066. @rmdir($path);
  1067. }
  1068. }
  1069. }
  1070. // --------------------------------------------------------------------
  1071. /**
  1072. * Fetch allowed channels
  1073. *
  1074. * This function fetches the ID numbers of the
  1075. * channels assigned to the currently logged in user.
  1076. *
  1077. * @access public
  1078. * @param bool
  1079. * @return array
  1080. */
  1081. function fetch_assigned_channels($all_sites = FALSE)
  1082. {
  1083. $allowed_channels = array();
  1084. if (REQ == 'CP' AND isset($this->EE->session->userdata['assigned_channels']) && $all_sites === FALSE)
  1085. {
  1086. $allowed_channels = array_keys($this->EE->session->userdata['assigned_channels']);
  1087. }
  1088. elseif ($this->EE->session->userdata['group_id'] == 1)
  1089. {
  1090. if ($all_sites === TRUE)
  1091. {
  1092. $this->EE->db->select('channel_id');
  1093. $query = $this->EE->db->get('channels');
  1094. }
  1095. else
  1096. {
  1097. $this->EE->db->select('channel_id');
  1098. $this->EE->db->where('site_id', $this->EE->config->item('site_id'));
  1099. $query = $this->EE->db->get('channels');
  1100. }
  1101. if ($query->num_rows() > 0)
  1102. {
  1103. foreach ($query->result_array() as $row)
  1104. {
  1105. $allowed_channels[] = $row['channel_id'];
  1106. }
  1107. }
  1108. }
  1109. else
  1110. {
  1111. if ($all_sites === TRUE)
  1112. {
  1113. $result = $this->EE->db->query("SELECT exp_channel_member_groups.channel_id FROM exp_channel_member_groups
  1114. WHERE exp_channel_member_groups.group_id = '".$this->EE->db->escape_str($this->EE->session->userdata['group_id'])."'");
  1115. }
  1116. else
  1117. {
  1118. $result = $this->EE->db->query("SELECT exp_channels.channel_id FROM exp_channels, exp_channel_member_groups
  1119. WHERE exp_channels.channel_id = exp_channel_member_groups.channel_id
  1120. AND exp_channels.site_id = '".$this->EE->db->escape_str($this->EE->config->item('site_id'))."'
  1121. AND exp_channel_member_groups.group_id = '".$this->EE->db->escape_str($this->EE->session->userdata['group_id'])."'");
  1122. }
  1123. if ($result->num_rows() > 0)
  1124. {
  1125. foreach ($result->result_array() as $row)
  1126. {
  1127. $allowed_channels[] = $row['channel_id'];
  1128. }
  1129. }
  1130. }
  1131. return array_values($allowed_channels);
  1132. }
  1133. // --------------------------------------------------------------------
  1134. /**
  1135. * Log Search terms
  1136. *
  1137. * @access public
  1138. * @param string
  1139. * @param string
  1140. * @return void
  1141. */
  1142. function log_search_terms($terms = '', $type = 'site')
  1143. {
  1144. if ($terms == '' OR $this->EE->db->table_exists('exp_search_log') === FALSE)
  1145. return;
  1146. if ($this->EE->config->item('enable_search_log') == 'n')
  1147. return;
  1148. $this->EE->load->helper('xml');
  1149. $search_log = array(
  1150. 'member_id' => $this->EE->session->userdata('member_id'),
  1151. 'screen_name' => $this->EE->session->userdata('screen_name'),
  1152. 'ip_address' => $this->EE->input->ip_address(),
  1153. 'search_date' => $this->EE->localize->now,
  1154. 'search_type' => $type,
  1155. 'search_terms' => xml_convert($this->EE->functions->encode_ee_tags($this->EE->security->xss_clean($terms), TRUE)),
  1156. 'site_id' => $this->EE->config->item('site_id')
  1157. );
  1158. $this->EE->db->query($this->EE->db->insert_string('exp_search_log', $search_log));
  1159. // Prune Database
  1160. srand(time());
  1161. if ((rand() % 100) < 5)
  1162. {
  1163. $max = ( ! is_numeric($this->EE->config->item('max_logged_searches'))) ? 500 : $this->EE->config->item('max_logged_searches');
  1164. $query = $this->EE->db->query("SELECT MAX(id) as search_id FROM exp_search_log WHERE site_id = '".$this->EE->db->escape_str($this->EE->config->item('site_id'))."'");
  1165. $row = $query->row_array();
  1166. if (isset($row['search_id'] ) && $row['search_id'] > $max)
  1167. {
  1168. $this->EE->db->query("DELETE FROM exp_search_log WHERE site_id = '".$this->EE->db->escape_str($this->EE->config->item('site_id'))."' AND id < ".($row['search_id'] -$max)."");
  1169. }
  1170. }
  1171. }
  1172. // --------------------------------------------------------------------
  1173. /**
  1174. * Fetch Action ID
  1175. *
  1176. * @access public
  1177. * @param string
  1178. * @param string
  1179. * @return string
  1180. */
  1181. function fetch_action_id($class, $method)
  1182. {
  1183. if ($class == '' OR $method == '')
  1184. {
  1185. return FALSE;
  1186. }
  1187. $this->action_ids[ucfirst($class)][$method] = $method;
  1188. return LD.'AID:'.ucfirst($class).':'.$method.RD;
  1189. }
  1190. // --------------------------------------------------------------------
  1191. /**
  1192. * Insert Action IDs
  1193. *
  1194. * @access public
  1195. * @param string
  1196. * @return string
  1197. */
  1198. function insert_action_ids($str)
  1199. {
  1200. if (count($this->action_ids) == 0)
  1201. {
  1202. return $str;
  1203. }
  1204. $sql = "SELECT action_id, class, method FROM exp_actions WHERE";
  1205. foreach($this->action_ids as $key => $value)
  1206. {
  1207. foreach($value as $k => $v)
  1208. {
  1209. $sql .= " (class= '".$this->EE->db->escape_str($key)."' AND method = '".$this->EE->db->escape_str($v)."') OR";
  1210. }
  1211. }
  1212. $query = $this->EE->db->query(substr($sql, 0, -3));
  1213. if ($query->num_rows() > 0)
  1214. {
  1215. foreach($query->result_array() as $row)
  1216. {
  1217. $str = str_replace(LD.'AID:'.$row['class'].':'.$row['method'].RD, $row['action_id'], $str);
  1218. }
  1219. }
  1220. return $str;
  1221. }
  1222. // --------------------------------------------------------------------
  1223. /**
  1224. * Compile and cache relationship data
  1225. *
  1226. * This is used when submitting new channel entries or gallery posts.
  1227. * It serializes the related entry data. The reason it's in this
  1228. * file is because it gets called from the publish class and the
  1229. * gallery class so we need it somewhere that is accessible to both.
  1230. *
  1231. * @access public
  1232. * @param string
  1233. * @param bool
  1234. * @param bool
  1235. * @return void
  1236. */
  1237. function compile_relationship($data, $parent_entry = TRUE, $reverse = FALSE)
  1238. {
  1239. if ($data['type'] == 'channel' OR ($reverse === TRUE && $parent_entry === FALSE))
  1240. {
  1241. $sql = "SELECT t.entry_id, t.channel_id, t.forum_topic_id, t.author_id, t.ip_address, t.title, t.url_title, t.status, t.dst_enabled, t.view_count_one, t.view_count_two, t.view_count_three, t.view_count_four, t.allow_comments, t.comment_expiration_date, t.sticky, t.entry_date, t.year, t.month, t.day, t.entry_date, t.edit_date, t.expiration_date, t.recent_comment_date, t.comment_total, t.site_id as entry_site_id,
  1242. w.channel_title, w.channel_name, w.channel_url, w.comment_url, w.comment_moderate, w.channel_html_formatting, w.channel_allow_img_urls, w.channel_auto_link_urls,
  1243. m.username, m.email, m.url, m.screen_name, m.location, m.occupation, m.interests, m.aol_im, m.yahoo_im, m.msn_im, m.icq, m.signature, m.sig_img_filename, m.sig_img_width, m.sig_img_height, m.avatar_filename, m.avatar_width, m.avatar_height, m.photo_filename, m.photo_width, m.photo_height, m.group_id, m.member_id, m.bday_d, m.bday_m, m.bday_y, m.bio,
  1244. md.*,
  1245. wd.*
  1246. FROM exp_channel_titles AS t
  1247. LEFT JOIN exp_channels AS w ON t.channel_id = w.channel_id
  1248. LEFT JOIN exp_channel_data AS wd ON t.entry_id = wd.entry_id
  1249. LEFT JOIN exp_members AS m ON m.member_id = t.author_id
  1250. LEFT JOIN exp_member_data AS md ON md.member_id = m.member_id
  1251. WHERE t.entry_id = '".(($reverse === TRUE && $parent_entry === FALSE) ? $data['parent_id'] : $data['child_id'])."'";
  1252. $entry_query = $this->EE->db->query($sql);
  1253. // Is there a category group associated with this channel?
  1254. $query = $this->EE->db->query("SELECT cat_group FROM exp_channels WHERE channel_id = '".$entry_query->row('channel_id') ."'");
  1255. $cat_group = (trim($query->row('cat_group')) == '') ? FALSE : $query->row('cat_group');
  1256. $this->cat_array = array();
  1257. $cat_array = array();
  1258. if ($cat_group !== FALSE)
  1259. {
  1260. $this->get_categories($cat_group, ($reverse === TRUE && $parent_entry === FALSE) ? $data['parent_id'] : $data['child_id']);
  1261. }
  1262. $cat_array = $this->cat_array;
  1263. if ($parent_entry == TRUE)
  1264. {
  1265. $this->EE->db->query("INSERT INTO exp_relationships (rel_parent_id, rel_child_id, rel_type, rel_data, reverse_rel_data)
  1266. VALUES ('".$data['parent_id']."', '".$data['child_id']."', '".$data['type']."',
  1267. '".addslashes(serialize(array('query' => $entry_query, 'cats_fixed' => '1', 'categories' => $cat_array)))."', '')");
  1268. return $this->EE->db->insert_id();
  1269. }
  1270. else
  1271. {
  1272. if ($reverse === TRUE)
  1273. {
  1274. $this->EE->db->query("UPDATE exp_relationships
  1275. SET reverse_rel_data = '".addslashes(serialize(array('query' => $entry_query, 'cats_fixed' => '1', 'categories' => $cat_array)))."'
  1276. WHERE rel_type = '".$this->EE->db->escape_str($data['type'])."' AND rel_parent_id = '".$data['parent_id']."'");
  1277. }
  1278. else
  1279. {
  1280. $this->EE->db->query("UPDATE exp_relationships
  1281. SET rel_data = '".addslashes(serialize(array('query' => $entry_query, 'cats_fixed' => '1', 'categories' => $cat_array)))."'
  1282. WHERE rel_type = 'channel' AND rel_child_id = '".$data['child_id']."'");
  1283. }
  1284. }
  1285. }
  1286. }
  1287. // --------------------------------------------------------------------
  1288. /**
  1289. * Get Categories for Channel Entry/Entries
  1290. *
  1291. * @access public
  1292. * @param string
  1293. * @param string
  1294. * @return array
  1295. */
  1296. function get_categories($cat_group, $entry_id)
  1297. {
  1298. // fetch the custom category fields
  1299. $field_sqla = '';
  1300. $field_sqlb = '';
  1301. $query = $this->EE->db->query("SELECT field_id, field_name FROM exp_category_fields WHERE group_id IN ('".str_replace('|', "','", $this->EE->db->escape_str($cat_group))."')");
  1302. if ($query->num_rows() > 0)
  1303. {
  1304. foreach ($query->result_array() as $row)
  1305. {
  1306. $this->catfields[] = array('field_name' => $row['field_name'], 'field_id' => $row['field_id']);
  1307. }
  1308. $field_sqla = ", cg.field_html_formatting, fd.* ";
  1309. $field_sqlb = " LEFT JOIN exp_category_field_data AS fd ON fd.cat_id = c.cat_id
  1310. LEFT JOIN exp_category_groups AS cg ON cg.group_id = c.group_id";
  1311. }
  1312. $sql = "SELECT c.cat_name, c.cat_url_title, c.cat_id, c.cat_image, p.cat_id, c.parent_id, c.cat_description, c.group_id
  1313. {$field_sqla}
  1314. FROM (exp_categories AS c, exp_category_posts AS p)
  1315. {$field_sqlb}
  1316. WHERE c.group_id IN ('".str_replace('|', "','", $this->EE->db->escape_str($cat_group))."')
  1317. AND p.entry_id = '".$entry_id."'
  1318. AND c.cat_id = p.cat_id
  1319. ORDER BY c.parent_id, c.cat_order";
  1320. $sql = str_replace("\t", " ", $sql);
  1321. $query = $this->EE->db->query($sql);
  1322. $this->cat_array = array();
  1323. $parents = array();
  1324. if ($query->num_rows() > 0)
  1325. {
  1326. $this->temp_array = array();
  1327. foreach ($query->result_array() as $row)
  1328. {
  1329. $this->temp_array[$row['cat_id']] = array($row['cat_id'], $row['parent_id'], $row['cat_name'], $row['cat_image'], $row['cat_description'], $row['group_id'], $row['cat_url_title']);
  1330. if ($field_sqla != '')
  1331. {
  1332. foreach ($row as $k => $v)
  1333. {
  1334. if (strpos($k, 'field') !== FALSE)
  1335. {
  1336. $this->temp_array[$row['cat_id']][$k] = $v;
  1337. }
  1338. }
  1339. }
  1340. if ($row['parent_id'] > 0 && ! isset($this->temp_array[$row['parent_id']])) $parents[$row['parent_id']] = '';
  1341. unset($parents[$row['cat_id']]);
  1342. }
  1343. foreach($this->temp_array as $k => $v)
  1344. {
  1345. if (isset($parents[$v[1]])) $v[1] = 0;
  1346. if (0 == $v[1])
  1347. {
  1348. $this->cat_array[] = $v;
  1349. $this->process_subcategories($k);
  1350. }
  1351. }
  1352. unset($this->temp_array);
  1353. }
  1354. }
  1355. // --------------------------------------------------------------------
  1356. /**
  1357. * Process Subcategories
  1358. *
  1359. * @access public
  1360. * @param string
  1361. * @return void
  1362. */
  1363. function process_subcategories($parent_id)
  1364. {
  1365. foreach($this->temp_array as $key => $val)
  1366. {
  1367. if ($parent_id == $val[1])
  1368. {
  1369. $this->cat_array[] = $val;
  1370. $this->process_subcategories($key);
  1371. }
  1372. }
  1373. }
  1374. // --------------------------------------------------------------------
  1375. /**
  1376. * Add security hashes to forms
  1377. *
  1378. * @access public
  1379. * @param string
  1380. * @return string
  1381. */
  1382. function add_form_security_hash($str)
  1383. {
  1384. if ($this->EE->config->item('secure_forms') == 'y')
  1385. {
  1386. if (preg_match_all("/({XID_HASH})/", $str, $matches))
  1387. {
  1388. $db_reset = FALSE;
  1389. // Disable DB caching if it's currently set
  1390. if ($this->EE->db->cache_on == TRUE)
  1391. {
  1392. $this->EE->db->cache_off();
  1393. $db_reset = TRUE;
  1394. }
  1395. // Add security hashes
  1396. $sql = "INSERT INTO exp_security_hashes (date, ip_address, hash) VALUES";
  1397. foreach ($matches[1] as $val)
  1398. {
  1399. $hash = $this->random('encrypt');
  1400. $str = preg_replace("/{XID_HASH}/", $hash, $str, 1);
  1401. $sql .= "(UNIX_TIMESTAMP(), '".$this->EE->input->ip_address()."', '".$hash."'),";
  1402. }
  1403. $this->EE->db->query(substr($sql,0,-1));
  1404. // Re-enable DB caching
  1405. if ($db_reset == TRUE)
  1406. {
  1407. $this->EE->db->cache_on();
  1408. }
  1409. }
  1410. }
  1411. return $str;
  1412. }
  1413. // --------------------------------------------------------------------
  1414. /**
  1415. * Generate CAPTCHA
  1416. *
  1417. * @access public
  1418. * @param string
  1419. * @return string
  1420. */
  1421. function create_captcha($old_word = '')
  1422. {
  1423. if ($this->EE->config->item('captcha_require_members') == 'n' AND $this->EE->session->userdata['member_id'] != 0)
  1424. {
  1425. return '';
  1426. }
  1427. // -------------------------------------------
  1428. // 'create_captcha_start' hook.
  1429. // - Allows rewrite of how CAPTCHAs are created
  1430. //
  1431. if ($this->EE->extensions->active_hook('create_captcha_start') === TRUE)
  1432. {
  1433. $edata = $this->EE->extensions->call('create_captcha_start', $old_word);
  1434. if ($this->EE->extensions->end_script === TRUE) return $edata;
  1435. }
  1436. // -------------------------------------------
  1437. $img_path = $this->EE->config->slash_item('captcha_path', 1);
  1438. $img_url = $this->EE->config->slash_item('captcha_url');
  1439. $use_font = ($this->EE->config->item('captcha_font') == 'y') ? TRUE : FALSE;
  1440. $font_face = "texb.ttf";
  1441. $font_size = 16;
  1442. $expiration = 60*60*2; // 2 hours
  1443. $img_width = 140; // Image width
  1444. $img_height = 30; // Image height
  1445. if ($img_path == '' OR $img_url == '')
  1446. {
  1447. return FALSE;
  1448. }
  1449. if ( ! @is_dir($img_path))
  1450. {
  1451. return FALSE;
  1452. }
  1453. if ( ! is_really_writable($img_path))
  1454. {
  1455. return FALSE;
  1456. }
  1457. if ( ! file_exists(APPPATH.'config/captcha.php'))
  1458. {
  1459. return FALSE;
  1460. }
  1461. if ( ! extension_loaded('gd'))
  1462. {
  1463. return FALSE;
  1464. }
  1465. if (substr($img_url, -1) != '/') $img_url .= '/';
  1466. // Disable DB caching if it's currently set
  1467. $db_reset = FALSE;
  1468. if ($this->EE->db->cache_on == TRUE)
  1469. {
  1470. $this->EE->db->cache_off();
  1471. $db_reset = TRUE;
  1472. }
  1473. // Remove old images - add a bit of randomness so we aren't doing this every page access
  1474. list($usec, $sec) = explode(" ", microtime());
  1475. $now = ((float)$usec + (float)$sec);
  1476. if ((mt_rand() % 100) < $this->EE->session->gc_probability)
  1477. {
  1478. $old = time() - $expiration;
  1479. $this->EE->db->query("DELETE FROM exp_captcha WHERE date < ".$old);
  1480. $current_dir = @opendir($img_path);
  1481. while($filename = @readdir($current_dir))
  1482. {
  1483. if ($filename != "." and $filename != ".." and $filename != "index.html")
  1484. {
  1485. $name = str_replace(".jpg", "", $filename);
  1486. if (($name + $expiration) < $now)
  1487. {
  1488. @unlink($img_path.$filename);
  1489. }
  1490. }
  1491. }
  1492. @closedir($current_dir);
  1493. }
  1494. // Fetch and insert word
  1495. if ($old_word == '')
  1496. {
  1497. require APPPATH.'config/captcha.php';
  1498. $word = $words[array_rand($words)];
  1499. if ($this->EE->config->item('captcha_rand') == 'y')
  1500. {
  1501. $word .= $this->random('nozero', 2);
  1502. }
  1503. $this->EE->db->query("INSERT INTO exp_captcha (date, ip_address, word) VALUES (UNIX_TIMESTAMP(), '".$this->EE->input->ip_address()."', '".$this->EE->db->escape_str($word)."')");
  1504. }
  1505. else
  1506. {
  1507. $word = $old_word;
  1508. }
  1509. $this->cached_captcha = $word;
  1510. // Determine angle and position
  1511. $length = strlen($word);
  1512. $angle = ($length >= 6) ? rand(-($length-6), ($length-6)) : 0;
  1513. $x_axis = rand(6, (360/$length)-16);
  1514. $y_axis = ($angle >= 0 ) ? rand($img_height, $img_width) : rand(6, $img_height);
  1515. // Create image
  1516. $im = ImageCreate($img_width, $img_height);
  1517. // Assign colors
  1518. $bg_color = ImageColorAllocate($im, 255, 255, 255);
  1519. $border_color = ImageColorAllocate($im, 153, 102, 102);
  1520. $text_color = ImageColorAllocate($im, 204, 153, 153);
  1521. $grid_color = imagecolorallocate($im, 255, 182, 182);
  1522. $shadow_color = imagecolorallocate($im, 255, 240, 240);
  1523. // Create the rectangle
  1524. ImageFilledRectangle($im, 0, 0, $img_width, $img_height, $bg_color);
  1525. // Create the spiral pattern
  1526. $theta = 1;
  1527. $thetac = 6;
  1528. $radius = 12;
  1529. $circles = 20;
  1530. $points = 36;
  1531. for ($i = 0; $i < ($circles * $points) - 1; $i++)
  1532. {
  1533. $theta = $theta + $thetac;
  1534. $rad = $radius * ($i / $points );
  1535. $x = ($rad * cos($theta)) + $x_axis;
  1536. $y = ($rad * sin($theta)) + $y_axis;
  1537. $theta = $theta + $thetac;
  1538. $rad1 = $radius * (($i + 1) / $points);
  1539. $x1 = ($rad1 * cos($theta)) + $x_axis;
  1540. $y1 = ($rad1 * sin($theta )) + $y_axis;
  1541. imageline($im, $x, $y, $x1, $y1, $grid_color);
  1542. $theta = $theta - $thetac;
  1543. }
  1544. //imageline($im, $img_width, $img_height, 0, 0, $grid_color);
  1545. // Write the text
  1546. $font_path = APPPATH.'fonts/'.$font_face;
  1547. if ($use_font == TRUE)
  1548. {
  1549. if ( ! file_exists($font_path))
  1550. {
  1551. $use_font = FALSE;
  1552. }
  1553. }
  1554. if ($use_font == FALSE OR ! function_exists('imagettftext'))
  1555. {
  1556. $font_size = 5;
  1557. ImageString($im, $font_size, $x_axis, $img_height/3.8, $word, $text_color);
  1558. }
  1559. else
  1560. {
  1561. imagettftext($im, $font_size, $angle, $x_axis, $img_height/1.5, $text_color, $font_path, $word);
  1562. }
  1563. // Create the border
  1564. imagerectangle($im, 0, 0, $img_width-1, $img_height-1, $border_color);
  1565. // Generate the image
  1566. $img_name = $now.'.jpg';
  1567. ImageJPEG($im, $img_path.$img_name);
  1568. $img = "<img src=\"$img_url$img_name\" width=\"$img_width\" height=\"$img_height\" style=\"border:0;\" alt=\" \" />";
  1569. ImageDestroy($im);
  1570. // Re-enable DB caching
  1571. if ($db_reset == TRUE)
  1572. {
  1573. $this->EE->db->cache_on();
  1574. }
  1575. return $img;
  1576. }
  1577. // --------------------------------------------------------------------
  1578. /**
  1579. * SQL "AND" or "OR" string for conditional tag parameters
  1580. *
  1581. * This function lets us build a specific type of query
  1582. * needed when tags have conditional parameters:
  1583. *
  1584. * {exp:some_tag param="value1|value2|value3"}
  1585. *
  1586. * Or the parameter can contain "not":
  1587. *
  1588. * {exp:some_tag param="not value1|value2|value3"}
  1589. *
  1590. * This function explodes the pipes and constructs a series of AND
  1591. * conditions or OR conditions
  1592. *
  1593. * We should probably put this in the DB class but it's not
  1594. * something that is typically used
  1595. *
  1596. * @access public
  1597. * @param string
  1598. * @param string
  1599. * @param string
  1600. * @param bool
  1601. * @return string
  1602. */
  1603. function sql_andor_string($str, $field, $prefix = '', $null=FALSE)
  1604. {
  1605. if ($str == "" OR $field == "")
  1606. {
  1607. return '';
  1608. }
  1609. $str = trim($str);
  1610. $sql = '';
  1611. $not = '';
  1612. if ($prefix != '')
  1613. {
  1614. $prefix .= '.';
  1615. }
  1616. if (strpos($str, '|') !== FALSE)
  1617. {
  1618. $parts = preg_split('/\|/', $str, -1, PREG_SPLIT_NO_EMPTY);
  1619. $parts = array_map('trim', array_map(array($this->EE->db, 'escape_str'), $parts));
  1620. if (count($parts) > 0)
  1621. {
  1622. if (strncasecmp($parts[0], 'not ', 4) == 0)
  1623. {
  1624. $parts[0] = substr($parts[0], 4);
  1625. $not = 'NOT';
  1626. }
  1627. if ($null === TRUE)
  1628. {
  1629. $sql .= "AND ({$prefix}{$field} {$not} IN ('".implode("','", $parts)."') OR {$prefix}{$field} IS NULL)";
  1630. }
  1631. else
  1632. {
  1633. $sql .= "AND {$prefix}{$field} {$not} IN ('".implode("','", $parts)."')";
  1634. }
  1635. }
  1636. }
  1637. else
  1638. {
  1639. if (strncasecmp($str, 'not ', 4) == 0)
  1640. {
  1641. $str = trim(substr($str, 3));
  1642. $not = '!';
  1643. }
  1644. if ($null === TRUE)
  1645. {
  1646. $sql .= "AND ({$prefix}{$field} {$not}= '".$this->EE->db->escape_str($str)."' OR {$prefix}{$field} IS NULL)";
  1647. }
  1648. else
  1649. {
  1650. $sql .= "AND {$prefix}{$field} {$not}= '".$this->EE->db->escape_str($str)."'";
  1651. }
  1652. }
  1653. return $sql;
  1654. }
  1655. // --------------------------------------------------------------------
  1656. /**
  1657. * AR "AND" or "OR" string for conditional tag parameters
  1658. *
  1659. * This function lets us build a specific type of query
  1660. * needed when tags have conditional parameters:
  1661. *
  1662. * {exp:some_tag param="value1|value2|value3"}
  1663. *
  1664. * Or the parameter can contain "not":
  1665. *
  1666. * {exp:some_tag param="not value1|value2|value3"}
  1667. *
  1668. * This function explodes the pipes and builds an AR query.
  1669. *
  1670. * We should probably put this in the DB class but it's not
  1671. * something that is typically used
  1672. *
  1673. * @access public
  1674. * @param string
  1675. * @param string
  1676. * @param string
  1677. * @param bool
  1678. */
  1679. function ar_andor_string($str, $field, $prefix = '', $null=FALSE)
  1680. {
  1681. if ($str == "" OR $field == "")
  1682. {
  1683. return '';
  1684. }
  1685. $str = trim($str);
  1686. if ($prefix != '')
  1687. {
  1688. $prefix .= '.';
  1689. }
  1690. if (strpos($str, '|') !== FALSE)
  1691. {
  1692. $parts = preg_split('/\|/', $str, -1, PREG_SPLIT_NO_EMPTY);
  1693. $parts = array_map('trim', array_map(array($this->EE->db, 'escape_str'), $parts));
  1694. if (count($parts) > 0)
  1695. {
  1696. if ($null === TRUE)
  1697. {
  1698. // MySQL Only
  1699. if (strncasecmp($parts[0], 'not ', 4) == 0)
  1700. {
  1701. $parts[0] = substr($parts[0], 4);
  1702. $sql = "({$prefix}{$field} NOT IN ('".implode("','", $parts)."') OR {$prefix}{$field} IS NULL)";
  1703. }
  1704. else
  1705. {
  1706. $sql = "({$prefix}{$field} IN ('".implode("','", $parts)."') OR {$prefix}{$field} IS NULL)";
  1707. }
  1708. $this->EE->db->where($sql);
  1709. // END MySQL Only
  1710. }
  1711. else
  1712. {
  1713. if (strncasecmp($parts[0], 'not ', 4) == 0)
  1714. {
  1715. $parts[0] = substr($parts[0], 4);
  1716. $this->EE->db->where_not_in($prefix.$field, $parts);
  1717. }
  1718. else
  1719. {
  1720. $this->EE->db->where_in($prefix.$field, $parts);
  1721. }
  1722. }
  1723. }
  1724. }
  1725. else
  1726. {
  1727. if ($null === TRUE)
  1728. {
  1729. // MySQL Only
  1730. if (strncasecmp($str, 'not ', 4) == 0)
  1731. {
  1732. $str = trim(substr($str, 3));
  1733. $sql = "({$prefix}{$field} != '".$this->EE->db->escape_str($str)."' OR {$prefix}{$field} IS NULL)";
  1734. }
  1735. else
  1736. {
  1737. $sql = "({$prefix}{$field} = '".$this->EE->db->escape_str($str)."' OR {$prefix}{$field} IS NULL)";
  1738. }
  1739. $this->EE->db->where($sql);
  1740. // END MySQL Only
  1741. }
  1742. else
  1743. {
  1744. if (strncasecmp($str, 'not ', 4) == 0)
  1745. {
  1746. $str = trim(substr($str, 3));
  1747. $this->EE->db->where($prefix.$field.' !=', $str);
  1748. }
  1749. else
  1750. {
  1751. $this->EE->db->where($prefix.$field, $str);
  1752. }
  1753. }
  1754. }
  1755. }
  1756. // --------------------------------------------------------------------
  1757. /**
  1758. * Assign Conditional Variables
  1759. *
  1760. * @access public
  1761. * @param string
  1762. * @param string
  1763. * @param string
  1764. * @param string
  1765. * @return array
  1766. */
  1767. function assign_conditional_variables($str, $slash = '/', $LD = '{', $RD = '}')
  1768. {
  1769. // The first half of this function simply gathers the openging "if" tags
  1770. // and a numeric value that corresponds to the depth of nesting.
  1771. // The second half parses out the chunks
  1772. $conds = array();
  1773. $var_cond = array();
  1774. $modified_str = $str; // Not an alias!
  1775. // Find the conditionals.
  1776. // Added a \s in there to make sure it does not match {if:elseif} or {if:else} would would give
  1777. // us a bad array and cause havoc.
  1778. if ( ! preg_match_all("/".$LD."if(\s.*?)".$RD."/s", $modified_str, $eek))
  1779. {
  1780. return $var_cond;
  1781. }
  1782. $total_conditionals = count($eek[0]);
  1783. // Mark all opening conditionals, sequentially.
  1784. if (count($modified_str) > 0)
  1785. {
  1786. for ($i = 0; $i < $total_conditionals; $i++)
  1787. {
  1788. // Embedded variable fix
  1789. if ($ld_location = strpos($eek[1][$i],$LD))
  1790. {
  1791. if (preg_match_all("|".preg_quote($eek[0][$i])."(.*?)".$RD."|s", $modified_str, $fix_eek))
  1792. {
  1793. if (count($fix_eek) > 0)
  1794. {
  1795. $eek[0][$i] = $fix_eek[0][0];
  1796. $eek[1][$i] .= $RD.$fix_eek[1][0];
  1797. }
  1798. }
  1799. }
  1800. $modified_string_length = strlen($eek[1][$i]);
  1801. $replace_value[$i] = $LD.'if'.$i;
  1802. $p1 = strpos($modified_str,$eek[0][$i]);
  1803. $p2 = $p1+strlen($replace_value[$i].$eek[1][$i])-strlen($i);
  1804. $p3 = strlen($modified_str);
  1805. $modified_str = substr($modified_str,0,$p1).$replace_value[$i].$eek[1][$i].substr($modified_str,$p2, $p3);
  1806. }
  1807. }
  1808. // Mark all closing conditions.
  1809. $closed_position = array();
  1810. for ($t=$i-1; $t >= 0; $t--)
  1811. {
  1812. // Find the conditional's start
  1813. $coordinate = strpos($modified_str, $LD.'if'.$t);
  1814. // Find the shortned string.
  1815. $shortened = substr($modified_str, $coordinate);
  1816. // Find the conditional's end. Should be first closing tag.
  1817. $closed_position = strpos($shortened,$LD.$slash.'if'.$RD);
  1818. // Location of the next closing tag in main content var
  1819. $p1 = $coordinate + $closed_position;
  1820. $p2 = $p1 + strlen($LD.$slash.'if'.$t.$RD) - 1;
  1821. $modified_str = substr($modified_str,0,$p1).$LD.$slash.'if'.$t.$RD.substr($modified_str,$p2);
  1822. }
  1823. // Create Rick's array
  1824. for ($i = 0; $i < $total_conditionals; $i++)
  1825. {
  1826. $p1 = strpos($modified_str, $LD.'if'.$i.' ');
  1827. $p2 = strpos($modified_str, $LD.$slash.'if'.$i.$RD);
  1828. $length = $p2-$p1;
  1829. $text_range = substr($modified_str,$p1,$length);
  1830. // We use \d here because we want to look for one of the 'marked' conditionals, but
  1831. // not an Advanced Conditional, which would have a colon
  1832. if (preg_match_all("/".$LD."if(\d.*?)".$RD."/", $text_range, $depth_check))
  1833. {
  1834. // Depth is minus one, since it counts itself
  1835. $conds[] = array($LD.'if'.$eek[1][$i].$RD, count($depth_check[0]));
  1836. }
  1837. }
  1838. // Create detailed conditional array
  1839. $float = $str;
  1840. $CE = $LD.$slash.'if'.$RD;
  1841. $offset = strlen($CE);
  1842. $start = 1;
  1843. $duplicates = array();
  1844. foreach ($conds as $key => $val)
  1845. {
  1846. if ($val[1] > $start) $start = $val[1];
  1847. $open_tag = strpos($float, $val[0]);
  1848. $float = substr($float, $open_tag);
  1849. $temp = $float;
  1850. $len = 0;
  1851. $duplicates = array();
  1852. $i = 1;
  1853. while (FALSE !== ($in_point = strpos($temp, $CE)))
  1854. {
  1855. $temp = substr($temp, $in_point + $offset);
  1856. $len += $in_point + $offset;
  1857. if ($i === $val[1])
  1858. {
  1859. $tag = str_replace($LD, '', $val[0]);
  1860. $tag = str_replace($RD, '', $tag);
  1861. $outer = substr($float, 0, $len);
  1862. if (isset($duplicates[$val[1]]) && in_array($outer, $duplicates[$val[1]]))
  1863. {
  1864. break;
  1865. }
  1866. $duplicates[$val[1]][] = $outer;
  1867. $inner = substr($outer, strlen($val[0]), -$offset);
  1868. $tag = str_replace("|", "\|", $tag);
  1869. $tagb = preg_replace("/^if/", "", $tag);
  1870. $field = ( ! preg_match("#(\S+?)\s*(\!=|==|<|>|<=|>=|<>)#s", $tag, $match)) ? trim($tagb) : $match[1];
  1871. // Array prototype:
  1872. // offset 0: the full opening tag sans delimiters: if extended
  1873. // offset 1: the complete conditional chunk
  1874. // offset 2: the inner conditional chunk
  1875. // offset 3: the field name
  1876. $var_cond[$val[1]][] = array($tag, $outer, $inner, $field);
  1877. $float = substr($float, strlen($val[0]));
  1878. break;
  1879. }
  1880. $i++;
  1881. }
  1882. }
  1883. // Parse Order
  1884. $final_conds = array();
  1885. for ($i=$start; $i > 0; --$i)
  1886. {
  1887. if (isset($var_cond[$i])) $final_conds = array_merge($final_conds, $var_cond[$i]);
  1888. }
  1889. return $final_conds;
  1890. }
  1891. // --------------------------------------------------------------------
  1892. /**
  1893. * Assign Tag Variables
  1894. *
  1895. * This function extracts the variables contained within the current tag
  1896. * being parsed and assigns them to one of three arrays.
  1897. *
  1898. * There are three types of variables:
  1899. *
  1900. * Simple variables: {some_variable}
  1901. *
  1902. * Paired variables: {variable} stuff... {/variable}
  1903. *
  1904. * Contidionals: {if something != 'val'} stuff... {if something}
  1905. *
  1906. * Each of the three variables is parsed slightly different and appears in its own array
  1907. *
  1908. * @access public
  1909. * @param string
  1910. * @param string
  1911. * @return array
  1912. */
  1913. function assign_variables($str = '', $slash = '/')
  1914. {
  1915. $return['var_single'] = array();
  1916. $return['var_pair'] = array();
  1917. if ($str == '')
  1918. {
  1919. return $return;
  1920. }
  1921. // No variables? No reason to continue...
  1922. if (strpos($str, '{') === FALSE OR ! preg_match_all("/".LD."(.+?)".RD."/", $str, $matches))
  1923. {
  1924. return $return;
  1925. }
  1926. $temp_close = array();
  1927. $temp_misc = array();
  1928. $slash_length = strlen($slash);
  1929. foreach($matches[1] as $key => $val)
  1930. {
  1931. if (strncmp($val, 'if ', 3) !== 0 &&
  1932. strncmp($val, 'if:', 3) !== 0 &&
  1933. substr($val, 0, $slash_length+2) != $slash."if")
  1934. {
  1935. if (strpos($val, '{') !== FALSE)
  1936. {
  1937. if (preg_match("/(.+?)".LD."(.*)/s", $val, $matches2))
  1938. {
  1939. $temp_misc[$key] = $matches2[2];
  1940. }
  1941. }
  1942. elseif (strncmp($val, $slash, $slash_length) === 0)
  1943. {
  1944. $temp_close[$key] = str_replace($slash, '', $val);
  1945. }
  1946. else
  1947. {
  1948. $temp_misc[$key] = $val;
  1949. }
  1950. }
  1951. elseif (strpos($val, '{') !== FALSE) // Variable in conditional. ::sigh::
  1952. {
  1953. $full_conditional = substr($this->full_tag($matches[0][$key], $str), 1, -1);
  1954. // We only need the first match here, all others will get caught by our
  1955. // previous code as they won't start with if.
  1956. if (preg_match("/".LD."(.*?)".RD."/s", $full_conditional, $cond_vars))
  1957. {
  1958. $temp_misc[$key] = $cond_vars[1];
  1959. }
  1960. }
  1961. }
  1962. // $temp_misc contains all (opening) tags
  1963. // $temp_close contains all closing tags
  1964. // In 1.x we assumed that a closing tag meant that the variable was
  1965. // a tag pair. We now have variables that output as pairs and single tags
  1966. // so we need to properly match the pairs.
  1967. // In order to find proper pairs, we need to find equivalent opening and
  1968. // closing tags that are closest together (no nesting).
  1969. // The easiest way to go about this is to find all opening tags up to a
  1970. // closing tag - and then just take the last one.
  1971. $temp_pair = array();
  1972. $temp_single = array();
  1973. $open_stack = array();
  1974. foreach($temp_misc as $key => $item)
  1975. {
  1976. foreach($temp_close as $idx => $row)
  1977. {
  1978. // Find the closest (potential) closing tag following it
  1979. if (($idx > $key) && substr($item, 0, strlen($row)) == $row)
  1980. {
  1981. // There could be another opening tag between these
  1982. // so we create a stack of opening tag values
  1983. $open_stack[$idx][] = $key;
  1984. continue;
  1985. }
  1986. }
  1987. }
  1988. // Pop the last item off each stack of opening tags - these are pairs
  1989. foreach($open_stack as $potential_openings)
  1990. {
  1991. $open_tag_key = array_pop($potential_openings);
  1992. if (isset($temp_misc[$open_tag_key]))
  1993. {
  1994. $temp_pair[] = $temp_misc[$open_tag_key];
  1995. unset($temp_misc[$open_tag_key]);
  1996. }
  1997. }
  1998. // The rest of them are single tags
  1999. $temp_single = array_values($temp_misc);
  2000. // Weed out the duplicatess
  2001. $temp_single = array_unique($temp_single);
  2002. $temp_pair = array_unique($temp_pair);
  2003. // Assign Single Variables
  2004. $var_single = array();
  2005. foreach($temp_single as $val)
  2006. {
  2007. // simple conditionals
  2008. if (stristr($val, '\|') && substr($val, 0, 6) != 'switch' && substr($val, 0, 11) != 'multi_field')
  2009. {
  2010. $var_single[$val] = $this->fetch_simple_conditions($val);
  2011. }
  2012. // date variables
  2013. elseif (strpos($val, 'format') !== FALSE && preg_match("/.+?\s+?format/", $val))
  2014. {
  2015. $var_single[$val] = $this->fetch_date_variables($val);
  2016. }
  2017. else // single variables
  2018. {
  2019. $var_single[$val] = $val;
  2020. }
  2021. }
  2022. // Assign Variable Pairs
  2023. $var_pair = array();
  2024. foreach($temp_pair as $val)
  2025. {
  2026. $var_pair[$val] = $this->assign_parameters($val);
  2027. }
  2028. $return['var_single'] = $var_single;
  2029. $return['var_pair'] = $var_pair;
  2030. return $return;
  2031. }
  2032. // --------------------------------------------------------------------
  2033. /**
  2034. * Find the Full Opening Tag
  2035. *
  2036. * @access public
  2037. * @param string
  2038. * @param string
  2039. * @param string
  2040. * @param string
  2041. * @return string
  2042. */
  2043. function full_tag($str, $chunk='', $open='', $close='')
  2044. {
  2045. if ($chunk == '') $chunk = (isset($this->EE->TMPL) && is_object($this->EE->TMPL)) ? $this->EE->TMPL->fl_tmpl : '';
  2046. if ($open == '') $open = LD;
  2047. if ($close == '') $close = RD;
  2048. // Warning: preg_match() Compilation failed: regular expression is too large at offset #
  2049. // This error will occur if someone tries to stick over 30k-ish strings as tag parameters that also happen to include curley brackets.
  2050. // Instead of preventing the error, we let it take place, so the user will hopefully visit the forums seeking assistance
  2051. if ( ! preg_match("/".preg_quote($str, '/')."(.*?)".$close."/s", $chunk, $matches))
  2052. {
  2053. return $str;
  2054. }
  2055. if (isset($matches[1]) && $matches[1] != '' && stristr($matches[1], $open) !== false)
  2056. {
  2057. $matches[0] = $this->full_tag($matches[0], $chunk, $open, $close);
  2058. }
  2059. return $matches[0];
  2060. }
  2061. // --------------------------------------------------------------------
  2062. /**
  2063. * Fetch simple conditionals
  2064. *
  2065. * @access public
  2066. * @param string
  2067. * @return string
  2068. */
  2069. function fetch_simple_conditions($str)
  2070. {
  2071. if ($str == '')
  2072. {
  2073. return;
  2074. }
  2075. $str = str_replace(' ', '', trim($str, '|'));
  2076. return explode('|', $str);
  2077. }
  2078. // --------------------------------------------------------------------
  2079. /**
  2080. * Fetch date variables
  2081. *
  2082. * This function looks for a variable that has this prototype:
  2083. *
  2084. * {date format="%Y %m %d"}
  2085. *
  2086. * If found, returns only the datecodes: %Y %m %d
  2087. *
  2088. * @access public
  2089. * @param string
  2090. * @return string
  2091. */
  2092. function fetch_date_variables($datestr)
  2093. {
  2094. if ($datestr == '')
  2095. return;
  2096. if ( ! preg_match("/format\s*=\s*[\'|\"](.*?)[\'|\"]/s", $datestr, $match))
  2097. return FALSE;
  2098. return $match[1];
  2099. }
  2100. // --------------------------------------------------------------------
  2101. /**
  2102. * Return parameters as an array
  2103. *
  2104. * Creates an associative array from a string
  2105. * of parameters: sort="asc" limit="2" etc.
  2106. *
  2107. * @access public
  2108. * @param string
  2109. * @return bool
  2110. */
  2111. function assign_parameters($str)
  2112. {
  2113. if ($str == "")
  2114. return FALSE;
  2115. // \047 - Single quote octal
  2116. // \042 - Double quote octal
  2117. // I don't know for sure, but I suspect using octals is more reliable than ASCII.
  2118. // I ran into a situation where a quote wasn't being matched until I switched to octal.
  2119. // I have no idea why, so just to be safe I used them here. - Rick
  2120. // matches[0] => attribute and value
  2121. // matches[1] => attribute name
  2122. // matches[2] => single or double quote
  2123. // matches[3] => attribute value
  2124. preg_match_all("/(\S+?)\s*=\s*(\042|\047)([^\\2]*?)\\2/is", $str, $matches, PREG_SET_ORDER);
  2125. if (count($matches) > 0)
  2126. {
  2127. $result = array();
  2128. foreach($matches as $match)
  2129. {
  2130. $result[$match[1]] = (trim($match[3]) == '') ? $match[3] : trim($match[3]);
  2131. }
  2132. return $result;
  2133. }
  2134. return FALSE;
  2135. }
  2136. // --------------------------------------------------------------------
  2137. /**
  2138. * Prep conditional
  2139. *
  2140. * This function lets us do a little prepping before
  2141. * running any conditionals through eval()
  2142. *
  2143. * @access public
  2144. * @param string
  2145. * @return string
  2146. */
  2147. function prep_conditional($cond = '')
  2148. {
  2149. $cond = preg_replace("/^if/", "", $cond);
  2150. if (preg_match("/(\S+)\s*(\!=|==|<=|>=|<>|<|>)\s*(.+)/", $cond, $match))
  2151. {
  2152. $cond = trim($match[1]).' '.trim($match[2]).' '.trim($match[3]);
  2153. }
  2154. $rcond = substr($cond, strpos($cond, ' '));
  2155. $cond = str_replace($rcond, $rcond, $cond);
  2156. // Since we allow the following shorthand condition: {if username}
  2157. // but it's not legal PHP, we'll correct it by adding: != ''
  2158. if ( ! preg_match("/(\!=|==|<|>|<=|>=|<>)/", $cond))
  2159. {
  2160. $cond .= ' != "" ';
  2161. }
  2162. return trim($cond);
  2163. }
  2164. // --------------------------------------------------------------------
  2165. /**
  2166. * Reverse Key Sort
  2167. *
  2168. * @access public
  2169. * @param string
  2170. * @param string
  2171. * @return string
  2172. */
  2173. function reverse_key_sort($a, $b) {return strlen($b) > strlen($a);}
  2174. // --------------------------------------------------------------------
  2175. /**
  2176. * Prep conditionals
  2177. *
  2178. * @access public
  2179. * @param string
  2180. * @param string
  2181. * @param string
  2182. * @param string
  2183. * @return array
  2184. */
  2185. function prep_conditionals($str, $vars, $safety='n', $prefix='')
  2186. {
  2187. if (isset($this->EE->TMPL->embed_vars))
  2188. {
  2189. // If this is being called from a module tag, embedded variables
  2190. // aren't going to be available yet. So this is a quick workaround
  2191. // to ensure advanced conditionals using embedded variables can do
  2192. // their thing in mod tags.
  2193. $vars = array_merge($vars, $this->EE->TMPL->embed_vars);
  2194. }
  2195. if (count($vars) == 0) return $str;
  2196. $switch = array();
  2197. $protect = array();
  2198. $prep_id = $this->random('alpha', 3);
  2199. $embedded_tags = (stristr($str, LD.'exp:')) ? TRUE : FALSE;
  2200. $valid = array('!=','==','<=','>=','<','>','<>',
  2201. 'AND', 'XOR', 'OR','&&','||',
  2202. ')','(',
  2203. 'TRUE', 'FALSE');
  2204. $str = str_replace(LD.'if:else'.RD, unique_marker('if_else_safety'), $str);
  2205. // The ((else)*if) is actually faster than (elseif|if) in PHP 5.0.4,
  2206. // but only by a half a thousandth of a second. However, why not be
  2207. // as efficient as possible? It also gives me a chance to catch some
  2208. // user error mistakes.
  2209. if (preg_match_all("/".preg_quote(LD)."((if:else)*if)\s+(.*?)".preg_quote(RD)."/s", $str, $matches))
  2210. {
  2211. // PROTECT QUOTED TEXT
  2212. // That which is in quotes should be protected and ignored as it will screw
  2213. // up the parsing if the variable is found within a string
  2214. if (preg_match_all('/([\"\'])([^\\1]*?)\\1/s', implode(' ', $matches[3]), $quote_matches))
  2215. {
  2216. foreach($quote_matches[0] as $ii => $quote_match)
  2217. {
  2218. $md5_key = (string) hexdec($prep_id.md5($quote_match));
  2219. $protect[$quote_match] = $md5_key;
  2220. // To better protect quotes inside conditional quotes, we need to
  2221. // determine which kind of quote to surround the newly-encoded string
  2222. $surrounding_quote = surrounding_character($quote_match);
  2223. if (($surrounding_quote != '"' AND $surrounding_quote != "'")
  2224. OR $surrounding_quote === FALSE)
  2225. {
  2226. $surrounding_quote = '"';
  2227. }
  2228. // We do these conversions on variables below, so we need
  2229. // to also do them on the hardcoded values to make sure
  2230. // the conditionals resolve as expected.
  2231. // e.g. {if location == "pony's house"}
  2232. $quote_match = $surrounding_quote.
  2233. str_replace(
  2234. array("'", '"', '(', ')', '$', '{', '}', "\n", "\r", '\\'),
  2235. array('&#39;', '&#34;', '&#40;', '&#41;', '&#36;', '', '', '', '', '&#92;'),
  2236. $quote_matches[2][$ii]
  2237. ).
  2238. $surrounding_quote;
  2239. $switch[$md5_key] = $quote_match;
  2240. }
  2241. $matches[3] = str_replace(array_keys($protect), array_values($protect), $matches[3]);
  2242. // Remove quoted values altogether to find variables...
  2243. $matches['t'] = str_replace($valid, ' ', str_replace(array_values($protect), '', $matches[3]));
  2244. }
  2245. else
  2246. {
  2247. $matches['t'] = str_replace($valid, ' ', $matches[3]);
  2248. }
  2249. // Find what we need, nothing more!!
  2250. $data = array();
  2251. foreach($matches['t'] as $cond)
  2252. {
  2253. if (trim($cond) == '') continue;
  2254. $x = preg_split("/\s+/", trim($cond)); $i=0;
  2255. do
  2256. {
  2257. if (array_key_exists($x[$i], $vars))
  2258. {
  2259. $data[$x[$i]] = trim($vars[$x[$i]]);
  2260. }
  2261. elseif($embedded_tags === TRUE && ! is_numeric($x[$i]))
  2262. {
  2263. $data[$x[$i]] = $x[$i];
  2264. }
  2265. elseif(strncmp($x[$i], 'embed:', 6) == 0)
  2266. {
  2267. $data[$x[$i]] = '';
  2268. }
  2269. if ($i > 500) break; ++$i;
  2270. }
  2271. while(isset($x[$i]));
  2272. }
  2273. // This should prevent, for example, the variable 'comment' from
  2274. // overwriting the variable 'comments'.
  2275. uksort($data, array($this, 'reverse_key_sort'));
  2276. if ($safety == 'y')
  2277. {
  2278. // Make sure we have the same amount of opening conditional tags
  2279. // as closing conditional tags.
  2280. $tstr = preg_replace("/<script.*?".">.*?<\/script>/is", '', $str);
  2281. $opening = substr_count($tstr, LD.'if') - substr_count($tstr, LD.'if:elseif');
  2282. $closing = substr_count($tstr, LD.'/if'.RD);
  2283. if ($opening > $closing)
  2284. {
  2285. $str .= str_repeat(LD.'/if'.RD, $opening-$closing);
  2286. }
  2287. }
  2288. // Prep the data array to remove characters we do not want
  2289. // And also just add the quotes around the value for good measure.
  2290. while (list($key) = each($data))
  2291. {
  2292. if ( is_array($data[$key])) continue;
  2293. // TRUE AND FALSE values are for short hand conditionals,
  2294. // like {if logged_in} and so we have no need to remove
  2295. // unwanted characters and we do not quote it.
  2296. if ($data[$key] != 'TRUE' && $data[$key] != 'FALSE' && ($key != $data[$key] OR $embedded_tags !== TRUE))
  2297. {
  2298. $data[$key] = '"'.
  2299. str_replace(array("'", '"', '(', ')', '$', '{', '}', "\n", "\r", '\\'),
  2300. array('&#39;', '&#34;', '&#40;', '&#41;', '&#36;', '', '', '', '', '&#92;'),
  2301. (strlen($data[$key]) > 100) ? substr(htmlspecialchars($data[$key]), 0, 100) : $data[$key]
  2302. ).
  2303. '"';
  2304. }
  2305. $md5_key = (string) hexdec($prep_id.md5($key));
  2306. $protect[$key] = $md5_key;
  2307. $switch[$md5_key] = $data[$key];
  2308. if ($prefix != '')
  2309. {
  2310. $md5_key = (string) hexdec($prep_id.md5($prefix.$key));
  2311. $protect[$prefix.$key] = $md5_key;
  2312. $switch[$md5_key] = $data[$key];
  2313. }
  2314. }
  2315. $matches[3] = str_replace(array_keys($protect), array_values($protect), $matches[3]);
  2316. if ($safety == 'y')
  2317. {
  2318. $matches['s'] = str_replace($protect, '^', $matches[3]);
  2319. $matches['s'] = preg_replace('/"(.*?)"/s', '^', $matches['s']);
  2320. $matches['s'] = preg_replace("/'(.*?)'/s", '^', $matches['s']);
  2321. $matches['s'] = str_replace($valid, ' ', $matches['s']);
  2322. $matches['s'] = preg_replace("/(^|\s+)[0-9]+(\s|$)/", ' ', $matches['s']); // Remove unquoted numbers
  2323. $done = array();
  2324. }
  2325. for($i=0, $s = count($matches[0]); $i < $s; ++$i)
  2326. {
  2327. if ($safety == 'y' && ! in_array($matches[0][$i], $done))
  2328. {
  2329. $done[] = $matches[0][$i];
  2330. // Make sure someone did put in an {if:else conditional}
  2331. // when they likely meant to have an {if:elseif conditional}
  2332. if ($matches[2][$i] == '' &&
  2333. substr($matches[3][$i], 0, 5) == ':else' &&
  2334. $matches[1][$i] == 'if')
  2335. {
  2336. $matches[3][$i] = substr($matches[3][$i], 5);
  2337. $matches[2][$i] == 'elseif';
  2338. trigger_error('Invalid Conditional, Assumed ElseIf : '.str_replace(' :else',
  2339. ':else',
  2340. $matches[0][$i]),
  2341. E_USER_WARNING);
  2342. }
  2343. // If there are parentheses, then we
  2344. // try to make sure they match up correctly.
  2345. $left = substr_count($matches[3][$i], '(');
  2346. $right = substr_count($matches[3][$i], ')');
  2347. if ($left > $right)
  2348. {
  2349. $matches[3][$i] .= str_repeat(')', $left-$right);
  2350. }
  2351. elseif ($right > $left)
  2352. {
  2353. $matches[3][$i] = str_repeat('(', $right-$left).$matches[3][$i];
  2354. }
  2355. // Check for unparsed variables
  2356. if (trim($matches['s'][$i]) != '' && trim($matches['s'][$i]) != '^')
  2357. {
  2358. $x = preg_split("/\s+/", trim($matches['s'][$i]));
  2359. for($j=0, $sj=count($x); $j < $sj; ++$j)
  2360. {
  2361. if ($x[$j] == '^') continue;
  2362. if (substr($x[$j], 0, 1) != '^')
  2363. {
  2364. // We have an unset variable in the conditional.
  2365. // Set the unparsed variable to FALSE
  2366. $matches[3][$i] = str_replace($x[$j], 'FALSE', $matches[3][$i]);
  2367. if ($this->conditional_debug === TRUE)
  2368. {
  2369. trigger_error('Unset EE Conditional Variable ('.$x[$j].') : '.$matches[0][$i],
  2370. E_USER_WARNING);
  2371. }
  2372. }
  2373. else
  2374. {
  2375. // There is a partial variable match being done
  2376. // because they are doing something like segment_11
  2377. // when there is no such variable but there is a segment_1
  2378. // echo $x[$j]."\n<br />\n";
  2379. trigger_error('Invalid EE Conditional Variable: '.
  2380. $matches[0][$i],
  2381. E_USER_WARNING);
  2382. // Set entire conditional to FALSE since it fails
  2383. $matches[3][$i] = 'FALSE';
  2384. }
  2385. }
  2386. }
  2387. }
  2388. $matches[3][$i] = LD.$matches[1][$i].' '.trim($matches[3][$i]).RD;
  2389. }
  2390. $str = str_replace($matches[0], $matches[3], $str);
  2391. $str = str_replace(array_keys($switch), array_values($switch), $str);
  2392. }
  2393. unset($data);
  2394. unset($switch);
  2395. unset($matches);
  2396. unset($protect);
  2397. $str = str_replace(unique_marker('if_else_safety'),LD.'if:else'.RD, $str);
  2398. return $str;
  2399. }
  2400. // --------------------------------------------------------------------
  2401. /**
  2402. * Fetch file upload paths
  2403. *
  2404. * @access public
  2405. * @return array
  2406. */
  2407. function fetch_file_paths()
  2408. {
  2409. if ( ! empty($this->file_paths))
  2410. {
  2411. return $this->file_paths;
  2412. }
  2413. // if $this->file_paths === FALSE,
  2414. // we've queried and have nuttin
  2415. if ($this->file_paths === FALSE)
  2416. {
  2417. return array();
  2418. }
  2419. $this->EE->load->model('file_upload_preferences_model');
  2420. $upload_prefs = $this->EE->file_upload_preferences_model->get_file_upload_preferences(NULL, NULL, TRUE);
  2421. if (count($upload_prefs) == 0)
  2422. {
  2423. // Set $this->file_paths to FALSE so we check for it
  2424. // the next time through a coupla lines up.
  2425. // by default it's array().
  2426. $this->file_paths = FALSE;
  2427. return array();
  2428. }
  2429. foreach ($upload_prefs as $row)
  2430. {
  2431. $this->file_paths[$row['id']] = $row['url'];
  2432. }
  2433. return $this->file_paths;
  2434. }
  2435. // --------------------------------------------------------------------
  2436. /**
  2437. * Clones an Object
  2438. *
  2439. * This is required because of the way PHP 5 handles the passing of objects
  2440. * @php4
  2441. *
  2442. * @deprecated as of EE 2.1.2
  2443. * @param object
  2444. * @return object
  2445. */
  2446. function clone_object($object)
  2447. {
  2448. $this->EE->load->library('logger');
  2449. $this->EE->logger->deprecated('2.1.2');
  2450. return clone $object;
  2451. }
  2452. // --------------------------------------------------------------------
  2453. /**
  2454. * bookmarklet qstr decode
  2455. *
  2456. * @param string
  2457. */
  2458. function bm_qstr_decode($str)
  2459. {
  2460. $str = str_replace("%20", " ", $str);
  2461. $str = str_replace("%uFFA5", "&#8226;", $str);
  2462. $str = str_replace("%uFFCA", " ", $str);
  2463. $str = str_replace("%uFFC1", "-", $str);
  2464. $str = str_replace("%uFFC9", "...", $str);
  2465. $str = str_replace("%uFFD0", "-", $str);
  2466. $str = str_replace("%uFFD1", "-", $str);
  2467. $str = str_replace("%uFFD2", "\"", $str);
  2468. $str = str_replace("%uFFD3", "\"", $str);
  2469. $str = str_replace("%uFFD4", "\'", $str);
  2470. $str = str_replace("%uFFD5", "\'", $str);
  2471. $str = preg_replace("/\%u([0-9A-F]{4,4})/e","'&#'.base_convert('\\1',16,10).';'", $str);
  2472. $str = $this->security->xss_clean(stripslashes(urldecode($str)));
  2473. return $str;
  2474. }
  2475. // --------------------------------------------------------------------
  2476. }
  2477. // END CLASS
  2478. /* End of file Functions.php */
  2479. /* Location: ./system/expressionengine/libraries/Functions.php */