APIv4 - Variable substution in docblocks
authorColeman Watts <coleman@civicrm.org>
Fri, 31 Jan 2020 22:06:41 +0000 (17:06 -0500)
committerColeman Watts <coleman@civicrm.org>
Sat, 1 Feb 2020 19:01:53 +0000 (14:01 -0500)
Replaces $ENTITY and $ACTION in docblocks to improve help text in the api explorer.

28 files changed:
Civi/Api4/Action/Entity/Get.php
Civi/Api4/Action/GetActions.php
Civi/Api4/Generic/AbstractAction.php
Civi/Api4/Generic/AbstractBatchAction.php
Civi/Api4/Generic/AbstractCreateAction.php
Civi/Api4/Generic/AbstractGetAction.php
Civi/Api4/Generic/AbstractQueryAction.php
Civi/Api4/Generic/AbstractSaveAction.php
Civi/Api4/Generic/AbstractUpdateAction.php
Civi/Api4/Generic/BasicBatchAction.php
Civi/Api4/Generic/BasicCreateAction.php
Civi/Api4/Generic/BasicGetAction.php
Civi/Api4/Generic/BasicGetFieldsAction.php
Civi/Api4/Generic/BasicReplaceAction.php
Civi/Api4/Generic/BasicSaveAction.php
Civi/Api4/Generic/BasicUpdateAction.php
Civi/Api4/Generic/DAOCreateAction.php
Civi/Api4/Generic/DAODeleteAction.php
Civi/Api4/Generic/DAOGetAction.php
Civi/Api4/Generic/DAOSaveAction.php
Civi/Api4/Generic/DAOUpdateAction.php
Civi/Api4/Utils/ReflectionUtils.php
ang/api4Explorer/Explorer.html
ang/api4Explorer/Explorer.js
tests/phpunit/api/v4/Entity/ParticipantTest.php
tests/phpunit/api/v4/Mock/MockV4ReflectionBase.php
tests/phpunit/api/v4/Mock/MockV4ReflectionGrandchild.php
tests/phpunit/api/v4/Utils/ReflectionUtilsTest.php

