Merge branch 'master' of ssh://git.claws-mail.org/home/git/claws
[claws.git] / src / plugins / attachwarner / attachwarner.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail Team
4  * Copyright (C) 2006-2013 Ricardo Mones
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation, 
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #include "claws-features.h"
24 #endif
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28
29 #include "version.h"
30 #include "attachwarner.h"
31 #include "attachwarner_prefs.h"
32 #include "codeconv.h"
33 #include "prefs_common.h"
34
35 /** Identifier for the hook. */
36 static guint hook_id;
37
38 static AttachWarnerMention *aw_matcherlist_string_match(MatcherList *matchers, gchar *str, gchar *sig_separator)
39 {
40         MsgInfo info;
41         int i = 0;
42         gboolean ret = FALSE;
43         gchar **lines = NULL;
44         AttachWarnerMention *awm = NULL; 
45
46         if (str == NULL || *str == '\0') {
47                 return awm;
48         }
49         
50         lines = g_strsplit(str, "\n", -1);
51         if (attwarnerprefs.skip_quotes
52                 && *prefs_common_get_prefs()->quote_chars != '\0') {
53                 debug_print("checking without quotes\n");
54                 for (i = 0; lines[i] != NULL && ret == FALSE; i++) {
55                         if(attwarnerprefs.skip_signature
56                                 && sig_separator != NULL
57                                 && *sig_separator != '\0'
58                                 && strcmp(lines[i], sig_separator) == 0) {
59                                 debug_print("reached signature delimiter at line %d\n", i);
60                                 break;
61                         }
62                         if (line_has_quote_char(lines[i], 
63                                 prefs_common_get_prefs()->quote_chars) == NULL) {
64                                 debug_print("testing line %d\n", i);
65                                 info.subject = lines[i];
66                                 ret = matcherlist_match(matchers, &info);
67                                 debug_print("line %d: %d\n", i, ret);
68                         }
69                 }
70         } else {
71                 debug_print("checking with quotes\n");
72                 for (i = 0; lines[i] != NULL && ret == FALSE; i++) {
73                         if(attwarnerprefs.skip_signature
74                                 && sig_separator != NULL
75                                 && *sig_separator != '\0'
76                                 && strcmp(lines[i], sig_separator) == 0) {
77                                 debug_print("reached signature delimiter at line %d\n", i);
78                                 break;
79                         }
80                         debug_print("testing line %d\n", i);
81                         info.subject = lines[i];
82                         ret = matcherlist_match(matchers, &info);
83                         debug_print("line %d: %d\n", i, ret);
84                 }
85         }
86         if (ret != FALSE) {
87                 awm = g_new0(AttachWarnerMention, 1);
88                 awm->line = i; /* usual humans count lines from 1 */
89                 awm->context = g_strdup(lines[i - 1]);
90                 debug_print("found at line %d, context \"%s\"\n", awm->line, awm->context);
91         }
92         g_strfreev(lines);
93
94         return awm;
95 }
96
97 /**
98  * Looks for attachment references in the composer text.
99  *
100  * @param compose The composer object to inspect.
101  *
102  * @return A pointer to an AttachWarnerMention if attachment references
103  * are found, or NULL otherwise.
104  */
105 AttachWarnerMention *are_attachments_mentioned(Compose *compose)
106 {
107         GtkTextView *textview = NULL;
108         GtkTextBuffer *textbuffer = NULL;
109         GtkTextIter start, end;
110         gchar *text = NULL;
111         AttachWarnerMention *mention = NULL;
112         MatcherList *matchers = NULL;
113
114         matchers = matcherlist_new_from_lines(attwarnerprefs.match_strings, FALSE, attwarnerprefs.case_sensitive);
115
116         if (matchers == NULL) {
117                 g_warning("couldn't allocate matcher");
118                 return FALSE;
119         }
120
121         textview = GTK_TEXT_VIEW(compose->text);
122         textbuffer = gtk_text_view_get_buffer(textview);
123         gtk_text_buffer_get_start_iter(textbuffer, &start);
124         gtk_text_buffer_get_end_iter(textbuffer, &end);
125         text = gtk_text_buffer_get_text(textbuffer, &start, &end, FALSE);
126
127         debug_print("checking text for attachment mentions\n");
128         if (text != NULL) {
129                 mention = aw_matcherlist_string_match(matchers, text, compose->account->sig_sep);
130                 g_free(text);
131         }       
132         if (matchers != NULL)
133                 matcherlist_free(matchers);
134         debug_print("done\n");
135         return mention;
136 }
137
138 /**
139  * Looks for files attached in the composer.
140  *
141  * @param compose The composer object to inspect.
142  *
143  * @return TRUE if there is one or more files attached, FALSE otherwise.
144  */
145 gboolean does_not_have_attachments(Compose *compose)
146 {
147         GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
148         GtkTreeModel *model;
149         GtkTreeIter iter;
150
151         model = gtk_tree_view_get_model(tree_view);
152
153         debug_print("checking for attachments existence\n");
154         if (!gtk_tree_model_get_iter_first(model, &iter))
155                 return TRUE;
156
157         return FALSE;
158 }
159
160 /**
161  * Check whether not check while redirecting or forwarding.
162  *
163  * @param mode The current compose->mode.
164  *
165  * @return TRUE for cancel further checking because it's being redirected or
166  *         forwarded and user configured not to check, FALSE otherwise.
167  */
168 gboolean do_not_check_redirect_forward(int mode)
169 {
170         switch (mode) {
171         case COMPOSE_FORWARD:
172         case COMPOSE_FORWARD_AS_ATTACH:
173         case COMPOSE_FORWARD_INLINE:
174         case COMPOSE_REDIRECT:
175                 if (attwarnerprefs.skip_forwards_and_redirections)
176                         return TRUE;
177         default:
178                 return FALSE;
179         }
180 }
181
182 /**
183  * Callback function to be called before sending the mail.
184  * 
185  * @param source The composer to be checked.
186  * @param data Additional data.
187  *
188  * @return TRUE if no attachments are mentioned or files are attached,
189  *         FALSE if attachments are mentioned and no files are attached.
190  */
191 static gboolean attwarn_before_send_hook(gpointer source, gpointer data)
192 {
193         Compose *compose = (Compose *)source;
194         AttachWarnerMention *mention = NULL;
195
196         debug_print("attachwarner invoked\n");
197         if (compose->batch)
198                 return FALSE;   /* do not check while queuing */
199
200         if (do_not_check_redirect_forward(compose->mode))
201                 return FALSE;
202
203         mention = are_attachments_mentioned(compose); 
204         if (does_not_have_attachments(compose) && mention != NULL) { 
205                 AlertValue aval;
206                 gchar *button_label;
207                 gchar *message;
208                 
209                 debug_print("user has to decide\n");
210                 if (compose->sending)
211                         button_label = _("+_Send");
212                 else
213                         button_label = _("+_Queue");
214
215                 message = g_strdup_printf(
216                                 _("An attachment is mentioned in the mail you're sending, but no file was attached. Mention appears on line %d, which begins with text: <span weight=\"bold\">%.20s</span>...\n\n%s it anyway?"),
217                                 mention->line,
218                                 mention->context,
219                                 compose->sending?_("Send"):_("Queue"));
220                 aval = alertpanel(_("Attachment warning"), message,
221                                   GTK_STOCK_CANCEL, button_label, NULL);
222                 g_free(message);
223                 if (aval != G_ALERTALTERNATE)
224                         return TRUE;
225         }
226         if (mention != NULL) {
227                 if (mention->context != NULL)
228                         g_free(mention->context);
229                 g_free(mention);
230         }
231
232         return FALSE;   /* continue sending */
233 }
234
235 /**
236  * Initialize plugin.
237  *
238  * @param error  For storing the returned error message.
239  *
240  * @return 0 if initialization succeeds, -1 on failure.
241  */
242 gint plugin_init(gchar **error)
243 {
244         if (!check_plugin_version(MAKE_NUMERIC_VERSION(2,9,2,72),
245                                   VERSION_NUMERIC, _("Attach warner"), error))
246                 return -1;
247
248         hook_id = hooks_register_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, 
249                                       attwarn_before_send_hook, NULL);
250         
251         if (hook_id == -1) {
252                 *error = g_strdup(_("Failed to register check before send hook"));
253                 return -1;
254         }
255
256         attachwarner_prefs_init();
257
258         debug_print("Attachment warner plugin loaded\n");
259
260         return 0;
261 }
262
263 /**
264  * Destructor for the plugin.
265  * Unregister the callback function and frees matcher.
266  */
267 gboolean plugin_done(void)
268 {       
269         hooks_unregister_hook(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, hook_id);
270         attachwarner_prefs_done();
271         debug_print("Attachment warner plugin unloaded\n");
272         return TRUE;
273 }
274
275 /**
276  * Get the name of the plugin.
277  *
278  * @return The plugin name (maybe translated).
279  */
280 const gchar *plugin_name(void)
281 {
282         return _("Attach warner");
283 }
284
285 /**
286  * Get the description of the plugin.
287  *
288  * @return The plugin description (maybe translated).
289  */
290 const gchar *plugin_desc(void)
291 {
292         return _("Warns user if some reference to attachments is found in the "
293                  "message text and no file is attached.");
294 }
295
296 /**
297  * Get the kind of plugin.
298  *
299  * @return The "GTK2" constant.
300  */
301 const gchar *plugin_type(void)
302 {
303         return "GTK2";
304 }
305
306 /**
307  * Get the license acronym the plugin is released under.
308  *
309  * @return The "GPL" constant.
310  */
311 const gchar *plugin_licence(void)
312 {
313         return "GPL3+";
314 }
315
316 /**
317  * Get the version of the plugin.
318  *
319  * @return The current version string.
320  */
321 const gchar *plugin_version(void)
322 {
323         return VERSION;
324 }
325
326 /**
327  * Get the features implemented by the plugin.
328  *
329  * @return A constant PluginFeature structure with the features.
330  */
331 struct PluginFeature *plugin_provides(void)
332 {
333         static struct PluginFeature features[] = 
334                 { {PLUGIN_OTHER, N_("Attach warner")},
335                   {PLUGIN_NOTHING, NULL}};
336
337         return features;
338 }
339