PageRenderTime 63ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/plugin_api.php

https://github.com/markkimsal/mantisbt
PHP | 926 lines | 531 code | 154 blank | 241 comment | 120 complexity | 50562fab9aa2ab358aecc5c32d4ea99f MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. # MantisBT - A PHP based bugtracking system
  3. # MantisBT is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # MantisBT is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with MantisBT. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Plugin API
  17. *
  18. * Handles the initialisation, management, and execution of plugins.
  19. *
  20. * @package CoreAPI
  21. * @subpackage PluginAPI
  22. * @copyright Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  23. * @copyright Copyright (C) 2002 - 2010 MantisBT Team - mantisbt-dev@lists.sourceforge.net
  24. * @link http://www.mantisbt.org
  25. *
  26. * @uses access_api.php
  27. * @uses config_api.php
  28. * @uses constant_inc.php
  29. * @uses database_api.php
  30. * @uses error_api.php
  31. * @uses event_api.php
  32. * @uses helper_api.php
  33. * @uses history_api.php
  34. * @uses lang_api.php
  35. */
  36. require_api( 'access_api.php' );
  37. require_api( 'config_api.php' );
  38. require_api( 'constant_inc.php' );
  39. require_api( 'database_api.php' );
  40. require_api( 'error_api.php' );
  41. require_api( 'event_api.php' );
  42. require_api( 'helper_api.php' );
  43. require_api( 'history_api.php' );
  44. require_api( 'lang_api.php' );
  45. # Cache variables #####
  46. $g_plugin_cache = array();
  47. $g_plugin_cache_priority = array();
  48. $g_plugin_cache_protected = array();
  49. $g_plugin_current = array();
  50. # Public API #####
  51. /**
  52. * Get the currently executing plugin's basename.
  53. * @return string Plugin basename, or null if no current plugin
  54. */
  55. function plugin_get_current() {
  56. global $g_plugin_current;
  57. return( isset( $g_plugin_current[0] ) ? $g_plugin_current[0] : null );
  58. }
  59. /**
  60. * Add the current plugin to the stack
  61. * @param string Plugin basename
  62. */
  63. function plugin_push_current( $p_basename ) {
  64. global $g_plugin_current;
  65. array_unshift( $g_plugin_current, $p_basename );
  66. }
  67. /**
  68. * Remove the current plugin from the stack
  69. * @return string Plugin basename, or null if no current plugin
  70. */
  71. function plugin_pop_current() {
  72. global $g_plugin_current;
  73. return( isset( $g_plugin_current[0] ) ? array_shift( $g_plugin_current ) : null );
  74. }
  75. /**
  76. * Get the URL to the plugin wrapper page.
  77. * @param string Page name
  78. * @param string Plugin basename (defaults to current plugin)
  79. */
  80. function plugin_page( $p_page, $p_redirect = false, $p_basename = null ) {
  81. if( is_null( $p_basename ) ) {
  82. $t_current = plugin_get_current();
  83. } else {
  84. $t_current = $p_basename;
  85. }
  86. if( $p_redirect ) {
  87. return 'plugin.php?page=' . $t_current . '/' . $p_page;
  88. } else {
  89. return helper_mantis_url( 'plugin.php?page=' . $t_current . '/' . $p_page );
  90. }
  91. }
  92. /**
  93. * Return a path to a plugin file.
  94. * @param string File name
  95. * @param string Plugin basename
  96. * @return mixed File path or false if FNF
  97. */
  98. function plugin_file_path( $p_filename, $p_basename ) {
  99. $t_file_path = config_get( 'plugin_path' );
  100. $t_file_path .= $p_basename . DIRECTORY_SEPARATOR;
  101. $t_file_path .= 'files' . DIRECTORY_SEPARATOR . $p_filename;
  102. return( is_file( $t_file_path ) ? $t_file_path : false );
  103. }
  104. /**
  105. * Get the URL to the plugin wrapper page.
  106. * @param string Page name
  107. * @param string Plugin basename (defaults to current plugin)
  108. */
  109. function plugin_file( $p_file, $p_redirect = false, $p_basename = null ) {
  110. if( is_null( $p_basename ) ) {
  111. $t_current = plugin_get_current();
  112. } else {
  113. $t_current = $p_basename;
  114. }
  115. if( $p_redirect ) {
  116. return 'plugin_file.php?file=' . $t_current . '/' . $p_file;
  117. } else {
  118. return helper_mantis_url( 'plugin_file.php?file=' . $t_current . '/' . $p_file );
  119. }
  120. }
  121. /**
  122. * Include the contents of a file as output.
  123. * @param string File name
  124. * @param string Plugin basename
  125. */
  126. function plugin_file_include( $p_filename, $p_basename = null ) {
  127. if( is_null( $p_basename ) ) {
  128. $t_current = plugin_get_current();
  129. } else {
  130. $t_current = $p_basename;
  131. }
  132. $t_file_path = plugin_file_path( $p_filename, $t_current );
  133. if( false === $t_file_path ) {
  134. trigger_error( ERROR_GENERIC, ERROR );
  135. }
  136. readfile( $t_file_path );
  137. }
  138. /**
  139. * Given a base table name for a plugin, add appropriate prefix and suffix.
  140. * Convenience for plugin schema definitions.
  141. * @param string Table name
  142. * @param string Plugin basename (defaults to current plugin)
  143. * @return string Full table name
  144. */
  145. function plugin_table( $p_name, $p_basename = null ) {
  146. if( is_null( $p_basename ) ) {
  147. $t_current = plugin_get_current();
  148. } else {
  149. $t_current = $p_basename;
  150. }
  151. return db_get_table( 'plugin_' . $t_current . '_' . $p_name );
  152. }
  153. /**
  154. * Get a plugin configuration option.
  155. * @param string Configuration option name
  156. * @param multi Default option value
  157. */
  158. function plugin_config_get( $p_option, $p_default = null, $p_global = false ) {
  159. $t_basename = plugin_get_current();
  160. $t_full_option = 'plugin_' . $t_basename . '_' . $p_option;
  161. if( $p_global ) {
  162. return config_get_global( $t_full_option, $p_default );
  163. } else {
  164. return config_get( $t_full_option, $p_default );
  165. }
  166. }
  167. /**
  168. * Set a plugin configuration option in the database.
  169. * @param string Configuration option name
  170. * @param multi Option value
  171. * @param int User ID
  172. * @param int Project ID
  173. * @param int Access threshold
  174. */
  175. function plugin_config_set( $p_option, $p_value, $p_user = NO_USER, $p_project = ALL_PROJECTS, $p_access = DEFAULT_ACCESS_LEVEL ) {
  176. if( $p_access == DEFAULT_ACCESS_LEVEL ) {
  177. $p_access = config_get_global( 'admin_site_threshold' );
  178. }
  179. $t_basename = plugin_get_current();
  180. $t_full_option = 'plugin_' . $t_basename . '_' . $p_option;
  181. config_set( $t_full_option, $p_value, $p_user, $p_project, $p_access );
  182. }
  183. /**
  184. * Delete a plugin configuration option from the database.
  185. * @param string Configuration option name
  186. * @param int User ID
  187. * @param int Project ID
  188. */
  189. function plugin_config_delete( $p_option, $p_user = ALL_USERS, $p_project = ALL_PROJECTS ) {
  190. $t_basename = plugin_get_current();
  191. $t_full_option = 'plugin_' . $t_basename . '_' . $p_option;
  192. config_delete( $t_full_option, $p_user, $p_project );
  193. }
  194. /**
  195. * Set plugin default values to global values without overriding anything.
  196. * @param array Array of configuration option name/value pairs.
  197. */
  198. function plugin_config_defaults( $p_options ) {
  199. if( !is_array( $p_options ) ) {
  200. return;
  201. }
  202. $t_basename = plugin_get_current();
  203. $t_option_base = 'plugin_' . $t_basename . '_';
  204. foreach( $p_options as $t_option => $t_value ) {
  205. $t_full_option = $t_option_base . $t_option;
  206. config_set_global( $t_full_option, $t_value, false );
  207. }
  208. }
  209. /**
  210. * Get a language string for the plugin.
  211. * Automatically prepends plugin_<basename> to the string requested.
  212. * @param string Language string name
  213. * @param string Plugin basename
  214. * @return string Language string
  215. */
  216. function plugin_lang_get( $p_name, $p_basename = null ) {
  217. if( is_null( $p_basename ) ) {
  218. $t_basename = plugin_get_current();
  219. } else {
  220. $t_basename = $p_basename;
  221. }
  222. $t_name = 'plugin_' . $t_basename . '_' . $p_name;
  223. return lang_get( $t_name );
  224. }
  225. function plugin_history_log( $p_bug_id, $p_field_name, $p_old_value, $p_new_value = '', $p_user_id = null, $p_basename = null ) {
  226. if( is_null( $p_basename ) ) {
  227. $t_basename = plugin_get_current();
  228. } else {
  229. $t_basename = $p_basename;
  230. }
  231. $t_field_name = $t_basename . '_' . $p_field_name;
  232. history_log_event_direct( $p_bug_id, $t_field_name, $p_old_value, $p_new_value, $p_user_id, PLUGIN_HISTORY );
  233. }
  234. /**
  235. * Trigger a plugin-specific error with the given name and type.
  236. * @param string Error name
  237. * @param int Error type
  238. * @param string Plugin basename
  239. */
  240. function plugin_error( $p_error_name, $p_error_type = ERROR, $p_basename = null ) {
  241. if( is_null( $p_basename ) ) {
  242. $t_basename = plugin_get_current();
  243. } else {
  244. $t_basename = $p_basename;
  245. }
  246. $t_error_code = "plugin_${t_basename}_${p_error_name}";
  247. $MANTIS_ERROR = lang_get( 'MANTIS_ERROR' );
  248. if( isset( $MANTIS_ERROR[$t_error_code] ) ) {
  249. trigger_error( $t_error_code, $p_error_type );
  250. } else {
  251. error_parameters( $p_error_name, $t_basename );
  252. trigger_error( ERROR_PLUGIN_GENERIC, ERROR );
  253. }
  254. return null;
  255. }
  256. /**
  257. * Hook a plugin's callback function to an event.
  258. * @param string Event name
  259. * @param string Callback function
  260. */
  261. function plugin_event_hook( $p_name, $p_callback ) {
  262. $t_basename = plugin_get_current();
  263. event_hook( $p_name, $p_callback, $t_basename );
  264. }
  265. /**
  266. * Hook multiple plugin callbacks at once.
  267. * @param array Array of event name/callback key/value pairs
  268. */
  269. function plugin_event_hook_many( $p_hooks ) {
  270. if( !is_array( $p_hooks ) ) {
  271. return;
  272. }
  273. $t_basename = plugin_get_current();
  274. foreach( $p_hooks as $t_event => $t_callbacks ) {
  275. if( !is_array( $t_callbacks ) ) {
  276. event_hook( $t_event, $t_callbacks, $t_basename );
  277. continue;
  278. }
  279. foreach( $t_callbacks as $t_callback ) {
  280. event_hook( $t_event, $t_callback, $t_basename );
  281. }
  282. }
  283. }
  284. /**
  285. * Allows a plugin to declare a 'child plugin' that
  286. * can be loaded from the same parent directory.
  287. * @param string Child plugin basename.
  288. */
  289. function plugin_child( $p_child ) {
  290. $t_basename = plugin_get_current();
  291. $t_plugin = plugin_register( $t_basename, false, $p_child );
  292. if( !is_null( $t_plugin ) ) {
  293. plugin_init( $p_child );
  294. }
  295. return $t_plugin;
  296. }
  297. # ## Plugin Management Helpers
  298. /**
  299. * Checks if a given plugin has been registered and initialized,
  300. * and returns a boolean value representing the "loaded" state.
  301. * @param string Plugin basename
  302. * @return boolean Plugin loaded
  303. */
  304. function plugin_is_loaded( $p_basename ) {
  305. global $g_plugin_cache_init;
  306. return ( isset( $g_plugin_cache_init[ $p_basename ] ) && $g_plugin_cache_init[ $p_basename ] );
  307. }
  308. /**
  309. * Converts a version string to an array, using some punctuation and
  310. * number/lettor boundaries as splitting points.
  311. * @param string Version string
  312. * @return array Version array
  313. */
  314. function plugin_version_array( $p_version ) {
  315. $t_version = preg_replace( '/([a-zA-Z]+)([0-9]+)/', '\1.\2', $p_version );
  316. $t_version = preg_replace( '/([0-9]+)([a-zA-Z]+)/', '\1.\2', $t_version );
  317. $t_search = array(
  318. ',',
  319. '-',
  320. '_',
  321. );
  322. $t_replace = array(
  323. '.',
  324. '.',
  325. '.',
  326. );
  327. $t_version = explode( '.', str_replace( $t_search, $t_replace, $t_version ) );
  328. return $t_version;
  329. }
  330. /**
  331. * Checks two version arrays sequentially for minimum or maximum version dependencies.
  332. * @param array Version array to check
  333. * @param array Version array required
  334. * @param boolean Minimum (false) or maximum (true) version check
  335. * @return int 1 if the version dependency succeeds, -1 if it fails
  336. */
  337. function plugin_version_check( $p_version1, $p_version2, $p_maximum = false ) {
  338. while( count( $p_version1 ) > 0 && count( $p_version2 ) > 0 ) {
  339. # Grab the next version bits
  340. $t_version1 = array_shift( $p_version1 );
  341. $t_version2 = array_shift( $p_version2 );
  342. # Convert to integers if possible
  343. if( is_numeric( $t_version1 ) ) {
  344. $t_version1 = (int) $t_version1;
  345. }
  346. if( is_numeric( $t_version2 ) ) {
  347. $t_version2 = (int) $t_version2;
  348. }
  349. # Check for immediate version differences
  350. if( $p_maximum ) {
  351. if( $t_version1 < $t_version2 ) {
  352. return 1;
  353. }
  354. else if( $t_version1 > $t_version2 ) {
  355. return -1;
  356. }
  357. } else {
  358. if( $t_version1 > $t_version2 ) {
  359. return 1;
  360. }
  361. else if( $t_version1 < $t_version2 ) {
  362. return -1;
  363. }
  364. }
  365. }
  366. # Versions matched exactly
  367. if ( count( $p_version1 ) == 0 && count( $p_version2 ) == 0 ) {
  368. return 1;
  369. }
  370. # Handle unmatched version bits
  371. if( $p_maximum ) {
  372. if ( count( $p_version2 ) > 0 ) {
  373. return 1;
  374. }
  375. } else {
  376. if ( count( $p_version1 ) > 0 ) {
  377. return 1;
  378. }
  379. }
  380. # No more comparisons
  381. return -1;
  382. }
  383. /**
  384. * Check a plugin dependency given a basename and required version.
  385. * Versions are checked using PHP's library version_compare routine
  386. * and allows both minimum and maximum version requirements.
  387. * Returns 1 if plugin dependency is met, 0 if dependency not met,
  388. * or -1 if dependency is the wrong version.
  389. * @param string Plugin basename
  390. * @param string Required version
  391. * @return integer Plugin dependency status
  392. */
  393. function plugin_dependency( $p_basename, $p_required, $p_initialized = false ) {
  394. global $g_plugin_cache, $g_plugin_cache_init;
  395. # check for registered dependency
  396. if( isset( $g_plugin_cache[$p_basename] ) ) {
  397. # require dependency initialized?
  398. if( $p_initialized && !isset( $g_plugin_cache_init[$p_basename] ) ) {
  399. return 0;
  400. }
  401. $t_required_array = explode( ',', $p_required );
  402. foreach( $t_required_array as $t_required ) {
  403. $t_required = trim( $t_required );
  404. $t_maximum = false;
  405. # check for a less-than-or-equal version requirement
  406. $t_ltpos = strpos( $t_required, '<=' );
  407. if( $t_ltpos !== false ) {
  408. $t_required = trim( utf8_substr( $t_required, $t_ltpos + 2 ) );
  409. $t_maximum = true;
  410. } else {
  411. $t_ltpos = strpos( $t_required, '<' );
  412. if( $t_ltpos !== false ) {
  413. $t_required = trim( utf8_substr( $t_required, $t_ltpos + 1 ) );
  414. $t_maximum = true;
  415. }
  416. }
  417. $t_version1 = plugin_version_array( $g_plugin_cache[$p_basename]->version );
  418. $t_version2 = plugin_version_array( $t_required );
  419. $t_check = plugin_version_check( $t_version1, $t_version2, $t_maximum );
  420. if ( $t_check < 1 ) {
  421. return $t_check;
  422. }
  423. }
  424. return 1;
  425. } else {
  426. return 0;
  427. }
  428. }
  429. /**
  430. * Checks to see if a plugin is 'protected' from uninstall.
  431. * @param string Plugin basename
  432. * @return boolean True if plugin is protected
  433. */
  434. function plugin_protected( $p_basename ) {
  435. global $g_plugin_cache_protected;
  436. # For pseudo-plugin MantisCore, return protected as 1.
  437. if( $p_basename == 'MantisCore' ) {
  438. return 1;
  439. }
  440. return $g_plugin_cache_protected[$p_basename];
  441. }
  442. /**
  443. * Gets a plugin's priority.
  444. * @param string Plugin basename
  445. * @return int Plugin priority
  446. */
  447. function plugin_priority( $p_basename ) {
  448. global $g_plugin_cache_priority;
  449. # For pseudo-plugin MantisCore, return priority as 3.
  450. if( $p_basename == 'MantisCore' ) {
  451. return 3;
  452. }
  453. return $g_plugin_cache_priority[$p_basename];
  454. }
  455. # ## Plugin management functions
  456. /**
  457. * Determine if a given plugin is installed.
  458. * @param string Plugin basename
  459. * @return boolean True if plugin is installed
  460. */
  461. function plugin_is_installed( $p_basename ) {
  462. $t_plugin_table = db_get_table( 'plugin' );
  463. $t_forced_plugins = config_get_global( 'plugins_force_installed' );
  464. foreach( $t_forced_plugins as $t_basename => $t_priority ) {
  465. if ( $t_basename == $p_basename ) {
  466. return true;
  467. }
  468. }
  469. $t_query = "SELECT COUNT(*) FROM $t_plugin_table WHERE basename=" . db_param();
  470. $t_result = db_query_bound( $t_query, array( $p_basename ) );
  471. return( 0 < db_result( $t_result ) );
  472. }
  473. /**
  474. * Install a plugin to the database.
  475. * @param string Plugin basename
  476. */
  477. function plugin_install( $p_plugin ) {
  478. access_ensure_global_level( config_get_global( 'manage_plugin_threshold' ) );
  479. if( plugin_is_installed( $p_plugin->basename ) ) {
  480. trigger_error( ERROR_PLUGIN_ALREADY_INSTALLED, WARNING );
  481. return null;
  482. }
  483. plugin_push_current( $p_plugin->basename );
  484. if( !$p_plugin->install() ) {
  485. plugin_pop_current( $p_plugin->basename );
  486. return null;
  487. }
  488. $t_plugin_table = db_get_table( 'plugin' );
  489. $t_query = "INSERT INTO $t_plugin_table ( basename, enabled )
  490. VALUES ( " . db_param() . ", '1' )";
  491. db_query_bound( $t_query, array( $p_plugin->basename ) );
  492. if( false === ( plugin_config_get( 'schema', false ) ) ) {
  493. plugin_config_set( 'schema', -1 );
  494. }
  495. plugin_upgrade( $p_plugin );
  496. plugin_pop_current();
  497. }
  498. /**
  499. * Determine if an installed plugin needs to upgrade its schema.
  500. * @param string Plugin basename
  501. * @return boolean True if plugin needs schema ugrades.
  502. */
  503. function plugin_needs_upgrade( $p_plugin ) {
  504. $t_plugin_schema = $p_plugin->schema();
  505. if( is_null( $t_plugin_schema ) ) {
  506. return false;
  507. }
  508. $t_config_option = 'plugin_' . $p_plugin->basename . '_schema';
  509. $t_plugin_schema_version = config_get( $t_config_option, -1, ALL_USERS, ALL_PROJECTS );
  510. return( $t_plugin_schema_version < count( $t_plugin_schema ) - 1 );
  511. }
  512. /**
  513. * Upgrade an installed plugin's schema.
  514. * @param string Plugin basename
  515. * @return multi True if upgrade completed, null if problem
  516. */
  517. function plugin_upgrade( $p_plugin ) {
  518. access_ensure_global_level( config_get_global( 'manage_plugin_threshold' ) );
  519. if( !plugin_is_installed( $p_plugin->basename ) ) {
  520. return;
  521. }
  522. require_api( 'install_helper_functions_api.php' );
  523. plugin_push_current( $p_plugin->basename );
  524. $t_schema_version = plugin_config_get( 'schema', -1 );
  525. $t_schema = $p_plugin->schema();
  526. global $g_db;
  527. $t_dict = NewDataDictionary( $g_db );
  528. $i = $t_schema_version + 1;
  529. while( $i < count( $t_schema ) ) {
  530. if( !$p_plugin->upgrade( $i ) ) {
  531. plugin_pop_current();
  532. return false;
  533. }
  534. $t_target = $t_schema[$i][1][0];
  535. if( $t_schema[$i][0] == 'InsertData' ) {
  536. $t_sqlarray = array(
  537. 'INSERT INTO ' . $t_schema[$i][1][0] . $t_schema[$i][1][1],
  538. );
  539. } else if( $t_schema[$i][0] == 'UpdateSQL' ) {
  540. $t_sqlarray = array(
  541. 'UPDATE ' . $t_schema[$i][1][0] . $t_schema[$i][1][1],
  542. );
  543. $t_target = $t_schema[$i][1];
  544. } else if( $t_schema[$i][0] == 'UpdateFunction' ) {
  545. $t_sqlarray = false;
  546. if( isset( $t_schema[$i][2] ) ) {
  547. $t_status = call_user_func( 'install_' . $t_schema[$i][1], $t_schema[$i][2] );
  548. } else {
  549. $t_status = call_user_func( 'install_' . $t_schema[$i][1] );
  550. }
  551. } else {
  552. $t_sqlarray = call_user_func_array( Array( $t_dict, $t_schema[$i][0] ), $t_schema[$i][1] );
  553. }
  554. if( $t_sqlarray ) {
  555. $t_status = $t_dict->ExecuteSQLArray( $t_sqlarray );
  556. }
  557. if( 2 == $t_status ) {
  558. plugin_config_set( 'schema', $i );
  559. } else {
  560. error_parameters( $i );
  561. trigger_error( ERROR_PLUGIN_UPGRADE_FAILED, ERROR );
  562. return null;
  563. }
  564. $i++;
  565. }
  566. plugin_pop_current();
  567. return true;
  568. }
  569. /**
  570. * Uninstall a plugin from the database.
  571. * @param string Plugin basename
  572. */
  573. function plugin_uninstall( $p_plugin ) {
  574. access_ensure_global_level( config_get_global( 'manage_plugin_threshold' ) );
  575. if( !plugin_is_installed( $p_plugin->basename ) || plugin_protected( $p_plugin->basename ) ) {
  576. return;
  577. }
  578. $t_plugin_table = db_get_table( 'plugin' );
  579. $t_query = "DELETE FROM $t_plugin_table WHERE basename=" . db_param();
  580. db_query_bound( $t_query, array( $p_plugin->basename ) );
  581. plugin_push_current( $p_plugin->basename );
  582. $p_plugin->uninstall();
  583. plugin_pop_current();
  584. }
  585. # ## Core usage only.
  586. /**
  587. * Search the plugins directory for plugins.
  588. * @return array Plugin basename/info key/value pairs.
  589. */
  590. function plugin_find_all() {
  591. $t_plugin_path = config_get_global( 'plugin_path' );
  592. $t_plugins = array(
  593. 'MantisCore' => new MantisCorePlugin( 'MantisCore' ),
  594. );
  595. if( $t_dir = opendir( $t_plugin_path ) ) {
  596. while(( $t_file = readdir( $t_dir ) ) !== false ) {
  597. if( '.' == $t_file || '..' == $t_file ) {
  598. continue;
  599. }
  600. if( is_dir( $t_plugin_path . $t_file ) ) {
  601. $t_plugin = plugin_register( $t_file, true );
  602. if( !is_null( $t_plugin ) ) {
  603. $t_plugins[$t_file] = $t_plugin;
  604. }
  605. }
  606. }
  607. closedir( $t_dir );
  608. }
  609. return $t_plugins;
  610. }
  611. /**
  612. * Load a plugin's core class file.
  613. * @param string Plugin basename
  614. */
  615. function plugin_include( $p_basename, $p_child = null ) {
  616. $t_path = config_get_global( 'plugin_path' ) . $p_basename . DIRECTORY_SEPARATOR;
  617. if( is_null( $p_child ) ) {
  618. $t_plugin_file = $t_path . $p_basename . '.php';
  619. } else {
  620. $t_plugin_file = $t_path . $p_child . '.php';
  621. }
  622. $t_included = false;
  623. if( is_file( $t_plugin_file ) ) {
  624. include_once( $t_plugin_file );
  625. $t_included = true;
  626. }
  627. return $t_included;
  628. }
  629. /**
  630. * Register a plugin with MantisBT.
  631. * The plugin class must already be loaded before calling.
  632. * @param string Plugin classname without 'Plugin' postfix
  633. */
  634. function plugin_register( $p_basename, $p_return = false, $p_child = null ) {
  635. global $g_plugin_cache;
  636. $t_basename = is_null( $p_child ) ? $p_basename : $p_child;
  637. if( !isset( $g_plugin_cache[$t_basename] ) ) {
  638. if( is_null( $p_child ) ) {
  639. $t_classname = $p_basename . 'Plugin';
  640. } else {
  641. $t_classname = $p_child . 'Plugin';
  642. }
  643. # Include the plugin script if the class is not already declared.
  644. if( !class_exists( $t_classname ) ) {
  645. if( !plugin_include( $p_basename, $p_child ) ) {
  646. return null;
  647. }
  648. }
  649. # Make sure the class exists and that it's of the right type.
  650. if( class_exists( $t_classname ) && is_subclass_of( $t_classname, 'MantisPlugin' ) ) {
  651. plugin_push_current( is_null( $p_child ) ? $p_basename : $p_child );
  652. $t_plugin = new $t_classname( is_null( $p_child ) ? $p_basename : $p_child );
  653. plugin_pop_current();
  654. # Final check on the class
  655. if( is_null( $t_plugin->name ) || is_null( $t_plugin->version ) ) {
  656. return null;
  657. }
  658. if( $p_return ) {
  659. return $t_plugin;
  660. } else {
  661. $g_plugin_cache[$t_basename] = $t_plugin;
  662. }
  663. }
  664. }
  665. return $g_plugin_cache[$t_basename];
  666. }
  667. /**
  668. * Find and register all installed plugins.
  669. */
  670. function plugin_register_installed() {
  671. global $g_plugin_cache_priority, $g_plugin_cache_protected;
  672. # register plugins specified in the site configuration
  673. $t_forced_plugins = config_get_global( 'plugins_force_installed' );
  674. foreach( $t_forced_plugins as $t_basename => $t_priority ) {
  675. plugin_register( $t_basename );
  676. $g_plugin_cache_priority[$t_basename] = $t_priority;
  677. $g_plugin_cache_protected[$t_basename] = true;
  678. }
  679. # register plugins installed via the interface/database
  680. $t_plugin_table = db_get_table( 'plugin' );
  681. $t_query = "SELECT basename, priority, protected FROM $t_plugin_table WHERE enabled=" . db_param() . ' ORDER BY priority DESC';
  682. $t_result = db_query_bound( $t_query, Array( 1 ) );
  683. while( $t_row = db_fetch_array( $t_result ) ) {
  684. $t_basename = $t_row['basename'];
  685. plugin_register( $t_basename );
  686. $g_plugin_cache_priority[$t_basename] = $t_row['priority'];
  687. $g_plugin_cache_protected[$t_basename] = $t_row['protected'];
  688. }
  689. }
  690. /**
  691. * Initialize all installed plugins.
  692. * Post-signals EVENT_PLUGIN_INIT.
  693. */
  694. function plugin_init_installed() {
  695. if( OFF == config_get_global( 'plugins_enabled' ) || !db_table_exists( db_get_table( 'plugin' ) ) ) {
  696. return;
  697. }
  698. global $g_plugin_cache, $g_plugin_current, $g_plugin_cache_priority, $g_plugin_cache_protected, $g_plugin_cache_init;
  699. $g_plugin_cache = array();
  700. $g_plugin_current = array();
  701. $g_plugin_cache_init = array();
  702. $g_plugin_cache_priority = array();
  703. $g_plugin_cache_protected = array();
  704. plugin_register( 'MantisCore' );
  705. plugin_register_installed();
  706. $t_plugins = array_keys( $g_plugin_cache );
  707. do {
  708. $t_continue = false;
  709. $t_plugins_retry = array();
  710. foreach( $t_plugins as $t_basename ) {
  711. if( plugin_init( $t_basename ) ) {
  712. $t_continue = true;
  713. } else {
  714. # Dependent plugin
  715. $t_plugins_retry[] = $t_basename;
  716. }
  717. }
  718. $t_plugins = $t_plugins_retry;
  719. }
  720. while( $t_continue );
  721. event_signal( 'EVENT_PLUGIN_INIT' );
  722. }
  723. /**
  724. * Initialize a single plugin.
  725. * @param string Plugin basename
  726. * @return boolean True if plugin initialized, false otherwise.
  727. */
  728. function plugin_init( $p_basename ) {
  729. global $g_plugin_cache, $g_plugin_cache_init;
  730. # handle dependent plugins
  731. if( isset( $g_plugin_cache[$p_basename] ) ) {
  732. $t_plugin = $g_plugin_cache[$p_basename];
  733. # hard dependencies; return false if the dependency is not registered,
  734. # does not meet the version requirement, or is not yet initialized.
  735. if( is_array( $t_plugin->requires ) ) {
  736. foreach( $t_plugin->requires as $t_required => $t_version ) {
  737. if( plugin_dependency( $t_required, $t_version, true ) !== 1 ) {
  738. return false;
  739. }
  740. }
  741. }
  742. # soft dependencies; only return false if the soft dependency is
  743. # registered, but not yet initialized.
  744. if( is_array( $t_plugin->uses ) ) {
  745. foreach( $t_plugin->uses as $t_used => $t_version ) {
  746. if ( isset( $g_plugin_cache[ $t_used ] ) && !isset( $g_plugin_cache_init[ $t_used ] ) ) {
  747. return false;
  748. }
  749. }
  750. }
  751. # if plugin schema needs an upgrade, do not initialize
  752. if ( plugin_needs_upgrade( $t_plugin ) ) {
  753. return false;
  754. }
  755. plugin_push_current( $p_basename );
  756. # load plugin error strings
  757. global $g_lang_strings;
  758. $t_lang = lang_get_current();
  759. $t_plugin_errors = $t_plugin->errors();
  760. foreach( $t_plugin_errors as $t_error_name => $t_error_string ) {
  761. $t_error_code = "plugin_${p_basename}_${t_error_name}";
  762. $g_lang_strings[$t_lang]['MANTIS_ERROR'][$t_error_code] = $t_error_string;
  763. }
  764. # finish initializing the plugin
  765. $t_plugin->__init();
  766. $g_plugin_cache_init[$p_basename] = true;
  767. plugin_pop_current();
  768. return true;
  769. } else {
  770. return false;
  771. }
  772. }