Aleksander Machniak
2015-11-22 9f1f754daf4b57a0d0d3aea95d2321716d218cf5
commit | author | age
48e9c1 1 <?php
b0349c 2
48e9c1 3 /**
T 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
37e9bd 35 if (class_exists('filesystem_attachments', false) && !defined('TESTS_DIR')) {
b0349c 36     die("Configuration issue. There can be only one enabled plugin for attachments handling");
AM 37 }
38
e2e2e8 39 require_once(RCUBE_PLUGINS_DIR . 'filesystem_attachments/filesystem_attachments.php');
48e9c1 40
T 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
e9ca5e 44     const PREFIX = "ATTACH";
48e9c1 45
T 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
1c861d 64         $rcmail = rcube::get_instance();
48e9c1 65
T 66         // load configuration
67         $this->load_config();
68
e9ca5e 69         $ttl    = 12 * 60 * 60; // 12 hours
AM 70         $ttl    = $rcmail->config->get('redundant_attachments_cache_ttl', $ttl);
71         $prefix = self::PREFIX;
72
73         if ($id = session_id()) {
74             $prefix .= $id;
75         }
df9d00 76
48e9c1 77         // Init SQL cache (disable cache data serialization)
e9ca5e 78         $this->cache = $rcmail->get_cache($prefix, 'db', $ttl, false);
48e9c1 79
T 80         // Init memcache (fallback) cache
81         if ($rcmail->config->get('redundant_attachments_memcache')) {
e9ca5e 82             $this->mem_cache = $rcmail->get_cache($prefix, 'memcache', $ttl, false);
48e9c1 83         }
T 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     {
9f1f75 93         $uname = $args['path'] ?: $args['name'];
0389fa 94         return $args['group'] . md5(time() . $uname . $_SESSION['user_id']);
48e9c1 95     }
T 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
cccf8a 132         $data = $args['path'] ? file_get_contents($args['path']) : $args['data'];
AM 133
b3bf9c 134         $args['data'] = null;
48e9c1 135
T 136         $key  = $this->_key($args);
cccf8a 137         $data = base64_encode($data);
48e9c1 138
T 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 }