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