0
|
1 <?php
|
|
2
|
|
3 /**
|
|
4 * Redundant attachments
|
|
5 *
|
|
6 * This plugin provides a redundant storage for temporary uploaded
|
|
7 * attachment files. They are stored in both the database backend
|
|
8 * as well as on the local file system.
|
|
9 *
|
|
10 * It provides also memcache store as a fallback (see config file).
|
|
11 *
|
|
12 * This plugin relies on the core filesystem_attachments plugin
|
|
13 * and combines it with the functionality of the database_attachments plugin.
|
|
14 *
|
|
15 * @author Thomas Bruederli <roundcube@gmail.com>
|
|
16 * @author Aleksander Machniak <machniak@kolabsys.com>
|
|
17 *
|
|
18 * Copyright (C) 2011, The Roundcube Dev Team
|
|
19 * Copyright (C) 2011, Kolab Systems AG
|
|
20 *
|
|
21 * This program is free software; you can redistribute it and/or modify
|
|
22 * it under the terms of the GNU General Public License version 2
|
|
23 * as published by the Free Software Foundation.
|
|
24 *
|
|
25 * This program is distributed in the hope that it will be useful,
|
|
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
28 * GNU General Public License for more details.
|
|
29 *
|
|
30 * You should have received a copy of the GNU General Public License along
|
|
31 * with this program; if not, write to the Free Software Foundation, Inc.,
|
|
32 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
33 */
|
|
34
|
|
35 if (class_exists('filesystem_attachments', false) && !defined('TESTS_DIR')) {
|
|
36 die("Configuration issue. There can be only one enabled plugin for attachments handling");
|
|
37 }
|
|
38
|
|
39 require_once(RCUBE_PLUGINS_DIR . 'filesystem_attachments/filesystem_attachments.php');
|
|
40
|
|
41 class redundant_attachments extends filesystem_attachments
|
|
42 {
|
|
43 // A prefix for the cache key used in the session and in the key field of the cache table
|
|
44 const PREFIX = "ATTACH";
|
|
45
|
|
46 // rcube_cache instance for SQL DB
|
|
47 private $cache;
|
|
48
|
|
49 // rcube_cache instance for memcache
|
|
50 private $mem_cache;
|
|
51
|
|
52 private $loaded;
|
|
53
|
|
54
|
|
55 /**
|
|
56 * Loads plugin configuration and initializes cache object(s)
|
|
57 */
|
|
58 private function _load_drivers()
|
|
59 {
|
|
60 if ($this->loaded) {
|
|
61 return;
|
|
62 }
|
|
63
|
|
64 $rcmail = rcube::get_instance();
|
|
65
|
|
66 // load configuration
|
|
67 $this->load_config();
|
|
68
|
|
69 $ttl = 12 * 60 * 60; // 12 hours
|
|
70 $ttl = $rcmail->config->get('redundant_attachments_cache_ttl', $ttl);
|
|
71 $prefix = self::PREFIX;
|
|
72
|
|
73 if ($id = session_id()) {
|
|
74 $prefix .= $id;
|
|
75 }
|
|
76
|
|
77 // Init SQL cache (disable cache data serialization)
|
|
78 $this->cache = $rcmail->get_cache($prefix, 'db', $ttl, false);
|
|
79
|
|
80 // Init memcache (fallback) cache
|
|
81 if ($rcmail->config->get('redundant_attachments_memcache')) {
|
|
82 $this->mem_cache = $rcmail->get_cache($prefix, 'memcache', $ttl, false);
|
|
83 }
|
|
84
|
|
85 $this->loaded = true;
|
|
86 }
|
|
87
|
|
88 /**
|
|
89 * Helper method to generate a unique key for the given attachment file
|
|
90 */
|
|
91 private function _key($args)
|
|
92 {
|
|
93 $uname = $args['path'] ?: $args['name'];
|
|
94 return $args['group'] . md5(time() . $uname . $_SESSION['user_id']);
|
|
95 }
|
|
96
|
|
97 /**
|
|
98 * Save a newly uploaded attachment
|
|
99 */
|
|
100 function upload($args)
|
|
101 {
|
|
102 $args = parent::upload($args);
|
|
103
|
|
104 $this->_load_drivers();
|
|
105
|
|
106 $key = $this->_key($args);
|
|
107 $data = base64_encode(file_get_contents($args['path']));
|
|
108
|
|
109 $status = $this->cache->write($key, $data);
|
|
110
|
|
111 if (!$status && $this->mem_cache) {
|
|
112 $status = $this->mem_cache->write($key, $data);
|
|
113 }
|
|
114
|
|
115 if ($status) {
|
|
116 $args['id'] = $key;
|
|
117 $args['status'] = true;
|
|
118 }
|
|
119
|
|
120 return $args;
|
|
121 }
|
|
122
|
|
123 /**
|
|
124 * Save an attachment from a non-upload source (draft or forward)
|
|
125 */
|
|
126 function save($args)
|
|
127 {
|
|
128 $args = parent::save($args);
|
|
129
|
|
130 $this->_load_drivers();
|
|
131
|
|
132 $data = $args['path'] ? file_get_contents($args['path']) : $args['data'];
|
|
133
|
|
134 $args['data'] = null;
|
|
135
|
|
136 $key = $this->_key($args);
|
|
137 $data = base64_encode($data);
|
|
138
|
|
139 $status = $this->cache->write($key, $data);
|
|
140
|
|
141 if (!$status && $this->mem_cache) {
|
|
142 $status = $this->mem_cache->write($key, $data);
|
|
143 }
|
|
144
|
|
145 if ($status) {
|
|
146 $args['id'] = $key;
|
|
147 $args['status'] = true;
|
|
148 }
|
|
149
|
|
150 return $args;
|
|
151 }
|
|
152
|
|
153 /**
|
|
154 * Remove an attachment from storage
|
|
155 * This is triggered by the remove attachment button on the compose screen
|
|
156 */
|
|
157 function remove($args)
|
|
158 {
|
|
159 parent::remove($args);
|
|
160
|
|
161 $this->_load_drivers();
|
|
162
|
|
163 $status = $this->cache->remove($args['id']);
|
|
164
|
|
165 if (!$status && $this->mem_cache) {
|
|
166 $status = $this->cache->remove($args['id']);
|
|
167 }
|
|
168
|
|
169 // we cannot trust the result of any of the methods above
|
|
170 // assume true, attachments will be removed on cleanup
|
|
171 $args['status'] = true;
|
|
172
|
|
173 return $args;
|
|
174 }
|
|
175
|
|
176 /**
|
|
177 * When composing an html message, image attachments may be shown
|
|
178 * For this plugin, $this->get() will check the file and
|
|
179 * return it's contents
|
|
180 */
|
|
181 function display($args)
|
|
182 {
|
|
183 return $this->get($args);
|
|
184 }
|
|
185
|
|
186 /**
|
|
187 * When displaying or sending the attachment the file contents are fetched
|
|
188 * using this method. This is also called by the attachment_display hook.
|
|
189 */
|
|
190 function get($args)
|
|
191 {
|
|
192 // attempt to get file from local file system
|
|
193 $args = parent::get($args);
|
|
194
|
|
195 if ($args['path'] && ($args['status'] = file_exists($args['path'])))
|
|
196 return $args;
|
|
197
|
|
198 $this->_load_drivers();
|
|
199
|
|
200 // fetch from database if not found on FS
|
|
201 $data = $this->cache->read($args['id']);
|
|
202
|
|
203 // fetch from memcache if not found on FS and DB
|
|
204 if (($data === false || $data === null) && $this->mem_cache) {
|
|
205 $data = $this->mem_cache->read($args['id']);
|
|
206 }
|
|
207
|
|
208 if ($data) {
|
|
209 $args['data'] = base64_decode($data);
|
|
210 $args['status'] = true;
|
|
211 }
|
|
212
|
|
213 return $args;
|
|
214 }
|
|
215
|
|
216 /**
|
|
217 * Delete all temp files associated with this user
|
|
218 */
|
|
219 function cleanup($args)
|
|
220 {
|
|
221 $this->_load_drivers();
|
|
222
|
|
223 if ($this->cache) {
|
|
224 $this->cache->remove($args['group'], true);
|
|
225 }
|
|
226
|
|
227 if ($this->mem_cache) {
|
|
228 $this->mem_cache->remove($args['group'], true);
|
|
229 }
|
|
230
|
|
231 parent::cleanup($args);
|
|
232
|
|
233 $args['status'] = true;
|
|
234
|
|
235 return $args;
|
|
236 }
|
|
237 }
|