We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

newnotice.php 14.4 KB
Newer Older
Evan Prodromou's avatar
Evan Prodromou committed
1
<?php
Evan Prodromou's avatar
Evan Prodromou committed
2
/**
3
 * StatusNet, the distributed open-source microblogging tool
Evan Prodromou's avatar
Evan Prodromou committed
4
 *
Evan Prodromou's avatar
Evan Prodromou committed
5 6 7 8 9
 * Handler for posting new notices
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
Evan Prodromou's avatar
Evan Prodromou committed
10 11 12
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
Evan Prodromou's avatar
Evan Prodromou committed
13
 *
Evan Prodromou's avatar
Evan Prodromou committed
14 15
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Evan Prodromou's avatar
Evan Prodromou committed
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Evan Prodromou's avatar
Evan Prodromou committed
17
 * GNU Affero General Public License for more details.
Evan Prodromou's avatar
Evan Prodromou committed
18
 *
Evan Prodromou's avatar
Evan Prodromou committed
19
 * You should have received a copy of the GNU Affero General Public License
Evan Prodromou's avatar
Evan Prodromou committed
20 21 22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Personal
23
 * @package   StatusNet
24 25 26
 * @author    Evan Prodromou <evan@status.net>
 * @author    Zach Copley <zach@status.net>
 * @author    Sarven Capadisli <csarven@status.net>
27
 * @copyright 2008-2009 StatusNet, Inc.
Evan Prodromou's avatar
Evan Prodromou committed
28
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
29
 * @link      http://status.net/
Evan Prodromou's avatar
Evan Prodromou committed
30 31
 */

32
if (!defined('STATUSNET') && !defined('LACONICA')) {
Evan Prodromou's avatar
Evan Prodromou committed
33 34 35 36
    exit(1);
}

require_once INSTALLDIR.'/lib/noticelist.php';
Evan Prodromou's avatar
Evan Prodromou committed
37

Evan Prodromou's avatar
Evan Prodromou committed
38 39 40 41
/**
 * Action for posting new notices
 *
 * @category Personal
42
 * @package  StatusNet
43 44 45
 * @author   Evan Prodromou <evan@status.net>
 * @author   Zach Copley <zach@status.net>
 * @author   Sarven Capadisli <csarven@status.net>
Evan Prodromou's avatar
Evan Prodromou committed
46
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
47
 * @link     http://status.net/
Evan Prodromou's avatar
Evan Prodromou committed
48
 */
49

50 51
class NewnoticeAction extends Action
{
Evan Prodromou's avatar
Evan Prodromou committed
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    /**
     * Error message, if any
     */

    var $msg = null;

    /**
     * Title of the page
     *
     * Note that this usually doesn't get called unless something went wrong
     *
     * @return string page title
     */

    function title()
    {
        return _('New notice');
    }

    /**
     * Handle input, produce output
     *
     * Switches based on GET or POST method. On GET, shows a form
     * for posting a notice. On POST, saves the results of that form.
     *
     * Results may be a full page, or just a single notice list item,
     * depending on whether AJAX was requested.
     *
     * @param array $args $_REQUEST contents
     *
     * @return void
     */
84

85 86
    function handle($args)
    {
87
        if (!common_logged_in()) {
88
            $this->clientError(_('Not logged in.'));
89
        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
90 91 92 93
            // check for this before token since all POST and FILES data
            // is losts when size is exceeded
            if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
                $this->clientError(sprintf(_('The server was unable to handle ' .
Evan Prodromou's avatar
Evan Prodromou committed
94 95
                                             'that much POST data (%s bytes) due to its current configuration.'),
                                           $_SERVER['CONTENT_LENGTH']));
96 97
            }
            parent::handle($args);
98

99
            // CSRF protection
100 101
            $token = $this->trimmed('token');
            if (!$token || $token != common_session_token()) {
Evan Prodromou's avatar
Evan Prodromou committed
102 103
                $this->clientError(_('There was a problem with your session token. '.
                                     'Try again, please.'));
104
            }
105 106 107 108 109 110
            try {
                $this->saveNewNotice();
            } catch (Exception $e) {
                $this->showForm($e->getMessage());
                return;
            }
111
        } else {
Evan Prodromou's avatar
Evan Prodromou committed
112
            $this->showForm();
113 114 115
        }
    }

