PageRenderTime 48ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/core/plugin_api.php

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