From 136ca5bb23d15f2d3bbe0451f9d17e217e53861a Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 29 Jan 2020 20:34:49 -0500 Subject: [PATCH 1/1] Use markdown in php docblocks & display in APIv4 Explorer Reformats some docblocks to use markdown, and uses the marked.js library to display them in the APIv4 Explorer. --- CRM/Api4/Page/Api4Explorer.php | 1 + Civi/Api4/ACL.php | 8 ++--- .../Action/Setting/AbstractSettingAction.php | 2 +- Civi/Api4/CustomValue.php | 4 +-- Civi/Api4/Generic/AbstractAction.php | 22 +++++++----- Civi/Api4/Generic/AbstractGetAction.php | 8 ++--- Civi/Api4/Generic/DAOGetAction.php | 4 +-- Civi/Api4/Utils/ReflectionUtils.php | 11 +++--- ang/api4Explorer/Explorer.html | 11 ++---- ang/api4Explorer/Explorer.js | 35 +++++++++++++------ api/api.php | 21 ++++++----- composer.json | 4 +++ composer.lock | 2 +- .../api/v4/Utils/ReflectionUtilsTest.php | 2 +- 14 files changed, 79 insertions(+), 56 deletions(-) diff --git a/CRM/Api4/Page/Api4Explorer.php b/CRM/Api4/Page/Api4Explorer.php index f1fb6f734d..adc1d74326 100644 --- a/CRM/Api4/Page/Api4Explorer.php +++ b/CRM/Api4/Page/Api4Explorer.php @@ -33,6 +33,7 @@ class CRM_Api4_Page_Api4Explorer extends CRM_Core_Page { ->addPermissions(['access debug output']) ->addScriptFile('civicrm', 'js/load-bootstrap.js') ->addScriptFile('civicrm', 'bower_components/js-yaml/dist/js-yaml.min.js') + ->addScriptFile('civicrm', 'bower_components/marked/marked.min.js') ->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js') ->addStyleFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.css'); diff --git a/Civi/Api4/ACL.php b/Civi/Api4/ACL.php index 5ac059f613..8501391d8e 100644 --- a/Civi/Api4/ACL.php +++ b/Civi/Api4/ACL.php @@ -26,11 +26,11 @@ namespace Civi\Api4; * * An ACL record consists of: * - * - an Operation (e.g. 'View' or 'Edit') - * - a set of Data that the operation can be performed on (e.g. a group of contacts) - * - and a Role that has permission to do this operation. + * 1. An Operation (e.g. 'View' or 'Edit'). + * 2. A set of Data that the operation can be performed on (e.g. a group of contacts). + * 3. A Role that has permission to do this operation. * - * Creating a new ACL requires at minimum a entity table, entity ID and object_table. + * Creating a new ACL requires at minimum an entity table, entity ID and object_table. * * @see https://docs.civicrm.org/user/en/latest/initial-set-up/permissions-and-access-control * @package Civi\Api4 diff --git a/Civi/Api4/Action/Setting/AbstractSettingAction.php b/Civi/Api4/Action/Setting/AbstractSettingAction.php index 53b9cdd891..d4a7a7dbe5 100644 --- a/Civi/Api4/Action/Setting/AbstractSettingAction.php +++ b/Civi/Api4/Action/Setting/AbstractSettingAction.php @@ -58,7 +58,7 @@ abstract class AbstractSettingAction extends \Civi\Api4\Generic\AbstractAction { } /** - * Checks that really ought to be taken care of by Civi::settings + * Checks that really ought to be taken care of by `Civi::settings`. * * @param int $domain * @return array diff --git a/Civi/Api4/CustomValue.php b/Civi/Api4/CustomValue.php index 3ed03a8d61..60c6d6c870 100644 --- a/Civi/Api4/CustomValue.php +++ b/Civi/Api4/CustomValue.php @@ -29,8 +29,8 @@ namespace Civi\Api4; * * Each action takes the name of the custom group as a parameter, or in traditional syntax the entity is prefixed with 'Custom_' * - * Ex. OOP: \Civi\Api4\CustomValue::get('MyStuff')->addWhere('id', '=', 123) - * Non-OOP: civicrm_api4('Custom_MyStuff', 'get', ['where' => [['id', '=', 123]]]); + * **Ex. OOP:** `\Civi\Api4\CustomValue::get('MyStuff')->addWhere('id', '=', 123);` + * **Non-OOP:** `civicrm_api4('Custom_MyStuff', 'get', ['where' => [['id', '=', 123]]]);` * * Note: This class does NOT extend AbstractEntity so it doesn't get mistaken for a "real" entity. * @package Civi\Api4 diff --git a/Civi/Api4/Generic/AbstractAction.php b/Civi/Api4/Generic/AbstractAction.php index 310df486e5..d4a0ed9371 100644 --- a/Civi/Api4/Generic/AbstractAction.php +++ b/Civi/Api4/Generic/AbstractAction.php @@ -56,16 +56,21 @@ abstract class AbstractAction implements \ArrayAccess { * * Keys can be any string - this will be the name given to the output. * - * You can reference other values in the api results in this call by prefixing them with $ + * You can reference other values in the api results in this call by prefixing them with `$`. * * For example, you could create a contact and place them in a group by chaining the - * GroupContact api to the Contact api: + * `GroupContact` api to the `Contact` api: * + * ```php * Contact::create() * ->setValue('first_name', 'Hello') - * ->addChain('add_to_a_group', GroupContact::create()->setValue('contact_id', '$id')->setValue('group_id', 123)) + * ->addChain('add_a_group', GroupContact::create() + * ->setValue('contact_id', '$id') + * ->setValue('group_id', 123) + * ) + * ``` * - * This will substitute the id of the newly created contact with $id. + * This will substitute the id of the newly created contact with `$id`. * * @var array */ @@ -84,10 +89,10 @@ abstract class AbstractAction implements \ArrayAccess { /** * Add debugging info to the api result. * - * When enabled, the $result->debug will be populated with information about the api call, + * When enabled, `$result->debug` will be populated with information about the api call, * including sql queries executed. * - * Note: with checkPermissions enabled, debug info will only be returned if the user has "view debug output" permission. + * **Note:** with checkPermissions enabled, debug info will only be returned if the user has "view debug output" permission. * * @var bool */ @@ -175,9 +180,8 @@ abstract class AbstractAction implements \ArrayAccess { * @param string $name * Unique name for this chained request * @param \Civi\Api4\Generic\AbstractAction $apiRequest - * @param string|int $index - * Either a string for how the results should be indexed e.g. 'name' - * or the index of a single result to return e.g. 0 for the first result. + * @param string|int|array $index + * See `civicrm_api4()` for documentation of `$index` param * @return $this */ public function addChain($name, AbstractAction $apiRequest, $index = NULL) { diff --git a/Civi/Api4/Generic/AbstractGetAction.php b/Civi/Api4/Generic/AbstractGetAction.php index 06d3f6c2f8..7a32a54da6 100644 --- a/Civi/Api4/Generic/AbstractGetAction.php +++ b/Civi/Api4/Generic/AbstractGetAction.php @@ -34,12 +34,12 @@ use Civi\Api4\Utils\SelectUtil; abstract class AbstractGetAction extends AbstractQueryAction { /** - * Fields to return. Defaults to all fields ["*"]. + * Fields to return. Defaults to all fields `["*"]`. * * Use the * wildcard by itself to select all available fields, or use it to match similarly-named fields. - * E.g. "is_*" will match fields named is_primary, is_active, etc. + * E.g. `is_*` will match fields named is_primary, is_active, etc. * - * Set to ["row_count"] to return only the number of items found. + * Set to `["row_count"]` to return only the number of items found. * * @var array */ @@ -96,7 +96,7 @@ abstract class AbstractGetAction extends AbstractQueryAction { * * Ex: If getRecords fetches a long list of items each with a unique name, * but the user has specified a single record to retrieve, you can optimize the call - * by checking $this->_itemsToGet('name') and only fetching the item(s) with that name. + * by checking `$this->_itemsToGet('name')` and only fetching the item(s) with that name. * * @param string $field * @return array|null diff --git a/Civi/Api4/Generic/DAOGetAction.php b/Civi/Api4/Generic/DAOGetAction.php index a85682e480..a6968a147f 100644 --- a/Civi/Api4/Generic/DAOGetAction.php +++ b/Civi/Api4/Generic/DAOGetAction.php @@ -32,9 +32,9 @@ class DAOGetAction extends AbstractGetAction { use Traits\DAOActionTrait; /** - * Fields to return. Defaults to all non-custom fields ["*"]. + * Fields to return. Defaults to all non-custom fields `["*"]`. * - * Use the dot notation to perform joins in the select clause, e.g. selecting ["*", "contact.*"] from Email.get + * Use the dot notation to perform joins in the select clause, e.g. selecting `["*", "contact.*"]` from `Email::get()` * will select all fields for the email + all fields for the related contact. * * @var array diff --git a/Civi/Api4/Utils/ReflectionUtils.php b/Civi/Api4/Utils/ReflectionUtils.php index a0d4b799f3..9cb4f6fb5d 100644 --- a/Civi/Api4/Utils/ReflectionUtils.php +++ b/Civi/Api4/Utils/ReflectionUtils.php @@ -68,10 +68,13 @@ class ReflectionUtils { if (!$num || strpos($line, '*/') !== FALSE) { continue; } - $line = preg_replace('/[ ]+/', ' ', ltrim(trim($line), '* ')); - if (strpos($line, '@') === 0) { - $words = explode(' ', $line); - $key = substr(array_shift($words), 1); + $line = ltrim(trim($line), '*'); + if (strlen($line) && $line[0] === ' ') { + $line = substr($line, 1); + } + if (strpos(ltrim($line), '@') === 0) { + $words = explode(' ', ltrim($line, ' @')); + $key = array_shift($words); $param = NULL; if ($key == 'var') { $info['type'] = explode('|', $words[0]); diff --git a/ang/api4Explorer/Explorer.html b/ang/api4Explorer/Explorer.html index bfa66e2750..e924625746 100644 --- a/ang/api4Explorer/Explorer.html +++ b/ang/api4Explorer/Explorer.html @@ -112,15 +112,8 @@

