123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- <?php
- declare(strict_types = 1);
- // {{{ License
- // This file is part of GNU social - https://www.gnu.org/software/social
- //
- // GNU social 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.
- //
- // GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
- // }}}
- /**
- * Common utility functions
- *
- * @package GNUsocial
- * @category Util
- *
- * @author Hugo Sales <hugo@hsal.es>
- * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
- * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
- */
- namespace App\Util;
- use App\Core\I18n\I18n;
- use App\Core\Router\Router;
- use App\Core\Security;
- use App\Entity\Actor;
- use App\Entity\LocalUser;
- use App\Util\Exception\NoLoggedInUser;
- use Component\Language\Entity\Language;
- use Egulias\EmailValidator\EmailValidator;
- use Egulias\EmailValidator\Validation\RFCValidation as RFCEmailValidation;
- use Functional as F;
- use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\Routing\Exception\ResourceNotFoundException;
- use Symfony\Component\Yaml;
- abstract class Common
- {
- private static array $defaults;
- private static ?array $config = null;
- public static function setupConfig(ContainerBagInterface $config)
- {
- $components = $config->has('components') ? $config->get('components') : [];
- $plugins = $config->has('plugins') ? $config->get('plugins') : [];
- self::$config = array_merge_recursive($config->get('gnusocial'), ['components' => $components], ['plugins' => $plugins]);
- self::$defaults = $config->get('gnusocial_defaults');
- }
- private static ?Request $request = null;
- public static function setRequest(Request $req)
- {
- self::$request = $req;
- }
- /**
- * Don't use this
- */
- public static function getRequest(): ?Request
- {
- return self::$request;
- }
- public static function route()
- {
- return self::$request->attributes->get('_route');
- }
- public static function isRoute(string|array $routes)
- {
- return \in_array(self::route(), \is_array($routes) ? $routes : [$routes]);
- }
- /**
- * Access sysadmin's configuration preferences for GNU social
- * Returns value if exists, null if not set
- */
- public static function config(string $section, ?string $setting = null)
- {
- if (!\array_key_exists($section, self::$config)) {
- return null;
- } else {
- if ($setting !== null) {
- if (\array_key_exists($setting, self::$config[$section])) {
- return self::$config[$section][$setting];
- } else {
- return null;
- }
- } else {
- return self::$config[$section];
- }
- }
- }
- /**
- * Set sysadmin's configuration preferences for GNU social
- *
- * @param bool $transient keep this setting in memory only
- */
- public static function setConfig(string $section, string $setting, $value, bool $transient = false): void
- {
- self::$config[$section][$setting] = $value;
- if (!$transient) {
- $diff = self::arrayDiffRecursive(self::$config, self::$defaults);
- $yaml = (new Yaml\Dumper(indentation: 2))->dump(['parameters' => ['locals' => ['gnusocial' => $diff]]], Yaml\Yaml::DUMP_OBJECT_AS_MAP);
- rename(INSTALLDIR . '/social.local.yaml', INSTALLDIR . '/social.local.yaml.back');
- file_put_contents(INSTALLDIR . '/social.local.yaml', $yaml);
- }
- }
- public static function getConfigDefaults()
- {
- return self::$defaults;
- }
- public static function user(): ?LocalUser
- {
- // This returns the user stored in the session. We only use
- // LocalUser, but this is more generic and returns
- // UserInterface, so we need a type cast
- /** @var LocalUser */
- return Security::getUser();
- }
- public static function actor(): ?Actor
- {
- return self::user()?->getActor();
- }
- public static function userNickname(): ?string
- {
- return self::ensureLoggedIn()?->getNickname();
- }
- public static function userId(): ?int
- {
- return self::ensureLoggedIn()?->getId();
- }
- /**
- * @throws NoLoggedInUser
- */
- public static function ensureLoggedIn(): LocalUser
- {
- if (\is_null($user = self::user())) {
- throw new NoLoggedInUser();
- // TODO Maybe redirect to login page and back
- } else {
- return $user;
- }
- }
- /**
- * checks if user is logged in
- *
- * @return bool true if user is logged; false if it isn't
- */
- public static function isLoggedIn(): bool
- {
- return !\is_null(self::user());
- }
- /**
- * Is the given string identical to a system path or route?
- * This could probably be put in some other class, but at
- * at the moment, only Nickname requires this functionality.
- */
- public static function isSystemPath(string $str): bool
- {
- try {
- $route = Router::match('/' . $str);
- return $route['is_system_path'] ?? true;
- } catch (ResourceNotFoundException $e) {
- return false;
- }
- }
- /**
- * A recursive `array_diff`, while PHP itself doesn't provide one
- */
- public static function arrayDiffRecursive($array1, $array2): array
- {
- $diff = [];
- foreach ($array1 as $key => $value) {
- if (\array_key_exists($key, $array2)) {
- if (\is_array($value)) {
- $recursive_diff = static::arrayDiffRecursive($value, $array2[$key]);
- if (\count($recursive_diff)) {
- $diff[$key] = $recursive_diff;
- }
- } else {
- if ($value != $array2[$key]) {
- $diff[$key] = $value;
- }
- }
- } else {
- $diff[$key] = $value;
- }
- }
- return $diff;
- }
- /**
- * Remove keys from the _values_ of $keys from the array $from
- */
- public static function arrayRemoveKeys(array $from, array $keys, bool $strict = false)
- {
- return F\filter($from, fn ($_, $key) => !\in_array($key, $keys, $strict));
- }
- /**
- * An internal helper function that converts a $size from php.ini for
- * file size limit from the 'human-readable' shorthand into a int. If
- * $size is empty (the value is not set in php.ini), returns a default
- * value (3M)
- *
- * @return int the php.ini upload limit in machine-readable format
- */
- public static function sizeStrToInt(string $size): int
- {
- // `memory_limit` can be -1 and `post_max_size` can be 0
- // for unlimited. Consistency.
- if (empty($size) || $size === '-1' || $size === '0') {
- $size = '3M';
- }
- $suffix = mb_substr($size, -1);
- $size = (int) mb_substr($size, 0, -1);
- switch (mb_strtoupper($suffix)) {
- case 'P':
- $size *= 1024;
- // no break
- case 'T':
- $size *= 1024;
- // no break
- case 'G':
- $size *= 1024;
- // no break
- case 'M':
- $size *= 1024;
- // no break
- case 'K':
- $size *= 1024;
- break;
- default:
- if ($suffix >= '0' && $suffix <= '9') {
- $size = (int) "{$size}{$suffix}";
- }
- }
- return $size;
- }
- /**
- * Uses `size_str_to_int()` to find the smallest value for uploads in php.ini
- */
- public static function getPreferredPhpUploadLimit(): int
- {
- return min(
- self::sizeStrToInt(ini_get('post_max_size')),
- self::sizeStrToInt(ini_get('upload_max_filesize')),
- self::sizeStrToInt(ini_get('memory_limit')),
- );
- }
- /**
- * Uses common config 'attachments' 'file_quota' while respecting PreferredPhpUploadLimit
- */
- public static function getUploadLimit(): int
- {
- return min(
- self::getPreferredPhpUploadLimit(),
- self::config('attachments', 'file_quota'),
- );
- }
- /**
- * Clamps a value between 2 numbers
- *
- * @return float|int clamped value
- */
- public static function clamp(int|float $value, int|float $min, int|float $max): int|float
- {
- return min(max($value, $min), $max);
- }
- /**
- * If $ensure_secure is true, only allow https URLs to pass
- */
- public static function isValidHttpUrl(string $url, bool $ensure_secure = false)
- {
- if (empty($url)) {
- return false;
- }
- // (if false, we use '?' in 'https?' to say the 's' is optional)
- $regex = $ensure_secure ? '/^https$/' : '/^https?$/';
- return filter_var($url, \FILTER_VALIDATE_URL) !== false
- && preg_match($regex, parse_url($url, \PHP_URL_SCHEME));
- }
- public static function isValidEmail(string $email): bool
- {
- return (new EmailValidator())->isValid($email, new RFCEmailValidation());
- }
- /**
- * Flatten an array of ['note' => note, 'replies' => [notes]] to an array of notes
- */
- public static function flattenNoteArray(array $a): array
- {
- $notes = [];
- foreach ($a as $n) {
- $notes[] = $n['note'];
- if (isset($n['replies'])) {
- $notes = array_merge($notes, static::flattenNoteArray($n['replies']));
- }
- }
- return $notes;
- }
- public static function currentLanguage(): Language
- {
- return self::actor()?->getTopLanguage() ?? Language::getByLocale(self::$request->headers->has('accept-language') ? I18n::clientPreferredLanguage(self::$request->headers->get('accept-language')) : self::config('site', 'language'));
- }
- // Convert the ArrayBuffer to string using Uint8 array.
- // btoa takes chars from 0-255 and base64 encodes.
- // Then convert the base64 encoded to base64url encoded.
- // (replace + with -, replace / with _, trim trailing =)
- public static function base64url_encode(string $data): string
- {
- return rtrim(strtr(strtr(base64_encode($data), '+', '-'), '/', '_'), '=');
- }
- public static function base64url_decode(string $data): string
- {
- return base64_decode(str_pad(strtr(strtr($data, '_', '/'), '-', '+'), mb_strlen($data) % 4, '=', \STR_PAD_RIGHT));
- }
- }
|