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 the following Doxygen constructs:

The module was designed to produce the Drupal developer documentation available at !api_site.

Set up

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.

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.

The module indexes code branches during cron runs, so make sure the site has cron functionality set up properly.

', array('!api_site' => l('http://api.drupal.org', 'http://api.drupal.org', array(), NULL, NULL, TRUE), '!api_settings_page' => l(t('API settings page'), 'admin/settings/api'))); return $output; } } function api_get_branches() { static $branches; if (!isset($branches)) { $result = db_query('SELECT branch_name, title FROM {api_branch}'); $branches = array(); while ($branch = db_fetch_object($result)) { $branches[$branch->branch_name] = $branch; } } return $branches; } /** * Implementation of hook_menu(). */ function api_menu($may_cache) { $items = array(); $access = user_access('access API reference'); $branches = api_get_branches(); $default_branch = variable_get('api_default_branch', 'HEAD'); if ($may_cache) { $items[] = array( 'path' => 'api', 'title' => t('API reference'), 'access' => $access, 'callback' => 'api_page_branch', 'callback arguments' => array($default_branch), ); $items[] = array( 'path' => 'api/functions', 'title' => t('Functions'), 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($default_branch, 'function'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'api/constants', 'title' => t('Constants'), 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($default_branch, 'constant'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'api/file', 'title' => t('Files'), 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($default_branch, 'file'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'api/files', 'title' => t('Files'), 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($default_branch, 'file'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'api/groups', 'title' => t('Topics'), 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($default_branch, 'group'), 'type' => MENU_CALLBACK, ); foreach ($branches as $branch) { $items[] = array( 'path' => 'api/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_branch', 'access' => $access, 'callback arguments' => array($branch->branch_name), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'api/function_dump/'. $branch->branch_name, 'title' => t('Functions'), 'callback' => 'api_page_function_dump', 'access' => $access, 'callback arguments' => array($branch->branch_name), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'api/functions/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($branch->branch_name, 'function'), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'api/constants/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($branch->branch_name, 'constant'), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'api/files/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($branch->branch_name, 'file'), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'api/groups/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_listing', 'access' => $access, 'callback arguments' => array($branch->branch_name, 'group'), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); } $items[] = array( 'path' => 'apis', 'title' => t('API search'), 'callback' => 'api_search_listing', 'access' => $access, 'type' => MENU_CALLBACK, ); $items[] = array('path' => 'api/autocomplete', 'callback' => 'api_autocomplete', 'access' => $access, 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/settings/api', 'title' => t('API reference'), 'description' => t('Configure Drupal branches for documentation.'), 'access' => user_access('administer API reference'), 'callback' => 'api_page_admin'); } else { if (arg(0) == 'api') { if (arg(1) == 'function' && is_string(arg(2))) { $items[] = array( 'path' => 'api/function/'. arg(2), 'title' => t('Function'), 'callback' => 'api_page_function', 'access' => $access, 'callback arguments' => array($default_branch, arg(2)), 'type' => MENU_CALLBACK, ); foreach ($branches as $branch) { if (db_result(db_query("SELECT did FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'function'", arg(2), $branch->branch_name))) { $items[] = array( 'path' => 'api/function/'. arg(2) .'/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_function', 'access' => $access, 'callback arguments' => array($branch->branch_name, arg(2)), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); } } $version = is_null(arg(3)) ? $default_branch : arg(3); $items[] = array( 'path' => 'api/function/'. arg(2) .'/'. $version .'/documentation', 'title' => t('View documentation'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10 ); $items[] = array( 'path' => 'api/function/'. arg(2) .'/'. $version .'/references', 'title' => t('List references'), 'callback' => 'api_page_function_references', 'access' => $access, 'callback arguments' => array(arg(3), arg(2)), 'type' => MENU_LOCAL_TASK, ); } if (arg(1) == 'constant' && is_string(arg(2))) { $items[] = array( 'path' => 'api/constant/'. arg(2), 'title' => t('Constant'), 'callback' => 'api_page_constant', 'access' => $access, 'callback arguments' => array($default_branch, arg(2)), 'type' => MENU_CALLBACK, ); foreach ($branches as $branch) { if (db_result(db_query("SELECT did FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'constant'", arg(2), $branch->branch_name))) { $items[] = array( 'path' => 'api/constant/'. arg(2) .'/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_constant', 'access' => $access, 'callback arguments' => array($branch->branch_name, arg(2)), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); } } } if (arg(1) == 'file' && is_string(arg(2))) { // The file path to the file makes determining the correct menu path a // bit tricky. We have to break off the beginning section of the path, // then test the last part to see if it's a branch name, then build the // filename/branch name from there. $parts = $parts2 = explode('/', substr($_GET['q'], strlen('api/file/'))); $last = array_pop($parts2); if ($last == 'source' || $last == 'documentation') { $last = array_pop($parts2); } if (in_array($last, array_keys($branches))) { $main_file_name = implode('/', $parts2); $branch_name = $last; } else { $main_file_name = implode('/', $parts); $branch_name = $default_branch; } foreach ($branches as $branch) { if ($branch->branch_name == $branch_name) { $items[] = array( 'path' => 'api/file/'. $main_file_name, 'title' => basename($main_file_name), 'callback' => 'api_page_file', 'access' => $access, 'callback arguments' => array($branch->branch_name, $main_file_name), 'type' => MENU_CALLBACK, ); } // Check if file path has changed in different branches. if ($file_name = db_result(db_query("SELECT file_name FROM {api_documentation} WHERE title = '%s' AND branch_name = '%s' AND object_type = 'file'", basename($main_file_name), $branch->branch_name))) { $items[] = array( 'path' => 'api/file/'. $file_name .'/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_file', 'access' => $access, 'callback arguments' => array($branch->branch_name, $file_name), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); } } $items[] = array( 'path' => 'api/file/'. $main_file_name .'/'. $branch_name .'/documentation', 'title' => t('View documentation'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items[] = array( 'path' => 'api/file/'. $main_file_name .'/'. $branch_name .'/source', 'title' => t('View source'), 'callback' => 'api_page_file_source', 'access' => $access, 'callback arguments' => array($branch_name, $main_file_name), 'type' => MENU_LOCAL_TASK, ); } if (arg(1) == 'group' && is_string(arg(2))) { $items[] = array( 'path' => 'api/group/'. arg(2), 'title' => t('Topic'), 'callback' => 'api_page_group', 'access' => $access, 'callback arguments' => array($default_branch, arg(2)), 'type' => MENU_CALLBACK, ); foreach ($branches as $branch) { if (db_result(db_query("SELECT did FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'group'", arg(2), $branch->branch_name))) { $items[] = array( 'path' => 'api/group/'. arg(2) .'/'. $branch->branch_name, 'title' => $branch->title, 'callback' => 'api_page_group', 'access' => $access, 'callback arguments' => array($branch->branch_name, arg(2)), 'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); } } } if (arg(1) == 'search' && is_string(arg(3))) { $items[] = array( 'path' => 'api/search', 'title' => t('Search'), 'callback' => 'api_search_listing', 'access' => $access, 'type' => MENU_CALLBACK, ); foreach ($branches as $branch) { $items[] = array( 'path' => 'api/search/'. $branch->branch_name .'/'. arg(3), 'title' => $branch->title, 'callback' => 'api_search_listing', 'callback arguments' => array($branch->branch_name, arg(3)), 'access' => $access, //'type' => ($branch->branch_name == $default_branch) ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, 'type' => MENU_LOCAL_TASK, ); } } } } return $items; } /** * Implementation of hook_perm(). */ function api_perm() { return array('access API reference', 'administer API reference'); } function api_block($op, $delta = NULL, $edit = array()) { switch ($op) { case 'list': return array( 'api-search' => array( 'info' => t('API search'), ), 'navigation' => array( 'info' => t('API navigation'), ), ); case 'view': switch ($delta) { case 'api-search': drupal_add_css(drupal_get_path('module', 'api') .'/api.css', 'module'); $branches = api_get_branches(); $branch = api_get_active_branch(); return array( 'subject' => t('Search @branch', array('@branch' => $branches[$branch]->title)), 'content' => drupal_get_form('api_search_form', $branches[$branch]), ); case 'navigation': // We forgo the menu system because we want to link to non-default // local tasks; as of Drupal 5.x, this seems impossible. $branches = api_get_branches(); $branch = api_get_active_branch(); if ($branch == variable_get('api_default_branch', 'HEAD')) { $suffix = ''; } else { $suffix = '/'. $branch; } $links = array(); $links[] = l($branches[$branch]->title, 'api'. $suffix); $links[] = l(t('Constants'), 'api/constants'. $suffix); $links[] = l(t('Files'), 'api/files'. $suffix); $links[] = l(t('Functions'), 'api/functions'. $suffix); $links[] = l(t('Topics'), 'api/groups'. $suffix); return array( 'content' => theme('item_list', $links), ); } } } function api_get_active_branch() { if (arg(0) == 'api') { if (in_array(arg(1), array('function_dump', 'functions', 'constants', 'files', 'groups', 'search'))) { $possible_branch = arg(2); } elseif (in_array(arg(1), array('function', 'constant', 'group'))) { $possible_branch = arg(3); } elseif (arg(1) == 'file') { // Starting at arg(2), we have a variable number of directories. The // possible branch name is either in the last, or, if we are on a // secondary local task, second to last argument. $current = arg(3); $i = 4; while (!is_null(arg($i))) { $last = $current; $current = arg($i); $i += 1; } if (in_array($current, array('documentation', 'source'))) { $possible_branch = $last; } else { $possible_branch = $current; } } else { // Maybe we are on one of the branch home pages. $possible_branch = arg(1); } } if (isset($possible_branch)) { $branches = api_get_branches(); if (isset($branches[$possible_branch])) { return $possible_branch; } } return variable_get('api_default_branch', 'HEAD'); } function api_search_form($branch) { $form = array(); $form['branch_name'] = array( '#type' => 'value', '#value' => $branch->branch_name, ); $form['search'] = array( '#title' => t('Function, file, or topic'), '#type' => 'textfield', '#autocomplete_path' => 'api/autocomplete/'. $branch->branch_name, '#default_value' => '', '#required' => TRUE, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Search'), ); return $form; } function api_search_form_submit($form_id, $form_values) { return 'api/search/'. $form_values['branch_name'] .'/'. $form_values['search']; } /** * Menu callback; perform a global search for documentation. */ function api_search_listing($branch_name, $search_text = '') { if ($search_text == '') { $search_text = $branch_name; $branch_name = variable_get('api_default_branch', 'HEAD'); } drupal_set_title(t('API Search for “%search” in %branch', array('%search' => $search_text, '%branch' => $branch_name))); // Exact match. $result = db_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND title = '%s'", $branch_name, $search_text); $count = db_num_rows($result); if ($count != 1) { // Wildcard search. $result = db_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND title LIKE '%%%s%%'", $branch_name, $search_text); $count = db_num_rows($result); } switch ($count) { case 0: return t("Search found no results."); case 1: $item = db_fetch_object($result); drupal_goto('api/'. $item->object_type .'/'. $item->object_name .'/'. $item->branch_name); break; default: $result = pager_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND object_name LIKE '%%%s%%'". tablesort_sql(_api_listing_headers()), 50, 0, NULL, $branch_name, $search_text); return api_render_listing($result); } } /** * Prepare a listing of potential documentation matches for a branch. */ function api_autocomplete($branch_name, $search) { $matches = array(); $result = db_query("SELECT title FROM {api_documentation} WHERE title LIKE '%%%s%%' AND branch_name = '%s' ORDER BY LENGTH(title) LIMIT 20", $search, $branch_name); while ($r = db_fetch_object($result)) { $matches[$r->title] = check_plain($r->title); } print drupal_to_js($matches); exit(); } /** * Menu callback; displays the main documentation page. */ function api_page_branch($branch_name) { $result = db_query("SELECT documentation FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'mainpage'", $branch_name, $branch_name); if ($branch = db_fetch_object($result)) { return api_link_documentation($branch->documentation, $branch_name); } else { return t('A main page for this branch has not been indexed. A documentation comment with @mainpage {title} needs to exist, or has not been indexed yet. For Drupal core, this is availiable in the developer documentation in the contributions repository.'); } } /** * Menu callback; displays an object listing. */ function api_page_listing($branch_name, $object_type) { $result = pager_query("SELECT * FROM {api_documentation} WHERE branch_name = '%s' AND object_type = '%s'". tablesort_sql(_api_listing_headers()), 50, 0, NULL, $branch_name, $object_type); return api_render_listing($result); } function _api_listing_headers() { $headers = array(); $headers[] = array('data' => t('Name'), 'field' => 'title'); $headers[] = array('data' => t('Location'), 'field' => 'file_name'); $headers[] = t('Description'); return $headers; } /** * Render a table with an overview of documentation objects. */ function api_render_listing($result) { $headers = _api_listing_headers(); $rows = array(); while ($object = db_fetch_object($result)) { $row = array(); $row[] = l($object->title, 'api/'. $object->object_type .'/'. $object->object_name .'/'. $object->branch_name); $row[] = l(str_replace('/', '/ ', $object->file_name), 'api/file/'. $object->file_name .'/'. $object->branch_name); $row[] = api_link_documentation($object->summary, $object->branch_name); $rows[] = $row; } $output = theme('table', $headers, $rows); $output .= theme('pager', NULL, 50, 0); return $output; } /** * Menu callback; produces a textual listing of all functions for use in IDEs. */ function api_page_function_dump($branch_name) { $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); while ($object = db_fetch_object($result)) { print($object->signature); print(' ### '. $object->summary ."\n"); } } /** * Menu callback; displays documentation for a function. */ function api_page_function($branch_name, $object_name) { drupal_add_css(drupal_get_path('module', 'api') .'/api.css'); $result = db_query("SELECT d.did, d.title, d.file_name, d.documentation, d.code, f.signature, f.start_line, f.parameters, f.return FROM {api_documentation} d INNER JOIN {api_function} f ON d.did = f.did WHERE d.object_name = '%s' AND d.branch_name = '%s' AND d.object_type = 'function'", $object_name, $branch_name); if ($function = db_fetch_object($result)) { $output = ''; $output .= '