{{ helpTitle }}

-

{{ helpContent.description }}

-
-
-

{{ text }}

-
    -
  • {{ item.substr(1) }}
  • -
-
-
+

+

{{ key }}: {{ item }}

diff --git a/ang/api4Explorer/Explorer.js b/ang/api4Explorer/Explorer.js index 542b84edae..a34f66460a 100644 --- a/ang/api4Explorer/Explorer.js +++ b/ang/api4Explorer/Explorer.js @@ -34,6 +34,7 @@ $scope.perm = { accessDebugOutput: CRM.checkPerm('access debug output') }; + marked.setOptions({highlight: prettyPrintOne}); var getMetaParams = {}, objectParams = {orderBy: 'ASC', values: '', chain: ['Entity', '', '{}']}, docs = CRM.vars.api4.docs, @@ -152,16 +153,33 @@ return fields; } - $scope.help = function(title, param) { - if (!param) { + $scope.help = function(title, content) { + if (!content) { $scope.helpTitle = helpTitle; $scope.helpContent = helpContent; } else { $scope.helpTitle = title; - $scope.helpContent = param; + $scope.helpContent = convertMarkdown(content); } }; + // Sets the static help text (which gets overridden by mousing over other elements) + function setHelp(title, content) { + $scope.helpTitle = helpTitle = title; + $scope.helpContent = helpContent = convertMarkdown(content); + } + + function convertMarkdown(rawContent) { + var formatted = _.cloneDeep(rawContent); + if (formatted.description) { + formatted.description = marked(formatted.description); + } + if (formatted.comment) { + formatted.comment = marked(formatted.comment); + } + return formatted; + } + // Format the href for a @see help annotation $scope.formatRef = function(see) { var match = see.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/); @@ -599,17 +617,15 @@ // Help for an entity with no action selected function showEntityHelp(entityName) { var entityInfo = getEntity(entityName); - $scope.helpTitle = helpTitle = $scope.entity; - $scope.helpContent = helpContent = { + setHelp($scope.entity, { description: entityInfo.description, comment: entityInfo.comment, see: entityInfo.see - }; + }); } if (!$scope.entity) { - $scope.helpTitle = helpTitle = ts('APIv4 Explorer'); - $scope.helpContent = helpContent = {description: docs.description, comment: docs.comment, see: docs.see}; + setHelp(ts('APIv4 Explorer'), {description: docs.description, comment: docs.comment, see: docs.see}); } else if (!actions.length && !getEntity().actions) { getMetaParams.actions = [$scope.entity, 'getActions', {chain: {fields: [$scope.entity, 'getFields', {action: '$name'}]}}]; fetchMeta(); @@ -635,8 +651,7 @@ if ($scope.entity && $routeParams.api4action !== newVal && !_.isUndefined(newVal)) { $location.url('/explorer/' + $scope.entity + '/' + newVal); } else if (newVal) { - $scope.helpTitle = helpTitle = $scope.entity + '::' + newVal; - $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment', 'see']); + setHelp($scope.entity + '::' + newVal, _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment', 'see'])); } }); diff --git a/api/api.php b/api/api.php index 224846279f..1a70bc3921 100644 --- a/api/api.php +++ b/api/api.php @@ -33,12 +33,12 @@ function civicrm_api(string $entity = NULL, string $action, array $params, $extr * @see https://docs.civicrm.org/dev/en/latest/api/v4/usage/ * * @param string $entity Name of the CiviCRM entity to access. - * All entity names are capitalized CamelCase, e.g. "ContributionPage". - * Most entities correspond to a database table (e.g. "Contact" is the table "civicrm_contact"). - * For a complete list of available entities, call civicrm_api4('Entity', 'get'); + * All entity names are capitalized CamelCase, e.g. `ContributionPage`. + * Most entities correspond to a database table (e.g. `Contact` is the table `civicrm_contact`). + * For a complete list of available entities, call `civicrm_api4('Entity', 'get');` * * @param string $action The "verb" of the api call. - * For a complete list of actions for a given entity (e.g. Contact), call civicrm_api4('Contact', 'getActions'); + * For a complete list of actions for a given entity (e.g. `Contact`), call `civicrm_api4('Contact', 'getActions');` * * @param array $params An array of API input keyed by parameter name. * The easiest way to discover all available parameters is to visit the API Explorer on your CiviCRM site. @@ -47,11 +47,14 @@ function civicrm_api(string $entity = NULL, string $action, array $params, $extr * @param string|int|array $index Controls the Result array format. * By default the api Result contains a non-associative array of data. Passing an $index tells the api to * automatically reformat the array, depending on the variable type passed: - * - * - Integer: return a single result array; e.g. index = 0 will return the first result, 1 will return the second, and -1 will return the last. - * - String: index the results by a field value; e.g. index = "name" will return an associative array with the field 'name' as keys. - * - Non-associative array: return a single value from each result; e.g. index = ['title'] will return a non-associative array of strings - the 'title' field from each result. - * - Associative array: a combination of the previous two modes; e.g. index = ['name' => 'title'] will return an array of strings - the 'title' field keyed by the 'name' field. + * - **Integer:** return a single result array; + * e.g. `$index = 0` will return the first result, 1 will return the second, and -1 will return the last. + * - **String:** index the results by a field value; + * e.g. `$index = "name"` will return an associative array with the field 'name' as keys. + * - **Non-associative array:** return a single value from each result; + * e.g. `$index = ['title']` will return a non-associative array of strings - the 'title' field from each result. + * - **Associative array:** a combination of the previous two modes; + * e.g. `$index = ['name' => 'title']` will return an array of strings - the 'title' field keyed by the 'name' field. * * @return \Civi\Api4\Generic\Result * @throws \API_Exception diff --git a/composer.json b/composer.json index 62faa8f79a..b49a224770 100644 --- a/composer.json +++ b/composer.json @@ -194,6 +194,10 @@ "url": "https://github.com/FortAwesome/Font-Awesome/archive/v4.7.0.zip", "ignore": ["*/.*", "*.json", "src", "*.yml", "Gemfile", "Gemfile.lock", "*.md"] }, + "marked": { + "url": "https://github.com/markedjs/marked/archive/v0.8.0.zip", + "ignore": [".*", "*.json", "*.md", "Makefile", "*/*"] + }, "google-code-prettify": { "url": "https://github.com/tcollard/google-code-prettify/archive/v1.0.5.zip", "ignore": ["closure-compiler", "js-modules", "tests", "yui-compressor", "Makefile"] diff --git a/composer.lock b/composer.lock index cd6eedbff3..1265218ed3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3fb18d6fdb3244cc94be8eaa883b7ae", + "content-hash": "36eeb97f14e2af530920b89c59124de3", "packages": [ { "name": "civicrm/civicrm-cxn-rpc", diff --git a/tests/phpunit/api/v4/Utils/ReflectionUtilsTest.php b/tests/phpunit/api/v4/Utils/ReflectionUtilsTest.php index 8e02868a53..494e4d5993 100644 --- a/tests/phpunit/api/v4/Utils/ReflectionUtilsTest.php +++ b/tests/phpunit/api/v4/Utils/ReflectionUtilsTest.php @@ -86,7 +86,7 @@ This is the base class.'; '$foo' => [ 'type' => ['int', 'string'], 'description' => '', - 'comment' => "Nothing interesting.\n", + 'comment' => " Nothing interesting.\n", ], '$bar' => [ 'type' => NULL, -- 2.25.1