116
    function getUploadedFileType() {
117 118
        require_once 'MIME/Type.php';

119 120 121
        $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
        $cmd = common_config('attachments', 'filecommand');

122 123
        $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
        if (in_array($filetype, common_config('attachments', 'supported'))) {
124
            return $filetype;
125 126 127 128 129 130 131 132
        }
        $media = MIME_Type::getMedia($filetype);
        if ('application' !== $media) {
            $hint = sprintf(_(' Try using another %s format.'), $media);
        } else {
            $hint = '';
        }
        $this->clientError(sprintf(
Evan Prodromou's avatar
Evan Prodromou committed
133
                                   _('%s is not a supported filetype on this server.'), $filetype) . $hint);
134 135 136 137
    }

    function isRespectsQuota($user) {
        $file = new File;
138
        $ret = $file->isRespectsQuota($user,$_FILES['attach']['size']);
139 140
        if (true === $ret) return true;
        $this->clientError($ret);
141 142
    }

Evan Prodromou's avatar
Evan Prodromou committed
143 144 145 146 147 148 149 150 151 152 153
    /**
     * Save a new notice, based on arguments
     *
     * If successful, will show the notice, or return an Ajax-y result.
     * If not, it will show an error message -- possibly Ajax-y.
     *
     * Also, if the notice input looks like a command, it will run the
     * command and show the results -- again, possibly ajaxy.
     *
     * @return void
     */
154

Evan Prodromou's avatar
Evan Prodromou committed
155 156
    function saveNewNotice()
    {
157
        $user = common_current_user();
Evan Prodromou's avatar
Evan Prodromou committed
158
        assert($user); // XXX: maybe an error instead...
159 160 161
        $content = $this->trimmed('status_textarea');

        if (!$content) {
162
            $this->clientError(_('No content!'));
163 164 165
        } else {
            $content_shortened = common_shorten_links($content);
            if (mb_strlen($content_shortened) > 140) {
166 167
                $this->clientError(_('That\'s too long. '.
                                     'Max notice size is 140 chars.'));
168 169 170 171 172 173 174 175 176
            }
        }

        $inter = new CommandInterpreter();

        $cmd = $inter->handle_command($user, $content_shortened);

        if ($cmd) {
            if ($this->boolean('ajax')) {
177
                $cmd->execute(new AjaxWebChannel($this));
178
            } else {
179
                $cmd->execute(new WebChannel($this));
180 181 182 183 184
            }
            return;
        }

        $replyto = $this->trimmed('inreplyto');
185 186 187 188 189
        #If an ID of 0 is wrongly passed here, it will cause a database error,
        #so override it...
        if ($replyto == 0) {
            $replyto = 'false';
        }
190

191
        if (isset($_FILES['attach']['error'])) {
192
            switch ($_FILES['attach']['error']) {
Evan Prodromou's avatar
Evan Prodromou committed
193 194 195
             case UPLOAD_ERR_NO_FILE:
                // no file uploaded, nothing to do
                break;
196

Evan Prodromou's avatar
Evan Prodromou committed
197 198 199 200 201 202
             case UPLOAD_ERR_OK:
                $mimetype = $this->getUploadedFileType();
                if (!$this->isRespectsQuota($user)) {
                    die('clientError() should trigger an exception before reaching here.');
                }
                break;
203

Evan Prodromou's avatar
Evan Prodromou committed
204 205
             case UPLOAD_ERR_INI_SIZE:
                $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
206

Evan Prodromou's avatar
Evan Prodromou committed
207 208
             case UPLOAD_ERR_FORM_SIZE:
                $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
209

Evan Prodromou's avatar
Evan Prodromou committed
210 211
             case UPLOAD_ERR_PARTIAL:
                $this->clientError(_('The uploaded file was only partially uploaded.'));
212

Evan Prodromou's avatar
Evan Prodromou committed
213 214
             case  UPLOAD_ERR_NO_TMP_DIR:
                $this->clientError(_('Missing a temporary folder.'));
215

Evan Prodromou's avatar
Evan Prodromou committed
216 217
             case UPLOAD_ERR_CANT_WRITE:
                $this->clientError(_('Failed to write file to disk.'));
218

Evan Prodromou's avatar
Evan Prodromou committed
219 220
             case UPLOAD_ERR_EXTENSION:
                $this->clientError(_('File upload stopped by extension.'));
221

Evan Prodromou's avatar
Evan Prodromou committed
222 223
             default:
                die('Should never reach here.');
224
            }
225
        }
226

227 228 229 230 231
        if (isset($mimetype)) {
            $filename = $this->saveFile($mimetype);
            if (empty($filename)) {
                $this->clientError(_('Couldn\'t save file.'));
            }
232 233 234 235

            $fileRecord = $this->storeFile($filename, $mimetype);

            $fileurl = common_local_url('attachment',
Evan Prodromou's avatar
Evan Prodromou committed
236
                                        array('attachment' => $fileRecord->id));
237 238 239 240

            // not sure this is necessary -- Zach
            $this->maybeAddRedir($fileRecord->id, $fileurl);

241
            $short_fileurl = common_shorten_url($fileurl);
242 243 244 245
            if (!$short_fileurl) {
                // todo -- Consider forcing default shortener if none selected?
                $short_fileurl = $fileurl;
            }
246
            $content_shortened .= ' ' . $short_fileurl;
247

248 249 250 251
            if (mb_strlen($content_shortened) > 140) {
                $this->deleteFile($filename);
                $this->clientError(_('Max notice size is 140 chars, including attachment URL.'));
            }
252 253 254

            // Also, not sure this is necessary -- Zach
            $this->maybeAddRedir($fileRecord->id, $short_fileurl);
255 256
        }

257
        $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
Evan Prodromou's avatar
Evan Prodromou committed
258
                                  ($replyto == 'false') ? null : $replyto);