'. t('Definition') .'

'; $output .= '

'. $function->signature .'
'; $output .= l($function->file_name, 'api/file/'. $function->file_name .'/'. $branch_name) .', '. t('line') .' '. $function->start_line .'

'; if (!empty($function->documentation)) { $output .= '

'. t('Description') .'

'; $output .= api_link_documentation($function->documentation, $branch_name); } if (!empty($function->parameters)) { $output .= '

'. t('Parameters') .'

'; $output .= api_link_documentation($function->parameters, $branch_name); } if (!empty($function->return)) { $output .= '

'. t('Return value') .'

'; $output .= api_link_documentation($function->return, $branch_name); } $headers = array(array('data' => t('Name'), 'field' => 'd.title'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.to_did = d.did AND d.object_type = 'group' WHERE r.from_did = %d". tablesort_sql($headers), $function->did); while ($group = db_fetch_object($result)) { $rows[] = array( l($group->title, 'api/group/'. $group->object_name .'/'. $branch_name), api_link_documentation($group->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Related topics') .'

'; $output .= theme('table', $headers, $rows); } if (!empty($function->code)) { $output .= '

'. t('Code') .'

'; $output .= api_link_code($function->code, $branch_name); } drupal_set_title($function->title); return $output; } else { drupal_not_found(); } } /** * Menu callback; displays all functions that reference another function. */ function api_page_function_references($branch_name, $object_name) { drupal_add_css(drupal_get_path('module', 'api') .'/api.css'); $result = db_query("SELECT d.did, d.title FROM {api_documentation} d WHERE d.object_name = '%s' AND d.branch_name = '%s' AND d.object_type = 'function'", $object_name, $branch_name); if ($function = db_fetch_object($result)) { $output = ''; $headers = array(array('data' => t('Name'), 'field' => 'd.title'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.to_did = d.did AND d.object_type = 'group' WHERE r.from_did = %d". tablesort_sql($headers), $function->did); while ($group = db_fetch_object($result)) { $rows[] = array( l($group->title, 'api/group/'. $group->object_name .'/'. $branch_name), api_link_documentation($group->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Related topics') .'

