Passed
Push — master ( 18151e...9ce410 )
by El
02:57
created

lib/Model/Paste.php (2 issues)

1
<?php
2
/**
3
 * PrivateBin
4
 *
5
 * a zero-knowledge paste bin
6
 *
7
 * @link      https://github.com/PrivateBin/PrivateBin
8
 * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
9
 * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10
 * @version   1.2.1
11
 */
12
13
namespace PrivateBin\Model;
14
15
use Exception;
16
use PrivateBin\Controller;
17
use PrivateBin\Persistence\ServerSalt;
18
use PrivateBin\Sjcl;
19
20
/**
21
 * Paste
22
 *
23
 * Model of a PrivateBin paste.
24
 */
25
class Paste extends AbstractModel
26
{
27
    /**
28
     * Get paste data.
29
     *
30
     * @access public
31
     * @throws Exception
32
     * @return stdClass
0 ignored issues
show
The type PrivateBin\Model\stdClass was not found. Did you mean stdClass? If so, make sure to prefix the type with \.
Loading history...
33
     */
34 34
    public function get()
35
    {
36 34
        $data = $this->_store->read($this->getId());
37 34
        if ($data === false) {
38 1
            throw new Exception(Controller::GENERIC_ERROR, 64);
39
        }
40
41
        // check if paste has expired and delete it if neccessary.
42 33
        if (property_exists($data->meta, 'expire_date')) {
43 5
            if ($data->meta->expire_date < time()) {
44 4
                $this->delete();
45 4
                throw new Exception(Controller::GENERIC_ERROR, 63);
46
            }
47
            // We kindly provide the remaining time before expiration (in seconds)
48 1
            $data->meta->remaining_time = $data->meta->expire_date - time();
49
        }
50
51
        // check if non-expired burn after reading paste needs to be deleted
52 29
        if (property_exists($data->meta, 'burnafterreading') && $data->meta->burnafterreading) {
53 5
            $this->delete();
54
        }
55
56
        // set formatter for for the view.
57 29
        if (!property_exists($data->meta, 'formatter')) {
58
            // support < 0.21 syntax highlighting
59 4
            if (property_exists($data->meta, 'syntaxcoloring') && $data->meta->syntaxcoloring === true) {
60 2
                $data->meta->formatter = 'syntaxhighlighting';
61
            } else {
62 2
                $data->meta->formatter = $this->_conf->getKey('defaultformatter');
63
            }
64
        }
65
66
        // support old paste format with server wide salt
67 29
        if (!property_exists($data->meta, 'salt')) {
68 4
            $data->meta->salt = ServerSalt::get();
69
        }
70 29
        $data->comments       = array_values($this->getComments());
71 29
        $data->comment_count  = count($data->comments);
72 29
        $data->comment_offset = 0;
73 29
        $data->{'@context'}   = 'js/paste.jsonld';
74 29
        $this->_data          = $data;
75
76
        // If the paste was meant to be read only once, delete it.
77 29
        if ($this->isBurnafterreading()) {
78 5
            $this->delete();
79
        }
80
81 29
        return $this->_data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_data returns the type stdClass which is incompatible with the documented return type PrivateBin\Model\stdClass.
Loading history...
82
    }
83
84
    /**
85
     * Store the paste's data.
86
     *
87
     * @access public
88
     * @throws Exception
89
     */
90 35
    public function store()
91
    {
92
        // Check for improbable collision.
93 35
        if ($this->exists()) {
94 3
            throw new Exception('You are unlucky. Try again.', 75);
95
        }
96
97 33
        $this->_data->meta->postdate = time();
98 33
        $this->_data->meta->salt     = serversalt::generate();
99
100
        // store paste
101
        if (
102 33
            $this->_store->create(
103 33
                $this->getId(),
104 33
                json_decode(json_encode($this->_data), true)
105 33
            ) === false
106
        ) {
107
            throw new Exception('Error saving paste. Sorry.', 76);
108
        }
109 33
    }
110
111
    /**
112
     * Delete the paste.
113
     *
114
     * @access public
115
     * @throws Exception
116
     */
117 26
    public function delete()
118
    {
119 26
        $this->_store->delete($this->getId());
120 26
    }
121
122
    /**
123
     * Test if paste exists in store.
124
     *
125
     * @access public
126
     * @return bool
127
     */
128 76
    public function exists()
129
    {
130 76
        return $this->_store->exists($this->getId());
131
    }
132
133
    /**
134
     * Get a comment, optionally a specific instance.
135
     *
136
     * @access public
137
     * @param string $parentId
138
     * @param string $commentId
139
     * @throws Exception
140
     * @return Comment
141
     */
142 20
    public function getComment($parentId, $commentId = null)
143
    {
144 20
        if (!$this->exists()) {
145 1
            throw new Exception('Invalid data.', 62);
146
        }
147 19
        $comment = new Comment($this->_conf, $this->_store);
148 19
        $comment->setPaste($this);
149 19
        $comment->setParentId($parentId);
150 17
        if ($commentId !== null) {
151 5
            $comment->setId($commentId);
152
        }
153 17
        return $comment;
154
    }
155
156
    /**
157
     * Get all comments, if any.
158
     *
159
     * @access public
160
     * @return array
161
     */
162 29
    public function getComments()
163
    {
164 29
        return $this->_store->readComments($this->getId());
165
    }
166
167
    /**
168
     * Generate the "delete" token.
169
     *
170
     * The token is the hmac of the pastes ID signed with the server salt.
171
     * The paste can be deleted by calling:
172
     * https://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken>
173
     *
174
     * @access public
175
     * @return string
176
     */
177 32
    public function getDeleteToken()
178
    {
179 32
        if (!property_exists($this->_data->meta, 'salt')) {
180
            $this->get();
181
        }
182 32
        return hash_hmac(
183 32
            $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256',
184 32
            $this->getId(),
185 32
            $this->_data->meta->salt
186
        );
187
    }
188
189
    /**
190
     * Set paste's attachment.
191
     *
192
     * @access public
193
     * @param string $attachment
194
     * @throws Exception
195
     */
196 2
    public function setAttachment($attachment)
197
    {
198 2
        if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachment)) {
199
            throw new Exception('Invalid attachment.', 71);
200
        }
