274 lines
9.2 KiB
PHP
274 lines
9.2 KiB
PHP
<?php
|
|
/**
|
|
* @copyright Copyright (C) 2010-2022, the Friendica project
|
|
*
|
|
* @license GNU AGPL version 3 or any later version
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
namespace Friendica\Module\Calendar\Event;
|
|
|
|
use Friendica\App;
|
|
use Friendica\BaseModule;
|
|
use Friendica\Core\L10n;
|
|
use Friendica\Core\Protocol;
|
|
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
|
use Friendica\Core\System;
|
|
use Friendica\Core\Worker;
|
|
use Friendica\Database\DBA;
|
|
use Friendica\Model\Contact;
|
|
use Friendica\Model\Conversation;
|
|
use Friendica\Model\Event;
|
|
use Friendica\Model\Item;
|
|
use Friendica\Model\Post;
|
|
use Friendica\Model\User;
|
|
use Friendica\Module\Response;
|
|
use Friendica\Navigation\SystemMessages;
|
|
use Friendica\Network\HTTPException\BadRequestException;
|
|
use Friendica\Network\HTTPException\UnauthorizedException;
|
|
use Friendica\Util\ACLFormatter;
|
|
use Friendica\Util\DateTimeFormat;
|
|
use Friendica\Util\Profiler;
|
|
use Friendica\Util\Strings;
|
|
use Friendica\Worker\Delivery;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* Basic API class for events
|
|
* currently supports create, delete, ignore, unignore
|
|
*
|
|
* @todo: make create/update as REST-call instead of POST
|
|
*/
|
|
class API extends BaseModule
|
|
{
|
|
const ACTION_CREATE = 'create';
|
|
const ACTION_DELETE = 'delete';
|
|
const ACTION_IGNORE = 'ignore';
|
|
const ACTION_UNIGNORE = 'unignore';
|
|
|
|
const ALLOWED_ACTIONS = [
|
|
self::ACTION_CREATE,
|
|
self::ACTION_DELETE,
|
|
self::ACTION_IGNORE,
|
|
self::ACTION_UNIGNORE,
|
|
];
|
|
|
|
/** @var IHandleUserSessions */
|
|
protected $session;
|
|
/** @var SystemMessages */
|
|
protected $sysMessages;
|
|
/** @var ACLFormatter */
|
|
protected $aclFormatter;
|
|
/** @var string */
|
|
protected $timezone;
|
|
|
|
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, SystemMessages $sysMessages, ACLFormatter $aclFormatter, App $app, array $server, array $parameters = [])
|
|
{
|
|
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
|
|
|
$this->session = $session;
|
|
$this->sysMessages = $sysMessages;
|
|
$this->aclFormatter = $aclFormatter;
|
|
$this->timezone = $app->getTimeZone();
|
|
|
|
if (!$this->session->getLocalUserId()) {
|
|
throw new UnauthorizedException($this->t('Permission denied.'));
|
|
}
|
|
}
|
|
|
|
protected function post(array $request = [])
|
|
{
|
|
$this->createEvent($request);
|
|
}
|
|
|
|
protected function rawContent(array $request = [])
|
|
{
|
|
if (empty($this->parameters['action']) || !in_array($this->parameters['action'], self::ALLOWED_ACTIONS)) {
|
|
throw new BadRequestException($this->t('Invalid Request'));
|
|
}
|
|
|
|
// CREATE is done per POSt, so nothing to do left
|
|
if ($this->parameters['action'] === static::ACTION_CREATE) {
|
|
return;
|
|
}
|
|
|
|
if (empty($this->parameters['id'])) {
|
|
throw new BadRequestException($this->t('Event id is missing.'));
|
|
}
|
|
|
|
$returnPath = $request['return_path'] ?? 'calendar';
|
|
|
|
switch ($this->parameters['action']) {
|
|
case self::ACTION_IGNORE:
|
|
Event::setIgnore($this->session->getLocalUserId(), $this->parameters['id']);
|
|
break;
|
|
case self::ACTION_UNIGNORE:
|
|
Event::setIgnore($this->session->getLocalUserId(), $this->parameters['id'], false);
|
|
break;
|
|
case self::ACTION_DELETE:
|
|
// Remove an event from the calendar and its related items
|
|
$event = Event::getByIdAndUid($this->session->getLocalUserId(), $this->parameters['id']);
|
|
|
|
// Delete only real events (no birthdays)
|
|
if (!empty($event) && $event['type'] == 'event') {
|
|
Item::deleteForUser(['id' => $event['itemid']], $this->session->getLocalUserId());
|
|
}
|
|
|
|
if (Post::exists(['id' => $event['itemid']])) {
|
|
$this->sysMessages->addNotice($this->t('Failed to remove event'));
|
|
}
|
|
break;
|
|
default:
|
|
throw new BadRequestException($this->t('Invalid Request'));
|
|
}
|
|
|
|
$this->baseUrl->redirect($returnPath);
|
|
}
|
|
|
|
protected function createEvent(array $request)
|
|
{
|
|
$eventId = !empty($request['event_id']) ? intval($request['event_id']) : 0;
|
|
$uid = (int)$this->session->getLocalUserId();
|
|
$cid = !empty($request['cid']) ? intval($request['cid']) : 0;
|
|
|
|
$strStartDateTime = Strings::escapeHtml($request['start_text'] ?? '');
|
|
$strFinishDateTime = Strings::escapeHtml($request['finish_text'] ?? '');
|
|
|
|
$noFinish = intval($request['nofinish'] ?? 0);
|
|
|
|
$share = intval($request['share'] ?? 0);
|
|
$isPreview = intval($request['preview'] ?? 0);
|
|
|
|
$start = DateTimeFormat::convert($strStartDateTime ?? DBA::NULL_DATETIME, $this->timezone);
|
|
if (!$noFinish) {
|
|
$finish = DateTimeFormat::convert($strFinishDateTime ?? DBA::NULL_DATETIME, 'UTC', $this->timezone);
|
|
} else {
|
|
$finish = DBA::NULL_DATETIME;
|
|
}
|
|
|
|
// Don't allow the event to finish before it begins.
|
|
// It won't hurt anything, but somebody will file a bug report,
|
|
// and we'll waste a bunch of time responding to it. Time that
|
|
// could've been spent doing something else.
|
|
|
|
$summary = trim($request['summary'] ?? '');
|
|
$desc = trim($request['desc'] ?? '');
|
|
$location = trim($request['location'] ?? '');
|
|
$type = 'event';
|
|
|
|
$params = [
|
|
'summary' => $summary,
|
|
'description' => $desc,
|
|
'location' => $location,
|
|
'start' => $strStartDateTime,
|
|
'finish' => $strFinishDateTime,
|
|
'nofinish' => $noFinish,
|
|
];
|
|
|
|
$action = empty($eventId) ? 'new' : 'edit/' . $eventId;
|
|
$redirectOnError = 'calendar/event/' . $action . '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
|
|
|
|
if (strcmp($finish, $start) < 0 && !$noFinish) {
|
|
if ($isPreview) {
|
|
System::httpExit($this->t('Event can not end before it has started.'));
|
|
} else {
|
|
$this->sysMessages->addNotice($this->t('Event can not end before it has started.'));
|
|
$this->baseUrl->redirect($redirectOnError);
|
|
}
|
|
}
|
|
|
|
if (empty($summary) || ($start === DBA::NULL_DATETIME)) {
|
|
if ($isPreview) {
|
|
System::httpExit($this->t('Event title and start time are required.'));
|
|
} else {
|
|
$this->sysMessages->addNotice($this->t('Event title and start time are required.'));
|
|
$this->baseUrl->redirect($redirectOnError);
|
|
}
|
|
}
|
|
|
|
$self = Contact::getPublicIdByUserId($uid);
|
|
|
|
$aclFormatter = $this->aclFormatter;
|
|
|
|
if ($share) {
|
|
$user = User::getById($uid, ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']);
|
|
if (empty($user)) {
|
|
$this->logger->warning('Cannot find user for an event.', ['uid' => $uid, 'event' => $eventId]);
|
|
$this->response->setStatus(500);
|
|
return;
|
|
}
|
|
|
|
$strAclContactAllow = isset($request['contact_allow']) ? $aclFormatter->toString($request['contact_allow']) : $user['allow_cid'] ?? '';
|
|
$strAclGroupAllow = isset($request['group_allow']) ? $aclFormatter->toString($request['group_allow']) : $user['allow_gid'] ?? '';
|
|
$strContactDeny = isset($request['contact_deny']) ? $aclFormatter->toString($request['contact_deny']) : $user['deny_cid'] ?? '';
|
|
$strGroupDeny = isset($request['group_deny']) ? $aclFormatter->toString($request['group_deny']) : $user['deny_gid'] ?? '';
|
|
|
|
$visibility = $request['visibility'] ?? '';
|
|
if ($visibility === 'public') {
|
|
// The ACL selector introduced in version 2019.12 sends ACL input data even when the Public visibility is selected
|
|
$strAclContactAllow = $strAclGroupAllow = $strContactDeny = $strGroupDeny = '';
|
|
} else if ($visibility === 'custom') {
|
|
// Since we know from the visibility parameter the item should be private, we have to prevent the empty ACL
|
|
// case that would make it public. So we always append the author's contact id to the allowed contacts.
|
|
// See https://github.com/friendica/friendica/issues/9672
|
|
$strAclContactAllow .= $aclFormatter->toString($self);
|
|
}
|
|
} else {
|
|
$strAclContactAllow = $aclFormatter->toString($self);
|
|
$strAclGroupAllow = $strContactDeny = $strGroupDeny = '';
|
|
}
|
|
|
|
$datarray = [
|
|
'start' => $start,
|
|
'finish' => $finish,
|
|
'summary' => $summary,
|
|
'desc' => $desc,
|
|
'location' => $location,
|
|
'type' => $type,
|
|
'nofinish' => $noFinish,
|
|
'uid' => $uid,
|
|
'cid' => $cid,
|
|
'allow_cid' => $strAclContactAllow,
|
|
'allow_gid' => $strAclGroupAllow,
|
|
'deny_cid' => $strContactDeny,
|
|
'deny_gid' => $strGroupDeny,
|
|
'id' => $eventId,
|
|
];
|
|
|
|
if (intval($request['preview'])) {
|
|
System::httpExit(Event::getHTML($datarray));
|
|
}
|
|
|
|
$eventId = Event::store($datarray);
|
|
|
|
$newItem = Event::getItemArrayForId($eventId, [
|
|
'network' => Protocol::DFRN,
|
|
'protocol' => Conversation::PARCEL_DIRECT,
|
|
'direction' => Conversation::PUSH
|
|
]);
|
|
if (Item::insert($newItem)) {
|
|
$uriId = (int)$newItem['uri-id'];
|
|
} else {
|
|
$uriId = 0;
|
|
}
|
|
|
|
if (!$cid && $uriId) {
|
|
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::POST, $uriId, $uid);
|
|
}
|
|
|
|
$this->baseUrl->redirect('calendar');
|
|
}
|
|
}
|