259 260

        if (is_string($notice)) {
261 262 263
            if (isset($filename)) {
                $this->deleteFile($filename);
            }
264
            $this->clientError($notice);
265 266
        }

267
        if (isset($mimetype)) {
268
            $this->attachFile($notice, $fileRecord);
269
        }
270

271 272 273
        common_broadcast_notice($notice);

        if ($this->boolean('ajax')) {
274
            $this->startHTML('text/xml;charset=utf-8');
275 276 277 278
            $this->elementStart('head');
            $this->element('title', null, _('Notice posted'));
            $this->elementEnd('head');
            $this->elementStart('body');
Evan Prodromou's avatar
Evan Prodromou committed
279
            $this->showNotice($notice);
280 281
            $this->elementEnd('body');
            $this->elementEnd('html');
282 283 284 285 286 287 288 289 290 291 292 293 294 295
        } else {
            $returnto = $this->trimmed('returnto');

            if ($returnto) {
                $url = common_local_url($returnto,
                                        array('nickname' => $user->nickname));
            } else {
                $url = common_local_url('shownotice',
                                        array('notice' => $notice->id));
            }
            common_redirect($url, 303);
        }
    }

296 297 298 299 300 301 302
    function saveFile($mimetype) {

        $cur = common_current_user();

        if (empty($cur)) {
            $this->serverError(_('Somehow lost the login in saveFile'));
        }
303 304 305

        $basename = basename($_FILES['attach']['name']);

306
        $filename = File::filename($cur->getProfile(), $basename, $mimetype);
307 308 309 310

        $filepath = File::path($filename);

        if (move_uploaded_file($_FILES['attach']['tmp_name'], $filepath)) {
311 312 313 314 315
            return $filename;
        } else {
            $this->clientError(_('File could not be moved to destination directory.'));
        }
    }
316

317 318 319 320 321
    function deleteFile($filename)
    {
        $filepath = File::path($filename);
        @unlink($filepath);
    }
322

323 324
    function storeFile($filename, $mimetype) {

325 326
        $file = new File;
        $file->filename = $filename;
327

328
        $file->url = File::url($filename);
329

330
        $filepath = File::path($filename);
331

332 333 334 335 336 337 338 339 340 341 342
        $file->size = filesize($filepath);
        $file->date = time();
        $file->mimetype = $mimetype;

        $file_id = $file->insert();

        if (!$file_id) {
            common_log_db_error($file, "INSERT", __FILE__);
            $this->clientError(_('There was a database error while saving your file. Please try again.'));
        }

343
        return $file;
344
    }