'; $output .= theme('table', $headers, $rows); } $headers = array(array('data' => t('Name'), 'field' => 'd.title'), array('data' => t('Location'), 'field' => 'd.file_name'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary, d.file_name 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". tablesort_sql($headers), $function->did); while ($reference = db_fetch_object($result)) { $rows[] = array( l($reference->title, 'api/function/'. $reference->object_name .'/'. $branch_name), l($reference->file_name, 'api/file/'. $reference->file_name .'/'. $branch_name), api_link_documentation($reference->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Functions that call %name()', array('%name' => $function->title)) .'

'; $output .= theme('table', $headers, $rows); } $headers = array(array('data' => t('Name'), 'field' => 'd.title'), array('data' => t('Location'), 'field' => 'd.file_name'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary, d.file_name 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". tablesort_sql($headers), $function->did); while ($reference = db_fetch_object($result)) { $rows[] = array( l($reference->title, 'api/function/'. $reference->object_name .'/'. $branch_name), l($reference->file_name, 'api/file/'. $reference->file_name .'/'. $branch_name), api_link_documentation($reference->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Functions called by %name()', array('%name' => $function->title)) .'

'; $output .= theme('table', $headers, $rows); } drupal_set_title($function->title); return $output; } else { drupal_not_found(); } } /** * Menu callback; displays documentation for a constant. */ function api_page_constant($branch_name, $object_name) { drupal_add_css(drupal_get_path('module', 'api') .'/api.css'); $result = db_query("SELECT d.did, d.title, d.file_name, d.documentation, d.code FROM {api_documentation} d WHERE d.object_name = '%s' AND d.branch_name = '%s' AND d.object_type = 'constant'", $object_name, $branch_name); if ($constant = db_fetch_object($result)) { $output = ''; $output .= '

