mailsystem.module 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. /**
  3. * @file
  4. * Provide UI for controlling the mail_system variable.
  5. */
  6. /**
  7. * Implements hook_init().
  8. *
  9. * Caches the list of MailSystemInterface classes, and removes classes
  10. * from the mail_system variable which are no longer available.
  11. *
  12. * @see mailsystem_get_classes()
  13. */
  14. function mailsystem_init() {
  15. mailsystem_get_classes();
  16. // @todo Remove this when issue #299138 gets resolved.
  17. if (!function_exists('mailsystem_html_to_text')) {
  18. module_load_include('inc', 'mailsystem', 'html_to_text');
  19. }
  20. }
  21. /**
  22. * Implements hook_permission().
  23. *
  24. * Defines a permission for managing the mail_system variable.
  25. */
  26. function mailsystem_permission() {
  27. return array(
  28. 'administer mailsystem' => array(
  29. 'title' => t('Administer Mail System'),
  30. 'description' => t(
  31. 'Select the default, per-module, and per-mailing <a href="!interface"><code>@interface</code></a> to use for formatting and sending email messages.',
  32. array(
  33. '!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'),
  34. '@interface' => 'MailSystemInterface',
  35. )
  36. ),
  37. ),
  38. );
  39. }
  40. /**
  41. * Implements hook_menu().
  42. */
  43. function mailsystem_menu() {
  44. $items['admin/config/system/mailsystem'] = array(
  45. 'title' => 'Mail System',
  46. 'description' => 'Configure per-module Mail System settings.',
  47. 'page callback' => 'drupal_get_form',
  48. 'page arguments' => array('mailsystem_admin_settings'),
  49. 'access arguments' => array('administer mailsystem'),
  50. 'file' => 'mailsystem.admin.inc',
  51. );
  52. return $items;
  53. }
  54. /**
  55. * Returns the id for the default mail_system setting.
  56. */
  57. function mailsystem_default_id() {
  58. // @todo: Is there a way to get this from core?
  59. return 'default-system';
  60. }
  61. /**
  62. * Returns the value for the default mail_system setting.
  63. */
  64. function mailsystem_default_value() {
  65. // @todo: Is there a way to get this from core?
  66. return 'DefaultMailSystem';
  67. }
  68. /**
  69. * Returns the default settings for the mail_system variable.
  70. */
  71. function mailsystem_defaults() {
  72. return array(mailsystem_default_id() => mailsystem_default_value());
  73. }
  74. /**
  75. * Returns the current mail_system settings.
  76. *
  77. * @return The contents of the mail_system variable merged with its defaults.
  78. */
  79. function mailsystem_get() {
  80. return array_merge(
  81. mailsystem_defaults(),
  82. variable_get('mail_system', mailsystem_defaults())
  83. );
  84. }
  85. /**
  86. * Returns the default list of MailSystemInterface methods.
  87. *
  88. * @return
  89. * An array whose keys are the names of the methods defined by
  90. * MailSystemInterface and whose values are the default class used to
  91. * provide that method.
  92. */
  93. function mailsystem_default_methods() {
  94. $mail_system = mailsystem_get();
  95. $default_class = $mail_system[mailsystem_default_id()];
  96. $methods = get_class_methods('MailSystemInterface');
  97. return array_combine(
  98. $methods,
  99. array_fill(0, count($methods), $default_class)
  100. );
  101. }
  102. /**
  103. * Creates and registers a new MailSystemInterface class.
  104. *
  105. * The newly-created class gets its name and each of its class methods from the
  106. * other classes specified by the $class parameter.
  107. *
  108. * @param $class An associative array of ($method_name => $class_name) tuples,
  109. * where each $method_name is the name of a class method to be created, and
  110. * each $class_name is the name of a class to use for that method.
  111. *
  112. * @return
  113. * The name of the newly-created class if successful; otherwise FALSE.
  114. */
  115. function mailsystem_create_class($classes) {
  116. // Merge in defaults.
  117. $classes += mailsystem_default_methods();
  118. ksort($classes);
  119. // Do not create a new class whose methods all derive from the same class.
  120. if (count(array_unique($classes)) === 1) {
  121. return FALSE;
  122. }
  123. $class_name = implode('__', $classes);
  124. // Ensure that the mailsystem directory exists.
  125. $class_dir = file_build_uri('mailsystem');
  126. if (!file_prepare_directory($class_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
  127. return FALSE;
  128. }
  129. // Build the class filename.
  130. $class_file = drupal_realpath($class_dir) . DIRECTORY_SEPARATOR . "$class_name.mail.inc";
  131. // Strip DRUPAL_ROOT.
  132. $drupal_root = drupal_realpath(DRUPAL_ROOT) . DIRECTORY_SEPARATOR;
  133. $class_file = preg_replace('#^' . preg_quote($drupal_root, '#') . '#', '', $class_file);
  134. // Build the class implementation as a string.
  135. $class_contents = '<?php
  136. class ' . $class_name . ' implements MailSystemInterface {';
  137. // Create a protected variable to hold each method class.
  138. foreach (array_keys($classes) as $method) {
  139. $class_contents .= '
  140. protected $' . $method . 'Class;';
  141. }
  142. // Create a class construction function to populate the variables.
  143. $class_contents .= '
  144. public function __construct() {';
  145. foreach ($classes as $method => $class) {
  146. $class_contents .= '
  147. if (drupal_autoload_class(\'' . $class . '\')) {
  148. $this->' . $method . 'Class = new ' . $class . ';
  149. }
  150. else {
  151. $this->' . $method . 'Class = new ' . mailsystem_default_value() . ';
  152. }';
  153. }
  154. $class_contents .= '
  155. }';
  156. // Create each class method.
  157. foreach (array_keys($classes) as $method) {
  158. $class_contents .= '
  159. public function ' . $method . '(array $message) {
  160. return $this->' . $method . 'Class->' . $method . '($message);
  161. }';
  162. }
  163. $class_contents .= '
  164. }
  165. ';
  166. if (file_unmanaged_save_data($class_contents, $class_file, FILE_EXISTS_REPLACE)) {
  167. // Remove any conflicting registry entries to avoid a database error.
  168. $class_condition = db_and()
  169. ->condition('name', $class_name)
  170. ->condition('type', 'class');
  171. $file_condition = db_and()
  172. ->condition('filename', $class_file);
  173. db_delete('registry_file')
  174. ->condition($file_condition);
  175. db_delete('registry')->condition(
  176. db_or()->condition($class_condition)
  177. ->condition($file_condition)
  178. );
  179. // Make sure that registry functions are available.
  180. require_once 'includes/registry.inc';
  181. // Parse the newly-created class file and add it to the registry.
  182. _registry_parse_file($class_file, $class_contents, 'mailsystem');
  183. // Clear the mailsystem cache so that it will pick up the new class.
  184. drupal_static_reset('mailsystem_get_classes');
  185. drupal_set_message(
  186. t('Class <code>%class</code> written to <code>%file</code>.',
  187. array('%class' => $class_name, '%file' => $class_file)
  188. )
  189. );
  190. }
  191. return $class_name;
  192. }
  193. /**
  194. * Helps other modules safely set their own key within mail_system. This
  195. * function should be called from hook_enable() implementations.
  196. *
  197. * @param $setting An associative array ($id => $value) where:
  198. * - $id is the machine-readable module name optionally followed by '_'
  199. * and a key.
  200. * - $value is one of
  201. * - (string) The name of a class that implements MailSystemInterface.
  202. * - (array) An associative array whose keys are the names of methods
  203. * defined by MailSystemInterface and whose values are the names of
  204. * the class to use for that method.
  205. *
  206. * @see drupal_mail(), mailsystem_default_methods()
  207. */
  208. function mailsystem_set(array $setting) {
  209. $mail_system = mailsystem_get();
  210. foreach ($setting as $key => $class) {
  211. if (is_array($class)) {
  212. unset($setting[$key]);
  213. if ($new_class = mailsystem_create_class($class)) {
  214. $setting[$key] = $new_class;
  215. }
  216. }
  217. }
  218. variable_set('mail_system', array_merge(mailsystem_get(), $setting));
  219. }
  220. /**
  221. * Helps other modules safely remove their settings from mail_system. This
  222. * function should be called from the other module's hook_disable() function.
  223. *
  224. * @param $setting An associative array ($module => $classname) describing
  225. * a module and associated MailSystemInterface class that are being disabled.
  226. * - $module is the machine-readable module name.
  227. * - $classname is a class that implements MailSystemInterface.
  228. *
  229. * If $classname is empty, only the $module entry is removed.
  230. *
  231. * @param $class
  232. * The name of the class to be removed, if any.
  233. */
  234. function mailsystem_clear(array $setting) {
  235. variable_set(
  236. 'mail_system',
  237. array_merge(
  238. mailsystem_defaults(),
  239. array_diff_key(array_diff(mailsystem_get(), $setting), $setting)
  240. )
  241. );
  242. }
  243. /**
  244. * Returns a list of classes which implement MailSystemInterface.
  245. */
  246. function &mailsystem_get_classes() {
  247. $mailsystem_classes = &drupal_static(__FUNCTION__);
  248. if (!isset($mailsystem_classes)) {
  249. $mailsystem_classes = array();
  250. // @todo Is there a better way to find all mail-related classes?
  251. $declared_classes = get_declared_classes();
  252. $all_classes = array_combine(
  253. $declared_classes,
  254. array_fill(0, count($declared_classes), 0)
  255. );
  256. $mail_classes = db_select('registry', 'registry')
  257. ->distinct()
  258. ->fields('registry', array('name', 'filename'))
  259. ->where("type=:type AND ( filename like :filename OR name like :name )",
  260. // Making the HUGE assumption that all classes which implement
  261. // MailSystemInterface have filenames containing '.mail.' or
  262. // classnames ending in 'MailSystem'.
  263. array(
  264. ':type' => 'class',
  265. ':name' => '%MailSystem',
  266. ':filename' => '%.mail.%',
  267. )
  268. )
  269. ->execute()
  270. ->fetchAllKeyed();
  271. foreach ($mail_classes as $classname => $classfile) {
  272. if ( file_exists($classfile)
  273. && drupal_autoload_class($classname)
  274. ) {
  275. $all_classes[$classname] = 1;
  276. }
  277. }
  278. foreach ($all_classes as $classname => $autoload) {
  279. if ( ($autoload || preg_match('/MailSystem/', $classname))
  280. && ($object = new $classname)
  281. && ($object instanceof MailSystemInterface)
  282. ) {
  283. $mailsystem_classes[$classname] = $classname;
  284. }
  285. elseif ($autoload) {
  286. // Clear classes that are no longer available.
  287. db_delete('registry')
  288. ->condition('name', $classname)
  289. ->execute();
  290. }
  291. }
  292. foreach (array_unique(mailsystem_get()) as $classname) {
  293. if (class_exists($classname)) {
  294. $mailsystem_classes[$classname] = $classname;
  295. }
  296. else {
  297. mailsystem_clear(array(mailsystem_default_id() => $classname));
  298. }
  299. }
  300. ksort($mailsystem_classes);
  301. }
  302. return $mailsystem_classes;
  303. }
  304. /**
  305. * Implements hook_theme_registry_alter().
  306. */
  307. function mailsystem_theme_registry_alter(&$theme_registry) {
  308. module_load_include('inc', 'mailsystem', 'mailsystem.theme');
  309. return mailsystem_theme_theme_registry_alter($theme_registry);
  310. }
  311. /**
  312. * Retrieves the key of the theme used to render the emails.
  313. *
  314. * @todo Add some kind of hook to let other modules alter this behavior.
  315. */
  316. function mailsystem_get_mail_theme() {
  317. global $theme_key;
  318. $theme = variable_get('mailsystem_theme', 'current');
  319. switch ($theme) {
  320. case 'default':
  321. $theme = variable_get('theme_default', NULL);
  322. break;
  323. case 'current':
  324. $theme = $theme_key;
  325. break;
  326. case 'domain':
  327. // Fetch the theme for the current domain.
  328. if (module_exists('domain_theme')) {
  329. // Assign the selected theme, based on the active domain.
  330. global $_domain;
  331. $domain_theme = domain_theme_lookup($_domain['domain_id']);
  332. // The above returns -1 on failure.
  333. $theme = ($domain_theme != -1) ? $domain_theme['theme'] : $theme_key;
  334. }
  335. break;
  336. }
  337. return $theme;
  338. }