commit 6d611b541b02632c6b753cf735187742532b96dc
Author: Stefaan Lippens <soxofaan@41478.no-reply.drupal.org>
Date:   Tue Dec 27 02:13:11 2011 +0100

    Issue #1377380 by Sheldon Rampton and soxofaan: provide hook to customize CAPTCHA placement

diff --git a/captcha.inc b/captcha.inc
index 818dd90..e8212b1 100644
--- a/captcha.inc
+++ b/captcha.inc
@@ -240,15 +240,11 @@ function _captcha_get_captcha_placement($form_id, $form) {
     $placement_map = variable_get('captcha_placement_map_cache', NULL);
 
     if ($placement_map === NULL) {
-      // If second level cache missed: start from a fresh placement map.
-      $placement_map = array();
-
-      // This is the place to (pre)fill the placement map with some hard coded default entries.
-      // However, probably all Drupal core forms are correctly handled with the best effort
-      // guess based on the 'actions' element (see below). So not much to here at the moment.
-
-      // TODO: provide a hook here so other modules can inject placements here?
-      // TODO: also make the placement 'overridable' from the admin UI?
+      // If second level cache missed: initialize the placement map
+      // and let other modules hook into this with the hook_captcha_placement_map hook.
+      // By default however, probably all Drupal core forms are already correctly
+      // handled with the best effort guess based on the 'actions' element (see below).
+      $placement_map = module_invoke_all('captcha_placement_map');
     }
   }
 
@@ -329,7 +325,7 @@ function _captcha_search_buttons($form) {
  *       CAPTCHA element should be inserted.
  *     - 'key': the key of the element before which the CAPTCHA element
  *       should be inserted. If the field 'key' is undefined or NULL, the CAPTCHA will
- *       just be appended to the container.
+ *       just be appended in the container.
  *     - 'weight': if 'key' is not NULL: should be the weight of the element defined by 'key'.
  *       If 'key' is NULL and weight is not NULL: set the weight property of the CAPTCHA element
  *       to this value.
diff --git a/captcha.module b/captcha.module
index 1c98bd2..985f4d8 100644
--- a/captcha.module
+++ b/captcha.module
@@ -707,3 +707,12 @@ function captcha_captcha($op, $captcha_type = '') {
       break;
   }
 }
+
+/**
+ * Implements hook_modules_enabled.
+ */
+function captcha_modules_enabled() {
+  // When new modules are enabled: clear the CAPTCHA placement cache, so that
+  // new hook_captcha_placement_map hooks can be triggered.
+  variable_del('captcha_placement_map_cache');
+}
diff --git a/captcha_api.txt b/captcha_api.txt
index bf6b79c..2770195 100644
--- a/captcha_api.txt
+++ b/captcha_api.txt
@@ -176,3 +176,44 @@ function foo_captcha_custom_validation($solution, $response, $element, $form_sta
 These extra arguments are the $element and $form_state arguments of the validation function
 of the #captcha element. See captcha_validate() in captcha.module for more info about this.
 
+
+
+=== Hook into CAPTCHA placement ===
+The CAPTCHA module attempts to place the CAPTCHA element in an appropriate spot
+at the bottom of the targeted form, but this automatic detection may be insufficient
+for complex forms.
+The hook_captcha_placement_map hook allows to define the placement of the CAPTCHA element
+as desired. The hook should return an array, mapping form IDs to placement arrays, which are
+associative arrays with the following fields:
+  - 'path': path (array of path items) of the container in the form where the
+    CAPTCHA element should be inserted.
+  - 'key': the key of the element before which the CAPTCHA element
+    should be inserted. If the field 'key' is undefined or NULL, the CAPTCHA will
+    just be appended in the container.
+  - 'weight': if 'key' is not NULL: should be the weight of the element defined by 'key'.
+    If 'key' is NULL and weight is not NULL/unset: set the weight property of the CAPTCHA element
+    to this value.
+
+For example:
+"""
+/**
+ * Implementation of hook_captcha_placement_map
+ */
+function hook_captcha_placement_map() {
+  return array(
+    'my_fancy_form' => array(
+      'path' => array('items', 'buttons'),
+      'key' => 'savebutton',
+    ),
+    'another_form' => array(
+      'path' => array(),
+      'weight' => 34,
+    ),
+  );
+}
+"""
+This will place the CAPTCHA element
+  - in the 'my_fancy_form' form inside the container $form['items']['buttons'],
+    just before the element $form['items']['buttons']['sacebutton'].
+  - in the 'another_form' form at the toplevel of the form, with a weight 34.
+