'. t('Definition') .'

'; $output .= '

'. l($constant->file_name, 'api/file/'. $constant->file_name .'/'. $branch_name) .'

'; if (!empty($constant->documentation)) { $output .= '

'. t('Description') .'

'; $output .= api_link_documentation($constant->documentation, $branch_name); } $headers = array(array('data' => t('Name'), 'field' => 'd.title'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.to_did = d.did AND d.object_type = 'group' WHERE r.from_did = %d". tablesort_sql($headers), $constant->did); while ($group = db_fetch_object($result)) { $rows[] = array( l($group->title, 'api/group/'. $group->object_name .'/'. $branch_name), api_link_documentation($group->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Related topics') .'

'; $output .= theme('table', $headers, $rows); } if (!empty($constant->code)) { $output .= '

'. t('Code') .'

'; $output .= api_link_code($constant->code, $branch_name); } drupal_set_title($constant->title); return $output; } else { drupal_not_found(); } } /** * Menu callback; displays documentation for a file. */ function api_page_file($branch_name, $object_name) { drupal_add_css(drupal_get_path('module', 'api') .'/api.css'); $result = db_query("SELECT d.did, d.title, d.documentation, f.version FROM {api_documentation} d INNER JOIN {api_file} f ON d.did = f.did WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'file'", $object_name, $branch_name); $file = db_fetch_object($result); if (!$file) { $result = db_query("SELECT d.object_name FROM {api_documentation} d WHERE d.object_name LIKE '%%/%s' AND branch_name = '%s' AND object_type = 'file'", $object_name, $branch_name); if ($object_name = db_result($result)) { drupal_goto('api/file/'. $object_name .'/'. $branch_name); } } if ($file) { $output = ''; if (!empty($file->version)) { $output .= '