index 37e59f2edc7ab0aca99360abfebd804b535db308..b8e0763ee2ee76d4eea187dea2c4f48420c0e4ed 100644 (file)
@@ -132,7 +132,7 @@ class Get extends \Civi\Api4\Generic\BasicGetAction {
    */
   private function addDocs(&$entity) {
     $reflection = new \ReflectionClass("\\Civi\\Api4\\" . $entity['name']);
-    $entity += ReflectionUtils::getCodeDocs($reflection);
+    $entity += ReflectionUtils::getCodeDocs($reflection, NULL, ['$ENTITY' => $entity['name']]);
     unset($entity['package'], $entity['method']);
   }
 
index 46dae610314aaeb00ad368b74a7e7fd039b07c4b..0ce201b7972748f991930c17bf52a2b23ce43f8f 100644 (file)
@@ -27,7 +27,9 @@ use Civi\Api4\Utils\ActionUtil;
 use Civi\Api4\Utils\ReflectionUtils;
 
 /**
- * Get actions for an entity with a list of accepted params
+ * Get all API actions for the $ENTITY entity.
+ *
+ * Includes a list of accepted parameters for each action, descriptions and other documentation.
  */
 class GetActions extends BasicGetAction {
 
@@ -86,13 +88,18 @@ class GetActions extends BasicGetAction {
         if (is_object($action)) {
           $this->_actions[$actionName] = ['name' => $actionName];
           if ($this->_isFieldSelected('description', 'comment', 'see')) {
+            $vars = ['$ENTITY' => $this->getEntityName(), '$ACTION' => $actionName];
             // Docblock from action class
-            $actionDocs = ReflectionUtils::getCodeDocs($action->reflect());
+            $actionDocs = ReflectionUtils::getCodeDocs($action->reflect(), NULL, $vars);
             unset($actionDocs['method']);
             // Docblock from action factory function in entity class. This takes precedence since most action classes are generic.
             if ($method) {
-              $methodDocs = ReflectionUtils::getCodeDocs($method, 'Method');
-              $actionDocs = $methodDocs + $actionDocs;
+              $methodDocs = ReflectionUtils::getCodeDocs($method, 'Method', $vars);
+              // Allow method doc to inherit class doc
+              if (strpos($method->getDocComment(), '@inheritDoc') !== FALSE && !empty($methodDocs['comment']) && !empty($actionDocs['comment'])) {
+                $methodDocs['comment'] .= "\n\n" . $actionDocs['comment'];
+              }
+              $actionDocs = array_filter($methodDocs) + $actionDocs;
             }
             $this->_actions[$actionName] += $actionDocs;
           }
index d4a0ed93711dc61ef9b4239d33398a83ccfc15d8..5138c1c87ad2d07f85c9eff2de5307e9fefba72b 100644 (file)
@@ -270,10 +270,19 @@ abstract class AbstractAction implements \ArrayAccess {
   public function getParamInfo($param = NULL) {
     if (!isset($this->_paramInfo)) {
       $defaults = $this->getParamDefaults();
+      $vars = [
+        '$ENTITY' => $this->getEntityName(),
+        '$ACTION' => $this->getActionName(),
+      ];
+      // For actions like "getFields" and "getActions" they are not getting the entity itself.
+      // So generic docs will make more sense like this:
+      if (substr($vars['$ACTION'], 0, 3) === 'get' && substr($vars['$ACTION'], -1) === 's') {
+        $vars['$ENTITY'] = lcfirst(substr($vars['$ACTION'], 3, -1));
+      }
       foreach ($this->reflect()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
         $name = $property->getName();
         if ($name != 'version' && $name[0] != '_') {
-          $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property');
+          $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property', $vars);
           $this->_paramInfo[$name]['default'] = $defaults[$name];
         }
       }
index 05c078b5870ba911a644b92dc09e293983b8df6d..89fe786eea5a36bfcb9ef0ed1cf376bc3b73726d 100644 (file)
@@ -31,7 +31,7 @@ namespace Civi\Api4\Generic;
 abstract class AbstractBatchAction extends AbstractQueryAction {
 
   /**
-   * Criteria for selecting items to process.
+   * Criteria for selecting $ENTITYs to process.
    *
    * @var array
    * @required
index 416e15f056268105faea080ed17fe6f806437bea..ea9d3cdb9d2f2b758667ebe0bda9ae54f6bdbe6c 100644 (file)
@@ -22,7 +22,7 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Base class for all "Create" api actions.
+ * Base class for all `Create` api actions.
  *
  * @method $this setValues(array $values) Set all field values from an array of key => value pairs.
  * @method array getValues() Get field values.
@@ -32,7 +32,7 @@ namespace Civi\Api4\Generic;
 abstract class AbstractCreateAction extends AbstractAction {
 
   /**
-   * Field values to set
+   * Field values to set for the new $ENTITY.
    *
    * @var array
    */
index 7a32a54da68d081091e85f744b061504d3d42c6d..0f4f37e3959aed47e82660774c0f440f3b3c1728 100644 (file)
@@ -24,7 +24,7 @@ namespace Civi\Api4\Generic;
 use Civi\Api4\Utils\SelectUtil;
 
 /**
- * Base class for all "Get" api actions.
+ * Base class for all `Get` api actions.
  *
  * @package Civi\Api4\Generic
  *
@@ -34,12 +34,12 @@ use Civi\Api4\Utils\SelectUtil;
 abstract class AbstractGetAction extends AbstractQueryAction {
 
   /**
-   * Fields to return. Defaults to all fields `["*"]`.
+   * Fields to return for each $ENTITY. 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.
    *
-   * Set to `["row_count"]` to return only the number of items found.
+   * Set to `["row_count"]` to return only the number of $ENTITYs found.
    *
    * @var array
    */
index 96d442ecc805fc8d316b00aec3bf57e251c33021..74aa495b82c2a66f6a0b39df9da673fe1dc368d7 100644 (file)
@@ -22,7 +22,7 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Base class for all actions that need to fetch records (Get, Update, Delete, etc)
+ * Base class for all actions that need to fetch records (`Get`, `Update`, `Delete`, etc.).
  *
  * @package Civi\Api4\Generic
  *
@@ -38,27 +38,29 @@ namespace Civi\Api4\Generic;
 abstract class AbstractQueryAction extends AbstractAction {
 
   /**
-   * Criteria for selecting items.
-   *
-   * $example->addWhere('contact_type', 'IN', array('Individual', 'Household'))
+   * Criteria for selecting $ENTITYs.
    *
+   * ```php
+   * $example->addWhere('contact_type', 'IN', ['Individual', 'Household'])
+   * ```
    * @var array
    */
   protected $where = [];
 
   /**
-   * Array of field(s) to use in ordering the results
+   * Array of field(s) to use in ordering the results.
    *
    * Defaults to id ASC
    *
+   * ```php
    * $example->addOrderBy('sort_name', 'ASC')
-   *
+   * ```
    * @var array
    */
   protected $orderBy = [];
 
   /**
-   * Maximum number of results to return.
+   * Maximum number of $ENTITYs to return.
    *
    * Defaults to unlimited.
    *
@@ -70,9 +72,9 @@ abstract class AbstractQueryAction extends AbstractAction {
   protected $limit = 0;
 
   /**
-   * Zero-based index of first result to return.
+   * Zero-based index of first $ENTITY to return.
    *
-   * Defaults to "0" - first record.
+   * Defaults to "0" - first $ENTITY found.
    *
    * @var int
    */
index a3cc1327679e0d7df08c05a4c6faa40810376773..062beb792661623d37a7f37c6a17caa33ee58ad2 100644 (file)
@@ -22,7 +22,7 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Base class for all "Save" api actions.
+ * Base class for all `Save` api actions.
  *
  * @method $this setRecords(array $records) Set array of records to be saved.
  * @method array getRecords()
@@ -36,9 +36,9 @@ namespace Civi\Api4\Generic;
 abstract class AbstractSaveAction extends AbstractAction {
 
   /**
-   * Array of records.
+   * Array of $ENTITYs to save.
    *
-   * Should be in the same format as returned by Get.
+   * Should be in the same format as returned by `Get`.
    *
    * @var array
    * @required
@@ -48,18 +48,18 @@ abstract class AbstractSaveAction extends AbstractAction {
   /**
    * Array of default values.
    *
-   * These defaults will be applied to all records unless they specify otherwise.
+   * These defaults will be applied to all $ENTITYs unless they specify otherwise.
    *
    * @var array
    */
   protected $defaults = [];
 
   /**
-   * Reload records after saving.
+   * Reload $ENTITYs after saving.
    *
-   * By default this api typically returns partial records containing only the fields
-   * that were updated. Set reload to TRUE to do an additional lookup after saving
-   * to return complete records.
+   * By default this action typically returns partial records containing only the fields
+   * that were updated. Set `reload` to `true` to do an additional lookup after saving
+   * to return complete values for every $ENTITY.
    *
    * @var bool
    */
index b3fe4d86352729be0c2e0eeaa373af6c9fdcf2e6..6c6ca176cbbbd61d021eff9b69b8e4198b0383b3 100644 (file)
@@ -22,7 +22,7 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Base class for all "Update" api actions
+ * Base class for all `Update` api actions
  *
  * @method $this setValues(array $values) Set all field values from an array of key => value pairs.
  * @method array getValues() Get field values.
@@ -42,10 +42,10 @@ abstract class AbstractUpdateAction extends AbstractBatchAction {
   protected $values = [];
 
   /**
-   * Reload objects after saving.
+   * Reload $ENTITYs after saving.
    *
-   * Setting to TRUE will load complete records and return them as the api result.
-   * If FALSE the api usually returns only the fields specified to be updated.
+   * Setting to `true` will load complete records and return them as the api result.
+   * If `false` the api usually returns only the fields specified to be updated.
    *
    * @var bool
    */
@@ -61,7 +61,8 @@ abstract class AbstractUpdateAction extends AbstractBatchAction {
   }
 
   /**
-   * Add an item to the values array
+   * Add an item to the values array.
+   *
    * @param string $fieldName
    * @param mixed $value
    * @return $this
index 259b68cbe87a97da93942465eb9a9be9bbcc230c..860e4273a52f0b460669825b8e5db20eb3497dd6 100644 (file)
@@ -24,12 +24,9 @@ namespace Civi\Api4\Generic;
 use Civi\API\Exception\NotImplementedException;
 
 /**
- * Basic action for deleting or performing some other task with a set of records.  Ex:
+ * $ACTION one or more $ENTITYs.
  *
- * $myAction = new BasicBatchAction('Entity', 'action', function($item) {
- *   // Do something with $item
- *   $return $item;
- * });
+ * $ENTITYs are selected based on criteria specified in `where` parameter (required).
  *
  * @package Civi\Api4\Generic
  */
@@ -45,6 +42,13 @@ class BasicBatchAction extends AbstractBatchAction {
   /**
    * BasicBatchAction constructor.
    *
+   * ```php
+   * $myAction = new BasicBatchAction($entityName, $actionName, function($item) {
+   *   // Do something with $item
+   *   $return $item;
+   * });
+   * ```
+   *
    * @param string $entityName
    * @param string $actionName
    * @param string|array $select
index ff26bd315c56896fc387226c9bde80909cc5488c..e06c869423f4100e9a56b289a82fbc19f885bff6 100644 (file)
@@ -24,9 +24,10 @@ namespace Civi\Api4\Generic;
 use Civi\API\Exception\NotImplementedException;
 
 /**
- * Create a new object from supplied values.
+ * Create a new $ENTITY from supplied values.
  *
- * This function will create 1 new object. It cannot be used to update existing objects. Use the Update or Replace actions for that.
+ * This action will create 1 new $ENTITY.
+ * It cannot be used to update existing $ENTITYs; use the `Update` or `Replace` actions for that.
  */
 class BasicCreateAction extends AbstractCreateAction {
 
index 67f3b0a0ed37b79ee9d3c611b5379d612e0e5a71..0a9e047daaff6497bedff31387a3ad0f84bd7865 100644 (file)
@@ -24,9 +24,9 @@ namespace Civi\Api4\Generic;
 use Civi\API\Exception\NotImplementedException;
 
 /**
- * Retrieve items based on criteria specified in the 'where' param.
+ * Retrieve $ENTITYs based on criteria specified in the `where` parameter.
  *
- * Use the 'select' param to determine which fields are returned, defaults to *.
+ * Use the `select` param to determine which fields are returned, defaults to `[*]`.
  */
 class BasicGetAction extends AbstractGetAction {
   use Traits\ArrayQueryActionTrait;
index 19f548e6a06a3b97b8ec889aaaeab1caf374307e..ae11f65f48269ed543cda5e12f14d2630eea5c24 100644 (file)
@@ -25,12 +25,12 @@ use Civi\API\Exception\NotImplementedException;
 use Civi\Api4\Utils\ActionUtil;
 
 /**
- * Get information about an entity's fields.
+ * Lists information about fields for the $ENTITY entity.
  *
  * This field information is also known as "metadata."
  *
  * Note that different actions may support different lists of fields.
- * By default this will fetch the field list relevant to Get,
+ * By default this will fetch the field list relevant to `get`,
  * but a different list may be returned if you specify another action.
  *
  * @method $this setLoadOptions(bool $value)
index 31f12a44acad344f76a851bbf1b7fcee2da55620..8ae267cf597e24d8fbafc20ebfe11bd0f78545ba 100644 (file)
@@ -25,11 +25,18 @@ use Civi\API\Exception\NotImplementedException;
 use Civi\Api4\Utils\ActionUtil;
 
 /**
- * Given a set of records, will appropriately update the database.
+ * Replaces an existing set of $ENTITYs with a new one.
  *
- * @method $this setRecords(array $records) Array of records.
+ * This will select a group of existing $ENTITYs based on the `where` parameter.
+ * Each will be compared with the $ENTITYs passed in as `records`:
+ *
+ *  - $ENTITYs in `records` that don't already exist will be created.
+ *  - Existing $ENTITYs that are included in `records` will be updated.
+ *  - Existing $ENTITYs that are omitted from `records` will be deleted.
+ *
+ * @method $this setRecords(array $records) Set array of records.
  * @method array getRecords()
- * @method $this setDefaults(array $defaults) Array of defaults.
+ * @method $this setDefaults(array $defaults) Set array of defaults.
  * @method array getDefaults()
  * @method $this setReload(bool $reload) Specify whether complete objects will be returned after saving.
  * @method bool getReload()
@@ -37,9 +44,9 @@ use Civi\Api4\Utils\ActionUtil;
 class BasicReplaceAction extends AbstractBatchAction {
 
   /**
-   * Array of records.
+   * Array of $ENTITY records.
    *
-   * Should be in the same format as returned by Get.
+   * Should be in the same format as returned by `Get`.
    *
    * @var array
    * @required
@@ -49,18 +56,21 @@ class BasicReplaceAction extends AbstractBatchAction {
   /**
    * Array of default values.
    *
-   * Will be merged into $records before saving.
+   * Will be merged into `records` before saving.
+   *
+   * **Note:** Values from the `where` clause that use the `=` operator are _also_ saved into each record;
+   * those do not need to be repeated here.
    *
    * @var array
    */
   protected $defaults = [];
 
   /**
-   * Reload records after saving.
+   * Reload $ENTITYs after saving.
    *
-   * By default this api typically returns partial records containing only the fields
-   * that were updated. Set reload to TRUE to do an additional lookup after saving
-   * to return complete records.
+   * By default this action typically returns partial records containing only the fields
+   * that were updated. Set `reload` to `true` to do an additional lookup after saving
+   * to return complete values for every $ENTITY.
    *
    * @var bool
    */
index db08fb95a6ab92f7ec08cc8d134ae1b76c99e09d..ba4c1a12df133acfaa834b30007b5fcc8d5228f8 100644 (file)
@@ -25,11 +25,11 @@ use Civi\API\Exception\NotImplementedException;
 use Civi\Api4\Utils\ActionUtil;
 
 /**
- * Create or update one or more records.
+ * $ACTION one or more $ENTITYs.
  *
- * If creating more than one record with similar values, use the "defaults" param.
+ * If saving more than one new $ENTITY with similar values, use the `defaults` parameter.
  *
- * Set "reload" if you need the api to return complete records.
+ * Set `reload` if you need the api to return complete $ENTITY records.
  */
 class BasicSaveAction extends AbstractSaveAction {
 
index 7e91570bdf9aa57ddc06cb791305b7f59b164d7c..a7c8865f8ccc7bb5bca1cab08c84781c4f171667 100644 (file)
@@ -24,9 +24,9 @@ namespace Civi\Api4\Generic;
 use Civi\API\Exception\NotImplementedException;
 
 /**
- * Update one or more records with new values.
+ * Update one or more $ENTITY with new values.
  *
- * Use the where clause (required) to select them.
+ * Use the `where` clause (required) to select them.
  */
 class BasicUpdateAction extends AbstractUpdateAction {
 
index 36b19a0a6dc25545081d819aa2e662e6cfd837f1..59436c0d99d30656f04e00da87323b254dd459e9 100644 (file)
 namespace Civi\Api4\Generic;
 
 /**
- * Create a new object from supplied values.
+ * Create a new $ENTITY from supplied values.
  *
- * This function will create 1 new object. It cannot be used to update existing objects. Use the Update or Replace actions for that.
+ * This action will create 1 new $ENTITY.
+ * It cannot be used to update existing $ENTITYs; use the `Update` or `Replace` actions for that.
  */
 class DAOCreateAction extends AbstractCreateAction {
   use Traits\DAOActionTrait;
index 7398fdf3cb3c7a6c13faa85533b7c1aa0d00af6c..83b39a38bf725364d4cde90c3846100fc2d57db2 100644 (file)
@@ -22,7 +22,9 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Delete one or more items, based on criteria specified in Where param (required).
+ * Delete one or more $ENTITYs.
+ *
+ * $ENTITYs are deleted based on criteria specified in `where` parameter (required).
  */
 class DAODeleteAction extends AbstractBatchAction {
   use Traits\DAOActionTrait;
index a6968a147f5eedd3b058f9b787cb19e3a68f008d..da97a68d40146f5aede6a509156834b9fc1abf36 100644 (file)
@@ -22,9 +22,9 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Retrieve items based on criteria specified in the 'where' param.
+ * Retrieve $ENTITYs based on criteria specified in the `where` parameter.
  *
- * Use the 'select' param to determine which fields are returned, defaults to *.
+ * Use the `select` param to determine which fields are returned, defaults to `[*]`.
  *
  * Perform joins on other related entities using a dot notation.
  */
@@ -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
index cd91e53be1dbfeed9d13c0395d5ca2865d7cdc47..ab4888a38adc08d5c11af659fefca2d19eaa0c16 100644 (file)
 namespace Civi\Api4\Generic;
 
 /**
- * Create or update one or more records.
+ * Create or update one or more $ENTITYs.
  *
- * If creating more than one record with similar values, use the "defaults" param.
+ * If creating more than one $ENTITY with similar values, use the `defaults` param.
  *
- * Set "reload" if you need the api to return complete records.
+ * Set `reload` if you need the api to return complete records for each saved $ENTITY.
  */
 class DAOSaveAction extends AbstractSaveAction {
   use Traits\DAOActionTrait;
index feff2f414ba6b198fb36a049adab6059b8103681..b2768ee4d18f082524b4c7a5b6797b63526b8b55 100644 (file)
@@ -22,9 +22,9 @@
 namespace Civi\Api4\Generic;
 
 /**
- * Update one or more records with new values.
+ * Update one or more $ENTITY with new values.
  *
- * Use the where clause (required) to select them.
+ * Use the `where` clause (required) to select them.
  */
 class DAOUpdateAction extends AbstractUpdateAction {
   use Traits\DAOActionTrait;
index 9cb4f6fb5dd91c90ff534060c182a99753ddc682..4ff7d9775e11b8cf7e2ee69bff619464eaf5963a 100644 (file)
@@ -20,11 +20,16 @@ class ReflectionUtils {
    * @param \Reflector|\ReflectionClass $reflection
    * @param string $type
    *   If we are not reflecting the class itself, specify "Method", "Property", etc.
-   *
+   * @param array $vars
+   *   Variable substitutions to perform in the docblock
    * @return array
    */
-  public static function getCodeDocs($reflection, $type = NULL) {
-    $docs = self::parseDocBlock($reflection->getDocComment());
+  public static function getCodeDocs($reflection, $type = NULL, $vars = []) {
+    $comment = $reflection->getDocComment();
+    if ($vars) {
+      $comment = str_replace(array_keys($vars), array_values($vars), $comment);
+    }
+    $docs = self::parseDocBlock($comment);
 
     // Recurse into parent functions
     if (isset($docs['inheritDoc']) || isset($docs['inheritdoc'])) {
@@ -47,7 +52,7 @@ class ReflectionUtils {
       }
       if ($newReflection) {
         // Mix in
-        $additionalDocs = self::getCodeDocs($newReflection, $type);
+        $additionalDocs = self::getCodeDocs($newReflection, $type, $vars);
         if (!empty($docs['comment']) && !empty($additionalDocs['comment'])) {
           $docs['comment'] .= "\n\n" . $additionalDocs['comment'];
         }
@@ -108,19 +113,26 @@ class ReflectionUtils {
         $info['params'][$param]['comment'] .= $line . "\n";
       }
       elseif ($num == 1) {
-        $info['description'] = $line;
+        $info['description'] = ucfirst($line);
       }
       elseif (!$line) {
         if (isset($info['comment'])) {
           $info['comment'] .= "\n";
         }
+        else {
+          $info['comment'] = NULL;
+        }
+      }
+      // For multi-line description.
+      elseif (count($info) === 1 && isset($info['description']) && substr($info['description'], -1) !== '.') {
+        $info['description'] .= ' ' . $line;
       }
       else {
         $info['comment'] = isset($info['comment']) ? "{$info['comment']}\n$line" : $line;
       }
     }
     if (isset($info['comment'])) {
-      $info['comment'] = trim($info['comment']);
+      $info['comment'] = rtrim($info['comment']);
     }
     return $info;
   }
index e9246257462a3392a92a211811432a0701a49b59..adb55b0807af99cc135df87fbff48fb4be688490 100644 (file)
           <div ng-if="helpContent.see">
             <strong>See:</strong>
             <ul>
-              <li ng-repeat="ref in helpContent.see">
-                <a target="{{ formatRef(ref)[0] === '#' ? '_self' : '_blank'}}" ng-href="{{ formatRef(ref) }}">{{ ref }}</a>
-              </li>
+              <li ng-repeat="ref in helpContent.see" ng-bind-html="ref"> </li>
             </ul>
           </div>
         </div>
index a34f66460a62ab0bf4e366bbb417f7db8130912d..7a20910ba8a223557e03894579a84a9f66f7f843 100644 (file)
         $scope.helpContent = helpContent;
       } else {
         $scope.helpTitle = title;
-        $scope.helpContent = convertMarkdown(content);
+        $scope.helpContent = formatHelp(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);
+      $scope.helpContent = helpContent = formatHelp(content);
     }
 
-    function convertMarkdown(rawContent) {
+    // Convert plain-text help to markdown; replace variables and format links
+    function formatHelp(rawContent) {
+      function formatRefs(see) {
+        _.each(see, function(ref, idx) {
+          var match = ref.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/);
+          if (match) {
+            ref = '#/explorer/' + match[1];
+          }
+          if (ref[0] === '\\') {
+            ref = 'https://github.com/civicrm/civicrm-core/blob/master' + ref.replace(/\\/i, '/') + '.php';
+          }
+          see[idx] = '<a target="' + (ref[0] === '#' ? '_self' : '_blank') + '" href="' + ref + '">' + see[idx] + '</a>';
+        });
+      }
       var formatted = _.cloneDeep(rawContent);
       if (formatted.description) {
         formatted.description = marked(formatted.description);
       if (formatted.comment) {
         formatted.comment = marked(formatted.comment);
       }
+      formatRefs(formatted.see);
       return formatted;
     }
 
-    // Format the href for a @see help annotation
-    $scope.formatRef = function(see) {
-      var match = see.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/);
-      if (match) {
-        return '#/explorer/' + match[1];
-      }
-      if (see[0] === '\\') {
-        return 'https://github.com/civicrm/civicrm-core/blob/master' + see.replace(/\\/i, '/') + '.php';
-      }
-      return see;
-    };
-
     $scope.fieldHelp = function(fieldName) {
       var field = getField(fieldName, $scope.entity, $scope.action);
       if (!field) {
index 875cbdb1ba494852d562221f282cf72f80d06316..6f243c4faed1e1eaf9e5ea51bd9f688966291990 100644 (file)
@@ -47,10 +47,10 @@ class ParticipantTest extends UnitTestCase {
       ->indexBy('name');
 
     $getParams = $result['get']['params'];
-    $whereDescription = 'Criteria for selecting items.';
+    $whereDescription = 'Criteria for selecting Participants';
 
     $this->assertEquals(TRUE, $getParams['checkPermissions']['default']);
-    $this->assertEquals($whereDescription, $getParams['where']['description']);
+    $this->assertContains($whereDescription, $getParams['where']['description']);
   }
 
   public function testGet() {
index de6bd7e91a4411f5f35beefde0a95db9b769a02b..2eb9ce07f3cc5abe580ef4304cfb63a24fb2780d 100644 (file)
@@ -32,7 +32,7 @@ class MockV4ReflectionBase {
   /**
    * This is the foo property.
    *
-   * In general, you can do nothing with it.
+   *  - In general, you can do nothing with it.
    *
    * @var array
    */
index e5f7742ba6a645c062523607ad9a82a562329330..3e22ef59aa78c7605e662bcd8db1a3b477a9a61b 100644 (file)
 namespace api\v4\Mock;
 
 /**
- * Grandchild class
+ * Grandchild class for $ENTITY,
+ * with a 2-line description!
  *
- * This is an extended description.
+ * This is an extended comment.
  *
- * There is a line break in this description.
+ *   There is a line break in this comment.
  *
  * @inheritdoc
  */
index 494e4d59931851ab3dc915e212132999e6ba4307..fb3b6445853548549cdd44d24892dc58334a2465 100644 (file)
@@ -36,14 +36,14 @@ class ReflectionUtilsTest extends UnitTestCase {
   public function testGetDocBlockForClass() {
     $grandChild = new MockV4ReflectionGrandchild();
     $reflection = new \ReflectionClass($grandChild);
-    $doc = ReflectionUtils::getCodeDocs($reflection);
+    $doc = ReflectionUtils::getCodeDocs($reflection, NULL, ['$ENTITY' => "Test"]);
 
     $this->assertEquals(TRUE, $doc['internal']);
-    $this->assertEquals('Grandchild class', $doc['description']);
+    $this->assertEquals('Grandchild class for Test, with a 2-line description!', $doc['description']);
 
-    $expectedComment = 'This is an extended description.
+    $expectedComment = 'This is an extended comment.
 
-There is a line break in this description.
+  There is a line break in this comment.
 
 This is the base class.';
 
@@ -59,7 +59,7 @@ This is the base class.';
     $doc = ReflectionUtils::getCodeDocs($reflection->getProperty('foo'), 'Property');
 
     $this->assertEquals('This is the foo property.', $doc['description']);
-    $this->assertEquals("In the child class, foo has been barred.\n\nIn general, you can do nothing with it.", $doc['comment']);
+    $this->assertEquals("In the child class, foo has been barred.\n\n - In general, you can do nothing with it.", $doc['comment']);
   }
 
   public function docBlockExamples() {