mirror of
https://github.com/friendica/friendica
synced 2024-11-10 03:42:53 +00:00
Merge branch 'develop' into show_image_upload_limit
# Conflicts: # src/Util/Strings.php # view/lang/C/messages.po
This commit is contained in:
commit
a01872a117
72 changed files with 1605 additions and 1038 deletions
|
@ -45,7 +45,7 @@
|
|||
"paragonie/hidden-string": "^1.0",
|
||||
"patrickschur/language-detection": "^5.0.0",
|
||||
"pear/console_table": "^1.3",
|
||||
"phpseclib/phpseclib": "^2.0",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"pragmarx/google2fa": "^5.0",
|
||||
"pragmarx/recovery": "^0.2",
|
||||
"psr/container": "^1.0",
|
||||
|
|
26
composer.lock
generated
26
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e8626dc6957dff9cc783daad10cfc26f",
|
||||
"content-hash": "2e082bac083ca61cc0c22f7055d690bf",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asika/simple-console",
|
||||
|
@ -2952,32 +2952,32 @@
|
|||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "2.0.38",
|
||||
"version": "3.0.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd"
|
||||
"reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b03536539f43a4f9aa33c4f0b2f3a1c752088fcd",
|
||||
"reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/dbc2307d5c69aeb22db136c52e91130d7f2ca761",
|
||||
"reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
"paragonie/constant_time_encoding": "^1|^2",
|
||||
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||
"php": ">=5.6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phing/phing": "~2.7",
|
||||
"phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
|
||||
"squizlabs/php_codesniffer": "~2.0"
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
|
||||
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
|
||||
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
|
||||
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
|
||||
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
|
||||
"ext-xml": "Install the XML extension to load XML formatted public keys."
|
||||
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -2985,7 +2985,7 @@
|
|||
"phpseclib/bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"phpseclib\\": "phpseclib/"
|
||||
"phpseclib3\\": "phpseclib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
@ -3054,7 +3054,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-09-02T17:04:26+00:00"
|
||||
"time": "2022-10-24T10:51:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
|
|
|
@ -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/)
|
||||
|
@ -104,24 +105,26 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
|
|||
- [`DELETE /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
|
||||
- [`GET /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
|
||||
- [`GET /api/v1/search`](https://docs.joinmastodon.org/methods/search/)
|
||||
- [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/#create)
|
||||
- Additionally to the static values `public`, `unlisted` and `private`, the `visibility` parameter can contain a numeric value with a group id.
|
||||
- [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`DELETE /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`GET /api/v1/statuses/:id/context`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`GET /api/v1/statuses/:id/reblogged_by`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`GET /api/v1/statuses/:id/favourited_by`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/favourite`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/unfavourite`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/reblog`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/unreblog`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/bookmark`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/unbookmark`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/mute`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/unmute`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/pin`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`POST /api/v1/statuses/:id/unpin`](https://docs.joinmastodon.org/methods/statuses/)
|
||||
- [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/#get)
|
||||
- [`DELETE /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/#delete)
|
||||
- [`GET /api/v1/statuses/:id/context`](https://docs.joinmastodon.org/methods/statuses/#context)
|
||||
- [`GET /api/v1/statuses/:id/reblogged_by`](https://docs.joinmastodon.org/methods/statuses/#reblogged_by)
|
||||
- [`GET /api/v1/statuses/:id/favourited_by`](https://docs.joinmastodon.org/methods/statuses/#favourited_by)
|
||||
- [`POST /api/v1/statuses/:id/favourite`](https://docs.joinmastodon.org/methods/statuses/#favourite)
|
||||
- [`POST /api/v1/statuses/:id/unfavourite`](https://docs.joinmastodon.org/methods/statuses/#unfavourite)
|
||||
- [`POST /api/v1/statuses/:id/reblog`](https://docs.joinmastodon.org/methods/statuses/#boost)
|
||||
- [`POST /api/v1/statuses/:id/unreblog`](https://docs.joinmastodon.org/methods/statuses/#unreblog)
|
||||
- [`POST /api/v1/statuses/:id/bookmark`](https://docs.joinmastodon.org/methods/statuses/#bookmark)
|
||||
- [`POST /api/v1/statuses/:id/unbookmark`](https://docs.joinmastodon.org/methods/statuses/#unbookmark)
|
||||
- [`POST /api/v1/statuses/:id/mute`](https://docs.joinmastodon.org/methods/statuses/#mute)
|
||||
- [`POST /api/v1/statuses/:id/unmute`](https://docs.joinmastodon.org/methods/statuses/#unmute)
|
||||
- [`POST /api/v1/statuses/:id/pin`](https://docs.joinmastodon.org/methods/statuses/#pin)
|
||||
- [`POST /api/v1/statuses/:id/unpin`](https://docs.joinmastodon.org/methods/statuses/#unpin)
|
||||
- [`POST /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/#edit)
|
||||
- [`GET /api/v1/statuses/:id/source`](https://docs.joinmastodon.org/methods/statuses/#source)
|
||||
- [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/#card)
|
||||
- [`GET /api/v1/suggestions`](https://docs.joinmastodon.org/methods/accounts/suggestions/)
|
||||
- [`GET /api/v1/timelines/direct`](https://docs.joinmastodon.org/methods/timelines/)
|
||||
- [`GET /api/v1/timelines/home`](https://docs.joinmastodon.org/methods/timelines/)
|
||||
|
@ -136,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)
|
||||
|
@ -144,7 +146,6 @@ These emdpoints are planned to be implemented somewhere in the future.
|
|||
- [`GET /api/v1/trends/statuses`](https://github.com/mastodon/mastodon/pull/17431)
|
||||
- [`GET /api/v1/trends/tags`](https://github.com/mastodon/mastodon/pull/16917)
|
||||
- [`POST /api/v1/polls/:id/votes`](https://docs.joinmastodon.org/methods/statuses/polls/)
|
||||
- [`GET /api/v1/statuses/{id:\d+}/source`](https://github.com/mastodon/mastodon/pull/16697)
|
||||
- [`GET /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/)
|
||||
- [`POST /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/)
|
||||
- [`DELETE /api/v1/featured_tags/:id`](https://docs.joinmastodon.org/methods/accounts/featured_tags/)
|
||||
|
|
160
mod/fbrowser.php
160
mod/fbrowser.php
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ bin/daemon.php
|
|||
bin/testargs.php
|
||||
bin/wait-for-connection
|
||||
bin/worker.php
|
||||
config/addon-sample.config.php
|
||||
config/local-sample.config.php
|
||||
doc/
|
||||
images/
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,25 +48,27 @@ class BaseCollection extends \ArrayIterator
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($key, $value): void
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
if (is_null($key)) {
|
||||
$this->totalCount++;
|
||||
}
|
||||
|
||||
parent::offsetSet($offset, $value);
|
||||
parent::offsetSet($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($key): void
|
||||
{
|
||||
if ($this->offsetExists($offset)) {
|
||||
if ($this->offsetExists($key)) {
|
||||
$this->totalCount--;
|
||||
}
|
||||
|
||||
parent::offsetUnset($offset);
|
||||
parent::offsetUnset($key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -794,7 +794,7 @@ class Conversation
|
|||
return [];
|
||||
}
|
||||
|
||||
$str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get($this->session->getLocalUserId(), 'system', 'blocked'));
|
||||
$str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get($this->session->getLocalUserId(), 'system', 'blocked') ?? '');
|
||||
if (empty($str_blocked)) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ class Database extends AbstractSessionHandler
|
|||
return true;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function read($id)
|
||||
{
|
||||
if (empty($id)) {
|
||||
|
@ -136,6 +137,7 @@ class Database extends AbstractSessionHandler
|
|||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function gc($max_lifetime): bool
|
||||
{
|
||||
try {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -379,6 +379,14 @@ abstract class DI
|
|||
return self::$dice->create(Factory\Api\Mastodon\Status::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\StatusSource
|
||||
*/
|
||||
public static function mstdnStatusSource()
|
||||
{
|
||||
return self::$dice->create(Factory\Api\Mastodon\StatusSource::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Mastodon\ScheduledStatus
|
||||
*/
|
||||
|
|
|
@ -110,7 +110,7 @@ class Database
|
|||
$this->connected = false;
|
||||
|
||||
$port = 0;
|
||||
$serveraddr = trim($this->configCache->get('database', 'hostname'));
|
||||
$serveraddr = trim($this->configCache->get('database', 'hostname') ?? '');
|
||||
$serverdata = explode(':', $serveraddr);
|
||||
$host = trim($serverdata[0]);
|
||||
if (count($serverdata) > 1) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
48
src/Factory/Api/Mastodon/StatusSource.php
Normal file
48
src/Factory/Api/Mastodon/StatusSource.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?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\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
class StatusSource extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* @param int $uriId Uri-ID of the item
|
||||
*
|
||||
* @return \Friendica\Object\Api\Mastodon\StatusSource
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException*@throws \Exception
|
||||
*/
|
||||
public function createFromUriId(int $uriId, int $uid): \Friendica\Object\Api\Mastodon\StatusSource
|
||||
{
|
||||
$post = Post::selectFirst(['uri-id', 'raw-body', 'body', 'title'], ['uri-id' => $uriId, 'uid' => [0, $uid]]);
|
||||
|
||||
$spoiler_text = $post['title'] ?: BBCode::toPlaintext(BBCode::getAbstract($post['body'], Protocol::ACTIVITYPUB));
|
||||
$body = BBCode::toMarkdown($post['body']);
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\StatusSource($post['uri-id'], $body, $spoiler_text);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -687,7 +687,7 @@ class Media
|
|||
$previews[] = $medium['preview'];
|
||||
}
|
||||
|
||||
$type = explode('/', explode(';', $medium['mimetype'])[0]);
|
||||
$type = explode('/', explode(';', $medium['mimetype'] ?? '')[0]);
|
||||
if (count($type) < 2) {
|
||||
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $medium]);
|
||||
$filetype = 'unkn';
|
||||
|
|
|
@ -325,8 +325,8 @@ class Site extends BaseAdmin
|
|||
/* Installed langs */
|
||||
$lang_choices = DI::l10n()->getAvailableLanguages();
|
||||
|
||||
if (strlen(DI::config()->get('system', 'directory_submit_url')) &&
|
||||
!strlen(DI::config()->get('system', 'directory'))) {
|
||||
if (DI::config()->get('system', 'directory_submit_url') &&
|
||||
!DI::config()->get('system', 'directory')) {
|
||||
DI::config()->set('system', 'directory', dirname(DI::config()->get('system', 'directory_submit_url')));
|
||||
DI::config()->delete('system', 'directory_submit_url');
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, '#');
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\App\Router;
|
||||
use Friendica\Content\Text\Markdown;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
|
@ -35,6 +34,7 @@ use Friendica\Model\Photo;
|
|||
use Friendica\Model\Post;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Images;
|
||||
|
||||
|
@ -48,7 +48,47 @@ class Statuses extends BaseApi
|
|||
self::checkAllowedScope(self::SCOPE_WRITE);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$this->response->unsupported(Router::PUT, $request);
|
||||
$request = $this->getRequest([
|
||||
'status' => '', // Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.
|
||||
'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply
|
||||
'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field.
|
||||
'language' => '', // ISO 639 language code for this status.
|
||||
], $request);
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
|
||||
$condition = [
|
||||
'uid' => $uid,
|
||||
'uri-id' => $this->parameters['id'],
|
||||
'contact-id' => $owner['id'],
|
||||
'author-id' => Contact::getPublicIdByUserId($uid),
|
||||
'origin' => true,
|
||||
];
|
||||
|
||||
$post = Post::selectFirst(['uri-id', 'id'], $condition);
|
||||
if (empty($post['id'])) {
|
||||
throw new HTTPException\NotFoundException('Item with URI ID ' . $this->parameters['id'] . ' not found for user ' . $uid . '.');
|
||||
}
|
||||
|
||||
// The imput is defined as text. So we can use Markdown for some enhancements
|
||||
$item = ['body' => Markdown::toBBCode($request['status']), 'app' => $this->getApp()];
|
||||
|
||||
if (!empty($request['language'])) {
|
||||
$item['language'] = json_encode([$request['language'] => 1]);
|
||||
}
|
||||
|
||||
if (!empty($request['spoiler_text'])) {
|
||||
if ($request['in_reply_to_id'] != $post['uri-id']) {
|
||||
$item['body'] = '[abstract=' . Protocol::ACTIVITYPUB . ']' . $request['spoiler_text'] . "[/abstract]\n" . $item['body'];
|
||||
} else {
|
||||
$item['title'] = $request['spoiler_text'];
|
||||
}
|
||||
}
|
||||
|
||||
Item::update($item, ['id' => $post['id']]);
|
||||
Item::updateDisplayCache($post['uri-id']);
|
||||
|
||||
System::jsonExit(DI::mstdnStatus()->createFromUriId($post['uri-id'], $uid));
|
||||
}
|
||||
|
||||
protected function post(array $request = [])
|
||||
|
@ -80,14 +120,7 @@ class Statuses extends BaseApi
|
|||
$item['contact-id'] = $owner['id'];
|
||||
$item['author-id'] = $item['owner-id'] = Contact::getPublicIdByUserId($uid);
|
||||
$item['body'] = $body;
|
||||
|
||||
if (!empty(self::getCurrentApplication()['name'])) {
|
||||
$item['app'] = self::getCurrentApplication()['name'];
|
||||
}
|
||||
|
||||
if (empty($item['app'])) {
|
||||
$item['app'] = 'API';
|
||||
}
|
||||
$item['app'] = $this->getApp();
|
||||
|
||||
switch ($request['visibility']) {
|
||||
case 'public':
|
||||
|
@ -257,4 +290,13 @@ class Statuses extends BaseApi
|
|||
|
||||
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid));
|
||||
}
|
||||
|
||||
private function getApp(): string
|
||||
{
|
||||
if (!empty(self::getCurrentApplication()['name'])) {
|
||||
return self::getCurrentApplication()['name'];
|
||||
} else {
|
||||
return 'API';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
57
src/Module/Api/Mastodon/Statuses/Source.php
Normal file
57
src/Module/Api/Mastodon/Statuses/Source.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?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\Api\Mastodon\Statuses;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/statuses/#source
|
||||
*/
|
||||
class Source extends BaseApi
|
||||
{
|
||||
/**
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
self::checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($this->parameters['id'])) {
|
||||
DI::mstdnError()->UnprocessableEntity();
|
||||
}
|
||||
|
||||
$id = $this->parameters['id'];
|
||||
|
||||
if (!Post::exists(['uri-id' => $id, 'uid' => [0, $uid]])) {
|
||||
throw new HTTPException\NotFoundException('Item with URI ID ' . $id . ' not found' . ($uid ? ' for user ' . $uid : '.'));
|
||||
}
|
||||
|
||||
$source = DI::mstdnStatusSource()->createFromUriId($id, $uid);
|
||||
|
||||
System::jsonExit($source->toArray());
|
||||
}
|
||||
}
|
|
@ -48,7 +48,10 @@ class Suggestions extends BaseApi
|
|||
$accounts = [];
|
||||
|
||||
foreach ($suggestions as $suggestion) {
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($suggestion['id'], $uid);
|
||||
$accounts[] = [
|
||||
'source' => 'past_interactions',
|
||||
'account' => DI::mstdnAccount()->createFromContactId($suggestion['id'], $uid)
|
||||
];
|
||||
}
|
||||
|
||||
System::jsonExit($accounts);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -327,7 +327,7 @@ class Profile extends BaseModule
|
|||
'$submit' => $this->t('Submit'),
|
||||
'$lbl_info1' => $lbl_info1,
|
||||
'$lbl_info2' => $this->t('Their personal note'),
|
||||
'$reason' => trim($contact['reason']),
|
||||
'$reason' => trim($contact['reason'] ?? ''),
|
||||
'$infedit' => $this->t('Edit contact notes'),
|
||||
'$common_link' => 'contact/' . $contact['id'] . '/contacts/common',
|
||||
'$relation_text' => $relation_text,
|
||||
|
|
100
src/Module/Media/Attachment/Browser.php
Normal file
100
src/Module/Media/Attachment/Browser.php
Normal 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),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
125
src/Module/Media/Photo/Browser.php
Normal file
125
src/Module/Media/Photo/Browser.php
Normal 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'],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
/**
|
|
@ -142,14 +142,9 @@ class Salmon extends \Friendica\BaseModule
|
|||
throw new HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
$key_info = explode('.', $key);
|
||||
$this->logger->info('Key details', ['info' => $key]);
|
||||
|
||||
$m = Strings::base64UrlDecode($key_info[1]);
|
||||
$e = Strings::base64UrlDecode($key_info[2]);
|
||||
|
||||
$this->logger->info('Key details', ['info' => $key_info]);
|
||||
|
||||
$pubkey = Crypto::meToPem($m, $e);
|
||||
$pubkey = SalmonProtocol::magicKeyToPem($key);
|
||||
|
||||
// We should have everything we need now. Let's see if it verifies.
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -23,11 +23,9 @@ namespace Friendica\Module;
|
|||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException\BadRequestException;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Protocol\Salmon;
|
||||
|
||||
/**
|
||||
* prints the public RSA key of a user
|
||||
|
@ -47,9 +45,10 @@ class PublicRSAKey extends BaseModule
|
|||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
Crypto::pemToMe($user['spubkey'], $modulus, $exponent);
|
||||
|
||||
$content = 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true);
|
||||
System::httpExit($content, Response::TYPE_BLANK, 'application/magic-public-key');
|
||||
System::httpExit(
|
||||
Salmon::salmonKey($user['spubkey']),
|
||||
Response::TYPE_BLANK,
|
||||
'application/magic-public-key'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,7 +233,7 @@ class PortableContacts extends BaseModule
|
|||
}
|
||||
|
||||
if ($selectedFields['tags']) {
|
||||
$tags = str_replace(',', ' ', $contact['keywords']);
|
||||
$tags = str_replace(',', ' ', $contact['keywords'] ?? '');
|
||||
$tags = explode(' ', $tags);
|
||||
|
||||
$cleaned = [];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ use Friendica\Protocol\ActivityNamespace;
|
|||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Email;
|
||||
use Friendica\Protocol\Feed;
|
||||
use Friendica\Protocol\Salmon;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
|
@ -1512,12 +1513,10 @@ class Probe
|
|||
$pubkey = $curlResult->getBody();
|
||||
}
|
||||
|
||||
$key = explode('.', $pubkey);
|
||||
try {
|
||||
$data['pubkey'] = Salmon::magicKeyToPem($pubkey);
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
if (sizeof($key) >= 3) {
|
||||
$m = Strings::base64UrlDecode($key[1]);
|
||||
$e = Strings::base64UrlDecode($key[2]);
|
||||
$data['pubkey'] = Crypto::meToPem($m, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
54
src/Object/Api/Mastodon/StatusSource.php
Normal file
54
src/Object/Api/Mastodon/StatusSource.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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\Object\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseDataTransferObject;
|
||||
|
||||
/**
|
||||
* Class StatusSource
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/entities/StatusSource/
|
||||
*/
|
||||
class StatusSource extends BaseDataTransferObject
|
||||
{
|
||||
/** @var string */
|
||||
protected $id;
|
||||
/** @var string */
|
||||
protected $text;
|
||||
/** @var string */
|
||||
protected $spoiler_text = "";
|
||||
|
||||
/**
|
||||
* Creates a source record from an post array.
|
||||
*
|
||||
* @param integer $id
|
||||
* @param string $text
|
||||
* @param string $spoiler_text
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(int $id, string $text, string $spoiler_text)
|
||||
{
|
||||
$this->id = (string)$id;
|
||||
$this->text = $text;
|
||||
$this->spoiler_text = $spoiler_text;
|
||||
}
|
||||
}
|
|
@ -200,6 +200,7 @@ class Email implements IEmail
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
|
|
|
@ -224,14 +224,34 @@ class Diaspora
|
|||
|
||||
// Is it a private post? Then decrypt the outer Salmon
|
||||
if (is_object($data)) {
|
||||
$encrypted_aes_key_bundle = base64_decode($data->aes_key);
|
||||
$ciphertext = base64_decode($data->encrypted_magic_envelope);
|
||||
try {
|
||||
if (!isset($data->aes_key) || !isset($data->encrypted_magic_envelope)) {
|
||||
Logger::info('Missing keys "aes_key" and/or "encrypted_magic_envelope"', ['data' => $data]);
|
||||
throw new \RuntimeException('Missing keys "aes_key" and/or "encrypted_magic_envelope"');
|
||||
}
|
||||
|
||||
$outer_key_bundle = '';
|
||||
@openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $privKey);
|
||||
$j_outer_key_bundle = json_decode($outer_key_bundle);
|
||||
$encrypted_aes_key_bundle = base64_decode($data->aes_key);
|
||||
$ciphertext = base64_decode($data->encrypted_magic_envelope);
|
||||
|
||||
if (!is_object($j_outer_key_bundle)) {
|
||||
$outer_key_bundle = '';
|
||||
@openssl_private_decrypt($encrypted_aes_key_bundle, $outer_key_bundle, $privKey);
|
||||
$j_outer_key_bundle = json_decode($outer_key_bundle);
|
||||
|
||||
if (!is_object($j_outer_key_bundle)) {
|
||||
Logger::info('Unable to decode outer key bundle', ['outer_key_bundle' => $outer_key_bundle]);
|
||||
throw new \RuntimeException('Unable to decode outer key bundle');
|
||||
}
|
||||
|
||||
if (!isset($j_outer_key_bundle->iv) || !isset($j_outer_key_bundle->key)) {
|
||||
Logger::info('Missing keys "iv" and/or "key" from outer Salmon', ['j_outer_key_bundle' => $j_outer_key_bundle]);
|
||||
throw new \RuntimeException('Missing keys "iv" and/or "key" from outer Salmon');
|
||||
}
|
||||
|
||||
$outer_iv = base64_decode($j_outer_key_bundle->iv);
|
||||
$outer_key = base64_decode($j_outer_key_bundle->key);
|
||||
|
||||
$xml = self::aesDecrypt($outer_key, $outer_iv, $ciphertext);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::notice('Outer Salmon did not verify. Discarding.');
|
||||
if ($no_exit) {
|
||||
return false;
|
||||
|
@ -239,11 +259,6 @@ class Diaspora
|
|||
throw new \Friendica\Network\HTTPException\BadRequestException();
|
||||
}
|
||||
}
|
||||
|
||||
$outer_iv = base64_decode($j_outer_key_bundle->iv);
|
||||
$outer_key = base64_decode($j_outer_key_bundle->key);
|
||||
|
||||
$xml = self::aesDecrypt($outer_key, $outer_iv, $ciphertext);
|
||||
} else {
|
||||
$xml = $raw;
|
||||
}
|
||||
|
|
|
@ -25,9 +25,11 @@ use Friendica\Core\Logger;
|
|||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\Salmon\Format\Magic;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
/**
|
||||
* Salmon Protocol class
|
||||
|
@ -243,7 +245,19 @@ class Salmon
|
|||
*/
|
||||
public static function salmonKey(string $pubkey): string
|
||||
{
|
||||
Crypto::pemToMe($pubkey, $modulus, $exponent);
|
||||
return 'RSA' . '.' . Strings::base64UrlEncode($modulus, true) . '.' . Strings::base64UrlEncode($exponent, true);
|
||||
\phpseclib3\Crypt\RSA::addFileFormat(Magic::class);
|
||||
|
||||
return PublicKeyLoader::load($pubkey)->toString('Magic');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $magic Magic key format starting with "RSA."
|
||||
* @return string
|
||||
*/
|
||||
public static function magicKeyToPem(string $magic): string
|
||||
{
|
||||
\phpseclib3\Crypt\RSA::addFileFormat(Magic::class);
|
||||
|
||||
return (string) PublicKeyLoader::load($magic);
|
||||
}
|
||||
}
|
||||
|
|
77
src/Protocol/Salmon/Format/Magic.php
Normal file
77
src/Protocol/Salmon/Format/Magic.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?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\Protocol\Salmon\Format;
|
||||
|
||||
use Friendica\Util\Strings;
|
||||
use phpseclib3\Math\BigInteger;
|
||||
|
||||
/**
|
||||
* This custom public RSA key format class is meant to be used with the \phpseclib3\Crypto\RSA::addFileFormat method.
|
||||
*
|
||||
* It handles Salmon's specific magic key string starting with "RSA." and which MIME type is application/magic-key or
|
||||
* application/magic-public-key
|
||||
*
|
||||
* @see https://web.archive.org/web/20160506073138/http://salmon-protocol.googlecode.com:80/svn/trunk/draft-panzer-magicsig-01.html#anchor13
|
||||
*/
|
||||
class Magic
|
||||
{
|
||||
public static function load($key, $password = ''): array
|
||||
{
|
||||
if (!is_string($key)) {
|
||||
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
|
||||
}
|
||||
|
||||
$key_info = explode('.', $key);
|
||||
|
||||
if (count($key_info) !== 3) {
|
||||
throw new \UnexpectedValueException('Key should have three components separated by periods');
|
||||
}
|
||||
|
||||
if ($key_info[0] !== 'RSA') {
|
||||
throw new \UnexpectedValueException('Key first component should be "RSA"');
|
||||
}
|
||||
|
||||
if (preg_match('#[+/]#', $key_info[1])
|
||||
|| preg_match('#[+/]#', $key_info[1])
|
||||
) {
|
||||
throw new \UnexpectedValueException('Wrong encoding, expecting Base64URLencoding');
|
||||
}
|
||||
|
||||
$m = Strings::base64UrlDecode($key_info[1]);
|
||||
$e = Strings::base64UrlDecode($key_info[2]);
|
||||
|
||||
if (!$m || !$e) {
|
||||
throw new \UnexpectedValueException('Base64 decoding produced an error');
|
||||
}
|
||||
|
||||
return [
|
||||
'modulus' => new BigInteger($m, 256),
|
||||
'publicExponent' => new BigInteger($e, 256),
|
||||
'isPublicKey' => true,
|
||||
];
|
||||
}
|
||||
|
||||
public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string
|
||||
{
|
||||
return 'RSA.' . Strings::base64UrlEncode($n->toBytes(), true) . '.' . Strings::base64UrlEncode($e->toBytes(), true);
|
||||
}
|
||||
}
|
|
@ -21,14 +21,11 @@
|
|||
|
||||
namespace Friendica\Util;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use ParagonIE\ConstantTime\Base64UrlSafe;
|
||||
use phpseclib\Crypt\RSA;
|
||||
use phpseclib\Math\BigInteger;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
/**
|
||||
* Crypto class
|
||||
|
@ -66,22 +63,6 @@ class Crypto
|
|||
return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param string $m modulo
|
||||
* @param string $e exponent
|
||||
* @return string
|
||||
*/
|
||||
public static function meToPem($m, $e)
|
||||
{
|
||||
$rsa = new RSA();
|
||||
$rsa->loadKey([
|
||||
'e' => new BigInteger($e, 256),
|
||||
'n' => new BigInteger($m, 256)
|
||||
]);
|
||||
return $rsa->getPublicKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform RSA public keys to standard PEM output
|
||||
*
|
||||
|
@ -91,29 +72,7 @@ class Crypto
|
|||
*/
|
||||
public static function rsaToPem(string $key)
|
||||
{
|
||||
$rsa = new RSA();
|
||||
$rsa->setPublicKey($key);
|
||||
|
||||
return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the modulo and exponent reference from a public PEM key
|
||||
*
|
||||
* @param string $key public PEM key
|
||||
* @param string $modulus (ref) modulo reference
|
||||
* @param string $exponent (ref) exponent reference
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function pemToMe(string $key, &$modulus, &$exponent)
|
||||
{
|
||||
$rsa = new RSA();
|
||||
$rsa->loadKey($key);
|
||||
$rsa->setPublicKey();
|
||||
|
||||
$modulus = $rsa->modulus->toBytes();
|
||||
$exponent = $rsa->exponent->toBytes();
|
||||
return (string)PublicKeyLoader::load($key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,50 +111,6 @@ class Crypto
|
|||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new elliptic curve key pair
|
||||
*
|
||||
* @return array with the elements "prvkey", "pubkey", "vapid-public" and "vapid-private"
|
||||
*/
|
||||
public static function newECKeypair()
|
||||
{
|
||||
$openssl_options = [
|
||||
'curve_name' => 'prime256v1',
|
||||
'private_key_type' => OPENSSL_KEYTYPE_EC
|
||||
];
|
||||
|
||||
$conf = DI::config()->get('system', 'openssl_conf_file');
|
||||
if ($conf) {
|
||||
$openssl_options['config'] = $conf;
|
||||
}
|
||||
$result = openssl_pkey_new($openssl_options);
|
||||
|
||||
if (empty($result)) {
|
||||
throw new Exception('Key creation failed');
|
||||
}
|
||||
|
||||
$response = ['prvkey' => '', 'pubkey' => ''];
|
||||
|
||||
// Get private key
|
||||
openssl_pkey_export($result, $response['prvkey']);
|
||||
|
||||
// Get public key
|
||||
$pkey = openssl_pkey_get_details($result);
|
||||
$response['pubkey'] = $pkey['key'];
|
||||
|
||||
// Create VAPID keys
|
||||
// @see https://github.com/web-push-libs/web-push-php/blob/256a18b2a2411469c94943725fb6eccb9681bd75/src/Utils.php#L60-L62
|
||||
$hexString = '04';
|
||||
$hexString .= str_pad(bin2hex($pkey['ec']['x']), 64, '0', STR_PAD_LEFT);
|
||||
$hexString .= str_pad(bin2hex($pkey['ec']['y']), 64, '0', STR_PAD_LEFT);
|
||||
$response['vapid-public'] = Base64UrlSafe::encode(hex2bin($hexString));
|
||||
|
||||
// @see https://github.com/web-push-libs/web-push-php/blob/256a18b2a2411469c94943725fb6eccb9681bd75/src/VAPID.php
|
||||
$response['vapid-private'] = Base64UrlSafe::encode(hex2bin(str_pad(bin2hex($pkey['ec']['d']), 64, '0', STR_PAD_LEFT)));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string with 'aes-256-cbc' cipher method.
|
||||
*
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Friendica\Util;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* Proxy utilities class
|
||||
|
@ -173,12 +174,15 @@ class Proxy
|
|||
*/
|
||||
private static function parseQuery(string $url): array
|
||||
{
|
||||
$query = parse_url($url, PHP_URL_QUERY);
|
||||
$query = html_entity_decode($query);
|
||||
try {
|
||||
$uri = new Uri($url);
|
||||
|
||||
parse_str($query, $arr);
|
||||
parse_str($uri->getQuery(), $arr);
|
||||
|
||||
return $arr;
|
||||
return $arr;
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Friendica\Util;
|
|||
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Core\Logger;
|
||||
use ParagonIE\ConstantTime\Base64;
|
||||
|
||||
/**
|
||||
* This class handles string functions
|
||||
|
@ -245,16 +246,17 @@ class Strings
|
|||
* @param string $s URL to encode
|
||||
* @param boolean $strip_padding Optional. Default false
|
||||
* @return string Encoded URL
|
||||
* @see https://web.archive.org/web/20160506073138/http://salmon-protocol.googlecode.com:80/svn/trunk/draft-panzer-magicsig-01.html#params
|
||||
*/
|
||||
public static function base64UrlEncode(string $s, bool $strip_padding = false): string
|
||||
{
|
||||
$s = strtr(base64_encode($s), '+/', '-_');
|
||||
|
||||
if ($strip_padding) {
|
||||
$s = str_replace('=', '', $s);
|
||||
$s = Base64::encodeUnpadded($s);
|
||||
} else {
|
||||
$s = Base64::encode($s);
|
||||
}
|
||||
|
||||
return $s;
|
||||
return strtr($s, '+/', '-_');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,26 +265,11 @@ class Strings
|
|||
* @param string $s URL to decode
|
||||
* @return string Decoded URL
|
||||
* @throws \Exception
|
||||
* @see https://web.archive.org/web/20160506073138/http://salmon-protocol.googlecode.com:80/svn/trunk/draft-panzer-magicsig-01.html#params
|
||||
*/
|
||||
public static function base64UrlDecode(string $s): string
|
||||
{
|
||||
/*
|
||||
* // Placeholder for new rev of salmon which strips base64 padding.
|
||||
* // PHP base64_decode handles the un-padded input without requiring this step
|
||||
* // Uncomment if you find you need it.
|
||||
*
|
||||
* $l = strlen($s);
|
||||
* if (!strpos($s,'=')) {
|
||||
* $m = $l % 4;
|
||||
* if ($m == 2)
|
||||
* $s .= '==';
|
||||
* if ($m == 3)
|
||||
* $s .= '=';
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
return base64_decode(strtr($s, '-_', '+/'));
|
||||
return Base64::decode(strtr($s, '-_', '+/'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 = [
|
||||
|
@ -277,7 +275,7 @@ return [
|
|||
'/statuses/{id:\d+}/pin' => [Module\Api\Mastodon\Statuses\Pin::class, [ R::POST]],
|
||||
'/statuses/{id:\d+}/unpin' => [Module\Api\Mastodon\Statuses\Unpin::class, [ R::POST]],
|
||||
'/statuses/{id:\d+}/history' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented
|
||||
'/statuses/{id:\d+}/source' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented
|
||||
'/statuses/{id:\d+}/source' => [Module\Api\Mastodon\Statuses\Source::class, [R::GET ]],
|
||||
'/streaming/direct' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented
|
||||
'/streaming/hashtag' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented
|
||||
'/streaming/hashtag/local' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented
|
||||
|
@ -469,6 +467,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]],
|
||||
|
||||
|
@ -556,7 +562,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]],
|
||||
|
||||
|
|
1
tests/datasets/crypto/rsa/salmon-public-magic
Normal file
1
tests/datasets/crypto/rsa/salmon-public-magic
Normal file
|
@ -0,0 +1 @@
|
|||
RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB
|
4
tests/datasets/crypto/rsa/salmon-public-pem
Normal file
4
tests/datasets/crypto/rsa/salmon-public-pem
Normal file
|
@ -0,0 +1,4 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALb7KAWWy1L6lrPtHfAuYVUC4ywo48cm
|
||||
W9e0ZvP/RQ6gWFIoAUhQ3CQsxtuxTPs7nXcKYCQdLw7jykJ7efZCkbMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
105
tests/src/Protocol/SalmonTest.php
Normal file
105
tests/src/Protocol/SalmonTest.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?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\Test\src\Protocol;
|
||||
|
||||
use Friendica\Protocol\Salmon;
|
||||
|
||||
class SalmonTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function dataMagic(): array
|
||||
{
|
||||
return [
|
||||
'salmon' => [
|
||||
'magic' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/salmon-public-magic'),
|
||||
'pem' => file_get_contents(__DIR__ . '/../../datasets/crypto/rsa/salmon-public-pem'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataMagic
|
||||
*
|
||||
* @param $magic
|
||||
* @param $pem
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testSalmonKey($magic, $pem)
|
||||
{
|
||||
$this->assertEquals($magic, Salmon::salmonKey($pem));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataMagic
|
||||
*
|
||||
* @param $magic
|
||||
* @param $pem
|
||||
* @return void
|
||||
*/
|
||||
public function testMagicKeyToPem($magic, $pem)
|
||||
{
|
||||
$this->assertEquals($pem, Salmon::magicKeyToPem($magic));
|
||||
}
|
||||
|
||||
public function dataMagicFailure(): array
|
||||
{
|
||||
return [
|
||||
'empty string' => [
|
||||
'magic' => '',
|
||||
],
|
||||
'Missing algo' => [
|
||||
'magic' => 'tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB',
|
||||
],
|
||||
'Missing modulus' => [
|
||||
'magic' => 'RSA.AQAB',
|
||||
],
|
||||
'Missing exponent' => [
|
||||
'magic' => 'RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw',
|
||||
],
|
||||
'Missing key parts' => [
|
||||
'magic' => 'RSA.',
|
||||
],
|
||||
'Too many parts' => [
|
||||
'magic' => 'RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB.AQAB',
|
||||
],
|
||||
'Wrong encoding' => [
|
||||
'magic' => 'RSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8/9FDqBYUigBSFDcJCzG27FM+zuddwpgJB0vDuPKQnt59kKRsw.AQAB',
|
||||
],
|
||||
'Wrong algo' => [
|
||||
'magic' => 'ECDSA.tvsoBZbLUvqWs-0d8C5hVQLjLCjjxyZb17Rm8_9FDqBYUigBSFDcJCzG27FM-zuddwpgJB0vDuPKQnt59kKRsw.AQAB',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataMagicFailure
|
||||
*
|
||||
* @param $magic
|
||||
* @return void
|
||||
*/
|
||||
public function testMagicKeyToPemFailure($magic)
|
||||
{
|
||||
$this->expectException(\Throwable::class);
|
||||
|
||||
Salmon::magicKeyToPem($magic);
|
||||
}
|
||||
}
|
|
@ -65,7 +65,7 @@ class CryptoTest extends TestCase
|
|||
self::assertEquals(11111111, $test);
|
||||
}
|
||||
|
||||
public function dataRsa()
|
||||
public function dataRsa(): array
|
||||
{
|
||||
return [
|
||||
'diaspora' => [
|
||||
|
@ -92,34 +92,6 @@ class CryptoTest extends TestCase
|
|||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataPEM
|
||||
*/
|
||||
public function testPemToMe(string $key)
|
||||
{
|
||||
Crypto::pemToMe($key, $m, $e);
|
||||
|
||||
$expectedRSA = new RSA();
|
||||
$expectedRSA->loadKey([
|
||||
'e' => new BigInteger($e, 256),
|
||||
'n' => new BigInteger($m, 256)
|
||||
]);
|
||||
|
||||
self::assertEquals($expectedRSA->getPublicKey(), $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataPEM
|
||||
*/
|
||||
public function testMeToPem(string $key)
|
||||
{
|
||||
Crypto::pemToMe($key, $m, $e);
|
||||
|
||||
$checkKey = Crypto::meToPem($m, $e);
|
||||
|
||||
self::assertEquals($key, $checkKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
|
@ -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() {
|
||||
|
|
156
view/js/module/media/browser.js
Normal file
156
view/js/module/media/browser.js
Normal 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
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: 2022.12-dev\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-26 23:37+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:1007
|
||||
#: mod/photos.php:1108 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: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
|
||||
|
@ -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:1076
|
||||
#: 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 ""
|
||||
|
||||
|
@ -554,7 +527,17 @@ msgstr ""
|
|||
msgid "Delete Album"
|
||||
msgstr ""
|
||||
|
||||
#: mod/photos.php:1033
|
||||
#: 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 ""
|
||||
|
||||
|
@ -570,7 +553,7 @@ msgstr ""
|
|||
msgid "Show Oldest First"
|
||||
msgstr ""
|
||||
|
||||
#: mod/photos.php:1061 src/Module/Profile/Photos/Index.php:140
|
||||
#: mod/photos.php:1051 src/Module/Profile/Photos.php:140
|
||||
msgid "View Photo"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1690,6 +1673,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 ""
|
||||
|
@ -3184,7 +3172,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 ""
|
||||
|
||||
|
@ -5279,7 +5267,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 ""
|
||||
|
||||
|
@ -5908,10 +5896,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 ""
|
||||
|
||||
|
@ -7143,6 +7131,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 ""
|
||||
|
@ -8148,28 +8168,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 ""
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -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]);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
250
view/theme/frio/js/module/media/browser.js
Normal file
250
view/theme/frio/js/module/media/browser.js
Normal 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
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;}
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in a new issue