PageRenderTime 88ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/core/plugin_api.php

http://github.com/mantisbt/mantisbt
PHP | 1102 lines | 603 code | 165 blank | 334 comment | 120 complexity | 6d067fba590b83f0c91e9a59125a5b38 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 2000 - 2002 Kenzaburo Ito - kenito@300baud.org
  23. * @copyright Copyright 2002 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 file_api.php
  33. * @uses helper_api.php
  34. * @uses history_api.php
  35. * @uses lang_api.php
  36. * @uses logging_api.php
  37. */
  38. require_api( 'access_api.php' );
  39. require_api( 'config_api.php' );
  40. require_api( 'constant_inc.php' );
  41. require_api( 'database_api.php' );
  42. require_api( 'error_api.php' );
  43. require_api( 'event_api.php' );
  44. require_api( 'file_api.php' );
  45. require_api( 'helper_api.php' );
  46. require_api( 'history_api.php' );
  47. require_api( 'lang_api.php' );
  48. require_api( 'logging_api.php' );
  49. # Cache variables #####
  50. $g_plugin_cache = array();
  51. $g_plugin_cache_priority = array();
  52. $g_plugin_cache_protected = array();
  53. $g_plugin_current = array();
  54. # Public API #####
  55. /**
  56. * Get the currently executing plugin's basename.
  57. * @return string Plugin basename, or null if no current plugin
  58. */
  59. function plugin_get_current() {
  60. global $g_plugin_current;
  61. return( isset( $g_plugin_current[0] ) ? $g_plugin_current[0] : null );
  62. }
  63. /**
  64. * Add the current plugin to the stack
  65. * @param string $p_base_name Plugin base name.
  66. * @return void
  67. */
  68. function plugin_push_current( $p_base_name ) {
  69. global $g_plugin_current;
  70. array_unshift( $g_plugin_current, $p_base_name );
  71. }
  72. /**
  73. * Remove the current plugin from the stack
  74. * @return string Plugin basename, or null if no current plugin
  75. */
  76. function plugin_pop_current() {
  77. global $g_plugin_current;
  78. return( isset( $g_plugin_current[0] ) ? array_shift( $g_plugin_current ) : null );
  79. }
  80. /**
  81. * Returns the list of force-installed plugins
  82. * @see $g_plugins_force_installed
  83. * @return array List of plugins (basename => priority)
  84. */
  85. function plugin_get_force_installed() {
  86. $t_forced_plugins = config_get_global( 'plugins_force_installed' );
  87. # MantisCore pseudo-plugin is force-installed by definition, with priority 3
  88. $t_forced_plugins['MantisCore'] = 3;
  89. return $t_forced_plugins;
  90. }
  91. /**
  92. * Returns an object representing the specified plugin
  93. * Triggers an error if the plugin is not registered
  94. * @param string|null $p_basename Plugin base name (defaults to current plugin).
  95. * @return object Plugin Object
  96. */
  97. function plugin_get( $p_basename = null ) {
  98. global $g_plugin_cache;
  99. if( is_null( $p_basename ) ) {
  100. $t_current = plugin_get_current();
  101. } else {
  102. $t_current = $p_basename;
  103. }
  104. if( !plugin_is_registered( $t_current ) ) {
  105. error_parameters( $t_current );
  106. trigger_error( ERROR_PLUGIN_NOT_REGISTERED, ERROR );
  107. }
  108. return $g_plugin_cache[$t_current];
  109. }
  110. /**
  111. * Get the URL to the plugin wrapper page.
  112. * @param string $p_page Page name.
  113. * @param boolean $p_redirect Return url for redirection.
  114. * @param string $p_base_name Plugin base name (defaults to current plugin).
  115. * @return string
  116. */
  117. function plugin_page( $p_page, $p_redirect = false, $p_base_name = null ) {
  118. if( is_null( $p_base_name ) ) {
  119. $t_current = plugin_get_current();
  120. } else {
  121. $t_current = $p_base_name;
  122. }
  123. if( $p_redirect ) {
  124. return 'plugin.php?page=' . $t_current . '/' . $p_page;
  125. } else {
  126. return helper_mantis_url( 'plugin.php?page=' . $t_current . '/' . $p_page );
  127. }
  128. }
  129. /**
  130. * Gets the route group (base path under '/api/rest', e.g. /plugins/Example
  131. *
  132. * @param string $p_base_name The basename for plugin or null for current plugin.
  133. * @return string The route group path to use.
  134. */
  135. function plugin_route_group( $p_base_name = null ) {
  136. if( is_null( $p_base_name ) ) {
  137. $t_current = plugin_get_current();
  138. } else {
  139. $t_current = $p_base_name;
  140. }
  141. return '/plugins/' . $t_current;
  142. }
  143. /**
  144. * Return a path to a plugin file.
  145. * @param string $p_filename File name.
  146. * @param string $p_base_name Plugin base name.
  147. * @return mixed File path or false if FNF
  148. */
  149. function plugin_file_path( $p_filename, $p_base_name ) {
  150. $t_file_path = config_get_global( 'plugin_path' );
  151. $t_file_path .= $p_base_name . DIRECTORY_SEPARATOR;
  152. $t_file_path .= 'files' . DIRECTORY_SEPARATOR . $p_filename;
  153. return( is_file( $t_file_path ) ? $t_file_path : false );
  154. }
  155. /**
  156. * Get the URL to the plugin wrapper file page.
  157. * @param string $p_file File name.
  158. * @param boolean $p_redirect Return url for redirection.
  159. * @param string $p_base_name Plugin base name (defaults to current plugin).
  160. * @return string
  161. */
  162. function plugin_file( $p_file, $p_redirect = false, $p_base_name = null ) {
  163. if( is_null( $p_base_name ) ) {
  164. $t_current = plugin_get_current();
  165. } else {
  166. $t_current = $p_base_name;
  167. }
  168. if( $p_redirect ) {
  169. return 'plugin_file.php?file=' . $t_current . '/' . $p_file;
  170. } else {
  171. return helper_mantis_url( 'plugin_file.php?file=' . $t_current . '/' . $p_file );
  172. }
  173. }
  174. /**
  175. * Include the contents of a file as output.
  176. * @param string $p_filename File name.
  177. * @param string $p_basename Plugin basename.
  178. * @return void
  179. */
  180. function plugin_file_include( $p_filename, $p_basename = null ) {
  181. global $g_plugin_mime_types;
  182. if( is_null( $p_basename ) ) {
  183. $t_current = plugin_get_current();
  184. } else {
  185. $t_current = $p_basename;
  186. }
  187. $t_file_path = plugin_file_path( $p_filename, $t_current );
  188. if( false === $t_file_path ) {
  189. error_parameters( $t_current, $p_filename );
  190. trigger_error( ERROR_PLUGIN_FILE_NOT_FOUND, ERROR );
  191. }
  192. $t_content_type = '';
  193. $t_file_info_type = file_get_mime_type( $t_file_path );
  194. if( $t_file_info_type !== false ) {
  195. $t_content_type = $t_file_info_type;
  196. }
  197. # allow overriding the content type for specific text and image extensions
  198. # see bug #13193 for details
  199. if( strpos( $t_content_type, 'text/' ) === 0 || strpos( $t_content_type, 'image/' ) === 0 ) {
  200. $t_extension = pathinfo( $t_file_path, PATHINFO_EXTENSION );
  201. if( $t_extension && array_key_exists( $t_extension, $g_plugin_mime_types ) ) {
  202. $t_content_type = $g_plugin_mime_types[$t_extension];
  203. }
  204. }
  205. if( $t_content_type ) {
  206. header( 'Content-Type: ' . $t_content_type );
  207. }
  208. readfile( $t_file_path );
  209. }
  210. /**
  211. * Given a base table name for a plugin, add appropriate prefix and suffix.
  212. * Convenience for plugin schema definitions.
  213. * @param string $p_name Table name.
  214. * @param string $p_basename Plugin basename (defaults to current plugin).
  215. * @return string Full table name
  216. */
  217. function plugin_table( $p_name, $p_basename = null ) {
  218. if( is_null( $p_basename ) ) {
  219. $t_current = plugin_get_current();
  220. } else {
  221. $t_current = $p_basename;
  222. }
  223. # Determine plugin table prefix including trailing '_'
  224. $t_prefix = trim( config_get_global( 'db_table_plugin_prefix' ) );
  225. if( !empty( $t_prefix ) && '_' != substr( $t_prefix, -1 ) ) {
  226. $t_prefix .= '_';
  227. }
  228. return db_get_table( $t_prefix . $t_current . '_' . $p_name );
  229. }
  230. /**
  231. * Get a plugin configuration option.
  232. * @param string $p_option Configuration option name.
  233. * @param mixed $p_default Default option value.
  234. * @param boolean $p_global Get global config variables only.
  235. * @param integer $p_user A user identifier.
  236. * @param integer $p_project A Project identifier.
  237. * @return string
  238. */
  239. function plugin_config_get( $p_option, $p_default = null, $p_global = false, $p_user = null, $p_project = null ) {
  240. $t_basename = plugin_get_current();
  241. $t_full_option = 'plugin_' . $t_basename . '_' . $p_option;
  242. if( $p_global ) {
  243. return config_get_global( $t_full_option, $p_default );
  244. } else {
  245. return config_get( $t_full_option, $p_default, $p_user, $p_project );
  246. }
  247. }
  248. /**
  249. * Set a plugin configuration option in the database.
  250. * @param string $p_option Configuration option name.
  251. * @param mixed $p_value Option value.
  252. * @param integer $p_user A user identifier.
  253. * @param integer $p_project A project identifier.
  254. * @param integer $p_access Access threshold.
  255. * @return void
  256. */
  257. function plugin_config_set( $p_option, $p_value, $p_user = NO_USER, $p_project = ALL_PROJECTS, $p_access = DEFAULT_ACCESS_LEVEL ) {
  258. if( $p_access == DEFAULT_ACCESS_LEVEL ) {
  259. $p_access = config_get_global( 'admin_site_threshold' );
  260. }
  261. $t_basename = plugin_get_current();
  262. $t_full_option = 'plugin_' . $t_basename . '_' . $p_option;
  263. config_set( $t_full_option, $p_value, $p_user, $p_project, $p_access );
  264. }
  265. /**
  266. * Delete a plugin configuration option from the database.
  267. * @param string $p_option Configuration option name.
  268. * @param integer $p_user A user identifier.
  269. * @param integer $p_project A project identifier.
  270. * @return void
  271. */
  272. function plugin_config_delete( $p_option, $p_user = ALL_USERS, $p_project = ALL_PROJECTS ) {
  273. $t_basename = plugin_get_current();
  274. $t_full_option = 'plugin_' . $t_basename . '_' . $p_option;
  275. config_delete( $t_full_option, $p_user, $p_project );
  276. }
  277. /**
  278. * Set plugin default values to global values without overriding anything.
  279. * @param array $p_options Array of configuration option name/value pairs.
  280. * @return void
  281. */
  282. function plugin_config_defaults( array $p_options ) {
  283. if( !is_array( $p_options ) ) {
  284. return;
  285. }
  286. $t_basename = plugin_get_current();
  287. $t_option_base = 'plugin_' . $t_basename . '_';
  288. foreach( $p_options as $t_option => $t_value ) {
  289. $t_full_option = $t_option_base . $t_option;
  290. config_set_global( $t_full_option, $t_value, false );
  291. }
  292. }
  293. /**
  294. * Get a language string for the plugin.
  295. * Automatically prepends plugin_<basename> to the string requested.
  296. * @param string $p_name Language string name.
  297. * @param string $p_basename Plugin basename.
  298. * @return string Language string
  299. */
  300. function plugin_lang_get( $p_name, $p_basename = null ) {
  301. if( !is_null( $p_basename ) ) {
  302. plugin_push_current( $p_basename );
  303. }
  304. $t_basename = plugin_get_current();
  305. $t_name = 'plugin_' . $t_basename . '_' . $p_name;
  306. $t_string = lang_get( $t_name );
  307. if( !is_null( $p_basename ) ) {
  308. plugin_pop_current();
  309. }
  310. return $t_string;
  311. }
  312. /**
  313. * Get a defaulted language string for the plugin.
  314. * - If found, return the appropriate string.
  315. * - If not found, no default supplied, return the supplied string as is.
  316. * - If not found, default supplied, return default.
  317. * Automatically prepends plugin_<basename> to the string requested.
  318. * @see lang_get_defaulted()
  319. *
  320. * @param string $p_name Language string name.
  321. * @param string $p_default The default value to return.
  322. * @param string $p_basename Plugin basename.
  323. *
  324. * @return string Language string
  325. */
  326. function plugin_lang_get_defaulted( $p_name, $p_default = null, $p_basename = null ) {
  327. if( !is_null( $p_basename ) ) {
  328. plugin_push_current( $p_basename );
  329. }
  330. $t_basename = plugin_get_current();
  331. $t_name = 'plugin_' . $t_basename . '_' . $p_name;
  332. $t_string = lang_get_defaulted( $t_name, $p_default );
  333. if( !is_null( $p_basename ) ) {
  334. plugin_pop_current();
  335. }
  336. return $t_string;
  337. }
  338. /**
  339. * log history event from plugin
  340. * @param integer $p_bug_id A bug identifier.
  341. * @param string $p_field_name A field name.
  342. * @param string $p_old_value The old value.
  343. * @param string $p_new_value The new value.
  344. * @param integer $p_user_id A user identifier.
  345. * @param string $p_basename The plugin basename (or current plugin if null).
  346. * @return void
  347. */
  348. function plugin_history_log( $p_bug_id, $p_field_name, $p_old_value, $p_new_value = '', $p_user_id = null, $p_basename = null ) {
  349. if( is_null( $p_basename ) ) {
  350. $t_basename = plugin_get_current();
  351. } else {
  352. $t_basename = $p_basename;
  353. }
  354. $t_field_name = $t_basename . '_' . $p_field_name;
  355. history_log_event_direct( $p_bug_id, $t_field_name, $p_old_value, $p_new_value, $p_user_id, PLUGIN_HISTORY );
  356. }
  357. /**
  358. * Trigger a plugin-specific error with the given name and type.
  359. * @param string $p_error_name Error name.
  360. * @param integer $p_error_type Error type.
  361. * @param string $p_basename The plugin basename (or current plugin if null).
  362. * @return void
  363. */
  364. function plugin_error( $p_error_name, $p_error_type = ERROR, $p_basename = null ) {
  365. if( is_null( $p_basename ) ) {
  366. $t_basename = plugin_get_current();
  367. } else {
  368. $t_basename = $p_basename;
  369. }
  370. $t_error_code = "plugin_${t_basename}_${p_error_name}";
  371. trigger_error( $t_error_code, $p_error_type );
  372. }
  373. /**
  374. * Hook a plugin's callback function to an event.
  375. * @param string $p_name Event name.
  376. * @param string $p_callback Callback function.
  377. * @return void
  378. */
  379. function plugin_event_hook( $p_name, $p_callback ) {
  380. $t_basename = plugin_get_current();
  381. event_hook( $p_name, $p_callback, $t_basename );
  382. }
  383. /**
  384. * Hook multiple plugin callbacks at once.
  385. * @param array $p_hooks Array of event name/callback key/value pairs.
  386. * @return void
  387. */
  388. function plugin_event_hook_many( array $p_hooks ) {
  389. if( !is_array( $p_hooks ) ) {
  390. return;
  391. }
  392. $t_basename = plugin_get_current();
  393. foreach( $p_hooks as $t_event => $t_callbacks ) {
  394. if( !is_array( $t_callbacks ) ) {
  395. event_hook( $t_event, $t_callbacks, $t_basename );
  396. continue;
  397. }
  398. foreach( $t_callbacks as $t_callback ) {
  399. event_hook( $t_event, $t_callback, $t_basename );
  400. }
  401. }
  402. }
  403. /**
  404. * Allows a plugin to declare a 'child plugin' that
  405. * can be loaded from the same parent directory.
  406. * @param string $p_child Child plugin basename.
  407. * @return mixed
  408. */
  409. function plugin_child( $p_child ) {
  410. $t_base_name = plugin_get_current();
  411. $t_plugin = plugin_register( $t_base_name, false, $p_child );
  412. if( !is_null( $t_plugin ) ) {
  413. plugin_init( $p_child );
  414. }
  415. return $t_plugin;
  416. }
  417. /**
  418. * Checks if a given plugin has been registered and initialized,
  419. * and returns a boolean value representing the "loaded" state.
  420. * @param string $p_base_name Plugin basename.
  421. * @return boolean Plugin loaded
  422. */
  423. function plugin_is_loaded( $p_base_name ) {
  424. global $g_plugin_cache_init;
  425. return ( isset( $g_plugin_cache_init[$p_base_name] ) && $g_plugin_cache_init[$p_base_name] );
  426. }
  427. /**
  428. * Checks two versions for minimum or maximum version dependencies.
  429. * @param string $p_version Version number to check.
  430. * @param string $p_required Version number required.
  431. * @param boolean $p_maximum Minimum (false) or maximum (true) version check.
  432. * @return integer 1 if the version dependency succeeds, -1 if it fails
  433. */
  434. function plugin_version_check( $p_version, $p_required, $p_maximum = false ) {
  435. if( $p_maximum ) {
  436. $t_operator = '<';
  437. } else {
  438. $t_operator = '>=';
  439. }
  440. $t_result = version_compare( $p_version, $p_required, $t_operator );
  441. return $t_result ? 1 : -1;
  442. }
  443. /**
  444. * Check a plugin dependency given a basename and required version.
  445. * Versions are checked using PHP's library version_compare routine
  446. * and allows both minimum and maximum version requirements.
  447. * Returns 1 if plugin dependency is met, 0 if dependency not met,
  448. * or -1 if dependency is the wrong version.
  449. * @param string $p_base_name Plugin base name.
  450. * @param string $p_required Required version.
  451. * @param boolean $p_initialized Whether plugin is initialized.
  452. * @return integer Plugin dependency status
  453. */
  454. function plugin_dependency( $p_base_name, $p_required, $p_initialized = false ) {
  455. global $g_plugin_cache, $g_plugin_cache_init;
  456. # check for registered dependency
  457. if( isset( $g_plugin_cache[$p_base_name] ) ) {
  458. # require dependency initialized?
  459. if( $p_initialized && !isset( $g_plugin_cache_init[$p_base_name] ) ) {
  460. return 0;
  461. }
  462. $t_plugin_version = $g_plugin_cache[$p_base_name]->version;
  463. $t_required_array = explode( ',', $p_required );
  464. # Set maximum dependency for MantisCore if none is specified.
  465. # This effectively disables plugins which have not been specifically
  466. # designed for a new major Mantis release to force authors to review
  467. # their code, adapt it if necessary, and release a new version of the
  468. # plugin with updated dependencies.
  469. if( $p_base_name == 'MantisCore' && strpos( $p_required, '<' ) === false ) {
  470. $t_version_core = mb_substr( $t_plugin_version, 0, strpos( $t_plugin_version, '.' ) );
  471. $t_is_current_core_supported = false;
  472. foreach( $t_required_array as $t_version_required ) {
  473. $t_is_current_core_supported = $t_is_current_core_supported
  474. || version_compare( trim( $t_version_required ), $t_version_core, '>=' );
  475. }
  476. if( !$t_is_current_core_supported ) {
  477. # Add current major version as maximum
  478. $t_required_array[] = '<' . $t_version_core;
  479. }
  480. }
  481. foreach( $t_required_array as $t_required ) {
  482. $t_required = trim( $t_required );
  483. $t_maximum = false;
  484. # check for a less-than-or-equal version requirement
  485. $t_ltpos = strpos( $t_required, '<=' );
  486. if( $t_ltpos !== false ) {
  487. $t_required = trim( mb_substr( $t_required, $t_ltpos + 2 ) );
  488. $t_maximum = true;
  489. } else {
  490. $t_ltpos = strpos( $t_required, '<' );
  491. if( $t_ltpos !== false ) {
  492. $t_required = trim( mb_substr( $t_required, $t_ltpos + 1 ) );
  493. $t_maximum = true;
  494. }
  495. }
  496. $t_check = plugin_version_check( $t_plugin_version, $t_required, $t_maximum );
  497. if( $t_check < 1 ) {
  498. return $t_check;
  499. }
  500. }
  501. return 1;
  502. } else {
  503. return 0;
  504. }
  505. }
  506. /**
  507. * Checks to see if a plugin is 'protected' from uninstall.
  508. * @param string $p_base_name Plugin base name.
  509. * @return boolean True if plugin is protected
  510. */
  511. function plugin_protected( $p_base_name ) {
  512. global $g_plugin_cache_protected;
  513. return $g_plugin_cache_protected[$p_base_name];
  514. }
  515. /**
  516. * Gets a plugin's priority.
  517. * @param string $p_base_name Plugin base name.
  518. * @return int Plugin priority
  519. */
  520. function plugin_priority( $p_base_name ) {
  521. global $g_plugin_cache_priority;
  522. return $g_plugin_cache_priority[$p_base_name];
  523. }
  524. /**
  525. * Determine if a given plugin is installed.
  526. * @param string $p_basename Plugin basename.
  527. * @return boolean True if plugin is installed
  528. */
  529. function plugin_is_installed( $p_basename ) {
  530. foreach( plugin_get_force_installed() as $t_basename => $t_priority ) {
  531. if( $t_basename == $p_basename ) {
  532. return true;
  533. }
  534. }
  535. db_param_push();
  536. $t_query = 'SELECT COUNT(*) FROM {plugin} WHERE basename=' . db_param();
  537. $t_result = db_query( $t_query, array( $p_basename ) );
  538. return( 0 < db_result( $t_result ) );
  539. }
  540. /**
  541. * Install a plugin to the database.
  542. * @param MantisPlugin $p_plugin Plugin basename.
  543. * @return null
  544. */
  545. function plugin_install( MantisPlugin $p_plugin ) {
  546. if( plugin_is_installed( $p_plugin->basename ) ) {
  547. error_parameters( $p_plugin->basename );
  548. trigger_error( ERROR_PLUGIN_ALREADY_INSTALLED, WARNING );
  549. return null;
  550. }
  551. plugin_push_current( $p_plugin->basename );
  552. if( !$p_plugin->install() ) {
  553. plugin_pop_current();
  554. return null;
  555. }
  556. db_param_push();
  557. $t_query = 'INSERT INTO {plugin} ( basename, enabled )
  558. VALUES ( ' . db_param() . ', ' . db_param() . ' )';
  559. db_query( $t_query, array( $p_plugin->basename, true ) );
  560. if( false === ( plugin_config_get( 'schema', false ) ) ) {
  561. plugin_config_set( 'schema', -1 );
  562. }
  563. plugin_upgrade( $p_plugin );
  564. plugin_pop_current();
  565. }
  566. /**
  567. * Determine if an installed plugin needs to upgrade its schema.
  568. * @param MantisPlugin $p_plugin Plugin basename.
  569. * @return boolean True if plugin needs schema upgrades.
  570. */
  571. function plugin_needs_upgrade( MantisPlugin $p_plugin ) {
  572. plugin_push_current( $p_plugin->name );
  573. $t_plugin_schema = $p_plugin->schema();
  574. plugin_pop_current();
  575. if( is_null( $t_plugin_schema ) ) {
  576. return false;
  577. }
  578. $t_config_option = 'plugin_' . $p_plugin->basename . '_schema';
  579. $t_plugin_schema_version = config_get( $t_config_option, -1, ALL_USERS, ALL_PROJECTS );
  580. return( $t_plugin_schema_version < count( $t_plugin_schema ) - 1 );
  581. }
  582. /**
  583. * Upgrade an installed plugin's schema.
  584. * This is mostly identical to the code in the MantisBT installer, and should
  585. * be reviewed and updated accordingly whenever that changes.
  586. * @param MantisPlugin $p_plugin Plugin basename.
  587. * @return boolean|null True if upgrade completed, null if problem
  588. */
  589. function plugin_upgrade( MantisPlugin $p_plugin ) {
  590. if( !plugin_is_installed( $p_plugin->basename ) ) {
  591. return null;
  592. }
  593. require_api( 'install_helper_functions_api.php' );
  594. plugin_push_current( $p_plugin->basename );
  595. $t_schema_version = (int)plugin_config_get( 'schema', -1 );
  596. $t_schema = $p_plugin->schema();
  597. global $g_db;
  598. $t_dict = NewDataDictionary( $g_db );
  599. $i = $t_schema_version + 1;
  600. while( $i < count( $t_schema ) ) {
  601. if( !$p_plugin->upgrade( $i ) ) {
  602. plugin_pop_current();
  603. return false;
  604. }
  605. switch( $t_schema[$i][0] ) {
  606. case 'InsertData':
  607. $t_sqlarray = array(
  608. 'INSERT INTO ' . $t_schema[$i][1][0] . $t_schema[$i][1][1],
  609. );
  610. break;
  611. case 'UpdateSQL':
  612. $t_sqlarray = array(
  613. 'UPDATE ' . $t_schema[$i][1][0] . $t_schema[$i][1][1],
  614. );
  615. break;
  616. case 'UpdateFunction':
  617. $t_sqlarray = false;
  618. if( isset( $t_schema[$i][2] ) ) {
  619. $t_status = call_user_func( 'install_' . $t_schema[$i][1], $t_schema[$i][2] );
  620. } else {
  621. $t_status = call_user_func( 'install_' . $t_schema[$i][1] );
  622. }
  623. break;
  624. case null:
  625. # No-op upgrade step
  626. $t_sqlarray = false;
  627. $t_status = 2;
  628. break;
  629. default:
  630. $t_sqlarray = call_user_func_array(
  631. array( $t_dict, $t_schema[$i][0] ),
  632. $t_schema[$i][1]
  633. );
  634. $t_status = false;
  635. }
  636. if( $t_sqlarray ) {
  637. $t_status = $t_dict->ExecuteSQLArray( $t_sqlarray );
  638. }
  639. if( 2 == $t_status ) {
  640. plugin_config_set( 'schema', $i );
  641. } else {
  642. error_parameters(
  643. $i,
  644. $g_db->ErrorMsg(),
  645. implode( '<br>', $t_sqlarray )
  646. );
  647. trigger_error( ERROR_PLUGIN_UPGRADE_FAILED, ERROR );
  648. return null;
  649. }
  650. $i++;
  651. }
  652. plugin_pop_current();
  653. return true;
  654. }
  655. /**
  656. * Uninstall a plugin from the database.
  657. * @param MantisPlugin $p_plugin Plugin basename.
  658. * @return void
  659. */
  660. function plugin_uninstall( MantisPlugin $p_plugin ) {
  661. access_ensure_global_level( config_get_global( 'manage_plugin_threshold' ) );
  662. if( !plugin_is_installed( $p_plugin->basename ) || plugin_protected( $p_plugin->basename ) ) {
  663. return;
  664. }
  665. db_param_push();
  666. $t_query = 'DELETE FROM {plugin} WHERE basename=' . db_param();
  667. db_query( $t_query, array( $p_plugin->basename ) );
  668. plugin_push_current( $p_plugin->basename );
  669. $p_plugin->uninstall();
  670. plugin_pop_current();
  671. }
  672. /**
  673. * Search the plugins directory for plugins.
  674. * @return array Plugin basename/info key/value pairs.
  675. */
  676. function plugin_find_all() {
  677. $t_plugin_path = config_get_global( 'plugin_path' );
  678. $t_plugins = array(
  679. 'MantisCore' => new MantisCorePlugin( 'MantisCore' ),
  680. );
  681. if( $t_dir = opendir( $t_plugin_path ) ) {
  682. while( ( $t_file = readdir( $t_dir ) ) !== false ) {
  683. if( '.' == $t_file || '..' == $t_file ) {
  684. continue;
  685. }
  686. if( is_dir( $t_plugin_path . $t_file ) ) {
  687. $t_plugin = plugin_register( $t_file, true );
  688. if( !is_null( $t_plugin ) ) {
  689. $t_plugins[$t_file] = $t_plugin;
  690. }
  691. }
  692. }
  693. closedir( $t_dir );
  694. }
  695. return $t_plugins;
  696. }
  697. /**
  698. * Load a plugin's core class file.
  699. * @param string $p_basename Plugin basename.
  700. * @param string $p_child Child filename.
  701. * @return boolean
  702. */
  703. function plugin_include( $p_basename, $p_child = null ) {
  704. $t_path = config_get_global( 'plugin_path' ) . $p_basename . DIRECTORY_SEPARATOR;
  705. if( is_null( $p_child ) ) {
  706. $t_plugin_file = $t_path . $p_basename . '.php';
  707. } else {
  708. $t_plugin_file = $t_path . $p_child . '.php';
  709. }
  710. $t_included = false;
  711. if( is_file( $t_plugin_file ) ) {
  712. include_once( $t_plugin_file );
  713. $t_included = true;
  714. }
  715. return $t_included;
  716. }
  717. /**
  718. * Allows a plugin page to require a plugin-specific API
  719. * @param string $p_file The API to be included.
  720. * @param string $p_basename Plugin's basename (defaults to current plugin).
  721. * @return void
  722. */
  723. function plugin_require_api( $p_file, $p_basename = null ) {
  724. if( is_null( $p_basename ) ) {
  725. $t_current = plugin_get_current();
  726. } else {
  727. $t_current = $p_basename;
  728. }
  729. $t_path = config_get_global( 'plugin_path' ) . $t_current . '/';
  730. require_once( $t_path . $p_file );
  731. }
  732. /**
  733. * Determine if a given plugin is registered.
  734. * @param string $p_basename Plugin basename.
  735. * @return boolean True if plugin is registered
  736. */
  737. function plugin_is_registered( $p_basename ) {
  738. global $g_plugin_cache;
  739. return isset( $g_plugin_cache[$p_basename] );
  740. }
  741. /**
  742. * Register a plugin with MantisBT.
  743. * The plugin class must already be loaded before calling.
  744. * @param string $p_basename Plugin classname without 'Plugin' postfix.
  745. * @param boolean $p_return Return.
  746. * @param string $p_child Child filename.
  747. * @return mixed
  748. */
  749. function plugin_register( $p_basename, $p_return = false, $p_child = null ) {
  750. global $g_plugin_cache;
  751. $t_basename = is_null( $p_child ) ? $p_basename : $p_child;
  752. if( !isset( $g_plugin_cache[$t_basename] ) ) {
  753. $t_classname = $t_basename . 'Plugin';
  754. # Include the plugin script if the class is not already declared.
  755. if( !class_exists( $t_classname ) ) {
  756. if( !plugin_include( $p_basename, $p_child ) ) {
  757. return null;
  758. }
  759. }
  760. # Make sure the class exists and that it's of the right type.
  761. if( class_exists( $t_classname ) && is_subclass_of( $t_classname, 'MantisPlugin' ) ) {
  762. plugin_push_current( is_null( $p_child ) ? $p_basename : $p_child );
  763. $t_plugin = new $t_classname( is_null( $p_child ) ? $p_basename : $p_child );
  764. plugin_pop_current();
  765. # Final check on the class
  766. if( is_null( $t_plugin->name ) || is_null( $t_plugin->version ) ) {
  767. return null;
  768. }
  769. if( $p_return ) {
  770. return $t_plugin;
  771. } else {
  772. $g_plugin_cache[$t_basename] = $t_plugin;
  773. }
  774. } else {
  775. error_parameters( $t_basename, $t_classname );
  776. trigger_error( ERROR_PLUGIN_CLASS_NOT_FOUND, ERROR );
  777. }
  778. }
  779. return $g_plugin_cache[$t_basename];
  780. }
  781. /**
  782. * Find and register all installed plugins.
  783. * This includes the MantisCore pseudo-plugin.
  784. * @return void
  785. */
  786. function plugin_register_installed() {
  787. global $g_plugin_cache_priority, $g_plugin_cache_protected;
  788. # register plugins specified in the site configuration
  789. foreach( plugin_get_force_installed() as $t_basename => $t_priority ) {
  790. plugin_register( $t_basename );
  791. $g_plugin_cache_priority[$t_basename] = $t_priority;
  792. $g_plugin_cache_protected[$t_basename] = true;
  793. }
  794. # register plugins installed via the interface/database
  795. db_param_push();
  796. $t_query = 'SELECT basename, priority, protected FROM {plugin} WHERE enabled=' . db_param() . ' ORDER BY priority DESC';
  797. $t_result = db_query( $t_query, array( true ) );
  798. while( $t_row = db_fetch_array( $t_result ) ) {
  799. $t_basename = $t_row['basename'];
  800. if( !plugin_is_registered( $t_basename ) ) {
  801. plugin_register( $t_basename );
  802. $g_plugin_cache_priority[$t_basename] = (int)$t_row['priority'];
  803. $g_plugin_cache_protected[$t_basename] = (bool)$t_row['protected'];
  804. }
  805. }
  806. }
  807. /**
  808. * Initialize all installed plugins.
  809. * Post-signals EVENT_PLUGIN_INIT.
  810. * @return void
  811. */
  812. function plugin_init_installed() {
  813. if( OFF == config_get_global( 'plugins_enabled' ) || !db_table_exists( db_get_table( 'plugin' ) ) ) {
  814. return;
  815. }
  816. global $g_plugin_cache, $g_plugin_current, $g_plugin_cache_priority, $g_plugin_cache_protected, $g_plugin_cache_init;
  817. $g_plugin_cache = array();
  818. $g_plugin_current = array();
  819. $g_plugin_cache_init = array();
  820. $g_plugin_cache_priority = array();
  821. $g_plugin_cache_protected = array();
  822. plugin_register_installed();
  823. $t_plugins = array_keys( $g_plugin_cache );
  824. do {
  825. $t_continue = false;
  826. $t_plugins_retry = array();
  827. foreach( $t_plugins as $t_basename ) {
  828. if( plugin_init( $t_basename ) ) {
  829. $t_continue = true;
  830. } else {
  831. # Dependent plugin
  832. $t_plugins_retry[] = $t_basename;
  833. }
  834. }
  835. $t_plugins = $t_plugins_retry;
  836. } while( $t_continue );
  837. event_signal( 'EVENT_PLUGIN_INIT' );
  838. }
  839. /**
  840. * Initialize a single plugin.
  841. * @param string $p_basename Plugin basename.
  842. * @return boolean True if plugin initialized, false otherwise.
  843. */
  844. function plugin_init( $p_basename ) {
  845. global $g_plugin_cache, $g_plugin_cache_init;
  846. # handle dependent plugins
  847. if( isset( $g_plugin_cache[$p_basename] ) ) {
  848. $t_plugin = $g_plugin_cache[$p_basename];
  849. # hard dependencies; return false if the dependency is not registered,
  850. # does not meet the version requirement, or is not yet initialized.
  851. if( is_array( $t_plugin->requires ) ) {
  852. foreach( $t_plugin->requires as $t_required => $t_version ) {
  853. if( plugin_dependency( $t_required, $t_version, true ) !== 1 ) {
  854. return false;
  855. }
  856. }
  857. }
  858. # soft dependencies; only return false if the soft dependency is
  859. # registered, but not yet initialized.
  860. if( is_array( $t_plugin->uses ) ) {
  861. foreach( $t_plugin->uses as $t_used => $t_version ) {
  862. if( isset( $g_plugin_cache[$t_used] ) && !isset( $g_plugin_cache_init[$t_used] ) ) {
  863. return false;
  864. }
  865. }
  866. }
  867. # if plugin schema needs an upgrade, do not initialize
  868. if( plugin_needs_upgrade( $t_plugin ) ) {
  869. return false;
  870. }
  871. plugin_push_current( $p_basename );
  872. # load plugin error strings
  873. global $g_lang_strings;
  874. $t_lang = lang_get_current();
  875. $t_plugin_errors = $t_plugin->errors();
  876. foreach( $t_plugin_errors as $t_error_name => $t_error_string ) {
  877. $t_error_code = "plugin_${p_basename}_${t_error_name}";
  878. $g_lang_strings[$t_lang]['MANTIS_ERROR'][$t_error_code] = $t_error_string;
  879. }
  880. # finish initializing the plugin
  881. $t_plugin->__init();
  882. $g_plugin_cache_init[$p_basename] = true;
  883. plugin_pop_current();
  884. return true;
  885. } else {
  886. return false;
  887. }
  888. }
  889. /**
  890. * Log a plugin-specific event.
  891. *
  892. * @param string|array $p_msg Either a string, or an array structured as
  893. * (string,execution time).
  894. * @param string $p_basename Plugin's basename (defaults to current plugin)
  895. */
  896. function plugin_log_event( $p_msg, $p_basename = null ) {
  897. $t_current_plugin = plugin_get_current();
  898. if( is_null( $p_basename ) ) {
  899. $t_basename = $t_current_plugin;
  900. } else {
  901. $t_basename = $p_basename;
  902. }
  903. if( $t_basename != $t_current_plugin ) {
  904. plugin_push_current( $t_basename );
  905. log_event( LOG_PLUGIN, $p_msg);
  906. plugin_pop_current();
  907. } else {
  908. log_event( LOG_PLUGIN, $p_msg);
  909. }
  910. }
  911. /**
  912. * Retrieve plugin-defined menu items for a given event.
  913. *
  914. * These are HTML hyperlinks (<a> tags).
  915. *
  916. * @param string $p_event Plugin event to signal
  917. * @return array
  918. */
  919. function plugin_menu_items( $p_event ) {
  920. $t_items = array();
  921. if( $p_event ) {
  922. $t_event_items = event_signal( $p_event );
  923. foreach( $t_event_items as $t_plugin => $t_plugin_items ) {
  924. foreach( $t_plugin_items as $t_callback => $t_callback_items ) {
  925. if( is_array( $t_callback_items ) ) {
  926. $t_items = array_merge( $t_items, $t_callback_items );
  927. }
  928. else {
  929. if( $t_callback_items !== null ) {
  930. $t_items[] = $t_callback_items;
  931. }
  932. }
  933. }
  934. }
  935. }
  936. return $t_items;
  937. }