345

346 347 348 349 350
    function rememberFile($file, $short)
    {
        $this->maybeAddRedir($file->id, $short);
    }

351 352 353
    function maybeAddRedir($file_id, $url)
    {
        $file_redir = File_redirection::staticGet('url', $url);
354

355 356 357 358
        if (empty($file_redir)) {
            $file_redir = new File_redirection;
            $file_redir->url = $url;
            $file_redir->file_id = $file_id;
359

360
            $result = $file_redir->insert();
361

362 363 364 365
            if (!$result) {
                common_log_db_error($file_redir, "INSERT", __FILE__);
                $this->clientError(_('There was a database error while saving your file. Please try again.'));
            }
366 367 368
        }
    }

369 370 371 372
    function attachFile($notice, $filerec)
    {
        File_to_post::processNew($filerec->id, $notice->id);

373
        $this->maybeAddRedir($filerec->id,
Evan Prodromou's avatar
Evan Prodromou committed
374
                             common_local_url('file', array('notice' => $notice->id)));
375 376
    }

Evan Prodromou's avatar
Evan Prodromou committed
377 378 379 380 381 382 383 384 385 386 387
    /**
     * Show an Ajax-y error message
     *
     * Goes back to the browser, where it's shown in a popup.
     *
     * @param string $msg Message to show
     *
     * @return void
     */

    function ajaxErrorMsg($msg)
388
    {
389
        $this->startHTML('text/xml;charset=utf-8', true);
390 391 392 393 394 395 396
        $this->elementStart('head');
        $this->element('title', null, _('Ajax Error'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $this->element('p', array('id' => 'error'), $msg);
        $this->elementEnd('body');
        $this->elementEnd('html');
397 398
    }

Evan Prodromou's avatar
Evan Prodromou committed
399 400 401 402 403 404 405 406 407 408 409 410 411 412
    /**
     * Formerly page output
     *
     * This used to be the whole page output; now that's been largely
     * subsumed by showPage. So this just stores an error message, if
     * it was passed, and calls showPage.
     *
     * Note that since we started doing Ajax output, this page is rarely
     * seen.
     *
     * @param string $msg An error message, if any
     *
     * @return void
     */
413

Evan Prodromou's avatar
Evan Prodromou committed
414
    function showForm($msg=null)
415
    {
416
        if ($msg && $this->boolean('ajax')) {
Evan Prodromou's avatar
Evan Prodromou committed
417
            $this->ajaxErrorMsg($msg);
418 419
            return;
        }
Evan Prodromou's avatar
Evan Prodromou committed
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434

        $this->msg = $msg;
        $this->showPage();
    }

    /**
     * Overload for replies or bad results
     *
     * We show content in the notice form if there were replies or results.
     *
     * @return void
     */

    function showNoticeForm()
    {
435 436 437
        $content = $this->trimmed('status_textarea');
        if (!$content) {
            $replyto = $this->trimmed('replyto');
438
            $inreplyto = $this->trimmed('inreplyto');
439 440 441 442 443
            $profile = Profile::staticGet('nickname', $replyto);
            if ($profile) {
                $content = '@' . $profile->nickname . ' ';
            }
        }
Evan Prodromou's avatar
Evan Prodromou committed
444

445
        $notice_form = new NoticeForm($this, '', $content, null, $inreplyto);
Evan Prodromou's avatar
Evan Prodromou committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
        $notice_form->show();
    }

    /**
     * Show an error message
     *
     * Shows an error message if there is one.
     *
     * @return void
     *
     * @todo maybe show some instructions?
     */

    function showPageNotice()
    {
        if ($this->msg) {
            $this->element('p', array('id' => 'error'), $this->msg);
463 464 465
        }
    }

Evan Prodromou's avatar
Evan Prodromou committed
466 467 468 469 470 471 472 473 474 475 476
    /**
     * Output a notice
     *
     * Used to generate the notice code for Ajax results.
     *
     * @param Notice $notice Notice that was saved
     *
     * @return void
     */

    function showNotice($notice)
477
    {
Evan Prodromou's avatar
Evan Prodromou committed
478
        $nli = new NoticeListItem($notice, $this);
479
        $nli->show();
480
    }
481
}
482