'. t('Version') .'

'; $output .= '

'. $file->version .'

'; } if (!empty($file->documentation)) { $output .= '

'. t('Description') .'

'; $output .= api_link_documentation($file->documentation, $branch_name); } $headers = array(array('data' => t('Name'), 'field' => 'title'), t('Description')); $rows = array(); $result = db_query("SELECT title, object_name, summary FROM {api_documentation} WHERE file_name = '%s' AND branch_name = '%s' AND object_type = 'constant'". tablesort_sql($headers), $object_name, $branch_name); while ($object = db_fetch_object($result)) { $rows[] = array( l($object->title, 'api/constant/'. $object->object_name .'/'. $branch_name), api_link_documentation($object->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Constants') .'

'; $output .= theme('table', $headers, $rows); } $headers = array(array('data' => t('Name'), 'field' => 'title'), t('Description')); $rows = array(); $result = db_query("SELECT title, object_name, summary FROM {api_documentation} WHERE file_name = '%s' AND branch_name = '%s' AND object_type = 'function'". tablesort_sql($headers), $object_name, $branch_name); while ($object = db_fetch_object($result)) { $rows[] = array( l($object->title, 'api/function/'. $object->object_name .'/'. $branch_name), api_link_documentation($object->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Functions') .'

'; $output .= theme('table', $headers, $rows); } return $output; } else { drupal_not_found(); } } /** * Menu callback; displays source code for a file. */ function api_page_file_source($branch_name, $object_name) { drupal_add_css(drupal_get_path('module', 'api') .'/api.css'); $result = db_query("SELECT title, code FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'file'", $object_name, $branch_name); if ($file = db_fetch_object($result)) { drupal_set_title($file->title); return api_link_code($file->code, $branch_name); } else { drupal_not_found(); } } /** * Menu callback; displays documentation for a group. */ function api_page_group($branch_name, $object_name) { drupal_add_css(drupal_get_path('module', 'api') .'/api.css'); $result = db_query("SELECT did, title, documentation FROM {api_documentation} WHERE object_name = '%s' AND branch_name = '%s' AND object_type = 'group'", $object_name, $branch_name); if ($group = db_fetch_object($result)) { $output = ''; if (!empty($group->documentation)) { $output .= api_link_documentation($group->documentation, $branch_name); } $headers = array(array('data' => t('Name'), 'field' => 'd.title'), array('data' => t('Location'), 'field' => 'd.file_name'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary, d.file_name FROM {api_reference_storage} r INNER JOIN {api_documentation} d ON r.from_did = d.did AND d.object_type = 'constant' WHERE r.to_did = %d". tablesort_sql($headers), $group->did); while ($object = db_fetch_object($result)) { $rows[] = array( l($object->title, 'api/constant/'. $object->object_name .'/'. $branch_name), l($object->file_name, 'api/file/'. $object->file_name .'/'. $branch_name), api_link_documentation($object->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Constants') .'

'; $output .= theme('table', $headers, $rows); } $headers = array(array('data' => t('Name'), 'field' => 'd.title'), array('data' => t('Location'), 'field' => 'd.file_name'), t('Description')); $rows = array(); $result = db_query("SELECT d.object_name, d.title, d.summary, d.file_name 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". tablesort_sql($headers), $group->did); while ($object = db_fetch_object($result)) { $rows[] = array( l($object->title, 'api/function/'. $object->object_name .'/'. $branch_name), l($object->file_name, 'api/file/'. $object->file_name .'/'. $branch_name), api_link_documentation($object->summary, $branch_name)); } if (count($rows) > 0) { $output .= '

'. t('Functions') .'

'; $output .= theme('table', $headers, $rows); } drupal_set_title($group->title); return $output; } else { drupal_not_found(); } } /** * Menu callback; displays the administration page. */ function api_page_admin() { $output = ''; $output .= '

'. t('Branches to index') .'

'; $output .= drupal_get_form('api_page_admin_form'); $output .= '

