Merge remote-tracking branch 'upstream/develop' into api-tags

This commit is contained in:
Michael 2022-11-28 02:13:20 +00:00
commit a6d060b0e4
41 changed files with 1075 additions and 812 deletions

View file

@ -47,6 +47,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- [`POST /api/v1/accounts/:id/unmute`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/relationships`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/search`](https://docs.joinmastodon.org/methods/accounts)
- [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/#update_credentials)
- [`GET /api/v1/accounts/verify_credentials`](https://docs.joinmastodon.org/methods/accounts)
- [`POST /api/v1/apps`](https://docs.joinmastodon.org/methods/apps/)
- [`GET /api/v1/apps/verify_credentials`](https://docs.joinmastodon.org/methods/apps/)
@ -138,7 +139,6 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
These emdpoints are planned to be implemented somewhere in the future.
- [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/)
- [`POST /api/v1/accounts/:id/remove_from_followers`](https://github.com/mastodon/mastodon/pull/16864)
- [`GET /api/v1/accounts/familiar_followers`](https://github.com/mastodon/mastodon/pull/17700)
- [`GET /api/v1/accounts/lookup`](https://github.com/mastodon/mastodon/pull/15740)

View file

@ -1,160 +0,0 @@
<?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/>.
*
* @package Friendica\modules
* @subpackage FileBrowser
* @author Fabio Comuni <fabrixxm@kirgroup.com>
*/
use Friendica\App;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Util\Images;
use Friendica\Util\Strings;
/**
* @param App $a
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function fbrowser_content(App $a)
{
if (!DI::userSession()->getLocalUserId()) {
System::exit();
}
if (DI::args()->getArgc() == 1) {
System::exit();
}
// Needed to match the correct template in a module that uses a different theme than the user/site/default
$theme = Strings::sanitizeFilePathItem($_GET['theme'] ?? '');
if ($theme && is_file("view/theme/$theme/config.php")) {
$a->setCurrentTheme($theme);
}
$template_file = "filebrowser.tpl";
$o = '';
switch (DI::args()->getArgv()[1]) {
case "image":
$path = ['' => DI::l10n()->t('Photos')];
$albums = false;
$sql_extra = "";
$sql_extra2 = " ORDER BY created DESC LIMIT 0, 10";
if (DI::args()->getArgc() == 2) {
$photos = DBA::toArray(DBA::p("SELECT distinct(`album`) AS `album` FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?)",
DI::userSession()->getLocalUserId(),
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
));
$albums = array_column($photos, 'album');
}
if (DI::args()->getArgc() == 3) {
$album = DI::args()->getArgv()[2];
$sql_extra = sprintf("AND `album` = '%s' ", DBA::escape($album));
$sql_extra2 = "";
$path[$album] = $album;
}
$r = DBA::toArray(DBA::p("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
min(`scale`) AS `hiq`, max(`scale`) AS `loq`, ANY_VALUE(`desc`) AS `desc`, ANY_VALUE(`created`) AS `created`
FROM `photo` WHERE `uid` = ? $sql_extra AND NOT `photo-type` IN (?, ?)
GROUP BY `resource-id` $sql_extra2",
DI::userSession()->getLocalUserId(),
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
));
function _map_files1($rr)
{
$a = DI::app();
$types = Images::supportedTypes();
$ext = $types[$rr['type']];
$filename_e = $rr['filename'];
// Take the largest picture that is smaller or equal 640 pixels
$photo = Photo::selectFirst(['scale'], ["`resource-id` = ? AND `height` <= ? AND `width` <= ?", $rr['resource-id'], 640, 640], ['order' => ['scale']]);
$scale = $photo['scale'] ?? $rr['loq'];
return [
DI::baseUrl() . '/photos/' . $a->getLoggedInUserNickname() . '/image/' . $rr['resource-id'],
$filename_e,
DI::baseUrl() . '/photo/' . $rr['resource-id'] . '-' . $scale . '.'. $ext,
$rr['desc']
];
}
$files = array_map("_map_files1", $r);
$tpl = Renderer::getMarkupTemplate($template_file);
$o = Renderer::replaceMacros($tpl, [
'$type' => 'image',
'$path' => $path,
'$folders' => $albums,
'$files' => $files,
'$cancel' => DI::l10n()->t('Cancel'),
'$nickname' => $a->getLoggedInUserNickname(),
'$upload' => DI::l10n()->t('Upload')
]);
break;
case "file":
if (DI::args()->getArgc()==2) {
$files = DBA::selectToArray('attach', ['id', 'filename', 'filetype'], ['uid' => DI::userSession()->getLocalUserId()]);
function _map_files2($rr)
{
list($m1, $m2) = explode("/", $rr['filetype']);
$filetype = ( (file_exists("images/icons/$m1.png"))?$m1:"zip");
$filename_e = $rr['filename'];
return [DI::baseUrl() . '/attach/' . $rr['id'], $filename_e, DI::baseUrl() . '/images/icons/16/' . $filetype . '.png'];
}
$files = array_map("_map_files2", $files);
$tpl = Renderer::getMarkupTemplate($template_file);
$o = Renderer::replaceMacros($tpl, [
'$type' => 'file',
'$path' => ['' => DI::l10n()->t('Files')],
'$folders' => false,
'$files' => $files,
'$cancel' => DI::l10n()->t('Cancel'),
'$nickname' => $a->getLoggedInUserNickname(),
'$upload' => DI::l10n()->t('Upload')
]);
}
break;
}
if (!empty($_GET['mode'])) {
return $o;
} else {
System::httpExit($o);
}
}

View file

@ -85,6 +85,8 @@ class Arguments
/**
* @return string The module name based on the arguments
* @deprecated 2022.12 - With the new (sub-)routes, it's not trustworthy anymore, use the ModuleClass instead
* @see Router::getModuleClass()
*/
public function getModuleName(): string
{

View file

@ -40,6 +40,7 @@ use Friendica\Module\HTTPException\MethodNotAllowed;
use Friendica\Module\HTTPException\PageNotFound;
use Friendica\Module\Special\Options;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Network\HTTPException\MethodNotAllowedException;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Util\Router\FriendicaGroupCountBased;
@ -114,6 +115,9 @@ class Router
/** @var array */
private $server;
/** @var string|null */
protected $moduleClass = null;
/**
* @param array $server The $_SERVER variable
* @param string $baseRoutesFilepath The path to a base routes file to leverage cache, can be empty
@ -216,7 +220,7 @@ class Router
*
* @return bool
*/
private function isGroup(array $config)
private function isGroup(array $config): bool
{
return
is_array($config) &&
@ -252,21 +256,39 @@ class Router
*
* @return RouteCollector|null
*/
public function getRouteCollector()
public function getRouteCollector(): ?RouteCollector
{
return $this->routeCollector;
}
/**
* Returns the Friendica\BaseModule-extending class name if a route rule matched
*
* @return string
*
* @throws InternalServerErrorException
* @throws MethodNotAllowedException
* @throws NotFoundException
*/
public function getModuleClass(): string
{
if (empty($this->moduleClass)) {
$this->determineModuleClass();
}
return $this->moduleClass;
}
/**
* Returns the relevant module class name for the given page URI or NULL if no route rule matched.
*
* @return string A Friendica\BaseModule-extending class name if a route rule matched
* @return void
*
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't
* @throws HTTPException\NotFoundException If no rule matched
*/
private function getModuleClass(): string
private function determineModuleClass(): void
{
$cmd = $this->args->getCommand();
$cmd = '/' . ltrim($cmd, '/');
@ -277,21 +299,19 @@ class Router
// Check if the HTTP method is OPTIONS and return the special Options Module with the possible HTTP methods
if ($this->args->getMethod() === static::OPTIONS) {
$moduleClass = Options::class;
$this->parameters = ['allowedMethods' => $dispatcher->getOptions($cmd)];
$this->moduleClass = Options::class;
$this->parameters = ['allowedMethods' => $dispatcher->getOptions($cmd)];
} else {
$routeInfo = $dispatcher->dispatch($this->args->getMethod(), $cmd);
if ($routeInfo[0] === Dispatcher::FOUND) {
$moduleClass = $routeInfo[1];
$this->parameters = $routeInfo[2];
$this->moduleClass = $routeInfo[1];
$this->parameters = $routeInfo[2];
} elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
} else {
throw new HTTPException\NotFoundException($this->l10n->t('Page not found.'));
}
}
return $moduleClass;
}
public function getModule(?string $module_class = null): ICanHandleRequests

View file

@ -568,7 +568,15 @@ class Worker
// Set the workerLogger as new default logger
if ($method_call) {
call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv);
try {
call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv);
} catch (\TypeError $e) {
// No need to defer a worker queue entry if the arguments are invalid
Logger::notice('Wrong worker arguments', ['class' => $funcname, 'argv' => $argv, 'queue' => $queue, 'message' => $e->getMessage()]);
} catch (\Throwable $e) {
Logger::error('Uncaught exception in worker execution', ['class' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTraceAsString()]);
Worker::defer();
}
} else {
$funcname($argv, count($argv));
}

View file

@ -76,16 +76,17 @@ class Status extends BaseFactory
}
/**
* @param int $uriId Uri-ID of the item
* @param int $uid Item user
* @param int $uriId Uri-ID of the item
* @param int $uid Item user
* @param bool $reblog Check for reblogged post
*
* @return \Friendica\Object\Api\Mastodon\Status
* @throws HTTPException\InternalServerErrorException
* @throws ImagickException|HTTPException\NotFoundException
*/
public function createFromUriId(int $uriId, int $uid = 0): \Friendica\Object\Api\Mastodon\Status
public function createFromUriId(int $uriId, int $uid = 0, bool $reblog = true): \Friendica\Object\Api\Mastodon\Status
{
$fields = ['uri-id', 'uid', 'author-id', 'author-uri-id', 'author-link', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
$fields = ['uri-id', 'uid', 'author-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
'created', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!$item) {
@ -95,7 +96,10 @@ class Status extends BaseFactory
}
throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.'));
}
$account = $this->mstdnAccountFactory->createFromUriId($item['author-uri-id'], $uid);
$is_reshare = $reblog && !is_null($item['causer-uri-id']) && ($item['post-reason'] == Item::PR_ANNOUNCEMENT);
$account = $this->mstdnAccountFactory->createFromUriId($is_reshare ? $item['causer-uri-id']:$item['author-uri-id'], $uid);
$count_announce = Post::countPosts([
'thr-parent-id' => $uriId,
@ -183,6 +187,10 @@ class Status extends BaseFactory
$reshare = [];
}
if ($is_reshare) {
$reshare = $this->createFromUriId($uriId, $uid, false)->toArray();
}
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $reshare, $poll);
}

View file

@ -3420,8 +3420,7 @@ class Contact
["(NOT `unsearchable` OR `nurl` IN (SELECT `nurl` FROM `owner-view` WHERE `publish` OR `net-publish`))
AND (`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]);
$contacts = self::selectToArray([], $condition, $params);
return $contacts;
return self::selectToArray([], $condition, $params);
}
/**

View file

@ -1213,7 +1213,7 @@ class GServer
if (!empty($data['url'])) {
$serverdata['platform'] = strtolower($data['platform']);
$serverdata['version'] = $data['version'];
$serverdata['version'] = $data['version'] ?? 'N/A';
}
if (!empty($data['plugins'])) {
@ -2172,7 +2172,7 @@ class GServer
foreach ($servers['instances'] as $server) {
$url = (is_null($server['https_score']) ? 'http' : 'https') . '://' . $server['name'];
self::add($url);
}
}
}
}
}

View file

@ -385,6 +385,9 @@ class Item
Post\ThreadUser::update($item['uri-id'], $item['uid'], ['hidden' => true]);
}
DI::notify()->deleteForItem($item['uri-id']);
DI::notification()->deleteForItem($item['uri-id']);
Logger::info('Item has been marked for deletion.', ['id' => $item_id]);
return true;

View file

@ -173,6 +173,64 @@ class Photo
return $photo;
}
/**
* Returns all browsable albums for a given user
*
* @param int $uid The given user
*
* @return array An array of albums
* @throws \Exception
*/
public static function getBrowsableAlbumsForUser(int $uid): array
{
$photos = DBA::toArray(
DBA::p(
"SELECT DISTINCT(`album`) AS `album` FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?)",
$uid,
static::CONTACT_AVATAR,
static::CONTACT_BANNER
)
);
return array_column($photos, 'album');
}
/**
* Returns browsable photos for a given user (optional and a given album)
*
* @param int $uid The given user id
* @param string|null $album (optional) The given album
*
* @return array All photos of the user/album
* @throws \Exception
*/
public static function getBrowsablePhotosForUser(int $uid, string $album = null): array
{
$values = [
$uid,
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
];
if (!empty($album)) {
$sqlExtra = "AND `album` = ? ";
$values[] = $album;
$sqlExtra2 = "";
} else {
$sqlExtra = '';
$sqlExtra2 = ' ORDER BY created DESC LIMIT 0, 10';
}
return DBA::toArray(
DBA::p(
"SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
min(`scale`) AS `hiq`, max(`scale`) AS `loq`, ANY_VALUE(`desc`) AS `desc`, ANY_VALUE(`created`) AS `created`
FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?) $sqlExtra
GROUP BY `resource-id` $sqlExtra2",
$values
));
}
/**
* Check if photo with given conditions exists
*

View file

@ -195,7 +195,7 @@ class Delayed
$id = Item::insert($item, $notify, $preparation_mode == self::PREPARED);
Logger::notice('Post stored', ['id' => $id, 'uid' => $item['uid'], 'cid' => $item['contact-id']]);
Logger::notice('Post stored', ['id' => $id, 'uid' => $item['uid'], 'cid' => $item['contact-id'] ?? 'N/A']);
if (empty($uri) && !empty($item['uri'])) {
$uri = $item['uri'];

View file

@ -21,8 +21,12 @@
namespace Friendica\Module\Api\Mastodon\Accounts;
use Friendica\App\Router;
use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Module\BaseApi;
/**
@ -35,8 +39,72 @@ class UpdateCredentials extends BaseApi
self::checkAllowedScope(self::SCOPE_WRITE);
$uid = self::getCurrentUserID();
Logger::info('Patch data', ['data' => $request]);
$owner = User::getOwnerDataById($uid);
$this->response->unsupported(Router::PATCH, $request);
$request = $this->getRequest([
'bot' => ($owner['contact-type'] == Contact::TYPE_NEWS),
'discoverable' => $owner['net-publish'],
'display_name' => $owner['name'],
'fields_attributes' => [],
'locked' => $owner['manually-approve'],
'note' => $owner['about'],
'avatar' => [],
'header' => [],
], $request);
$user = [];
$profile = [];
if ($request['bot']) {
$user['account-type'] = Contact::TYPE_NEWS;
$user['page-flags'] = User::PAGE_FLAGS_SOAPBOX;
} elseif ($owner['contact-type'] == Contact::TYPE_NEWS) {
$user['account-type'] = Contact::TYPE_PERSON;
} else {
$user['account-type'] = $owner['contact-type'];
}
$profile['net-publish'] = $request['discoverable'];
if (!empty($request['display_name'])) {
$user['username'] = $request['display_name'];
}
if ($user['account-type'] == Contact::TYPE_COMMUNITY) {
$user['page-flags'] = $request['locked'] ? User::PAGE_FLAGS_PRVGROUP : User::PAGE_FLAGS_COMMUNITY;
} elseif ($user['account-type'] == Contact::TYPE_PERSON) {
if ($request['locked']) {
$user['page-flags'] = User::PAGE_FLAGS_NORMAL;
} elseif ($owner['page-flags'] == User::PAGE_FLAGS_NORMAL) {
$user['page-flags'] = User::PAGE_FLAGS_SOAPBOX;
}
}
if (!empty($request['note'])) {
$profile['about'] = $request['note'];
}
Logger::debug('Patch data', ['data' => $request, 'files' => $_FILES]);
Logger::info('Update profile and user', ['uid' => $uid, 'user' => $user, 'profile' => $profile]);
if (!empty($request['avatar'])) {
Photo::uploadAvatar(1, $request['avatar']);
}
if (!empty($request['header'])) {
Photo::uploadBanner(1, $request['header']);
}
User::update($user, $uid);
Profile::update($profile, $uid);
$cdata = Contact::getPublicAndUserContactID($owner['id'], $uid);
if (empty($cdata)) {
DI::mstdnError()->InternalError();
}
$account = DI::mstdnAccount()->createFromContactId($cdata['user'], $uid);
$this->response->exitWithJson($account->toArray());
}
}

View file

@ -26,6 +26,7 @@ use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Module\BaseApi;
@ -67,10 +68,24 @@ class Search extends BaseApi
if (empty($request['type']) || ($request['type'] == 'accounts')) {
$result['accounts'] = self::searchAccounts($uid, $request['q'], $request['resolve'], $limit, $request['offset'], $request['following']);
if (!is_array($result['accounts'])) {
// Curbing the search if we got an exact result
$request['type'] = 'accounts';
$result['accounts'] = [$result['accounts']];
}
}
if ((empty($request['type']) || ($request['type'] == 'statuses')) && (strpos($request['q'], '@') == false)) {
$result['statuses'] = self::searchStatuses($uid, $request['q'], $request['account_id'], $request['max_id'], $request['min_id'], $limit, $request['offset']);
if (!is_array($result['statuses'])) {
// Curbing the search if we got an exact result
$request['type'] = 'statuses';
$result['statuses'] = [$result['statuses']];
}
}
if ((empty($request['type']) || ($request['type'] == 'hashtags')) && (strpos($request['q'], '@') == false)) {
$result['hashtags'] = self::searchHashtags($request['q'], $request['exclude_unreviewed'], $limit, $request['offset'], $this->parameters['version']);
}
@ -78,31 +93,59 @@ class Search extends BaseApi
System::jsonExit($result);
}
/**
* @param int $uid
* @param string $q
* @param bool $resolve
* @param int $limit
* @param int $offset
* @param bool $following
* @return array|\Friendica\Object\Api\Mastodon\Account Object if result is absolute (exact account match), list if not
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \Friendica\Network\HTTPException\NotFoundException
* @throws \ImagickException
*/
private static function searchAccounts(int $uid, string $q, bool $resolve, int $limit, int $offset, bool $following)
{
$accounts = [];
if ((strrpos($q, '@') > 0) || Network::isValidHttpUrl($q)) {
$id = Contact::getIdForURL($q, 0, $resolve ? null : false);
if (!empty($id)) {
$accounts[] = DI::mstdnAccount()->createFromContactId($id, $uid);
}
if (
(strrpos($q, '@') > 0 || Network::isValidHttpUrl($q))
&& $id = Contact::getIdForURL($q, 0, $resolve ? null : false)
) {
return DI::mstdnAccount()->createFromContactId($id, $uid);
}
if (empty($accounts)) {
$contacts = Contact::searchByName($q, '', $following ? $uid : 0, $limit, $offset);
foreach ($contacts as $contact) {
$accounts[] = DI::mstdnAccount()->createFromContactId($contact['id'], $uid);
}
DBA::close($contacts);
$accounts = [];
foreach (Contact::searchByName($q, '', $following ? $uid : 0, $limit, $offset) as $contact) {
$accounts[] = DI::mstdnAccount()->createFromContactId($contact['id'], $uid);
}
return $accounts;
}
/**
* @param int $uid
* @param string $q
* @param string $account_id
* @param int $max_id
* @param int $min_id
* @param int $limit
* @param int $offset
* @return array|\Friendica\Object\Api\Mastodon\Status Object is result is absolute (exact post match), list if not
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \Friendica\Network\HTTPException\NotFoundException
* @throws \ImagickException
*/
private static function searchStatuses(int $uid, string $q, string $account_id, int $max_id, int $min_id, int $limit, int $offset)
{
if (Network::isValidHttpUrl($q)) {
$q = Network::convertToIdn($q);
// If the user-specific search failed, we search and probe a public post
$item_id = Item::fetchByLink($q, $uid) ?: Item::fetchByLink($q);
if ($item_id && $item = Post::selectFirst(['uri-id'], ['id' => $item_id])) {
return DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid);
}
}
$params = ['order' => ['uri-id' => true], 'limit' => [$offset, $limit]];
if (substr($q, 0, 1) == '#') {
@ -148,7 +191,7 @@ class Search extends BaseApi
return $statuses;
}
private static function searchHashtags(string $q, bool $exclude_unreviewed, int $limit, int $offset, int $version)
private static function searchHashtags(string $q, bool $exclude_unreviewed, int $limit, int $offset, int $version): array
{
$q = ltrim($q, '#');

View file

@ -37,7 +37,6 @@ class Attach extends BaseModule
*/
protected function rawContent(array $request = [])
{
$a = DI::app();
if (empty($this->parameters['item'])) {
throw new \Friendica\Network\HTTPException\BadRequestException();
}

View file

@ -0,0 +1,100 @@
<?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\Media\Attachment;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System;
use Friendica\Model\Attach;
use Friendica\Module\Response;
use Friendica\Network\HTTPException\UnauthorizedException;
use Friendica\Util\Profiler;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
/**
* Browser for Attachments
*/
class Browser extends BaseModule
{
/** @var IHandleUserSessions */
protected $session;
/** @var App */
protected $app;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, App $app, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session;
$this->app = $app;
}
protected function content(array $request = []): string
{
if (!$this->session->getLocalUserId()) {
throw new UnauthorizedException($this->t('You need to be logged in to access this page.'));
}
// Needed to match the correct template in a module that uses a different theme than the user/site/default
$theme = Strings::sanitizeFilePathItem($request['theme'] ?? '');
if ($theme && is_file("view/theme/$theme/config.php")) {
$this->app->setCurrentTheme($theme);
}
$files = Attach::selectToArray(['id', 'filename', 'filetype'], ['uid' => $this->session->getLocalUserId()]);
$fileArray = array_map([$this, 'map_files'], $files);
$tpl = Renderer::getMarkupTemplate('media/browser.tpl');
$output = Renderer::replaceMacros($tpl, [
'$type' => 'attachment',
'$path' => ['' => $this->t('Files')],
'$folders' => false,
'$files' => $fileArray,
'$cancel' => $this->t('Cancel'),
'$nickname' => $this->app->getLoggedInUserNickname(),
'$upload' => $this->t('Upload'),
]);
if (empty($request['mode'])) {
System::httpExit($output);
}
return $output;
}
protected function map_files(array $record): array
{
list($m1, $m2) = explode('/', $record['filetype']);
$filetype = file_exists(sprintf('images/icons/%s.png', $m1) ? $m1 : 'text');
return [
sprintf('%s/attach/%s', $this->baseUrl, $record['id']),
$record['filename'],
sprintf('%s/images/icon/16/%s.png', $this->baseUrl, $filetype),
];
}
}

View file

@ -19,7 +19,7 @@
*
*/
namespace Friendica\Module\Profile\Attachment;
namespace Friendica\Module\Media\Attachment;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
@ -73,30 +73,12 @@ class Upload extends \Friendica\BaseModule
$this->response->setType(Response::TYPE_JSON, 'application/json');
}
$nick = $this->parameters['nickname'];
$owner = User::getOwnerDataByNick($nick);
$owner = User::getOwnerDataById($this->userSession->getLocalUserId());
if (!$owner) {
$this->logger->warning('owner is not a valid record:', ['owner' => $owner, 'nick' => $nick]);
$this->logger->warning('Owner not found.', ['uid' => $this->userSession->getLocalUserId()]);
return $this->return(401, $this->t('Invalid request.'));
}
$can_post = false;
$contact_id = 0;
$page_owner_uid = $owner['uid'];
$community_page = $owner['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
if ($this->userSession->getLocalUserId() && $this->userSession->getLocalUserId() == $page_owner_uid) {
$can_post = true;
} elseif ($community_page && !empty($this->userSession->getRemoteContactID($page_owner_uid))) {
$contact_id = $this->userSession->getRemoteContactID($page_owner_uid);
$can_post = $this->database->exists('contact', ['blocked' => false, 'pending' => false, 'id' => $contact_id, 'uid' => $page_owner_uid]);
}
if (!$can_post) {
$this->logger->warning('User does not have required permissions', ['contact_id' => $contact_id, 'page_owner_uid' => $page_owner_uid]);
return $this->return(403, $this->t('Permission denied.'), true);
}
if (empty($_FILES['userfile'])) {
$this->logger->warning('No file uploaded (empty userfile)');
return $this->return(401, $this->t('Invalid request.'), true);
@ -126,7 +108,7 @@ class Upload extends \Friendica\BaseModule
return $this->return(401, $msg);
}
$newid = Attach::storeFile($tempFileName, $page_owner_uid, $fileName, '<' . $owner['id'] . '>');
$newid = Attach::storeFile($tempFileName, $owner['uid'], $fileName, '<' . $owner['id'] . '>');
@unlink($tempFileName);

View file

@ -0,0 +1,125 @@
<?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\Media\Photo;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System;
use Friendica\Model\Photo;
use Friendica\Module\Response;
use Friendica\Network\HTTPException\UnauthorizedException;
use Friendica\Util\Images;
use Friendica\Util\Profiler;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
/**
* Browser for Photos
*/
class Browser extends BaseModule
{
/** @var IHandleUserSessions */
protected $session;
/** @var App */
protected $app;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, App $app, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->session = $session;
$this->app = $app;
}
protected function content(array $request = []): string
{
if (!$this->session->getLocalUserId()) {
throw new UnauthorizedException($this->t('You need to be logged in to access this page.'));
}
// Needed to match the correct template in a module that uses a different theme than the user/site/default
$theme = Strings::sanitizeFilePathItem($request['theme'] ?? '');
if ($theme && is_file("view/theme/$theme/config.php")) {
$this->app->setCurrentTheme($theme);
}
$album = $this->parameters['album'] ?? null;
$photos = Photo::getBrowsablePhotosForUser($this->session->getLocalUserId(), $album);
$albums = $album ? false : Photo::getBrowsableAlbumsForUser($this->session->getLocalUserId());
$path = [
'' => $this->t('Photos'),
];
if (!empty($album)) {
$path[$album] = $album;
}
$photosArray = array_map([$this, 'map_files'], $photos);
$tpl = Renderer::getMarkupTemplate('media/browser.tpl');
$output = Renderer::replaceMacros($tpl, [
'$type' => 'photo',
'$path' => $path,
'$folders' => $albums,
'$files' => $photosArray,
'$cancel' => $this->t('Cancel'),
'$nickname' => $this->app->getLoggedInUserNickname(),
'$upload' => $this->t('Upload'),
]);
if (empty($request['mode'])) {
System::httpExit($output);
}
return $output;
}
protected function map_files(array $record): array
{
$types = Images::supportedTypes();
$ext = $types[$record['type']];
$filename_e = $record['filename'];
// Take the largest picture that is smaller or equal 640 pixels
$photo = Photo::selectFirst(
['scale'],
[
"`resource-id` = ? AND `height` <= ? AND `width` <= ?",
$record['resource-id'],
640,
640
],
['order' => ['scale']]);
$scale = $photo['scale'] ?? $record['loq'];
return [
sprintf('%s/photos/%s/image/%s', $this->baseUrl, $this->app->getLoggedInUserNickname(), $record['resource-id']),
$filename_e,
sprintf('%s/photo/%s-%s.%s', $this->baseUrl, $record['resource-id'], $scale, $ext),
$record['desc'],
];
}
}

View file

@ -19,7 +19,7 @@
*
*/
namespace Friendica\Module\Profile\Photos;
namespace Friendica\Module\Media\Photo;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
@ -76,36 +76,11 @@ class Upload extends \Friendica\BaseModule
$album = trim($request['album'] ?? '');
if (empty($_FILES['media'])) {
$user = $this->database->selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['nickname' => $this->parameters['nickname'], 'blocked' => false]);
} else {
$user = $this->database->selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['uid' => BaseApi::getCurrentUserID() ?: null, 'blocked' => false]);
}
$owner = User::getOwnerDataById($this->userSession->getLocalUserId());
if (!$this->database->isResult($user)) {
$this->logger->warning('User is not valid', ['nickname' => $this->parameters['nickname'], 'user' => $user]);
return $this->return(404, $this->t('User not found.'));
}
/*
* Setup permissions structures
*/
$can_post = false;
$visitor = 0;
$contact_id = 0;
$page_owner_uid = $user['uid'];
if ($this->userSession->getLocalUserId() && $this->userSession->getLocalUserId() == $page_owner_uid) {
$can_post = true;
} elseif ($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY && !$this->userSession->getRemoteContactID($page_owner_uid)) {
$contact_id = $this->userSession->getRemoteContactID($page_owner_uid);
$can_post = $this->database->exists('contact', ['blocked' => false, 'pending' => false, 'id' => $contact_id, 'uid' => $page_owner_uid]);
$visitor = $contact_id;
}
if (!$can_post) {
$this->logger->warning('No permission to upload files', ['contact_id' => $contact_id, 'page_owner_uid' => $page_owner_uid]);
return $this->return(403, $this->t('Permission denied.'), true);
if (!$owner) {
$this->logger->warning('Owner not found.', ['uid' => $this->userSession->getLocalUserId()]);
return $this->return(401, $this->t('Invalid request.'));
}
if (empty($_FILES['userfile']) && empty($_FILES['media'])) {
@ -223,9 +198,9 @@ class Upload extends \Friendica\BaseModule
$album = $this->t('Wall Photos');
}
$allow_cid = '<' . $user['id'] . '>';
$allow_cid = '<' . $owner['id'] . '>';
$result = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 0, Photo::DEFAULT, $allow_cid);
$result = Photo::store($image, $owner['uid'], 0, $resource_id, $filename, $album, 0, Photo::DEFAULT, $allow_cid);
if (!$result) {
$this->logger->warning('Photo::store() failed', ['result' => $result]);
return $this->return(401, $this->t('Image upload failed.'));
@ -233,7 +208,7 @@ class Upload extends \Friendica\BaseModule
if ($width > 640 || $height > 640) {
$image->scaleDown(640);
$result = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 1, Photo::DEFAULT, $allow_cid);
$result = Photo::store($image, $owner['uid'], 0, $resource_id, $filename, $album, 1, Photo::DEFAULT, $allow_cid);
if ($result) {
$smallest = 1;
}
@ -241,14 +216,14 @@ class Upload extends \Friendica\BaseModule
if ($width > 320 || $height > 320) {
$image->scaleDown(320);
$result = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 2, Photo::DEFAULT, $allow_cid);
$result = Photo::store($image, $owner['uid'], 0, $resource_id, $filename, $album, 2, Photo::DEFAULT, $allow_cid);
if ($result && ($smallest == 0)) {
$smallest = 2;
}
}
$this->logger->info('upload done');
return $this->return(200, "\n\n" . '[url=' . $this->baseUrl . '/photos/' . $user['nickname'] . '/image/' . $resource_id . '][img]' . $this->baseUrl . "/photo/$resource_id-$smallest." . $image->getExt() . "[/img][/url]\n\n");
return $this->return(200, "\n\n" . '[url=' . $this->baseUrl . '/photos/' . $owner['nickname'] . '/image/' . $resource_id . '][img]' . $this->baseUrl . "/photo/$resource_id-$smallest." . $image->getExt() . "[/img][/url]\n\n");
}
/**

View file

@ -19,7 +19,7 @@
*
*/
namespace Friendica\Module\Profile\Photos;
namespace Friendica\Module\Profile;
use Friendica\App;
use Friendica\Content\Pager;
@ -40,7 +40,7 @@ use Friendica\Util\Images;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
class Index extends \Friendica\Module\BaseProfile
class Photos extends \Friendica\Module\BaseProfile
{
/** @var IHandleUserSessions */
private $session;

View file

@ -33,6 +33,7 @@ use Friendica\Navigation\Notifications\Collection;
use Friendica\Navigation\Notifications\Entity;
use Friendica\Navigation\Notifications\Factory;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface;
@ -268,4 +269,23 @@ class Notification extends BaseRepository
return $this->db->delete(self::$table_name, $condition);
}
public function deleteForItem(int $itemUriId): bool
{
$conditionTarget = [
'vid' => Verb::getID(Activity::POST),
'target-uri-id' => $itemUriId,
];
$conditionParent = [
'vid' => Verb::getID(Activity::POST),
'parent-uri-id' => $itemUriId,
];
$this->logger->notice('deleteForItem', ['conditionTarget' => $conditionTarget, 'conditionParent' => $conditionParent]);
return
$this->db->delete(self::$table_name, $conditionTarget)
&& $this->db->delete(self::$table_name, $conditionParent);
}
}

View file

@ -807,4 +807,10 @@ class Notify extends BaseRepository
return $this->storeAndSend($params, $sitelink, $tsitelink, $hsitelink, $title, $subject, $preamble, $epreamble, $item['body'], $itemlink, true);
}
public function deleteForItem(int $itemUriId): void
{
$this->db->delete('notify', ['otype' => 'item', 'uri-id' => $itemUriId]);
$this->db->delete('notify', ['otype' => 'item', 'parent-uri-id' => $itemUriId]);
}
}

View file

@ -31,18 +31,16 @@ use Friendica\App\Router as R;
use Friendica\Module;
$profileRoutes = [
'' => [Module\Profile\Index::class, [R::GET]],
'/attachment/upload' => [Module\Profile\Attachment\Upload::class, [ R::POST]],
'/contacts/common' => [Module\Profile\Common::class, [R::GET]],
'/contacts[/{type}]' => [Module\Profile\Contacts::class, [R::GET]],
'/media' => [Module\Profile\Media::class, [R::GET]],
'/photos' => [Module\Profile\Photos\Index::class, [R::GET ]],
'/photos/upload' => [Module\Profile\Photos\Upload::class, [ R::POST]],
'/profile' => [Module\Profile\Profile::class, [R::GET]],
'/remote_follow' => [Module\Profile\RemoteFollow::class, [R::GET, R::POST]],
'/schedule' => [Module\Profile\Schedule::class, [R::GET, R::POST]],
'/status[/{category}[/{date1}[/{date2}]]]' => [Module\Profile\Status::class, [R::GET]],
'/unkmail' => [Module\Profile\UnkMail::class, [R::GET, R::POST]],
'' => [Module\Profile\Index::class, [R::GET]],
'/contacts/common' => [Module\Profile\Common::class, [R::GET]],
'/contacts[/{type}]' => [Module\Profile\Contacts::class, [R::GET]],
'/media' => [Module\Profile\Media::class, [R::GET]],
'/photos' => [Module\Profile\Photos::class, [R::GET ]],
'/profile' => [Module\Profile\Profile::class, [R::GET]],
'/remote_follow' => [Module\Profile\RemoteFollow::class, [R::GET, R::POST]],
'/schedule' => [Module\Profile\Schedule::class, [R::GET, R::POST]],
'/status[/{category}[/{date1}[/{date2}]]]' => [Module\Profile\Status::class, [R::GET]],
'/unkmail' => [Module\Profile\UnkMail::class, [R::GET, R::POST]],
];
$apiRoutes = [
@ -473,6 +471,14 @@ return [
'/magic' => [Module\Magic::class, [R::GET]],
'/manifest' => [Module\Manifest::class, [R::GET]],
'/friendica.webmanifest' => [Module\Manifest::class, [R::GET]],
'/media' => [
'/attachment/browser' => [Module\Media\Attachment\Browser::class, [R::GET]],
'/attachment/upload' => [Module\Media\Attachment\Upload::class, [ R::POST]],
'/photo/browser[/{album}]' => [Module\Media\Photo\Browser::class, [R::GET]],
'/photo/upload' => [Module\Media\Photo\Upload::class, [ R::POST]],
],
'/moderation' => [
'[/]' => [Module\Moderation\Summary::class, [R::GET]],
@ -560,7 +566,7 @@ return [
// Kept for backwards-compatibility
// @TODO remove by version 2023.12
'/photos/{nickname}' => [Module\Profile\Photos\Index::class, [R::GET]],
'/photos/{nickname}' => [Module\Profile\Photos::class, [R::GET]],
'/ping' => [Module\Notifications\Ping::class, [R::GET]],

View file

@ -345,12 +345,12 @@ img.acpopup-img {
.fbrowser .path a:before, .fbrowser .path .btn-link:before { content: "/"; padding-right: 5px;}
.fbrowser .folders ul { list-style-type: none; padding-left: 10px;}
.fbrowser .list { height: auto; overflow-y: hidden; margin: 10px 0px; }
.fbrowser.image .photo-album-image-wrapper { float: left; }
.fbrowser.image a img, .fbrowser.image .btn-link img { height: 48px; }
.fbrowser.image a p, .fbrowser.image .btn-link p { display: none;}
.fbrowser.file .photo-album-image-wrapper { float:none; white-space: nowrap; }
.fbrowser.file img { display: inline; }
.fbrowser.file p { display: inline; white-space: nowrap; }
.fbrowser.photo .photo-album-image-wrapper { float: left; }
.fbrowser.photo a img, .fbrowser.photo .btn-link img { height: 48px; }
.fbrowser.photo a p, .fbrowser.photo .btn-link p { display: none;}
.fbrowser.attachment .photo-album-image-wrapper { float:none; white-space: nowrap; }
.fbrowser.attachment img { display: inline; }
.fbrowser.attachment p { display: inline; white-space: nowrap; }
.fbrowser .upload { clear: both; padding-top: 1em;}
.fbrowser .error { background: #ffeeee; border: 1px solid #994444; color: #994444; padding: 0.5em;}
.fbrowser .error .close { float: right; font-weight: bold; }

View file

@ -1,144 +0,0 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later
/**
* Filebrowser - Friendica Communications Server
*
* Copyright (c) 2010-2021, the Friendica project
*
* 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 code handle user interaction for image/file upload/browser dialog.
* Is loaded from filebrowser_plain.tpl
*
* To load filebrowser in colorbox, call
*
* Dialog.doImageBrowser(eventname, id);
*
* or
*
* Dialog.doFileBrowser(eventname, id);
*
* where:
*
* eventname: event name to catch return value
* id: id returned to event handler
*
* When user select an item, an event in fired in parent page, on body element
* The event is named
*
* fbrowser.<type>.[<eventname>]
*
* <type> will be one of "image" or "file", and the event handler will
* get the following params:
*
* filemane: filename of item choosed by user
* embed: bbcode to embed element into posts
* id: id from caller code
*
* example:
*
* // open dialog for select an image for a textarea with id "myeditor"
* var id="myeditor";
* Dialog.doImageBrowser("example", id);
*
* // setup event handler to get user selection
* $("body").on("fbrowser.image.example", function(event, filename, bbcode, id) {
* // close colorbox
* $.colorbox.close();
* // replace textxarea text with bbcode
* $(id).value = bbcode;
* });
**/
var FileBrowser = {
nickname : "",
type : "",
event: "",
id : null,
init: function(nickname, type) {
FileBrowser.nickname = nickname;
FileBrowser.type = type;
FileBrowser.event = "fbrowser."+type;
if (location['hash']!=="") {
var h = location['hash'].replace("#","");
FileBrowser.event = FileBrowser.event + "." + h.split("-")[0];
FileBrowser.id = h.split("-")[1];
}
console.log("FileBrowser:", nickname, type,FileBrowser.event, FileBrowser.id );
$(".error a.close").on("click", function(e) {
e.preventDefault();
$(".error").addClass("hidden");
});
$(".folders a, .path a").on("click", function(e){
e.preventDefault();
location.href = baseurl + "/fbrowser/" + FileBrowser.type + "/" + encodeURIComponent(this.dataset.folder) + "?mode=minimal" + location['hash'];
});
$(".photo-album-photo-link").on('click', function(e){
e.preventDefault();
var embed = "";
if (FileBrowser.type == "image") {
embed = "[url="+this.dataset.link+"][img="+this.dataset.img+"]"+this.dataset.alt+"[/img][/url]";
}
if (FileBrowser.type=="file") {
// attachment links are "baseurl/attach/id"; we need id
embed = "[attachment]"+this.dataset.link.split("/").pop()+"[/attachment]";
}
console.log(FileBrowser.event, this.dataset.filename, embed, FileBrowser.id);
parent.$("body").trigger(FileBrowser.event, [
this.dataset.filename,
embed,
FileBrowser.id
]);
});
if ($("#upload-image").length)
var image_uploader = new window.AjaxUpload(
'upload-image',
{ action: 'profile/' + FileBrowser.nickname + '/photos/upload?response=json',
name: 'userfile',
responseType: 'json',
onSubmit: function(file,ext) { $('#profile-rotator').show(); $(".error").addClass('hidden'); },
onComplete: function(file,response) {
if (response['error']!= undefined) {
$(".error span").html(response['error']);
$(".error").removeClass('hidden');
$('#profile-rotator').hide();
return;
}
location = baseurl + "/fbrowser/image/?mode=minimal"+location['hash'];
location.reload(true);
}
}
);
if ($("#upload-file").length)
var file_uploader = new window.AjaxUpload(
'upload-file',
{ action: 'profile/' + FileBrowser.nickname + '/attachment/upload?response=json',
name: 'userfile',
responseType: 'json',
onSubmit: function(file,ext) { $('#profile-rotator').show(); $(".error").addClass('hidden'); },
onComplete: function(file,response) {
if (response['error']!= undefined) {
$(".error span").html(response['error']);
$(".error").removeClass('hidden');
$('#profile-rotator').hide();
return;
}
location = baseurl + "/fbrowser/file/?mode=minimal"+location['hash'];
location.reload(true);
}
}
);
}
};
// @license-end

View file

@ -166,7 +166,7 @@ $(function() {
/* event from comment textarea button popups */
/* insert returned bbcode at cursor position or replace selected text */
$("body").on("fbrowser.image.comment", function(e, filename, bbcode, id) {
$('body').on('fbrowser.photo.comment', function(e, filename, bbcode, id) {
$.colorbox.close();
var textarea = document.getElementById("comment-edit-text-" +id);
var start = textarea.selectionStart;
@ -1069,7 +1069,7 @@ var Dialog = {
* to the event handler
*/
doImageBrowser : function (name, id) {
var url = Dialog._get_url("image",name,id);
var url = Dialog._get_url('photo', name, id);
return Dialog.show(url);
},
@ -1086,7 +1086,7 @@ var Dialog = {
* to the event handler
*/
doFileBrowser : function (name, id) {
var url = Dialog._get_url("file",name,id);
var url = Dialog._get_url('attachment', name, id);
return Dialog.show(url);
},
@ -1095,7 +1095,7 @@ var Dialog = {
if (id !== undefined) {
hash = hash + "-" + id;
}
return baseurl + "/fbrowser/"+type+"/?mode=minimal#"+hash;
return 'media/' + type + '/browser?mode=minimal#' + hash;
},
_get_size: function() {

View file

@ -0,0 +1,156 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later
/**
* Filebrowser - Friendica Communications Server
*
* Copyright (c) 2010-2021, the Friendica project
*
* 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 code handle user interaction for image/file upload/browser dialog.
* Is loaded from filebrowser_plain.tpl
*
* To load filebrowser in colorbox, call
*
* Dialog.doImageBrowser(eventname, id);
*
* or
*
* Dialog.doFileBrowser(eventname, id);
*
* where:
*
* eventname: event name to catch return value
* id: id returned to event handler
*
* When user select an item, an event in fired in parent page, on body element
* The event is named
*
* fbrowser.<type>.[<eventname>]
*
* <type> will be one of "image" or "file", and the event handler will
* get the following params:
*
* filename: filename of item chosen by user
* embed: bbcode to embed element into posts
* id: id from caller code
*
* example:
*
* // open dialog for select an image for a textarea with id "myeditor"
* var id="myeditor";
* Dialog.doImageBrowser("example", id);
*
* // setup event handler to get user selection
* $("body").on("fbrowser.image.example", function(event, filename, bbcode, id) {
* // close colorbox
* $.colorbox.close();
* // replace textarea text with bbcode
* $(id).value = bbcode;
* });
**/
const Browser = {
nickname: '',
type: '',
event: '',
id: null,
init: function (nickname, type) {
Browser.nickname = nickname;
Browser.type = type;
Browser.event = 'fbrowser.' + type;
if (location['hash'] !== '') {
const h = location['hash'].replace('#', '');
Browser.event = Browser.event + '.' + h.split('-')[0];
Browser.id = h.split('-')[1];
}
$('.error a.close').on('click', function (e) {
e.preventDefault();
$('.error').addClass('hidden');
});
$('.folders a, .path a').on('click', function (e) {
e.preventDefault();
location.href = Browser._getUrl("minimal", location['hash'], this.dataset.folder);
location.reload();
});
$(".photo-album-photo-link").on('click', function (e) {
e.preventDefault();
let embed = '';
if (Browser.type === "photo") {
embed = '[url=' + this.dataset.link + '][img=' + this.dataset.img + ']' + this.dataset.alt + '[/img][/url]';
}
if (Browser.type === "attachment") {
embed = '[attachment]' + this.dataset.link + '[/attachment]';
}
parent.$('body').trigger(Browser.event, [
this.dataset.filename,
embed,
Browser.id
]);
});
if ($('#upload-photo').length) {
new window.AjaxUpload(
'upload-photo',
{
action: 'media/photo/upload?response=json',
name: 'userfile',
responseType: 'json',
onSubmit: function (file, ext) {
$('#profile-rotator').show();
$('.error').addClass('hidden');
},
onComplete: function (file, response) {
if (response['error'] !== undefined) {
$('.error span').html(response['error']);
$('.error').removeClass('hidden');
$('#profile-rotator').hide();
return;
}
location.href = Browser._getUrl("minimal", location['hash']);
location.reload();
}
}
);
}
if ($('#upload-attachment').length) {
new window.AjaxUpload(
'upload-attachment',
{
action: 'media/attachment/upload?response=json',
name: 'userfile',
responseType: 'json',
onSubmit: function (file, ext) {
$('#profile-rotator').show();
$('.error').addClass('hidden');
},
onComplete: function (file, response) {
if (response['error'] !== undefined) {
$('.error span').html(response['error']);
$('.error').removeClass('hidden');
$('#profile-rotator').hide();
return;
}
location.href = Browser._getUrl("minimal", location['hash']);
location.reload();
}
}
);
}
},
_getUrl: function (mode, hash, folder) {
let folderValue = folder !== undefined ? folder : Browser.folder;
let folderUrl = folderValue !== undefined ? '/' + encodeURIComponent(folderValue) : '';
return 'media/' + Browser.type + '/browser' + folderUrl + '?mode=' + mode + hash;
}
};
// @license-end

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2022.12-dev\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-23 18:16+0100\n"
"POT-Creation-Date: 2022-11-27 00:36+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,36 +18,13 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
#: mod/fbrowser.php:61 src/Content/Nav.php:195 src/Module/BaseProfile.php:64
#: view/theme/frio/theme.php:242
msgid "Photos"
msgstr ""
#: mod/fbrowser.php:119 mod/fbrowser.php:146 mod/photos.php:997
#: mod/photos.php:1098 src/Content/Conversation.php:389
#: src/Module/Contact/Follow.php:173 src/Module/Contact/Revoke.php:109
#: src/Module/Contact/Unfollow.php:126 src/Module/Post/Edit.php:164
#: src/Module/Post/Tag/Remove.php:109 src/Module/Profile/RemoteFollow.php:134
#: src/Module/Security/TwoFactor/SignOut.php:125
msgid "Cancel"
msgstr ""
#: mod/fbrowser.php:121 mod/fbrowser.php:148
#: src/Module/Settings/Profile/Photo/Index.php:128
msgid "Upload"
msgstr ""
#: mod/fbrowser.php:143
msgid "Files"
msgstr ""
#: mod/item.php:129 mod/item.php:133
msgid "Unable to locate original post."
msgstr ""
#: mod/item.php:179 mod/item.php:184 mod/item.php:853 mod/message.php:69
#: mod/item.php:179 mod/item.php:184 mod/item.php:855 mod/message.php:69
#: mod/message.php:114 mod/notes.php:44 mod/photos.php:159 mod/photos.php:884
#: src/Module/Attach.php:56 src/Module/BaseApi.php:94
#: src/Module/Attach.php:55 src/Module/BaseApi.php:94
#: src/Module/BaseNotifications.php:98 src/Module/BaseSettings.php:52
#: src/Module/Calendar/Event/API.php:88 src/Module/Calendar/Event/Form.php:84
#: src/Module/Calendar/Event/Show.php:54 src/Module/Calendar/Export.php:62
@ -62,9 +39,8 @@ msgstr ""
#: src/Module/Notifications/Notification.php:76
#: src/Module/Notifications/Notification.php:107
#: src/Module/OStatus/Repair.php:60 src/Module/OStatus/Subscribe.php:66
#: src/Module/Post/Edit.php:76 src/Module/Profile/Attachment/Upload.php:97
#: src/Module/Profile/Common.php:55 src/Module/Profile/Contacts.php:55
#: src/Module/Profile/Photos/Upload.php:108 src/Module/Profile/Schedule.php:39
#: src/Module/Post/Edit.php:76 src/Module/Profile/Common.php:55
#: src/Module/Profile/Contacts.php:55 src/Module/Profile/Schedule.php:39
#: src/Module/Profile/Schedule.php:56 src/Module/Profile/UnkMail.php:69
#: src/Module/Profile/UnkMail.php:121 src/Module/Profile/UnkMail.php:132
#: src/Module/Register.php:77 src/Module/Register.php:90
@ -84,23 +60,23 @@ msgstr ""
msgid "Permission denied."
msgstr ""
#: mod/item.php:328 mod/item.php:333
#: mod/item.php:330 mod/item.php:335
msgid "Empty post discarded."
msgstr ""
#: mod/item.php:671
#: mod/item.php:673
msgid "Post updated."
msgstr ""
#: mod/item.php:681 mod/item.php:686
#: mod/item.php:683 mod/item.php:688
msgid "Item wasn't stored."
msgstr ""
#: mod/item.php:697
#: mod/item.php:699
msgid "Item couldn't be fetched."
msgstr ""
#: mod/item.php:829 src/Module/Admin/Themes/Details.php:39
#: mod/item.php:831 src/Module/Admin/Themes/Details.php:39
#: src/Module/Admin/Themes/Index.php:59 src/Module/Debug/ItemBody.php:42
#: src/Module/Debug/ItemBody.php:57 src/Module/Item/Feed.php:80
msgid "Item not found."
@ -414,31 +390,28 @@ msgstr ""
#: src/Module/HCard.php:51 src/Module/Profile/Common.php:40
#: src/Module/Profile/Common.php:51 src/Module/Profile/Contacts.php:39
#: src/Module/Profile/Contacts.php:49 src/Module/Profile/Media.php:38
#: src/Module/Profile/Photos/Index.php:77
#: src/Module/Profile/Photos/Upload.php:87
#: src/Module/Profile/RemoteFollow.php:71 src/Module/Profile/Status.php:58
#: src/Module/Register.php:267
#: src/Module/Profile/Photos.php:77 src/Module/Profile/RemoteFollow.php:71
#: src/Module/Profile/Status.php:58 src/Module/Register.php:267
msgid "User not found."
msgstr ""
#: mod/photos.php:107 src/Module/BaseProfile.php:67
#: src/Module/Profile/Photos/Index.php:169
#: src/Module/Profile/Photos.php:169
msgid "Photo Albums"
msgstr ""
#: mod/photos.php:108 src/Module/Profile/Photos/Index.php:170
#: src/Module/Profile/Photos/Index.php:187
#: mod/photos.php:108 src/Module/Profile/Photos.php:170
#: src/Module/Profile/Photos.php:187
msgid "Recent Photos"
msgstr ""
#: mod/photos.php:110 mod/photos.php:1066
#: src/Module/Profile/Photos/Index.php:172
#: src/Module/Profile/Photos/Index.php:189
#: mod/photos.php:110 mod/photos.php:1066 src/Module/Profile/Photos.php:172
#: src/Module/Profile/Photos.php:189
msgid "Upload New Photos"
msgstr ""
#: mod/photos.php:128 src/Module/BaseSettings.php:74
#: src/Module/Profile/Photos/Index.php:153
#: src/Module/Profile/Photos.php:153
msgid "everybody"
msgstr ""
@ -472,7 +445,7 @@ msgid "%1$s was tagged in %2$s by %3$s"
msgstr ""
#: mod/photos.php:630 mod/photos.php:633 mod/photos.php:660
#: src/Module/Profile/Photos/Upload.php:213
#: src/Module/Media/Photo/Upload.php:188
#: src/Module/Settings/Profile/Photo/Index.php:59
#, php-format
msgid "Image exceeds size limit of %s"
@ -496,19 +469,19 @@ msgstr ""
msgid "Image file is empty."
msgstr ""
#: mod/photos.php:683 src/Module/Profile/Photos/Upload.php:179
#: src/Module/Profile/Photos/Upload.php:180
#: mod/photos.php:683 src/Module/Media/Photo/Upload.php:154
#: src/Module/Media/Photo/Upload.php:155
#: src/Module/Settings/Profile/Photo/Index.php:68
msgid "Unable to process image."
msgstr ""
#: mod/photos.php:709 src/Module/Profile/Photos/Upload.php:231
#: mod/photos.php:709 src/Module/Media/Photo/Upload.php:206
#: src/Module/Settings/Profile/Photo/Index.php:95
msgid "Image upload failed."
msgstr ""
#: mod/photos.php:795 src/Module/Conversation/Community.php:187
#: src/Module/Directory.php:48 src/Module/Profile/Photos/Index.php:72
#: src/Module/Directory.php:48 src/Module/Profile/Photos.php:72
#: src/Module/Search/Index.php:64
msgid "Public access denied."
msgstr ""
@ -517,7 +490,7 @@ msgstr ""
msgid "No photos selected"
msgstr ""
#: mod/photos.php:869 src/Module/Profile/Photos/Index.php:92
#: mod/photos.php:869 src/Module/Profile/Photos.php:92
msgid "Access to this item is restricted."
msgstr ""
@ -550,6 +523,16 @@ msgstr ""
msgid "Delete Album"
msgstr ""
#: mod/photos.php:997 mod/photos.php:1098 src/Content/Conversation.php:389
#: src/Module/Contact/Follow.php:173 src/Module/Contact/Revoke.php:109
#: src/Module/Contact/Unfollow.php:126
#: src/Module/Media/Attachment/Browser.php:78
#: src/Module/Media/Photo/Browser.php:88 src/Module/Post/Edit.php:164
#: src/Module/Post/Tag/Remove.php:109 src/Module/Profile/RemoteFollow.php:134
#: src/Module/Security/TwoFactor/SignOut.php:125
msgid "Cancel"
msgstr ""
#: mod/photos.php:1023
msgid "Edit Album"
msgstr ""
@ -566,7 +549,7 @@ msgstr ""
msgid "Show Oldest First"
msgstr ""
#: mod/photos.php:1051 src/Module/Profile/Photos/Index.php:140
#: mod/photos.php:1051 src/Module/Profile/Photos.php:140
msgid "View Photo"
msgstr ""
@ -1686,6 +1669,11 @@ msgstr ""
msgid "Your profile page"
msgstr ""
#: src/Content/Nav.php:195 src/Module/BaseProfile.php:64
#: src/Module/Media/Photo/Browser.php:74 view/theme/frio/theme.php:242
msgid "Photos"
msgstr ""
#: src/Content/Nav.php:195 view/theme/frio/theme.php:242
msgid "Your photos"
msgstr ""
@ -3180,7 +3168,7 @@ msgstr ""
msgid "[no subject]"
msgstr ""
#: src/Model/Photo.php:1086 src/Module/Profile/Photos/Upload.php:223
#: src/Model/Photo.php:1139 src/Module/Media/Photo/Upload.php:198
msgid "Wall Photos"
msgstr ""
@ -5265,7 +5253,7 @@ msgstr ""
msgid "Applications"
msgstr ""
#: src/Module/Attach.php:50 src/Module/Attach.php:62
#: src/Module/Attach.php:49 src/Module/Attach.php:61
msgid "Item was not found."
msgstr ""
@ -5894,10 +5882,10 @@ msgid "The contact could not be added."
msgstr ""
#: src/Module/Contact/MatchInterests.php:94
#: src/Module/Profile/Attachment/Upload.php:80
#: src/Module/Profile/Attachment/Upload.php:102
#: src/Module/Profile/Photos/Upload.php:113
#: src/Module/Profile/Photos/Upload.php:162
#: src/Module/Media/Attachment/Upload.php:80
#: src/Module/Media/Attachment/Upload.php:85
#: src/Module/Media/Photo/Upload.php:83 src/Module/Media/Photo/Upload.php:88
#: src/Module/Media/Photo/Upload.php:137
msgid "Invalid request."
msgstr ""
@ -7129,6 +7117,38 @@ msgstr ""
msgid "A Decentralized Social Network"
msgstr ""
#: src/Module/Media/Attachment/Browser.php:58
#: src/Module/Media/Photo/Browser.php:59
msgid "You need to be logged in to access this page."
msgstr ""
#: src/Module/Media/Attachment/Browser.php:75
msgid "Files"
msgstr ""
#: src/Module/Media/Attachment/Browser.php:80
#: src/Module/Media/Photo/Browser.php:90
#: src/Module/Settings/Profile/Photo/Index.php:128
msgid "Upload"
msgstr ""
#: src/Module/Media/Attachment/Upload.php:100
msgid "Sorry, maybe your upload is bigger than the PHP configuration allows"
msgstr ""
#: src/Module/Media/Attachment/Upload.php:100
msgid "Or - did you try to upload an empty file?"
msgstr ""
#: src/Module/Media/Attachment/Upload.php:107
#, php-format
msgid "File exceeds size limit of %s"
msgstr ""
#: src/Module/Media/Attachment/Upload.php:117
msgid "File upload failed."
msgstr ""
#: src/Module/Moderation/BaseUsers.php:72
msgid "List of all users"
msgstr ""
@ -8134,28 +8154,11 @@ msgstr ""
msgid "Remove"
msgstr ""
#: src/Module/Profile/Attachment/Upload.php:117
msgid "Sorry, maybe your upload is bigger than the PHP configuration allows"
msgstr ""
#: src/Module/Profile/Attachment/Upload.php:117
msgid "Or - did you try to upload an empty file?"
msgstr ""
#: src/Module/Profile/Attachment/Upload.php:124
#, php-format
msgid "File exceeds size limit of %s"
msgstr ""
#: src/Module/Profile/Attachment/Upload.php:134
msgid "File upload failed."
msgstr ""
#: src/Module/Profile/Contacts.php:119
msgid "No contacts."
msgstr ""
#: src/Module/Profile/Photos/Index.php:146
#: src/Module/Profile/Photos.php:146
msgid "View Album"
msgstr ""

View file

@ -61,11 +61,11 @@ function enableOnUser(){
**/
/* callback */
$('body').on('fbrowser.image.main', function(e, filename, embedcode, id) {
$('body').on('fbrowser.photo.main', function(e, filename, embedcode, id) {
$.colorbox.close();
addeditortext(embedcode);
});
$('body').on('fbrowser.file.main', function(e, filename, embedcode, id) {
$('body').on('fbrowser.attachment.main', function(e, filename, embedcode, id) {
$.colorbox.close();
addeditortext(embedcode);
});

View file

@ -1,11 +1,11 @@
<!--
This is the template used by mod/fbrowser.php
-->
<script type="text/javascript" src="{{$baseurl}}/view/js/ajaxupload.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="{{$baseurl}}/view/js/filebrowser.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/js/ajaxupload.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script type="text/javascript" src="view/js/module/media/browser.js?v={{$smarty.const.FRIENDICA_VERSION}}"></script>
<script>
$(function() {
FileBrowser.init("{{$nickname}}", "{{$type}}");
Browser.init("{{$nickname}}", "{{$type}}");
});
</script>
<div class="fbrowser {{$type}}">
@ -33,7 +33,7 @@
{{foreach $files as $f}}
<div class="photo-album-image-wrapper">
<a href="#" class="photo-album-photo-link" data-link="{{$f.0}}" data-filename="{{$f.1}}" data-img="{{$f.2}}" data-alt="{{$f.3}}">
<img src="{{$f.2}}">
<img alt="{{$f.3}}" src="{{$f.1}}">
<p>{{$f.1}}</p>
</a>
</div>

View file

@ -1563,10 +1563,10 @@ textarea.comment-edit-text:focus + .comment-edit-form .preview {
max-height: calc(100vh - 220px);
}
}
.fbrowser.image .photo-album-image-wrapper {
.fbrowser.photo .photo-album-image-wrapper {
box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.2);
}
.fbrowser.image .photo-album-image-wrapper .caption {
.fbrowser.photo .photo-album-image-wrapper .caption {
pointer-events: none;
}
.fbrowser .profile-rotator-wrapper {

View file

@ -1,264 +0,0 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later
/**
* Filebrowser - Friendica Communications Server
*
* Copyright (c) 2010-2021, the Friendica project
*
* 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 code handle user interaction for image/file upload/browser dialog.
* Is loaded from filebrowser_plain.tpl
*
* To load filebrowser in colorbox, call
*
* Dialog.doImageBrowser(eventname, id);
*
* or
*
* Dialog.doFileBrowser(eventname, id);
*
* where:
*
* eventname: event name to catch return value
* id: id returned to event handler
*
* When user select an item, an event in fired in parent page, on body element
* The event is named
*
* fbrowser.<type>.[<eventname>]
*
* <type> will be one of "image" or "file", and the event handler will
* get the following params:
*
* filemane: filename of item choosed by user
* embed: bbcode to embed element into posts
* id: id from caller code
*
* example:
*
* // open dialog for select an image for a textarea with id "myeditor"
* var id="myeditor";
* Dialog.doImageBrowser("example", id);
*
* // setup event handler to get user selection
* $("body").on("fbrowser.image.example", function(event, filename, bbcode, id) {
* // close colorbox
* $.colorbox.close();
* // replace textxarea text with bbcode
* $(id).value = bbcode;
* });
**/
/*
* IMPORTANT
*
* This is a modified version to work with
* the frio theme.and bootstrap modals
*
* The origninal file is under:
* js/filebrowser.js
*
*/
var FileBrowser = {
nickname: "",
type: "",
event: "",
folder: "",
id: null,
init: function (nickname, type, hash) {
FileBrowser.nickname = nickname;
FileBrowser.type = type;
FileBrowser.event = "fbrowser." + type;
if (hash !== "") {
var h = hash.replace("#", "");
var destination = h.split("-")[0];
FileBrowser.id = h.split("-")[1];
FileBrowser.event = FileBrowser.event + "." + destination;
if (destination === "comment") {
// Get the comment textimput field
var commentElm = document.getElementById("comment-edit-text-" + FileBrowser.id);
}
}
console.log("FileBrowser: " + nickname, type, FileBrowser.event, FileBrowser.id);
FileBrowser.postLoad();
$(".error .close").on("click", function (e) {
e.preventDefault();
$(".error").addClass("hidden");
});
// Click on album link
$(".fbrowser").on("click", ".folders button, .path button", function (e) {
e.preventDefault();
var url =
baseurl +
"/fbrowser/" +
FileBrowser.type +
"/" +
encodeURIComponent(this.dataset.folder) +
"?mode=none&theme=frio";
FileBrowser.folder = this.dataset.folder;
FileBrowser.loadContent(url);
});
//Embed on click
$(".fbrowser").on("click", ".photo-album-photo-link", function (e) {
e.preventDefault();
var embed = "";
if (FileBrowser.type === "image") {
embed = "[url=" + this.dataset.link + "][img=" + this.dataset.img + "]" + this.dataset.alt + "[/img][/url]";
}
if (FileBrowser.type === "file") {
// attachment links are "baseurl/attach/id"; we need id
embed = "[attachment]" + this.dataset.link.split("/").pop() + "[/attachment]";
}
// Delete prefilled Text of the comment input
// Note: not the best solution but function commentOpenUI don't
// work as expected (we need a way to wait until commentOpenUI would be finished).
// As for now we insert pieces of this function here
if (commentElm !== null && typeof commentElm !== "undefined") {
if (commentElm.value === "") {
$("#comment-edit-text-" + FileBrowser.id)
.addClass("comment-edit-text-full")
.removeClass("comment-edit-text-empty");
$("#comment-edit-submit-wrapper-" + FileBrowser.id).show();
$("#comment-edit-text-" + FileBrowser.id).attr("tabindex", "9");
$("#comment-edit-submit-" + FileBrowser.id).attr("tabindex", "10");
}
}
console.log(FileBrowser.event, this.dataset.filename, embed, FileBrowser.id);
$("body").trigger(FileBrowser.event, [this.dataset.filename, embed, FileBrowser.id, this.dataset.img]);
// Close model
$("#modal").modal("hide");
// Update autosize for this textarea
autosize.update($(".text-autosize"));
});
// EventListener for switching between image and file mode
$(".fbrowser").on("click", ".fbswitcher .btn", function (e) {
e.preventDefault();
FileBrowser.type = this.getAttribute("data-mode");
$(".fbrowser")
.removeClass()
.addClass("fbrowser " + FileBrowser.type);
url = baseurl + "/fbrowser/" + FileBrowser.type + "?mode=none&theme=frio";
FileBrowser.loadContent(url);
});
},
// Initialize the AjaxUpload for the upload buttons
uploadButtons: function () {
if ($("#upload-image").length) {
//AjaxUpload for images
var image_uploader = new window.AjaxUpload("upload-image", {
action:
"profile/" +
FileBrowser.nickname +
"/photos/upload?response=json&album=" +
encodeURIComponent(FileBrowser.folder),
name: "userfile",
responseType: "json",
onSubmit: function (file, ext) {
$(".fbrowser-content").hide();
$(".fbrowser .profile-rotator-wrapper").show();
$(".error").addClass("hidden");
},
onComplete: function (file, response) {
if (response["error"] != undefined) {
$(".error span").html(response["error"]);
$(".error").removeClass("hidden");
$(".fbrowser .profile-rotator-wrapper").hide();
$(".fbrowser-content").show();
return;
}
// load new content to fbrowser window
FileBrowser.loadContent(
baseurl +
"/fbrowser/" +
FileBrowser.type +
"/" +
encodeURIComponent(FileBrowser.folder) +
"?mode=none&theme=frio",
);
},
});
}
if ($("#upload-file").length) {
//AjaxUpload for files
var file_uploader = new window.AjaxUpload("upload-file", {
action: "profile/" + FileBrowser.nickname + "/attachment/upload?response=json",
name: "userfile",
responseType: "json",
onSubmit: function (file, ext) {
$(".fbrowser-content").hide();
$(".fbrowser .profile-rotator-wrapper").show();
$(".error").addClass("hidden");
},
onComplete: function (file, response) {
if (response["error"] != undefined) {
$(".error span").html(response["error"]);
$(".error").removeClass("hidden");
$(".fbrowser .profile-rotator-wrapper").hide();
$(".fbrowser-content").show();
return;
}
var url = baseurl + "/fbrowser/" + FileBrowser.type + "?mode=none&theme=frio";
// Load new content to fbrowser window
FileBrowser.loadContent(url);
},
});
}
},
// Stuff which should be executed if ne content was loaded
postLoad: function () {
FileBrowser.initGallery();
$(".fbrowser .fbswitcher .btn").removeClass("active");
$(".fbrowser .fbswitcher [data-mode=" + FileBrowser.type + "]").addClass("active");
// We need to add the AjaxUpload to the button
FileBrowser.uploadButtons();
},
// Load new content (e.g. change photo album)
loadContent: function (url) {
$(".fbrowser-content").hide();
$(".fbrowser .profile-rotator-wrapper").show();
// load new content to fbrowser window
$(".fbrowser").load(url, function (responseText, textStatus) {
$(".profile-rotator-wrapper").hide();
if (textStatus === "success") {
$(".fbrowser_content").show();
FileBrowser.postLoad();
}
});
},
// Initialize justified Gallery
initGallery: function () {
$(".fbrowser.image .fbrowser-content-container").justifiedGallery({
rowHeight: 80,
margins: 4,
border: 0,
});
},
};
// @license-end

View file

@ -82,7 +82,7 @@ $(document).ready(function () {
});
// Insert filebrowser images into the input field (field_fileinput.tpl).
$("body").on("fbrowser.image.input", function (e, filename, embedcode, id, img) {
$("body").on("fbrowser.photo.input", function (e, filename, embedcode, id, img) {
// Select the clicked button by it's attribute.
var elm = $("[image-input='select']");
// Select the input field which belongs to this button.
@ -132,12 +132,12 @@ Dialog.show = function (url, title) {
Dialog._get_url = function (type, name, id) {
var hash = name;
if (id !== undefined) hash = hash + "-" + id;
return "fbrowser/" + type + "/?mode=none&theme=frio#" + hash;
return 'media/' + type + '/browser?mode=none&theme=frio#' + hash;
};
// Does load the filebrowser into the jot modal.
Dialog.showJot = function () {
var type = "image";
var type = "photo";
var name = "main";
var url = Dialog._get_url(type, name);
@ -159,15 +159,15 @@ Dialog._load = function (url) {
let filebrowser = document.getElementById("filebrowser");
// Try to fetch the hash form the url.
let match = url.match(/fbrowser\/[a-z]+\/.*(#.*)/);
let match = url.match(/media\/[a-z]+\/.*(#.*)/);
if (!filebrowser || match === null) {
return; //not fbrowser
}
// Initialize the filebrowser.
loadScript("view/js/ajaxupload.js");
loadScript("view/theme/frio/js/filebrowser.js", function () {
FileBrowser.init(filebrowser.dataset.nickname, filebrowser.dataset.type, match[1]);
loadScript("view/theme/frio/js/module/media/browser.js", function () {
Browser.init(filebrowser.dataset.nickname, filebrowser.dataset.type, match[1]);
});
};

View file

@ -0,0 +1,250 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later
/**
* Filebrowser - Friendica Communications Server
*
* Copyright (c) 2010-2021, the Friendica project
*
* 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 code handle user interaction for photo/file upload/browser dialog.
* Is loaded from filebrowser_plain.tpl
*
* To load filebrowser in colorbox, call
*
* Dialog.doImageBrowser(eventname, id);
*
* or
*
* Dialog.doFileBrowser(eventname, id);
*
* where:
*
* eventname: event name to catch return value
* id: id returned to event handler
*
* When user select an item, an event in fired in parent page, on body element
* The event is named
*
* fbrowser.<type>.[<eventname>]
*
* <type> will be one of "image" or "file", and the event handler will
* get the following params:
*
* filename: filename of item chosen by user
* embed: bbcode to embed element into posts
* id: id from caller code
*
* example:
*
* // open dialog for select an image for a textarea with id "myeditor"
* var id="myeditor";
* Dialog.doImageBrowser("example", id);
*
* // setup event handler to get user selection
* $("body").on("fbrowser.image.example", function(event, filename, bbcode, id) {
* // close colorbox
* $.colorbox.close();
* // replace textarea text with bbcode
* $(id).value = bbcode;
* });
**/
/*
* IMPORTANT
*
* This is a modified version to work with
* the frio theme and Bootstrap modals
*
* The original file is under:
* js/module/media/browser.js
*
*/
var Browser = {
nickname: '',
type: '',
event: '',
folder: '',
id: null,
init: function (nickname, type, hash) {
Browser.nickname = nickname;
Browser.type = type;
Browser.event = 'fbrowser.' + type;
if (hash !== '') {
const h = hash.replace('#', '');
const destination = h.split('-')[0];
Browser.id = h.split('-')[1];
Browser.event = Browser.event + '.' + destination;
if (destination === 'comment') {
// Get the comment textinput field
var commentElm = document.getElementById('comment-edit-text-' + Browser.id);
}
}
Browser.postLoad();
$('.error .close').on('click', function (e) {
e.preventDefault();
$('.error').addClass('hidden');
});
// Click on album link
$('.fbrowser').on('click', '.folders button, .path button', function (e) {
e.preventDefault();
let url = Browser._getUrl("none", this.dataset.folder);
Browser.folder = this.dataset.folder;
Browser.loadContent(url);
});
//Embed on click
$('.fbrowser').on('click', '.photo-album-photo-link', function (e) {
e.preventDefault();
let embed = '';
if (Browser.type === 'photo') {
embed = '[url=' + this.dataset.link + '][img=' + this.dataset.img + ']' + this.dataset.alt + '[/img][/url]';
}
if (Browser.type === 'attachment') {
embed = '[attachment]' + this.dataset.link + '[/attachment]';
}
// Delete prefilled Text of the comment input
// Note: not the best solution but function commentOpenUI don't
// work as expected (we need a way to wait until commentOpenUI would be finished).
// As for now we insert pieces of this function here
if (commentElm !== null && typeof commentElm !== 'undefined') {
if (commentElm.value === '') {
$('#comment-edit-text-' + Browser.id)
.addClass('comment-edit-text-full')
.removeClass('comment-edit-text-empty');
$('#comment-edit-submit-wrapper-' + Browser.id).show();
$('#comment-edit-text-' + Browser.id).attr('tabindex', '9');
$('#comment-edit-submit-' + Browser.id).attr('tabindex', '10');
}
}
console.log(Browser.event, this.dataset.filename, embed, Browser.id);
$('body').trigger(Browser.event, [this.dataset.filename, embed, Browser.id, this.dataset.img]);
// Close model
$('#modal').modal('hide');
// Update autosize for this textarea
autosize.update($('.text-autosize'));
});
// EventListener for switching between photo and file mode
$('.fbrowser').on('click', '.fbswitcher .btn', function (e) {
e.preventDefault();
Browser.type = this.getAttribute('data-mode');
$('.fbrowser')
.removeClass()
.addClass('fbrowser ' + Browser.type);
Browser.loadContent(Browser._getUrl("none"));
});
},
// Initialize the AjaxUpload for the upload buttons
uploadButtons: function () {
if ($('#upload-photo').length) {
//AjaxUpload for photos
new window.AjaxUpload(
'upload-photo',
{
action: 'media/photo/upload?response=json&album=' + encodeURIComponent(Browser.folder),
name: 'userfile',
responseType: 'json',
onSubmit: function (file, ext) {
$('.fbrowser-content').hide();
$('.fbrowser .profile-rotator-wrapper').show();
$('.error').addClass('hidden');
},
onComplete: function (file, response) {
if (response['error'] !== undefined) {
$('.error span').html(response['error']);
$('.error').removeClass('hidden');
$('.fbrowser .profile-rotator-wrapper').hide();
$('.fbrowser-content').show();
return;
}
// load new content to fbrowser window
Browser.loadContent(Browser._getUrl("none"));
},
});
}
if ($('#upload-attachment').length) {
//AjaxUpload for files
new window.AjaxUpload(
'upload-attachment',
{
action: 'media/attachment/upload?response=json',
name: 'userfile',
responseType: 'json',
onSubmit: function (file, ext) {
$('.fbrowser-content').hide();
$('.fbrowser .profile-rotator-wrapper').show();
$('.error').addClass('hidden');
},
onComplete: function (file, response) {
if (response["error"] !== undefined) {
$('.error span').html(response['error']);
$('.error').removeClass('hidden');
$('.fbrowser .profile-rotator-wrapper').hide();
$('.fbrowser-content').show();
return;
}
// Load new content to fbrowser window
Browser.loadContent(Browser._getUrl("none"));
},
});
}
},
// Stuff which should be executed if no content was loaded
postLoad: function () {
Browser.initGallery();
$('.fbrowser .fbswitcher .btn').removeClass('active');
$('.fbrowser .fbswitcher [data-mode=' + Browser.type + ']').addClass('active');
// We need to add the AjaxUpload to the button
Browser.uploadButtons();
},
// Load new content (e.g. change photo album)
loadContent: function (url) {
$('.fbrowser-content').hide();
$('.fbrowser .profile-rotator-wrapper').show();
// load new content to fbrowser window
$('.fbrowser').load(url, function (responseText, textStatus) {
$('.profile-rotator-wrapper').hide();
if (textStatus === 'success') {
$(".fbrowser_content").show();
Browser.postLoad();
}
});
},
// Initialize justified Gallery
initGallery: function () {
$('.fbrowser.photo .fbrowser-content-container').justifiedGallery({
rowHeight: 80,
margins: 4,
border: 0,
});
},
_getUrl: function (mode, folder) {
let folderValue = folder !== undefined ? folder : Browser.folder;
let folderUrl = folderValue !== undefined ? '/' + encodeURIComponent(folderValue) : '';
return 'media/' + Browser.type + '/browser' + folderUrl + '?mode=' + mode + "&theme=frio";
}
};
// @license-end

View file

@ -6,7 +6,7 @@
<div class="contact-entry-photo mframe" id="contact-entry-photo-{{$contact.id}}">
<div class="contact-photo-image-wrapper hidden-xs">
<a href="{{if $contact.photo_menu.edit}}{{$contact.photo_menu.edit.1}}{{else}}{{$contact.url}}{{/if}}">
<a href="{{if !empty($contact.photo_menu.edit)}}{{$contact.photo_menu.edit.1}}{{else}}{{$contact.url}}{{/if}}">
<img class="contact-photo media-object xl" src="{{$contact.thumb}}" {{$contact.sparkle}} alt="{{$contact.name}}" />
</a>
</div>

View file

@ -61,13 +61,13 @@
**/
/* callback */
$('body').on('fbrowser.image.main', function(e, filename, embedcode, id) {
$('body').on('fbrowser.photo.main', function(e, filename, embedcode, id) {
///@todo this part isn't ideal and need to be done in a better way
jotTextOpenUI(document.getElementById("profile-jot-text"));
jotActive();
addeditortext(embedcode);
})
.on('fbrowser.file.main', function(e, filename, embedcode, id) {
.on('fbrowser.attachment.main', function(e, filename, embedcode, id) {
jotTextOpenUI(document.getElementById("profile-jot-text"));
jotActive();
addeditortext(embedcode);

View file

@ -16,9 +16,9 @@
{{/foreach}}
{{* Switch between image and file mode *}}
<div class="fbswitcher btn-group btn-group-xs pull-right" aria-label="Switch between image and file mode">
<button type="button" class="btn btn-default" data-mode="image" aria-label="Image Mode"><i class="fa fa-picture-o" aria-hidden="true"></i></button>
<button type="button" class="btn btn-default" data-mode="file" aria-label="File Mode"><i class="fa fa-file-o" aria-hidden="true"></i></button>
<div class="fbswitcher btn-group btn-group-xs pull-right" aria-label="Switch between photo and attachment mode">
<button type="button" class="btn btn-default" data-mode="photo" aria-label="Photo Mode"><i class="fa fa-picture-o" aria-hidden="true"></i></button>
<button type="button" class="btn btn-default" data-mode="attachment" aria-label="Attachment Mode"><i class="fa fa-file-o" aria-hidden="true"></i></button>
</div>
</ol>

View file

@ -2517,29 +2517,29 @@ footer {
.fbrowser .list {
padding: 10px;
}
.fbrowser.image .photo-album-image-wrapper {
.fbrowser.photo .photo-album-image-wrapper {
width: 48px;
height: 48px;
}
.fbrowser.image a img {
.fbrowser.photo a img {
width: auto;
height: 48px;
}
.fbrowser.image a p {
.fbrowser.photo a p {
display: none;
}
.fbrowser.file .photo-album-image-wrapper {
.fbrowser.attachment .photo-album-image-wrapper {
float: none;
white-space: nowrap;
width: 100%;
height: auto;
}
.fbrowser.file img {
.fbrowser.attachment img {
display: inline;
width: 16px;
height: 16px;
}
.fbrowser.file p {
.fbrowser.attachment p {
display: inline;
white-space: nowrap;
}

View file

@ -2516,29 +2516,29 @@ footer {
.fbrowser .list {
padding: 10px;
}
.fbrowser.image .photo-album-image-wrapper {
.fbrowser.photo .photo-album-image-wrapper {
width: 48px;
height: 48px;
}
.fbrowser.image a img {
.fbrowser.photo a img {
width: auto;
height: 48px;
}
.fbrowser.image a p {
.fbrowser.photo a p {
display: none;
}
.fbrowser.file .photo-album-image-wrapper {
.fbrowser.attachment .photo-album-image-wrapper {
float: none;
white-space: nowrap;
width: 100%;
height: auto;
}
.fbrowser.file img {
.fbrowser.attachment img {
display: inline;
width: 16px;
height: 16px;
}
.fbrowser.file p {
.fbrowser.attachment p {
display: inline;
white-space: nowrap;
}

View file

@ -2516,29 +2516,29 @@ footer {
.fbrowser .list {
padding: 10px;
}
.fbrowser.image .photo-album-image-wrapper {
.fbrowser.photo .photo-album-image-wrapper {
width: 48px;
height: 48px;
}
.fbrowser.image a img {
.fbrowser.photo a img {
width: auto;
height: 48px;
}
.fbrowser.image a p {
.fbrowser.photo a p {
display: none;
}
.fbrowser.file .photo-album-image-wrapper {
.fbrowser.attachment .photo-album-image-wrapper {
float: none;
white-space: nowrap;
width: 100%;
height: auto;
}
.fbrowser.file img {
.fbrowser.attachment img {
display: inline;
width: 16px;
height: 16px;
}
.fbrowser.file p {
.fbrowser.attachment p {
display: inline;
white-space: nowrap;
}

View file

@ -1673,11 +1673,11 @@ footer { height: 100px; display: table-row; }
}
.fbrowser .folders ul { list-style: url("icons/folder.png"); padding-left: 22px;}
.fbrowser .list { padding: 10px; }
.fbrowser.image .photo-album-image-wrapper { width: 48px; height: 48px; }
.fbrowser.image a img { width: auto; height: 48px; }
.fbrowser.image a p { display: none;}
.fbrowser.file .photo-album-image-wrapper { float:none; white-space: nowrap; width: 100%; height: auto; }
.fbrowser.file img { display: inline; width: 16px; height: 16px}
.fbrowser.file p { display: inline; white-space: nowrap; }
.fbrowser.photo .photo-album-image-wrapper { width: 48px; height: 48px; }
.fbrowser.photo a img { width: auto; height: 48px; }
.fbrowser.photo a p { display: none;}
.fbrowser.attachment .photo-album-image-wrapper { float:none; white-space: nowrap; width: 100%; height: auto; }
.fbrowser.attachment img { display: inline; width: 16px; height: 16px}
.fbrowser.attachment p { display: inline; white-space: nowrap; }
.fbrowser .upload { clear: both; padding-top: 1em;}

View file

@ -3261,7 +3261,7 @@ img.photo-album-photo {
}
/* upload/select popup */
fbrowser.image .photo-album-image-wrapper { margin-left: 10px; }
fbrowser.photo .photo-album-image-wrapper { margin-left: 10px; }
#message-preview { margin-top: 15px; }
#message-preview span { width: 100%; }
#message-preview .mail-count, #message-preview .mail-delete { display:none; }