Add new public_calendar additional feature

- This gives anonymous access to public events
This commit is contained in:
Hypolite Petovan 2022-11-30 17:52:19 -05:00
parent ae76fa1174
commit 84b2a35e05
6 changed files with 169 additions and 162 deletions

View file

@ -120,6 +120,12 @@ class Feature
['tagadelic', DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', 'tagadelic', false)], ['tagadelic', DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', 'tagadelic', false)],
['profile_membersince', DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', 'profile_membersince', false)], ['profile_membersince', DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', 'profile_membersince', false)],
], ],
//Advanced Calendar Settings
'advanced_calendar' => [
DI::l10n()->t('Advanced Calendar Settings'),
['public_calendar', DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', 'public_calendar', false)],
]
]; ];
// removed any locked features and remove the entire category if this makes it empty // removed any locked features and remove the entire category if this makes it empty

View file

@ -21,6 +21,7 @@
namespace Friendica\Model; namespace Friendica\Model;
use Friendica\Content\Feature;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\Core\Logger; use Friendica\Core\Logger;
@ -28,8 +29,7 @@ use Friendica\Core\Renderer;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Network\HTTPException\NotFoundException; use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\UnauthorizedException;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Map; use Friendica\Util\Map;
@ -503,25 +503,30 @@ class Event
* @param string $nickname * @param string $nickname
* *
* @return array the owner array * @return array the owner array
* @throws NotFoundException The given nickname does not exist * @throws HTTPException\InternalServerErrorException
* @throws UnauthorizedException The access for the given nickname is restricted * @throws HTTPException\NotFoundException The given nickname does not exist
* @throws HTTPException\UnauthorizedException The access for the given nickname is restricted
*/ */
public static function getOwnerForNickname(string $nickname): array public static function getOwnerForNickname(string $nickname): array
{ {
$owner = User::getOwnerDataByNick($nickname); $owner = User::getOwnerDataByNick($nickname);
if (empty($owner) || $owner['account_removed'] || $owner['account_expired']) { if (empty($owner) || $owner['account_removed'] || $owner['account_expired']) {
throw new NotFoundException(DI::l10n()->t('User not found.')); throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
} }
if ($owner['hidewall'] && !DI::userSession()->isAuthenticated()) { if (!DI::userSession()->isAuthenticated() && $owner['hidewall']) {
throw new UnauthorizedException(DI::l10n()->t('Access to this profile has been restricted.')); throw new HTTPException\UnauthorizedException(DI::l10n()->t('Access to this profile has been restricted.'));
}
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
} }
return $owner; return $owner;
} }
/** /**
* Get an event by its event ID. * Get an event by its event ID. Checks permissions.
* *
* @param int $owner_uid The User ID of the owner of the event * @param int $owner_uid The User ID of the owner of the event
* @param int $event_id The ID of the event in the event table * @param int $event_id The ID of the event in the event table
@ -529,37 +534,32 @@ class Event
* @return array Query result * @return array Query result
* @throws \Exception * @throws \Exception
*/ */
public static function getByIdAndUid(int $owner_uid, int $event_id, string $nickname = null): array public static function getByIdAndUid(int $owner_uid, int $event_id): array
{ {
if (!empty($nickname)) {
$owner = static::getOwnerForNickname($nickname);
$owner_uid = $owner['uid'];
// get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
// we only want to have the events of the profile owner
$sql_extra = " AND `event`.`cid` = 0 " . $sql_perms;
} else {
$sql_extra = "";
}
// Only allow events if there is a valid owner_id. // Only allow events if there is a valid owner_id.
if ($owner_uid == 0) { if ($owner_uid == 0) {
return []; return [];
} }
// Query for the event by event id // get the permissions
$events = DBA::toArray(DBA::p("SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event` $sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
LEFT JOIN `post-user` ON `post-user`.`event-id` = `event`.`id` AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ? AND `event`.`id` = ? $sql_extra",
$owner_uid, $event_id));
// Query for the event by event id
$events = DBA::toArray(DBA::p(
"SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user`
ON `post-user`.`event-id` = `event`.`id`
AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`id` = ?
AND `event`.`uid` = ?
$sql_perms",
$event_id, $owner_uid
));
if (empty($events)) { if (empty($events)) {
throw new NotFoundException(DI::l10n()->t('Event not found.')); throw new HTTPException\NotFoundException(DI::l10n()->t('Event not found.'));
} else {
$events = self::removeDuplicates($events);
return $events[0];
} }
return $events[0];
} }
/** /**
@ -568,34 +568,23 @@ class Event
* @param int $owner_uid The User ID of the owner of the events. * @param int $owner_uid The User ID of the owner of the events.
* @param string|null $start Start time of the timeframe. * @param string|null $start Start time of the timeframe.
* @param string|null $finish Finish time of the timeframe. * @param string|null $finish Finish time of the timeframe.
* @param bool $ignore * @param bool|null $ignore Filters ignored events (false: unignored events, true: ignored events, null: all events)
* @param string|null $nickname
* *
* @return array Query results. * @return array Query results.
* @throws NotFoundException * @throws HTTPException\NotFoundException
* @throws UnauthorizedException * @throws HTTPException\UnauthorizedException
*/ */
public static function getListByDate(int $owner_uid, string $start = null, string $finish = null, bool $ignore = false, string $nickname = null): array public static function getListByDate(int $owner_uid, string $start = null, string $finish = null, ?bool $ignore = false): array
{ {
if (!empty($nickname)) {
$owner = static::getOwnerForNickname($nickname);
$owner_uid = $owner['uid'];
// get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
// we only want to have the events of the profile owner
$sql_extra = " AND `event`.`cid` = 0 " . $sql_perms;
} else {
$sql_extra = "";
}
// Only allow events if there is a valid owner_id. // Only allow events if there is a valid owner_id.
if ($owner_uid == 0) { if ($owner_uid == 0) {
return []; return [];
} }
if (empty($start) || empty($finish)) { // get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
if (empty($start) || empty($finish)) {
$y = intval(DateTimeFormat::localNow('Y')); $y = intval(DateTimeFormat::localNow('Y'));
$m = intval(DateTimeFormat::localNow('m')); $m = intval(DateTimeFormat::localNow('m'));
@ -607,41 +596,36 @@ class Event
} }
} }
if ($ignore === true) {
$sql_ignore = " AND `event`.`ignore` = 1";
} elseif ($ignore === false) {
$sql_ignore = " AND `event`.`ignore` = 0";
} else {
$sql_ignore = "";
}
// Query for the event by date. // Query for the event by date.
$events = DBA::toArray(DBA::p("SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event` $events = DBA::toArray(DBA::p(
LEFT JOIN `post-user` ON `post-user`.`event-id` = `event`.`id` AND `post-user`.`uid` = `event`.`uid` "SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
WHERE `event`.`uid` = ? AND `event`.`ignore` = ? LEFT JOIN `post-user`
AND (`finish` >= ? OR (`nofinish` AND `start` >= ?)) AND `start` <= ? ON `post-user`.`event-id` = `event`.`id`
" . $sql_extra, AND `post-user`.`uid` = `event`.`uid`
$owner_uid, $ignore, WHERE `event`.`uid` = ?
$start, $start, $finish $sql_ignore
AND (`finish` >= ? OR (`nofinish` AND `start` >= ?))
AND `start` <= ?
$sql_perms",
$owner_uid,
$start, $start,
$finish
)); ));
$events = self::removeDuplicates($events ?? []); $events = self::removeDuplicates($events);
return self::sortByDate($events); return self::sortByDate($events);
} }
/** /**
* Convert an array query results in an array which could be used by the events template. * Convert an event in an array which could be used by the event template.
*
* @param array $event_result Event query array.
* @return array Event array for the template.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function prepareListForTemplate(array $event_result): array
{
$event_list = [];
foreach ($event_result as $event) {
$event_list[] = static::prepareForItem($event);
}
return $event_list;
}
/**
* Convert an one event in an array which could be used by the events template.
* *
* @param array $event Event query array. * @param array $event Event query array.
* @return array Event array for the template. * @return array Event array for the template.
@ -687,11 +671,7 @@ class Event
[$title, $_trash] = explode("<br", BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])), BBCode::TWITTER_API); [$title, $_trash] = explode("<br", BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])), BBCode::TWITTER_API);
} }
$author_link = $event['author-link']; $event['author-link'] = Contact::magicLink($event['author-link']);
$event['author-link'] = Contact::magicLink($author_link);
$html = self::getHTML($event);
$event['summary'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary'])); $event['summary'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
$event['desc'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])); $event['desc'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc']));
$event['location'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['location'])); $event['location'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['location']));
@ -708,7 +688,7 @@ class Event
'drop' => $drop, 'drop' => $drop,
'copy' => $copy, 'copy' => $copy,
'item' => $event, 'item' => $event,
'html' => $html, 'html' => self::getHTML($event),
'plink' => Item::getPlink($event), 'plink' => Item::getPlink($event),
]; ];
} }
@ -718,8 +698,6 @@ class Event
* *
* @param array $events Query result for events. * @param array $events Query result for events.
* @param string $format The output format (ical/csv). * @param string $format The output format (ical/csv).
*
* @param string $timezone Timezone (missing parameter!)
* @return string Content according to selected export format. * @return string Content according to selected export format.
* *
* @todo Implement timezone support * @todo Implement timezone support

View file

@ -23,9 +23,11 @@ namespace Friendica\Module;
use Friendica\App; use Friendica\App;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\User;
class BaseProfile extends BaseModule class BaseProfile extends BaseModule
{ {
@ -87,9 +89,9 @@ class BaseProfile extends BaseModule
'id' => 'calendar-tab', 'id' => 'calendar-tab',
'accesskey' => 'c', 'accesskey' => 'c',
]; ];
// if the user is not the owner of the calendar we only show a calendar
// with the public events of the calendar owner
} else { } else {
$owner = User::getByNickname($nickname, ['uid']);
if(DI::userSession()->isAuthenticated() || $owner && Feature::isEnabled($owner['uid'], 'public_calendar')) {
$tabs[] = [ $tabs[] = [
'label' => DI::l10n()->t('Calendar'), 'label' => DI::l10n()->t('Calendar'),
'url' => DI::baseUrl() . '/calendar/show/' . $nickname, 'url' => DI::baseUrl() . '/calendar/show/' . $nickname,
@ -99,6 +101,7 @@ class BaseProfile extends BaseModule
'accesskey' => 'c', 'accesskey' => 'c',
]; ];
} }
}
if ($is_owner) { if ($is_owner) {
$tabs[] = [ $tabs[] = [

View file

@ -22,12 +22,14 @@
namespace Friendica\Module\Calendar\Event; namespace Friendica\Module\Calendar\Event;
use Friendica\App; use Friendica\App;
use Friendica\Content\Feature;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Model\Event; use Friendica\Model\Event;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
@ -43,24 +45,27 @@ class Get extends \Friendica\BaseModule
/** @var IHandleUserSessions */ /** @var IHandleUserSessions */
protected $session; protected $session;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, array $server, array $parameters = []) public function __construct(App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, array $server, array $parameters = [])
{ {
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session; $this->session = $session;
$this->app = $app;
} }
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
{ {
if (!$this->session->getLocalUserId()) { $nickname = $this->parameters['nickname'] ?? $this->app->getLoggedInUserNickname();
if (!$nickname) {
throw new HTTPException\UnauthorizedException(); throw new HTTPException\UnauthorizedException();
} }
// get events by id or by date $owner = Event::getOwnerForNickname($nickname);
if (!empty($request['id'])) { if (!empty($request['id'])) {
$events = [Event::getByIdAndUid($this->session->getLocalUserId(), $request['id'], $this->parameters['nickname'] ?? null)]; $events = [Event::getByIdAndUid($owner['uid'], $request['id'])];
} else { } else {
$events = Event::getListByDate($this->session->getLocalUserId(), $request['start'] ?? '', $request['end'] ?? '', false, $this->parameters['nickname'] ?? null); $events = Event::getListByDate($owner['uid'], $request['start'] ?? '', $request['end'] ?? '');
} }
System::jsonExit($events ? self::map($events) : []); System::jsonExit($events ? self::map($events) : []);

View file

@ -23,11 +23,13 @@ namespace Friendica\Module\Calendar\Event;
use Friendica\App; use Friendica\App;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Model\Event; use Friendica\Model\Event;
use Friendica\Model\User;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
@ -40,22 +42,27 @@ class Show extends BaseModule
{ {
/** @var IHandleUserSessions */ /** @var IHandleUserSessions */
protected $session; protected $session;
/** @var App */
private $app;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, array $server, array $parameters = []) public function __construct(App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, array $server, array $parameters = [])
{ {
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session; $this->session = $session;
$this->app = $app;
} }
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
{ {
if (!$this->session->getLocalUserId()) { $nickname = $this->parameters['nickname'] ?? $this->app->getLoggedInUserNickname();
throw new HTTPException\UnauthorizedException($this->t('Permission denied.')); if (!$nickname) {
throw new HTTPException\UnauthorizedException();
} }
$event = Event::getByIdAndUid($this->session->getLocalUserId(), (int)$this->parameters['id'] ?? 0, $this->parameters['nickname'] ?? ''); $owner = Event::getOwnerForNickname($nickname);
$event = Event::getByIdAndUid($owner['uid'], (int)$this->parameters['id'] ?? 0);
if (empty($event)) { if (empty($event)) {
throw new HTTPException\NotFoundException($this->t('Event not found.')); throw new HTTPException\NotFoundException($this->t('Event not found.'));
} }

View file

@ -23,6 +23,7 @@ namespace Friendica\Module\Calendar;
use Friendica\App; use Friendica\App;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Content\Nav; use Friendica\Content\Nav;
use Friendica\Content\Widget; use Friendica\Content\Widget;
use Friendica\Core\L10n; use Friendica\Core\L10n;
@ -30,9 +31,11 @@ use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Theme; use Friendica\Core\Theme;
use Friendica\Model\Event; use Friendica\Model\Event;
use Friendica\Model\User;
use Friendica\Module\BaseProfile; use Friendica\Module\BaseProfile;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
use Friendica\Network\HTTPException;
use Friendica\Navigation\SystemMessages; use Friendica\Navigation\SystemMessages;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -60,7 +63,21 @@ class Show extends BaseModule
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
if (!$this->session->getLocalUserId()) { $nickname = $this->parameters['nickname'] ?? $this->app->getLoggedInUserNickname();
if (!$nickname) {
throw new HTTPException\UnauthorizedException();
}
$owner = User::getOwnerDataByNick($nickname);
if (!$owner || $owner['account_expired'] || $owner['account_removed']) {
throw new HTTPException\NotFoundException($this->t('User not found.'));
}
if (!$this->session->isAuthenticated() && $owner['hidewall']) {
$this->baseUrl->redirect('profile/' . $nickname . '/restricted');
}
if (!$this->session->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
$this->sysMessages->addNotice($this->t('Permission denied.')); $this->sysMessages->addNotice($this->t('Permission denied.'));
return Login::form(); return Login::form();
} }
@ -73,35 +90,26 @@ class Show extends BaseModule
$this->page->registerFooterScript('view/asset/moment/min/moment-with-locales.min.js'); $this->page->registerFooterScript('view/asset/moment/min/moment-with-locales.min.js');
$this->page->registerFooterScript('view/asset/fullcalendar/dist/fullcalendar.min.js'); $this->page->registerFooterScript('view/asset/fullcalendar/dist/fullcalendar.min.js');
$htpl = Renderer::getMarkupTemplate('calendar/calendar_head.tpl'); $is_owner = $nickname == $this->app->getLoggedInUserNickname();
$htpl = Renderer::getMarkupTemplate('calendar/calendar_head.tpl');
$this->page['htmlhead'] .= Renderer::replaceMacros($htpl, [ $this->page['htmlhead'] .= Renderer::replaceMacros($htpl, [
'$calendar_api' => 'calendar/api/get' . (!empty($this->parameters['nickname']) ? '/' . $this->parameters['nickname'] : ''), '$calendar_api' => 'calendar/api/get' . ($is_owner ? '' : '/' . $nickname),
'$event_api' => 'calendar/event/show' . (!empty($this->parameters['nickname']) ? '/' . $this->parameters['nickname'] : ''), '$event_api' => 'calendar/event/show' . ($is_owner ? '' : '/' . $nickname),
'$modparams' => 2, '$modparams' => 2,
'$i18n' => $i18n, '$i18n' => $i18n,
]); ]);
$tabs = ''; Nav::setSelected($is_owner ? 'home' : 'calendar');
if (empty($this->parameters['nickname'])) {
Nav::setSelected('home');
$tabs = BaseProfile::getTabsHTML($this->app, 'calendar', true, $this->app->getLoggedInUserNickname(), false);
$this->page['aside'] .= Widget\CalendarExport::getHTML($this->session->getLocalUserId());
} else {
$owner = Event::getOwnerForNickname($this->parameters['nickname']);
Nav::setSelected('calendar');
// get the tab navigation bar
$tabs = BaseProfile::getTabsHTML($this->app, 'calendar', false, $owner['nickname'], $owner['hide-friends']);
if (!$is_owner) {
$this->page['aside'] .= Widget\VCard::getHTML($owner); $this->page['aside'] .= Widget\VCard::getHTML($owner);
$this->page['aside'] .= Widget\CalendarExport::getHTML($owner['uid']);
} }
$this->page['aside'] .= Widget\CalendarExport::getHTML($owner['uid']);
$tabs = BaseProfile::getTabsHTML('calendar', $is_owner, $nickname, !$is_owner && $owner['hide-friends']);
// ACL blocks are loaded in modals in frio // ACL blocks are loaded in modals in frio
$this->page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js')); $this->page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
$this->page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js')); $this->page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));