'. t('PHP Manual') .'

'; $output .= drupal_get_form('api_php_manual_index_form'); $output .= '

'. t('Refresh index') .'

'; $output .= '

'. t('Parse all indexed code files again, even if they have not been modified.') .'

'; $output .= drupal_get_form('api_reindex_form'); return $output; } function api_page_admin_form() { $form = array(); $result = db_query('SELECT branch_name, title, directory FROM {api_branch}'); $form['branches'] = array( '#tree' => TRUE, ); while ($branch = db_fetch_object($result)) { $form['branches'][$branch->branch_name] = array( '#tree' => TRUE, '#type' => 'fieldset', ); $form['branches'][$branch->branch_name]['branch_name'] = array( '#title' => t('Short name'), '#type' => 'textfield', '#description' => t('The name that will appear in URL paths.'), '#default_value' => $branch->branch_name, ); $form['branches'][$branch->branch_name]['title'] = array( '#title' => t('Long name'), '#type' => 'textfield', '#description' => t('The name that will appear in menu items.'), '#default_value' => $branch->title, ); $form['branches'][$branch->branch_name]['directory'] = array( '#title' => t('Directory name'), '#type' => 'textfield', '#default_value' => $branch->directory, '#description' => t('The absolute path of the directory to index. Multiple paths may be given, separated by colons, e.g.: "/mysite/drupal:/mysite/developer". Note that the module will index recursively from the path given.'), ); $radios[$branch->branch_name] = $branch->title; } $form['branches']['new'] = array('#tree' => TRUE, '#type' => 'fieldset'); $form['branches']['new']['branch_name'] = array( '#title' => t('Short name'), '#type' => 'textfield', ); $form['branches']['new']['title'] = array( '#title' => t('Long name'), '#type' => 'textfield', ); $form['branches']['new']['directory'] = array( '#title' => t('Directory name'), '#type' => 'textfield', '#description' => t('The absolute path of the directory to index. Multiple paths may be given, separated by colons, e.g.: "/mysite/drupal:/mysite/developer".'), ); global $base_url; if ($radios) { if (!variable_get('api_default_branch', FALSE)) { list($first_key) = array_keys($radios); variable_set('api_default_branch', $first_key); } $form['default_branch'] = array( '#type' => 'radios', '#title' => t('Default branch'), '#default_value' => variable_get('api_default_branch', $first_key), '#description' => t('Allows searching at %baseurl/apis/your+search+here', array('%baseurl' => $base_url)), '#options' => $radios, ); } $options = array( 10 => '10', 20 => '20', 30 => '30', 50 => '50', 100 => '100', 200 => '200', ); $form['api_files_per_cron'] = array( '#type' => 'select', '#title' => t('Maximun files to scan per cron run'), '#default_value' => variable_get('api_files_per_cron', 10), '#options' => $options, '#description' => t('Default is 10. It is not recommended to increase this, except temporarily if a large amount of files are to be indexed (ie, a new branch has been added).'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save changes'), ); return $form; } function api_page_admin_form_submit($form_id, $form_values) { foreach ($form_values['branches'] as $branch_name => $branch) { if ($branch_name == 'new') { if ($branch['branch_name'] != '') { db_query("INSERT INTO {api_branch} (branch_name, title, directory) VALUES ('%s', '%s', '%s')", $branch['branch_name'], $branch['title'], $branch['directory']); } } else { if ($branch['branch_name'] == '') { db_query("DELETE FROM {api_branch} WHERE branch_name = '%s'", $branch_name); $result = db_query("SELECT did FROM {api_documentation} WHERE branch_name = '%s'", $branch_name); while ($object = db_fetch_object($result)) { db_query('DELETE FROM {api_documentation} WHERE did = %d', $object->did); db_query('DELETE FROM {api_file} WHERE did = %d', $object->did); db_query('DELETE FROM {api_function} WHERE did = %d', $object->did); db_query('DELETE FROM {api_reference_storage} WHERE from_did = %d OR to_did = %d', $object->did, $object->did); } } else { db_query("UPDATE {api_branch} SET branch_name = '%s', title = '%s', directory = '%s' WHERE branch_name = '%s'", $branch['branch_name'], $branch['title'], $branch['directory'], $branch_name); if ($branch['branch_name'] != $branch_name) { db_query("UPDATE {api_documentation} SET branch_name = '%s' WHERE branch_name = '%s'", $branch['branch_name'], $branch_name); $result = db_query("SELECT did FROM {api_documentation} WHERE branch_name = '%s'", $branch['branch_name']); while ($object = db_fetch_object($result)) { db_query('UPDATE {api_file} SET modified = 52 WHERE did = %d', $object->did); } } } } } //save the variable for default branch if ($form_values['default_branch']) { variable_set('api_default_branch', $form_values['default_branch']); } // Save the variable for max files per cron. variable_set('api_files_per_cron', $form_values['api_files_per_cron']); // We may have menu changes, so clear the menu cache for all users. cache_clear_all('*', 'cache_menu', TRUE); drupal_set_message(t('Changes saved.')); } function api_php_manual_index_form() { $form = array(); $form['api_php_funcsummary'] = array( '#type' => 'textfield', '#default_value' => variable_get('api_php_funcsummary', 'http://cvs.php.net/viewcvs.cgi/phpdoc/funcsummary.txt?&view=markup'), '#description' => t('The URL of the PHP function summary document.'), ); $form['api_php_funcpath'] = array( '#type' => 'textfield', '#default_value' => variable_get('api_php_funcpath', 'http://php.net/!function'), '#description' => t('The URL format used to build the link to php functions. Use the variable !function in place of the function name.'), ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Index PHP manual pages'), ); return $form; } function api_php_manual_index_form_submit($form_id, $form_values) { include_once(drupal_get_path('module', 'api') .'/parser.inc'); variable_set('api_php_funcsummary', $form_values['api_php_funcsummary']); variable_set('api_php_funcpath', $form_values['api_php_funcpath']); db_query("DELETE FROM {api_documentation} WHERE branch_name = 'php'"); api_parse_php_manual($form_values['api_php_funcsummary']); drupal_set_message(t('Manual pages scanned.')); } function api_reindex_form() { $form = array(); $form['submit'] = array( '#type' => 'submit', '#value' => t('Reindex'), ); return $form; } function api_reindex_form_submit($form_id, $form_values) { db_query("UPDATE {api_file} SET modified = 52"); drupal_set_message(t('All files have been tagged for reindexing. The index will be rebuilt during the next few runs of !cron.', array('!cron' => l('cron.php', 'admin/logs/status/run-cron')))); } /** * Implementation of hook_cron(). */ function api_cron() { include_once(drupal_get_path('module', 'api') .'/parser.inc'); $files_scanned = 0; $max_files = variable_get('api_files_per_cron', 10); db_query("UPDATE {api_file} SET found = 0"); $branches = db_query('SELECT branch_name, directory FROM {api_branch}'); while ($branch = db_fetch_object($branches)) { $files = api_scan_directories($branch->directory); foreach ($files as $path => $file_name) { if ($files_scanned >= $max_files) { break; } $modified = 0; $result = db_query("SELECT f.did, f.modified FROM {api_documentation} d INNER JOIN {api_file} f ON d.did = f.did WHERE d.object_name = '%s' AND d.branch_name = '%s' AND d.object_type = 'file'", $file_name, $branch->branch_name); if ($file = db_fetch_object($result)) { $modified = $file->modified; db_query("UPDATE {api_file} SET found = 1 WHERE did = %d", $file->did); } if (filemtime($path) > $modified) { if (api_parse_file($path, $branch->branch_name, $file_name)) { $files_scanned++; } } } } // Remove outdated files. if ($files_scanned == 0) { $result = db_query("SELECT ad.file_name, ad.branch_name FROM {api_file} af LEFT JOIN {api_documentation} ad ON ad.did = af.did WHERE af.found = 0"); while ($file = db_fetch_object($result)) { watchdog('api', t('Removing %file', array('%file' => $file->file_name))); $doc_result = db_query("SELECT ad.did FROM {api_documentation} ad WHERE ad.file_name = '%s' AND ad.branch_name = '%s'", $file->file_name, $file->branch_name); while ($doc = db_fetch_object($doc_result)) { db_query("DELETE FROM {api_documentation} WHERE did = %d", $doc->did); db_query("DELETE FROM {api_file} WHERE did = %d", $doc->did); db_query("DELETE FROM {api_function} WHERE did = %d", $doc->did); db_query("DELETE FROM {api_reference_storage} WHERE from_did = %d OR to_did = %d", $doc->did, $doc->did); } } } // Update any references that were resolved during this run. api_update_references(); if ($files_scanned != 0) { // Some files were updated, so clear the page cache cache_clear_all(); } } /** * Turn function names into links. */ function api_link_code($code, $branch_name) { return _api_link_documentation($code, $branch_name, array('code function')); } /** * Turn function and file names into links. This is done on output because the * identifiers which are linked frequently change. */ function api_link_documentation($documentation, $branch_name) { return _api_link_documentation($documentation, $branch_name, array('tags', 'function', 'file')); } function _api_link_documentation($documentation, $branch_name, $stages = array()) { $patterns = array( // Find HTML tags, which will not be filtered. 'tags' => '/(<[^>]+?>)/', // Find function names, which are preceded by white space and followed by // '('. 'function' => '!(?<=^|\s)([a-zA-Z0-9_]+)\(!', // Find function names in marked-up code. 'code function' => '!([a-zA-Z0-9_]+)!', // Find file names, which are an arbitrary number of strings joined with // '.' 'file' => '%(?<=^|\s)'. API_RE_FILENAME .'(?=$|\s|[.,:;?!])%e', ); $stage = array_shift($stages); $callback_match = 'api_link_name'; $prepend = ''; $append = ''; switch ($stage) { case 'tags': $callback_match = NULL; break; case 'function': $append = '('; break; case 'code function': $prepend = ''; $append = ''; break; } if (count($stages) > 0) { $callback = '_api_link_documentation'; } else { $callback = NULL; } return api_split($patterns[$stage], $documentation, $callback_match, array($branch_name, $prepend, $append), $callback, array($branch_name, $stages)); } /** * Split a string using a regular expression and process the text using * callbacks. * * @param $pattern * The regular expression to match for splitting. * @param $subject * The string to process. * @param $callback_match * Function name to be called for text which matches $pattern. The first * argument will be the parenthesized expression in the pattern. Should * return a string. NULL to pass the text through unchanged. * @param $callback_match_arguments * An array of additional parameters for $callback_match. * @param $callback * Function name to be called for text which does not match $pattern. The * first argument will be the text. Should return a string. NULL to pass the * text through unchanged. * @param $callback_arguments * An array of additional parameters for $callback. * @return * The original string, with both matched and unmatched portions filtered by * the appropriate callbacks. */ function api_split($pattern, $subject, $callback_match = NULL, $callback_match_arguments = array(), $callback = NULL, $callback_arguments = array()) { $return = ''; $matched = false; foreach (preg_split($pattern, $subject, -1, PREG_SPLIT_DELIM_CAPTURE) as $part) { if ($matched) { if (is_null($callback_match)) { $return .= $part; } else { $return .= call_user_func_array($callback_match, array_merge(array($part), $callback_match_arguments)); } } else { if (is_null($callback)) { $return .= $part; } else { $return .= call_user_func_array($callback, array_merge(array($part), $callback_arguments)); } } $matched = !$matched; } return $return; } /** * Link an object name to its documentation. */ function api_link_name($name, $branch_name, $prepend = '', $append = '') { static $local_objects, $php_functions; if (is_null($local_objects)) { $result = db_query("SELECT object_name, title, object_type, summary FROM {api_documentation} WHERE branch_name = '%s'", $branch_name); $local_objects = array( 'function' => array(), 'file' => array(), 'constant' => array(), ); while ($object = db_fetch_object($result)) { $local_objects[$object->object_type][$object->title] = $object; } } if (is_null($php_functions)) { $result = db_query("SELECT object_name, summary FROM {api_documentation} WHERE branch_name = 'php' AND object_type = 'function'"); $php_functions = array(); while ($function = db_fetch_object($result)) { $php_functions[$function->object_name] = $function->summary; } } if (array_key_exists($name, $local_objects['function'])) { return $prepend . l($name, 'api/function/'. $local_objects['function'][$name]->object_name .'/'. $branch_name, array('title' => $local_objects['function'][$name]->summary, 'class' => 'local')) . $append; } elseif (array_key_exists($name, $local_objects['file'])) { return l($name, 'api/file/'. $local_objects['file'][$name]->object_name .'/'. $branch_name, array('title' => $local_objects['file'][$name]->summary, 'class' => 'local')); } elseif (array_key_exists($name, $local_objects['constant'])) { return l($name, 'api/constant/'. $local_objects['constant'][$name]->object_name .'/'. $branch_name, array('title' => $local_objects['constant'][$name]->summary, 'class' => 'local')); } elseif (array_key_exists($name, $php_functions)) { $link = strtr(variable_get('api_php_funcpath', 'http://php.net/!function'), array('!function' => $name)); return ''. $name .''; } else { return $prepend . $name . $append; } } /** * Find all the files in the directories specified for a branch. */ function api_scan_directories($directories) { $directory_array = explode(':', $directories); if (count($directory_array) > 1) { $directories_components = array(); foreach ($directory_array as $directory) { $directory_components = array(); $parts = explode('/', $directory); foreach ($parts as $part) { if (strlen($part)) { array_unshift($directory_components, reset($directory_components) .'/'. $part); } } $directories_components[] = $directory_components; } $common_ancestor_components = call_user_func_array('array_intersect', $directories_components); $common_ancestor = reset($common_ancestor_components); } else { $common_ancestor = $directories; } $source_files = array(); foreach ($directory_array as $directory) { $files = file_scan_directory($directory, '.*'); foreach ($files as $path => $file) { if (strpos($path, '/.') !== FALSE) { continue; } $file_name = substr($path, strlen($common_ancestor) + 1); $source_files[$path] = $file_name; } } return $source_files; } function api_simpletest() { $dir = drupal_get_path('module', 'api') .'/tests'; return array_keys(file_scan_directory($dir, '\.test$')); }