PageRenderTime 25ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/symphony/lib/toolkit/class.extensionmanager.php

https://github.com/scottkf/nicholscommunications
PHP | 483 lines | 321 code | 140 blank | 22 comment | 75 complexity | 99cf637f9bfb00c0d523633207ceaad3 MD5 | raw file
  1. <?php
  2. include_once(TOOLKIT . '/class.manager.php');
  3. include_once(TOOLKIT . '/class.extension.php');
  4. define_safe('EXTENSION_ENABLED', 10);
  5. define_safe('EXTENSION_DISABLED', 11);
  6. define_safe('EXTENSION_NOT_INSTALLED', 12);
  7. define_safe('EXTENSION_REQUIRES_UPDATE', 13);
  8. Class ExtensionManager extends Manager{
  9. static private $_enabled_extensions = NULL;
  10. static private $_subscriptions = NULL;
  11. static private $_extensions = NULL;
  12. function __getClassName($name){
  13. return 'extension_' . $name;
  14. }
  15. function __getClassPath($name){
  16. return EXTENSIONS . strtolower("/$name");
  17. }
  18. function __getDriverPath($name){
  19. return $this->__getClassPath($name) . '/extension.driver.php';
  20. }
  21. public function getClassPath($name){
  22. return EXTENSIONS . strtolower("/$name");
  23. }
  24. private function __buildExtensionList() {
  25. if (self::$_extensions == NULL) {
  26. $extensions = Symphony::Database()->fetch("SELECT * FROM `tbl_extensions`");
  27. foreach($extensions as $extension) {
  28. self::$_extensions[$extension['name']] = $extension;
  29. }
  30. }
  31. }
  32. public function sortByStatus($s1, $s2){
  33. if($s1['status'] == EXTENSION_ENABLED) $status_s1 = 2;
  34. elseif(in_array($s1['status'], array(EXTENSION_DISABLED, EXTENSION_NOT_INSTALLED, EXTENSION_REQUIRES_UPDATE))) $status_s1 = 1;
  35. else $status_s1 = 0;
  36. if($s2['status'] == EXTENSION_ENABLED) $status_s2 = 2;
  37. elseif(in_array($s2['status'], array(EXTENSION_DISABLED, EXTENSION_NOT_INSTALLED, EXTENSION_REQUIRES_UPDATE))) $status_s2 = 1;
  38. else $status_s2 = 0;
  39. return $status_s2 - $status_s1;
  40. }
  41. public function sortByName($a, $b) {
  42. return strnatcasecmp($a['name'], $b['name']);
  43. }
  44. public function enable($name){
  45. if(false == ($obj =& $this->create($name))){
  46. trigger_error(__('Could not %1$s %2$s, there was a problem loading the object. Check the driver class exists.', array(__FUNCTION__, $name)), E_USER_WARNING);
  47. return false;
  48. }
  49. ## If not installed, install it
  50. if($this->__requiresInstallation($name) && $obj->install() === false){
  51. return false;
  52. }
  53. ## If requires and upate before enabling, than update it first
  54. elseif(($about = $this->about($name)) && ($previousVersion = $this->__requiresUpdate($about)) !== false) $obj->update($previousVersion);
  55. $id = $this->registerService($name);
  56. ## Now enable the extension
  57. $obj->enable();
  58. unset($obj);
  59. return true;
  60. }
  61. public function disable($name){
  62. if(false == ($obj =& $this->create($name))){
  63. trigger_error(__('Could not %1$s %2$s, there was a problem loading the object. Check the driver class exists.', array(__FUNCTION__, $name)), E_USER_ERROR);
  64. return false;
  65. }
  66. $this->__canUninstallOrDisable($obj);
  67. $id = $this->registerService($name, false);
  68. $obj->disable();
  69. unset($obj);
  70. $this->pruneService($name, true);
  71. return true;
  72. }
  73. public function uninstall($name){
  74. if(false == ($obj =& $this->create($name))){
  75. trigger_error(__('Could not %1$s %2$s, there was a problem loading the object. Check the driver class exists.', array(__FUNCTION__, $name)), E_USER_WARNING);
  76. return false;
  77. }
  78. $this->__canUninstallOrDisable($obj);
  79. $obj->uninstall();
  80. unset($obj);
  81. $this->pruneService($name);
  82. return true;
  83. }
  84. private function __canUninstallOrDisable(Extension $obj){
  85. // Make sure, if this extension has provided Fields, Data Sources or Events, that they aren't in use
  86. $extension_handle = strtolower(preg_replace('/^extension_/i', NULL, get_class($obj)));
  87. // Fields:
  88. if(is_dir(EXTENSIONS . "/{$extension_handle}/fields")){
  89. foreach(glob(EXTENSIONS . "/{$extension_handle}/fields/field.*.php") as $file){
  90. $type = preg_replace(array('/^field\./i', '/\.php$/i'), NULL, basename($file));
  91. if(Symphony::Database()->fetchVar('count', 0, "SELECT COUNT(*) AS `count` FROM `tbl_fields` WHERE `type` = '{$type}'") > 0){
  92. $about = $obj->about();
  93. throw new Exception(
  94. __(
  95. "The field '%s', provided by the Extension '%s', is currently in use. Please remove it from your sections prior to uninstalling or disabling.",
  96. array(basename($file), $about['name'])
  97. )
  98. );
  99. }
  100. }
  101. }
  102. // Data Sources:
  103. if(is_dir(EXTENSIONS . "/{$extension_handle}/data-sources")){
  104. foreach(glob(EXTENSIONS . "/{$extension_handle}/data-sources/data.*.php") as $file){
  105. $handle = preg_replace(array('/^data\./i', '/\.php$/i'), NULL, basename($file));
  106. if(Symphony::Database()->fetchVar('count', 0, "SELECT COUNT(*) AS `count` FROM `tbl_pages` WHERE `data_sources` REGEXP '[[:<:]]{$handle}[[:>:]]' ") > 0){
  107. $about = $obj->about();
  108. throw new Exception(
  109. __(
  110. "The Data Source '%s', provided by the Extension '%s', is currently in use. Please remove it from your pages prior to uninstalling or disabling.",
  111. array(basename($file), $about['name'])
  112. )
  113. );
  114. }
  115. }
  116. }
  117. // Events
  118. if(is_dir(EXTENSIONS . "/{$extension_handle}/events")){
  119. foreach(glob(EXTENSIONS . "/{$extension_handle}/events/event.*.php") as $file){
  120. $handle = preg_replace(array('/^event\./i', '/\.php$/i'), NULL, basename($file));
  121. if(Symphony::Database()->fetchVar('count', 0, "SELECT COUNT(*) AS `count` FROM `tbl_pages` WHERE `events` REGEXP '[[:<:]]{$handle}[[:>:]]' ") > 0){
  122. $about = $obj->about();
  123. throw new Exception(
  124. __(
  125. "The Event '%s', provided by the Extension '%s', is currently in use. Please remove it from your pages prior to uninstalling or disabling.",
  126. array(basename($file), $about['name'])
  127. )
  128. );
  129. }
  130. }
  131. }
  132. }
  133. public function fetchStatus($name){
  134. $this->__buildExtensionList();
  135. $status = self::$_extensions[$name]['status'];
  136. if(!$status) return EXTENSION_NOT_INSTALLED;
  137. if($status == 'enabled') return EXTENSION_ENABLED;
  138. return EXTENSION_DISABLED;
  139. }
  140. public function pruneService($name, $delegates_only=false){
  141. $classname = $this->__getClassName($name);
  142. $path = $this->__getDriverPath($name);
  143. if(!@file_exists($path)) return false;
  144. $delegates = Symphony::Database()->fetchCol('id', "SELECT tbl_extensions_delegates.`id` FROM `tbl_extensions_delegates`
  145. LEFT JOIN `tbl_extensions` ON (`tbl_extensions`.id = `tbl_extensions_delegates`.extension_id)
  146. WHERE `tbl_extensions`.name = '$name'");
  147. if(!empty($delegates)) Symphony::Database()->delete('tbl_extensions_delegates', " `id` IN ('". implode("', '", $delegates). "') ");
  148. if(!$delegates_only) Symphony::Database()->delete('tbl_extensions', " `name` = '$name' ");
  149. ## Remove the unused DB records
  150. $this->__cleanupDatabase();
  151. return true;
  152. }
  153. public function registerService($name, $enable=true){
  154. $classname = $this->__getClassName($name);
  155. $path = $this->__getDriverPath($name);
  156. if(!@file_exists($path)) return false;
  157. require_once($path);
  158. $subscribed = call_user_func(array(&$classname, "getSubscribedDelegates"));
  159. if($existing_id = $this->fetchExtensionID($name))
  160. Symphony::Database()->delete('tbl_extensions_delegates', " `extension_id` = '$existing_id' ");
  161. Symphony::Database()->delete('tbl_extensions', " `name` = '$name' ");
  162. $info = $this->about($name);
  163. Symphony::Database()->insert(
  164. array(
  165. 'name' => $name,
  166. 'status' => ($enable ? 'enabled' : 'disabled'),
  167. 'version' => $info['version']
  168. ),
  169. 'tbl_extensions'
  170. );
  171. $id = Symphony::Database()->getInsertID();
  172. if(is_array($subscribed) && !empty($subscribed)){
  173. foreach($subscribed as $s){
  174. Symphony::Database()->insert(
  175. array(
  176. 'extension_id' => $id,
  177. 'page' => $s['page'],
  178. 'delegate' => $s['delegate'],
  179. 'callback' => $s['callback']
  180. ),
  181. 'tbl_extensions_delegates'
  182. );
  183. }
  184. }
  185. ## Remove the unused DB records
  186. $this->__cleanupDatabase();
  187. return $id;
  188. }
  189. public function listInstalledHandles(){
  190. if(is_null(self::$_enabled_extensions)) {
  191. self::$_enabled_extensions = Symphony::Database()->fetchCol('name',
  192. "SELECT `name` FROM `tbl_extensions` WHERE `status` = 'enabled'"
  193. );
  194. }
  195. return self::$_enabled_extensions;
  196. }
  197. ## Will return a list of all extensions and their about information
  198. public function listAll(){
  199. $extensions = array();
  200. $result = array();
  201. $structure = General::listStructure(EXTENSIONS, array(), false, 'asc', EXTENSIONS);
  202. $extensions = $structure['dirlist'];
  203. if(is_array($extensions) && !empty($extensions)){
  204. foreach($extensions as $e){
  205. if($about = $this->about($e)) $result[$e] = $about;
  206. }
  207. }
  208. return $result;
  209. }
  210. public function notifyMembers($delegate, $page, $context=array()){
  211. if((int)Symphony::Configuration()->get('allow_page_subscription', 'symphony') != 1) return;
  212. if (is_null(self::$_subscriptions)) {
  213. self::$_subscriptions = Symphony::Database()->fetch("
  214. SELECT t1.name, t2.page, t2.delegate, t2.callback
  215. FROM `tbl_extensions` as t1 INNER JOIN `tbl_extensions_delegates` as t2 ON t1.id = t2.extension_id
  216. WHERE t1.status = 'enabled'");
  217. }
  218. // Make sure $page is an array
  219. if(!is_array($page)){
  220. // Support for pseudo-global delegates (including legacy support for /administration/)
  221. if(preg_match('/\/?(administration|backend)\/?/', $page)){
  222. $page = array(
  223. 'backend', '/backend/',
  224. 'administration', '/administration/'
  225. );
  226. }
  227. else{
  228. $page = array($page);
  229. }
  230. }
  231. // Support for global delegate subscription
  232. if(!in_array('*', $page)){
  233. $page[] = '*';
  234. }
  235. $services = null;
  236. foreach(self::$_subscriptions as $subscription) {
  237. foreach($page as $p) {
  238. if ($p == $subscription['page'] && $delegate == $subscription['delegate']) {
  239. if (!is_array($services)) $services = array();
  240. $services[] = $subscription;
  241. }
  242. }
  243. }
  244. if(!is_array($services) || empty($services)) return NULL;
  245. $context += array('parent' => &$this->_Parent, 'page' => $page, 'delegate' => $delegate);
  246. foreach($services as $s){
  247. $obj = $this->create($s['name']);
  248. if(is_object($obj) && in_array($s['callback'], get_class_methods($obj))){
  249. $obj->{$s['callback']}($context);
  250. unset($obj);
  251. }
  252. }
  253. }
  254. ## Creates a new object and returns a pointer to it
  255. public function create($name, $param=array(), $slient=false){
  256. if(!is_array(self::$_pool)) $this->flush();
  257. if(!isset(self::$_pool[$name])){
  258. $classname = $this->__getClassName($name);
  259. $path = $this->__getDriverPath($name);
  260. if(!is_file($path)){
  261. if(!$slient) trigger_error(__('Could not find extension at location %s', array($path)), E_USER_ERROR);
  262. return false;
  263. }
  264. if(!class_exists($classname)) require_once($path);
  265. if(!is_array($param)) $param = array();
  266. if(!isset($param['parent'])) $param['parent'] =& $this->_Parent;
  267. ##Create the object
  268. self::$_pool[$name] = new $classname($param);
  269. }
  270. return self::$_pool[$name];
  271. }
  272. ## Return object instance of a named extension
  273. public function getInstance($name){
  274. $extensions = $this->_pool;
  275. foreach($extensions as $e){
  276. if(get_class($e) == $name) return $e;
  277. }
  278. }
  279. private function __requiresUpdate($info){
  280. if($info['status'] == EXTENSION_NOT_INSTALLED) return false;
  281. $current_version = $this->fetchInstalledVersion($info['handle']);
  282. return (version_compare($current_version, $info['version'], '<') ? $current_version : false);
  283. }
  284. private function __requiresInstallation($name){
  285. $this->__buildExtensionList();
  286. $id = self::$_extensions[$name]['id'];
  287. return (is_numeric($id) ? false : true);
  288. }
  289. public function fetchInstalledVersion($name){
  290. $this->__buildExtensionList();
  291. return $id = self::$_extensions[$name]['version'];
  292. }
  293. public function fetchExtensionID($name){
  294. $this->__buildExtensionList();
  295. return $id = self::$_extensions[$name]['id'];
  296. }
  297. public function fetchCustomMenu($name){
  298. $classname = $this->__getClassName($name);
  299. if(!class_exists($classname)){
  300. $path = $this->__getDriverPath($name);
  301. if(!@file_exists($path)) return false;
  302. require_once($path);
  303. }
  304. return @call_user_func(array(&$classname, 'fetchCustomMenu'));
  305. }
  306. ## Returns the about details of a service
  307. public function about($name){
  308. $classname = $this->__getClassName($name);
  309. $path = $this->__getDriverPath($name);
  310. if(!@file_exists($path)) return false;
  311. require_once($path);
  312. if($about = @call_user_func(array(&$classname, "about"))){
  313. $about['handle'] = $name;
  314. $about['status'] = $this->fetchStatus($name);
  315. $nav = @call_user_func(array(&$classname, 'fetchNavigation'));
  316. if($nav != NULL) $about['navigation'] = $nav;
  317. if($this->__requiresUpdate($about)) $about['status'] = EXTENSION_REQUIRES_UPDATE;
  318. return $about;
  319. }
  320. return false;
  321. }
  322. private function __cleanupDatabase(){
  323. ## Grab any extensions sitting in the database
  324. $rows = Symphony::Database()->fetch("SELECT `name` FROM `tbl_extensions`");
  325. ## Iterate over each row
  326. if(is_array($rows) && !empty($rows)){
  327. foreach($rows as $r){
  328. $name = $r['name'];
  329. ## Grab the install location
  330. $path = $this->__getClassPath($name);
  331. ## If it doesnt exist, remove the DB rows
  332. if(!@is_dir($path)){
  333. if($existing_id = $this->fetchExtensionID($name))
  334. Symphony::Database()->delete("tbl_extensions_delegates", " `extension_id` = $existing_id ");
  335. Symphony::Database()->delete('tbl_extensions', " `name` = '$name' LIMIT 1");
  336. }
  337. }
  338. }
  339. ## Remove old delegate information
  340. $disabled = Symphony::Database()->fetchCol('id', "SELECT `id` FROM `tbl_extensions` WHERE `status` = 'disabled'");
  341. if(!empty($disabled)) Symphony::Database()->delete('tbl_extensions_delegates', " `extension_id` IN ('". implode("', '", $disabled). "') ");
  342. }
  343. }