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

/mantisbt-1.2.8/core/plugin_api.php

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