source: trunk/server/www/app/models/result.php @ 383

Last change on this file since 383 was 383, checked in by sander, 11 years ago

Properly clean up all files in a result. Fixes #36

File size: 7.7 KB
Line 
1<?php
2/**
3 * Officeshots.org - Test your office documents in different applications
4 * Copyright (C) 2009 Stichting Lone Wolves
5 * Written by Sander Marechal <s.marechal@jejik.com>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21App::import('Behavior', 'Pipeline');
22
23/**
24 * The Result model
25 */
26class Result extends AppModel
27{
28        /**#@+
29         * Request states
30         */
31        const STATE_UPLOADING            =  1;
32        const STATE_POSTPROCESSOR_QUEUED =  2;
33        const STATE_SCAN_FOUND           =  4;
34        const STATE_POSTPROCESSOR_FAILED =  8;
35        const STATE_FINISHED             = 16;
36        /**#@-*/
37       
38        /**#@+
39         * Manual verification states
40         */
41        const VERIFY_PENDING = 0;
42        const VERIFY_PASS    = 1;
43        const VERIFY_FAIL    = 2;
44        /**#@-*/
45
46        /** @var array A result belongs to a job and a factory that created it */
47        public $belongsTo = array('Factory', 'Format');
48
49        /**
50         * @var array A result is always associated with a job.
51         *
52         * This looks backwards but otherwise it is impossible to find Jobs that have no Result yet.
53         */
54        public $hasOne = array('Job');
55
56        /**
57         * A result can have multiple ODF validators
58         */
59        public $hasMany = array('Validator' => array('foreignKey' => 'parent_id'));
60
61        /** @var array The result is a file */
62        public $actsAs = array(
63                'Containable',
64                'BeanStalk.Deferrable',
65                'File',
66                'Pipeline' => 'Postprocessor',
67        );
68
69        /** @var string Use the filename as the distinguising name */
70        public $displayField = 'filename';
71
72        /**
73         * Constructor
74         * Build the pipelines
75         */
76        public function __construct()
77        {
78                parent::__construct();
79
80                $this->Postprocessor->callback('scan');
81                $this->Postprocessor->callback('validateFile');
82                $this->Postprocessor->callback('saveField', 'state', self::STATE_FINISHED);
83                $this->Postprocessor->errback('saveField', 'state', self::STATE_POSTPROCESSOR_FAILED);
84        }
85
86        /**
87         * Check access control for this result
88         * @param string $user_id The user ID
89         * @param string $type "read" or "write"
90         * @param string $id The result ID
91         * @return boolean True or False
92         */
93        public function checkAccess($user_id, $type = 'read', $id = false)
94        {
95                if (!$id) {
96                        $id = $this->id;
97                }
98
99                if (!$id) {
100                        return false;
101                }
102
103                if ($type == 'admin') {
104                        return true;
105                }
106
107                $result = $this->find('first', array(
108                        'contain' => array('Job', 'Job.Request'),
109                        'conditions' => array('Result.id' => $id),
110                ));
111
112                if (!empty($result)) {
113                        return $this->Job->Request->checkAccess($user_id, $type, $result['Job']['Request']['id']);
114                }
115
116                return false;
117        }
118
119        /**
120         * Add an upload to the result
121         *
122         * If the add fails, &$errors contains the error messages
123         *
124         * @param array $data The data to save if not saving $this->data, including a file and the jobs
125         * @param array &$errors The errors
126         * @return boolean Success
127         */
128        public function addUpload($data, &$errors = array())
129        {
130                if (empty($data)) {
131                        $errors[] = __('The result was empty.', true);
132                        return false;
133                }
134
135                // Check that we have a file and jobs
136                if ((!isset($data['Result']['FileUpload']) || !is_array($data['Result']['FileUpload']))) {
137                        $errors[] = __('You did not submit a file.', true);
138                        return false;
139                }
140
141                if (!$this->setFileUpload($data['Result']['FileUpload'])) {
142                        $errors[] = __('The file upload failed. Please try again.', true);
143                        $errors = array_merge($errors, $this->Behaviors->File->errors);
144                        return false;
145                }
146
147                return true;
148        }
149
150        /**
151         * When deleting a result, find the Job that has it's ID and clear it.
152         * Also remove the files from the filesystem
153         */
154        public function beforeDelete()
155        {
156                $data = $this->read();
157                $this->Job->id = $data['Job']['id'];
158                $this->Job->saveField('result_id', '');
159
160                $path = $this->field('path');
161                if ($path) {
162                        $Folder = new Folder(FILES . $path);
163                        $Folder->delete();
164                }
165
166                return true;
167        }
168
169        /**
170         * Convert the Markdown description to HTML before saving
171         * @return boolean True to continue saving
172         */
173        public function beforeSave()
174        {
175                if (isset($this->data['Result']['description'])) {
176                        // Convert the description to HTML
177                        App::import('Vendor', 'markdown');
178                        App::import('Vendor', 'HTMLPurifier', array('file' => 'htmlpurifier/HTMLPurifier.standalone.php'));
179
180                        $config = HTMLPurifier_Config::createDefault();
181                        $config->set('Cache.SerializerPath', CACHE . DS . 'htmlpurifier');
182                        $purifier = new HTMLPurifier($config);
183
184                        $desc_html = Markdown($this->data['Result']['description']);
185                        $this->data['Result']['description_html'] = $purifier->purify($desc_html);
186
187                        // Save the Markdown version to file
188                        $path = FILES . $this->field('path') . DS . 'description.txt';
189                        file_put_contents($path, $this->data['Result']['description']);
190                }
191
192                return true;
193        }
194
195        /**
196         * After creating a new result, schedule it for scanning
197         */
198        public function afterSave($created)
199        {
200                $this->read();
201                if ($created === false || empty($this->data['Result']['path'])) {
202                        return;
203                }
204
205                // Create validators for this result
206                if ($this->data['Format']['code'] == 'odf') {
207                        $validators = Configure::read('Validator');
208                        foreach ($validators as $validator_name => $validator_config) {
209                                if (!$validator_config) {
210                                        continue;
211                                }
212
213                                $this->Validator->create();
214                                $this->Validator->save(array('Validator' => array(
215                                        'name' => $validator_name,
216                                        'parent_id' => $this->id,
217                                )));
218                        }
219                }
220
221                // Schedule the postprocessor to run
222                if (!$this->defer('run', 'Postprocessor')) {
223                        $this->log('Failed to queue the result for the virus scanner.', true);
224                }
225
226                $this->saveField('state', self::STATE_POSTPROCESSOR_QUEUED);
227        }
228
229        /**
230         * Run ClamAV on the file
231         */
232        public function scan()
233        {
234                $clamd_config = Configure::read('Clamd');
235                if (!$clamd_config) {
236                        return true; // Continue with the rest of the pipeline
237                }
238
239                $clamd = new Clamd($clamd_config);
240                $path = $this->getPath();
241                $result = '';
242
243                if (!$path) {
244                        $this->log('Result could not be scanned. ' . $path . ' (Result ID: ' . $this->id . ') does not exists. ');
245                        return false;
246                }
247
248                $status = $clamd->scan($path, $result);
249                if ($status == Clamd::OK) {
250                        // Scan OK.
251                        $this->log('Clamd scanned ' . $path . ' (Result ID: ' . $this->id . '): OK', LOG_DEBUG);
252                } elseif ( $status == Clamd::FOUND ) {
253                        // Note that the file is *not* deleted. This was we can later check if there really was a virus
254                        $this->data['Result']['state'] = self::STATE_SCAN_FOUND;
255                        $this->data['Result']['state_info'] = $result;
256                        $this->log('Clamd scanned ' . $path . ' (Result ID: ' . $this->id . '): FOUND ' . $result, LOG_DEBUG);
257                        return false;
258                } else {
259                        // There was an error.
260                        if ($status === false) {
261                                $result = $clamd->lastError();
262                                $status = Clamd::ERROR;
263                        }
264
265                        $this->data['Result']['state_info'] = $result;
266                        $this->log('Clamd error scanning ' . $path . ' (Result ID: ' . $this->id . '): ' . $result);
267                        return false;
268                }
269
270                return $this->save();
271        }
272
273        /**
274         * Run all the ODF validators associated with this request
275         */
276        public function validateFile()
277        {
278                $this->read();
279                if (!is_array($this->data['Validator'])) {
280                        $this->log('No validators found for Result: ' . $this->id, LOG_DEBUG);
281                        return true;
282                }
283
284                foreach ($this->data['Validator'] as $validator) {
285                        $this->Validator->id = $validator['id'];
286                        $this->Validator->run();
287                }
288
289                return true; // To continue the rest of the pipeline
290        }
291}
292
293?>
Note: See TracBrowser for help on using the repository browser.