]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/attachmentlistitem.php
de1087d44cb75f360df589ecd4abfe8ed9566207
[quix0rs-gnu-social.git] / lib / attachmentlistitem.php
1 <?php
2 /**
3  * StatusNet, the distributed open-source microblogging tool
4  *
5  * widget for displaying a list of notice attachments
6  *
7  * PHP version 5
8  *
9  * LICENCE: This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Affero General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Affero General Public License for more details.
18  *
19  * You should have received a copy of the GNU Affero General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * @category  UI
23  * @package   StatusNet
24  * @author    Evan Prodromou <evan@status.net>
25  * @author    Sarven Capadisli <csarven@status.net>
26  * @copyright 2008 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('GNUSOCIAL')) { exit(1); }
32
33 /**
34  * widget for displaying a single notice
35  *
36  * This widget has the core smarts for showing a single notice: what to display,
37  * where, and under which circumstances. Its key method is show(); this is a recipe
38  * that calls all the other show*() methods to build up a single notice. The
39  * ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
40  * author info (since that's implicit by the data in the page).
41  *
42  * @category UI
43  * @package  StatusNet
44  * @author   Evan Prodromou <evan@status.net>
45  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
46  * @link     http://status.net/
47  * @see      NoticeList
48  * @see      ProfileNoticeListItem
49  */
50 class AttachmentListItem extends Widget
51 {
52     /** The attachment this item will show. */
53
54     var $attachment = null;
55
56     /**
57      * @param File $attachment the attachment we will display
58      */
59     function __construct(File $attachment, $out=null)
60     {
61         parent::__construct($out);
62         $this->attachment  = $attachment;
63     }
64
65     function title() {
66         return $this->attachment->getTitle() ?: _('Untitled attachment');
67     }
68
69     function linkTitle() {
70         return $this->title();
71     }
72
73     /**
74      * recipe function for displaying a single notice.
75      *
76      * This uses all the other methods to correctly display a notice. Override
77      * it or one of the others to fine-tune the output.
78      *
79      * @return void
80      */
81     function show()
82     {
83         $this->showStart();
84         try {
85             $this->showNoticeAttachment();
86         } catch (Exception $e) {
87             $this->element('div', ['class'=>'error'], $e->getMessage());
88             common_debug($e->getMessage());
89         }
90         $this->showEnd();
91     }
92
93     function linkAttr() {
94         return array(
95                      'class' => 'u-url',
96                      'href' => $this->attachment->getAttachmentUrl(),
97                      'title' => $this->linkTitle());
98     }
99
100     function showNoticeAttachment()
101     {
102         $this->showRepresentation();
103     }
104
105     function showRepresentation() {
106         $enclosure = $this->attachment->getEnclosure();
107
108         if (Event::handle('StartShowAttachmentRepresentation', array($this->out, $this->attachment))) {
109
110             $this->out->elementStart('label');
111             $this->out->element('a', $this->linkAttr(), $this->title());
112             $this->out->elementEnd('label');
113
114             if (!empty($enclosure->mimetype)) {
115                 // First, prepare a thumbnail if it exists.
116                 $thumb = null;
117                 try {
118                     // Tell getThumbnail that we can show an animated image if it has one (4th arg, "force_still")
119                     $thumb = $this->attachment->getThumbnail(null, null, false, false);
120                 } catch (UseFileAsThumbnailException $e) {
121                     $thumb = null;
122                 } catch (UnsupportedMediaException $e) {
123                     // FIXME: Show a good representation of unsupported/unshowable images
124                     $thumb = null;
125                 }
126
127                 // Then get the kind of mediatype we're dealing with
128                 $mediatype = common_get_mime_media($enclosure->mimetype);
129
130                 // FIXME: Get proper mime recognition of Ogg files! If system has 'mediainfo', this should do it:
131                 // $ mediainfo --inform='General;%InternetMediaType%'
132                 if ($this->attachment->mimetype === 'application/ogg') {
133                     $mediatype = 'video';   // because this element can handle Ogg/Vorbis etc. on its own
134                 }
135
136                 // Ugly hack to show text/html links which have a thumbnail (such as from oEmbed/OpenGraph image URLs)
137                 if (!in_array($mediatype, ['image','audio','video']) && $thumb instanceof File_thumbnail) {
138                     $mediatype = 'image';
139                 }
140
141                 switch ($mediatype) {
142                 // Anything we understand as an image, if we need special treatment, do it in StartShowAttachmentRepresentation
143                 case 'image':
144                     if ($thumb instanceof File_thumbnail) {
145                         $this->out->element('img', $thumb->getHtmlAttrs(['class'=>'u-photo', 'alt' => '']));
146                     } else {
147                         try {
148                             // getUrl(true) because we don't want to hotlink, could be made configurable
149                             $this->out->element('img', ['class'=>'u-photo', 'src'=>$this->attachment->getUrl(true), 'alt' => $this->attachment->getTitle()]);
150                         } catch (FileNotStoredLocallyException $e) {
151                             $url = $e->file->getUrl(false);
152                             $this->out->element('a', ['href'=>$url, 'rel'=>'external'], $url);
153                         }
154                     }
155                     unset($thumb);  // there's no need carrying this along after this
156                     break;
157
158                 // HTML5 media elements
159                 case 'audio':
160                 case 'video':
161                     if ($thumb instanceof File_thumbnail) {
162                         $poster = $thumb->getUrl();
163                         unset($thumb);  // there's no need carrying this along after this
164                     } else {
165                         $poster = null;
166                     }
167
168                     $this->out->elementStart($mediatype,
169                                         array('class'=>"attachment_player u-{$mediatype}",
170                                             'poster'=>$poster,
171                                             'controls'=>'controls'));
172                     $this->out->element('source',
173                                         array('src'=>$this->attachment->getUrl(),
174                                             'type'=>$this->attachment->mimetype));
175                     $this->out->elementEnd($mediatype);
176                     break;
177
178                 default:
179                     unset($thumb);  // there's no need carrying this along
180                     switch (common_bare_mime($this->attachment->mimetype)) {
181                     case 'text/plain':
182                         $this->element('div', ['class'=>'e-content plaintext'], file_get_contents($this->attachment->getPath()));
183                         break;
184                     case 'text/html':
185                         if (!empty($this->attachment->filename)
186                                 && (GNUsocial::isAjax() || common_config('attachments', 'show_html'))) {
187                             // Locally-uploaded HTML. Scrub and display inline.
188                             $this->showHtmlFile($this->attachment);
189                             break;
190                         }
191                         // Fall through to default if it wasn't a _local_ text/html File object
192                     default:
193                         Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment));
194                     }
195                 }
196             } else {
197                 Event::handle('ShowUnsupportedAttachmentRepresentation', array($this->out, $this->attachment));
198             }
199         }
200         Event::handle('EndShowAttachmentRepresentation', array($this->out, $this->attachment));
201     }
202
203     protected function showHtmlFile(File $attachment)
204     {
205         $body = $this->scrubHtmlFile($attachment);
206         if ($body) {
207             $this->out->raw($body);
208         }
209     }
210
211     /**
212      * @return mixed false on failure, HTML fragment string on success
213      */
214     protected function scrubHtmlFile(File $attachment)
215     {
216         $path = $attachment->getPath();
217         $raw = file_get_contents($path);
218
219         // Normalize...
220         $dom = new DOMDocument();
221         if(!$dom->loadHTML($raw)) {
222             common_log(LOG_ERR, "Bad HTML in local HTML attachment $path");
223             return false;
224         }
225
226         // Remove <script>s or htmlawed will dump their contents into output!
227         // Note: removing child nodes while iterating seems to mess things up,
228         // hence the double loop.
229         $scripts = array();
230         foreach ($dom->getElementsByTagName('script') as $script) {
231             $scripts[] = $script;
232         }
233         foreach ($scripts as $script) {
234             common_log(LOG_DEBUG, $script->textContent);
235             $script->parentNode->removeChild($script);
236         }
237
238         // Trim out everything outside the body...
239         $body = $dom->saveHTML();
240         $body = preg_replace('/^.*<body[^>]*>/is', '', $body);
241         $body = preg_replace('/<\/body[^>]*>.*$/is', '', $body);
242
243         require_once INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php';
244         $purifier = new HTMLPurifier();
245         return $purifier->purify($body);
246     }
247
248     /**
249      * start a single notice.
250      *
251      * @return void
252      */
253     function showStart()
254     {
255         // XXX: RDFa
256         // TODO: add notice_type class e.g., notice_video, notice_image
257         $this->out->elementStart('li');
258     }
259
260     /**
261      * finish the notice
262      *
263      * Close the last elements in the notice list item
264      *
265      * @return void
266      */
267     function showEnd()
268     {
269         $this->out->elementEnd('li');
270     }
271 }