]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/noticesearch.php
XSS vulnerability when remote-subscribing
[quix0rs-gnu-social.git] / actions / noticesearch.php
1 <?php
2 /**
3  * Notice search action class.
4  *
5  * PHP version 5
6  *
7  * @category Action
8  * @package  StatusNet
9  * @author   Evan Prodromou <evan@status.net>
10  * @author   Robin Millette <millette@status.net>
11  * @author   Sarven Capadisli <csarven@status.net>
12  * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
13  * @link     http://status.net/
14  *
15  * StatusNet - the distributed open-source microblogging tool
16  * Copyright (C) 2008, 2009, StatusNet, Inc.
17  *
18  * This program is free software: you can redistribute it and/or modify
19  * it under the terms of the GNU Affero General Public License as published by
20  * the Free Software Foundation, either version 3 of the License, or
21  * (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful,
24  * but WITHOUT ANY WARRANTY; without even the implied warranty of
25  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  * GNU Affero General Public License for more details.
27  *
28  * You should have received a copy of the GNU Affero General Public License
29  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
30  */
31
32 if (!defined('STATUSNET') && !defined('LACONICA')) {
33     exit(1);
34 }
35
36 require_once INSTALLDIR.'/lib/searchaction.php';
37
38 /**
39  * Notice search action class.
40  *
41  * @category Action
42  * @package  StatusNet
43  * @author   Evan Prodromou <evan@status.net>
44  * @author   Robin Millette <millette@status.net>
45  * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
46  * @link     http://status.net/
47  * @todo     common parent for people and content search?
48  */
49 class NoticesearchAction extends SearchAction
50 {
51     protected $q = null;
52
53     function prepare($args)
54     {
55         parent::prepare($args);
56
57         $this->q = $this->trimmed('q');
58
59         // FIXME: very dependent on tag format
60         if (preg_match('/^#([\pL\pN_\-\.]{1,64})/ue', $this->q)) {
61             common_redirect(common_local_url('tag',
62                                              array('tag' => common_canonical_tag(substr($this->q, 1)))),
63                             303);
64         }
65
66         if (!empty($this->q)) {
67
68             $profile = Profile::current();
69             $stream  = new SearchNoticeStream($this->q, $profile);
70             $page    = $this->trimmed('page');
71
72             if (empty($page)) {
73                 $page = 1;
74             } else {
75                 $page = (int)$page;
76             }
77
78             $this->notice = $stream->getNotices((($page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
79         }
80
81         common_set_returnto($this->selfUrl());
82
83         return true;
84     }
85
86     /**
87      * Get instructions
88      *
89      * @return string instruction text
90      */
91     function getInstructions()
92     {
93         // TRANS: Instructions for Notice search page.
94         // TRANS: %%site.name%% is the name of the StatusNet site.
95         return _('Search for notices on %%site.name%% by their contents. Separate search terms by spaces; they must be 3 characters or more.');
96     }
97
98     /**
99      * Get title
100      *
101      * @return string title
102      */
103     function title()
104     {
105         // TRANS: Title of the page where users can search for notices.
106         return _('Text search');
107     }
108
109     function getFeeds()
110     {
111         $q = $this->trimmed('q');
112
113         if (!$q) {
114             return null;
115         }
116
117         return array(new Feed(Feed::RSS1, common_local_url('noticesearchrss',
118                                                            array('q' => $q)),
119                               // TRANS: Test in RSS notice search.
120                               // TRANS: %1$s is the query, %2$s is the StatusNet site name.
121                               sprintf(_('Search results for "%1$s" on %2$s'),
122                                       $q, common_config('site', 'name'))));
123     }
124
125     /**
126      * Show results
127      *
128      * @param string  $q    search query
129      * @param integer $page page number
130      *
131      * @return void
132      */
133     function showResults($q, $page)
134     {
135         if (Event::handle('StartNoticeSearchShowResults', array($this, $q, $this->notice))) {
136             if ($this->notice->N === 0) {
137                 $this->showEmptyResults($q, $page);
138             } else {
139                 $terms = preg_split('/[\s,]+/', $q);
140                 $nl = new SearchNoticeList($this->notice, $this, $terms);
141                 $cnt = $nl->show();
142                 $this->pagination($page > 1,
143                                   $cnt > NOTICES_PER_PAGE,
144                                   $page,
145                                   'noticesearch',
146                                   array('q' => $q));
147             }
148             Event::handle('EndNoticeSearchShowResults', array($this, $q, $this->notice));
149         }
150     }
151
152     function showEmptyResults($q, $page)
153     {
154             // TRANS: Text for notice search results is the query had no results.
155             $this->element('p', 'error', _('No results.'));
156
157             $this->searchSuggestions($q);
158             if (common_logged_in()) {
159                 // TRANS: Text for logged in users making a query for notices without results.
160                 // TRANS: This message contains a Markdown link.
161                 $message = sprintf(_('Be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
162             }
163             else {
164                 // TRANS: Text for not logged in users making a query for notices without results.
165                 // TRANS: This message contains Markdown links.
166                 $message = sprintf(_('Why not [register an account](%%%%action.register%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
167             }
168
169             $this->elementStart('div', 'guide');
170             $this->raw(common_markup_to_html($message));
171             $this->elementEnd('div');
172             return;
173     }
174
175     function showScripts()
176     {
177         parent::showScripts();
178         $this->autofocus('q');
179     }
180 }
181
182 class SearchNoticeList extends NoticeList {
183     function __construct($notice, $out=null, $terms)
184     {
185         parent::__construct($notice, $out);
186         $this->terms = $terms;
187     }
188
189     function newListItem($notice)
190     {
191         return new SearchNoticeListItem($notice, $this->out, $this->terms);
192     }
193 }
194
195 class SearchNoticeListItem extends NoticeListItem {
196     function __construct($notice, $out=null, $terms)
197     {
198         parent::__construct($notice, $out);
199         $this->terms = $terms;
200     }
201
202     function showContent()
203     {
204         // FIXME: URL, image, video, audio
205         $this->out->elementStart('p', array('class' => 'e-content'));
206         $this->out->raw($this->highlight($this->notice->getRendered(), $this->terms));
207         $this->out->elementEnd('p');
208
209     }
210
211     /**
212      * Highlist query terms
213      *
214      * @param string $text  notice text
215      * @param array  $terms terms to highlight
216      *
217      * @return void
218      */
219     function highlight($text, $terms)
220     {
221         /* Highligh search terms */
222         $options = implode('|', array_map('preg_quote', array_map('htmlspecialchars', $terms),
223                                                             array_fill(0, sizeof($terms), '/')));
224         $pattern = "/($options)/i";
225         $result = '';
226
227         /* Divide up into text (highlight me) and tags (don't touch) */
228         $chunks = preg_split('/(<[^>]+>)/', $text, 0, PREG_SPLIT_DELIM_CAPTURE);
229         foreach ($chunks as $i => $chunk) {
230             if ($i % 2 == 1) {
231                 // odd: delimiter (tag)
232                 $result .= $chunk;
233             } else {
234                 // even: freetext between tags
235                 $result .= preg_replace($pattern, '<strong>\\1</strong>', $chunk);
236             }
237         }
238
239         return $result;
240     }
241 }