1 /*
2  * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "evolution-config.h"
18 
19 #include <glib/gi18n-lib.h>
20 
21 #include <libebackend/libebackend.h>
22 #include <libedataserver/libedataserver.h>
23 
24 #include "e-util/e-util.h"
25 #include "composer/e-msg-composer.h"
26 #include "composer/e-composer-from-header.h"
27 #include "calendar/gui/e-comp-editor.h"
28 #include "calendar/gui/e-comp-editor-page-attachments.h"
29 #include "calendar/gui/itip-utils.h"
30 
31 #include "e-meeting-to-composer.h"
32 
33 /* Standard GObject macros */
34 #define E_TYPE_MEETING_TO_COMPOSER \
35 	(e_meeting_to_composer_get_type ())
36 #define E_MEETING_TO_COMPOSER(obj) \
37 	(G_TYPE_CHECK_INSTANCE_CAST \
38 	((obj), E_TYPE_MEETING_TO_COMPOSER, EMeetingToComposer))
39 #define E_MEETING_TO_COMPOSER_CLASS(cls) \
40 	(G_TYPE_CHECK_CLASS_CAST \
41 	((cls), E_TYPE_MEETING_TO_COMPOSER, EMeetingToComposerClass))
42 #define E_IS_MEETING_TO_COMPOSER(obj) \
43 	(G_TYPE_CHECK_INSTANCE_TYPE \
44 	((obj), E_TYPE_MEETING_TO_COMPOSER))
45 #define E_IS_MEETING_TO_COMPOSER_CLASS(cls) \
46 	(G_TYPE_CHECK_CLASS_TYPE \
47 	((cls), E_TYPE_MEETING_TO_COMPOSER))
48 #define E_MEETING_TO_COMPOSER_GET_CLASS(obj) \
49 	(G_TYPE_INSTANCE_GET_CLASS \
50 	((obj), E_TYPE_MEETING_TO_COMPOSER, EMeetingToComposerClass))
51 
52 typedef struct _EMeetingToComposer EMeetingToComposer;
53 typedef struct _EMeetingToComposerClass EMeetingToComposerClass;
54 
55 struct _EMeetingToComposer {
56 	EExtension parent;
57 };
58 
59 struct _EMeetingToComposerClass {
60 	EExtensionClass parent_class;
61 };
62 
63 GType e_meeting_to_composer_get_type (void) G_GNUC_CONST;
64 
G_DEFINE_DYNAMIC_TYPE(EMeetingToComposer,e_meeting_to_composer,E_TYPE_EXTENSION)65 G_DEFINE_DYNAMIC_TYPE (EMeetingToComposer, e_meeting_to_composer, E_TYPE_EXTENSION)
66 
67 static void
68 meeting_to_composer_unref_nonull_object (gpointer ptr)
69 {
70 	if (ptr)
71 		g_object_unref (ptr);
72 }
73 
74 static gboolean
meeting_to_composer_check_identity_source(ESource * source,const gchar * address,gchar ** alias_name,gchar ** alias_address)75 meeting_to_composer_check_identity_source (ESource *source,
76 					   const gchar *address,
77 					   gchar **alias_name,
78 					   gchar **alias_address)
79 {
80 	ESourceMailIdentity *identity_extension;
81 	GHashTable *aliases = NULL;
82 	const gchar *text;
83 	gboolean found = FALSE;
84 
85 	if (!E_IS_SOURCE (source) || !address ||
86 	    !e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY))
87 		return FALSE;
88 
89 	identity_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
90 
91 	text = e_source_mail_identity_get_address (identity_extension);
92 	found = text && g_ascii_strcasecmp (text, address) == 0;
93 
94 	if (!found) {
95 		aliases = e_source_mail_identity_get_aliases_as_hash_table (identity_extension);
96 		if (aliases) {
97 			found = g_hash_table_contains (aliases, address);
98 			if (found) {
99 				if (alias_name)
100 					*alias_name = g_strdup (g_hash_table_lookup (aliases, address));
101 				if (alias_address)
102 					*alias_address = g_strdup (address);
103 			}
104 		}
105 	}
106 
107 	if (aliases)
108 		g_hash_table_destroy (aliases);
109 
110 	return found;
111 }
112 
113 static void
meeting_to_composer_copy_attachments(ECompEditor * comp_editor,EMsgComposer * composer)114 meeting_to_composer_copy_attachments (ECompEditor *comp_editor,
115 				      EMsgComposer *composer)
116 {
117 	ECompEditorPage *page_attachments;
118 	EAttachmentView *attachment_view;
119 	EAttachmentStore *store;
120 	GList *attachments, *link;
121 
122 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
123 	g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
124 
125 	page_attachments = e_comp_editor_get_page (comp_editor, E_TYPE_COMP_EDITOR_PAGE_ATTACHMENTS);
126 	if (!page_attachments)
127 		return;
128 
129 	store = e_comp_editor_page_attachments_get_store (E_COMP_EDITOR_PAGE_ATTACHMENTS (page_attachments));
130 	attachments = e_attachment_store_get_attachments (store);
131 
132 	if (!attachments)
133 		return;
134 
135 	attachment_view = e_msg_composer_get_attachment_view (composer);
136 	store = e_attachment_view_get_store (attachment_view);
137 
138 	for (link = attachments; link; link = g_list_next (link)) {
139 		EAttachment *attachment = link->data;
140 
141 		e_attachment_store_add_attachment (store, attachment);
142 	}
143 
144 	g_list_free_full (attachments, g_object_unref);
145 }
146 
147 static void
meeting_to_composer_composer_created_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)148 meeting_to_composer_composer_created_cb (GObject *source_object,
149 					 GAsyncResult *result,
150 					 gpointer user_data)
151 {
152 	ECompEditor *comp_editor = user_data;
153 	EMsgComposer *composer;
154 	EComposerHeaderTable *header_table;
155 	gboolean did_updating;
156 	ICalComponent *icomp;
157 	ICalProperty *prop;
158 	const gchar *text;
159 	GPtrArray *to_recips, *cc_recips;
160 	GError *error = NULL;
161 
162 	g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
163 
164 	composer = e_msg_composer_new_finish (result, &error);
165 	if (!composer) {
166 		g_warning ("%s: Faild to create message composer: %s", G_STRFUNC, error ? error->message : "Unknown error");
167 		return;
168 	}
169 
170 	header_table = e_msg_composer_get_header_table (composer);
171 
172 	did_updating = e_comp_editor_get_updating (comp_editor);
173 	/* Just a trick to not show validation errors when getting the component */
174 	e_comp_editor_set_updating (comp_editor, TRUE);
175 
176 	icomp = i_cal_component_clone (e_comp_editor_get_component (comp_editor));
177 	e_comp_editor_fill_component (comp_editor, icomp);
178 
179 	e_comp_editor_set_updating (comp_editor, did_updating);
180 
181 	/* Subject */
182 	text = i_cal_component_get_summary (icomp);
183 	if (text && *text)
184 		e_composer_header_table_set_subject (header_table, text);
185 
186 	/* From */
187 	prop = i_cal_component_get_first_property (icomp, I_CAL_ORGANIZER_PROPERTY);
188 	if (prop) {
189 		EComposerHeader *from_header;
190 		const gchar *organizer;
191 
192 		from_header = e_composer_header_table_get_header (header_table, E_COMPOSER_HEADER_FROM);
193 		organizer = itip_strip_mailto (i_cal_property_get_organizer (prop));
194 
195 		if (organizer && *organizer && from_header) {
196 			GtkComboBox *identities_combo;
197 			GtkTreeModel *model;
198 			GtkTreeIter iter;
199 			gint id_column;
200 
201 			identities_combo = GTK_COMBO_BOX (from_header->input_widget);
202 			id_column = gtk_combo_box_get_id_column (identities_combo);
203 			model = gtk_combo_box_get_model (identities_combo);
204 
205 			if (gtk_tree_model_get_iter_first (model, &iter)) {
206 				do {
207 					ESource *source;
208 					gchar *uid;
209 					gboolean use_source;
210 					gchar *alias_name = NULL;
211 					gchar *alias_address = NULL;
212 
213 					gtk_tree_model_get (model, &iter, id_column, &uid, -1);
214 					source = e_composer_header_table_ref_source (header_table, uid);
215 
216 					use_source = meeting_to_composer_check_identity_source (source, organizer, &alias_name, &alias_address);
217 					if (use_source)
218 						e_composer_header_table_set_identity_uid (header_table, uid, alias_name, alias_address);
219 
220 					g_clear_object (&source);
221 					g_free (alias_name);
222 					g_free (alias_address);
223 					g_free (uid);
224 
225 					if (use_source)
226 						break;
227 				} while (gtk_tree_model_iter_next (model, &iter));
228 			}
229 		}
230 
231 		g_clear_object (&prop);
232 	}
233 
234 	/* Recipients */
235 	to_recips = g_ptr_array_new_with_free_func (meeting_to_composer_unref_nonull_object);
236 	cc_recips = g_ptr_array_new_with_free_func (meeting_to_composer_unref_nonull_object);
237 
238 	for (prop = i_cal_component_get_first_property (icomp, I_CAL_ATTENDEE_PROPERTY);
239 	     prop;
240 	     g_object_unref (prop), prop = i_cal_component_get_next_property (icomp, I_CAL_ATTENDEE_PROPERTY)) {
241 		ICalParameter *param;
242 		ICalParameterRole role = I_CAL_ROLE_REQPARTICIPANT;
243 		const gchar *name = NULL, *address;
244 		EDestination *dest;
245 
246 		address = itip_strip_mailto (i_cal_property_get_attendee (prop));
247 		if (!address || !*address)
248 			continue;
249 
250 		param = i_cal_property_get_first_parameter (prop, I_CAL_ROLE_PARAMETER);
251 		if (param) {
252 			role = i_cal_parameter_get_role (param);
253 			g_object_unref (param);
254 		}
255 
256 		if (role == I_CAL_ROLE_NONPARTICIPANT || role == I_CAL_ROLE_NONE)
257 			continue;
258 
259 		param = i_cal_property_get_first_parameter (prop, I_CAL_CN_PARAMETER);
260 		if (param)
261 			name = i_cal_parameter_get_cn (param);
262 
263 		if (name && !*name)
264 			name = NULL;
265 
266 		dest = e_destination_new ();
267 		e_destination_set_name (dest, name);
268 		e_destination_set_email (dest, address);
269 
270 		if (role == I_CAL_ROLE_REQPARTICIPANT)
271 			g_ptr_array_add (to_recips, dest);
272 		else
273 			g_ptr_array_add (cc_recips, dest);
274 
275 		g_clear_object (&param);
276 	}
277 
278 	if (to_recips->len > 0) {
279 		g_ptr_array_add (to_recips, NULL);
280 
281 		e_composer_header_table_set_destinations_to (header_table, (EDestination **) to_recips->pdata);
282 	}
283 
284 	if (cc_recips->len > 0) {
285 		g_ptr_array_add (cc_recips, NULL);
286 
287 		e_composer_header_table_set_destinations_cc (header_table, (EDestination **) cc_recips->pdata);
288 	}
289 
290 	g_ptr_array_free (to_recips, TRUE);
291 	g_ptr_array_free (cc_recips, TRUE);
292 
293 	/* Body */
294 	prop = i_cal_component_get_first_property (icomp, I_CAL_DESCRIPTION_PROPERTY);
295 	if (prop) {
296 		text = i_cal_property_get_description (prop);
297 
298 		if (text && *text) {
299 			EHTMLEditor *html_editor;
300 			EContentEditor *cnt_editor;
301 
302 			html_editor = e_msg_composer_get_editor (composer);
303 			cnt_editor = e_html_editor_get_content_editor (html_editor);
304 
305 			e_content_editor_set_html_mode (cnt_editor, FALSE);
306 			e_content_editor_insert_content (cnt_editor, text, E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_PLAIN);
307 		}
308 
309 		g_object_unref (prop);
310 	}
311 
312 	/* Attachments */
313 	meeting_to_composer_copy_attachments (comp_editor, composer);
314 
315 	gtk_window_present (GTK_WINDOW (composer));
316 
317 	gtk_widget_destroy (GTK_WIDGET (comp_editor));
318 	g_object_unref (icomp);
319 }
320 
321 static void
action_meeting_to_composer_cb(GtkAction * action,ECompEditor * comp_editor)322 action_meeting_to_composer_cb (GtkAction *action,
323 			       ECompEditor *comp_editor)
324 {
325 	ICalComponent *icomp;
326 	ICalComponentKind kind;
327 	const gchar *prompt_key;
328 
329 	g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
330 
331 	icomp = e_comp_editor_get_component (comp_editor);
332 	kind = icomp ? i_cal_component_isa (icomp) : I_CAL_VEVENT_COMPONENT;
333 
334 	if (kind == I_CAL_VTODO_COMPONENT)
335 		prompt_key = "mail-composer:prompt-task-to-composer";
336 	else if (kind == I_CAL_VJOURNAL_COMPONENT)
337 		prompt_key = "mail-composer:prompt-memo-to-composer";
338 	else
339 		prompt_key = "mail-composer:prompt-event-to-composer";
340 
341 	if (!e_util_prompt_user (GTK_WINDOW (comp_editor), NULL, NULL, prompt_key, NULL))
342 		return;
343 
344 	e_msg_composer_new (e_comp_editor_get_shell (comp_editor),
345 		meeting_to_composer_composer_created_cb, comp_editor);
346 }
347 
348 static void
e_meeting_to_composer_setup_ui(ECompEditor * comp_editor)349 e_meeting_to_composer_setup_ui (ECompEditor *comp_editor)
350 {
351 	const gchar *ui =
352 		"<ui>"
353 		"  <menubar action='main-menu'>"
354 		"    <menu action='file-menu'>"
355 		"      <placeholder name='custom-actions-placeholder'>"
356 		"        <menuitem action='meeting-to-composer-action'/>"
357 		"      </placeholder>"
358 		"    </menu>"
359 		"  </menubar>"
360 		"</ui>";
361 
362 	GtkActionEntry entries[] = {
363 		{ "meeting-to-composer-action",
364 		  "mail-message-new",
365 		  N_("Convert to M_essage"),
366 		  NULL,
367 		  N_("Convert to the mail message"),
368 		  G_CALLBACK (action_meeting_to_composer_cb) }
369 	};
370 
371 	GtkUIManager *ui_manager;
372 	GtkActionGroup *action_group;
373 	GError *error = NULL;
374 
375 	g_return_if_fail (E_IS_COMP_EDITOR (comp_editor));
376 
377 	ui_manager = e_comp_editor_get_ui_manager (comp_editor);
378 	action_group = e_comp_editor_get_action_group (comp_editor, "individual");
379 
380 	gtk_action_group_add_actions (action_group, entries, G_N_ELEMENTS (entries), comp_editor);
381 
382 	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
383 
384 	if (error) {
385 		g_critical ("%s: %s", G_STRFUNC, error->message);
386 		g_error_free (error);
387 	}
388 }
389 
390 static void
meeting_to_composer_constructed(GObject * object)391 meeting_to_composer_constructed (GObject *object)
392 {
393 	ECompEditor *comp_editor;
394 
395 	/* Chain up to parent's constructed() method. */
396 	G_OBJECT_CLASS (e_meeting_to_composer_parent_class)->constructed (object);
397 
398 	comp_editor = E_COMP_EDITOR (e_extension_get_extensible (E_EXTENSION (object)));
399 
400 	e_meeting_to_composer_setup_ui (comp_editor);
401 }
402 
403 static void
e_meeting_to_composer_class_init(EMeetingToComposerClass * class)404 e_meeting_to_composer_class_init (EMeetingToComposerClass *class)
405 {
406 	GObjectClass *object_class;
407 	EExtensionClass *extension_class;
408 
409 	object_class = G_OBJECT_CLASS (class);
410 	object_class->constructed = meeting_to_composer_constructed;
411 
412 	extension_class = E_EXTENSION_CLASS (class);
413 	extension_class->extensible_type = E_TYPE_COMP_EDITOR;
414 }
415 
416 static void
e_meeting_to_composer_class_finalize(EMeetingToComposerClass * class)417 e_meeting_to_composer_class_finalize (EMeetingToComposerClass *class)
418 {
419 }
420 
421 static void
e_meeting_to_composer_init(EMeetingToComposer * extension)422 e_meeting_to_composer_init (EMeetingToComposer *extension)
423 {
424 }
425 
426 void
e_meeting_to_composer_type_register(GTypeModule * type_module)427 e_meeting_to_composer_type_register (GTypeModule *type_module)
428 {
429 	/* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
430 	 *     function, so we have to wrap it with a public function in
431 	 *     order to register types from a separate compilation unit. */
432 	e_meeting_to_composer_register_type (type_module);
433 }
434