api.module

Tracking 6.x-1.x branch
  1. drupal
    1. 5 contributions/api/api.module
    2. 6 contributions/api/api.module

Generates and displays API documentation pages.

This is an implementation of a subset of the Doxygen documentation generator specification, tuned to produce output that best benefits the Drupal code base. It is designed to assume the code it documents follows Drupal coding conventions, and supports documentation blocks in formats described on http://drupal.org/node/1354

Functions & methods

NameDescription
api_autocompletePrepares a listing of documentation objects for a branch.
api_blockImplements hook_block().
api_branch_sortCallback for usort() within api_save_branch().
api_cronImplements hook_cron().
api_cron_queue_infoImplements hook_cron_queue_info().
api_db_rewrite_sqlImplements hook_db_rewrite_sql().
api_entity_decodeDecodes HTML entities.
api_filename_loadLoads an API file object.
api_filterImplementation of hook_filter().
api_filter_documentationTurns function names into links for a text filter.
api_get_active_branchReturns the currently active branch object.
api_get_branchesReturns a list of all defined branches.
api_get_branches_by_nameFinds all branches matching a branch name, across projects.
api_get_branch_by_idReturns a branch, given a branch ID number.
api_get_branch_by_nameLoads a branch, given a project and branch name.
api_get_branch_namesReturns the list of currently-used branch names across all projects.
api_helpImplements hook_help().
api_initImplements hook_init().
api_item_loadLoads an API object for the menu router.
api_legacy_1_2_listing_loadReturns the current path to listing pages accessed with old URLs.
api_legacy_1_2_object_loadFinds objects for old URLs and returns the new URL.
api_link_codeTurns function names into links in code.
api_link_documentationTurns function names into links in documentation.
api_link_linkTurns text into a link, using the first word as the object name.
api_link_member_nameLinks text to an appropriate class member variable, constant, or function.
api_link_nameLinks an object name to its documentation.
api_listing_countsCalculates the counts of each type of listing for a branch.
api_mark_for_reparseFlags a file, branch, or all branches, to be reparsed on the next cron run.
api_menuImplements hook_menu().
api_object_loadLoads a documentation object.
api_permImplements hook_perm().
api_preprocess_pagePreprocess pages: sets the page title if it's an API module page.
api_queue_file_deleteDeletes an obsolete JavaScript autocomplete index file.
api_queue_parse_fileParses a queued file.
api_reset_parse_queueResets the parse queue.
api_save_branchSaves an API branch.
api_search_formForm constructor for the API search form.
api_search_form_submitForm submission handler for api_search_form().
api_set_html_page_titleSets or returns the HTML page title.
api_splitSplits a string using a regular expression and processes using callbacks.
api_themeImplements hook_theme().
api_urlConstructs a link to an API object page.
_api_link_documentationRecursive internal callback for turning function names into links in code.

Constants

