Paste   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Test Coverage

Coverage 95.24%

Importance

Changes 0
Metric Value
eloc 80
dl 0
loc 232
ccs 80
cts 84
cp 0.9524
rs 8.96
c 0
b 0
f 0
wmc 43

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getDeleteToken() 0 9 3
A exists() 0 3 1
A _sanitize() 0 15 3
B _validate() 0 21 9
A getComment() 0 12 3
C get() 0 47 13
A delete() 0 3 1
A getComments() 0 3 1
A store() 0 18 3
A isOpendiscussion() 0 8 6

How to fix   Complexity   

Complex Class

Complex classes like Paste often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Paste, and based on these observations, apply Extract Interface, too.

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.3.1
11
 */
12
13
namespace PrivateBin\Model;
14
15
use Exception;
16
use PrivateBin\Controller;
17
use PrivateBin\Persistence\ServerSalt;
18
19
/**
20
 * Paste
21
 *
22
 * Model of a PrivateBin paste.
23
 */
24
class Paste extends AbstractModel
25
{
26
    /**
27
     * Get paste data.
28
     *
29
     * @access public
30
     * @throws Exception
31
     * @return array
32
     */
33 35
    public function get()
34
    {
35 35
        $data = $this->_store->read($this->getId());
36 35
        if ($data === false) {
37 1
            throw new Exception(Controller::GENERIC_ERROR, 64);
38
        }
39
40
        // check if paste has expired and delete it if neccessary.
41 34
        if (array_key_exists('expire_date', $data['meta'])) {
42 9
            if ($data['meta']['expire_date'] < time()) {
43 4
                $this->delete();
44 4
                throw new Exception(Controller::GENERIC_ERROR, 63);
45
            }
46
            // We kindly provide the remaining time before expiration (in seconds)
47 5
            $data['meta']['time_to_live'] = $data['meta']['expire_date'] - time();
48 5
            unset($data['meta']['expire_date']);
49
        }
50
51
        // check if non-expired burn after reading paste needs to be deleted
52
        if (
53 30
            (array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
54 30
            (array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
55
        ) {
56 4
            $this->delete();
57
        }
58
59
        // set formatter for the view in version 1 pastes.
60 30
        if (array_key_exists('data', $data) && !array_key_exists('formatter', $data['meta'])) {
61
            // support < 0.21 syntax highlighting
62 2
            if (array_key_exists('syntaxcoloring', $data['meta']) && $data['meta']['syntaxcoloring'] === true) {
63 2
                $data['meta']['formatter'] = 'syntaxhighlighting';
64
            } else {
65
                $data['meta']['formatter'] = $this->_conf->getKey('defaultformatter');
66
            }
67
        }
68
69
        // support old paste format with server wide salt
70 30
        if (!array_key_exists('salt', $data['meta'])) {
71 4
            $data['meta']['salt'] = ServerSalt::get();
72
        }
73 30
        $data['comments']       = array_values($this->getComments());
74 30
        $data['comment_count']  = count($data['comments']);
75 30
        $data['comment_offset'] = 0;
76 30
        $data['@context']       = '?jsonld=paste';
77 30
        $this->_data            = $data;
78
79 30
        return $this->_data;
80
    }
81
82
    /**
83
     * Store the paste's data.
84
     *
85
     * @access public
86
     * @throws Exception
87
     */
88 30
    public function store()
89
    {
90
        // Check for improbable collision.
91 30
        if ($this->exists()) {
92 3
            throw new Exception('You are unlucky. Try again.', 75);
93
        }
94
95 28
        $this->_data['meta']['created'] = time();
96 28
        $this->_data['meta']['salt']    = serversalt::generate();
97
98
        // store paste
99
        if (
100 28
            $this->_store->create(
101 28
                $this->getId(),
102 28
                $this->_data
103 28
            ) === false
104
        ) {
105
            throw new Exception('Error saving paste. Sorry.', 76);
106
        }
107 28
    }
108
109
    /**
110
     * Delete the paste.
111
     *
112
     * @access public
113
     * @throws Exception
114
     */
115 25
    public function delete()
116
    {
117 25
        $this->_store->delete($this->getId());
118 25
    }
119
120
    /**
121
     * Test if paste exists in store.
122
     *
123
     * @access public
124
     * @return bool
125
     */
126 70
    public function exists()
127
    {
128 70
        return $this->_store->exists($this->getId());
129
    }
130
131
    /**
132
     * Get a comment, optionally a specific instance.
133
     *
134
     * @access public
135
     * @param string $parentId
136
     * @param string $commentId
137
     * @throws Exception
138
     * @return Comment
139
     */
140 18
    public function getComment($parentId, $commentId = '')
141
    {
142 18
        if (!$this->exists()) {
143 1
            throw new Exception('Invalid data.', 62);
144
        }
145 17
        $comment = new Comment($this->_conf, $this->_store);
146 17
        $comment->setPaste($this);
147 17
        $comment->setParentId($parentId);
148 14
        if ($commentId !== '') {
149 2
            $comment->setId($commentId);
150
        }
151 14
        return $comment;
152
    }
153
154
    /**
155
     * Get all comments, if any.
156
     *
157
     * @access public
158
     * @return array
159
     */
160 30
    public function getComments()
161
    {
162 30
        return $this->_store->readComments($this->getId());
163
    }
164
165
    /**
166
     * Generate the "delete" token.
167
     *
168
     * The token is the hmac of the pastes ID signed with the server salt.
169
     * The paste can be deleted by calling:
170
     * https://example.com/privatebin/?pasteid=<pasteid>&deletetoken=<deletetoken>
171
     *
172
     * @access public
173
     * @return string
174
     */
175 28
    public function getDeleteToken()
176
    {
177 28
        if (!array_key_exists('salt', $this->_data['meta'])) {
178
            $this->get();
179
        }
180 28
        return hash_hmac(
181 28
            $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256',
182 28
            $this->getId(),
183 28
            $this->_data['meta']['salt']
184
        );
185
    }
186
187
    /**
188
     * Check if paste has discussions enabled.
189
     *
190
     * @access public
191
     * @throws Exception
192
     * @return bool
193
     */
194 12
    public function isOpendiscussion()
195
    {
196 12
        if (!array_key_exists('adata', $this->_data) && !array_key_exists('data', $this->_data)) {
197 8
            $this->get();
198
        }
199
        return
200 12
            (array_key_exists('adata', $this->_data) && $this->_data['adata'][2] === 1) ||
201 12
            (array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion']);
202
    }
203
204
    /**
205
     * Sanitizes data to conform with current configuration.
206
     *
207
     * @access protected
208
     * @param  array $data
209
     * @return array
210
     */
211 34
    protected function _sanitize(array $data)
212
    {
213 34
        $expiration = $data['meta']['expire'];
214 34
        unset($data['meta']['expire']);
215 34
        $expire_options = $this->_conf->getSection('expire_options');
216 34
        if (array_key_exists($expiration, $expire_options)) {
217 30
            $expire = $expire_options[$expiration];
218
        } else {
219
            // using getKey() to ensure a default value is present
220 4
            $expire = $this->_conf->getKey($this->_conf->getKey('default', 'expire'), 'expire_options');
221
        }
222 34
        if ($expire > 0) {
223 34
            $data['meta']['expire_date'] = time() + $expire;
224
        }
225 34
        return $data;
226
    }
227
228
    /**
229
     * Validate data.
230
     *
231
     * @access protected
232
     * @param  array $data
233
     * @throws Exception
234
     */
235 34
    protected function _validate(array $data)
236
    {
237
        // reject invalid or disabled formatters
238 34
        if (!array_key_exists($data['adata'][1], $this->_conf->getSection('formatter_options'))) {
239
            throw new Exception('Invalid data.', 75);
240
        }
241
242
        // discussion requested, but disabled in config or burn after reading requested as well, or invalid integer
243
        if (
244 34
            ($data['adata'][2] === 1 && ( // open discussion flag
0 ignored issues
show
introduced by El RIDO
Consider adding parentheses for clarity. Current Interpretation: ($data['adata'][2] === 1...$data['adata'][2] !== 1, Probably Intended Meaning: $data['adata'][2] === 1 ...data['adata'][2] !== 1)
Loading history...
245 31
                !$this->_conf->getKey('discussion') ||
246 31
                $data['adata'][3] === 1  // burn after reading flag
247
            )) ||
248 34
            ($data['adata'][2] !== 0 && $data['adata'][2] !== 1)
249
        ) {
250 2
            throw new Exception('Invalid data.', 74);
251
        }
252
253
        // reject invalid burn after reading
254 32
        if ($data['adata'][3] !== 0 && $data['adata'][3] !== 1) {
255 2
            throw new Exception('Invalid data.', 73);
256
        }
257 30
    }
258
}
259