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

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

Make requests, jobs and results managable by admins

File size: 7.6 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         */
153        public function beforeDelete()
154        {
155                $data = $this->read();
156                $this->Job->id = $data['Job']['id'];
157                $this->Job->saveField('result_id', '');
158                $this->deleteFile();
159
160                return true;
161        }
162
163        /**
164         * Convert the Markdown description to HTML before saving
165         * @return boolean True to continue saving
166         */
167        public function beforeSave()
168        {
169                if (isset($this->data['Result']['description'])) {
170                        // Convert the description to HTML
171                        App::import('Vendor', 'markdown');
172                        App::import('Vendor', 'HTMLPurifier', array('file' => 'htmlpurifier/HTMLPurifier.standalone.php'));
173
174                        $config = HTMLPurifier_Config::createDefault();
175                        $config->set('Cache.SerializerPath', CACHE . DS . 'htmlpurifier');
176                        $purifier = new HTMLPurifier($config);
177
178                        $desc_html = Markdown($this->data['Result']['description']);
179                        $this->data['Result']['description_html'] = $purifier->purify($desc_html);
180
181                        // Save the Markdown version to file
182                        $path = FILES . $this->field('path') . DS . 'description.txt';
183                        file_put_contents($path, $this->data['Result']['description']);
184                }
185
186                return true;
187        }
188
189        /**
190         * After creating a new result, schedule it for scanning
191         */
192        public function afterSave($created)
193        {
194                $this->read();
195                if ($created === false || empty($this->data['Result']['path'])) {
196                        return;
197                }
198
199                // Create validators for this result
200                if ($this->data['Format']['code'] == 'odf') {
201                        $validators = Configure::read('Validator');
202                        foreach ($validators as $validator_name => $validator_config) {
203                                if (!$validator_config) {
204                                        continue;
205                                }
206
207                                $this->Validator->create();
208                                $this->Validator->save(array('Validator' => array(
209                                        'name' => $validator_name,
210                                        'parent_id' => $this->id,
211                                )));
212                        }
213                }
214
215                // Schedule the postprocessor to run
216                if (!$this->defer('run', 'Postprocessor')) {
217                        $this->log('Failed to queue the result for the virus scanner.', true);
218                }
219
220                $this->saveField('state', self::STATE_POSTPROCESSOR_QUEUED);
221        }
222
223        /**
224         * Run ClamAV on the file
225         */
226        public function scan()
227        {
228                $clamd_config = Configure::read('Clamd');
229                if (!$clamd_config) {
230                        return true; // Continue with the rest of the pipeline
231                }
232
233                $clamd = new Clamd($clamd_config);
234                $path = $this->getPath();
235                $result = '';
236
237                if (!$path) {
238                        $this->log('Result could not be scanned. ' . $path . ' (Result ID: ' . $this->id . ') does not exists. ');
239                        return false;
240                }
241
242                $status = $clamd->scan($path, $result);
243                if ($status == Clamd::OK) {
244                        // Scan OK.
245                        $this->log('Clamd scanned ' . $path . ' (Result ID: ' . $this->id . '): OK', LOG_DEBUG);
246                } elseif ( $status == Clamd::FOUND ) {
247                        // Note that the file is *not* deleted. This was we can later check if there really was a virus
248                        $this->data['Result']['state'] = self::STATE_SCAN_FOUND;
249                        $this->data['Result']['state_info'] = $result;
250                        $this->log('Clamd scanned ' . $path . ' (Result ID: ' . $this->id . '): FOUND ' . $result, LOG_DEBUG);
251                        return false;
252                } else {
253                        // There was an error.
254                        if ($status === false) {
255                                $result = $clamd->lastError();
256                                $status = Clamd::ERROR;
257                        }
258
259                        $this->data['Result']['state_info'] = $result;
260                        $this->log('Clamd error scanning ' . $path . ' (Result ID: ' . $this->id . '): ' . $result);
261                        return false;
262                }
263
264                return $this->save();
265        }
266
267        /**
268         * Run all the ODF validators associated with this request
269         */
270        public function validateFile()
271        {
272                $this->read();
273                if (!is_array($this->data['Validator'])) {
274                        $this->log('No validators found for Result: ' . $this->id, LOG_DEBUG);
275                        return true;
276                }
277
278                foreach ($this->data['Validator'] as $validator) {
279                        $this->Validator->id = $validator['id'];
280                        $this->Validator->run();
281                }
282
283                return true; // To continue the rest of the pipeline
284        }
285}
286
287?>
Note: See TracBrowser for help on using the repository browser.