NameDescription
API_FILEPATH_SEPARATORFile path separator.
API_FILEPATH_SEPARATOR_REPLACEMENTFile path separator replacement.
API_RE_CLASS_NAME_TEXTRegular expression for matching class names in text.
API_RE_FILENAMERegular expression for matching file names.
API_RE_HOOK_NAMERegular expression for matching hook names.
API_RE_PHP_FUNCTIONRegular expression for matching PHP functions.
API_RE_PHP_FUNCTION_IN_TEXTRegular expression for matching PHP functions and methods in text.
API_RE_TAG_STARTRegular expression for starting inline @tags.
API_v1_3_FILEPATH_SEPARATOR_REPLACEMENTFile path separator replacement for API v 1.3.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Generates and displays API documentation pages.
  5. *
  6. * This is an implementation of a subset of the Doxygen documentation generator
  7. * specification, tuned to produce output that best benefits the Drupal code
  8. * base. It is designed to assume the code it documents follows Drupal coding
  9. * conventions, and supports documentation blocks in formats described on
  10. * http://drupal.org/node/1354
  11. */
  12. /**
  13. * Regular expression for matching file names.
  14. */
  15. define('API_RE_FILENAME', '([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+)');
  16. /**
  17. * Regular expression for matching PHP functions.
  18. *
  19. * Taken from Drupal 7's DRUPAL_PHP_FUNCTION_PATTERN.
  20. */
  21. define('API_RE_PHP_FUNCTION', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
  22. /**
  23. * Regular expression for matching PHP functions and methods in text.
  24. *
  25. * These are patterns like ClassName::methodName(), or just function_name().
  26. */
  27. define('API_RE_PHP_FUNCTION_IN_TEXT', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*');
  28. /**
  29. * Regular expression for matching hook names.
  30. *
  31. * Since the actual PHP function name already has a prefix, these can
  32. * technically start with a number, although it's unlikely.
  33. */
  34. define('API_RE_HOOK_NAME', '[a-zA-Z0-9_\x7f-\xff]+');
  35. /**
  36. * Regular expression for matching class names in text.
  37. *
  38. * Although class names can technically be just like function names, we
  39. * only want to match class names if they include a capital letter, so as
  40. * not to be too overly aggressive.
  41. */
  42. define('API_RE_CLASS_NAME_TEXT', '[a-zA-Z0-9_\x7f-\xff]*[A-Z][a-zA-Z0-9_\x7f-\xff]*');
  43. /**
  44. * Regular expression for starting inline \@tags.
  45. */
  46. define('API_RE_TAG_START', '(?<!\\\)@');
  47. /**
  48. * File path separator.
  49. */
  50. define('API_FILEPATH_SEPARATOR', '/');
  51. /**
  52. * File path separator replacement.
  53. */
  54. define('API_FILEPATH_SEPARATOR_REPLACEMENT', '!');
  55. /**
  56. * File path separator replacement for API v 1.3.
  57. */
  58. define('API_v1_3_FILEPATH_SEPARATOR_REPLACEMENT', '--');
  59. /**
  60. * Implements hook_help().
  61. */
  62. function api_help($path, $arg) {
  63. switch ($path) {
  64. case 'admin/help#api':
  65. return t('
  66. <p>This is an implementation of a subset of the Doxygen documentation generator specification, tuned to produce output that best benefits the Drupal code base. It is designed to assume the code it documents follows Drupal coding conventions, and supports documentation blocks in formats described on !doxygen_link.</p>
  67. <h3>Set up</h3>
  68. <p>Visit the !api_settings_page to configure the module. You must have the relevant Drupal code base on the same machine as the site hosting the API module. Follow the descriptions in the \'Branches to index\' section to set up the code base for indexing.</p>
  69. <p>Indexing of PHP functions is also supported. If the site has internet access, then the default settings for the \'PHP Manual\' section should work fine. For local development environments that have a PHP manual installed, you can edit the paths to point to the appropriate locations.</p>
  70. <p>The module indexes code branches during cron runs, so make sure the site has cron functionality set up properly.</p>
  71. ', array('!api_settings_page' => l(t('API settings page'), 'admin/settings/api'), '!doxygen_link' => l('http://drupal.org/node/1354', 'http://drupal.org/node/1354')));
  72. }
  73. }
  74. /**
  75. * Returns a list of all defined branches.
  76. *
  77. * @param $_reset
  78. * If set to TRUE, the cached return value is reset.
  79. *
  80. * @return
  81. * Array of branch objects, in order by branch weight.
  82. */
  83. function api_get_branches($_reset = FALSE) {
  84. static $branches;
  85. if (!isset($branches) || $_reset) {
  86. $result = db_query("SELECT branch_id, project, project_title, branch_name, title, type, data, status, weight FROM {api_branch} ORDER BY weight");
  87. $branches = array();
  88. while ($branch = db_fetch_object($result)) {
  89. drupal_unpack($branch);
  90. $branches[$branch->branch_id] = $branch;
  91. }
  92. }
  93. return $branches;
  94. }
  95. /**
  96. * Returns a branch, given a branch ID number.
  97. *
  98. * @param $id
  99. * Branch ID number.
  100. *
  101. * @return
  102. * Branch object for the given branch ID. If $id is not set, the default
  103. * branch is returned. If the requested branch does not exist, NULL.
  104. */
  105. function api_get_branch_by_id($id = NULL) {
  106. $branches = api_get_branches();
  107. if (!isset($id)) {
  108. $id = variable_get('api_default_branch', NULL);
  109. }
  110. if (isset($branches[$id])) {
  111. return $branches[$id];
  112. }
  113. return NULL;
  114. }
  115. /**
  116. * Returns the list of currently-used branch names across all projects.
  117. *
  118. * @param $_reset
  119. * If set to TRUE, the cached return value is reset.
  120. *
  121. * @return
  122. * Array of branch names in use.
  123. */
  124. function api_get_branch_names($_reset = FALSE) {
  125. static $branch_names;
  126. if (!isset($branch_names) || $_reset) {
  127. $result = db_query("SELECT DISTINCT branch_name FROM {api_branch} WHERE status = 1");
  128. $branch_names = array();
  129. while ($branch = db_fetch_object($result)) {
  130. $branch_names[$branch->branch_name] = $branch->branch_name;
  131. }
  132. }
  133. return $branch_names;
  134. }
  135. /**
  136. * Implements hook_menu().
  137. */
  138. function api_menu() {
  139. $items = array();
  140. $branches = api_get_branches();
  141. if (count($branches)) {
  142. $default_branch = $branches[variable_get('api_default_branch', NULL)];
  143. $projects = array();
  144. // We need a default branch for each project. If a project has a branch_name
  145. // matching $default_branch, use that. Otherwise, use the max. This assumes
  146. // branch names like '5' and '6'.
  147. foreach ($branches as $branch) {
  148. if ($branch->status) {
  149. if (!isset($projects[$branch->project])) {
  150. $projects[$branch->project] = array(
  151. 'max branch' => $branch->branch_name,
  152. 'use branch' => NULL,
  153. );
  154. }
  155. else {
  156. $projects[$branch->project]['max branch'] = max($projects[$branch->project]['max branch'], $branch->branch_name);
  157. }
  158. if ($branch->branch_name === $default_branch->branch_name) {
  159. $projects[$branch->project]['use branch'] = $branch->branch_name;
  160. }
  161. }
  162. }
  163. foreach (array_keys($projects) as $project) {
  164. if (is_null($projects[$project]['use branch'])) {
  165. $projects[$project]['use branch'] = $projects[$project]['max branch'];
  166. }
  167. }
  168. // Part 1: No object, Default branch
  169. $items['api/search'] = array(
  170. 'title' => 'API Search',
  171. 'page callback' => 'drupal_get_form',
  172. 'page arguments' => array('api_search_form', $default_branch),
  173. 'access arguments' => array('access API reference'),
  174. 'type' => MENU_CALLBACK,
  175. );
  176. $items['apis'] = array(
  177. 'title' => 'API search',
  178. 'page callback' => 'api_search_redirect',
  179. 'access arguments' => array('access API reference'),
  180. 'type' => MENU_CALLBACK,
  181. 'file' => 'api.pages.inc',
  182. );
  183. }
  184. // Admin
  185. $items['admin/settings/api'] = array(
  186. 'title' => 'API reference',
  187. 'description' => 'Configure branches for documentation.',
  188. 'access callback' => 'user_access',
  189. 'access arguments' => array('administer API reference'),
  190. 'page callback' => 'api_page_admin_overview',
  191. 'file' => 'api.admin.inc',
  192. );
  193. $items['admin/settings/api/branches'] = array(
  194. 'title' => 'Branches',
  195. 'access callback' => 'user_access',
  196. 'access arguments' => array('administer API reference'),
  197. 'type' => MENU_DEFAULT_LOCAL_TASK,
  198. );
  199. $items['admin/settings/api/branches/list'] = array(
  200. 'title' => 'List',
  201. 'access callback' => 'user_access',
  202. 'access arguments' => array('administer API reference'),
  203. 'type' => MENU_DEFAULT_LOCAL_TASK
  204. );
  205. $items['admin/settings/api/branches/new'] = array(
  206. 'title' => 'New branch',
  207. 'access callback' => 'user_access',
  208. 'access arguments' => array('administer API reference'),
  209. 'page callback' => 'api_admin_new_branch_page',
  210. 'file' => 'api.admin.inc',
  211. 'type' => MENU_LOCAL_TASK,
  212. );
  213. $items['admin/settings/api/branches/reset_queue'] = array(
  214. 'title' => 'Reset parse queue',
  215. 'access callback' => 'user_access',
  216. 'access arguments' => array('administer API reference'),
  217. 'page callback' => 'drupal_get_form',
  218. 'page arguments' => array('api_reset_queue_form'),
  219. 'file' => 'api.admin.inc',
  220. 'type' => MENU_LOCAL_TASK,
  221. );
  222. $items['admin/settings/api/branches/new/%'] = array(
  223. 'title' => 'New branch',
  224. 'access callback' => 'user_access',
  225. 'access arguments' => array('administer API reference'),
  226. 'page callback' => 'drupal_get_form',
  227. 'page arguments' => array('api_branch_edit_form', 5),
  228. 'file' => 'api.admin.inc',
  229. 'type' => MENU_CALLBACK,
  230. );
  231. $items['admin/settings/api/branches/%'] = array(
  232. 'title' => 'Edit branch',
  233. 'access callback' => 'user_access',
  234. 'access arguments' => array('administer API reference'),
  235. 'page callback' => 'drupal_get_form',
  236. 'page arguments' => array('api_branch_edit_form', 4),
  237. 'file' => 'api.admin.inc',
  238. 'type' => MENU_CALLBACK,
  239. );
  240. $items['admin/settings/api/reparse/%'] = array(
  241. 'title' => 'Reparse branch',
  242. 'access callback' => 'user_access',
  243. 'access arguments' => array('administer API reference'),
  244. 'page callback' => 'api_admin_reparse',
  245. 'page arguments' => array( 4),
  246. 'file' => 'api.admin.inc',
  247. 'type' => MENU_CALLBACK,
  248. );
  249. $items['admin/settings/api/branches/%/delete'] = array(
  250. 'access callback' => 'user_access',
  251. 'access arguments' => array('administer API reference'),
  252. 'page callback' => 'drupal_get_form',
  253. 'page arguments' => array('api_branch_delete_form', 4),
  254. 'file' => 'api.admin.inc',
  255. 'type' => MENU_CALLBACK,
  256. );
  257. if (module_exists('comment')) {
  258. $items['admin/settings/api/comments'] = array(
  259. 'title' => 'Comments',
  260. 'access arguments' => array('administer API reference'),
  261. 'page callback' => 'drupal_get_form',
  262. 'page arguments' => array('api_comments_settings_form'),
  263. 'file' => 'api.admin.inc',
  264. );
  265. }
  266. // OpenSearch metadata callback.
  267. $items['api/opensearch/%'] = array(
  268. 'page callback' => 'api_opensearch',
  269. 'page arguments' => array(2),
  270. 'access arguments' => array('access API reference'),
  271. 'type' => MENU_CALLBACK,
  272. 'file' => 'api.pages.inc',
  273. );
  274. // OpenSearch suggestions callback.
  275. $items['api/suggest/%/%menu_tail'] = array(
  276. 'page callback' => 'api_suggest',
  277. 'page arguments' => array(2, 3),
  278. 'access arguments' => array('access API reference'),
  279. 'type' => MENU_CALLBACK,
  280. 'file' => 'api.pages.inc',
  281. );
  282. // Autocomplete callback.
  283. // This returns ALL possibilities for a search term.
  284. $items['api/autocomplete/%'] = array(
  285. 'page callback' => 'api_autocomplete',
  286. 'page arguments' => array(2, TRUE),
  287. 'access arguments' => array('access API reference'),
  288. 'type' => MENU_CALLBACK,
  289. );
  290. // Function dumps for IDEs and code editors.
  291. $items['api/function_dump/%'] = array(
  292. 'page callback' => 'api_page_function_dump',
  293. 'page arguments' => array(2),
  294. 'access arguments' => array('access API reference'),
  295. 'type' => MENU_CALLBACK,
  296. 'file' => 'api.pages.inc',
  297. );
  298. foreach (api_get_branches() as $branch) {
  299. if ($branch->status) {
  300. $items['api/search/'. $branch->branch_name .'/%menu_tail'] = array(
  301. 'title' => $branch->branch_name,
  302. 'page callback' => 'api_search_listing',
  303. 'page arguments' => array($branch->branch_name, 3),
  304. 'access arguments' => array('access API reference'),
  305. 'type' => MENU_LOCAL_TASK,
  306. 'file' => 'api.pages.inc',
  307. );
  308. }
  309. }
  310. // Listings
  311. // Projects
  312. $items['api/projects'] = array(
  313. 'title' => 'Projects',
  314. 'page callback' => 'api_page_projects',
  315. 'access arguments' => array('access API reference'),
  316. 'type' => MENU_CALLBACK,
  317. 'file' => 'api.pages.inc',
  318. );
  319. // Files
  320. $items['api/%/%api_filename'] = array(
  321. 'title' => 'File',
  322. 'load arguments' => array(1, 3), // project, branch
  323. 'page callback' => 'api_page_file',
  324. 'page arguments' => array(2),
  325. 'access arguments' => array('access API reference'),
  326. 'type' => MENU_CALLBACK,
  327. 'file' => 'api.pages.inc',
  328. );
  329. $items['api/%/%api_filename/theme_invokes'] = array(
  330. 'title' => 'File',
  331. 'load arguments' => array(1, 4), // project, branch
  332. 'page callback' => 'api_page_function_calls',
  333. 'page arguments' => array(2, 'theme_invokes'),
  334. 'access arguments' => array('access API reference'),
  335. 'type' => MENU_CALLBACK,
  336. 'file' => 'api.pages.inc',
  337. );
  338. // Items
  339. $items['api/%/%/function/%api_item'] = array(
  340. 'title' => 'Function',
  341. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  342. 'page callback' => 'api_page_function',
  343. 'page arguments' => array(4),
  344. 'access arguments' => array('access API reference'),
  345. 'type' => MENU_CALLBACK,
  346. 'file' => 'api.pages.inc',
  347. );
  348. $items['api/%/%/function/calls/%api_item'] = array(
  349. 'title' => 'Function calls',
  350. 'load arguments' => array(1, 6, 2, 3), // project, branch, filename, type
  351. 'page callback' => 'api_page_function_calls',
  352. 'page arguments' => array(5, 'calls'),
  353. 'access arguments' => array('access API reference'),
  354. 'type' => MENU_CALLBACK,
  355. 'file' => 'api.pages.inc',
  356. );
  357. $items['api/%/%/function/implementations/%api_item'] = array(
  358. 'title' => 'Function implementations',
  359. 'load arguments' => array(1, 6, 2, 3), // project, branch, filename, type
  360. 'page callback' => 'api_page_function_calls',
  361. 'page arguments' => array(5, 'implementations'),
  362. 'access arguments' => array('access API reference'),
  363. 'type' => MENU_CALLBACK,
  364. 'file' => 'api.pages.inc',
  365. );
  366. $items['api/%/%/function/references/%api_item'] = array(
  367. 'title' => 'Function references',
  368. 'load arguments' => array(1, 6, 2, 3), // project, branch, filename, type
  369. 'page callback' => 'api_page_function_calls',
  370. 'page arguments' => array(5, 'references'),
  371. 'access arguments' => array('access API reference'),
  372. 'type' => MENU_CALLBACK,
  373. 'file' => 'api.pages.inc',
  374. );
  375. $items['api/%/%/function/invokes/%api_item'] = array(
  376. 'title' => 'Hook invocations',
  377. 'load arguments' => array(1, 6, 2, 3), // project, branch, filename, type
  378. 'page callback' => 'api_page_function_calls',
  379. 'page arguments' => array(5, 'invokes'),
  380. 'access arguments' => array('access API reference'),
  381. 'type' => MENU_CALLBACK,
  382. 'file' => 'api.pages.inc',
  383. );
  384. $items['api/%/%/function/theme_invokes/%api_item'] = array(
  385. 'title' => 'Theme invokes',
  386. 'load arguments' => array(1, 6, 2, 3), // project, branch, filename, type
  387. 'page callback' => 'api_page_function_calls',
  388. 'page arguments' => array(5, 'theme_invokes'),
  389. 'access arguments' => array('access API reference'),
  390. 'type' => MENU_CALLBACK,
  391. 'file' => 'api.pages.inc',
  392. );
  393. $items['api/%/%/constant/%api_item'] = array(
  394. 'title' => 'Constant',
  395. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  396. 'page callback' => 'api_page_simple_item',
  397. 'page arguments' => array(4, 'constant'),
  398. 'access arguments' => array('access API reference'),
  399. 'type' => MENU_CALLBACK,
  400. 'file' => 'api.pages.inc',
  401. );
  402. $items['api/%/%/global/%api_item'] = array(
  403. 'title' => 'Global',
  404. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  405. 'page callback' => 'api_page_simple_item',
  406. 'page arguments' => array(4, 'global'),
  407. 'access arguments' => array('access API reference'),
  408. 'type' => MENU_CALLBACK,
  409. 'file' => 'api.pages.inc',
  410. );
  411. $items['api/%/%/property/%api_item'] = array(
  412. 'title' => 'Property',
  413. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  414. 'page callback' => 'api_page_simple_item',
  415. 'page arguments' => array(4, 'property'),
  416. 'access arguments' => array('access API reference'),
  417. 'type' => MENU_CALLBACK,
  418. 'file' => 'api.pages.inc',
  419. );
  420. $items['api/%/%/class/%api_item'] = array(
  421. 'title' => 'Class',
  422. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  423. 'page callback' => 'api_page_class',
  424. 'page arguments' => array(4),
  425. 'access arguments' => array('access API reference'),
  426. 'type' => MENU_CALLBACK,
  427. 'file' => 'api.pages.inc',
  428. );
  429. $items['api/%/%/interface/%api_item'] = array(
  430. 'title' => 'Interface',
  431. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  432. 'page callback' => 'api_page_class',
  433. 'page arguments' => array(4),
  434. 'access arguments' => array('access API reference'),
  435. 'type' => MENU_CALLBACK,
  436. 'file' => 'api.pages.inc',
  437. );
  438. $items['api/%/%/group/%api_item'] = array(
  439. 'title' => 'Group',
  440. 'load arguments' => array(1, 5, 2, 3), // project, branch, filename, type
  441. 'page callback' => 'api_page_group',
  442. 'page arguments' => array(4),
  443. 'access arguments' => array('access API reference'),
  444. 'type' => MENU_CALLBACK,
  445. 'file' => 'api.pages.inc',
  446. );
  447. foreach ($branches as $branch) {
  448. if ($branch->status) {
  449. $is_default = ($branch->branch_name === $projects[$branch->project]['use branch']);
  450. // Main branch page
  451. if ($is_default) {
  452. $items['api/' . $branch->project] = array(
  453. 'title' => 'API reference',
  454. 'page callback' => 'api_page_branch',
  455. 'page arguments' => array($branch),
  456. 'access arguments' => array('access API reference'),
  457. 'type' => $branch->branch_id === $default_branch->branch_id ? MENU_NORMAL_ITEM : MENU_CALLBACK,
  458. 'file' => 'api.pages.inc',
  459. );
  460. }
  461. $items['api/' . $branch->project . '/' . $branch->branch_name] = array(
  462. 'title' => $branch->title,
  463. 'page callback' => 'api_page_branch',
  464. 'page arguments' => array($branch),
  465. 'access arguments' => array('access API reference'),
  466. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  467. 'file' => 'api.pages.inc',
  468. );
  469. // Listings
  470. if ($is_default) {
  471. $items['api/' . $branch->project . '/functions'] = array(
  472. 'title' => 'Functions',
  473. 'page callback' => 'api_page_listing',
  474. 'access arguments' => array('access API reference'),
  475. 'page arguments' => array($branch, 'function'),
  476. 'type' => MENU_CALLBACK,
  477. 'file' => 'api.pages.inc',
  478. );
  479. }
  480. $items['api/' . $branch->project . '/functions/'. $branch->branch_name] = array(
  481. 'title' => $branch->title,
  482. 'page callback' => 'api_page_listing',
  483. 'page arguments' => array($branch, 'function'),
  484. 'access arguments' => array('access API reference'),
  485. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  486. 'file' => 'api.pages.inc',
  487. );
  488. if ($is_default) {
  489. $items['api/' . $branch->project . '/constants'] = array(
  490. 'title' => 'Constants',
  491. 'page callback' => 'api_page_listing',
  492. 'access arguments' => array('access API reference'),
  493. 'page arguments' => array($branch, 'constant'),
  494. 'type' => MENU_CALLBACK,
  495. 'file' => 'api.pages.inc',
  496. );
  497. }
  498. $items['api/' . $branch->project . '/constants/'. $branch->branch_name] = array(
  499. 'title' => $branch->title,
  500. 'page callback' => 'api_page_listing',
  501. 'page arguments' => array($branch, 'constant'),
  502. 'access arguments' => array('access API reference'),
  503. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  504. 'file' => 'api.pages.inc',
  505. );
  506. if ($is_default) {
  507. $items['api/' . $branch->project . '/globals'] = array(
  508. 'title' => 'Globals',
  509. 'page callback' => 'api_page_listing',
  510. 'access arguments' => array('access API reference'),
  511. 'page arguments' => array($branch, 'global'),
  512. 'type' => MENU_CALLBACK,
  513. 'file' => 'api.pages.inc',
  514. );
  515. }
  516. $items['api/' . $branch->project . '/globals/'. $branch->branch_name] = array(
  517. 'title' => $branch->title,
  518. 'page callback' => 'api_page_listing',
  519. 'page arguments' => array($branch, 'global'),
  520. 'access arguments' => array('access API reference'),
  521. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  522. 'file' => 'api.pages.inc',
  523. );
  524. if ($is_default) {
  525. $items['api/' . $branch->project . '/files'] = array(
  526. 'title' => 'Files',
  527. 'page callback' => 'api_page_listing',
  528. 'access arguments' => array('access API reference'),
  529. 'page arguments' => array($branch, 'file'),
  530. 'type' => MENU_CALLBACK,
  531. 'file' => 'api.pages.inc',
  532. );
  533. }
  534. $items['api/' . $branch->project . '/files/'. $branch->branch_name] = array(
  535. 'title' => $branch->title,
  536. 'page callback' => 'api_page_listing',
  537. 'page arguments' => array($branch, 'file'),
  538. 'access arguments' => array('access API reference'),
  539. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  540. 'file' => 'api.pages.inc',
  541. );
  542. if ($is_default) {
  543. $items['api/' . $branch->project . '/classes'] = array(
  544. 'title' => 'Classes and interfaces',
  545. 'page callback' => 'api_page_listing',
  546. 'access arguments' => array('access API reference'),
  547. 'page arguments' => array($branch, 'class'),
  548. 'type' => MENU_CALLBACK,
  549. 'file' => 'api.pages.inc',
  550. );
  551. }
  552. $items['api/' . $branch->project . '/classes/' . $branch->branch_name] = array(
  553. 'title' => $branch->title,
  554. 'page callback' => 'api_page_listing',
  555. 'page arguments' => array($branch, 'class'),
  556. 'access arguments' => array('access API reference'),
  557. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  558. 'file' => 'api.pages.inc',
  559. );
  560. if ($is_default) {
  561. $items['api/' . $branch->project . '/groups'] = array(
  562. 'title' => 'Topics',
  563. 'page callback' => 'api_page_listing',
  564. 'access arguments' => array('access API reference'),
  565. 'page arguments' => array($branch, 'group'),
  566. 'type' => MENU_CALLBACK,
  567. 'file' => 'api.pages.inc',
  568. );
  569. }
  570. $items['api/' . $branch->project . '/groups/'. $branch->branch_name] = array(
  571. 'title' => $branch->title,
  572. 'page callback' => 'api_page_listing',
  573. 'page arguments' => array($branch, 'group'),
  574. 'access arguments' => array('access API reference'),
  575. 'type' => $is_default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
  576. 'file' => 'api.pages.inc',
  577. );
  578. }
  579. }
  580. // Redirect 1.0 file links.
  581. $items['api/file/%menu_tail'] = array(
  582. 'page callback' => 'api_file_redirect',
  583. 'page arguments' => array(2),
  584. 'access callback' => TRUE,
  585. 'file' => 'legacy.inc',
  586. );
  587. // Redirect 1.1 links.
  588. // Objects
  589. $items['api/function/%api_legacy_1_2_object'] =
  590. $items['api/function/%api_legacy_1_2_object/%'] = array(
  591. 'load arguments' => array('function', 3),
  592. 'page callback' => 'drupal_goto',
  593. 'page arguments' => array(2),
  594. 'access callback' => TRUE,
  595. 'type' => MENU_CALLBACK,
  596. );
  597. $items['api/constant/%api_legacy_1_2_object'] =
  598. $items['api/constant/%api_legacy_1_2_object/%'] = array(
  599. 'load arguments' => array('constant', 3),
  600. 'page callback' => 'drupal_goto',
  601. 'page arguments' => array(2),
  602. 'access callback' => TRUE,
  603. 'type' => MENU_CALLBACK,
  604. );
  605. $items['api/global/%api_legacy_1_2_object'] =
  606. $items['api/global/%api_legacy_1_2_object/%'] = array(
  607. 'load arguments' => array('global', 3),
  608. 'page callback' => 'drupal_goto',
  609. 'page arguments' => array(2),
  610. 'access callback' => TRUE,
  611. 'type' => MENU_CALLBACK,
  612. );
  613. $items['api/group/%api_legacy_1_2_object'] =
  614. $items['api/group/%api_legacy_1_2_object/%'] = array(
  615. 'load arguments' => array('group', 3),
  616. 'page callback' => 'drupal_goto',
  617. 'page arguments' => array(2),
  618. 'access callback' => TRUE,
  619. 'type' => MENU_CALLBACK,
  620. );
  621. // Default listings
  622. $items['api/functions'] = array(
  623. 'page callback' => 'drupal_goto',
  624. 'page arguments' => array('api/' . $default_branch->project . '/functions'),
  625. 'access callback' => TRUE,
  626. 'type' => MENU_CALLBACK,
  627. );
  628. $items['api/files'] = array(
  629. 'page callback' => 'drupal_goto',
  630. 'page arguments' => array('api/' . $default_branch->project . '/files'),
  631. 'access callback' => TRUE,
  632. 'type' => MENU_CALLBACK,
  633. );
  634. $items['api/constants'] = array(
  635. 'page callback' => 'drupal_goto',
  636. 'page arguments' => array('api/' . $default_branch->project . '/constants'),
  637. 'access callback' => TRUE,
  638. 'type' => MENU_CALLBACK,
  639. );
  640. $items['api/globals'] = array(
  641. 'page callback' => 'drupal_goto',
  642. 'page arguments' => array('api/' . $default_branch->project . '/globals'),
  643. 'access callback' => TRUE,
  644. 'type' => MENU_CALLBACK,
  645. );
  646. $items['api/groups'] = array(
  647. 'page callback' => 'drupal_goto',
  648. 'page arguments' => array('api/' . $default_branch->project . '/groups'),
  649. 'access callback' => TRUE,
  650. 'type' => MENU_CALLBACK,
  651. );
  652. $items['api'] = array(
  653. 'page callback' => 'drupal_goto',
  654. 'page arguments' => array('api/' . $default_branch->project),
  655. 'access callback' => TRUE,
  656. 'type' => MENU_CALLBACK,
  657. );
  658. // Branch listings
  659. $items['api/functions/%api_legacy_1_2_listing'] = array(
  660. 'load arguments' => array('functions'),
  661. 'page callback' => 'drupal_goto',
  662. 'page arguments' => array(2),
  663. 'access callback' => TRUE,
  664. 'type' => MENU_CALLBACK,
  665. );
  666. $items['api/files/%api_legacy_1_2_listing'] = array(
  667. 'load arguments' => array('files'),
  668. 'page callback' => 'drupal_goto',
  669. 'page arguments' => array(2),
  670. 'access callback' => TRUE,
  671. 'type' => MENU_CALLBACK,
  672. );
  673. $items['api/constants/%api_legacy_1_2_listing'] = array(
  674. 'load arguments' => array('constants'),
  675. 'page callback' => 'drupal_goto',
  676. 'page arguments' => array(2),
  677. 'access callback' => TRUE,
  678. 'type' => MENU_CALLBACK,
  679. );
  680. $items['api/globals/%api_legacy_1_2_listing'] = array(
  681. 'load arguments' => array('globals'),
  682. 'page callback' => 'drupal_goto',
  683. 'page arguments' => array(2),
  684. 'access callback' => TRUE,
  685. 'type' => MENU_CALLBACK,
  686. );
  687. $items['api/groups/%api_legacy_1_2_listing'] = array(
  688. 'load arguments' => array('groups'),
  689. 'page callback' => 'drupal_goto',
  690. 'page arguments' => array(2),
  691. 'access callback' => TRUE,
  692. 'type' => MENU_CALLBACK,
  693. );
  694. $items['api/%api_legacy_1_2_listing'] = array(
  695. 'page callback' => 'drupal_goto',
  696. 'page arguments' => array(1),
  697. 'access callback' => TRUE,
  698. 'type' => MENU_CALLBACK,
  699. );
  700. return $items;
  701. }
  702. /**
  703. * Finds objects for old URLs and returns the new URL.
  704. *
  705. * @param $object_name
  706. * Name of the object to find the URL of.
  707. * @param $object_type
  708. * Type of object ('function', 'constant', etc.).
  709. * @param $branch_name
  710. * Name of the branch to find the object in (across projects).
  711. *
  712. * @return
  713. * First matching URL for this combination of object name, type, and branch
  714. * name, or NULL if none is found.
  715. */
  716. function api_legacy_1_2_object_load($object_name, $object_type, $branch_name) {
  717. foreach (api_get_branches_by_name($branch_name) as $branch) {
  718. $object = api_object_load($object_name, $branch, $object_type);
  719. if (isset($object)) {
  720. return api_url($object);
  721. }
  722. }
  723. }
  724. /**
  725. * Returns the current path to listing pages accessed with old URLs.
  726. *
  727. * @param $branch_name
  728. * Name of the branch to redirect.
  729. * @param $type
  730. * Type of listing page ('functions', etc.), or NULL for the home page.
  731. *
  732. * @return
  733. * URL to redirect this listing to, in the default project.
  734. */
  735. function api_legacy_1_2_listing_load($branch_name, $type = NULL) {
  736. $branch = api_get_branch_by_id();
  737. if (empty($branch_name)) {
  738. if (isset($branch)) {
  739. $branch_name = $branch->branch_name;
  740. }
  741. else {
  742. $branch_name = '';
  743. }
  744. }
  745. if (isset($type)) {
  746. return 'api/' . $branch->project . '/' . $type . '/' . $branch_name;
  747. }
  748. else {
  749. return 'api/' . $branch->project . '/' . $branch_name;
  750. }
  751. }
  752. /**
  753. * Finds all branches matching a branch name, across projects.
  754. *
  755. * @param $branch_name
  756. * Branch name to match. If empty, defaults to the default branch.
  757. *
  758. * @return
  759. * Array of all branch objects with this name.
  760. */
  761. function api_get_branches_by_name($branch_name) {
  762. $return = array();
  763. if (empty($branch_name)) {
  764. $branch = api_get_branch_by_id();
  765. if (isset($branch)) {
  766. $branch_name = $branch->branch_name;
  767. }
  768. else {
  769. return array();
  770. }
  771. }
  772. foreach (api_get_branches() as $branch) {
  773. if ($branch->branch_name === $branch_name) {
  774. $return[] = $branch;
  775. }
  776. }
  777. return $return;
  778. }
  779. /**
  780. * Loads a branch, given a project and branch name.
  781. *
  782. * @param $project
  783. * The project name matching {api_branch}.project.
  784. * @param $branch_name
  785. * The branch name matching {api_branch}.branch_name. If not set, uses the
  786. * default branch name.
  787. *
  788. * @return
  789. * Object representing the branch, or NULL if there is no match.
  790. */
  791. function api_get_branch_by_name($project, $branch_name) {
  792. if (empty($branch_name)) {
  793. $branch = api_get_branch_by_id();
  794. if (isset($branch)) {
  795. $branch_name = $branch->branch_name;
  796. }
  797. else {
  798. $branch_name = '';
  799. }
  800. }
  801. foreach (api_get_branches() as $branch) {
  802. if ($branch->project === $project && $branch->branch_name === $branch_name) {
  803. return $branch;
  804. }
  805. }
  806. return NULL;
  807. }
  808. /**
  809. * Loads an API object for the menu router.
  810. *
  811. * Menu object load callback for %api_item in menu paths.
  812. *
  813. * @param $object_name
  814. * The object name matching {api_documentation}.object_name.
  815. * @param $project
  816. * The project name matching {api_branch}.project.
  817. * @param $branch_name
  818. * The branch name matching {api_branch}.branch_name.
  819. * @param $file_name
  820. * The name of the file the object is in, with API_FILEPATH_SEPARATOR for path
  821. * separators. We convert all API_FILEPATH_SEPARATOR_REPLACEMENTs back to
  822. * API_FILEPATH_SEPARATORs to maintain the original file path information.
  823. * (When a URL is created for a documentation object, all
  824. * API_FILEPATH_SEPARATORs in the original file path are replaced with
  825. * API_FILEPATH_SEPARATOR_REPLACEMENTs so that API_FILEPATH_SEPARATORs won't
  826. * be interpreted as part the of URL.
  827. * @param $type
  828. * API item type; one of function, constant, global, property, class,
  829. * interface, or group.
  830. *
  831. * @return
  832. * Loaded documentation object, or FALSE if not found (returning FALSE will
  833. * cause the Drupal menu system to recognize it's a 404 error).
  834. */
  835. function api_item_load($object_name, $project, $branch_name, $file_name, $type) {
  836. // Check type
  837. if (!in_array($type, array('function', 'constant', 'global', 'property', 'class', 'interface', 'group'))) {
  838. return FALSE;
  839. }
  840. // Load branch
  841. $branch = api_get_branch_by_name($project, $branch_name);
  842. if (is_null($branch)) {
  843. return FALSE;
  844. }
  845. // Load object
  846. $back_to_orig_filename = str_replace(API_FILEPATH_SEPARATOR_REPLACEMENT, API_FILEPATH_SEPARATOR, $file_name);
  847. $doc_object = api_object_load($object_name, $branch, $type, $back_to_orig_filename);
  848. if (empty($doc_object) && (strpos($file_name, API_v1_3_FILEPATH_SEPARATOR_REPLACEMENT) !== FALSE)) {
  849. // May be an API 1.3 style path. See if we can load a valid object with the
  850. // old replacement pattern.
  851. $doc_object = api_object_load($object_name, $branch, $type, str_replace(API_v1_3_FILEPATH_SEPARATOR_REPLACEMENT, API_FILEPATH_SEPARATOR, $file_name) );
  852. // If we could, redirect to the current URL.
  853. if (!empty($doc_object)) {
  854. drupal_goto(api_url($doc_object));
  855. }
  856. }
  857. if (empty($doc_object)) {
  858. return FALSE;
  859. }
  860. return $doc_object;
  861. }
  862. /**
  863. * Implements hook_cron_queue_info().
  864. */
  865. function api_cron_queue_info() {
  866. return array(
  867. 'api_parse' => array(
  868. 'worker callback' => 'api_queue_parse_file',
  869. 'time' => 15,
  870. ),
  871. 'api_delete' => array(
  872. 'worker callback' => 'api_queue_file_delete',
  873. 'time' => 15,
  874. ),
  875. );
  876. }
  877. /**
  878. * Loads an API file object.
  879. *
  880. * Menu object load callback for %api_filename in menu paths.
  881. *
  882. * API v 1.3 replaced all '/'s in the file path with '--' for the URL generated
  883. * for the file. We need to handle a URL with '--' (=
  884. * API_v1_3_FILEPATH_SEPARATOR_REPLACEMENT) and redirected it to the current,
  885. * correct URL. We now convert all '/'s (= API_FILEPATH_SEPARATOR) to
  886. * API_FILEPATH_SEPARATOR_REPLACEMENT when a URL is created for a documentatio
  887. * object. Here we need to go back to the original filename and path.
  888. *
  889. * @param $file_name
  890. * Name of the file to load.
  891. * @param $project
  892. * Name of the project the file is in.
  893. * @param $branch_name
  894. * Name of the branch the file is in.
  895. *
  896. * @return
  897. * Loaded documentation object, or FALSE if not found (returning FALSE will
  898. * cause the Drupal menu system to recognize it's a 404 error).
  899. */
  900. function api_filename_load($file_name, $project, $branch_name) {
  901. $branch = api_get_branch_by_name($project, $branch_name);
  902. if (is_null($branch)) {
  903. return FALSE;
  904. }
  905. $doc_object = api_object_load(str_replace(API_FILEPATH_SEPARATOR_REPLACEMENT, API_FILEPATH_SEPARATOR, $file_name), $branch, 'file');
  906. if (empty($doc_object) && (strpos($file_name, API_v1_3_FILEPATH_SEPARATOR_REPLACEMENT) !== FALSE)) {
  907. // May be an API 1.3 style path. See if we can load a valid object with the
  908. // old replacement pattern.
  909. $doc_object = api_object_load(str_replace(API_v1_3_FILEPATH_SEPARATOR_REPLACEMENT, API_FILEPATH_SEPARATOR, $file_name), $branch, 'file');
  910. // If we could, redirect to the current URL.
  911. if (!empty($doc_object)) {
  912. drupal_goto(api_url($doc_object));
  913. }
  914. }
  915. if (empty($doc_object)) {
  916. return FALSE;
  917. }
  918. return $doc_object;
  919. }
  920. /**
  921. * Loads a documentation object.
  922. *
  923. * @param $object_name_or_did
  924. * The string object name or documentation ID to load.
  925. * @param $branch
  926. * Branch object.
  927. * @param $object_type
  928. * A string type, or array of strings: class, interface, function, etc.
  929. * @param $file_name
  930. * Name of the file the object is in (if needed).
  931. *
  932. * @return
  933. * Object with information about the matching documentation, or NULL if it
  934. * does not exist.
  935. */
  936. function api_object_load($object_name_or_did, $branch, $object_type, $file_name = NULL) {
  937. static $cache;
  938. if (!is_array($object_type)) {
  939. $object_type = array($object_type);
  940. }
  941. $key = $object_name_or_did . ':' . implode('-', $object_type) . ':' . $branch->branch_id . ':' . $file_name;
  942. // See if we have this cached.
  943. if (isset($cache[$key])) {
  944. return $cache[$key];
  945. }
  946. // We don't have a cached value. Prepare the query.
  947. $tables = array('{api_documentation} ad', 'LEFT JOIN {api_overrides} ao ON ao.did = ad.did');
  948. $fields = array('ad.did', 'ad.branch_id', 'ad.object_name', 'ad.object_type', 'ad.title', 'ad.file_name', 'ad.summary', 'ad.documentation', 'ad.code', 'ad.start_line', 'ad.see', 'ad.class_did', 'ad.var', 'ad.throws', 'ao.overrides_did', 'ao.root_did', 'ad.member_name', 'ao.documented_did');
  949. $where = "WHERE ad.object_type IN (" . db_placeholders($object_type, 'text') . ") AND ad.branch_id = %d";
  950. $arguments = $object_type;
  951. $arguments[] = $branch->branch_id;
  952. if (is_int($object_name_or_did)) {
  953. $where .= " AND ad.did = %d";
  954. $arguments[] = $object_name_or_did;
  955. }
  956. else {
  957. $where .= " AND ad.object_name = '%s'";
  958. $arguments[] = $object_name_or_did;
  959. }
  960. if (!is_null($file_name)) {
  961. $where .= " AND ad.file_name = '%s'";
  962. $arguments[] = $file_name;
  963. }
  964. if (in_array('function', $object_type)) {
  965. $tables[] = 'LEFT JOIN {api_function} af ON af.did = ad.did';
  966. $fields[] = 'af.signature, af.parameters, af.return_value';
  967. }
  968. elseif (in_array('file', $object_type)) {
  969. $tables[] = 'LEFT JOIN {api_file} af ON af.did = ad.did';
  970. $fields[] = 'af.modified, af.queued';
  971. }
  972. // Now build the object. Don't store a NULL or FALSE result in the cache in
  973. // case we need to try again with a different filename or other argument.
  974. $result_object = db_fetch_object(db_query_range('SELECT '. implode(', ', $fields) .' FROM '. implode(' ', $tables) .' '. $where, $arguments, 0, 1));
  975. if (isset($result_object) && $result_object) {
  976. // Grab documentation from documented parent.
  977. if (!empty($result_object->documented_did) && $result_object->documented_did !== $result_object->did) {
  978. $documented_object = api_object_load((int) $result_object->documented_did, $branch, $object_type);
  979. if (isset($documented_object)) {
  980. foreach (array('documentation', 'parameters', 'return_value', 'see', 'throws', 'var') as $member) {
  981. $result_object->$member = $documented_object->$member;
  982. }
  983. }
  984. }
  985. // Store in the cache.
  986. $cache[$key] = $result_object;
  987. return $result_object;
  988. }
  989. return NULL;
  990. }
  991. /**
  992. * Implements hook_perm().
  993. */
  994. function api_perm() {
  995. return array('access API reference', 'administer API reference');
  996. }
  997. /**
  998. * Implements hook_theme().
  999. */
  1000. function api_theme() {
  1001. return array(
  1002. 'api_branch_table' => array(
  1003. 'arguments' => array('element' => NULL),
  1004. ),
  1005. 'api_defined' => array(
  1006. 'arguments' => array(
  1007. 'branch' => NULL,
  1008. 'object' => NULL,
  1009. ),
  1010. 'path' => drupal_get_path('module', 'api') . '/templates',
  1011. 'template' => 'api-defined',
  1012. 'file' => 'api.pages.inc',
  1013. 'preprocess functions' => array('api_preprocess_api_defined'),
  1014. ),
  1015. 'api_related_topics' => array(
  1016. 'arguments' => array(
  1017. 'topics' => array(),
  1018. ),
  1019. 'path' => drupal_get_path('module', 'api') . '/templates',
  1020. 'template' => 'api-related-topics',
  1021. ),
  1022. 'api_functions' => array(
  1023. 'arguments' => array(
  1024. 'functions' => array(),
  1025. ),
  1026. 'path' => drupal_get_path('module', 'api') . '/templates',
  1027. 'template' => 'api-functions',
  1028. ),
  1029. 'api_function_page' => array(
  1030. 'arguments' => array(
  1031. 'branch' => NULL,
  1032. 'object' => NULL,
  1033. 'signatures' => NULL,
  1034. 'documentation' => NULL,
  1035. 'parameters' => NULL,
  1036. 'return' => NULL,
  1037. 'related_topics' => NULL,
  1038. 'call_links' => array(),
  1039. 'code' => NULL,
  1040. 'see' => NULL,
  1041. 'throws' => NULL,
  1042. ),
  1043. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1044. 'path' => drupal_get_path('module', 'api') . '/templates',
  1045. 'template' => 'api-function-page',
  1046. 'file' => 'api.pages.inc',
  1047. ),
  1048. 'api_constant_page' => array(
  1049. 'arguments' => array(
  1050. 'branch' => NULL,
  1051. 'object' => NULL,
  1052. 'documentation' => NULL,
  1053. 'code' => NULL,
  1054. 'related_topics' => NULL,
  1055. 'see' => NULL,
  1056. ),
  1057. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1058. 'path' => drupal_get_path('module', 'api') . '/templates',
  1059. 'template' => 'api-constant-page',
  1060. 'file' => 'api.pages.inc',
  1061. ),
  1062. 'api_global_page' => array(
  1063. 'arguments' => array(
  1064. 'branch' => NULL,
  1065. 'object' => NULL,
  1066. 'documentation' => NULL,
  1067. 'code' => NULL,
  1068. 'related_topics' => NULL,
  1069. 'see' => NULL,
  1070. ),
  1071. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1072. 'path' => drupal_get_path('module', 'api') . '/templates',
  1073. 'template' => 'api-global-page',
  1074. 'file' => 'api.pages.inc',
  1075. ),
  1076. 'api_property_page' => array(
  1077. 'arguments' => array(
  1078. 'branch' => NULL,
  1079. 'object' => NULL,
  1080. 'documentation' => NULL,
  1081. 'code' => NULL,
  1082. 'related_topics' => NULL,
  1083. 'see' => NULL,
  1084. 'var' => NULL,
  1085. ),
  1086. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1087. 'path' => drupal_get_path('module', 'api') . '/templates',
  1088. 'template' => 'api-property-page',
  1089. 'file' => 'api.pages.inc',
  1090. ),
  1091. 'api_class_page' => array(
  1092. 'arguments' => array(
  1093. 'branch' => NULL,
  1094. 'object' => NULL,
  1095. 'documentation' => NULL,
  1096. 'implements' => NULL,
  1097. 'hierarchy' => NULL,
  1098. 'objects' => NULL,
  1099. 'code' => NULL,
  1100. 'related_topics' => NULL,
  1101. 'see' => NULL,
  1102. ),
  1103. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1104. 'path' => drupal_get_path('module', 'api') . '/templates',
  1105. 'template' => 'api-class-page',
  1106. 'file' => 'api.pages.inc',
  1107. ),
  1108. 'api_file_page' => array(
  1109. 'arguments' => array(
  1110. 'object' => NULL,
  1111. 'documentation' => NULL,
  1112. 'objects' => NULL,
  1113. 'code' => NULL,
  1114. 'see' => NULL,
  1115. 'related_topics' => NULL,
  1116. 'defined' => '',
  1117. 'call_links' => array(),
  1118. ),
  1119. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1120. 'path' => drupal_get_path('module', 'api') . '/templates',
  1121. 'template' => 'api-file-page',
  1122. 'file' => 'api.pages.inc',
  1123. ),
  1124. 'api_group_page' => array(
  1125. 'arguments' => array(
  1126. 'branch' => NULL,
  1127. 'object' => NULL,
  1128. 'documentation' => NULL,
  1129. 'objects' => NULL,
  1130. 'see' => NULL,
  1131. ),
  1132. 'preprocess functions' => array('api_preprocess_api_object_page'),
  1133. 'path' => drupal_get_path('module', 'api') . '/templates',
  1134. 'template' => 'api-group-page',
  1135. 'file' => 'api.pages.inc',
  1136. ),
  1137. 'api_branch_default_page' => array(
  1138. 'arguments' => array(
  1139. 'branch' => NULL,
  1140. ),
  1141. 'path' => drupal_get_path('module', 'api') . '/templates',
  1142. 'template' => 'api-branch-default-page'
  1143. ),
  1144. 'api_function_reference_link' => array(
  1145. 'arguments' => array(
  1146. 'type' => '',
  1147. 'count' => 0,
  1148. 'function' => (object) array(),
  1149. ),
  1150. 'file' => 'api.pages.inc',
  1151. ),
  1152. );
  1153. }
  1154. /**
  1155. * Sets or returns the HTML page title.
  1156. *
  1157. * @param $title
  1158. * If provided, sets the page title to this string.
  1159. *
  1160. * @return
  1161. * The previously-set page title, or NULL if it has not been set during
  1162. * this page load.
  1163. */
  1164. function api_set_html_page_title($title = NULL) {
  1165. static $page_title = NULL;
  1166. if (isset($title)) {
  1167. $page_title = $title;
  1168. }
  1169. return $page_title;
  1170. }
  1171. /**
  1172. * Preprocess pages: sets the page title if it's an API module page.
  1173. */
  1174. function api_preprocess_page(&$variables) {
  1175. $title = api_set_html_page_title();
  1176. if ($title) {
  1177. $variables['head_title'] = $title;
  1178. }
  1179. }
  1180. /**
  1181. * Implements hook_init().
  1182. *
  1183. * Adds CSS and JavaScript for the search auto-complete. Adds OpenSearch
  1184. * autodiscovery links. Redirects nodes of type 'api' to the correct URL.
  1185. */
  1186. function api_init() {
  1187. drupal_add_css(drupal_get_path('module', 'api') . '/jquery-autocomplete/jquery.autocomplete.css');
  1188. drupal_add_js(drupal_get_path('module', 'api') . '/jquery-autocomplete/jquery.autocomplete.js');
  1189. drupal_add_css(drupal_get_path('module', 'api') . '/api.css');
  1190. drupal_add_js(drupal_get_path('module', 'api') . '/api.js');
  1191. $branch = api_get_active_branch();
  1192. if (isset($branch)) {
  1193. drupal_add_js(array('apiAutoCompletePath' => base_path() . variable_get('api_autocomplete_path_' . $branch->branch_name, 'api/autocomplete/' . $branch->branch_name)), 'setting');
  1194. }
  1195. // Add OpenSearch autodiscovery links.
  1196. foreach (api_get_branch_names() as $branch_name) {
  1197. $title = t('Drupal API @branch', array('@branch' => $branch_name));
  1198. $url = url('api/opensearch/'. $branch_name, array('absolute' => TRUE));
  1199. drupal_set_html_head('<link rel="search" type="application/opensearchdescription+xml" href="'. $url .'" title="'. $title .'" />');
  1200. }
  1201. // If we happen to be on an API node page, redirect.
  1202. if (($node = menu_get_object()) && $node->type == 'api') {
  1203. $documentation = db_fetch_object(db_query('SELECT branch_id, object_type, file_name, object_name FROM {api_documentation} WHERE did = %d', $node->nid));
  1204. drupal_goto(api_url($documentation));
  1205. }
  1206. }
  1207. /**
  1208. * Implements hook_db_rewrite_sql().
  1209. *
  1210. * Excludes nodes of type 'api' from node queries.
  1211. */
  1212. function api_db_rewrite_sql($query, $primary_table, $primary_field) {
  1213. if ($primary_field == 'nid' && $primary_table == 'n') {
  1214. return array('where' => "n.type <> 'api'");
  1215. }
  1216. }
  1217. /**
  1218. * Implements hook_block().
  1219. */
  1220. function api_block($op, $delta = NULL, $edit = array()) {
  1221. switch ($op) {
  1222. case 'list':
  1223. return array(
  1224. 'api-search' => array(
  1225. 'info' => t('API search'),
  1226. 'cache' => BLOCK_CACHE_PER_PAGE,
  1227. ),
  1228. 'navigation' => array(
  1229. 'info' => t('API navigation'),
  1230. 'cache' => BLOCK_CACHE_PER_PAGE,
  1231. ),
  1232. );
  1233. case 'view':
  1234. $branch = api_get_active_branch();
  1235. switch ($delta) {
  1236. case 'api-search':
  1237. if (user_access('access API reference') && !empty($branch)) {
  1238. return array(
  1239. 'subject' => t('Search @branch', array('@branch' => $branch->branch_name)),
  1240. 'content' => drupal_get_form('api_search_form', $branch),
  1241. );
  1242. }
  1243. return;
  1244. case 'navigation':
  1245. if (user_access('access API reference') && !empty($branch)) {
  1246. $links = array();
  1247. $links[] = l($branch->title, 'api/' . $branch->project . '/' . $branch->branch_name);
  1248. $counts = api_listing_counts($branch);
  1249. if ($counts['constants'] > 0) {
  1250. $links[] = l(t('Constants'), 'api/' . $branch->project . '/constants/' . $branch->branch_name);
  1251. }
  1252. if ($counts['classes'] > 0) {
  1253. $links[] = l(t('Classes'), 'api/' . $branch->project . '/classes/' . $branch->branch_name);
  1254. }
  1255. if ($counts['files'] > 0) {
  1256. $links[] = l(t('Files'), 'api/' . $branch->project . '/files/' . $branch->branch_name);
  1257. }
  1258. if ($counts['functions'] > 0) {
  1259. $links[] = l(t('Functions'), 'api/' . $branch->project . '/functions/' . $branch->branch_name);
  1260. }
  1261. if ($counts['globals'] > 0) {
  1262. $links[] = l(t('Globals'), 'api/' . $branch->project . '/globals/' . $branch->branch_name);
  1263. }
  1264. if ($counts['groups'] > 0) {
  1265. $links[] = l(t('Topics'), 'api/' . $branch->project . '/groups/' . $branch->branch_name);
  1266. }
  1267. return array(
  1268. 'subject' => t('API Navigation'),
  1269. 'content' => theme('item_list', $links),
  1270. );
  1271. }
  1272. return;
  1273. }
  1274. }
  1275. }
  1276. /**
  1277. * Calculates the counts of each type of listing for a branch.
  1278. *
  1279. * @param $branch
  1280. * Object representing the branch to count.
  1281. *
  1282. * @return
  1283. * Associative array where the keys are the type of listing ('functions',
  1284. * 'classes', etc.) and the values are the count of how many there are in
  1285. * that listing for the given branch.
  1286. */
  1287. function api_listing_counts($branch) {
  1288. static $cached_counts = array();
  1289. // Check the cache.
  1290. $key = $branch->branch_name . $branch->branch_id;
  1291. if (isset($cached_counts[$key])) {
  1292. return $cached_counts[$key];
  1293. }
  1294. $return = array(
  1295. 'groups' => 0,
  1296. 'classes' => 0,
  1297. 'functions' => 0,
  1298. 'constants' => 0,
  1299. 'globals' => 0,
  1300. 'files' => 0,
  1301. );
  1302. // These queries mirror what is done in api_page_listing().
  1303. $result = db_query("SELECT COUNT(*) as num FROM {api_documentation} WHERE branch_id = %d AND object_type = 'group' GROUP BY branch_id", $branch->branch_id);
  1304. while ($obj = db_fetch_object($result)) {
  1305. $return['groups'] = $obj->num;
  1306. break;
  1307. }
  1308. $result = db_query("SELECT COUNT(*) as num FROM {api_documentation} WHERE branch_id = %d AND ( object_type = 'class' OR 'object_type' = 'interface') AND class_did = 0 GROUP BY branch_id", $branch->branch_id);
  1309. while ($obj = db_fetch_object($result)) {
  1310. $return['classes'] = $obj->num;
  1311. break;
  1312. }
  1313. foreach (array('function', 'constant', 'global', 'file') as $type) {
  1314. $result = db_query("SELECT COUNT(*) as num FROM {api_documentation} WHERE branch_id = %d AND object_type = '%s' AND class_did = 0 GROUP BY branch_id", $branch->branch_id, $type);
  1315. while ($obj = db_fetch_object($result)) {
  1316. $return[$type . 's'] = $obj->num;
  1317. break;
  1318. }
  1319. }
  1320. $cached_counts[$key] = $return;
  1321. return $return;
  1322. }
  1323. /**
  1324. * Implementation of hook_filter().
  1325. */
  1326. function api_filter($op, $delta = 0, $format = -1, $text = '') {
  1327. switch ($op) {
  1328. case 'list':
  1329. return array(0 => t('API filter'));
  1330. case 'description':
  1331. return t('Add links to API objects, like theme() or theme.inc.');
  1332. case 'process':
  1333. return api_filter_documentation($text, api_get_active_branch());
  1334. default:
  1335. return $text;
  1336. }
  1337. }
  1338. /**
  1339. * Constructs a link to an API object page.
  1340. *
  1341. * Construct a URL for an object, replacing any API_FILEPATH_SEPARATOR in a
  1342. * file path with API_FILEPATH_SEPARATOR_REPLACEMENT.
  1343. *
  1344. * @param $object
  1345. * An API object with object_type, object_name, and file_name properties.
  1346. * @param $file
  1347. * TRUE links to the object’s containing file, FALSE links to the object
  1348. * itself.
  1349. *
  1350. * @return
  1351. * A URL string, or an empty string if there was a problem.
  1352. */
  1353. function api_url($object, $file = FALSE) {
  1354. $branch = api_get_branch_by_id($object->branch_id);
  1355. if (!$branch) {
  1356. return '';
  1357. }
  1358. if ($file) {
  1359. $replaced_string = str_replace(API_FILEPATH_SEPARATOR, API_FILEPATH_SEPARATOR_REPLACEMENT, $object->file_name);
  1360. return 'api/' . $branch->project . '/' . $replaced_string . '/' . $branch->branch_name;
  1361. }
  1362. elseif ($object->object_type === 'file') {
  1363. $replaced_string = str_replace(API_FILEPATH_SEPARATOR, API_FILEPATH_SEPARATOR_REPLACEMENT, $object->object_name);
  1364. return 'api/' . $branch->project . '/' . $replaced_string . '/' . $branch->branch_name;
  1365. }
  1366. else {
  1367. $replaced_string = str_replace(API_FILEPATH_SEPARATOR, API_FILEPATH_SEPARATOR_REPLACEMENT, $object->file_name);
  1368. return 'api/' . $branch->project . '/' . $replaced_string . '/' . $object->object_type . '/' . $object->object_name . '/' . $branch->branch_name;
  1369. }
  1370. }
  1371. /**
  1372. * Saves an API branch.
  1373. *
  1374. * @param $branch
  1375. * A branch object, with branch_name, title, and directories properties.
  1376. */
  1377. function api_save_branch($branch) {
  1378. $branch->data = serialize($branch->data);
  1379. if (empty($branch->branch_id)) {
  1380. drupal_write_record('api_branch', $branch);
  1381. if (is_null(variable_get('api_default_branch', NULL))) {
  1382. variable_set('api_default_branch', $branch->branch_id);
  1383. }
  1384. }
  1385. else {
  1386. drupal_write_record('api_branch', $branch, 'branch_id');
  1387. }
  1388. // Reweight all branches.
  1389. api_get_branch_names(TRUE);
  1390. $branches = api_get_branches(TRUE);
  1391. usort($branches, 'api_branch_sort');
  1392. $weight = 0;
  1393. foreach ($branches as $branch) {
  1394. $branch->weight = $weight;
  1395. $weight += 1;
  1396. drupal_write_record('api_branch', $branch, 'branch_id');
  1397. }
  1398. menu_rebuild();
  1399. }
  1400. /**
  1401. * Callback for usort() within api_save_branch().
  1402. *
  1403. * Sorts branches by type, version number, and branch name.
  1404. */
  1405. function api_branch_sort($a, $b) {
  1406. // PHP type branches should come after file type branches.
  1407. $result = strcmp($a->type, $b->type);
  1408. if ($result != 0) {
  1409. return $result;
  1410. }
  1411. // Sort by numeric version number next.
  1412. $result = version_compare($a->branch_name, $b->branch_name);
  1413. if ($result != 0) {
  1414. return $result;
  1415. }
  1416. // If they are the same type and version_compare said they were the same,
  1417. // then just do alphabetical.
  1418. return strcasecmp($a->branch_name, $b->branch_name);
  1419. }
  1420. /**
  1421. * Returns the currently active branch object.
  1422. */
  1423. function api_get_active_branch() {
  1424. static $branch;
  1425. if (!isset($branch)) {
  1426. $item = menu_get_item();
  1427. $branches = api_get_branches();
  1428. $branch_names = api_get_branch_names();
  1429. $default_branch = variable_get('api_default_branch', NULL);
  1430. if (isset($item['page_arguments'][0]->branch_id)) {
  1431. $branch = $branches[$item['page_arguments'][0]->branch_id];
  1432. }
  1433. elseif (isset($item['page_arguments'][0]->branch_name)) {
  1434. $branch = $item['page_arguments'][0];
  1435. }
  1436. elseif (strpos($item['path'], 'api/search') === 0 && isset($branch_names[$item['page_arguments'][0]])) {
  1437. // Search page, use the default project if possible
  1438. foreach ($branches as $possible_branch) {
  1439. if ($possible_branch->project === $branches[$default_branch]->project && $possible_branch->branch_name === $item['page_arguments'][0]) {
  1440. $branch = $possible_branch;
  1441. }
  1442. }
  1443. }
  1444. if (!isset($branch)) {
  1445. if (!is_null($default_branch)) {
  1446. $branch = $branches[$default_branch];
  1447. }
  1448. else {
  1449. $branch = NULL;
  1450. }
  1451. }
  1452. }
  1453. return $branch;
  1454. }
  1455. /**
  1456. * Form constructor for the API search form.
  1457. *
  1458. * @param $branch
  1459. * Object representing the branch to build the search form for.
  1460. *
  1461. * @see api_search_form_submit()
  1462. */
  1463. function api_search_form($form_state, $branch) {
  1464. $form = array();
  1465. $form['#token'] = FALSE;
  1466. $form['#attributes']['class'] = 'api-search-form';
  1467. $form['#branch'] = $branch;
  1468. $form['search'] = array(
  1469. '#title' => t('Function, file, or topic'),
  1470. '#type' => 'textfield',
  1471. '#default_value' => '',
  1472. '#required' => TRUE,
  1473. '#attributes' => array('class' => 'api-search-keywords'),
  1474. );
  1475. $form['submit'] = array(
  1476. '#type' => 'submit',
  1477. '#value' => t('Search'),
  1478. );
  1479. return $form;
  1480. }
  1481. /**
  1482. * Form submission handler for api_search_form().
  1483. */
  1484. function api_search_form_submit($form, &$form_state) {
  1485. $form_state['redirect'] = 'api/search/'. $form['#branch']->branch_name .'/'. $form_state['values']['search'];
  1486. }
  1487. /**
  1488. * Prepares a listing of documentation objects for a branch.
  1489. *
  1490. * @param $branch_name
  1491. * Name of the branch to list.
  1492. * @param $page
  1493. * TRUE if this will be embedded in a page, and FALSE if it is an AHAH
  1494. * callback.
  1495. *
  1496. * @return
  1497. * JavaScript listing of all the objects in the branch.
  1498. */
  1499. function api_autocomplete($branch_name, $page = TRUE) {
  1500. $result = db_query("SELECT ad.title, ad.object_type FROM {api_documentation} ad INNER JOIN {api_branch} b ON ad.branch_id = b.branch_id WHERE b.branch_name = '%s' AND ad.object_type <> 'mainpage' ORDER BY LENGTH(ad.title), ad.title", $branch_name);
  1501. $objects = array();
  1502. while ($object = db_fetch_object($result)) {
  1503. $objects[] = $object->title;
  1504. }
  1505. $objects = array_values(array_unique($objects));
  1506. if ($page) {
  1507. drupal_json($objects);
  1508. }
  1509. else {
  1510. return drupal_to_js($objects);
  1511. }
  1512. }
  1513. /**
  1514. * Implements hook_cron().
  1515. */
  1516. function api_cron() {
  1517. include_once './'. drupal_get_path('module', 'api') .'/parser.inc';
  1518. api_update_all_branches();
  1519. }
  1520. /**
  1521. * Turns function names into links in code.
  1522. *
  1523. * @param $code
  1524. * PHP code to scan for function names.
  1525. * @param $branch
  1526. * Branch to make the links in.
  1527. * @param $class_did
  1528. * Documentation ID of the class the code is in (if any).
  1529. *
  1530. * @return
  1531. * Code with function names formatted as links.
  1532. */
  1533. function api_link_code($code, $branch, $class_did = NULL) {
  1534. return _api_link_documentation($code, $branch, $class_did, array('code hook name', 'code alter hook name', 'code theme hook name', 'code function', 'code member', 'code string'));
  1535. }
  1536. /**
  1537. * Turns function names into links in documentation.
  1538. *
  1539. * @param $documentation
  1540. * Documentation to scan for function names.
  1541. * @param $branch
  1542. * Branch to make the links in.
  1543. * @param $class_did
  1544. * Documentation ID of the class the documentation is in (if any).
  1545. * @param $aggressive_classes
  1546. * Try linking every word with a capital letter to a class or interface, if
  1547. * TRUE.
  1548. *
  1549. * @return
  1550. * Documentation with function names formatted as links.
  1551. */
  1552. function api_link_documentation($documentation, $branch, $class_did = NULL, $aggressive_classes = FALSE) {
  1553. return _filter_url(api_filter_documentation($documentation, $branch, $class_did, $aggressive_classes), NULL);
  1554. }
  1555. /**
  1556. * Turns function names into links for a text filter.
  1557. *
  1558. * This is the process callback for the API filter supplied by api_filter().
  1559. * It turns function names into links on output, using the currently active
  1560. * branch.
  1561. *
  1562. * @param $text
  1563. * Text to filter.
  1564. * @param $branch
  1565. * Branch object to use for links.
  1566. * @param $class_did
  1567. * Documentation ID of the class the documentation is in (if any).
  1568. * @param $aggressive_classes
  1569. * Try linking every word with a capital letter to a class or interface, if
  1570. * TRUE.
  1571. *
  1572. * @return
  1573. * Text with function names turned into links.
  1574. */
  1575. function api_filter_documentation($text, $branch, $class_did = NULL, $aggressive_classes = FALSE) {
  1576. // Remove escaping from \@.
  1577. $stages = array('tags', 'link', 'function', 'file', 'constant');
  1578. if ($aggressive_classes) {
  1579. $stages[] = 'class';
  1580. }
  1581. return preg_replace('!\\\@!', '@', _api_link_documentation($text, $branch, $class_did, $stages));
  1582. }
  1583. /**
  1584. * Recursive internal callback for turning function names into links in code.
  1585. *
  1586. * @param $documentation
  1587. * PHP code to scan for function names.
  1588. * @param $branch
  1589. * Branch to make the links in.
  1590. * @param $class_did
  1591. * Documentation ID of the class the documentation is in (if any).
  1592. * @stages
  1593. * Array of stages to process.
  1594. *
  1595. * @return
  1596. * Code with function names formatted as links.
  1597. *
  1598. * @see api_link_code()
  1599. * @see api_link_name()
  1600. */
  1601. function _api_link_documentation($documentation, $branch, $class_did = NULL, $stages = array()) {
  1602. $stage = array_shift($stages);
  1603. $callback_match = 'api_link_name';
  1604. $prepend = '';
  1605. $append = '';
  1606. $prepend_if_not_found = NULL;
  1607. $use_php = TRUE;
  1608. $type = '';
  1609. $pattern = '';
  1610. switch ($stage) {
  1611. case 'tags':
  1612. // Find HTML tags, not filtered.
  1613. $callback_match = NULL;
  1614. $pattern = '/(<[^>]+?'.'>)/';
  1615. break;
  1616. case 'link':
  1617. // Find @link.
  1618. $pattern = '/' . API_RE_TAG_START . 'link\s+(.*)\s+' . API_RE_TAG_START . 'endlink/U';
  1619. $callback_match = 'api_link_link';
  1620. break;
  1621. case 'function':
  1622. // Find function names, which are preceded by white space and followed by
  1623. // '('.
  1624. $append = '(';
  1625. $pattern = '!(?<=^|\s)(' . API_RE_PHP_FUNCTION_IN_TEXT . ')\(!';
  1626. $type = 'function';
  1627. break;
  1628. case 'code function':
  1629. // Find function names in marked-up code.
  1630. $pattern = '!<span class="php-function-or-constant">(' . API_RE_PHP_FUNCTION . ')</span>!';
  1631. $prepend = '<span class="php-function-or-constant">';
  1632. $append = '</span>';
  1633. break;
  1634. case 'code string':
  1635. // Find potential function names (callback strings) in marked-up code.
  1636. // These are all strings that are legal function names, where the function
  1637. // name is put into something like a hook_menu() page callback as a
  1638. // string.
  1639. $pattern = '!<span class="php-string">\'(' . API_RE_PHP_FUNCTION . ')\'</span>!';
  1640. $prepend = '<span class="php-function-or-constant">\'';
  1641. $append = '\'</span>';
  1642. $prepend_if_not_found = '<span class="php-string">\'';
  1643. $use_php = FALSE;
  1644. $type = 'function';
  1645. break;
  1646. case 'code hook name':
  1647. // Find potential hook names in marked-up code. These are strings that
  1648. // are legal function names, which were found in parsing to be inside
  1649. // module_implements() and related functions.
  1650. $pattern = '!<span class="php-string potential-hook">\'(' . API_RE_HOOK_NAME . ')\'</span>!';
  1651. $prepend = '<span class="php-function-or-constant">\'';
  1652. $append = '\'</span>';
  1653. $prepend_if_not_found = '<span class="php-string">\'';
  1654. $use_php = FALSE;
  1655. $type = 'hook';
  1656. break;
  1657. case 'code alter hook name':
  1658. // Works like 'code hook name' above, but for alter hooks.
  1659. $pattern = '!<span class="php-string potential-alter">\'(' . API_RE_HOOK_NAME . ')\'</span>!';
  1660. $prepend = '<span class="php-function-or-constant">\'';
  1661. $append = '\'</span>';
  1662. $prepend_if_not_found = '<span class="php-string">\'';
  1663. $use_php = FALSE;
  1664. $type = 'alter hook';
  1665. break;
  1666. case 'code theme hook name':
  1667. // Works like 'code hook name' above, but for theme hooks.
  1668. $pattern = '!<span class="php-string potential-theme">\'(' . API_RE_PHP_FUNCTION . ')\'</span>!';
  1669. $prepend = '<span class="php-function-or-constant">\'';
  1670. $append = '\'</span>';
  1671. $prepend_if_not_found = '<span class="php-string">\'';
  1672. $use_php = FALSE;
  1673. $type = 'theme';
  1674. break;
  1675. case 'code member':
  1676. // Works like 'code hook name' above, but for class members.
  1677. $callback_match = 'api_link_member_name';
  1678. $pattern = '!(<span class="php-function-or-constant [^"]+ member-of-[^"]+">' . API_RE_PHP_FUNCTION . '</span>)!';
  1679. break;
  1680. case 'file':
  1681. // Find file names, which are an arbitrary number of strings joined with
  1682. // '.'
  1683. $pattern = '%(?<=^|\s|\()'. API_RE_FILENAME .'(?=$|\s|[.,:;?!\)])%';
  1684. $type = 'file';
  1685. break;
  1686. case 'constant':
  1687. // Find constants, UPPERCASE_LETTERS_WITH_UNDERSCORES.
  1688. $pattern = '/\b([A-Z_]+)\b/';
  1689. $type = 'constant';
  1690. break;
  1691. case 'class':
  1692. // Find class names, which have a capital letter.
  1693. $pattern = '/\b(' . API_RE_CLASS_NAME_TEXT . ')\b/';
  1694. $type = 'class';
  1695. break;
  1696. }
  1697. if (count($stages) > 0) {
  1698. $callback = '_api_link_documentation';
  1699. }
  1700. else {
  1701. $callback = NULL;
  1702. }
  1703. return api_split($pattern, $documentation, $callback_match, array($branch, $prepend, $append, $class_did, NULL, NULL, $use_php, $prepend_if_not_found, NULL, $type), $callback, array($branch, $class_did, $stages));
  1704. }
  1705. /**
  1706. * Splits a string using a regular expression and processes using callbacks.
  1707. *
  1708. * @param $pattern
  1709. * The regular expression to match for splitting.
  1710. * @param $subject
  1711. * The string to process.
  1712. * @param $callback_match
  1713. * Function name to be called for text which matches $pattern. The first
  1714. * argument will be the parenthesized expression in the pattern. Should
  1715. * return a string. NULL to pass the text through unchanged.
  1716. * @param $callback_match_arguments
  1717. * An array of additional parameters for $callback_match.
  1718. * @param $callback
  1719. * Function name to be called for text which does not match $pattern. The
  1720. * first argument will be the text. Should return a string. NULL to pass the
  1721. * text through unchanged.
  1722. * @param $callback_arguments
  1723. * An array of additional parameters for $callback.
  1724. *
  1725. * @return
  1726. * The original string, with both matched and unmatched portions filtered by
  1727. * the appropriate callbacks.
  1728. */
  1729. function api_split($pattern, $subject, $callback_match = NULL, $callback_match_arguments = array(), $callback = NULL, $callback_arguments = array()) {
  1730. $return = '';
  1731. $matched = FALSE;
  1732. foreach (preg_split($pattern . 'sm', $subject, -1, PREG_SPLIT_DELIM_CAPTURE) as $part) {
  1733. if ($matched) {
  1734. if (is_null($callback_match)) {
  1735. $return .= $part;
  1736. }
  1737. else {
  1738. $return .= call_user_func_array($callback_match, array_merge(array($part), $callback_match_arguments));
  1739. }
  1740. }
  1741. else {
  1742. if (is_null($callback)) {
  1743. $return .= $part;
  1744. }
  1745. else {
  1746. $return .= call_user_func_array($callback, array_merge(array($part), $callback_arguments));
  1747. }
  1748. }
  1749. $matched = !$matched;
  1750. }
  1751. return $return;
  1752. }
  1753. /**
  1754. * Links an object name to its documentation.
  1755. *
  1756. * @param $name
  1757. * Object name to link to.
  1758. * @param $branch
  1759. * Branch object indicating which branch to make the link in.
  1760. * @param $prepend
  1761. * Text to prepend on the link.
  1762. * @param $append
  1763. * Text to append on the link.
  1764. * @param $class_did
  1765. * (unused) Documentation ID of the class this is part of (if any).
  1766. * @param $text
  1767. * Link text. If omitted, uses $name.
  1768. * @param $is_link
  1769. * TRUE if this was inside a @link.
  1770. * @param $use_php
  1771. * TRUE if links to PHP functions should be made; FALSE if only Drupal
  1772. * objects.
  1773. * @param $prepend_if_not_found
  1774. * Text to prepend if object is not found (defaults to $prepend).
  1775. * @param $append_if_not_found
  1776. * Text to append if object is not found (defaults to $append).
  1777. * @param $type
  1778. * The type of information $name represents. Possible values:
  1779. * - '': (default) $name is a normal object name.
  1780. * - 'hook': $name is a hook name.
  1781. * - 'alter hook': $name is an alter hook name.
  1782. * - 'theme': $name is a theme hook name.
  1783. * - 'function': $name is specifically a function ('file', 'constant', etc.
  1784. * also are supported).
  1785. *
  1786. * @return
  1787. * The text as a link to the object page.
  1788. */
  1789. function api_link_name($name, $branch, $prepend = '', $append = '', $class_did = NULL, $text = NULL, $is_link = FALSE, $use_php = TRUE, $prepend_if_not_found = NULL, $append_if_not_found = NULL, $type = '') {
  1790. if (is_null($text)) {
  1791. $text = $name;
  1792. }
  1793. $query = "SELECT ad.did, ad.branch_id, ad.object_name, ad.title, ad.object_type, ad.summary, ad.file_name, ad.object_name as match_name FROM {api_documentation} ad WHERE ad.branch_id = %d AND ad.object_name = '%s'";
  1794. // See if this is a link to a group/topic.
  1795. if ($is_link) {
  1796. $result = db_query($query . " AND ad.object_type = 'group'", $branch->branch_id, $name);
  1797. while ($object = db_fetch_object($result)) {
  1798. // MySQL is not case-sensitive, so check the match for exact string.
  1799. if ($object->match_name == $name) {
  1800. // Note: Use html = TRUE here because the @link stuff was check_plained
  1801. // during parsing. If we run it through check_plain() again, we get
  1802. // double encoding of entities.
  1803. return $prepend . l($text, api_url($object), array('attributes' => array('title' => api_entity_decode($object->summary), 'class' => 'local'), 'html' => TRUE)) . $append;
  1804. }
  1805. }
  1806. // See if it could be a file name being linked.
  1807. $tmpquery = "SELECT ad.did, ad.branch_id, ad.object_name, ad.title, ad.object_type, ad.summary, ad.file_name, af.basename as match_name FROM {api_documentation} ad INNER JOIN {api_file} af ON ad.did = af.did WHERE ad.branch_id = %d AND af.basename = '%s' AND ad.object_type = 'file'";
  1808. $result = db_query($tmpquery, $branch->branch_id, $name);
  1809. while ($object = db_fetch_object($result)) {
  1810. if ($object->match_name == $name) {
  1811. return $prepend . l($text, api_url($object), array('attributes' => array('title' => api_entity_decode($object->summary), 'class' => 'local'), 'html' => TRUE)) . $append;
  1812. }
  1813. }
  1814. }
  1815. // Figure out what potential names we should match on.
  1816. $potential_names = array($name);
  1817. if ($type == 'hook') {
  1818. $potential_names = array(
  1819. 'hook_' . $name,
  1820. 'hook_field_' . $name,
  1821. 'field_default_' . $name,
  1822. 'hook_user_' . $name,
  1823. );
  1824. $query .= " AND ad.object_type = 'function'";
  1825. }
  1826. elseif ($type == 'alter hook') {
  1827. $potential_names = array('hook_' . $name . '_alter');
  1828. $query .= " AND ad.object_type = 'function'";
  1829. }
  1830. elseif ($type == 'theme') {
  1831. $potential_names = array();
  1832. // Potential matches are the whole theme call, or with stripped off pieces
  1833. // separated by __. And we look for template files preferably over
  1834. // functions.
  1835. $hook_elements = explode('__', $name);
  1836. while (count($hook_elements) > 0) {
  1837. $hook = implode('__', $hook_elements);
  1838. $potential_names[] = str_replace('_', '-', $hook) . '.tpl.php';
  1839. $potential_names[] = 'theme_' . $hook;
  1840. array_pop($hook_elements);
  1841. }
  1842. // Because this needs to match theme files, change the query to match on
  1843. // object title (which is the file base name).
  1844. $query = "SELECT ad.did, ad.branch_id, ad.object_name, ad.title, ad.object_type, ad.summary, ad.file_name, ad.title as match_name FROM {api_documentation} ad WHERE ad.branch_id = %d AND ad.title = '%s' AND (ad.object_type = 'file' OR ad.object_type = 'function')";
  1845. }
  1846. elseif ($type == 'function') {
  1847. $query .= " AND ad.object_type = 'function'";
  1848. }
  1849. elseif ($type == 'file') {
  1850. // Because this needs to match files, change the query to match on
  1851. // basename field in {api_files}.
  1852. $query = "SELECT ad.did, ad.branch_id, ad.object_name, ad.title, ad.object_type, ad.summary, ad.file_name, af.basename as match_name FROM {api_documentation} ad INNER JOIN {api_file} af ON ad.did = af.did WHERE ad.branch_id = %d AND af.basename = '%s' AND ad.object_type = 'file'";
  1853. }
  1854. elseif ($type == 'constant') {
  1855. $query .= " AND ad.object_type = 'constant'";
  1856. }
  1857. elseif ($type == 'class') {
  1858. $query .= " AND (ad.object_type = 'class' OR ad.object_type = 'interface')";
  1859. }
  1860. foreach ($potential_names as $possible_name) {
  1861. $url = NULL;
  1862. $options = array();
  1863. // See if there are any matches for this name.
  1864. $result = db_query($query, $branch->branch_id, $possible_name);
  1865. while ($object = db_fetch_object($result)) {
  1866. // MySQL is not case-sensitive, so check the match for exact string.
  1867. if ($object->match_name != $possible_name) {
  1868. continue;
  1869. }
  1870. if (isset($url)) {
  1871. // This is the second match, so make this a search.
  1872. $url = 'api/search/' . $branch->branch_name . '/' . $possible_name;
  1873. $options = array(
  1874. 'attributes' => array(
  1875. 'title' => t('Multiple implementations exist.'),
  1876. 'class' => 'local',
  1877. ),
  1878. );
  1879. break;
  1880. }
  1881. else {
  1882. $url = api_url($object);
  1883. $options = array(
  1884. 'attributes' => array(
  1885. 'title' => api_entity_decode($object->summary),
  1886. 'class' => 'local',
  1887. ),
  1888. );
  1889. }
  1890. }
  1891. // See if we found a match.
  1892. if (isset($url)) {
  1893. return $prepend . l($text, $url, $options) . $append;
  1894. }
  1895. }
  1896. // If we get here, there wasn't a match. Try PHP functions.
  1897. if ($use_php) {
  1898. $result = db_query("SELECT d.object_name, d.summary, b.data FROM {api_documentation} d INNER JOIN {api_branch} b ON b.branch_id = d.branch_id AND b.type = 'php' WHERE d.object_type = 'function' AND d.object_name = '%s'", $name);
  1899. while ($info = db_fetch_object($result)) {
  1900. // MySQL is not case-sensitive, so check the match for exact string.
  1901. if ($info->object_name != $name) {
  1902. continue;
  1903. }
  1904. $data = unserialize($info->data);
  1905. $link = strtr($data['path'], array('!function' => $name));
  1906. return $prepend . l($text, $link, array('attributes' => array('title' => api_entity_decode($info->summary), 'class' => 'php-manual'))) . $append;
  1907. }
  1908. }
  1909. // If we get here, there still wasn't a match, so return non-linked text.
  1910. if (isset($prepend_if_not_found)) {
  1911. $prepend = $prepend_if_not_found;
  1912. }
  1913. if (isset($append_if_not_found)) {
  1914. $append = $append_if_not_found;
  1915. }
  1916. return $prepend . $text . $append;
  1917. }
  1918. /**
  1919. * Links text to an appropriate class member variable, constant, or function.
  1920. *
  1921. * @param $text
  1922. * Text matched by the regular expression.
  1923. * @param $branch
  1924. * Branch object indicating which branch to make the link in.
  1925. * @param $prepend
  1926. * Unused.
  1927. * @param $append
  1928. * Unused.
  1929. * @param $class_did
  1930. * Documentation ID of the class this is part of (if any).
  1931. */
  1932. function api_link_member_name($text, $branch, $prepend = '', $append = '', $class_did = NULL) {
  1933. // The pattern matched to get here contains the entire span with all of its
  1934. // classes. Parse it out.
  1935. $matches = array();
  1936. preg_match('!<span class="php-function-or-constant ([^"]+) member-of-([^"]+)">(' . API_RE_PHP_FUNCTION . ')</span>!', $text, $matches);
  1937. $name = $matches[3];
  1938. $member_type = $matches[2];
  1939. $object_type = $matches[1];
  1940. if ($object_type == 'function') {
  1941. $object_type_where = " AND ad.object_type = 'function'";
  1942. }
  1943. else {
  1944. $object_type_where = " AND ad.object_type <> 'function'";
  1945. }
  1946. $prepend = '<span class="php-function-or-constant">';
  1947. $append = '</span>';
  1948. // Figure out what class we're looking for a member of, and build a query.
  1949. // $member_type is one of: parent, self, variable, or class-NAME.
  1950. $result = NULL;
  1951. if ($member_type == 'parent') {
  1952. // We're looking for a member of the parent class. Find the class doc ID.
  1953. if ($item = db_fetch_object(db_query("SELECT ad.did FROM {api_reference_storage} ars INNER JOIN {api_documentation} ad ON ad.did = ars.to_did WHERE ars.from_did = %d AND ars.object_type = 'class'", $class_did))) {
  1954. $class_did = $item->did;
  1955. $member_type = 'self';
  1956. }
  1957. else {
  1958. // There is no parent class recorded (probably a built-in PHP class).
  1959. $member_type = 'none';
  1960. }
  1961. }
  1962. elseif (strpos($member_type, 'class-') === 0) {
  1963. // We're looking for a specific class. Find the class doc ID.
  1964. $class_name = substr($member_type, 6);
  1965. if ($item = db_fetch_object(db_query("SELECT ad.did FROM {api_documentation} ad WHERE ad.object_type = 'class' AND ad.branch_id = %d AND ad.object_name = '%s'", $branch->branch_id, $class_name))) {
  1966. $class_did = $item->did;
  1967. $member_type = 'self';
  1968. }
  1969. else {
  1970. // Class name doesn't exist (probably a built-in PHP class).
  1971. $member_type = 'none';
  1972. }
  1973. }
  1974. if ($member_type == 'self') {
  1975. // Looking for a member of a particular class. Note that we use the
  1976. // {api_members} table here, since it includes members inherited from
  1977. // parent classes.
  1978. $result = db_query("SELECT ad.branch_id, ad.title, ad.object_name, ad.summary, ad.object_type, ad.file_name, ad.did, ad.member_name FROM {api_members} am INNER JOIN {api_documentation} ad ON am.did = ad.did WHERE am.class_did = %d AND ad.member_name = '%s'" . $object_type_where, $class_did, $name);
  1979. }
  1980. elseif ($member_type == 'variable') {
  1981. // This was some kind of a variable like $foo->member(). So match any member
  1982. // of any class.
  1983. $result = db_query("SELECT ad.branch_id, ad.title, ad.object_name, ad.summary, ad.object_type, ad.file_name, ad.did, ad.member_name FROM {api_documentation} ad WHERE ad.member_name = '%s'" . $object_type_where, $name);
  1984. }
  1985. // See if we have one result, more than one result, or no results.
  1986. $url = NULL;
  1987. $options = array();
  1988. if (isset($result)) {
  1989. while ($object = db_fetch_object($result)) {
  1990. // MySQL is not case-sensitive, so check the match for exact string.
  1991. if ($object->member_name != $name) {
  1992. continue;
  1993. }
  1994. if (strlen($url)) {
  1995. // This is the second match, so make this a search.
  1996. $url = 'api/search/' . $branch->branch_name . '/' . $name;
  1997. $options = array(
  1998. 'attributes' => array(
  1999. 'title' => t('Multiple implementations exist.'),
  2000. 'class' => 'local',
  2001. ),
  2002. );
  2003. break;
  2004. }
  2005. else {
  2006. // This is the first match, make a link.
  2007. $url = api_url($object);
  2008. $options = array(
  2009. 'attributes' => array(
  2010. 'title' => api_entity_decode($object->summary),
  2011. 'class' => 'local',
  2012. ),
  2013. );
  2014. }
  2015. }
  2016. // See if we found a match.
  2017. if (isset($url)) {
  2018. return $prepend . l($name, $url, $options) . $append;
  2019. }
  2020. }
  2021. // If we got here, we didn't have a match.
  2022. return $prepend . $name . $append;
  2023. }
  2024. /**
  2025. * Decodes HTML entities.
  2026. *
  2027. * @param $text
  2028. * Text to decode.
  2029. *
  2030. * @return
  2031. * Text with all HTML entities decoded.
  2032. */
  2033. function api_entity_decode($text) {
  2034. $text = html_entity_decode($text);
  2035. // html_entity_decode does not decode numeric entities, and there are
  2036. // many cases of &#39; (quote) in here.
  2037. $text = str_replace('&#039;', "'", $text);
  2038. $text = str_replace('&#39;', "'", $text);
  2039. return $text;
  2040. }
  2041. /**
  2042. * Turns text into a link, using the first word as the object name.
  2043. *
  2044. * @param $name
  2045. * Text to link.
  2046. * @param $branch
  2047. * Branch object indicating which branch to make the link in.
  2048. * @param $prepend
  2049. * Text to prepend on the link.
  2050. * @param $append
  2051. * Text to append on the link.
  2052. * @param $class_did
  2053. * Documentation ID of the class the link is in (if any).
  2054. *
  2055. * @return
  2056. * The text as a link.
  2057. */
  2058. function api_link_link($name, $branch, $prepend = '', $append = '', $class_did = NULL) {
  2059. $words = preg_split('/\s+/', $name);
  2060. $name = array_shift($words);
  2061. return api_link_name($name, $branch, $prepend, $append, $class_did, implode(' ', $words), TRUE);
  2062. }
  2063. /**
  2064. * Flags a file, branch, or all branches, to be reparsed on the next cron run.
  2065. *
  2066. * @param $branch_or_file
  2067. * (optional) Identifier or ID number of the branch to reparse, or name of a
  2068. * single file to reparse. If omitted all branches will be reparsed. File
  2069. * names must include the path relative to the common path to the directories
  2070. * indexed by this branch.
  2071. * @param $is_branch_name
  2072. * If passing in a branch name, set to TRUE so that if it happens to be an
  2073. * integer, it won't be considered to be a branch ID number by mistake.
  2074. * Otherwise, just leave this as the default FALSE.
  2075. *
  2076. * @return
  2077. * Number of files marked for reparsing.
  2078. */
  2079. function api_mark_for_reparse($branch_or_file = NULL, $is_branch_name = FALSE) {
  2080. $time_in_past = 52;
  2081. if (empty($branch_or_file)) {
  2082. // Reparse all.
  2083. db_query("UPDATE {api_file} SET modified = %d", $time_in_past);
  2084. }
  2085. else {
  2086. if (!$is_branch_name && is_numeric($branch_or_file) && ($branch_or_file == intval($branch_or_file))) {
  2087. $branch_id = $branch_or_file;
  2088. }
  2089. else {
  2090. $branch_id = db_result(db_query("SELECT branch_id FROM {api_branch} WHERE branch_name = '%s'", $branch_or_file));
  2091. }
  2092. if (!empty($branch_id)) {
  2093. // Reparse a branch.
  2094. db_query("UPDATE {api_file} f INNER JOIN {api_documentation} d ON d.object_type = 'file' AND d.did = f.did SET f.modified = %d WHERE d.branch_id = %d", $time_in_past, $branch_id);
  2095. }
  2096. else {
  2097. // Reparse a file.
  2098. db_query("UPDATE {api_file} f INNER JOIN {api_documentation} d ON d.object_type = 'file' AND d.did = f.did SET f.modified = %d WHERE d.file_name = '%s'", $time_in_past, $branch_or_file);
  2099. }
  2100. }
  2101. return db_affected_rows();
  2102. }
  2103. /**
  2104. * Parses a queued file.
  2105. *
  2106. * @data
  2107. * Array of information about the file to be parsed.
  2108. */
  2109. function api_queue_parse_file($data) {
  2110. module_load_include('inc', 'api', 'parser');
  2111. api_parse_file($data['parser'], $data['path'], $data['branch'], $data['file']);
  2112. watchdog('api', 'API parse %branch %file', array('%branch' => $data['branch_name'], '%file' => $data['file']));
  2113. }
  2114. /**
  2115. * Deletes an obsolete JavaScript autocomplete index file.
  2116. *
  2117. * @param $data
  2118. * Array of infomration about the file to be deleted.
  2119. */
  2120. function api_queue_file_delete($data) {
  2121. file_delete($data['path']);
  2122. watchdog('api', 'Remove expired API JSON, %path.', array('%path' => $data['path']));
  2123. }
  2124. /**
  2125. * Resets the parse queue.
  2126. *
  2127. * Empties all parse jobs from the queue, and sets all files to "unqueued"
  2128. * status.
  2129. */
  2130. function api_reset_parse_queue() {
  2131. drupal_queue_include();
  2132. $queue = DrupalQueue::get('api_parse');
  2133. $queue->deleteQueue();
  2134. db_query('UPDATE {api_file} SET queued=0');
  2135. }