]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/QnA/classes/QnA_Question.php
XSS vulnerability when remote-subscribing
[quix0rs-gnu-social.git] / plugins / QnA / classes / QnA_Question.php
1 <?php
2 /**
3  * Data class to mark a notice as a question
4  *
5  * PHP version 5
6  *
7  * @category QnA
8  * @package  StatusNet
9  * @author   Zach Copley <zach@status.net>
10  * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
11  * @link     http://status.net/
12  *
13  * StatusNet - the distributed open-source microblogging tool
14  * Copyright (C) 2011, StatusNet, Inc.
15  *
16  * This program is free software: you can redistribute it and/or modify
17  * it under the terms of the GNU Affero General Public License as published by
18  * the Free Software Foundation, either version 3 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
24  * GNU Affero General Public License for more details.
25  *
26  * You should have received a copy of the GNU Affero General Public License
27  * along with this program. If not, see <http://www.gnu.org/licenses/>.
28  */
29
30 if (!defined('STATUSNET')) {
31     exit(1);
32 }
33
34 /**
35  * For storing a question
36  *
37  * @category QnA
38  * @package  StatusNet
39  * @author   Zach Copley <zach@status.net>
40  * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
41  * @link     http://status.net/
42  *
43  * @see      DB_DataObject
44  */
45 class QnA_Question extends Managed_DataObject
46 {
47     const OBJECT_TYPE = 'http://activityschema.org/object/question';
48
49     public $__table = 'qna_question'; // table name
50     public $id;          // char(36) primary key not null -> UUID
51     public $uri;         // varchar(191)   not 255 because utf8mb4 takes more space
52     public $profile_id;  // int -> profile.id
53     public $title;       // text
54     public $description; // text
55     public $closed;      // int (boolean) whether a question is closed
56     public $created;     // datetime
57
58     /**
59      * The One True Thingy that must be defined and declared.
60      */
61     public static function schemaDef()
62     {
63         return array(
64             'description' => 'Per-notice question data for QNA plugin',
65             'fields' => array(
66                 'id' => array(
67                     'type'        => 'char',
68                     'length'      => 36,
69                     'not null'    => true,
70                     'description' => 'UUID'
71                 ),
72                 'uri' => array(
73                     'type'     => 'varchar',
74                     'length'   => 191,
75                     'not null' => true
76                 ),
77                 'profile_id'  => array('type' => 'int'),
78                 'title'       => array('type' => 'text'),
79                 'closed'      => array('type' => 'int', 'size' => 'tiny'),
80                 'description' => array('type' => 'text'),
81                 'created'     => array(
82                     'type'     => 'datetime',
83                     'not null' => true
84                 ),
85             ),
86             'primary key' => array('id'),
87             'unique keys' => array(
88                 'question_uri_key' => array('uri'),
89             ),
90         );
91     }
92
93     /**
94      * Get a question based on a notice
95      *
96      * @param Notice $notice Notice to check for
97      *
98      * @return Question found question or null
99      */
100     static function getByNotice($notice)
101     {
102         return self::getKV('uri', $notice->uri);
103     }
104
105     function getNotice()
106     {
107         return Notice::getKV('uri', $this->uri);
108     }
109
110     function getUrl()
111     {
112         return $this->getNotice()->getUrl();
113     }
114
115     function getProfile()
116     {
117         $profile = Profile::getKV('id', $this->profile_id);
118         if (empty($profile)) {
119             // TRANS: Exception trown when getting a profile for a non-existing ID.
120             // TRANS: %s is the provided profile ID.
121             throw new Exception(sprintf(_m('No profile with ID %s'),$this->profile_id));
122         }
123         return $profile;
124     }
125
126     /**
127      * Get the answer from a particular user to this question, if any.
128      *
129      * @param Profile $profile
130      *
131      * @return Answer object or null
132      */
133     function getAnswer(Profile $profile)
134     {
135         $a = new QnA_Answer();
136         $a->question_id = $this->id;
137         $a->profile_id = $profile->id;
138         $a->find();
139         if ($a->fetch()) {
140             return $a;
141         } else {
142             return null;
143         }
144     }
145
146     function getAnswers()
147     {
148         $a = new QnA_Answer();
149         $a->question_id = $this->id;
150         $cnt = $a->find();
151         if (!empty($cnt)) {
152             return $a;
153         } else {
154             return null;
155         }
156     }
157
158     function countAnswers()
159     {
160         $a = new QnA_Answer();
161
162         $a->question_id = $this->id;
163
164         return $a->count();
165     }
166
167     static function fromNotice($notice)
168     {
169         return QnA_Question::getKV('uri', $notice->uri);
170     }
171
172     function asHTML()
173     {
174         return self::toHTML($this->getProfile(), $this);
175     }
176
177     function asString()
178     {
179         return self::toString($this->getProfile(), $this);
180     }
181
182     static function toHTML($profile, $question)
183     {
184         $notice = $question->getNotice();
185
186         $out = new XMLStringer();
187
188         $cls = array('qna_question');
189
190         if (!empty($question->closed)) {
191             $cls[] = 'closed';
192         }
193
194         $out->elementStart('p', array('class' => implode(' ', $cls)));
195
196         if (!empty($question->description)) {
197             $out->elementStart('span', 'question-description');
198             $out->raw(common_render_text($question->description));
199             $out->elementEnd('span');
200         }
201
202         $cnt = $question->countAnswers();
203
204         if (!empty($cnt)) {
205             $out->elementStart('span', 'answer-count');
206             // TRANS: Number of given answers to a question.
207             // TRANS: %s is the number of given answers.
208             $out->text(sprintf(_m('%s answer','%s answers',$cnt), $cnt));
209             $out->elementEnd('span');
210         }
211
212         if (!empty($question->closed)) {
213             $out->elementStart('span', 'question-closed');
214             // TRANS: Notification that a question cannot be answered anymore because it is closed.
215             $out->text(_m('This question is closed.'));
216             $out->elementEnd('span');
217         }
218
219         $out->elementEnd('p');
220
221         return $out->getString();
222     }
223
224     static function toString($profile, $question, $answers)
225     {
226         return sprintf(htmlspecialchars($question->description));
227     }
228
229     /**
230      * Save a new question notice
231      *
232      * @param Profile $profile
233      * @param string  $question
234      * @param string  $title
235      * @param string  $description
236      * @param array   $option // and whatnot
237      *
238      * @return Notice saved notice
239      */
240     static function saveNew($profile, $title, $description, $options = array())
241     {
242         $q = new QnA_Question();
243
244         $q->id          = UUID::gen();
245         $q->profile_id  = $profile->id;
246         $q->title       = $title;
247         $q->description = $description;
248
249         if (array_key_exists('created', $options)) {
250             $q->created = $options['created'];
251         } else {
252             $q->created = common_sql_now();
253         }
254
255         if (array_key_exists('uri', $options)) {
256             $q->uri = $options['uri'];
257         } else {
258             $q->uri = common_local_url(
259                 'qnashowquestion',
260                 array('id' => $q->id)
261             );
262         }
263
264         common_log(LOG_DEBUG, "Saving question: $q->id $q->uri");
265         $q->insert();
266
267         if (Notice::contentTooLong($q->title . ' ' . $q->uri)) {
268             $max       = Notice::maxContent();
269             $uriLen    = mb_strlen($q->uri);
270             $targetLen = $max - ($uriLen + 15);
271             $title = mb_substr($q->title, 0, $targetLen) . '…';
272         }
273
274         $content = $title . ' ' . $q->uri;
275
276         $link = '<a href="' . htmlspecialchars($q->uri) . '">' . htmlspecialchars($q->title) . '</a>';
277         // TRANS: Rendered version of the notice content creating a question.
278         // TRANS: %s a link to the question as link description.
279         $rendered = sprintf(_m('Question: %s'), $link);
280
281         $tags    = array('question');
282         $replies = array();
283
284         $options = array_merge(
285             array(
286                 'urls'        => array(),
287                 'rendered'    => $rendered,
288                 'tags'        => $tags,
289                 'replies'     => $replies,
290                 'object_type' => self::OBJECT_TYPE
291             ),
292             $options
293         );
294
295         if (!array_key_exists('uri', $options)) {
296             $options['uri'] = $q->uri;
297         }
298
299         $saved = Notice::saveNew(
300             $profile->id,
301             $content,
302             array_key_exists('source', $options) ?
303             $options['source'] : 'web',
304             $options
305         );
306
307         return $saved;
308     }
309 }