201 2
        $this->_data->meta->attachment = $attachment;
202 2
    }
203
204
    /**
205
     * Set paste's attachment name.
206
     *
207
     * @access public
208
     * @param string $attachmentname
209
     * @throws Exception
210
     */
211 2
    public function setAttachmentName($attachmentname)
212
    {
213 2
        if (!$this->_conf->getKey('fileupload') || !Sjcl::isValid($attachmentname)) {
214
            throw new Exception('Invalid attachment.', 72);
215
        }
216 2
        $this->_data->meta->attachmentname = $attachmentname;
217 2
    }
218
219
    /**
220
     * Set paste expiration.
221
     *
222
     * @access public
223
     * @param string $expiration
224
     */
225 7
    public function setExpiration($expiration)
226
    {
227 7
        $expire_options = $this->_conf->getSection('expire_options');
228 7
        if (array_key_exists($expiration, $expire_options)) {
229 5
            $expire = $expire_options[$expiration];
230
        } else {
231
            // using getKey() to ensure a default value is present
232 2
            $expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
233
        }
234 7
        if ($expire > 0) {
235 7
            $this->_data->meta->expire_date = time() + $expire;
236
        }
237 7
    }
238
239
    /**
240
     * Set paste's burn-after-reading type.
241
     *
242
     * @access public
243
     * @param string $burnafterreading
244
     * @throws Exception
245
     */
246 3
    public function setBurnafterreading($burnafterreading = '1')
247
    {
248 3
        if ($burnafterreading === '0') {
249 1
            $this->_data->meta->burnafterreading = false;
250
        } else {
251 3
            if ($burnafterreading !== '1') {
252 2
                throw new Exception('Invalid data.', 73);
253
            }
254 1
            $this->_data->meta->burnafterreading = true;
255 1
            $this->_data->meta->opendiscussion   = false;
256
        }
257 1
    }
258
259
    /**
260
     * Set paste's discussion state.
261
     *
262
     * @access public
263
     * @param string $opendiscussion
264
     * @throws Exception
265
     */
266 11
    public function setOpendiscussion($opendiscussion = '1')
267
    {
268
        if (
269 11
            !$this->_conf->getKey('discussion') ||
270 11
            $this->isBurnafterreading() ||
271 11
            $opendiscussion === '0'
272
        ) {
273 1
            $this->_data->meta->opendiscussion = false;
274
        } else {
275 11
            if ($opendiscussion !== '1') {
276 2
                throw new Exception('Invalid data.', 74);
277
            }
278 9
            $this->_data->meta->opendiscussion = true;
279
        }
280 9
    }
281
282
    /**
283
     * Set paste's format.
284
     *
285
     * @access public
286
     * @param string $format
287
     * @throws Exception
288
     */
289 8
    public function setFormatter($format)
290
    {
291 8
        if (!array_key_exists($format, $this->_conf->getSection('formatter_options'))) {
292 2
            $format = $this->_conf->getKey('defaultformatter');
293
        }
294 8
        $this->_data->meta->formatter = $format;
295 8
    }
296
297
    /**
298
     * Check if paste is of burn-after-reading type.
299
     *
300
     * @access public
301
     * @throws Exception
302
     * @return bool
303
     */
304 39
    public function isBurnafterreading()
305
    {
306 39
        if (!property_exists($this->_data, 'data')) {
307 14
            $this->get();
308
        }
309 37
        return property_exists($this->_data->meta, 'burnafterreading') &&
310 37
               $this->_data->meta->burnafterreading === true;
311
    }
312
313
    /**
314
     * Check if paste has discussions enabled.
315
     *
316
     * @access public
317
     * @throws Exception
318
     * @return bool
319
     */
320 13
    public function isOpendiscussion()
321
    {
322 13
        if (!property_exists($this->_data, 'data')) {
323 8
            $this->get();
324
        }
325 13
        return property_exists($this->_data->meta, 'opendiscussion') &&
326 13
               $this->_data->meta->opendiscussion === true;
327
    }
328
}
329