From b88c54c2f8c18baf40eb7899c9c8bd1e8ec1dc2b Mon Sep 17 00:00:00 2001
From: Lorenz Schori <lo@znerol.ch>
Date: Tue, 8 May 2012 22:02:22 +0200
Subject: [PATCH 3/4] Rewrite mailsystem_get_classes

* Remove code which tries to remove classes from the registry and from the
  mail_system variable which are not available.
* Do not instantiate classes in order to test if they implement a given
  interface but use class_implements function.
* Introduce variables mailsystem_get_classes_whitelist and
  mailsystem_get_classes_blacklist variables in order to customize the
  query used to select which classes from the registry should be tested.
---
 mailsystem.module |  188 ++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 142 insertions(+), 46 deletions(-)

diff --git a/mailsystem.module b/mailsystem.module
index 8779676..438b514 100644
--- a/mailsystem.module
+++ b/mailsystem.module
@@ -244,64 +244,160 @@ function _mailsystem_delegate_get_mailsystem($module, $key, $action) {
 
 /**
  * Returns a list of classes which implement MailSystemInterface.
+ *
+ * This method assumes that all classes which implement MailSystemInterface
+ * have filenames containing '.mail.' or class names containing 'MailSystem'.
+ *
+ * If necessary you may override the default discovery whitelist by setting
+ * the mailsystem_get_classes_whitelist and mailsystem_get_classes_blacklist
+ * variables. The default value is as follows:
+ *
+ * @code
+ * variable_set('mailsystem_get_classes_whitelist', array(
+ *   array(
+ *     'field' => 'name',
+ *     'value' => '%MailSystem',
+ *     'operator' => 'LIKE',
+ *   ),
+ *   array(
+ *     'field' => 'filename',
+ *     'value' => '%.mail.%',
+ *     'operator' => 'LIKE',
+ *   ),
+ * ));
+ *
+ * variable_set('mailsystem_get_classes_blacklist', array(
+ *   array(
+ *     'field' => 'filename',
+ *     'value' => '%.test',
+ *     'operator' => 'NOT LIKE',
+ *   ),
+ * ));
+ * @endcode
+ *
+ * The 'field' value specifies in which database-field of the registry table
+ * the lookup for 'value' should take place. Typical values for 'field' are
+ * either 'name' for the classname or 'filename' for the name of the file the
+ * class is contained in.
+ *
+ * For example, if your own mail system implementation called 'MyMailer' should
+ * be available in the mailsystem module you may add the following record to
+ * the whitelist:
+ *
+ * @code
+ * array(
+ *   'field' => 'name',
+ *   'value' => 'MyMailer',
+ * );
+ * @endcode
+ *
+ * Note that if you set the blacklist, you need to negate the operator. Use
+ * 'NOT Like' or the not-equal operator '<>'.
  */
-function &mailsystem_get_classes() {
+function mailsystem_get_classes() {
   $mailsystem_classes = &drupal_static(__FUNCTION__);
   if (!isset($mailsystem_classes)) {
-    $mailsystem_classes = array();
-    // @todo Is there a better way to find all mail-related classes?
-    $declared_classes = get_declared_classes();
-    $all_classes = array_combine(
-      $declared_classes,
-      array_fill(0, count($declared_classes), 0)
-    );
-    $mail_classes = db_select('registry', 'registry')
-      ->distinct()
+    $query = db_select('registry', 'registry')
       ->fields('registry', array('name', 'filename'))
-      ->where("type=:type AND ( filename like :filename OR name like :name )",
-        // Making the HUGE assumption that all classes which implement
-        // MailSystemInterface have filenames containing '.mail.' or
-        // classnames ending in 'MailSystem'.
-        array(
-          ':type' => 'class',
-          ':name' => '%MailSystem',
-          ':filename' => '%.mail.%',
-        )
-      )
-      ->execute()
-      ->fetchAllKeyed();
+      ->condition('type', 'class');
+
+    if ($whitelist = _mailsystem_get_registry_whitelist_condition()) {
+      $query->condition($whitelist);
+    }
+
+    if ($blacklist = _mailsystem_get_registry_blacklist_condition()) {
+      $query->condition($blacklist);
+    }
+
+    $mail_classes = $query->execute()->fetchAllKeyed();
+
+    $mailsystem_classes = array();
     foreach ($mail_classes as $classname => $classfile) {
-      if ( file_exists($classfile)
-        && drupal_autoload_class($classname)
-      ) {
-        $all_classes[$classname] = 1;
+      if (file_exists($classfile)) {
+        $interfaces = class_implements($classname);
+        if (isset($interfaces['MailSystemInterface'])) {
+          $mailsystem_classes[$classname] = $classname;
+        }
       }
     }
-    foreach ($all_classes as $classname => $autoload) {
-      if ( ($autoload || preg_match('/MailSystem/', $classname))
-        && ($object = new $classname)
-        && ($object instanceof MailSystemInterface)
-      ) {
-        $mailsystem_classes[$classname] = $classname;
-      }
-      elseif ($autoload) {
-        // Clear classes that are no longer available.
-        db_delete('registry')
-          ->condition('name', $classname)
-          ->execute();
+    ksort($mailsystem_classes);
+  }
+  return $mailsystem_classes;
+}
+
+/**
+ * Return a DatabaseCondition representing the registry-whitelist.
+ *
+ * @return DatabaseConnection representing the whitelist conditions or NULL.
+ *
+ * @see mailsystem_get_classes
+ */
+function _mailsystem_get_registry_whitelist_condition() {
+  $whitelist = variable_get('mailsystem_get_classes_whitelist', array(
+    array(
+      'field' => 'name',
+      'value' => '%MailSystem',
+      'operator' => 'LIKE',
+    ),
+    array(
+      'field' => 'filename',
+      'value' => '%.mail.%',
+      'operator' => 'LIKE',
+    ),
+  ));
+
+  if (!empty($whitelist)) {
+    $or= db_or();
+    foreach ($whitelist as $entry) {
+      if (!empty($entry['field']) && !empty($entry['value']) && !empty($entry['operator'])) {
+        $or->condition($entry['field'], $entry['value'], $entry['operator']);
+        $condition_added = TRUE;
       }
     }
-    foreach (array_unique(mailsystem_get()) as $classname) {
-      if (class_exists($classname)) {
-        $mailsystem_classes[$classname] = $classname;
-      }
-      else {
-        mailsystem_clear(array(mailsystem_default_id() => $classname));
+    if ($or->count()) {
+      return $or;
+    }
+  }
+}
+
+/**
+ * Return a DatabaseCondition representing the registry-blacklist.
+ *
+ * By default all files ending with .test are excluded if the simpletest module
+ * is disabled. The blacklist pattern can be overridden using the variable
+ * mailsystem_get_classes_blacklist.
+ *
+ * @return DatabaseConnection representing the blacklist conditions or NULL.
+ *
+ * @see mailsystem_get_classes
+ */
+function _mailsystem_get_registry_blacklist_condition() {
+  if (!module_exists('simpletest')) {
+    $default_blacklist = array(
+      array(
+        'field' => 'filename',
+        'value' => '%.test',
+        'operator' => 'NOT LIKE',
+      ),
+    );
+  }
+  else {
+    $default_blacklist = array();
+  }
+
+  $blacklist = variable_get('mailsystem_get_classes_blacklist', $default_blacklist);
+
+  if (!empty($blacklist)) {
+    $and = db_and();
+    foreach ($blacklist as $entry) {
+      if (!empty($entry['field']) && !empty($entry['value']) && !empty($entry['operator'])) {
+        $and->condition($entry['field'], $entry['value'], $entry['operator']);
       }
     }
-    ksort($mailsystem_classes);
+    if ($and->count()) {
+      return $and;
+    }
   }
-  return $mailsystem_classes;
 }
 
 /**
-- 
1.7.2.5

