* @return string
*/
public function run() {
- CRM_Utils_System::setTitle('CiviCRM API');
CRM_Core_Resources::singleton()
->addScriptFile('civicrm', 'templates/CRM/Admin/Page/APIExplorer.js')
->addScriptUrl('//cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js', 99)
}
CRM_Utils_System::civiExit();
}
+ CRM_Utils_System::permissionDenied();
+ }
+
+ /**
+ * Ajax callback to display code docs
+ */
+ public static function getDoc() {
+ if (!empty($_GET['entity']) && strpos($_GET['entity'], '.') === FALSE) {
+ $entity = _civicrm_api_get_camel_name($_GET['entity']);
+ $action = CRM_Utils_Array::value('action', $_GET);
+ $doc = self::getDocblock($entity, $action);
+ $result = array(
+ 'doc' => $doc ? self::formatDocBlock($doc[0]) : 'Not found.',
+ 'code' => $doc ? $doc[1] : NULL,
+ );
+ if (!$action) {
+ $actions = civicrm_api3($entity, 'getactions');
+ $result['actions'] = CRM_Utils_Array::makeNonAssociative(array_combine($actions['values'], $actions['values']));
+ }
+ CRM_Utils_JSON::output($result);
+ }
+ CRM_Utils_System::permissionDenied();
+ }
+
+ /**
+ * @param string $entity
+ * @param string|null $action
+ * @return array|bool
+ * [docblock, code]
+ */
+ private static function getDocBlock($entity, $action) {
+ if (!$entity) {
+ return FALSE;
+ }
+ $contents = file_get_contents("api/v3/$entity.php", FILE_USE_INCLUDE_PATH);
+ if (!$contents) {
+ // Api does not exist
+ return FALSE;
+ }
+ $docblock = $code = array();
+ // Fetch docblock for the api file
+ if (!$action) {
+ if (preg_match('#/\*\*\n.*?\n \*/\n#s', $contents, $docblock)) {
+ return array($docblock[0], NULL);
+ }
+ }
+ // Fetch block for a specific action
+ else {
+ $action = strtolower($action);
+ $fnName = 'civicrm_api3_' . _civicrm_api_get_entity_name_from_camel($entity) . '_' . $action;
+ // Support the alternate "1 file per action" structure
+ $actionFile = file_get_contents("api/v3/$entity/" . ucfirst($action) . '.php', FILE_USE_INCLUDE_PATH);
+ if ($actionFile) {
+ $contents = $actionFile;
+ }
+ // If action isn't in this file, try generic
+ if (strpos($contents, $fnName) === FALSE) {
+ $fnName = "civicrm_api3_generic_$action";
+ $contents = file_get_contents("api/v3/Generic/" . ucfirst($action) . '.php', FILE_USE_INCLUDE_PATH);
+ if (!$contents) {
+ $contents = file_get_contents("api/v3/Generic.php", FILE_USE_INCLUDE_PATH);
+ }
+ }
+ if (preg_match('#(/\*\*(\n \*.*)*\n \*/\n)function[ ]+' . $fnName . '#i', $contents, $docblock)) {
+ // Fetch the code in a separate regex to preserve sanity
+ preg_match("#^function[ ]+$fnName.*?^}#ism", $contents, $code);
+ return array($docblock[1], $code[0]);
+ }
+ }
+ }
+
+ /**
+ * Format a docblock to be a bit more readable
+ * Not a proper doc parser... patches welcome :)
+ *
+ * @param string $text
+ * @return string
+ */
+ private static function formatDocBlock($text) {
+ // Get rid of comment stars
+ $text = str_replace(array("\n * ", "\n *\n", "\n */\n", "/**\n"), array("\n", "\n\n", '', ''), $text);
+
+ // Format for html
+ $text = htmlspecialchars($text);
+
+ // Extract code blocks - save for later to skip html conversion
+ $code = array();
+ preg_match_all('#@code(.*?)@endcode#is', $text, $code);
+ $text = preg_replace('#@code.*?@endcode#is', '<pre></pre>', $text);
+
+ // Convert @annotations to titles
+ $text = preg_replace_callback(
+ '#^[ ]*@(\w+)([ ]*)#m',
+ function($matches) {
+ return "<strong>" . ucfirst($matches[1]) . "</strong>" . (empty($matches[2]) ? '' : ': ');
+ },
+ $text);
+
+ // Preserve indentation
+ $text = str_replace("\n ", "\n ", $text);
+
+ // Convert newlines
+ $text = nl2br($text);
+
+ // Add unformatted code blocks back in
+ if ($code && !empty($code[1])) {
+ foreach ($code[1] as $block) {
+ $text = preg_replace('#<pre></pre>#', "<pre class='prettyprint'>$block</pre>", $text, 1);
+ }
+ }
+ return $text;
}
}
<page_callback>CRM_Admin_Page_APIExplorer::getExampleFile</page_callback>
<access_arguments>access CiviCRM</access_arguments>
</item>
+ <item>
+ <path>civicrm/ajax/apidoc</path>
+ <page_callback>CRM_Admin_Page_APIExplorer::getDoc</page_callback>
+ <access_arguments>access CiviCRM</access_arguments>
+ </item>
<item>
<path>civicrm/ajax/rest</path>
<page_callback>CRM_Utils_REST::ajax</page_callback>
optionsTpl = _.template($('#api-options-tpl').html()),
returnTpl = _.template($('#api-return-tpl').html()),
chainTpl = _.template($('#api-chain-tpl').html()),
+ docCodeTpl = _.template($('#doc-code-tpl').html()),
// These types of entityRef don't require any input to open
OPEN_IMMEDIATELY = ['RelationshipType', 'Event', 'Group', 'Tag'],
}
}
+ /**
+ * Fetch entity docs & actions
+ */
+ function getDocEntity() {
+ CRM.utils.setOptions($('#doc-action').prop('disabled', true).addClass('loading'), []);
+ $.getJSON(CRM.url('civicrm/ajax/apidoc', {entity: $(this).val()}))
+ .done(function(result) {
+ CRM.utils.setOptions($('#doc-action').prop('disabled', false).removeClass('loading'), result.actions);
+ $('#doc-result').html(result.doc);
+ prettyPrint();
+ });
+ }
+
+ /**
+ * Fetch entity+action docs & code
+ */
+ function getDocAction() {
+ var
+ entity = $('#doc-entity').val(),
+ action = $('#doc-action').val();
+ if (entity && action) {
+ $('#doc-result').block();
+ $.get(CRM.url('civicrm/ajax/apidoc', {entity: entity, action: action}))
+ .done(function(result) {
+ $('#doc-result').unblock().html(result.doc);
+ if (result.code) {
+ $('#doc-result').append(docCodeTpl(result));
+ }
+ prettyPrint();
+ });
+ } else {
+ $('#doc-result').html($('#doc-result').attr('title'));
+ }
+ }
+
$(document).ready(function() {
// Set up tabs - bind active tab to document hash because... it's cool?
document.location.hash = document.location.hash || 'explorer';
document.location.hash = ui.newPanel.attr('id').replace('-tab', '');
}
});
+ $(window).on('hashchange', function() {
+ $('#mainTabContainer').tabs('option', 'active', $(document.location.hash + '-tab').index() - 1);
+ });
// Initialize widgets
- $('#api-entity, #example-entity').crmSelect2({
+ $('#api-entity, #example-entity, #doc-entity').crmSelect2({
// Add strikethough class to selection to indicate deprecated apis
formatSelection: function(option) {
return $(option.element).hasClass('strikethrough') ? '<span class="strikethrough">' + option.text + '</span>' : option.text;
.on('change', 'select.api-chain-entity', getChainedAction);
$('#example-entity').on('change', getExamples);
$('#example-action').on('change', getExample);
+ $('#doc-entity').on('change', getDocEntity);
+ $('#doc-action').on('change', getDocAction);
$('#api-params-add').on('click', function(e) {
e.preventDefault();
addField();
margin-bottom: .6em;
}
pre#api-result,
+ div#doc-result,
pre#example-result {
padding:1em;
max-height: 50em;
pre ol.linenums li:hover {
color: #9c9c9c;
}
+ .api-doc-code {
+ margin-top: 1em;
+ border-top: 1px solid #d3d3d3;
+ }
+ .api-doc-code .collapsible-title {
+ font-weight: bold;
+ margin-top: .5em;
+ }
{/literal}
</style>
<div id="mainTabContainer">
<ul>
- <li class="ui-corner-all"><a href="#explorer-tab">{ts}Explorer{/ts}</a></li>
- <li class="ui-corner-all"><a href="#examples-tab">{ts}Examples{/ts}</a></li>
+ <li class="ui-corner-all" title="GUI to build and execute API calls">
+ <a href="#explorer-tab">{ts}Explorer{/ts}</a>
+ </li>
+ <li class="ui-corner-all" title="Auto-generated examples from the test suite">
+ <a href="#examples-tab">{ts}Examples{/ts}</a>
+ </li>
+ <li class="ui-corner-all" title="API source-code and code-level documentation">
+ <a href="#docs-tab">{ts}Code Docs{/ts}</a>
+ </li>
</ul>
<div id="explorer-tab">
</pre>
</form>
</div>
+
+ <div id="docs-tab">
+ <form id="api-docs">
+ <label for="doc-entity">{ts}Entity{/ts}:</label>
+ <select class="crm-form-select big required" id="doc-entity" name="entity">
+ <option value="" selected="selected">{ts}Choose{/ts}...</option>
+ {foreach from=$entities.values item=entity}
+ <option value="{$entity}" {if !empty($entities.deprecated) && in_array($entity, $entities.deprecated)}class="strikethrough"{/if}>
+ {$entity}
+ </option>
+ {/foreach}
+ </select>
+
+ <label for="doc-action">{ts}Action{/ts}:</label>
+ <select class="crm-form-select big crm-select2" id="doc-action" name="action">
+ <option value="" selected="selected">{ts}Choose{/ts}...</option>
+ </select>
+ <div id="doc-result" title="{ts escape='html'}Results are displayed here.{/ts}">
+ {ts}Results are displayed here.{/ts}
+ </div>
+ </form>
+ </div>
</div>
{strip}
</td>
</tr>
</script>
+
+<script type="text/template" id="doc-code-tpl">
+ <div class="crm-collapsible collapsed api-doc-code">
+ <div class="collapsible-title">{ts}Source Code{/ts}</div>
+ <pre class="prettyprint lang-php linenums"><%- code %></pre>
+ </div>
+</script>
{/strip}