mailsystem.module 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. // First we try the private filesystem.
  126. $private_files = variable_get('file_private_path', '');
  127. $private_files_full = $private_files . '/mailsystem';
  128. $public_files = variable_get('file_public_path', conf_path() . '/files');
  129. $public_files_full = $public_files . '/mailsystem';
  130. if ($private_files && file_prepare_directory($private_files_full, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
  131. $class_dir = $private_files . '/mailsystem';
  132. }
  133. // If private filesystem is not defined or writable, we use the public filesystem.
  134. elseif (file_prepare_directory($public_files_full, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
  135. $class_dir = $public_files . '/mailsystem';
  136. }
  137. else {
  138. return FALSE;
  139. }
  140. // Build the class filename.
  141. $class_file = $class_dir . DIRECTORY_SEPARATOR . "$class_name.mail.inc";
  142. // Build the class implementation as a string.
  143. $class_contents = '<?php
  144. class ' . $class_name . ' implements MailSystemInterface {';
  145. // Create a protected variable to hold each method class.
  146. foreach (array_keys($classes) as $method) {
  147. $class_contents .= '
  148. protected $' . $method . 'Class;';
  149. }
  150. // Create a class construction function to populate the variables.
  151. $class_contents .= '
  152. public function __construct() {';
  153. foreach ($classes as $method => $class) {
  154. $class_contents .= '
  155. if (drupal_autoload_class(\'' . $class . '\')) {
  156. $this->' . $method . 'Class = new ' . $class . ';
  157. }
  158. else {
  159. $this->' . $method . 'Class = new ' . mailsystem_default_value() . ';
  160. }';
  161. }
  162. $class_contents .= '
  163. }';
  164. // Create each class method.
  165. foreach (array_keys($classes) as $method) {
  166. $class_contents .= '
  167. public function ' . $method . '(array $message) {
  168. return $this->' . $method . 'Class->' . $method . '($message);
  169. }';
  170. }
  171. $class_contents .= '
  172. }
  173. ';
  174. if (file_unmanaged_save_data($class_contents, $class_file, FILE_EXISTS_REPLACE)) {
  175. // Remove any conflicting registry entries to avoid a database error.
  176. $class_condition = db_and()
  177. ->condition('name', $class_name)
  178. ->condition('type', 'class');
  179. $file_condition = db_and()
  180. ->condition('filename', $class_file);
  181. db_delete('registry_file')
  182. ->condition($file_condition)
  183. ->execute();
  184. db_delete('registry')->condition(
  185. db_or()->condition($class_condition)
  186. ->condition($file_condition)
  187. )->execute();
  188. // Make sure that registry functions are available.
  189. require_once 'includes/registry.inc';
  190. // Parse the newly-created class file and add it to the registry.
  191. _registry_parse_file($class_file, $class_contents, 'mailsystem');
  192. // Clear the mailsystem caches so that it will pick up the new class.
  193. drupal_static_reset('mailsystem_get_classes');
  194. cache_clear_all('mailsystem_get_classes', 'cache');
  195. drupal_set_message(
  196. t('Class <code>%class</code> written to <code>%file</code>.',
  197. array('%class' => $class_name, '%file' => $class_file)
  198. )
  199. );
  200. }
  201. return $class_name;
  202. }
  203. /**
  204. * Helps other modules safely set their own key within mail_system. This
  205. * function should be called from hook_enable() implementations.
  206. *
  207. * @param $setting An associative array ($id => $value) where:
  208. * - $id is the machine-readable module name optionally followed by '_'
  209. * and a key.
  210. * - $value is one of
  211. * - (string) The name of a class that implements MailSystemInterface.
  212. * - (array) An associative array whose keys are the names of methods
  213. * defined by MailSystemInterface and whose values are the names of
  214. * the class to use for that method.
  215. *
  216. * @see drupal_mail(), mailsystem_default_methods()
  217. */
  218. function mailsystem_set(array $setting) {
  219. $mail_system = mailsystem_get();
  220. foreach ($setting as $key => $class) {
  221. if (is_array($class)) {
  222. unset($setting[$key]);
  223. if ($new_class = mailsystem_create_class($class)) {
  224. $setting[$key] = $new_class;
  225. }
  226. }
  227. }
  228. variable_set('mail_system', array_merge(mailsystem_get(), $setting));
  229. }
  230. /**
  231. * Helps other modules safely remove their settings from mail_system. This
  232. * function should be called from the other module's hook_disable() function.
  233. *
  234. * @param $setting An associative array ($module => $classname) describing
  235. * a module and associated MailSystemInterface class that are being disabled.
  236. * - $module is the machine-readable module name.
  237. * - $classname is a class that implements MailSystemInterface.
  238. *
  239. * If $classname is empty, only the $module entry is removed.
  240. *
  241. * @param $class
  242. * The name of the class to be removed, if any.
  243. */
  244. function mailsystem_clear(array $setting) {
  245. variable_set(
  246. 'mail_system',
  247. array_merge(
  248. mailsystem_defaults(),
  249. array_diff_key(array_diff(mailsystem_get(), $setting), $setting)
  250. )
  251. );
  252. }
  253. /**
  254. * Returns a list of classes which implement MailSystemInterface.
  255. */
  256. function &mailsystem_get_classes() {
  257. // Load static cache.
  258. $mailsystem_classes = &drupal_static(__FUNCTION__);
  259. // Check persistent cache if necessary.
  260. if (!isset($mailsystem_classes) && $cache = cache_get('mailsystem_get_classes')) {
  261. $mailsystem_classes = $cache->data;
  262. }
  263. // Load from db if no cache was hit.
  264. if (!isset($mailsystem_classes)) {
  265. $mailsystem_classes = array();
  266. // @todo Is there a better way to find all mail-related classes?
  267. $declared_classes = get_declared_classes();
  268. $all_classes = array_combine(
  269. $declared_classes,
  270. array_fill(0, count($declared_classes), 0)
  271. );
  272. $mail_classes = db_select('registry', 'registry')
  273. ->distinct()
  274. ->fields('registry', array('name', 'filename'))
  275. ->where("type=:type AND ( filename like :filename OR name like :name )",
  276. // Making the HUGE assumption that all classes which implement
  277. // MailSystemInterface have filenames containing '.mail.' or
  278. // classnames ending in 'MailSystem'.
  279. array(
  280. ':type' => 'class',
  281. ':name' => '%MailSystem',
  282. ':filename' => '%.mail.%',
  283. )
  284. )
  285. ->execute()
  286. ->fetchAllKeyed();
  287. foreach ($mail_classes as $classname => $classfile) {
  288. if (file_exists($classfile)
  289. && drupal_autoload_class($classname)
  290. ) {
  291. $all_classes[$classname] = 1;
  292. }
  293. }
  294. foreach ($all_classes as $classname => $autoload) {
  295. if (($autoload || preg_match('/MailSystem/', $classname))
  296. && ($object = new $classname)
  297. && ($object instanceof MailSystemInterface)
  298. ) {
  299. $mailsystem_classes[$classname] = $classname;
  300. }
  301. elseif ($autoload) {
  302. // Clear classes that are no longer available.
  303. db_delete('registry')
  304. ->condition('name', $classname)
  305. ->execute();
  306. }
  307. }
  308. foreach (array_unique(mailsystem_get()) as $classname) {
  309. if (class_exists($classname)) {
  310. $mailsystem_classes[$classname] = $classname;
  311. }
  312. else {
  313. mailsystem_clear(array(mailsystem_default_id() => $classname));
  314. }
  315. }
  316. ksort($mailsystem_classes);
  317. // Store in persistent cache.
  318. cache_set('mailsystem_get_classes', $mailsystem_classes, 'cache', CACHE_TEMPORARY);
  319. }
  320. return $mailsystem_classes;
  321. }
  322. /**
  323. * Implements hook_theme_registry_alter().
  324. */
  325. function mailsystem_theme_registry_alter(&$theme_registry) {
  326. module_load_include('inc', 'mailsystem', 'mailsystem.theme');
  327. return mailsystem_theme_theme_registry_alter($theme_registry);
  328. }
  329. /**
  330. * Retrieves the key of the theme used to render the emails.
  331. *
  332. * @todo Add some kind of hook to let other modules alter this behavior.
  333. */
  334. function mailsystem_get_mail_theme() {
  335. global $theme_key;
  336. $theme = variable_get('mailsystem_theme', 'current');
  337. switch ($theme) {
  338. case 'default':
  339. $theme = variable_get('theme_default', NULL);
  340. break;
  341. case 'current':
  342. $theme = $theme_key;
  343. break;
  344. case 'domain':
  345. // Fetch the theme for the current domain.
  346. if (module_exists('domain_theme')) {
  347. // Assign the selected theme, based on the active domain.
  348. global $_domain;
  349. $domain_theme = domain_theme_lookup($_domain['domain_id']);
  350. // The above returns -1 on failure.
  351. $theme = ($domain_theme != -1) ? $domain_theme['theme'] : $theme_key;
  352. }
  353. break;
  354. }
  355. return $theme;
  356. }