Passed
Pull Request — master (#1932)
by Struan
05:06
created

GLOSSARY::delete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
ccs 2
cts 3
cp 0.6667
crap 2.1481
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
5
The Glossary item handles:
6
    1. Search matching for particular items.
7
    2. Addition of glossary items
8
    3. Removal of glossary items
9
    4. Notification of pending glossary additions
10
11
Glossary items can only (at present) be added on the Search page,
12
and only in the event that the term has not already been defined.
13
14
[?] will it be possible to amend the term?
15
16
It should not be possible to add a term if no results are found during the search.
17
18
All Glossary items need to be confirmed by a moderator (unless posted by a moderator).
19
As they are being approved/declined they can be modified (spelling etc...).
20
21
*/
22
23
// This handles basic insertion and approval functions for all epobjects
24
include_once INCLUDESPATH . "easyparliament/searchengine.php";
25
26
class GLOSSARY {
27
    public $num_terms;			// how many glossary entries do we have
28
    // (changes depending on how GLOSSARY is called
29
    public $hansard_count;		// how many times does the phrase appear in hansard?
30
    public $query;				// search term
31
    public $glossary_id;		// if this is set then we only have 1 glossary term
32
    public $current_term;		// will only be set if we have a valid epobject_id
33
    public $current_letter;
34
35
    // constructor...
36
    public function __construct($args = []) {
37 8
        // We can optionally start the glossary with one of several arguments
38
        //		1. glossary_id - treat the glossary as a single term
39
        //		2. glossary_term - search within glossary for a term
40
        // With no argument it will pick up all items.
41
42
        $this->db = new ParlDB();
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
43 8
44
        $this->replace_order = [];
0 ignored issues
show
Bug Best Practice introduced by
The property replace_order does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
45 8
        if (isset($args['s']) && ($args['s'] != "")) {
46 8
            $args['s'] = urldecode($args['s']);
47
            $this->search_glossary($args);
48
        }
49
        $got = $this->get_glossary_item($args);
50 8
        if ($got && isset($args['sort']) && ($args['sort'] == 'regexp_replace')) {
51 8
            // We need to sort the terms in the array by "number of words in term".
52
            // This way, "prime minister" gets dealt with before "minister" when generating glossary links.
53
54
            // sort by number of words
55
            foreach ($this->terms as $glossary_id => $term) {
56 8
                $this->replace_order[$glossary_id] = count(explode(" ", $term['title']));
57 8
            }
58
            arsort($this->replace_order);
59 8
60
            // secondary sort for number of letters?
61
            // pending functionality...
62
63
            // We can either turn off the "current term" completely -
64
            // so that it never links to its own page,
65
            // Or we can handle it in $this->glossarise below
66
            /*
67
            if (isset($this->epobject_id)) {
68
                unset ($this->replace_order[$this->epobject_id]);
69
            }
70
            */
71
        }
72
73
        // These stop stupid submissions.
74
        // everything should be lowercase.
75
        $this->stopwords = [ "the", "of", "to", "and", "for", "in", "a", "on", "is", "that", "will", "secretary", "are", "ask", "state", "have", "be", "has", "by", "with", "i", "not", "what", "as", "it", "hon", "he", "which", "from", "if", "been", "this", "s", "we", "at", "government", "was", "my", "an", "department", "there", "make", "or", "made", "their", "all", "but", "they", "how", "debate" ];
0 ignored issues
show
Bug Best Practice introduced by
The property stopwords does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
76 8
77
    }
78 8
79
    public function get_glossary_item($args = []) {
80 8
        // Search for and fetch glossary item with title or glossary_id
81
        // We could also search glossary text that contains the title text, for cross references
82
83
        $this->alphabet = [];
0 ignored issues
show
Bug Best Practice introduced by
The property alphabet does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
84 8
        foreach (range("A", "Z") as $letter) {
85 8
            $this->alphabet[$letter] = [];
86 8
        }
87
88
        $q = $this->db->query("SELECT g.glossary_id, g.title, g.body, u.user_id, u.firstname, u.lastname
89 8
            FROM editqueue AS eq, glossary AS g, users AS u
90
            WHERE g.glossary_id=eq.glossary_id AND u.user_id=eq.user_id AND g.visible=1 AND eq.approved=1
91
            ORDER by g.title");
92
        if ($q->success() && $q->rows()) {
93 8
            foreach ($q as $row) {
94 8
                $this->terms[$row["glossary_id"]] = $row;
0 ignored issues
show
Bug Best Practice introduced by
The property terms does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
95 8
                // Now add the epobject to the alphabet navigation.
96
                $first_letter = strtoupper(substr($row["title"], 0, 1));
97 8
                $this->alphabet[$first_letter][] = $row["glossary_id"];
98 8
            }
99
100
            $this->num_terms = $q->rows();
101 8
102
            // If we were given a glossary_id, then we need one term in particular,
103
            // as well as knowing the next and previous terms for the navigation
104
            if (isset($args['glossary_id']) && ($args['glossary_id'] != "")) {
105 8
                $next = 0;
106
                $first_term = null;
107
                foreach ($this->terms as $term) {
108
                    if (!$first_term) {
109
                        $first_term = $term;
110
                    }
111
                    $last_term = $term;
112
                    if ($next == 1) {
113
                        $this->next_term = $term;
0 ignored issues
show
Bug Best Practice introduced by
The property next_term does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
114
                        break;
115
                    } elseif ($term['glossary_id'] == $args['glossary_id']) {
116
                        $this->glossary_id = $args['glossary_id'];
117
                        $this->current_term = $term;
118
                        $next = 1;
119
120
                    } else {
121
                        $this->previous_term = $term;
0 ignored issues
show
Bug Best Practice introduced by
The property previous_term does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
122
                    }
123
                }
124
                // The first term in the list has no previous, so we'll make it the last term
125
                if (!isset($this->previous_term)) {
126
                    $this->previous_term = $last_term;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last_term seems to be defined by a foreach iteration on line 107. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
127
                }
128
                // and the last has no next, so we'll make it the first
129
                if (!isset($this->next_term)) {
130
                    $this->next_term = $first_term;
131
                }
132
            }
133
134 8
            return ($this->num_terms);
135
        } else {
136
            return false;
137
        }
138
    }
139
140
    public function search_glossary($args = []) {
141
        // Search for and fetch glossary item with a title
142
        // Useful for the search page, and nowhere else (so far)
143
144
        $this->query = addslashes($args['s']);
145
        $this->search_matches = [];
0 ignored issues
show
Bug Best Practice introduced by
The property search_matches does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
146
        $this->num_search_matches = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property num_search_matches does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
147
148
        $query = "SELECT g.glossary_id, g.title, g.body, u.user_id, u.firstname, u.lastname
149
            FROM editqueue AS eq, glossary AS g, users AS u
150
            WHERE g.glossary_id=eq.glossary_id AND u.user_id=eq.user_id AND g.visible=1
151
                AND g.title LIKE '%" . $this->query . "%'
152
            ORDER by g.title";
153
        $q = $this->db->query($query);
154
        if ($q->success() && $q->rows()) {
155
            foreach ($q as $row) {
156
                $this->search_matches[$row["glossary_id"]] = $row;
157
            }
158
            $this->num_search_matches = $q->rows();
159
        }
160
    }
161
162
    public function create(&$data) {
163
        // Add a Glossary definition.
164
        // Sets visiblity to 0, and awaits moderator intervention.
165
        // For this we need to start up an epobject of type 2 and then an editqueue item
166
        // where editqueue.epobject_id_l = epobject.epobject_id
167
168
        $EDITQUEUE = new \MySociety\TheyWorkForYou\GlossaryEditQueue();
169
170
        // Assuming that everything is ok, we will need:
171
        // For epobject:
172
        // 		title VARCHAR(255),
173
        // 		body TEXT,
174
        // 		type INTEGER,
175
        // 		created DATETIME,
176
        // 		modified DATETIME,
177
        // and for editqueue:
178
        //		edit_id INTEGER PRIMARY KEY NOT NULL,
179
        //		user_id INTEGER,
180
        //		edit_type INTEGER,
181
        //		epobject_id_l INTEGER,
182
        //		title VARCHAR(255),
183
        //		body TEXT,
184
        //		submitted DATETIME,
185
        //		editor_id INTEGER,
186
        //		approved BOOLEAN,
187
        //		decided DATETIME
188
189
        global $THEUSER;
190
191
        if (!$THEUSER->is_able_to('addterm')) {
192
            error("Sorry, you are not allowed to add Glossary terms.");
193
            return false;
194
        }
195
196
        if ($data['title'] == '') {
197
            error("Sorry, you can't define a term without a title");
198
            return false;
199
        }
200
201
        if ($data['body'] == '') {
202
            error("You haven't entered a definition!");
203
            return false;
204
        }
205
206
        if (is_numeric($THEUSER->user_id()) && !$THEUSER->status == 'Superuser') {
207
            // Flood check - make sure the user hasn't just posted a term recently.
208
            // To help prevent accidental duplicates, among other nasty things.
209
210
            $flood_time_limit = 20; // How many seconds until a user can post again?
211
212
            $q = $this->db->query("SELECT glossary_id, submitted, submitted + 0 as s, NOW() as n, NOW() - $flood_time_limit as f
213
                            FROM	editqueue
214
                            WHERE	user_id = '" . $THEUSER->user_id() . "'
215
                            AND		submitted + 0 > NOW() - $flood_time_limit");
216
217
            if ($q->rows() > 0) {
218
                $data['error'] = "Sorry, we limit people to posting one term per $flood_time_limit seconds to help prevent duplicate postings. Please go back and try again, thanks.";
219
                return $data;
220
            }
221
        }
222
223
        // OK, let's get on with it...
224
225
        // Tidy up the HTML tags
226
        // (but we don't make URLs into links; only when displaying the comment).
227
        // We can display Glossary terms the same as the comments
228
        $data['title'] = filter_user_input($data['title'], 'comment_title'); // In utility.php
229
        $data['body'] = filter_user_input($data['body'], 'comment'); // In utility.php
230
        // Add the time and the edit type for the editqueue
231
        $data['posted'] = date('Y-m-d H:i:s', time());
232
        $data['edit_type'] = 2;
233
234
        // Add the item to the edit queue
235
        $success = $EDITQUEUE->add($data);
236
237
        if ($success) {
238
            // if the user is a super user then just add the entry without confirmation
239
            if ($THEUSER->status == 'Superuser') {
240
                $EDITQUEUE->approve(['approvals' => [$success], 'epobject_type' => 2]);
241
            }
242
            return ($success);
243
        } else {
244
            return false;
245
        }
246
    }
247
248
    public function delete($glossary_id) {
249
        $q = $this->db->query("DELETE from glossary where glossary_id=$glossary_id LIMIT 1;");
250 8
        // if that worked, we need to update the editqueue,
251
        // and remove the term from the already generated object list.
252
        if ($q->affected_rows() >= 1) {
253 8
            unset($this->replace_order[$glossary_id]);
254
            unset($this->terms[$glossary_id]);
255 8
        }
256 8
    }
257 8
258 8
    public function glossarise($body, $tokenize = 0, $urlize = 0) {
0 ignored issues
show
Unused Code introduced by
The parameter $tokenize is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

258
    public function glossarise($body, /** @scrutinizer ignore-unused */ $tokenize = 0, $urlize = 0) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
259
        // Turn a body of text into a link-up wonderland of glossary joy
260
261
        global $this_page;
262
263 8
        $findwords = [];
264
        $replacewords = [];
265
        $URL = new \MySociety\TheyWorkForYou\Url("glossary");
266
        $URL->insert(["gl" => ""]);
267
268
        // External links shown within their own definition
269
        // should be the complete and linked url.
270
        // NB. This should only match when $body is a definition beginning with "https:"
271
        if (is_string($body) && preg_match("/^(https?:*[^\s])$/i", $body)) {
272 8
            $body = "<a href=\"" . $body . "\" title=\"External link to " . $body . "\">" . $body . "</a>";
273
            return ($body);
274
        }
275
276
        // otherwise, just replace everything.
277 8
278 8
        // generate links from URL when wanted
279
        // NB WRANS is already doing this
280
        if ($urlize == 1) {
281
            $body = preg_replace("~(http(s)?:\/\/[^\s\n]*)\b(\/)?~i", "<a href=\"\\0\">\\0</a>", $body);
282 8
        }
283 8
284
        // check for any glossary terms to replace
285 8
        foreach ($this->replace_order as $glossary_id => $count) {
286
            if ($glossary_id == $this->glossary_id) {
287 8
                continue;
288
            }
289 8
290
            $term_body = $this->terms[$glossary_id]['body'];
291
            $term_title = $this->terms[$glossary_id]['title'];
292 8
293
            $URL->update(["gl" => $glossary_id]);
294
            # The regex here ensures that the phrase is only matched if it's not already within <a> tags, preventing double-linking. Kudos to http://stackoverflow.com/questions/7798829/php-regular-expression-to-match-keyword-outside-html-tag-a
295 8
            $findwords[$glossary_id] = "/\b(" . $term_title . ")\b(?!(?>[^<]*(?:<(?!\/?a\b)[^<]*)*)<\/a>)/i";
296
            // catch glossary terms within their own definitions
297 8
            if ($glossary_id == $this->glossary_id) {
298 8
                $replacewords[] = "<strong>\\1</strong>";
299
            } else {
300
                if ($this_page == "admin_glossary") {
301
                    $link_url = "#gl" . $glossary_id;
302 8
                } else {
303 8
                    $link_url = $URL->generate('url');
304
                }
305
                $title = _htmlentities(trim_characters($term_body, 0, 80));
306
                # strip markdown
307
                $title = preg_replace("/[*~_]/", "", $title);
308
                $replacewords[] = "<a href=\"$link_url\" title=\"$title\" class=\"glossary\">\\1</a>";
309
            }
310 8
        }
311
        // Highlight all occurrences of another glossary term in the definition.
312 8
        $body = preg_replace($findwords, $replacewords, $body, 1);
313
        if (isset($this->glossary_id)) {
314
            $body = preg_replace("/(?<![>\.\'\/])\b(" . $this->terms[$this->glossary_id]['title'] . ")\b(?![<\'])/i", '<strong>\\1</strong>', $body, 1);
315
        }
316
317
        // Replace any phrases in wikipedia
318
        // TODO: Merge this code into above, so our gloss and wikipedia
319
        // don't clash (e.g. URLs getting doubly munged etc.)
320
        $body = \MySociety\TheyWorkForYou\Utility\Wikipedia::wikipedize($body);
321
322
        return ($body);
323
    }
324
325
}
326