array( 'title' => t('Administer Mail System'), 'description' => t( 'Select the default, per-module, and per-mailing @interface to use for formatting and sending email messages.', array( '!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'), '@interface' => 'MailSystemInterface', ) ), ), ); } /** * Implements hook_menu(). */ function mailsystem_menu() { $items['admin/config/system/mailsystem'] = array( 'title' => 'Mail System', 'description' => 'Configure per-module Mail System settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('mailsystem_admin_settings'), 'access arguments' => array('administer mailsystem'), 'file' => 'mailsystem.admin.inc', ); return $items; } /** * Returns the id for the default mail_system setting. */ function mailsystem_default_id() { // @todo: Is there a way to get this from core? return 'default-system'; } /** * Returns the value for the default mail_system setting. */ function mailsystem_default_value() { // @todo: Is there a way to get this from core? return 'DefaultMailSystem'; } /** * Returns the default settings for the mail_system variable. */ function mailsystem_defaults() { return array(mailsystem_default_id() => mailsystem_default_value()); } /** * Returns the current mail_system settings. * * @return The contents of the mail_system variable merged with its defaults. */ function mailsystem_get() { return array_merge( mailsystem_defaults(), variable_get('mail_system', mailsystem_defaults()) ); } /** * Returns the default list of MailSystemInterface methods. * * @return * An array whose keys are the names of the methods defined by * MailSystemInterface and whose values are the default class used to * provide that method. */ function mailsystem_default_methods() { $mail_system = mailsystem_get(); $default_class = $mail_system[mailsystem_default_id()]; $methods = get_class_methods('MailSystemInterface'); return array_combine( $methods, array_fill(0, count($methods), $default_class) ); } /** * Creates and registers a new MailSystemInterface class. * * The newly-created class gets its name and each of its class methods from the * other classes specified by the $class parameter. * * @param $class An associative array of ($method_name => $class_name) tuples, * where each $method_name is the name of a class method to be created, and * each $class_name is the name of a class to use for that method. * * @return * The name of the newly-created class if successful; otherwise FALSE. */ function mailsystem_create_class($classes) { // Merge in defaults. $classes += mailsystem_default_methods(); ksort($classes); // Do not create a new class whose methods all derive from the same class. if (count(array_unique($classes)) === 1) { return FALSE; } $class_name = implode('__', $classes); // Ensure that the mailsystem directory exists. // First we try the private filesystem. $private_files = variable_get('file_private_path', ''); $private_files_full = $private_files . '/mailsystem'; $public_files = variable_get('file_public_path', conf_path() . '/files'); $public_files_full = $public_files . '/mailsystem'; if ($private_files && file_prepare_directory($private_files_full, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { $class_dir = $private_files . '/mailsystem'; } // If private filesystem is not defined or writable, we use the public filesystem. elseif (file_prepare_directory($public_files_full, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { $class_dir = $public_files . '/mailsystem'; } else { return FALSE; } // Build the class filename. $class_file = $class_dir . DIRECTORY_SEPARATOR . "$class_name.mail.inc"; // Build the class implementation as a string. $class_contents = ' $class) { $class_contents .= ' if (drupal_autoload_class(\'' . $class . '\')) { $this->' . $method . 'Class = new ' . $class . '; } else { $this->' . $method . 'Class = new ' . mailsystem_default_value() . '; }'; } $class_contents .= ' }'; // Create each class method. foreach (array_keys($classes) as $method) { $class_contents .= ' public function ' . $method . '(array $message) { return $this->' . $method . 'Class->' . $method . '($message); }'; } $class_contents .= ' } '; if (file_unmanaged_save_data($class_contents, $class_file, FILE_EXISTS_REPLACE)) { // Remove any conflicting registry entries to avoid a database error. $class_condition = db_and() ->condition('name', $class_name) ->condition('type', 'class'); $file_condition = db_and() ->condition('filename', $class_file); db_delete('registry_file') ->condition($file_condition) ->execute(); db_delete('registry')->condition( db_or()->condition($class_condition) ->condition($file_condition) )->execute(); // Make sure that registry functions are available. require_once 'includes/registry.inc'; // Parse the newly-created class file and add it to the registry. _registry_parse_file($class_file, $class_contents, 'mailsystem'); // Clear the mailsystem caches so that it will pick up the new class. drupal_static_reset('mailsystem_get_classes'); cache_clear_all('mailsystem_get_classes', 'cache'); drupal_set_message( t('Class %class written to %file.', array('%class' => $class_name, '%file' => $class_file) ) ); } return $class_name; } /** * Helps other modules safely set their own key within mail_system. This * function should be called from hook_enable() implementations. * * @param $setting An associative array ($id => $value) where: * - $id is the machine-readable module name optionally followed by '_' * and a key. * - $value is one of * - (string) The name of a class that implements MailSystemInterface. * - (array) An associative array whose keys are the names of methods * defined by MailSystemInterface and whose values are the names of * the class to use for that method. * * @see drupal_mail(), mailsystem_default_methods() */ function mailsystem_set(array $setting) { $mail_system = mailsystem_get(); foreach ($setting as $key => $class) { if (is_array($class)) { unset($setting[$key]); if ($new_class = mailsystem_create_class($class)) { $setting[$key] = $new_class; } } } variable_set('mail_system', array_merge(mailsystem_get(), $setting)); } /** * Helps other modules safely remove their settings from mail_system. This * function should be called from the other module's hook_disable() function. * * @param $setting An associative array ($module => $classname) describing * a module and associated MailSystemInterface class that are being disabled. * - $module is the machine-readable module name. * - $classname is a class that implements MailSystemInterface. * * If $classname is empty, only the $module entry is removed. * * @param $class * The name of the class to be removed, if any. */ function mailsystem_clear(array $setting) { variable_set( 'mail_system', array_merge( mailsystem_defaults(), array_diff_key(array_diff(mailsystem_get(), $setting), $setting) ) ); } /** * Returns a list of classes which implement MailSystemInterface. */ function &mailsystem_get_classes() { // Load static cache. $mailsystem_classes = &drupal_static(__FUNCTION__); // Check persistent cache if necessary. if (!isset($mailsystem_classes) && $cache = cache_get('mailsystem_get_classes')) { $mailsystem_classes = $cache->data; } // Load from db if no cache was hit. 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() ->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(); foreach ($mail_classes as $classname => $classfile) { if (file_exists($classfile) && drupal_autoload_class($classname) ) { $all_classes[$classname] = 1; } } 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(); } } foreach (array_unique(mailsystem_get()) as $classname) { if (class_exists($classname)) { $mailsystem_classes[$classname] = $classname; } else { mailsystem_clear(array(mailsystem_default_id() => $classname)); } } ksort($mailsystem_classes); // Store in persistent cache. cache_set('mailsystem_get_classes', $mailsystem_classes, 'cache', CACHE_TEMPORARY); } return $mailsystem_classes; } /** * Implements hook_theme_registry_alter(). */ function mailsystem_theme_registry_alter(&$theme_registry) { module_load_include('inc', 'mailsystem', 'mailsystem.theme'); return mailsystem_theme_theme_registry_alter($theme_registry); } /** * Retrieves the key of the theme used to render the emails. * * @todo Add some kind of hook to let other modules alter this behavior. */ function mailsystem_get_mail_theme() { global $theme_key; $theme = variable_get('mailsystem_theme', 'current'); switch ($theme) { case 'default': $theme = variable_get('theme_default', NULL); break; case 'current': $theme = $theme_key; break; case 'domain': // Fetch the theme for the current domain. if (module_exists('domain_theme')) { // Assign the selected theme, based on the active domain. global $_domain; $domain_theme = domain_theme_lookup($_domain['domain_id']); // The above returns -1 on failure. $theme = ($domain_theme != -1) ? $domain_theme['theme'] : $theme_key; } break; } return $theme; }