From 5fcaadbc9a16f711c0fe81933ec9615f289c8fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Vr=C3=A1til?= Date: Wed, 27 Apr 2011 17:10:14 +0200 Subject: Bug #641845 - Add default expansion variables to templates plugin Users can read values from original message in the template by $ORIG[header] and with a special value $ORIG[body]. --- plugins/templates/org-gnome-templates.eplug.xml | 5 +- plugins/templates/templates.c | 545 +++++++++++++++++++----- 2 files changed, 452 insertions(+), 98 deletions(-) (limited to 'plugins') diff --git a/plugins/templates/org-gnome-templates.eplug.xml b/plugins/templates/org-gnome-templates.eplug.xml index 7c19a6ea23..5bfa8a1e2b 100644 --- a/plugins/templates/org-gnome-templates.eplug.xml +++ b/plugins/templates/org-gnome-templates.eplug.xml @@ -5,9 +5,12 @@ id="org.gnome.evolution.plugin.templates" location="@PLUGINDIR@/liborg-gnome-templates@SOEXT@" _name="Templates"> - <_description>Drafts based template plugin + <_description>Drafts based template plugin. + + You can use variables like $ORIG[subject], $ORIG[from], $ORIG[to] or $ORIG[body], which will be replaced by values from an email you are replying to. + str; + while (next = strstr_nocase (p, find), next) { + if (p < next) + g_string_append_len (str, p, next - p); + if (replacement && *replacement) + g_string_append (str, replacement); + p = next + find_len; + } + g_string_append (str, p); + + g_string_assign (text, str->str); + + g_string_free (str, TRUE); + g_free (find); +} + +static void +replace_email_addresses (GString *template, CamelInternetAddress *internet_address, const gchar *variable) +{ + gint address_index = 0; + GString *emails = g_string_new (""); + const gchar *address_name, *address_email; + + g_return_if_fail (template); + g_return_if_fail (internet_address); + g_return_if_fail (variable); + + while (camel_internet_address_get (internet_address, address_index, &address_name, &address_email)) { + gchar *address = camel_internet_address_format_address (address_name, address_email); + + if (address_index > 0) + g_string_append_printf (emails, ", %s", address); + else + g_string_append_printf (emails, "%s", address); + + address_index++; + g_free (address); + } + replace_template_variable (template, variable, emails->str); + g_string_free (emails, TRUE); +} + +static CamelMimePart* +fill_template (CamelMimeMessage *message, CamelMimePart *template) +{ + struct _camel_header_raw *header; + CamelContentType *ct; + CamelStream *stream; + CamelMimePart *return_part; + CamelMimePart *message_part = NULL; + CamelDataWrapper *dw; + + CamelInternetAddress *internet_address; + + GString *template_body; + GByteArray *byte_array; + + gint i; + gboolean message_html, template_html; + + ct = camel_mime_part_get_content_type (template); + template_html = ct && camel_content_type_is (ct, "text", "html"); + + message_html = FALSE; + /* When template is html, then prefer HTML part of the original message. Otherwise go for plaintext */ + dw = camel_medium_get_content (CAMEL_MEDIUM (message)); + if (CAMEL_IS_MULTIPART (dw)) { + CamelMultipart *multipart = CAMEL_MULTIPART (dw); + + for (i = 0; i < camel_multipart_get_number (multipart); i++) { + CamelMimePart *part = camel_multipart_get_part (multipart, i); + CamelContentType *ct = camel_mime_part_get_content_type (part); + + if (!ct) + continue; + + if (camel_content_type_is (ct, "text", "html") && template_html) { + message_part = camel_multipart_get_part (multipart, i); + message_html = TRUE; + break; + } else if (camel_content_type_is (ct, "text", "plain") && message_html == FALSE) { + message_part = camel_multipart_get_part (multipart, i); + } + } + } else + message_part = CAMEL_MIME_PART (message); + + /* Get content of the template */ + stream = camel_stream_mem_new (); + camel_data_wrapper_decode_to_stream_sync (camel_medium_get_content (CAMEL_MEDIUM (template)), stream, NULL, NULL); + camel_stream_flush (stream, NULL, NULL); + byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream)); + template_body = g_string_new_len ((gchar *) byte_array->data, byte_array->len); + g_object_unref (stream); + + /* Replace all $ORIG[header_name] by respective values */ + header = CAMEL_MIME_PART (message)->headers; + while (header) { + if (g_ascii_strncasecmp (header->name, "content-", 8) != 0 && + g_ascii_strncasecmp (header->name, "to", 2) != 0 && + g_ascii_strncasecmp (header->name, "cc", 2) != 0 && + g_ascii_strncasecmp (header->name, "bcc", 3) != 0 && + g_ascii_strncasecmp (header->name, "from", 4) != 0 && + g_ascii_strncasecmp (header->name, "subject", 7) != 0) + replace_template_variable (template_body, header->name, header->value); + + header = header->next; + } + + /* Now manually replace the *subject* header. The header->value for subject header could be + base64 encoded, so let camel_mime_message to decode it for us if needed */ + replace_template_variable (template_body, "subject", camel_mime_message_get_subject (message)); + + /* Replace TO and FROM modifiers. */ + internet_address = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); + replace_email_addresses (template_body, internet_address, "to"); + + internet_address = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); + replace_email_addresses (template_body, internet_address, "cc"); + + internet_address = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC); + replace_email_addresses (template_body, internet_address, "bcc"); + + internet_address = camel_mime_message_get_from (message); + replace_email_addresses (template_body, internet_address, "from"); + + /* Now extract body of the original message and replace the $ORIG[body] modifier in template */ + if (message_part && strstr_nocase (template_body->str, "$ORIG[body]")) { + GString *message_body; + + stream = camel_stream_mem_new (); + camel_data_wrapper_decode_to_stream_sync (camel_medium_get_content (CAMEL_MEDIUM (message_part)), stream, NULL, NULL); + camel_stream_flush (stream, NULL, NULL); + byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream)); + message_body = g_string_new_len ((gchar*)byte_array->data, byte_array->len); + g_object_unref (stream); + + if (template_html && !message_html) { + gchar *html = camel_text_to_html (message_body->str, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | + CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | + CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | + CAMEL_MIME_FILTER_TOHTML_MARK_CITATION | + CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0); + g_string_assign (message_body, html); + g_free (html); + } else if (!template_html && message_html) { + g_string_prepend (message_body, "
");
+			g_string_append (message_body, "
"); + } /* Other cases should not occur. And even if they happen to do, there's nothing we can really do about it */ + + replace_template_variable (template_body, "body", message_body->str); + g_string_free (message_body, TRUE); + } else { + replace_template_variable (template_body, "body", ""); + } + + return_part = camel_mime_part_new (); + + if (template_html) + camel_mime_part_set_content (return_part, template_body->str, template_body->len, "text/html"); + else + camel_mime_part_set_content (return_part, template_body->str, template_body->len, "text/plain"); + + g_string_free (template_body, TRUE); + + return return_part; +} + static void create_new_message (CamelFolder *folder, const gchar *uid, CamelMimeMessage *message, gpointer data) { - GtkAction *action = data; - CamelMimeMessage *new, *template; + CamelMimeMessage *new; + CamelMimeMessage *template = CAMEL_MIME_MESSAGE (data); + CamelMultipart *new_multipart; + CamelContentType *new_content_type = NULL; + CamelDataWrapper *dw; struct _camel_header_raw *header; - CamelStream *mem; EShell *shell; + gint i; + + CamelMimePart *template_part = NULL; + CamelMimePart *out_part = NULL; - g_return_if_fail (data != NULL); + g_return_if_fail (template != NULL); g_return_if_fail (message != NULL); /* FIXME Pass this in somehow. */ shell = e_shell_get_default (); folder = e_mail_local_get_folder (E_MAIL_LOCAL_FOLDER_TEMPLATES); - template = g_object_get_data (G_OBJECT (action), "template"); - /* The new message we are creating */ new = camel_mime_message_new (); + new_multipart = camel_multipart_new (); + camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (new_multipart), "multipart/alternative"); + camel_multipart_set_boundary (new_multipart, NULL); + + dw = camel_medium_get_content (CAMEL_MEDIUM (template)); + /* If template is a multipart, then try to use HTML. When no HTML part is available, use plaintext. Every other + add as an attachment */ + if (CAMEL_IS_MULTIPART (dw)) { + for (i = 0; i < camel_multipart_get_number (CAMEL_MULTIPART (dw)); i++) { + CamelMimePart *part = camel_multipart_get_part (CAMEL_MULTIPART (dw), i); + CamelContentType *ct = camel_mime_part_get_content_type (part); + + if (ct && camel_content_type_is (ct, "text", "html")) { + new_content_type = ct; + template_part = camel_multipart_get_part (CAMEL_MULTIPART (dw), i); + } else if (ct && camel_content_type_is (ct, "text", "plain") && new_content_type == NULL) { + new_content_type = ct; + template_part = camel_multipart_get_part (CAMEL_MULTIPART (dw), i); + } else { + /* Copy any other parts (attachments...) to the output message */ + camel_mime_part_set_disposition (part, "attachment"); + camel_multipart_add_part (new_multipart, part); + } + } + } else { + CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (template)); - /* make the exact copy of the template message, with all - its attachments and message structure */ - mem = camel_stream_mem_new (); - camel_data_wrapper_write_to_stream_sync ( - CAMEL_DATA_WRAPPER (template), mem, NULL, NULL); - camel_stream_reset (mem, NULL); - camel_data_wrapper_construct_from_stream_sync ( - CAMEL_DATA_WRAPPER (new), mem, NULL, NULL); - g_object_unref (mem); + if (ct && (camel_content_type_is (ct, "text", "html") || + camel_content_type_is (ct, "text", "plain"))) { + template_part = CAMEL_MIME_PART (template); + new_content_type = ct; + } + } + + /* Here replace all the modifiers in template body by values from message and return the newly created part */ + out_part = fill_template (message, template_part); + + /* Assigning part directly to mime_message causes problem with "Content-type" header displaying + in the HTML message (camel parsing bug?) */ + camel_multipart_add_part (new_multipart, out_part); + g_object_unref (out_part); + camel_medium_set_content (CAMEL_MEDIUM (new), CAMEL_DATA_WRAPPER (new_multipart)); /* Add the headers from the message we are replying to, so CC and that - * stuff is preserved. */ - header = ((CamelMimePart *)message)->headers; + stuff is preserved. Also replace any $ORIG[header-name] modifiers ignoring + 'content-*' headers */ + header = CAMEL_MIME_PART (message)->headers; while (header) { if (g_ascii_strncasecmp (header->name, "content-", 8) != 0) { - camel_medium_add_header ((CamelMedium *) new, - header->name, - header->value); + + /* Some special handling of the 'subject' header */ + if (g_ascii_strncasecmp (header->name, "subject", 7) == 0) { + GString *subject = g_string_new (camel_mime_message_get_subject (template)); + + /* Now replace all possible $ORIG[]s in the subject line by values from original message */ + struct _camel_header_raw *m_header = CAMEL_MIME_PART (message)->headers; + while (m_header) { + if (g_ascii_strncasecmp (m_header->name, "content-", 8) != 0 && + g_ascii_strncasecmp (m_header->name, "subject", 7) !=0) + replace_template_variable (subject, m_header->name, m_header->value); + + m_header = m_header->next; + } + /* Now replace $ORIG[subject] variable, handling possible base64 encryption */ + replace_template_variable (subject, "subject", + camel_mime_message_get_subject (message)); + header->value = g_strdup (subject->str); + g_string_free (subject, TRUE); + } + + camel_medium_add_header (CAMEL_MEDIUM (new), + header->name, + header->value); } + header = header->next; } @@ -522,38 +805,55 @@ create_new_message (CamelFolder *folder, const gchar *uid, CamelMimeMessage *mes /* Create the composer */ em_utils_edit_message (shell, folder, new); + g_object_unref (template); + g_object_unref (new_multipart); g_object_unref (new); } static void action_reply_with_template_cb (GtkAction *action, - const gchar *message_uid) + EShellView *shell_view) { - CamelFolder *folder; + CamelFolder *folder, *template_folder; + EShellContent *shell_content; + EMailReader *reader; + GPtrArray *uids; + const gchar *uid; + CamelMimeMessage *template; - g_return_if_fail (message_uid != NULL); + shell_content = e_shell_view_get_shell_content (shell_view); + reader = E_MAIL_READER (shell_content); + folder = e_mail_reader_get_folder (reader); + uids = e_mail_reader_get_selected_uids (reader); - folder = CAMEL_FOLDER (g_object_get_data (G_OBJECT (action), "message_folder")); - g_return_if_fail (folder != NULL); + if (!uids->len || !folder) + return; g_object_ref (G_OBJECT (action)); - mail_get_message (folder, message_uid, create_new_message, action, mail_msg_unordered_push); + template_folder = g_object_get_data (G_OBJECT (action), "template-folder"); + uid = g_object_get_data (G_OBJECT (action), "template-uid"); + template = camel_folder_get_message_sync (template_folder, uid, NULL, NULL); + + mail_get_message (folder, uids->pdata[0], create_new_message, + (gpointer)template, mail_msg_unordered_push); g_object_unref (G_OBJECT (action)); + + em_utils_uids_free (uids); } static void build_template_menus_recurse (GtkUIManager *ui_manager, - GtkActionGroup *action_group, + GtkActionGroup *action_group, const gchar *menu_path, guint *action_count, guint merge_id, CamelFolderInfo *folder_info, - CamelFolder *message_folder, - const gchar *message_uid) + EShellView *shell_view) { CamelStore *store; + EShellWindow *shell_window = e_shell_view_get_shell_window (shell_view); store = e_mail_local_get_store (); @@ -591,6 +891,12 @@ build_template_menus_recurse (GtkUIManager *ui_manager, ui_manager, merge_id, menu_path, action_name, action_name, GTK_UI_MANAGER_MENU, FALSE); + /* Disconnect previous connection to avoid possible multiple calls because + folder is a persistent structure */ + g_signal_handlers_disconnect_by_func (folder, G_CALLBACK (templates_folder_msg_changed_cb), shell_window); + g_signal_connect (folder, "changed", + G_CALLBACK (templates_folder_msg_changed_cb), shell_window); + path = g_strdup_printf ("%s/%s", menu_path, action_name); g_object_unref (action); @@ -601,7 +907,7 @@ build_template_menus_recurse (GtkUIManager *ui_manager, build_template_menus_recurse ( ui_manager, action_group, path, action_count, merge_id, - folder_info->child, message_folder, message_uid); + folder_info->child, shell_view); if (!folder) { g_free (path); @@ -613,7 +919,7 @@ build_template_menus_recurse (GtkUIManager *ui_manager, uids = camel_folder_get_uids (folder); for (ii = 0; uids && ii < uids->len; ii++) { CamelMimeMessage *template; - const gchar *uid = uids->pdata[ii], *muid; + const gchar *uid = uids->pdata[ii]; guint32 flags; /* If the UIDs is marked for deletion, skip it. */ @@ -641,25 +947,14 @@ build_template_menus_recurse (GtkUIManager *ui_manager, action = gtk_action_new ( action_name, action_label, NULL, NULL); - muid = camel_pstring_strdup (message_uid); - g_object_ref (message_folder); - - g_object_set_data_full ( - G_OBJECT (action), "message_uid", (gpointer) muid, - (GDestroyNotify) camel_pstring_free); - - g_object_set_data_full ( - G_OBJECT (action), "message_folder", message_folder, - (GDestroyNotify) g_object_unref); + g_object_set_data(G_OBJECT (action), "template-uid", (gpointer) uid); - g_object_set_data_full ( - G_OBJECT (action), "template", template, - (GDestroyNotify) g_object_unref); + g_object_set_data(G_OBJECT (action), "template-folder", folder); g_signal_connect ( action, "activate", G_CALLBACK (action_reply_with_template_cb), - (gpointer) muid); + shell_view); gtk_action_group_add_action (action_group, action); @@ -669,6 +964,7 @@ build_template_menus_recurse (GtkUIManager *ui_manager, g_object_unref (action); g_free (action_name); + g_object_unref (template); } camel_folder_free_uids (folder, uids); @@ -750,52 +1046,28 @@ static GtkActionEntry composer_entries[] = { }; static void -update_actions_cb (EShellView *shell_view) +build_menu (EShellWindow *shell_window, + GtkActionGroup *action_group) { - EShellContent *shell_content; - EShellWindow *shell_window; - GtkActionGroup *action_group; - GtkUIManager *ui_manager; - CamelFolderInfo *folder_info; - CamelFolder *templates_folder; + EShellView *shell_view; CamelFolder *folder; CamelStore *store; - EMailReader *reader; - GPtrArray *uids; - const gchar *full_name; - guint action_count = 0; + CamelFolderInfo *folder_info; + GtkUIManager *ui_manager; guint merge_id; - gpointer data; - - shell_content = e_shell_view_get_shell_content (shell_view); - shell_window = e_shell_view_get_shell_window (shell_view); + guint action_count = 0; + const gchar *full_name; ui_manager = e_shell_window_get_ui_manager (shell_window); - action_group = e_lookup_action_group (ui_manager, "templates"); - data = g_object_get_data (G_OBJECT (action_group), "merge-id"); - merge_id = GPOINTER_TO_UINT (data); - g_return_if_fail (merge_id > 0); + shell_view = e_shell_window_get_shell_view (shell_window, "mail"); - gtk_ui_manager_remove_ui (ui_manager, merge_id); - e_action_group_remove_all_actions (action_group); - gtk_ui_manager_ensure_update (ui_manager); - - if (!plugin_enabled) - return; - - reader = E_MAIL_READER (shell_content); - folder = e_mail_reader_get_folder (reader); - uids = e_mail_reader_get_selected_uids (reader); - - if (uids->len != 1) - goto exit; + merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action_group), "merge-id")); /* Now recursively build template submenus in the pop-up menu. */ - store = e_mail_local_get_store (); - templates_folder = e_mail_local_get_folder ( + folder = e_mail_local_get_folder ( E_MAIL_LOCAL_FOLDER_TEMPLATES); - full_name = camel_folder_get_full_name (templates_folder); + full_name = camel_folder_get_full_name (folder); /* FIXME Not passing a GCancellable or GError here. */ folder_info = camel_store_get_folder_info_sync ( @@ -807,11 +1079,32 @@ update_actions_cb (EShellView *shell_view) ui_manager, action_group, "/mail-message-popup/mail-message-templates", &action_count, merge_id, folder_info, - folder, uids->pdata[0]); + shell_view); camel_store_free_folder_info (store, folder_info); -exit: - em_utils_uids_free (uids); +} + +static void +update_actions_cb (EShellView *shell_view, GtkActionGroup *action_group) +{ + GList *list; + gint length; + + if (!plugin_enabled) + return; + + list = gtk_action_group_list_actions (action_group); + length = g_list_length (list); + + if (!length) { + EShellWindow *shell_window = e_shell_view_get_shell_window (shell_view); + build_menu (shell_window, action_group); + } + + gtk_action_group_set_sensitive (action_group, TRUE); + gtk_action_group_set_visible (action_group, TRUE); + + g_list_free (list); } gboolean @@ -831,31 +1124,89 @@ init_composer_actions (GtkUIManager *ui_manager, } static void -mail_shell_view_created_cb (EShellWindow *shell_window, - EShellView *shell_view) +rebuild_template_menu (EShellWindow *shell_window) +{ + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + guint merge_id; + + ui_manager = e_shell_window_get_ui_manager (shell_window); + + action_group = e_lookup_action_group (ui_manager, "templates"); + merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action_group), "merge-id")); + + gtk_ui_manager_remove_ui (ui_manager, merge_id); + e_action_group_remove_all_actions (action_group); + gtk_ui_manager_ensure_update (ui_manager); + + build_menu (shell_window, action_group); +} + +static void +templates_folder_msg_changed_cb (CamelFolder *folder, + CamelFolderChangeInfo *change_info, + EShellWindow *shell_window) { - g_signal_connect ( - shell_view, "update-actions", - G_CALLBACK (update_actions_cb), NULL); + rebuild_template_menu (shell_window); } -gboolean -init_shell_actions (GtkUIManager *ui_manager, - EShellWindow *shell_window) +static void +templates_folder_changed_cb (CamelStore *store, + CamelFolderInfo *folder_info, + EShellWindow *shell_window) { - EShellView *shell_view; + if (folder_info->full_name && strstr (folder_info->full_name, _("Templates")) != NULL) + rebuild_template_menu (shell_window); +} + +static void +templates_folder_renamed_cb (CamelStore *store, + const gchar *old_name, + CamelFolderInfo *folder_info, + EShellWindow *shell_window) +{ + if (folder_info->full_name && strstr (folder_info->full_name, _("Templates")) != NULL) + rebuild_template_menu (shell_window); +} + +static void +mail_shell_view_created_cb (EShellWindow *shell_window, + EShellView *shell_view) +{ + GtkUIManager *ui_manager; GtkActionGroup *action_group; + CamelFolder *folder; + CamelStore *store; guint merge_id; - /* This is where we keep dynamically-built menu items. */ + ui_manager = e_shell_window_get_ui_manager (shell_window); e_shell_window_add_action_group (shell_window, "templates"); action_group = e_lookup_action_group (ui_manager, "templates"); - merge_id = gtk_ui_manager_new_merge_id (ui_manager); + g_object_set_data (G_OBJECT (action_group), "merge-id", + GUINT_TO_POINTER (merge_id)); - g_object_set_data ( - G_OBJECT (action_group), "merge-id", - GUINT_TO_POINTER (merge_id)); + folder = e_mail_local_get_folder (E_MAIL_LOCAL_FOLDER_TEMPLATES); + store = e_mail_local_get_store (); + + g_signal_connect (folder, "changed", + G_CALLBACK (templates_folder_msg_changed_cb), shell_window); + g_signal_connect (store, "folder-created", + G_CALLBACK (templates_folder_changed_cb), shell_window); + g_signal_connect (store, "folder-deleted", + G_CALLBACK (templates_folder_changed_cb), shell_window); + g_signal_connect (store, "folder-renamed", + G_CALLBACK (templates_folder_renamed_cb), shell_window); + + g_signal_connect (shell_view, "update-actions", + G_CALLBACK (update_actions_cb), action_group); +} + +gboolean +init_shell_actions (GtkUIManager *ui_manager, + EShellWindow *shell_window) +{ + EShellView *shell_view; /* Be careful not to instantiate the mail view ourselves. */ shell_view = e_shell_window_peek_shell_view (shell_window, "mail"); -- cgit v1.2.3