/[drupal]/contributions/modules/api/api.module
ViewVC logotype

Contents of /contributions/modules/api/api.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.88 - (show annotations) (download) (as text)
Mon Jan 26 20:23:44 2009 UTC (10 months ago) by drumm
Branch: MAIN
CVS Tags: DRUPAL-6--1-0, HEAD
Branch point for: DRUPAL-6--1
Changes since 1.87: +27 -21 lines
File MIME type: text/x-php
Do not show empty references
1 <?php
2 // $Id: api.module,v 1.87 2009/01/26 20:13:16 drumm Exp $
3
4 /**
5 * @file
6 * Generates and displays API documentation pages.
7 *
8 * This is an implementation of a subset of the Doxygen documentation generator
9 * specification, tuned to produce output that best benefits the Drupal code base.
10 * It is designed to assume the code it documents follows Drupal coding conventions,
11 * and supports the following Doxygen constructs:
12 * @ mainpage
13 * @ file
14 * @ defgroup
15 * @ ingroup
16 * @ addtogroup (as a synonym of @ ingroup)
17 * @ param
18 * @ return
19 * @ link
20 */
21
22 /**
23 * Regular expression for matching file names.
24 */
25 define('API_RE_FILENAME', '([a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+)');
26
27 /**
28 * Regular expression for starting inline @tags.
29 */
30 define('API_RE_TAG_START', '(?<!\\\)@');
31
32 /**
33 * The maximum number of path elements to look for in file names.
34 */
35 define('API_MAX_FILE_ELEMENTS', 5);
36
37 /**
38 * Implementation of hook_help()
39 */
40 function api_help($path, $arg) {
41 switch ($path) {
42 case 'admin/help#api':
43 return t('
44 <p>
45 This is an implementation of a subset of the Doxygen documentation generator specification, tuned to produce output that best benefits the Drupal code base.
46 </p>
47
48 <p>
49 It is designed to assume the code it documents follows Drupal coding conventions,
50 and supports the following Doxygen constructs:
51 <ul>
52 <li>@mainpage
53 <li>@file
54 <li>@defgroup
55 <li>@ingroup
56 <li>@addtogroup (as a synonym of @ ingroup)
57 <li>@param
58 <li>@return
59 <li>@link
60 <li>@see
61 <li>@{
62 <li>@}
63 </ul>
64 </p>
65
66 <p>
67 The module was designed to produce the Drupal developer documentation available at !api_site.
68 </p>
69
70 <p>
71 <strong>Set up</strong>
72 <p>
73
74 <p>
75 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.
76 </p>
77
78 <p>
79 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.
80 </p>
81
82 <p>
83 The module indexes code branches during cron runs, so make sure the site has cron functionality set up properly.
84 </p>
85 ', array('!api_site' => l('http://api.drupal.org', 'http://api.drupal.org', array('absolute' => TRUE)), '!api_settings_page' => l(t('API settings page'), 'admin/settings/api')));
86
87 case 'admin/settings/api/refresh':
88 return t('Parse all indexed code files again, even if they have not been modified.');
89 }
90 }
91
92 function api_get_branches($_reset = FALSE) {
93 static $branches;
94
95 if (!isset($branches) || $_reset) {
96 $result = db_query('SELECT branch_name, title, directory FROM {api_branch} ORDER BY weight');
97 $branches = array();
98 while ($branch = db_fetch_object($result)) {
99 $branches[$branch->branch_name] = $branch;
100 }
101 }
102
103 return $branches;
104 }
105
106 /**
107 * Implementation of hook_menu().
108 */
109 function api_menu() {
110 $items = array();
111
112 $access_callback = 'user_access';
113 $access_arguments = array('access API reference');
114
115 $branches = api_get_branches();
116 $default_branch = variable_get('api_default_branch', NULL);
117
118 // Part 1: No object, Default branch
119 $items['api'] = array(
120 'title' => 'API reference',
121 'access callback' => $access_callback,
122 'access arguments' => $access_arguments,
123 'page callback' => 'api_page_branch',
124 'page arguments' => array($default_branch),
125 );
126 $items['api/functions'] = array(
127 'title' => 'Functions',
128 'page callback' => 'api_page_listing',
129 'access callback' => $access_callback,
130 'access arguments' => $access_arguments,
131 'page arguments' => array($default_branch, 'function'),
132 'type' => MENU_CALLBACK,
133 );
134 $items['api/constants'] = array(
135 'title' => 'Constants',
136 'page callback' => 'api_page_listing',
137 'access callback' => $access_callback,
138 'access arguments' => $access_arguments,
139 'page arguments' => array($default_branch, 'constant'),
140 'type' => MENU_CALLBACK,
141 );
142 $items['api/globals'] = array(
143 'title' => 'Globals',
144 'page callback' => 'api_page_listing',
145 'access callback' => $access_callback,
146 'access arguments' => $access_arguments,
147 'page arguments' => array($default_branch, 'global'),
148 'type' => MENU_CALLBACK,
149 );
150 $items['api/files'] = array(
151 'title' => 'Files',
152 'page callback' => 'api_page_listing',
153 'access callback' => $access_callback,
154 'access arguments' => $access_arguments,
155 'page arguments' => array($default_branch, 'file'),
156 'type' => MENU_CALLBACK,
157 );
158 $items['api/groups'] = array(
159 'title' => 'Topics',
160 'page callback' => 'api_page_listing',
161 'access callback' => $access_callback,
162 'access arguments' => $access_arguments,
163 'page arguments' => array($default_branch, 'group'),
164 'type' => MENU_CALLBACK,
165 );
166
167 $items['api/search'] = array(
168 'title' => 'API Search',
169 'page callback' => 'drupal_get_form',
170 'page arguments' => array('api_search_form', $default_branch),
171 'access callback' => $access_callback,
172 'access arguments' => $access_arguments,
173 'type' => MENU_CALLBACK,
174 );
175 $items['apis'] = array(
176 'title' => 'API search',
177 'page callback' => 'api_search_redirect',
178 'access callback' => $access_callback,
179 'access arguments' => $access_arguments,
180 'type' => MENU_CALLBACK,
181 );
182 $items['api/autocomplete'] = array(
183 'page callback' => 'api_autocomplete',
184 'access callback' => $access_callback,
185 'access arguments' => $access_arguments,
186 'type' => MENU_CALLBACK,
187 );
188 $items['admin/settings/api'] = array(
189 'title' => 'API reference',
190 'description' => 'Configure branches for documentation.',
191 'access callback' => 'user_access',
192 'access arguments' => array('administer API reference'),
193 'page callback' => 'drupal_get_form',
194 'page arguments' => array('api_page_admin_form'),
195 'file' => 'api.admin.inc',
196 );
197 $items['admin/settings/api/branches'] = array(
198 'title' => 'Branches',
199 'access callback' => 'user_access',
200 'access arguments' => array('administer API reference'),
201 'type' => MENU_DEFAULT_LOCAL_TASK,
202 );
203 $items['admin/settings/api/branches/list'] = array(
204 'title' => 'List',
205 'access callback' => 'user_access',
206 'access arguments' => array('administer API reference'),
207 'type' => MENU_DEFAULT_LOCAL_TASK
208 );
209 $items['admin/settings/api/branches/new'] = array(
210 'title' => 'New branch',
211 'access callback' => 'user_access',
212 'access arguments' => array('administer API reference'),
213 'page callback' => 'drupal_get_form',
214 'page arguments' => array('api_branch_edit_form'),
215 'file' => 'api.admin.inc',
216 'type' => MENU_LOCAL_TASK,
217 );
218 $items['admin/settings/api/branches/%'] = array(
219 'title' => 'Edit @branch',
220 'title arguments' => array('@branch' => 4),
221 'access callback' => 'user_access',
222 'access arguments' => array('administer API reference'),
223 'page callback' => 'drupal_get_form',
224 'page arguments' => array('api_branch_edit_form', 4),
225 'file' => 'api.admin.inc',
226 'type' => MENU_CALLBACK,
227 );
228 $items['admin/settings/api/branches/%/delete'] = array(
229 'access callback' => 'user_access',
230 'access arguments' => array('administer API reference'),
231 'page callback' => 'drupal_get_form',
232 'page arguments' => array('api_branch_delete_form', 4),
233 'file' => 'api.admin.inc',
234 'type' => MENU_CALLBACK,
235 );
236 $items['admin/settings/api/php'] = array(
237 'title' => 'PHP manual',
238 'access callback' => 'user_access',
239 'access arguments' => array('administer API reference'),
240 'page callback' => 'drupal_get_form',
241 'page arguments' => array('api_php_manual_index_form'),
242 'file' => 'api.admin.inc',
243 'type' => MENU_LOCAL_TASK,
244 );
245 $items['admin/settings/api/refresh'] = array(
246 'title' => 'Refresh index',
247 'access callback' => 'user_access',
248 'access arguments' => array('administer API reference'),
249 'page callback' => 'drupal_get_form',
250 'page arguments' => array('api_reindex_form'),
251 'file' => 'api.admin.inc',
252 'type' => MENU_LOCAL_TASK,
253 );
254
255 // Function dumps for IDEs and code editors.
256 $items['api/function_dump/%api_branch'] = array(
257 'page callback' => 'api_page_function_dump',
258 'callback arguments' => array(2),
259 'type' => MENU_CALLBACK,
260 );
261
262 // Part 2: Object provided, default branch.
263 $items['api/function/%api_object'] = array(
264 'title' => 'Function',
265 'load arguments' => array('function', $default_branch),
266 'page callback' => 'api_page_function',
267 'page arguments' => array(2),
268 'access callback' => $access_callback,
269 'access arguments' => $access_arguments,
270 'type' => MENU_CALLBACK,
271 );
272 $items['api/constant/%api_object'] = array(
273 'title' => 'Constant',
274 'load arguments' => array('constant', $default_branch),
275 'page callback' => 'api_page_constant',
276 'page arguments' => array(2),
277 'access callback' => $access_callback,
278 'access arguments' => $access_arguments,
279 'type' => MENU_CALLBACK,
280 );
281 $items['api/global/%api_object'] = array(
282 'title' => 'Global',
283 'load arguments' => array('global', $default_branch),
284 'page callback' => 'api_page_global',
285 'page arguments' => array(2),
286 'access callback' => $access_callback,
287 'access arguments' => $access_arguments,
288 'type' => MENU_CALLBACK,
289 );
290 // This is nonideal. We have a dynamic number of elments in the middle of the
291 // path when the menu system expects that sort of thing to be at the end.
292 for ($i = 0; $i < API_MAX_FILE_ELEMENTS; $i += 1) {
293 $elements = str_repeat('/%', $i);
294 $items['api/file/%api_filename'. $elements] = array(
295 'title' => 'File',
296 'load arguments' => array('%map', '%index', $default_branch),
297 'page callback' => 'api_page_file',
298 'page arguments' => array(2),
299 'access callback' => $access_callback,
300 'access arguments' => $access_arguments,
301 'type' => MENU_CALLBACK,
302 );
303 }
304 $items['api/group/%api_object'] = array(
305 'title' => 'Topic',
306 'load arguments' => array('group', $default_branch),
307 'page callback' => 'api_page_group',
308 'page arguments' => array(2),
309 'access callback' => $access_callback,
310 'access arguments' => $access_arguments,
311 'type' => MENU_CALLBACK,
312 );
313
314 foreach ($branches as $branch) {
315 // Part 3: No object, specific branch
316 $items['api/'. $branch->branch_name] = array(
317 'title' => $branch->title,
318 'page callback' => 'api_page_branch',
319 'page arguments' => array($branch->branch_name),
320 'access callback' => $access_callback,
321 'access arguments' => $access_arguments,
322 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
323 );
324 $items['api/functions/'. $branch->branch_name] = array(
325 'title' => $branch->title,
326 'page callback' => 'api_page_listing',
327 'page arguments' => array($branch->branch_name, 'function'),
328 'access callback' => $access_callback,
329 'access arguments' => $access_arguments,
330 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
331 );
332 $items['api/constants/'. $branch->branch_name] = array(
333 'title' => $branch->title,
334 'page callback' => 'api_page_listing',
335 'page arguments' => array($branch->branch_name, 'constant'),
336 'access callback' => $access_callback,
337 'access arguments' => $access_arguments,
338 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
339 );
340 $items['api/globals/'. $branch->branch_name] = array(
341 'title' => $branch->title,
342 'page callback' => 'api_page_listing',
343 'page arguments' => array($branch->branch_name, 'global'),
344 'access callback' => $access_callback,
345 'access arguments' => $access_arguments,
346 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
347 );
348 $items['api/files/'. $branch->branch_name] = array(
349 'title' => $branch->title,
350 'page callback' => 'api_page_listing',
351 'page arguments' => array($branch->branch_name, 'file'),
352 'access callback' => $access_callback,
353 'access arguments' => $access_arguments,
354 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
355 );
356 $items['api/groups/'. $branch->branch_name] = array(
357 'title' => $branch->title,
358 'page callback' => 'api_page_listing',
359 'page arguments' => array($branch->branch_name, 'group'),
360 'access callback' => $access_callback,
361 'access arguments' => $access_arguments,
362 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
363 );
364
365 // Part 4: Object provided, specific branch.
366 $items['api/function/%api_object/'. $branch->branch_name] = array(
367 'title' => $branch->title,
368 'load arguments' => array('function', $branch->branch_name),
369 'page callback' => 'api_page_function',
370 'page arguments' => array(2),
371 'access callback' => $access_callback,
372 'access arguments' => $access_arguments,
373 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
374 );
375
376 $items['api/constant/%api_object/'. $branch->branch_name] = array(
377 'title' => $branch->title,
378 'load arguments' => array('constant', $branch->branch_name),
379 'page callback' => 'api_page_constant',
380 'page arguments' => array(2),
381 'access callback' => $access_callback,
382 'access arguments' => $access_arguments,
383 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
384 );
385 $items['api/global/%api_object/'. $branch->branch_name] = array(
386 'title' => $branch->title,
387 'load arguments' => array('global', $branch->branch_name),
388 'page callback' => 'api_page_global',
389 'page arguments' => array(2),
390 'access callback' => $access_callback,
391 'access arguments' => $access_arguments,
392 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
393 );
394 // This is nonideal. We have a dynamic number of elments in the middle of
395 // the path when the menu system expects that sort of thing to be at the
396 // end.
397 for ($i = 0; $i < API_MAX_FILE_ELEMENTS; $i += 1) {
398 $elements = str_repeat('/%', $i);
399 $items['api/file/%api_filename'. $elements .'/'. $branch->branch_name] = array(
400 'title' => $branch->title,
401 'load arguments' => array('%map', '%index', $branch->branch_name),
402 'page callback' => 'api_page_file',
403 'page arguments' => array(2),
404 'access callback' => $access_callback,
405 'access arguments' => $access_arguments,
406 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
407 );
408 $items['api/file/%api_filename'. $elements .'/'. $branch->branch_name .'/documentation'] = array(
409 'title' => 'View documentation',
410 'load arguments' => array('%map', '%index', $branch->branch_name),
411 'type' => MENU_DEFAULT_LOCAL_TASK,
412 'weight' => -10,
413 );
414 $items['api/file/%api_filename'. $elements .'/'. $branch->branch_name .'/source'] = array(
415 'title' => 'View source',
416 'load arguments' => array('%map', '%index', $branch->branch_name),
417 'page callback' => 'api_page_file_source',
418 'page arguments' => array(2),
419 'access callback' => $access_callback,
420 'access arguments' => $access_arguments,
421 'type' => MENU_LOCAL_TASK,
422 );
423 }
424 $items['api/group/%api_object/'. $branch->branch_name] = array(
425 'title' => $branch->title,
426 'load arguments' => array('group', $branch->branch_name),
427 'page callback' => 'api_page_group',
428 'page arguments' => array(2),
429 'access callback' => $access_callback,
430 'access arguments' => $access_arguments,
431 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
432 );
433 $items['api/search/'. $branch->branch_name .'/%menu_tail'] = array(
434 'title' => $branch->title,
435 'page callback' => 'api_search_listing',
436 'page arguments' => array($branch->branch_name, 3),
437 'access callback' => $access_callback,
438 'access arguments' => $access_arguments,
439 'type' => MENU_LOCAL_TASK,
440 );
441 }
442 return $items;
443 }
444
445 function api_filename_load($first, $map, $index, $branch_name = NULL) {
446 $branches = api_get_branches();
447
448 $end = count($map) - 1;
449 $end -= in_array($map[$end], array('source', 'documentation'));
450 $end -= isset($branches[$map[$end]]);
451 $filename = implode('/', array_slice($map, $index, $end - $index + 1));
452
453 return api_object_load($filename, 'file', $branch_name);
454 }
455
456 /**
457 * Load an object from its name, type and branch.
458 */
459 function api_object_load($object_name, $object_type, $branch_name = NULL) {
460 static $cache;
461
462 if (is_null($branch_name)) {
463 $branch_name = variable_get('api_default_branch', NULL);
464 }
465
466 $key = $object_name .':'. $object_type .':'. $branch_name;
467 if (!isset($cache[$key])) {
468 // Prepare the query
469 $tables = array('{api_documentation} ad');
470 $fields = array('ad.*');
471 $where = "WHERE ad.object_name = '%s' AND ad.object_type = '%s' AND ad.branch_name = '%s'";
472 $arguments = array($object_name, $object_type, $branch_name);
473
474 if ($object_type == 'function') {
475 $tables[] = 'LEFT JOIN {api_function} af ON (af.did = ad.did)';
476 $fields[] = 'af.*';
477 }
478 else if ($object_type == 'file') {
479 $tables[] = 'LEFT JOIN {api_file} af ON (af.did = ad.did)';
480 $fields[] = 'af.*';
481 }
482 // Now build it
483 $cache[$key] = db_fetch_object(db_query('SELECT '. implode(', ', $fields) .' FROM '. implode(' ', $tables) .' '. $where, $arguments));
484 }
485
486 return $cache[$key];
487 }
488
489 /**
490 * Implementation of hook_perm().
491 */
492 function api_perm() {
493 return array('access API reference', 'administer API reference');
494 }
495
496 function api_theme() {
497 return array(
498 'api_branch_table' => array(
499 'arguments' => array('element' => NULL),
500 ),
501 'api_expandable' => array(
502 'arguments' => array(
503 'prompt' => NULL,
504 'content' => NULL,
505 'class' => NULL,
506 ),
507 'template' => 'templates/api-expandable',
508 ),
509 'api_related_topics' => array(
510 'arguments' => array(
511 'topics' => array(),
512 ),
513 'template' => 'templates/api-related-topics',
514 ),
515 'api_functions' => array(
516 'arguments' => array(
517 'functions' => array(),
518 ),
519 'template' => 'templates/api-functions',
520 ),
521 'api_function_page' => array(
522 'arguments' => array(
523 'function' => NULL,
524 'signatures' => NULL,
525 'branch_length' => NULL,
526 'documentation' => NULL,
527 'parameters' => NULL,
528 'return' => NULL,
529 'related_topics' => NULL,
530 'call' => NULL,
531 'called' => NULL,
532 'code' => NULL,
533 ),
534 'template' => 'templates/api-function-page',
535 ),
536 'api_constant_page' => array(
537 'arguments' => array(
538 'constant' => NULL,
539 'documentation' => NULL,
540 'code' => NULL,
541 'related_topics' => NULL,
542 ),
543 'template' => 'templates/api-constant-page',
544 ),
545 'api_global_page' => array(
546 'arguments' => array(
547 'global' => NULL,
548 'documentation' => NULL,
549 'code' => NULL,
550 'related_topics' => NULL,
551 ),
552 'template' => 'templates/api-global-page',
553 ),
554 'api_file_page' => array(
555 'arguments' => array(
556 'file' => NULL,
557 'documentation' => NULL,
558 'constants' => NULL,
559 'globals' => NULL,
560 'functions' => NULL,
561 ),
562 'template' => 'templates/api-file-page'
563 ),
564 'api_group_page' => array(
565 'arguments' => array(
566 'documentation' => NULL,
567 'constants' => NULL,
568 'globals' => NULL,
569 'functions' => NULL,
570 ),
571 'template' => 'templates/api-group-page'
572 ),
573 );
574 }
575
576 function api_init() {
577 drupal_add_css(drupal_get_path('module', 'api') .'/api.css');
578 drupal_add_js(drupal_get_path('module', 'api') .'/api.js');
579 }
580
581 function api_block($op, $delta = NULL, $edit = array()) {
582 switch ($op) {
583 case 'list':
584 return array(
585 'api-search' => array(
586 'info' => t('API search'),
587 'cache' => BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE,
588 ),
589 'navigation' => array(
590 'info' => t('API navigation'),
591 'cache' => BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE,
592 ),
593 );
594
595 case 'view':
596 $branches = api_get_branches();
597 $branch = api_get_active_branch();
598
599 switch ($delta) {
600 case 'api-search':
601 if (user_access('access API reference') && isset($branches[$branch])) {
602 return array(
603 'subject' => t('Search @branch', array('@branch' => $branches[$branch]->title)),
604 'content' => drupal_get_form('api_search_form', $branches[$branch]->branch_name),
605 );
606 }
607 return;
608
609 case 'navigation':
610 if (user_access('access API reference') && isset($branches[$branch])) {
611 // We forgo the menu system because we want to link to non-default
612 // local tasks; as of Drupal 5.x, this seems impossible.
613 if ($branch == variable_get('api_default_branch', NULL)) {
614 $suffix = '';
615 }
616 else {
617 $suffix = '/'. $branch;
618 }
619
620 $links = array();
621 $links[] = l($branches[$branch]->title, 'api'. $suffix);
622 $links[] = l(t('Constants'), 'api/constants'. $suffix);
623 $links[] = l(t('Files'), 'api/files'. $suffix);
624 $links[] = l(t('Functions'), 'api/functions'. $suffix);
625 $links[] = l(t('Globals'), 'api/globals'. $suffix);
626 $links[] = l(t('Topics'), 'api/groups'. $suffix);
627
628 return array(
629 'content' => theme('item_list', $links),
630 );
631 }
632 return;
633 }
634 }
635 }
636
637 /**
638 * Show link for theme('api_expandable').
639 */
640 function api_show_l($text) {
641 return l($text, $_REQUEST['q'], array('attributes' => array('class' => 'show-content')));
642 }
643
644 /**
645 * Hide link for theme('api_expandable').
646 */
647 function api_hide_l($text) {
648 return l($text, $_REQUEST['q'], array('attributes' => array('class' => 'hide-content')));
649 }
650
651 /**
652 * Save an API branch.
653 *
654 * @param $branch
655 * A branch object, with branch_name, title, and directory variables.
656 * @param $old_branch_name
657 * To replace a branch, provide the old branch name.
658 */
659 function api_save_branch($branch, $old_branch_name = NULL) {
660 if (empty($old_branch_name)) {
661 drupal_write_record('api_branch', $branch);
662 if (is_null(variable_get('api_default_branch', NULL))) {
663 variable_set('api_default_branch', $branch->branch_name);
664 }
665 }
666 else {
667 if ($branch->branch_name !== $old_branch_name) {
668 db_query("UPDATE {api_branch} SET branch_name = '%s' WHERE branch_name = '%s'", $branch->branch_name, $old_branch_name);
669 db_query("UPDATE {api_documentation} SET branch_name = '%s' WHERE branch_name = '%s'", $branch->branch_name, $old_branch_name);
670 db_query("UPDATE {api_file} f INNER JOIN {api_documentation} d ON d.did = f.did SET f.modified = 52 WHERE d.branch_name = '%s'", $branch->branch_name);
671 if (variable_get('api_default_branch', NULL) === $old_branch_name) {
672 variable_set('api_default_branch', $branch->branch_name);
673 }
674 }
675 drupal_write_record('api_branch', $branch, 'branch_name');
676 }
677
678 // Reweight all branches.
679 $branches = api_get_branches(TRUE);
680 uksort($branches, 'version_compare');
681 $weight = 0;
682 foreach ($branches as $branch) {
683 $branch->weight = $weight;
684 $weight += 1;
685 drupal_write_record('api_branch', $branch, 'branch_name');
686 }
687
688 menu_rebuild();
689 }
690
691 function api_get_active_branch() {
692 static $branch;
693
694 if (!isset($branch)) {
695 if (arg(0) == 'api') {
696 if (in_array(arg(1), array('function_dump', 'functions', 'constants', 'globals', 'files', 'groups', 'search'))) {
697 $possible_branch = arg(2);
698 }
699 elseif (in_array(arg(1), array('function', 'constant', 'global', 'group'))) {
700 $possible_branch = arg(3);
701 }
702 elseif (arg(1) == 'file') {
703 // Starting at arg(2), we have a variable number of directories. The
704 // possible branch name is either in the last, or, if we are on a
705 // secondary local task, second to last argument.
706 $current = arg(3);
707 $i = 4;
708 while (!is_null(arg($i))) {
709 $last = $current;
710 $current = arg($i);
711 $i += 1;
712 }
713
714 if (in_array($current, array('documentation', 'source'))) {
715 $possible_branch = $last;
716 }
717 else {
718 $possible_branch = $current;
719 }
720 }
721 else {
722 // Maybe we are on one of the branch home pages.
723 $possible_branch = arg(1);
724 }
725 }
726
727 if (isset($possible_branch)) {
728 $branches = api_get_branches();
729 if (isset($branches[$possible_branch])) {
730 $branch = $possible_branch;
731 }
732 }
733 if (!isset($branch)) {
734 $branch = variable_get('api_default_branch', NULL);
735 }
736 }
737
738 return $branch;
739 }
740
741 function api_search_form($form_state, $branch_name) {
742 $form = array();
743
744 $form['branch_name'] = array(
745 '#type' => 'value',
746 '#value' => $branch_name,
747 );
748 $form['search'] = array(
749 '#title' => t('Function, file, or topic'),
750 '#type' => 'textfield',
751 '#autocomplete_path' => 'api/autocomplete/'. $branch_name,
752 '#default_value' => '',
753 '#required' => TRUE,
754 );
755 $form['submit'] = array(
756 '#type' => 'submit',
757 '#value' => t('Search'),
758 );
759
760 return $form;
761 }
762
763 function api_search_form_submit($form, &$form_state) {
764 $form_state['redirect'] = 'api/search/'. $form_state['values']['branch_name'] .'/'. $form_state['values']['search'];
765 }
766
767 function api_search_redirect() {
768 drupal_goto('api/search/'. variable_get('api_default_branch', NULL) .'/'. implode('/', func_get_args()));
769 }
770
771 /**
772 * Menu callback; perform a global search for documentation.
773 */
774 function api_search_listing($branch_name) {
775 $search_text = func_get_args();
776 array_shift($search_text);
777 $search_text = implode('/', $search_text);
778 drupal_set_title(t('Search for %search', array('%search' => $search_text)));
779
780 $count = db_result(db_query("SELECT count(*) FROM {api_documentation} WHERE branch_name = '%s' AND title = '%s'", $branch_name, $search_text));
781 if ($count == 1) {
782 // Exact match.
783 $item = db_fetch_object(db_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND title = '%s'", $branch_name, $search_text));
784 drupal_goto('api/'. $item->object_type .'/'. $item->object_name .'/'. $item->branch_name);
785 }
786 else {
787 // Wildcard search.
788 $result = pager_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND object_name LIKE '%%%s%%' ORDER BY title", 50, 0, NULL, $branch_name, $search_text);
789 return api_render_listing($result, t('No search results found.')) . theme('pager', NULL, 50, 0);
790 }
791 }
792
793 /**
794 * Prepare a listing of potential documentation matches for a branch.
795 */
796 function api_autocomplete($branch_name, $search = '') {
797 $matches = array();
798 $result = db_query_range("SELECT title FROM {api_documentation} WHERE title LIKE '%%%s%%' AND branch_name = '%s' ORDER BY LENGTH(title)", $search, $branch_name, 0, 20);
799 while ($r = db_fetch_object($result)) {
800 $matches[$r->title] = check_plain($r->title);
801 }
802 print drupal_json($matches);
803 }
804
805 /**
806 * Menu callback; displays the main documentation page.
807 */
808 function api_page_branch($branch_name) {
809 $result = db_query("SELECT documentation FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'mainpage'", $branch_name, $branch_name);
810 if ($branch = db_fetch_object($result)) {
811 return api_link_documentation($branch->documentation, $branch_name);
812 }
813 else {
814 return t('A main page for this branch has not been indexed. A documentation comment with <code>@mainpage {title}</code> needs to exist, or has not been indexed yet. For Drupal core, this is availiable in the <a href="http://cvs.drupal.org/viewvc.py/drupal/contributions/docs/developer/">developer documentation</a> in the contributions repository.');
815 }
816 }
817
818 /**
819 * Menu callback; displays an object listing.
820 */
821 function api_page_listing($branch_name, $object_type) {
822 $result = pager_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND object_type = '%s' ORDER BY title", 50, 0, NULL, $branch_name, $object_type);
823 return api_render_listing($result) . theme('pager', NULL, 50, 0);
824 }
825
826 /**
827 * Render a table with an overview of documentation objects.
828 *
829 * @param $result
830 * A database query result object.
831 * @param $empty_message
832 * An optional string to display instead of an empty table.
833 */
834 function api_render_listing($result, $empty_message = NULL) {
835 $headers = array(
836 t('Name'),
837 t('Location'),
838 t('Description'),
839 );
840
841 $rows = array();
842 while ($object = db_fetch_object($result)) {
843 $rows[] = array(
844 l($object->title, 'api/'. $object->object_type .'/'. $object->object_name .'/'. $object->branch_name),
845 '<small>'. api_file_link($object) .'</small>',
846 api_link_documentation($object->summary, $object->branch_name),
847 );
848 }
849
850 if (count($rows) == 0) {
851 if ($empty_message == NULL) {
852 return '';
853 }
854 else {
855 return '<p><em>'. $empty_message .'</em></p>';
856 }
857 }
858 else {
859 return theme('table', $headers, $rows);
860 }
861 }
862
863 /**
864 * Menu callback; produces a textual listing of all functions for use in IDEs.
865 */
866 function api_page_function_dump($branch_name) {
867 $result = db_query("SELECT d.title, d.summary, f.signature FROM {api_documentation} d INNER JOIN {api_function} f ON d.did = f.did WHERE d.branch_name = '%s' AND d.object_type = 'function'", $branch_name);
868 while ($object = db_fetch_object($result)) {
869 print($object->signature);
870 print(' ### '. $object->summary ."\n");
871 }
872 }
873
874 /**
875 * Menu callback; displays documentation for a function.
876 */
877 function api_page_function($function) {
878 drupal_set_title($function->title);
879
880 $last_signature = '';
881 $signatures = array();
882 $n = 0;
883 $result = db_query("SELECT d.branch_name, f.signature FROM {api_documentation} d INNER JOIN {api_function} f ON f.did = d.did INNER JOIN {api_branch} b ON d.branch_name = b.branch_name WHERE d.object_type = 'function' AND d.title = '%s' ORDER BY b.weight", $function->title);
884 while ($signature = db_fetch_object($result)) {
885 if ($signature->signature == $last_signature) {
886 // Collapse unchanged signatures to one line.
887 $signature_info[$n - 1]['max branch name'] = $signature->branch_name;
888 $signature_info[$n - 1]['active'] = $signature_info[$n - 1]['active'] || $signature->branch_name == $function->branch_name;
889 }
890 else {
891 $tokens = token_get_all('<?php '. $signature->signature);
892 if ($tokens[1] == '&') {
893 $name = '&'. $tokens[2][1];
894 }
895 else {
896 $name = $tokens[1][1];
897 }
898 $signature_info[$n] = array(
899 'branch name' => $signature->branch_name,
900 'max branch name' => $signature->branch_name,
901 'active' => $signature->branch_name == $function->branch_name,
902 'arguments' => array(),
903 'other' => array(),
904 );
905 $start = TRUE;
906 $d = 0;
907 $a = -1;
908 $signature_info[$n]['other'][$a] = '';
909 foreach ($tokens as $token) {
910 $d += in_array($token, array('(', '{', '[')) - in_array($token, array(')', '}', ']'));
911 if ($d == 1 && $start && is_array($token) && $token[0] == T_VARIABLE) {
912 // New argument
913 $a += 1;
914 $signature_info[$n]['arguments'][$a] = $token[1];
915 $signature_info[$n]['other'][$a] = '';
916 $start = FALSE;
917 }
918 elseif ($d >= 1 && is_array($token)) {
919 $signature_info[$n]['other'][$a] .= $token[1];
920 }
921 elseif ($d >= 1) {
922 $signature_info[$n]['other'][$a] .= $token;
923 // Start looking for a new argument if we see a comma.
924 $start = $start || ($d == 1 && $token == ',');
925 }
926 }
927 $last_signature = $signature->signature;
928 $n += 1;
929 }
930 }
931 $branch_length = 0;
932 foreach ($signature_info as $n => $info) {
933 $new = array();
934 if (isset($signature_info[$n - 1])) {
935 $new = array_diff($info['arguments'], $signature_info[$n - 1]['arguments']);
936 }
937 $old = array();
938 if (isset($signature_info[$n + 1])) {
939 $old = array_diff($info['arguments'], $signature_info[$n + 1]['arguments']);
940 }
941 $branch_label = $info['branch name'];
942 if ($info['branch name'] != $info['max branch name']) {
943 $branch_label .= ' – '. $info['max branch name'];
944 }
945 $branch_length = max($branch_length, drupal_strlen($branch_label));
946 $signature = $name . $info['other'][-1];
947 foreach ($signature_info[$n]['arguments'] as $key => $argument) {
948 if (in_array($argument, $old)) {
949 $signature .= '<del>'. $argument .'</del>';
950 }
951 elseif (in_array($argument, $new)) {
952 $signature .= '<ins>'. $argument .'</ins>';
953 }
954 else {
955 $signature .= $argument;
956 }
957 $signature .= $info['other'][$key];
958 }
959 $signature .= ')';
960 $signatures[$branch_label] = array(
961 'signature' => $signature,
962 'url' => 'api/function/'. $name .'/'. $info['max branch name'],
963 'active' => $info['active'],
964 );
965 }
966
967 $documentation = api_link_documentation($function->documentation, $function->branch_name);
968 $parameters = api_link_documentation($function->parameters, $function->branch_name);
969 $return = api_link_documentation($function->return_value, $function->branch_name);
970 $code = api_link_code($function->code, $function->branch_name);
971 $related_topics = api_related_topics($function->did, $function->branch_name);
972
973 $call_count = db_result(db_query("SELECT count(*) FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.from_did = d.did AND d.object_type = 'function' WHERE r.to_did = %d", $function->did));
974 $call = '';
975 if ($call_count > 0) {
976 $result = db_query("SELECT d.object_name, d.title, d.summary, d.file_name, d.branch_name, d.object_type FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.from_did = d.did AND d.object_type = 'function' WHERE r.to_did = %d ORDER BY d.title", $function->did);
977 $call_functions = array();
978 while ($object = db_fetch_object($result)) {
979 $call_functions[] = array(
980 'function' => l($object->title, 'api/'. $object->object_type .'/'. $object->object_name .'/'. $object->branch_name),
981 'file' => api_file_link($object),
982 'description' => api_link_documentation($object->summary, $object->branch_name),
983 );
984 }
985 $call_title = format_plural($call_count, '1 function calls @name()', '@count functions call @name()', array('@name' => $function->title));
986 $call = theme('api_expandable', '<h3>'. api_show_l('▸ '. $call_title) .'</h3>', '<h3>'. api_hide_l('▾ '. $call_title) .'</h3>'. theme('api_functions', $call_functions));
987 }
988
989 $called_count = db_result(db_query("SELECT count(*) FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.to_did = d.did AND d.object_type = 'function' WHERE r.from_did = %d", $function->did));
990 $called = '';
991 if ($called_count > 0) {
992 $result = db_query("SELECT d.object_name, d.title, d.summary, d.file_name, d.branch_name, d.object_type FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.to_did = d.did AND d.object_type = 'function' WHERE r.from_did = %d ORDER BY d.title", $function->did);
993 $called_functions = array();
994 while ($object = db_fetch_object($result)) {
995 $called_functions[] = array(
996 'function' => l($object->title, 'api/'. $object->object_type .'/'. $object->object_name .'/'. $object->branch_name),
997 'file' => api_file_link($object),
998 'description' => api_link_documentation($object->summary, $object->branch_name),
999 );
1000 }
1001 $called_title = format_plural($called_count, '1 function called by @name()', '@count functions called by @name()', array('@name' => $function->title));
1002 $called = theme('api_expandable', '<h3>'. api_show_l('▸ '. $called_title) .'</h3>', '<h3>'. api_hide_l('▾ '. $called_title) .'</h3>'. theme('api_functions', $called_functions));
1003 }
1004
1005 return theme('api_function_page', $function, $signatures, $branch_length, $documentation, $parameters, $return, $related_topics, $call, $called, $code);
1006 }
1007
1008 /**
1009 * Menu callback; displays documentation for a constant.
1010 */
1011 function api_page_constant($constant) {
1012 drupal_set_title($constant->title);
1013
1014 $documentation = api_link_documentation($constant->documentation, $constant->branch_name);
1015 $code = api_link_code($constant->code, $constant->branch_name);
1016 $related_topics = api_related_topics($constant->did, $constant->branch_name);
1017
1018 return theme('api_constant_page', $constant, $documentation, $code, $related_topics);
1019 }
1020
1021 /**
1022 * Menu callback; displays documentation for a global.
1023 */
1024 function api_page_global($global) {
1025 drupal_set_title($global->title);
1026
1027 $documentation = api_link_documentation($global->documentation, $global->branch_name);
1028 $related_topics = api_related_topics($global->did, $global->branch_name);
1029 $code = api_link_code($global->code, $global->branch_name);
1030
1031 return theme('api_global_page', $global, $documentation, $code, $related_topics);
1032 }
1033
1034 /**
1035 * Menu callback; displays documentation for a file.
1036 */
1037 function api_page_file($file) {
1038 drupal_set_title($file->title);
1039
1040 $documentation = api_link_documentation($file->documentation, $file->branch_name);
1041 $result = db_query("SELECT title, object_name, summary, object_type, branch_name FROM {api_documentation} WHERE file_name = '%s' AND branch_name = '%s' AND object_type IN ('constant', 'global', 'function') ORDER BY title", $file->object_name, $file->branch_name);
1042 $rows = array(
1043 'constant' => array(),
1044 'global' => array(),
1045 'function' => array(),
1046 );
1047 while ($object = db_fetch_object($result)) {
1048 $rows[$object->object_type][] = array(
1049 l($object->title, 'api/'. $object->object_type .'/'. $object->object_name .'/'. $object->branch_name),
1050 api_link_documentation($object->summary, $file->branch_name),
1051 );
1052 }
1053 $header = array(
1054 t('Name'),
1055 t('Description'),
1056 );
1057 $constants = '';
1058 if (count($rows['constant']) > 0) {
1059 $constants = theme('table', $header, $rows['constant']);
1060 }
1061 $globals = '';
1062 if (count($rows['global']) > 0) {
1063