PageRenderTime 68ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/com/core.php

http://github.com/unirgy/buckyball
PHP | 2076 lines | 1300 code | 155 blank | 621 comment | 135 complexity | 16ca255eeaabe1ac94720535e496b80c MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause

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

  1. <?php
  2. /**
  3. * Copyright 2011 Unirgy LLC
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. * @package BuckyBall
  18. * @link http://github.com/unirgy/buckyball
  19. * @author Boris Gurvich <boris@unirgy.com>
  20. * @copyright (c) 2010-2012 Boris Gurvich
  21. * @license http://www.apache.org/licenses/LICENSE-2.0.html
  22. */
  23. define('BNULL', '!@BNULL#$');
  24. /**
  25. * Base class that allows easy singleton/instance creation and method overrides (decorator)
  26. *
  27. * This class is used for all BuckyBall framework base classes
  28. *
  29. * @see BClassRegistry for invokation
  30. */
  31. class BClass
  32. {
  33. /**
  34. * Original class to be used as event prefix to remain constant in overridden classes
  35. *
  36. * Usage:
  37. *
  38. * class Some_Class extends BClass
  39. * {
  40. * static protected $_origClass = __CLASS__;
  41. * }
  42. *
  43. * @var string
  44. */
  45. static protected $_origClass;
  46. /**
  47. * Retrieve original class name
  48. *
  49. * @return string
  50. */
  51. public static function origClass()
  52. {
  53. return static::$_origClass;
  54. }
  55. /**
  56. * Fallback singleton/instance factory
  57. *
  58. * @param bool|object $new if true returns a new instance, otherwise singleton
  59. * if object, returns singleton of the same class
  60. * @param array $args
  61. * @return BClass
  62. */
  63. public static function i($new=false, array $args=array())
  64. {
  65. if (is_object($new)) {
  66. $class = get_class($new);
  67. $new = false;
  68. } else {
  69. $class = get_called_class();
  70. }
  71. return BClassRegistry::i()->instance($class, $args, !$new);
  72. }
  73. public function __call($name, $args)
  74. {
  75. return BClassRegistry::i()->callMethod($this, $name, $args, static::$_origClass);
  76. }
  77. public static function __callStatic($name, $args)
  78. {
  79. return BClassRegistry::i()->callStaticMethod(get_called_class(), $name, $args, static::$_origClass);
  80. }
  81. }
  82. /**
  83. * Main BuckyBall Framework class
  84. *
  85. */
  86. class BApp extends BClass
  87. {
  88. /**
  89. * Registry of supported features
  90. *
  91. * @var array
  92. */
  93. protected static $_compat = array();
  94. /**
  95. * Global app vars registry
  96. *
  97. * @var array
  98. */
  99. protected $_vars = array();
  100. /**
  101. * Flags whether vars shouldn't be changed
  102. *
  103. * @var mixed
  104. */
  105. protected $_isConst = array();
  106. /**
  107. * Verify if a feature is currently supported. Features:
  108. *
  109. * - PHP5.3
  110. *
  111. * @param mixed $feature
  112. * @return boolean
  113. */
  114. public static function compat($feature)
  115. {
  116. if (!empty(static::$_compat[$feature])) {
  117. return static::$_compat[$feature];
  118. }
  119. switch ($feature) {
  120. case 'PHP5.3':
  121. $compat = version_compare(phpversion(), '5.3.0', '>=');
  122. break;
  123. default:
  124. BDebug::error(BLocale::_('Unknown feature: %s', $feature));
  125. }
  126. static::$_compat[$feature] = $compat;
  127. return $compat;
  128. }
  129. /**
  130. * Shortcut to help with IDE autocompletion
  131. *
  132. * @todo Run multiple applications within the same script
  133. * This requires to decide which registries should be app specific
  134. *
  135. * @param bool $new
  136. * @param array $args
  137. * @return BApp
  138. */
  139. public static function i($new=false, array $args=array())
  140. {
  141. return BClassRegistry::i()->instance(__CLASS__, $args, !$new);
  142. }
  143. /**
  144. * Application constructor
  145. *
  146. * Starts debugging session for timing
  147. *
  148. * @return BApp
  149. */
  150. public function __construct()
  151. {
  152. BDebug::i();
  153. umask(0);
  154. }
  155. /**
  156. * Shortcut to add configuration, used mostly from bootstrap index file
  157. *
  158. * @param array|string $config If string will load configuration from file
  159. * @return $this
  160. */
  161. public function config($config)
  162. {
  163. if (is_array($config)) {
  164. BConfig::i()->add($config);
  165. } elseif (is_string($config) && is_file($config)) {
  166. BConfig::i()->addFile($config);
  167. } else {
  168. BDebug::error("Invalid configuration argument");
  169. }
  170. return $this;
  171. }
  172. /**
  173. * Shortcut to scan folders for module manifest files
  174. *
  175. * @param string|array $folders Relative path(s) to manifests. May include wildcards.
  176. * @return $this
  177. */
  178. public function load($folders='.')
  179. {
  180. #echo "<pre>"; print_r(debug_backtrace()); echo "</pre>";
  181. if (is_string($folders)) {
  182. $folders = explode(',', $folders);
  183. }
  184. $modules = BModuleRegistry::i();
  185. foreach ($folders as $folder) {
  186. $modules->scan($folder);
  187. }
  188. return $this;
  189. }
  190. /**
  191. * The last method to be ran in bootstrap index file.
  192. *
  193. * Performs necessary initializations and dispatches requested action.
  194. *
  195. */
  196. public function run()
  197. {
  198. // load session variables
  199. BSession::i()->open();
  200. // bootstrap modules
  201. BModuleRegistry::i()->bootstrap();
  202. // run module migration scripts if necessary
  203. if (BConfig::i()->get('db/implicit_migration')) {
  204. BMigrate::i()->migrateModules(true);
  205. }
  206. // dispatch requested controller action
  207. BRouting::i()->dispatch();
  208. // If session variables were changed, update session
  209. BSession::i()->close();
  210. return $this;
  211. }
  212. /**
  213. * Shortcut for translation
  214. *
  215. * @param string $string Text to be translated
  216. * @param string|array $args Arguments for the text
  217. * @return string
  218. */
  219. public static function t($string, $args=array())
  220. {
  221. return Blocale::_($string, $args);
  222. }
  223. /**
  224. * Shortcut to get a current module or module by name
  225. *
  226. * @param string $modName
  227. * @return BModule
  228. */
  229. public static function m($modName=null)
  230. {
  231. $reg = BModuleRegistry::i();
  232. return is_null($modName) ? $reg->currentModule() : $reg->module($modName);
  233. }
  234. /**
  235. * Shortcut for base URL to use in views and controllers
  236. *
  237. * @param bool $full whether the URL should include schema and host
  238. * @param int $method
  239. * 1 : use config for full url
  240. * 2 : use entry point for full url
  241. * @return string
  242. */
  243. public static function baseUrl($full=true, $method=1)
  244. {
  245. static $baseUrl = array();
  246. $full = (int)$full;
  247. $key = $full.'|'.$method;
  248. if (empty($baseUrl[$key])) {
  249. /** @var BRequest */
  250. $r = BRequest::i();
  251. $c = BConfig::i();
  252. $scriptPath = pathinfo($r->scriptName());
  253. switch ($method) {
  254. case 1:
  255. $url = $c->get('web/base_href');
  256. if (!$url) {
  257. $url = $scriptPath['dirname'];
  258. }
  259. break;
  260. case 2:
  261. $url = $scriptPath['dirname'];
  262. break;
  263. }
  264. if (!($r->modRewriteEnabled() && $c->get('web/hide_script_name'))) {
  265. $url = rtrim($url, "\\"); //for windows installation
  266. $url = rtrim($url, '/') . '/' . $scriptPath['basename'];
  267. }
  268. if ($full) {
  269. $url = $r->scheme().'://'.$r->httpHost().$url;
  270. }
  271. $baseUrl[$key] = rtrim($url, '/').'/';
  272. }
  273. return $baseUrl[$key];
  274. }
  275. /**
  276. * Shortcut to generate URL of module base and custom path
  277. *
  278. * @deprecated by href() and src()
  279. * @param string $modName
  280. * @param string $url
  281. * @param string $method
  282. * @return string
  283. */
  284. public static function url($modName, $url='', $method='baseHref')
  285. {
  286. $m = BApp::m($modName);
  287. if (!$m) {
  288. BDebug::error('Invalid module: '.$modName);
  289. return '';
  290. }
  291. return $m->$method() . $url;
  292. }
  293. public static function href($url='', $full=true, $method=2)
  294. {
  295. return BApp::baseUrl($full, $method)
  296. . BRouting::processHref($url);
  297. }
  298. /**
  299. * Shortcut to generate URL with base src (js, css, images, etc)
  300. *)
  301. * @param string $modName
  302. * @param string $url
  303. * @param string $method
  304. * @return string
  305. */
  306. public static function src($modName, $url='', $method='baseSrc')
  307. {
  308. if ($modName[0]==='@' && !$url) {
  309. list($modName, $url) = explode('/', substr($modName, 1), 2);
  310. }
  311. $m = BApp::m($modName);
  312. if (!$m) {
  313. BDebug::error('Invalid module: '.$modName);
  314. return '';
  315. }
  316. return $m->$method() . '/' . rtrim($url, '/');
  317. }
  318. public function set($key, $val, $const=false)
  319. {
  320. if (!empty($this->_isConst[$key])) {
  321. BDebug::warning('Trying to reset a constant var: '.$key.' = '.$val);
  322. return $this;
  323. }
  324. $this->_vars[$key] = $val;
  325. if ($const) $this->_isConst[$key] = true;
  326. return $this;
  327. }
  328. public function get($key)
  329. {
  330. return isset($this->_vars[$key]) ? $this->_vars[$key] : null;
  331. }
  332. /**
  333. * Helper to get class singletons and instances from templates like Twig
  334. *
  335. * @param string $class
  336. * @param boolean $new
  337. * @param array $args
  338. * @return BClass
  339. */
  340. public function instance($class, $new=false, $args=array())
  341. {
  342. return $class::i($new, $args);
  343. }
  344. }
  345. /**
  346. * Bucky specialized exception
  347. */
  348. class BException extends Exception
  349. {
  350. /**
  351. * Logs exceptions
  352. *
  353. * @param string $message
  354. * @param int $code
  355. * @return BException
  356. */
  357. public function __construct($message="", $code=0)
  358. {
  359. parent::__construct($message, $code);
  360. //BApp::log($message, array(), array('event'=>'exception', 'code'=>$code, 'file'=>$this->getFile(), 'line'=>$this->getLine()));
  361. }
  362. }
  363. /**
  364. * Global configuration storage class
  365. */
  366. class BConfig extends BClass
  367. {
  368. /**
  369. * Configuration storage
  370. *
  371. * @var array
  372. */
  373. protected $_config = array();
  374. /**
  375. * Configuration that will be saved on request
  376. *
  377. * @var array
  378. */
  379. protected $_configToSave = array();
  380. /**
  381. * Enable double data storage for saving?
  382. *
  383. * @var boolean
  384. */
  385. protected $_enableSaving = true;
  386. protected $_encryptedPaths = array();
  387. /**
  388. * Shortcut to help with IDE autocompletion
  389. *
  390. * @return BConfig
  391. */
  392. public static function i($new=false, array $args=array())
  393. {
  394. return BClassRegistry::i()->instance(__CLASS__, $args, !$new);
  395. }
  396. /**
  397. * Add configuration fragment to global tree
  398. *
  399. * @param array $config
  400. * @param boolean $toSave whether this config should be saved in file
  401. * @return BConfig
  402. */
  403. public function add(array $config, $toSave=false)
  404. {
  405. $this->_config = BUtil::arrayMerge($this->_config, $config);
  406. if ($this->_enableSaving && $toSave) {
  407. $this->_configToSave = BUtil::arrayMerge($this->_configToSave, $config);
  408. }
  409. return $this;
  410. }
  411. /**
  412. * Add configuration from file, stored as JSON
  413. *
  414. * @param string $filename
  415. */
  416. public function addFile($filename, $toSave=false)
  417. {
  418. $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  419. #echo "<pre>"; print_r($this); echo "</pre>";
  420. if (!BUtil::isPathAbsolute($filename)) {
  421. $configDir = $this->get('fs/config_dir');
  422. if (!$configDir) {
  423. $configDir = BConfig::i()->get('fs/config_dir');
  424. }
  425. $filename = $configDir.'/'.$filename;
  426. }
  427. if (!is_readable($filename)) {
  428. BDebug::error(BLocale::_('Invalid configuration file name: %s', $filename));
  429. }
  430. switch ($ext) {
  431. case 'php':
  432. $config = include($filename);
  433. break;
  434. case 'yml':
  435. $config = BYAML::i()->load($filename);
  436. break;
  437. case 'json':
  438. $config = BUtil::fromJson(file_get_contents($filename));
  439. break;
  440. }
  441. if (!is_array($config)) {
  442. BDebug::error(BLocale::_('Invalid configuration contents: %s', $filename));
  443. }
  444. $this->add($config, $toSave);
  445. return $this;
  446. }
  447. public function setPathEncrypted($path)
  448. {
  449. $this->_encryptedPaths[$path] = true;
  450. return $this;
  451. }
  452. public function shouldBeEncrypted($path)
  453. {
  454. return !empty($this->_encryptedPaths[$path]);
  455. }
  456. /**
  457. * Set configuration data in $path location
  458. *
  459. * @param string $path slash separated path to the config node
  460. * @param mixed $value scalar or array value
  461. * @param boolean $merge merge new value to old?
  462. * @param bool $toSave
  463. * @return $this
  464. */
  465. public function set($path, $value, $merge=false, $toSave=false)
  466. {
  467. if (is_string($toSave) && $toSave==='_configToSave') { // limit?
  468. $node =& $this->$toSave;
  469. } else {
  470. $node =& $this->_config;
  471. }
  472. if ($this->shouldBeEncrypted($path)) {
  473. }
  474. foreach (explode('/', $path) as $key) {
  475. $node =& $node[$key];
  476. }
  477. if ($merge) {
  478. $node = BUtil::arrayMerge((array)$node, (array)$value);
  479. } else {
  480. $node = $value;
  481. }
  482. if ($this->_enableSaving && true===$toSave) {
  483. $this->set($path, $value, $merge, '_configToSave');
  484. }
  485. return $this;
  486. }
  487. /**
  488. * Get configuration data using path
  489. *
  490. * Ex: BConfig::i()->get('some/deep/config')
  491. *
  492. * @param string $path
  493. * @param boolean $toSave if true, get the configuration from config tree to save
  494. */
  495. public function get($path=null, $toSave=false)
  496. {
  497. $node = $toSave ? $this->_configToSave : $this->_config;
  498. if (is_null($path)) {
  499. return $node;
  500. }
  501. foreach (explode('/', $path) as $key) {
  502. if (!isset($node[$key])) {
  503. return null;
  504. }
  505. $node = $node[$key];
  506. }
  507. return $node;
  508. }
  509. public function writeFile($filename, $config=null, $format=null)
  510. {
  511. if (is_null($config)) {
  512. $config = $this->_configToSave;
  513. }
  514. if (is_null($format)) {
  515. $format = pathinfo($filename, PATHINFO_EXTENSION);
  516. }
  517. switch ($format) {
  518. case 'php':
  519. $contents = "<?php return ".var_export($config, 1).';';
  520. // Additional check for allowed tokens
  521. if ($this->isInvalidManifestPHP($contents)) {
  522. throw new BException('Invalid tokens in configuration found');
  523. }
  524. // a small formatting enhancement
  525. $contents = preg_replace('#=> \n\s+#', '=> ', $contents);
  526. break;
  527. case 'yml':
  528. $contents = BYAML::i()->dump($config);
  529. break;
  530. case 'json':
  531. $contents = BUtil::i()->toJson($config);
  532. break;
  533. }
  534. if (!BUtil::isPathAbsolute($filename)) {
  535. $configDir = $this->get('fs/config_dir');
  536. if (!$configDir) {
  537. $configDir = BConfig::i()->get('fs/config_dir');
  538. }
  539. $filename = $configDir . '/' . $filename;
  540. }
  541. BUtil::ensureDir(dirname($filename));
  542. // Write contents
  543. if (!file_put_contents($filename, $contents, LOCK_EX)) {
  544. BDebug::error('Error writing configuration file: '.$filename);
  545. }
  546. }
  547. public function unsetConfig()
  548. {
  549. $this->_config = array();
  550. }
  551. public function isInvalidManifestPHP($contents)
  552. {
  553. $tokens = token_get_all($contents);
  554. $allowed = array(T_OPEN_TAG=>1, T_RETURN=>1, T_WHITESPACE=>1, T_COMMENT=>1,
  555. T_ARRAY=>1, T_CONSTANT_ENCAPSED_STRING=>1, T_DOUBLE_ARROW=>1,
  556. T_DNUMBER=>1, T_LNUMBER=>1, T_STRING=>1,
  557. '('=>1, ','=>1, ')'=>1, ';'=>1);
  558. $denied = array();
  559. foreach ($tokens as $t) {
  560. if (is_string($t) && !isset($t)) {
  561. $denied[] = $t;
  562. } elseif (is_array($t) && !isset($allowed[$t[0]])) {
  563. $denied[] = token_name($t[0]).': '.$t[1]
  564. .(!empty($t[2]) ? ' ('.$t[2].')':'');
  565. }
  566. }
  567. if (count($denied)) {
  568. return $denied;
  569. }
  570. return false;
  571. }
  572. }
  573. /**
  574. * Registry of classes, class overrides and method overrides
  575. */
  576. class BClassRegistry extends BClass
  577. {
  578. /**
  579. * Self instance for singleton
  580. *
  581. * @var BClassRegistry
  582. */
  583. static protected $_instance;
  584. /**
  585. * Class overrides
  586. *
  587. * @var array
  588. */
  589. protected $_classes = array();
  590. /**
  591. * Method overrides and augmentations
  592. *
  593. * @var array
  594. */
  595. protected $_methods = array();
  596. /**
  597. * Cache for method callbacks
  598. *
  599. * @var array
  600. */
  601. protected $_methodOverrideCache = array();
  602. /**
  603. * Classes that require decoration because of overridden methods
  604. *
  605. * @var array
  606. */
  607. protected $_decoratedClasses = array();
  608. /**
  609. * Property setter/getter overrides and augmentations
  610. *
  611. * @var array
  612. */
  613. protected $_properties = array();
  614. /**
  615. * Registry of singletons
  616. *
  617. * @var array
  618. */
  619. protected $_singletons = array();
  620. /**
  621. * Shortcut to help with IDE autocompletion
  622. *
  623. * @param bool $new
  624. * @param array $args
  625. * @param bool $forceRefresh force the recreation of singleton
  626. * @return BClassRegistry
  627. */
  628. public static function i($new=false, array $args=array(), $forceRefresh=false)
  629. {
  630. if (!static::$_instance) {
  631. static::$_instance = new BClassRegistry;
  632. }
  633. if (!$new && !$forceRefresh) {
  634. return static::$_instance;
  635. }
  636. $class = get_called_class();
  637. return static::$_instance->instance($class, $args, !$new);
  638. }
  639. /**
  640. * Override a class
  641. *
  642. * Usage: BClassRegistry::i()->overrideClass('BaseClass', 'MyClass');
  643. *
  644. * Overridden class should be called one of the following ways:
  645. * - BClassRegistry::i()->instance('BaseClass')
  646. * - BaseClass:i() -- if it extends BClass or has the shortcut defined
  647. *
  648. * Remembering the module that overrode the class for debugging
  649. *
  650. * @todo figure out how to update events on class override
  651. *
  652. * @param string $class Class to be overridden
  653. * @param string $newClass New class
  654. * @param bool $replaceSingleton If there's already singleton of overridden class, replace with new one
  655. * @return BClassRegistry
  656. */
  657. public function overrideClass($class, $newClass, $replaceSingleton=false)
  658. {
  659. $this->_classes[$class] = array(
  660. 'class_name' => $newClass,
  661. 'module_name' => BModuleRegistry::i()->currentModuleName(),
  662. );
  663. BDebug::debug('OVERRIDE CLASS: '.$class.' -> '.$newClass);
  664. if ($replaceSingleton && !empty($this->_singletons[$class]) && get_class($this->_singletons[$class])!==$newClass) {
  665. $this->_singletons[$class] = $this->instance($newClass);
  666. }
  667. return $this;
  668. }
  669. /**
  670. * Dynamically add a class method
  671. *
  672. * @param string $class
  673. * - '*' - will add method to all classes
  674. * - 'extends AbstractClass' - will add method to all classes extending AbstractClass
  675. * - 'implements Interface' - will add method to all classes implementing Interface
  676. * @param string $name
  677. * @param callback $callback
  678. * @return BClassRegistry
  679. */
  680. public function addMethod($class, $method, $callback, $static=false)
  681. {
  682. $arr = explode(' ', $class);
  683. if (!empty($arr[1])) {
  684. $rel = $arr[0];
  685. $class = $arr[1];
  686. } else {
  687. $rel = 'is';
  688. }
  689. $this->_methods[$method][$static ? 1 : 0]['override'][$rel][$class] = array(
  690. 'module_name' => BModuleRegistry::i()->currentModuleName(),
  691. 'callback' => $callback,
  692. );
  693. return $this;
  694. }
  695. /**
  696. * Dynamically override a class method (decorator pattern)
  697. *
  698. * Already existing instances of the class will not be affected.
  699. *
  700. * Usage: BClassRegistry::i()->overrideMethod('BaseClass', 'someMethod', array('MyClass', 'someMethod'));
  701. *
  702. * Overridden class should be called one of the following ways:
  703. * - BClassRegistry::i()->instance('BaseClass')
  704. * - BaseClass:i() -- if it extends BClass or has the shortcut defined
  705. *
  706. * Callback method example (original method had 2 arguments):
  707. *
  708. * class MyClass {
  709. * static public function someMethod($origObject, $arg1, $arg2)
  710. * {
  711. * // do some custom stuff before call to original method here
  712. *
  713. * $origObject->someMethod($arg1, $arg2);
  714. *
  715. * // do some custom stuff after call to original method here
  716. *
  717. * return $origObject;
  718. * }
  719. * }
  720. *
  721. * Remembering the module that overrode the method for debugging
  722. *
  723. * @param string $class Class to be overridden
  724. * @param string $method Method to be overridden
  725. * @param mixed $callback Callback to invoke on method call
  726. * @param bool $static Whether the static method call should be overridden
  727. * @return BClassRegistry
  728. */
  729. public function overrideMethod($class, $method, $callback, $static=false)
  730. {
  731. $this->addMethod($class, $method, $callback, $static);
  732. $this->_decoratedClasses[$class] = true;
  733. return $this;
  734. }
  735. /**
  736. * Dynamically augment class method result
  737. *
  738. * Allows to change result of a method for every invocation.
  739. * Syntax similar to overrideMethod()
  740. *
  741. * Callback method example (original method had 2 arguments):
  742. *
  743. * class MyClass {
  744. * static public function someMethod($result, $origObject, $arg1, $arg2)
  745. * {
  746. * // augment $result of previous object method call
  747. * $result['additional_info'] = 'foo';
  748. *
  749. * return $result;
  750. * }
  751. * }
  752. *
  753. * A difference between overrideModule and augmentModule is that
  754. * you can override only with one another method, but augment multiple times.
  755. *
  756. * If augmented multiple times, each consequetive callback will receive result
  757. * changed by previous callback.
  758. *
  759. * @param string $class
  760. * @param string $method
  761. * @param mixed $callback
  762. * @param boolean $static
  763. * @return BClassRegistry
  764. */
  765. public function augmentMethod($class, $method, $callback, $static=false)
  766. {
  767. $this->_methods[$method][$static ? 1 : 0]['augment']['is'][$class][] = array(
  768. 'module_name' => BModuleRegistry::i()->currentModuleName(),
  769. 'callback' => $callback,
  770. );
  771. $this->_decoratedClasses[$class] = true;
  772. return $this;
  773. }
  774. /**
  775. * Augment class property setter/getter
  776. *
  777. * BClassRegistry::i()->augmentProperty('SomeClass', 'foo', 'set', 'override', 'MyClass::newSetter');
  778. * BClassRegistry::i()->augmentProperty('SomeClass', 'foo', 'get', 'after', 'MyClass::newGetter');
  779. *
  780. * class MyClass {
  781. * static public function newSetter($object, $property, $value)
  782. * {
  783. * $object->$property = myCustomProcess($value);
  784. * }
  785. *
  786. * static public function newGetter($object, $property, $prevResult)
  787. * {
  788. * return $prevResult+5;
  789. * }
  790. * }
  791. *
  792. * @param string $class
  793. * @param string $property
  794. * @param string $op {set|get}
  795. * @param string $type {override|before|after} get_before is not implemented
  796. * @param mixed $callback
  797. * @return BClassRegistry
  798. */
  799. public function augmentProperty($class, $property, $op, $type, $callback)
  800. {
  801. if ($op!=='set' && $op!=='get') {
  802. BDebug::error(BLocale::_('Invalid property augmentation operator: %s', $op));
  803. }
  804. if ($type!=='override' && $type!=='before' && $type!=='after') {
  805. BDebug::error(BLocale::_('Invalid property augmentation type: %s', $type));
  806. }
  807. $entry = array(
  808. 'module_name' => BModuleRegistry::i()->currentModuleName(),
  809. 'callback' => $callback,
  810. );
  811. if ($type==='override') {
  812. $this->_properties[$class][$property][$op.'_'.$type] = $entry;
  813. } else {
  814. $this->_properties[$class][$property][$op.'_'.$type][] = $entry;
  815. }
  816. //have to be added to redefine augmentProperty Setter/Getter methods
  817. $this->_decoratedClasses[$class] = true;
  818. return $this;
  819. }
  820. public function findMethodInfo($class, $method, $static=0, $type='override')
  821. {
  822. //$this->_methods[$method][$static ? 1 : 0]['override'][$rel][$class]
  823. if (!empty($this->_methods[$method][$static][$type]['is'][$class])) {
  824. //return $class;
  825. return $this->_methods[$method][$static][$type]['is'][$class];
  826. }
  827. $cacheKey = $class.'|'.$method.'|'.$static.'|'.$type;
  828. if (!empty($this->_methodOverrideCache[$cacheKey])) {
  829. return $this->_methodOverrideCache[$cacheKey];
  830. }
  831. if (!empty($this->_methods[$method][$static][$type]['extends'])) {
  832. $parents = class_parents($class);
  833. #echo "<pre>"; echo $class.'::'.$method.';'; print_r($parents); print_r($this->_methods[$method][$static][$type]['extends']); echo "</pre><hr>";
  834. foreach ($this->_methods[$method][$static][$type]['extends'] as $c=>$v) {
  835. if (isset($parents[$c])) {
  836. #echo ' * ';
  837. $this->_methodOverrideCache[$cacheKey] = $v;
  838. return $v;
  839. }
  840. }
  841. }
  842. if (!empty($this->_methods[$method][$static][$type]['implements'])) {
  843. $implements = class_implements($class);
  844. foreach ($this->_methods[$method][$static][$type]['implements'] as $i=>$v) {
  845. if (isset($implements[$i])) {
  846. $this->_methodOverrideCache[$cacheKey] = $v;
  847. return $v;
  848. }
  849. }
  850. }
  851. if (!empty($this->_methods[$method][$static][$type]['is']['*'])) {
  852. $v = $this->_methods[$method][$static][$type]['is']['*'];
  853. $this->_methodOverrideCache[$cacheKey] = $v;
  854. return $v;
  855. }
  856. return null;
  857. }
  858. /**
  859. * Check if the callback is callable, accounting for dynamic methods
  860. *
  861. * @param mixed $cb
  862. * @return boolean
  863. */
  864. public function isCallable($cb)
  865. {
  866. if (is_string($cb)) { // plain string callback?
  867. $cb = explode('::', $cb);
  868. if (empty($cb[1])) { // not static?
  869. $cb = BUtil::extCallback($cb); // account for special singleton syntax
  870. }
  871. } elseif (!is_array($cb)) { // unknown?
  872. return is_callable($cb);
  873. }
  874. if (empty($cb[1])) { // regular function?
  875. return function_exists($cb[0]);
  876. }
  877. if (method_exists($cb[0], $cb[1])) { // regular method?
  878. return true;
  879. }
  880. if (is_object($cb[0])) { // instance
  881. if (!$cb[0] instanceof BClass) { // regular class?
  882. return false;
  883. }
  884. return (bool)$this->findMethodInfo(get_class($cb[0]), $cb[1]);
  885. } elseif (is_string($cb[0])) { // static?
  886. return (bool)$this->findMethodInfo($cb[0], $cb[1], 1);
  887. } else { // unknown?
  888. return false;
  889. }
  890. }
  891. /**
  892. * Call overridden method
  893. *
  894. * @param object $origObject
  895. * @param string $method
  896. * @param mixed $args
  897. * @return mixed
  898. */
  899. public function callMethod($origObject, $method, array $args=array(), $origClass=null)
  900. {
  901. //$class = $origClass ? $origClass : get_class($origObject);
  902. $class = get_class($origObject);
  903. if (($info = $this->findMethodInfo($class, $method, 0, 'override'))) {
  904. $callback = $info['callback'];
  905. array_unshift($args, $origObject);
  906. $overridden = true;
  907. } elseif (method_exists($origObject, $method)) {
  908. $callback = array($origObject, $method);
  909. $overridden = false;
  910. } else {
  911. BDebug::error('Invalid method: '.get_class($origObject).'::'.$method);
  912. return null;
  913. }
  914. $result = call_user_func_array($callback, $args);
  915. if (($info = $this->findMethodInfo($class, $method, 0, 'augment'))) {
  916. if (!$overridden) {
  917. array_unshift($args, $origObject);
  918. }
  919. array_unshift($args, $result);
  920. foreach ($info as $augment) {
  921. $result = call_user_func_array($augment['callback'], $args);
  922. $args[0] = $result;
  923. }
  924. }
  925. return $result;
  926. }
  927. /**
  928. * Call static overridden method
  929. *
  930. * Static class properties will not be available to callbacks
  931. *
  932. * @todo decide if this is needed
  933. *
  934. * @param string $class
  935. * @param string $method
  936. * @param array $args
  937. */
  938. public function callStaticMethod($class, $method, array $args=array(), $origClass=null)
  939. {
  940. if (($info = $this->findMethodInfo($class, $method, 1, 'override'))) {
  941. $callback = $info['callback'];
  942. } else {
  943. if (method_exists($class, $method)) {
  944. $callback = array($class, $method);
  945. } else {
  946. throw new Exception('Invalid static method: '.$class.'::'.$method);
  947. }
  948. }
  949. $result = call_user_func_array($callback, $args);
  950. if (($info = $this->findMethodInfo($class, $method, 1, 'augment'))) {
  951. array_unshift($args, $result);
  952. foreach ($info as $augment) {
  953. $result = call_user_func_array($augment['callback'], $args);
  954. $args[0] = $result;
  955. }
  956. }
  957. return $result;
  958. }
  959. /**
  960. * Call augmented property setter
  961. *
  962. * @param object $origObject
  963. * @param string $property
  964. * @param mixed $value
  965. */
  966. public function callSetter($origObject, $property, $value)
  967. {
  968. $class = get_class($origObject);
  969. //print_r($this->_properties);exit;
  970. if (!empty($this->_properties[$class][$property]['set_before'])) {
  971. foreach ($this->_properties[$class][$property]['set_before'] as $entry) {
  972. call_user_func($entry['callback'], $origObject, $property, $value);
  973. }
  974. }
  975. if (!empty($this->_properties[$class][$property]['set_override'])) {
  976. $callback = $this->_properties[$class][$property]['set_override']['callback'];
  977. call_user_func($callback, $origObject, $property, $value);
  978. } else {
  979. $origObject->$property = $value;
  980. }
  981. if (!empty($this->_properties[$class][$property]['set_after'])) {
  982. foreach ($this->_properties[$class][$property]['set_after'] as $entry) {
  983. call_user_func($entry['callback'], $origObject, $property, $value);
  984. }
  985. }
  986. }
  987. /**
  988. * Call augmented property getter
  989. *
  990. * @param object $origObject
  991. * @param string $property
  992. * @return mixed
  993. */
  994. public function callGetter($origObject, $property)
  995. {
  996. $class = get_class($origObject);
  997. // get_before does not make much sense, so is not implemented
  998. if (!empty($this->_properties[$class][$property]['get_override'])) {
  999. $callback = $this->_properties[$class][$property]['get_override']['callback'];
  1000. $result = call_user_func($callback, $origObject, $property);
  1001. } else {
  1002. $result = $origObject->$property;
  1003. }
  1004. if (!empty($this->_properties[$class][$property]['get_after'])) {
  1005. foreach ($this->_properties[$class][$property]['get_after'] as $entry) {
  1006. $result = call_user_func($entry['callback'], $origObject, $property, $result);
  1007. }
  1008. }
  1009. return $result;
  1010. }
  1011. /**
  1012. * Get actual class name for potentially overridden class
  1013. *
  1014. * @param mixed $class
  1015. * @return mixed
  1016. */
  1017. public function className($class)
  1018. {
  1019. return !empty($this->_classes[$class]) ? $this->_classes[$class]['class_name'] : $class;
  1020. }
  1021. /**
  1022. * Get a new instance or a singleton of a class
  1023. *
  1024. * If at least one method of the class if overridden, returns decorator
  1025. *
  1026. * @param string $class
  1027. * @param mixed $args
  1028. * @param bool $singleton
  1029. * @return object
  1030. */
  1031. public function instance($class, array $args=array(), $singleton=false)
  1032. {
  1033. // if singleton is requested and already exists, return the singleton
  1034. if ($singleton && !empty($this->_singletons[$class])) {
  1035. return $this->_singletons[$class];
  1036. }
  1037. // get original or overridden class instance
  1038. $className = $this->className($class);
  1039. if (!class_exists($className, true)) {
  1040. BDebug::error(BLocale::_('Invalid class name: %s', $className));
  1041. }
  1042. $instance = new $className($args);
  1043. // if any methods are overridden or augmented, get decorator
  1044. if (!empty($this->_decoratedClasses[$class])) {
  1045. $instance = $this->instance('BClassDecorator', array($instance));
  1046. }
  1047. // if singleton is requested, save
  1048. if ($singleton) {
  1049. $this->_singletons[$class] = $instance;
  1050. }
  1051. return $instance;
  1052. }
  1053. public function unsetInstance()
  1054. {
  1055. static::$_instance = null;
  1056. }
  1057. }
  1058. /**
  1059. * Decorator class to allow easy method overrides
  1060. *
  1061. */
  1062. class BClassDecorator
  1063. {
  1064. /**
  1065. * Contains the decorated (original) object
  1066. *
  1067. * @var object
  1068. */
  1069. protected $_decoratedComponent;
  1070. /**
  1071. * Decorator constructor, creates an instance of decorated class
  1072. *
  1073. * @param array(object|string $class)
  1074. * @return BClassDecorator
  1075. */
  1076. public function __construct($args)
  1077. {
  1078. //echo '1: '; print_r($class);
  1079. $class = array_shift($args);
  1080. $this->_decoratedComponent = is_string($class) ? BClassRegistry::i()->instance($class, $args) : $class;
  1081. }
  1082. public function __destruct()
  1083. {
  1084. $this->_decoratedComponent = null;
  1085. }
  1086. /**
  1087. * Method override facility
  1088. *
  1089. * @param string $name
  1090. * @param array $args
  1091. * @return mixed Result of callback
  1092. */
  1093. public function __call($name, array $args)
  1094. {
  1095. return BClassRegistry::i()->callMethod($this->_decoratedComponent, $name, $args);
  1096. }
  1097. /**
  1098. * Static method override facility
  1099. *
  1100. * @param mixed $name
  1101. * @param mixed $args
  1102. * @return mixed Result of callback
  1103. */
  1104. public static function __callStatic($name, array $args)
  1105. {
  1106. return BClassRegistry::i()->callStaticMethod(get_called_class(), $name, $args);
  1107. }
  1108. /**
  1109. * Proxy to set decorated component property or a setter
  1110. *
  1111. * @param string $name
  1112. * @param mixed $value
  1113. */
  1114. public function __set($name, $value)
  1115. {
  1116. //$this->_decoratedComponent->$name = $value;
  1117. BClassRegistry::i()->callSetter($this->_decoratedComponent, $name, $value);
  1118. }
  1119. /**
  1120. * Proxy to get decorated component property or a getter
  1121. *
  1122. * @param string $name
  1123. * @return mixed
  1124. */
  1125. public function __get($name)
  1126. {
  1127. //return $this->_decoratedComponent->$name;
  1128. return BClassRegistry::i()->callGetter($this->_decoratedComponent, $name);
  1129. }
  1130. /**
  1131. * Proxy to unset decorated component property
  1132. *
  1133. * @param string $name
  1134. */
  1135. public function __unset($name)
  1136. {
  1137. unset($this->_decoratedComponent->$name);
  1138. }
  1139. /**
  1140. * Proxy to check whether decorated component property is set
  1141. *
  1142. * @param string $name
  1143. * @return boolean
  1144. */
  1145. public function __isset($name)
  1146. {
  1147. return isset($this->_decoratedComponent->$name);
  1148. }
  1149. /**
  1150. * Proxy to return decorated component as string
  1151. *
  1152. * @return string
  1153. */
  1154. public function __toString()
  1155. {
  1156. return (string)$this->_decoratedComponent;
  1157. }
  1158. /**
  1159. * Proxy method to serialize decorated component
  1160. *
  1161. */
  1162. public function __sleep()
  1163. {
  1164. if (method_exists($this->_decoratedComponent, '__sleep')) {
  1165. return $this->_decoratedComponent->__sleep();
  1166. }
  1167. return array();
  1168. }
  1169. /**
  1170. * Proxy method to perform for decorated component on unserializing
  1171. *
  1172. */
  1173. public function __wakeup()
  1174. {
  1175. if (method_exists($this->_decoratedComponent, '__wakeup')) {
  1176. $this->_decoratedComponent->__wakeup();
  1177. }
  1178. }
  1179. /**
  1180. * Proxy method to invoke decorated component as a method if it is callable
  1181. *
  1182. */
  1183. public function __invoke()
  1184. {
  1185. if (is_callable($this->_decoratedComponent)) {
  1186. return $this->_decoratedComponent(func_get_args());
  1187. }
  1188. return null;
  1189. }
  1190. /**
  1191. * Return object of decorated class
  1192. * @return object
  1193. */
  1194. public function getDecoratedComponent()
  1195. {
  1196. return $this->_decoratedComponent;
  1197. }
  1198. }
  1199. class BClassAutoload extends BClass
  1200. {
  1201. public $root_dir;
  1202. public $filename_cb;
  1203. public $module_name;
  1204. public function __construct($params)
  1205. {
  1206. foreach ($params as $k=>$v) {
  1207. $this->$k = $v;
  1208. }
  1209. spl_autoload_register(array($this, 'callback'), false);
  1210. BDebug::debug('AUTOLOAD: '.print_r($this,1));
  1211. }
  1212. /**
  1213. * Default autoload callback
  1214. *
  1215. * @param string $class
  1216. */
  1217. public function callback($class)
  1218. {
  1219. #echo $this->root_dir.' : '.$class.'<br>';
  1220. if ($this->filename_cb) {
  1221. $file = call_user_func($this->filename_cb, $class);
  1222. } else {
  1223. $file = str_replace(array('_', '\\'), array('/', '/'), $class).'.php';
  1224. }
  1225. if ($file) {
  1226. if ($file[0]!=='/' && $file[1]!==':') {
  1227. $file = $this->root_dir.'/'.$file;
  1228. }
  1229. if (file_exists($file)) {
  1230. include ($file);
  1231. }
  1232. }
  1233. }
  1234. }
  1235. /**
  1236. * Events and observers registry
  1237. */
  1238. class BEvents extends BClass
  1239. {
  1240. /**
  1241. * Stores events and observers
  1242. *
  1243. * @todo figure out how to update events on class override
  1244. *
  1245. * @var array
  1246. */
  1247. protected $_events = array();
  1248. /**
  1249. * Shortcut to help with IDE autocompletion
  1250. *
  1251. * @param bool $new
  1252. * @param array $args
  1253. * @return BEvents
  1254. */
  1255. public static function i($new=false, array $args=array())
  1256. {
  1257. return BClassRegistry::i()->instance(__CLASS__, $args, !$new);
  1258. }
  1259. /**
  1260. * Declare event with default arguments in bootstrap function
  1261. *
  1262. * This method is optional and currently not used.
  1263. *
  1264. * @param string|array $eventName accepts multiple events in form of non-associative array
  1265. * @param array|object $args
  1266. * @return BEvents
  1267. */
  1268. public function event($eventName, $args=array())
  1269. {
  1270. if (is_array($eventName)) {
  1271. foreach ($eventName as $event) {
  1272. $this->event($event[0], !empty($event[1]) ? $event[1] : array());
  1273. }
  1274. return $this;
  1275. }
  1276. $eventName = strtolower($eventName);
  1277. $this->_events[$eventName] = array(
  1278. 'observers' => array(),
  1279. 'args' => $args,
  1280. );
  1281. return $this;
  1282. }
  1283. /**
  1284. * Declare observers in bootstrap function
  1285. *
  1286. * observe|watch|on|sub|subscribe ?
  1287. *
  1288. * @param string|array $eventName accepts multiple observers in form of non-associative array
  1289. * @param mixed $callback
  1290. * @param array|object $args
  1291. * @return BEvents
  1292. */
  1293. public function on($eventName, $callback = null, $args = array(), $alias = null)
  1294. {
  1295. if (is_array($eventName)) {
  1296. foreach ($eventName as $obs) {
  1297. $this->on($obs[0], $obs[1], !empty($obs[2]) ? $obs[2] : array());
  1298. }
  1299. return $this;
  1300. }
  1301. if (is_null($alias) && is_string($callback)) {
  1302. $alias = $callback;
  1303. }
  1304. $observer = array('callback' => $callback, 'args' => $args, 'alias' => $alias);
  1305. if (($moduleName = BModuleRegistry::i()->currentModuleName())) {
  1306. $observer['module_name'] = $moduleName;
  1307. }
  1308. //TODO: create named observers
  1309. $eventName = strtolower($eventName);
  1310. $this->_events[$eventName]['observers'][] = $observer;
  1311. BDebug::debug('SUBSCRIBE '.$eventName, 1);
  1312. return $this;
  1313. }
  1314. /**
  1315. * Run callback on event only once, and remove automatically
  1316. *
  1317. * @param string|array $eventName accepts multiple observers in form of non-associative array
  1318. * @param mixed $callback
  1319. * @param array|object $args
  1320. * @return BEvents
  1321. */
  1322. public function once($eventName, $callback=null, $args=array(), $alias = null)
  1323. {
  1324. if (is_array($eventName)) {
  1325. foreach ($eventName as $obs) {
  1326. $this->once($obs[0], $obs[1], !empty($obs[2]) ? $obs[2] : array());
  1327. }
  1328. return $this;
  1329. }
  1330. $this->on($eventName, $callback, $args, $alias);
  1331. $lastId = sizeof($this->_events[$eventName]['observers']);
  1332. $this->on($eventName, function() use ($eventName, $lastId) {
  1333. BEvents::i()
  1334. ->off($eventName, $lastId-1) // remove the observer
  1335. ->off($eventName, $lastId) // remove the remover
  1336. ;
  1337. });
  1338. return $this;
  1339. }
  1340. /**
  1341. * Disable all observers for an event or a specific observer
  1342. *
  1343. * @param string $eventName
  1344. * @param callback $callback
  1345. * @return BEvents
  1346. */
  1347. public function off($eventName, $alias = null)
  1348. {
  1349. $eventName = strtolower($eventName);
  1350. if (true === $alias) { //TODO: null too?
  1351. unset($this->_events[$eventName]);
  1352. return $this;
  1353. }
  1354. if (is_numeric($alias)) {
  1355. unset($this->_events[$eventName]['observers'][$alias]);
  1356. return $this;
  1357. }
  1358. if (!empty($this->_events[$eventName]['observers'])) {
  1359. foreach ($this->_events[$eventName]['observers'] as $i=>$observer) {
  1360. if (!empty($observer['alias']) && $observer['alias'] === $alias) {
  1361. unset($this->_events[$eventName]['observers'][$i]);
  1362. }
  1363. }
  1364. }
  1365. return $this;
  1366. }
  1367. /**
  1368. * Dispatch event observers
  1369. *
  1370. * dispatch|fire|notify|pub|publish ?
  1371. *
  1372. * @param string $eventName
  1373. * @param array|object $args
  1374. * @return array Collection of results from observers
  1375. */
  1376. public function fire($eventName, $args=array())
  1377. {
  1378. $eventName = strtolower($eventName);
  1379. $profileStart = BDebug::debug('FIRE '.$eventName.(empty($this->_events[$eventName])?' (NO SUBSCRIBERS)':''), 1);
  1380. $result = array();
  1381. if (empty($this->_events[$eventName])) {
  1382. return $result;
  1383. }
  1384. $observers =& $this->_events[$eventName]['observers'];
  1385. // sort order observers
  1386. do {
  1387. $dirty = false;
  1388. foreach ($observers as $i=>$observer) {
  1389. if (!empty($observer['args']['position']) && empty($observer['ordered'])) {
  1390. unset($observers[$i]);
  1391. $observer['ordered'] = true;
  1392. $observers = BUtil::arrayInsert($observers, $observer, $observer['position']);
  1393. $dirty = true;
  1394. break;
  1395. }
  1396. }
  1397. } while ($dirty);
  1398. foreach ($observers as $i=>$observer) {
  1399. if (!empty($this->_events[$eventName]['args'])) {
  1400. $args = array_merge($this->_events[$eventName]['args'], $args);
  1401. }
  1402. if (!empty($observer['args'])) {
  1403. $args = array_merge($observer['args'], $args);
  1404. }
  1405. // Set current module to be used in observer callback
  1406. if (!empty($observer['module_name'])) {
  1407. BModuleRegistry::i()->pushModule($observer['module_name']);
  1408. }
  1409. $cb = $observer['callback'];
  1410. // For cases like BView
  1411. if (is_object($cb) && !$cb instanceof Closure) {
  1412. if (method_exists($cb, 'set')) {
  1413. $cb->set($args);
  1414. }
  1415. $result[] = (string)$cb;
  1416. continue;
  1417. }
  1418. // Special singleton syntax
  1419. if (is_string($cb)) {
  1420. foreach (array('.', '->') as $sep) {
  1421. $r = explode($sep, $cb);
  1422. if (sizeof($r)==2) {
  1423. if (!class_exists($r[0])) {
  1424. echo "<pre>"; debug_print_backtrace(); echo "</pre>";
  1425. }
  1426. $cb = array($r[0]::i(), $r[1]);
  1427. $observer['callback'] = $cb;
  1428. // remember for next call, don't want to use &$observer
  1429. $observers[$i]['callback'] = $cb;
  1430. break;
  1431. }
  1432. }
  1433. }
  1434. // Invoke observer
  1435. if (is_callable($cb)) {
  1436. BDebug::debug('ON '.$eventName/*.' : '.var_export($cb, 1)*/, 1);
  1437. $result[] = call_user_func($cb, $args);
  1438. } else {
  1439. BDebug::warning('Invalid callback: '.var_export($cb, 1), 1);
  1440. }
  1441. if (!empty($observer['module_name'])) {
  1442. BModuleRegistry::i()->popModule();
  1443. }
  1444. }
  1445. BDebug::profile($profileStart);
  1446. return $result;
  1447. }
  1448. public function fireRegexp($eventRegexp, $args)
  1449. {
  1450. $results = array();
  1451. foreach ($this->_events as $eventName => $event) {
  1452. if (preg_match($eventRegexp, $eventName)) {
  1453. $results += (array)$this->fire($eventName, $args);
  1454. }
  1455. }
  1456. return $results;
  1457. }
  1458. public function debug()
  1459. {
  1460. echo "<pre>"; print_r($this->_events); echo "</pre>";
  1461. }
  1462. }
  1463. /**
  1464. * Alias for backwards compatibility
  1465. *
  1466. * @deprecated by BEvents
  1467. */
  1468. class BPubSub extends BEvents {}
  1469. /**
  1470. * Facility to handle session state
  1471. */
  1472. class BSession extends BClass
  1473. {
  1474. /**
  1475. * Session data, specific to the application namespace
  1476. *
  1477. * @var array
  1478. */
  1479. public $data = null;
  1480. /**
  1481. * Current sesison ID
  1482. *
  1483. * @var string
  1484. */
  1485. protected $_sessionId;
  1486. /**
  1487. * Whether PHP session is currently open
  1488. *
  1489. * @var bool
  1490. */
  1491. protected $_phpSessionOpen = false;
  1492. /**
  1493. * Whether any session variable was changed since last session save
  1494. *
  1495. * @var bool
  1496. */
  1497. protected $_dirty = false;
  1498. protected $_availableHandlers = array();
  1499. protected $_defaultSessionCookieName = 'buckyball';
  1500. /**
  1501. * Shortcut to help with IDE autocompletion
  1502. *
  1503. * @return BSession
  1504. */
  1505. public static function i($new=false, array $args=array())
  1506. {
  1507. return BClassRegistry::i()->instance(__CLASS__, $args, !$new);
  1508. }
  1509. public function addHandler($name, $class)
  1510. {
  1511. $this->_availableHandlers[$name] = $class;
  1512. }
  1513. public function getHandlers()
  1514. {
  1515. $handlers = array_keys($this->_availableHandlers);
  1516. return array_combine($handlers, $handlers);
  1517. }
  1518. /**
  1519. * Open session
  1520. *
  1521. * @param string|null $id Optional session ID
  1522. * @param bool $autoClose
  1523. * @return $this
  1524. */
  1525. public function open($id=null, $autoClose=false)
  1526. {
  1527. if (!is_null($this->data)) {
  1528. return $this;
  1529. }
  1530. $config = BConfig::i()->get('cookie');
  1531. if (!empty($config['session_disable'])) {
  1532. return $this;
  1533. }
  1534. $ttl = !empty($config['timeout']) ? $config['timeout'] : 3600;
  1535. $path = !empty($config['path']) ? $config['path'] : BConfig::i()->get('web/base_href');
  1536. if (empty($path)) $path = BRequest::i()->webRoot();
  1537. $domain = !empty($config['domain']) ? $config['domain'] : BRequest::i()->httpHost(false);
  1538. if (!empty($config['session_handler']) && !empty($this->_availableHandlers[$config['session_handler']])) {
  1539. $class = $this->_availableHandlers[$config['session_handler']];
  1540. $class::i()->register($ttl);
  1541. }
  1542. //session_set_cookie_params($ttl, $path, $domain);
  1543. session_name(!empty($config['name']) ? $config['name'] : $this->_defaultSessionCookieName);
  1544. if (($dir = BConfig::i()->get('fs/storage_dir'))) {
  1545. $dir .= '/session';
  1546. BUtil::ensureDir($dir);
  1547. session_save_path($dir);
  1548. }
  1549. if (!empty($id) || ($id = BRequest::i()->get('SID'))) {
  1550. session_id($id);
  1551. }
  1552. if (headers_sent()) {
  1553. BDebug::warning("Headers already sent, can't start session");
  1554. } else {
  1555. session_set_cookie_params($ttl, $path, $domain);
  1556. session_start();
  1557. // update session cookie expiration to reflect current visit
  1558. // @see http://www.php.net/manual/en/function.session-set-cookie-params.php#100657
  1559. setcookie(session_name(), session_id(), time()+$ttl, $path, $domain);
  1560. }
  1561. $this->_phpSessionOpen = true;
  1562. $this->_sessionId = session_id();
  1563. if (!empty($config['session_check_ip'])) {
  1564. $ip = BRequest::i()->ip();
  1565. if (empty($_SESSION['_ip'])) {
  1566. $_SESSION

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