code:
<?php

// {{{ 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/>.
// }}}

namespace App\Entity;

use App\Core\Entity;
use DateTimeInterface;

/**
 * Entity for user's url shortener preferences
 *
 * @category  DB
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class UserUrlShortenerPrefs extends Entity
{
    // {{{ Autocode

    private int $user_id;
    private ?string $url_shortening_service;
    private int $max_url_length;
    private int $max_notice_length;
    private DateTimeInterface $created;
    private DateTimeInterface $modified;

    public function setUserId(int $user_id): self
    {
        $this->user_id = $user_id;
        return $this;
    }

    public function getUserId(): int
    {
        return $this->user_id;
    }

    public function setUrlShorteningService(?string $url_shortening_service): self
    {
        $this->url_shortening_service = $url_shortening_service;
        return $this;
    }

    public function getUrlShorteningService(): ?string
    {
        return $this->url_shortening_service;
    }

    public function setMaxUrlLength(int $max_url_length): self
    {
        $this->max_url_length = $max_url_length;
        return $this;
    }

    public function getMaxUrlLength(): int
    {
        return $this->max_url_length;
    }

    public function setMaxNoticeLength(int $max_notice_length): self
    {
        $this->max_notice_length = $max_notice_length;
        return $this;
    }

    public function getMaxNoticeLength(): int
    {
        return $this->max_notice_length;
    }

    public function setCreated(DateTimeInterface $created): self
    {
        $this->created = $created;
        return $this;
    }

    public function getCreated(): DateTimeInterface
    {
        return $this->created;
    }

    public function setModified(DateTimeInterface $modified): self
    {
        $this->modified = $modified;
        return $this;
    }

    public function getModified(): DateTimeInterface
    {
        return $this->modified;
    }

    // }}} Autocode

    public static function schemaDef(): array
    {
        return [
            'name'   => 'user_url_shortener_prefs',
            'fields' => [
                'user_id'                => ['type' => 'int', 'not null' => true, 'description' => 'user'],
                'url_shortening_service' => ['type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'],
                'max_url_length'         => ['type' => 'int', 'not null' => true, 'description' => 'urls greater than this length will be shortened, 0 = always, -1 = never'],
                'max_notice_length'      => ['type' => 'int', 'not null' => true, 'description' => 'notices with content greater than this value will have all urls shortened, 0 = always, -1 = only if notice text is longer than max allowed'],
                'created'                => ['type' => 'datetime',  'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
                'modified'               => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
            ],
            'primary key'  => ['user_id'],
            'foreign keys' => [
                'user_urlshortener_prefs_user_id_fkey' => ['user', ['user_id' => 'id']],
            ],
        ];
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Symfony Kernel, which is responsible for configuring the whole application
 *
 * @package GNUsocial
 * @category Kernel
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App;

use App\DependencyInjection\Compiler\ModuleManagerPass;
use App\DependencyInjection\Compiler\SchemaDefPass;
use const PHP_VERSION_ID;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    private const CONFIG_EXTS = '.{php,xml,yaml,yml}';

    public function __construct(string $environment, bool $debug)
    {
        parent::__construct($environment, $debug);

        if (!defined('INSTALLDIR')) {
            define('INSTALLDIR', dirname(__DIR__));
            define('SRCDIR', INSTALLDIR . '/src');
            define('PUBLICDIR', INSTALLDIR . '/public');
            define('GNUSOCIAL_ENGINE_NAME', 'GNU social');
            // MERGE Change to https://gnu.io/social/
            define('GNUSOCIAL_PROJECT_URL', 'https://gnusocial.network/');
            // MERGE Change to https://git.gnu.io/gnu/gnu-social
            define('GNUSOCIAL_REPOSITORY_URL', 'https://notabug.org/diogo/gnu-social/');
            // Current base version, major.minor.patch
            define('GNUSOCIAL_BASE_VERSION', '3.0.0');
            // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
            define('GNUSOCIAL_LIFECYCLE', 'dev');
            define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
            define('GNUSOCIAL_CODENAME', 'Big bang');
            define('URL_REGEX_DOMAIN_NAME', '(?:(?!-)[A-Za-z0-9\-]{1,63}(?<!-)\.)+[A-Za-z]{2,10}');

            // Work internally in UTC
            date_default_timezone_set('UTC');

            // Work internally with UTF-8
            mb_internal_encoding('UTF-8');
        }
    }

    /**
     * Symfony framework function override responsible for registering
     * bundles (similar to our modules)
     */
    public function registerBundles(): iterable
    {
        $contents = require $this->getProjectDir() . '/config/bundles.php';
        foreach ($contents as $class => $envs) {
            if ($envs[$this->environment] ?? $envs['all'] ?? false) {
                yield new $class();
            }
        }
    }

    public function getProjectDir(): string
    {
        return dirname(__DIR__);
    }

    /**
     * Configure the container. A 'compile-time' step in the Symfony
     * framework that allows caching of the initialization of all
     * services and modules
     */
    protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
    {
        $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php'));
        $container->setParameter('container.dumper.inline_class_loader', PHP_VERSION_ID < 70400 || $this->debug);
        $container->setParameter('container.dumper.inline_factories', true);

        $confDir = $this->getProjectDir() . '/config';

        $loader->load($confDir . '/{packages}/*' . self::CONFIG_EXTS, 'glob');
        $loader->load($confDir . '/{packages}/' . $this->environment . '/*' . self::CONFIG_EXTS, 'glob');
        $loader->load($confDir . '/{services}' . self::CONFIG_EXTS, 'glob');
        $loader->load($confDir . '/{services}_' . $this->environment . self::CONFIG_EXTS, 'glob');

        // Overriding doesn't work as we want, overrides the top-most key, do it manually
        $local_file = INSTALLDIR . '/social.local.yaml';
        if (!file_exists($local_file)) {
            file_put_contents($local_file, "parameters:\n  gnusocial:\n");
        }
        $loader->load($local_file);
        $locals = $container->getParameter('gnusocial');
        $loader->load(INSTALLDIR . '/social' . self::CONFIG_EXTS, 'glob');
        $container->setParameter('gnusocial_defaults', $defaults = $container->getParameter('gnusocial'));
        if (is_array($locals)) {
            $configs = array_replace_recursive($defaults, $locals);
            $container->setParameter('gnusocial', $configs);
        }
    }

    /**
     * Configure HTTP(S) route to controller mapping
     */
    protected function configureRoutes(RoutingConfigurator $routes): void
    {
        $config = \dirname(__DIR__) . '/config';
        $routes->import($config . '/{routes}/' . $this->environment . '/*.yaml');
        $routes->import($config . '/{routes}/*.yaml');

        if (is_file($config . '/routes.yaml')) {
            $routes->import($config . '/{routes}.yaml');
        } elseif (is_file($path = $config . '/routes.php')) {
            (require $path)($routes->withPath($path), $this);
        }
    }

    /**
     * 'Compile-time' step that builds the container, allowing us to
     * define compiler passes
     */
    protected function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new ModuleManagerPass());
        $container->addCompilerPass(new SchemaDefPass());
    }
}

repo_name: ksuudilla/gnu

license: mit


code:
<?php

// {{{ 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/>.

// }}}

/**
 * Define social's main routes
 *
 * @package  GNUsocial
 * @category Router
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @author    Eliseu Amaro <eliseu@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Routes;

use App\Controller as C;
use App\Core\Router\RouteLoader;
use App\Util\Nickname;
use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;

abstract class Main
{
    public static function load(RouteLoader $r): void
    {
        $r->connect('login', '/login', [C\Security::class, 'login']);
        $r->connect('logout', '/logout', [C\Security::class, 'logout']);
        $r->connect('register', '/register', [C\Security::class, 'register']);

        $r->connect('root', '/', RedirectController::class, ['defaults' => ['route' => 'main_all']]);
        $r->connect('main_public', '/main/public', [C\Network::class, 'public']);
        $r->connect('main_all', '/main/all', [C\Network::class, 'network']);
        $r->connect('home_all', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/all', [C\Network::class, 'home']);
        $r->connect('replies', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/replies', [C\Network::class, 'replies']);
        $r->connect('favourites', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/favourites', [C\Network::class, 'favourites']);

        $r->connect('panel', '/panel', [C\AdminPanel::class, 'site']);
        $r->connect('panel_site', '/panel/site', [C\AdminPanel::class, 'site']);

        // FAQ static pages
        foreach (['faq', 'contact', 'tags', 'groups', 'openid'] as $s) {
            $r->connect('doc_' . $s, '/doc/' . $s, C\TemplateController::class, ['template' => 'doc/faq/' . $s . '.html.twig']);
        }

        foreach (['privacy', 'tos', 'version', 'source'] as $s) {
            $r->connect('doc_' . $s, '/doc/' . $s, C\TemplateController::class, ['template' => 'doc/' . $s . '.html.twig']);
        }

        // Settings pages
        $r->connect('settings', '/settings', RedirectController::class, ['defaults' => ['route' => 'settings_personal_info']]);
        foreach (['personal_info', 'avatar', 'notifications', 'account'] as $s) {
            $r->connect('settings_' . $s, '/settings/' . $s, [C\UserPanel::class, $s]);
        }
    }
}


code:
<?php

// {{{ 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/>.
// }}}

namespace App\Security;

use App\Core\DB\DB;
use function App\Core\I18n\_m;
use App\Entity\User;
use App\Util\Nickname;
use Exception;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

/**
 * User authenticator
 *
 * @category  Authentication
 * @package   GNUsocial
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class Authenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    public const LOGIN_ROUTE = 'login';

    private $urlGenerator;
    private $csrfTokenManager;

    public function __construct(UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager)
    {
        $this->urlGenerator     = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
    }

    public function supports(Request $request)
    {
        return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'nickname'   => $request->request->get('nickname'),
            'password'   => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['nickname']
        );

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $nick = Nickname::normalize($credentials['nickname']);
        $user = null;
        try {
            $user = DB::findOneBy('local_user', ['or' => ['nickname' => $nick, 'outgoing_email' => $nick]]);
        } catch (Exception $e) {
            throw new CustomUserMessageAuthenticationException(
                _m('\'{nickname}\' doesn\'t match any registered nickname or email.', ['{nickname}' => $credentials['nickname']]));
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        if (!$user->checkPassword($credentials['password'])) {
            throw new CustomUserMessageAuthenticationException(_m('Invalid login credentials.'));
        } else {
            return true;
        }
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        return new RedirectResponse($this->urlGenerator->generate('main_all'));
    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate(self::LOGIN_ROUTE);
    }
}


code:
<?php

namespace App\Security;

use App\Core\DB\DB;
use App\Core\Mailer;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;

class EmailVerifier
{
    private $verify_email_helper;

    public function __construct(VerifyEmailHelperInterface $helper)
    {
        $this->verifyEmailHelper = $helper;
    }

    public function sendEmailConfirmation(string $verify_email_route_name, UserInterface $user, TemplatedEmail $email): void
    {
        $signatureComponents = $this->verify_email_helper->generateSignature(
            $verify_email_route_name,
            $user->getId(),
            $user->getOutgoingEmail()
        );

        $context              = $email->getContext();
        $context['signedUrl'] = $signatureComponents->getSignedUrl();
        $context['expiresAt'] = $signatureComponents->getExpiresAt();

        $email->context($context);

        Mailer::send($email);
    }

    /**
     * @throws VerifyEmailExceptionInterface
     */
    public function handleEmailConfirmation(Request $request, UserInterface $user): void
    {
        $this->verify_email_helper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getOutgoingEmail());

        $user->setIsEmailVerified(true);

        DB::persist($user);
        DB::flush();
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * GNU social Twig extensions
 *
 * @package   GNUsocial
 * @category  Twig
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;

class Extension extends AbstractExtension
{
    public function getFilters()
    {
        return [
            // new TwigFilter('foo', [GSRuntime::class, 'foo']),
        ];
    }

    public function getFunctions()
    {
        return [
            /** Twig function to output the 'active' class if the current route matches the given route */
            new TwigFunction('active', [Runtime::class, 'isCurrentRouteActive']),
            new TwigFunction('is_route', [Runtime::class, 'isCurrentRoute']),
            new TwigFunction('get_note_actions', [Runtime::class, 'getNoteActions']),
            new TwigFunction('config', [Runtime::class, 'getConfig']),
        ];
    }
}


code:
<?php

// {{{ 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@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Twig;

use App\Core\Event;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Formatting;
use Functional as F;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Extension\RuntimeExtensionInterface;

class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface
{
    private Request $request;
    public function __constructor(Request $req)
    {
        $this->request = $req;
    }

    public function isCurrentRouteActive(string ...$routes): string
    {
        return $this->isCurrentRoute('active', ...$routes);
    }

    public function isCurrentRoute(string $class, string ...$routes): string
    {
        $current_route = $this->request->attributes->get('_route');
        return F\some($routes, F\partial_left([Formatting::class, 'startsWith'], $current_route)) ? $class : '';
    }

    public function getNoteActions(Note $note)
    {
        $actions = [];
        Event::handle('AddNoteActions', [$this->request, $note, &$actions]);
        return $actions;
    }

    public function getConfig(...$args)
    {
        return Common::config(...$args);
    }

    // ----------------------------------------------------------

    // Request is not a service, can't find a better way to get it
    public function onKernelRequest(RequestEvent $event)
    {
        $this->request = $event->getRequest();
    }

    public static function getSubscribedEvents()
    {
        return [KernelEvents::REQUEST => 'onKernelRequest'];
    }
}


code:
<?php

// {{{ 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/>.
// }}}

namespace App\Util;

use App\Util\Exception\ServerException;

abstract class Bitmap
{
    /**
     * Convert an to or from an integer and an array of constants for
     * each bit. If $instance, return an object with the corresponding
     * properties set
     */
    private static function _do(int $r, bool $instance)
    {
        $init  = $r;
        $class = get_called_class();
        if ($instance) {
            $obj = new $class;
        } else {
            $vals = [];
        }

        $consts = (new \ReflectionClass($class))->getConstants();
        unset($consts['PREFIX']);

        foreach ($consts as $c => $v) {
            $b = ($r & $v) !== 0;
            if ($instance) {
                $c         = strtolower($c);
                $obj->{$c} = $b;
            }
            if ($b) {
                $r -= $v;
                if (!$instance) {
                    $vals[] = $class::PREFIX . $c;
                }
            }
        }

        if ($r != 0) {
            Log::error('Bitmap to array conversion failed');
            throw new ServerException("Bug in bitmap conversion for class {$class} from value {$init}");
        }

        if ($instance) {
            return $obj;
        } else {
            return $vals;
        }
    }

    public static function create(int $r): self
    {
        return self::_do($r, true);
    }

    public static function toArray(int $r): array
    {
        return self::_do($r, false);
    }
}


code:
<?php

// {{{ 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@fc.up.pt>
 * @copyright 2020 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\Router\Router;
use App\Core\Security;
use App\Entity\GSActor;
use App\Entity\LocalUser;
use App\Util\Exception\NoLoggedInUser;
use Exception;
use Functional as F;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
use Symfony\Component\Yaml;

abstract class Common
{
    private static array $defaults;
    private static ?array $config = null;
    public static function setupConfig(ContainerBagInterface $config)
    {
        self::$config   = $config->get('gnusocial');
        self::$defaults = $config->get('gnusocial_defaults');
    }

    /**
     * Access sysadmin's configuration preferences for GNU social
     */
    public static function config(string $section, string $setting)
    {
        return self::$config[$section][$setting];
    }

    /**
     * Set sysadmin's configuration preferences for GNU social
     *
     * @param mixed $value
     */
    public static function setConfig(string $section, string $setting, $value): void
    {
        self::$config[$section][$setting] = $value;
        $diff                             = self::array_diff_recursive(self::$config, self::$defaults);
        $yaml                             = (new Yaml\Dumper(2))->dump(['parameters' => ['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
    {
        return Security::getUser();
    }

    public static function actor(): ?GSActor
    {
        return self::user()->getActor();
    }

    public static function userNickname(): ?string
    {
        if (($user = self::user()) == null) {
            throw new NoLoggedInUser();
        } else {
            return $user->getNickname();
        }
    }

    public static function ensureLoggedIn(): LocalUser
    {
        if (($user = self::user()) == null) {
            throw new NoLoggedInUser();
        // TODO Maybe redirect to login page and back
        } else {
            return $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 {
            Router::match('/' . $str);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * A recursive `array_diff`, while PHP itself doesn't provide one
     */
    public function array_diff_recursive(array $array1, array $array2)
    {
        $difference = [];
        foreach ($array1 as $key => $value) {
            if (is_array($value)) {
                if (!isset($array2[$key]) || !is_array($array2[$key])) {
                    $difference[$key] = $value;
                } else {
                    $new_diff = self::array_diff_recursive($value, $array2[$key]);
                    if (!empty($new_diff)) {
                        $difference[$key] = $new_diff;
                    }
                }
            } elseif ((!isset($array2[$key]) || $array2[$key] != $value) && !($array2[$key] === null && $value === null)) {
                $difference[$key] = $value;
            }
        }
        return $difference ?? false;
    }

    /**
     * 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, function ($_, $key) use ($keys, $strict) { return !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 = substr($size, -1);
        $size   = (int) substr($size, 0, -1);
        switch (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;
        }
        return $size;
    }

    /**
     * Uses `size_str_to_int()` to find the smallest value for uploads in php.ini
     *
     * @return int
     */
    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'))
        );
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Client exception. Indicates a client request contains some sort of
 * error. HTTP code 400
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet Inc.
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @author    Alexei Sorokin <sor.alexei@meowr.ru>
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;
use Exception;

class ClientException extends Exception
{
    public function __construct(string $message = '', int $code = 500, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
    public function __toString()
    {
        return __CLASS__ . " [{$this->code}]: " . _m($this->message) . "\n";
    }
}


code:
<?php

// {{{ 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/>.

// }}}

namespace App\Util\Exception;

class InvalidFormException extends ClientException
{
    public function __construct()
    {
        parent::__construct(_m('Invalid form submitted'));
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Nickname empty exception
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;

class NicknameEmptyException extends NicknameInvalidException
{
    protected function defaultMessage(): string
    {
        // TRANS: Validation error in form for registration, profile and group settings, etc.
        return _m('Nickname cannot be empty.');
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Nickname empty exception
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

class NicknameException extends ClientException
{
    public function __construct(string $msg = null, int $code = 400)
    {
        if ($msg === null) {
            $msg = $this->defaultMessage();
        }
        parent::__construct($msg, $code);
    }

    protected function defaultMessage(): string
    {
        return '';
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Nickname invalid exception
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;

class NicknameInvalidException extends NicknameException
{
    protected function defaultMessage(): string
    {
        // TRANS: Validation error in form for registration, profile and group settings, etc.
        return _m('Nickname must have only lowercase letters and numbers and no spaces.');
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Nickname reserved exception
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;

class NicknameReservedException extends NicknameException
{
    protected function defaultMessage(): string
    {
        // TRANS: Validation error in form for registration, profile and group settings, etc.
        return _m('Nickname is reserved.');
    }
}


code:
<?php

// {{{ 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/>.

// }}}

/**
 * Nickname empty exception
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;

class NicknameTakenException extends NicknameException
{
    public ?Profile $profile = null;    // the Profile which occupies the nickname

    public function __construct(Profile $profile, string $msg = null, int $code = 400)
    {
        $this->profile = $profile;
        parent::__construct($msg, $code);
    }

    protected function defaultMessage(): string
    {
        // TRANS: Validation error in form for registration, profile and group settings, etc.
        return _m('Nickname is already in use on this server.');
    }
}


code:
<?php

// {{{ 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/>.

// }}}

/**
 * Nickname too long exception
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;
use App\Util\Nickname;

class NicknameTooLongException extends NicknameInvalidException
{
    protected function defaultMessage(): string
    {
        // TRANS: Validation error in form for registration, profile and group settings, etc.
        return _m('Nickname cannot be more than # character long.', ['count' => Nickname::MAX_LEN]);
    }
}


code:
<?php

// {{{ 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/>.
// }}}

namespace App\Util\Exception;

/**
 * No user logged in
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class NoLoggedInUser extends ClientException
{
}


code:
<?php

// {{{ 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/>.
// }}}

namespace App\Util\Exception;

use function App\Core\I18n\_m;

class NoSuchFileException extends ClientException
{
    public function __construct()
    {
        parent::__construct(_m('No such file found'));
    }
}


code:
<?php

// {{{ 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/>.

// }}}

namespace App\Util\Exception;

class NoSuchNoteException extends ClientException
{
    public function __construct()
    {
        parent::__construct(_m('No such note'));
    }
}


code:
<?php

// {{{ 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/>.

// }}}

namespace App\Util\Exception;

class NotFoundException extends ServerException
{
    public function __construct(string $m)
    {
        parent::__construct($m);
    }
}


code:
<?php

// {{{ 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/>.
// }}}

namespace App\Util\Exception;

use App\Core\Router\Router;
use Exception;
use Symfony\Component\HttpFoundation\RedirectResponse;

class RedirectException extends Exception
{
    public ?RedirectResponse $redirect_response = null;

    public function __construct(string $url_id = '', array $args = [], $message = '', $code = 302, Exception $previous_exception = null)
    {
        if (!empty($url_id)) {
            $this->redirect_response = new RedirectResponse(Router::url($url_id, $args));
        }
        parent::__construct($message, $code, $previous_exception);
    }
}


code:
<?php

// {{{ 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/>.

// }}}

/**
 * Class for server exceptions
 *
 * Subclass of PHP Exception for server errors.
 * The user typically can't fix these.
 *
 * @category  Exception
 * @package   GNUsocial
 *
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2008-2010 StatusNet, Inc.
 * @author    Alexei Sorokin <sor.alexei@meowr.ru>
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Exception;

use function App\Core\I18n\_m;
use Exception;

class ServerException extends Exception
{
    public function __construct(string $message = '', int $code = 500, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }

    public function __toString()
    {
        return __CLASS__ . " [{$this->code}]: " . _m($this->message) . "\n";
    }
}


code:
<?php

// {{{ 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/>.

// }}}

/**
 * Transform between string and list of typed profiles
 *
 * @package  GNUsocial
 * @category Form
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Form;

class ActorArrayTransformer extends ArrayTransformer
{
    /**
     * @param array $a
     *
     * @return string
     */
    public function transform($a)
    {
        // TODO convert each to string
        $s = parent::transform($a);
    }

    /**
     * @param string $s
     *
     * @return array
     */
    public function reverseTransform($s)
    {
        $a = parent::reverseTransform($s);
        // TODO convert each to profile
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Handle network public feed
 *
 * @package  GNUsocial
 * @category Form
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Form;

use App\Util\Formatting;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class ArrayTransformer implements DataTransformerInterface
{
    // Can't use type annotations, to conform to interface

    /**
     * @param array $a
     *
     * @return string
     */
    public function transform($a)
    {
        if (!is_array($a)) {
            throw new TransformationFailedException();
        }
        return Formatting::toString($a, Formatting::SPLIT_BY_SPACE);
    }

    /**
     * @param string $s
     *
     * @return array
     */
    public function reverseTransform($s)
    {
        if (empty($s)) {
            return [];
        }

        $arr;
        if (is_string($s) && Formatting::toArray($s, $arr, Formatting::SPLIT_BY_BOTH)) {
            return $arr;
        } else {
            throw new TransformationFailedException();
        }
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * String formatting utilities
 *
 * @package   GNUsocial
 * @category  Util
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 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 const DIRECTORY_SEPARATOR;
use Functional as F;
use InvalidArgumentException;
use Symfony\Component\Config\Definition\Exception\Exception;

abstract class Formatting
{
    /**
     * Normalize path by converting \ to /
     *
     * @param string $path
     *
     * @return string
     */
    public static function normalizePath(string $path): string
    {
        if (DIRECTORY_SEPARATOR !== '/') {
            $path = strtr($path, DIRECTORY_SEPARATOR, '/');
        }
        return $path;
    }

    /**
     * Get plugin name from it's path, or null if not a plugin
     *
     * @param string $path
     *
     * @return null|string
     */
    public static function pluginFromPath(string $path): ?string
    {
        $plug = strpos($path, '/plugins/');
        if ($plug === false) {
            return null;
        }
        $cut  = $plug + strlen('/plugins/');
        $cut2 = strpos($path, '/', $cut);
        if ($cut2) {
            $final = substr($path, $cut, $cut2 - $cut);
        } else {
            // We might be running directly from the plugins dir?
            // If so, there's no place to store locale info.
            throw new Exception('The GNU social install dir seems to contain a piece named plugin');
        }
        return $final;
    }

    /**
     * Check whether $haystack starts with $needle
     *
     * @param array|string $haystack if array, check that all strings start with $needle
     * @param string       $needle
     *
     * @return bool
     */
    public static function startsWith($haystack, string $needle): bool
    {
        if (is_string($haystack)) {
            $length = strlen($needle);
            return substr($haystack, 0, $length) === $needle;
        }
        return F\every($haystack,
            function ($haystack) use ($needle) {
                return self::startsWith($haystack, $needle);
            });
    }

    /**
     * Check whether $haystack ends with $needle
     *
     * @param array|string $haystack if array, check that all strings end with $needle
     * @param string       $needle
     *
     * @return bool
     */
    public static function endsWith($haystack, string $needle)
    {
        if (is_string($haystack)) {
            $length = strlen($needle);
            if ($length == 0) {
                return true;
            }
            return substr($haystack, -$length) === $needle;
        }
        return F\every($haystack,
            function ($haystack) use ($needle) {
                return self::endsWith($haystack, $needle);
            });
    }

    public static function camelCaseToSnakeCase(string $str): string
    {
        return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $str));
    }

    public static function snakeCaseToCamelCase(string $str): string
    {
        return implode('', F\map(preg_split('/[\b_]/', $str), F\ary('ucfirst', 1)));
    }

    /**
     * Indent $in, a string or array, $level levels
     *
     * @param array|string $in
     * @param int          $level How many levels of indentation
     * @param int          $count How many spaces per indentation
     *
     * @return string
     */
    public static function indent($in, int $level = 1, int $count = 2): string
    {
        if (is_string($in)) {
            return self::indent(explode("\n", $in), $level, $count);
        } elseif (is_array($in)) {
            $indent = str_repeat(' ', $count * $level);
            return implode("\n", F\map(F\select($in,
                F\ary(function ($s) {
                    return $s != '';
                }, 1)),
                function ($val) use ($indent) {
                    return F\concat($indent . $val);
                }));
        }
        throw new InvalidArgumentException('Formatting::indent\'s first parameter must be either an array or a string. Input was: ' . $in);
    }

    const SPLIT_BY_SPACE = ' ';
    const SPLIT_BY_COMMA = ', ';
    const SPLIT_BY_BOTH  = '/[, ]/';

    /**
     * Convert scalars, objects implementing __toString or arrays to strings
     *
     * @param mixed $value
     */
    public static function toString($value, string $split_type = self::SPLIT_BY_COMMA): string
    {
        if (!is_array($value)) {
            return (string) $value;
        } else {
            return implode($split_type, $value);
        }
    }

    /**
     * Convert a user supplied string to array and return whether the conversion was successfull
     *
     * @param mixed $output
     */
    public static function toArray(string $input, &$output, string $split_type = self::SPLIT_BY_COMMA): bool
    {
        $matches = [];
        if (preg_match('/^ *\[?([^,]+(, ?[^,]+)*)\]? *$/', $input, $matches)) {
            switch ($split_type) {
            case self::SPLIT_BY_BOTH:
                $arr = preg_split($split_type, $matches[1], 0, PREG_SPLIT_NO_EMPTY);
                break;
            case self::SPLIT_BY_COMMA:
                $arr = preg_split('/, ?/', $matches[1]);
                break;
            default:
                $arr = explode($split_type[0], $matches[1]);
            }
            $output = str_replace([' \'', '\'', ' "', '"'], '', $arr);
            $output = F\map($output, F\ary('trim', 1));
            return true;
        }
        return false;
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Functional utilities
 *
 * @package   GNUsocial
 * @category  Util
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util;

abstract class Functional
{
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * HTML Abstraction
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 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 Functional as F;

abstract class HTML
{
    /**
     * Creates an HTML tag without attributes
     *
     * @param string       $tag
     * @param array|string $content
     * @param bool         $empty_tag
     *
     * @return array
     */
    private static function tag(string $tag, $content = '', bool $empty_tag = false): array
    {
        return self::attr_tag($tag, '', $content);
    }

    /**
     * Create tag, possibly with attributes and indentation
     *
     * @param string       $tag
     * @param array|string $attrs     - element attributes
     * @param array|string $content   - what goes inside the tag
     * @param bool         $empty_tag
     *
     * @return array
     */
    private static function attr_tag(string $tag, $attrs, $content = '', bool $empty_tag = false): array
    {
        $html = '<' . $tag . (is_string($attrs) ? ($attrs ? ' ' : '') . $attrs : self::attr($attrs));
        if (empty($content) || $empty_tag) {
            $html .= '>';
        } else {
            $html .= ">\n" . Common::indent($content) . "\n" . Common::indent("</{$tag}>");
        }
        return explode("\n", $html);
    }

    /**
     * Attribute with given optional value
     *
     * @param array $attrs
     *
     * @return string
     */
    private static function attr(array $attrs): string
    {
        return ' ' . implode(' ', F\map($attrs, function ($val, $key, $_) {
            return "{$key} = '{$val}'";
        }));
    }

    /**
     * @param $html
     *
     * @return string
     */
    public static function html($html): string
    {
        if (is_string($html)) {
            return $html;
        } elseif (is_array($html)) {
            $out = '';
            foreach ($html as $tag => $contents) {
                if (isset($contents['empty'])) {
                    $out .= "<{$tag}>";
                } else {
                    $attrs  = isset($contents['attrs']) ? self::attr(array_shift($contents)) : '';
                    $is_tag = preg_match('/[A-Za-z][A-Za-z0-9]*/', $tag);
                    $inner  = self::html($contents);
                    $inner  = $is_tag ? Common::indent($inner) : $inner;
                    $out .= $is_tag ? "<{$tag}{$attrs}>\n{$inner}\n</{$tag}>\n" : $inner;
                }
            }
            return $out;
        } else {
            return '';
        }
    }
}


code:
<?php

// {{{ 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/>.

// }}}

namespace App\Util;

use App\Core\DB\DB;
use App\Entity\GSActor;
use App\Entity\LocalGroup;
use App\Entity\LocalUser;
use Normalizer;

/**
 * Nickname validation
 *
 * @category  Validation
 * @package   GNUsocial
 *
 * @author    Zach Copley <zach@status.net>
 * @copyright 2010 StatusNet Inc.
 * @author    Brion Vibber <brion@pobox.com>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @author    Nym Coy <nymcoy@gmail.com>
 * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
 * @auuthor   Daniel Supernault <danielsupernault@gmail.com>
 * @auuthor   Diogo Cordeiro <diogo@fc.up.pt>
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2018-2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class Nickname
{
    /**
     * Regex fragment for pulling a formated nickname *OR* ID number.
     * Suitable for router def of 'id' parameters on API actions.
     *
     * Not guaranteed to be valid after normalization; run the string through
     * Nickname::normalize() to get the canonical form, or Nickname::isValid()
     * if you just need to check if it's properly formatted.
     *
     * This, DISPLAY_FMT, and CANONICAL_FMT should not be enclosed in []s.
     *
     * @fixme would prefer to define in reference to the other constants
     */
    const INPUT_FMT = '(?:[0-9]+|[0-9a-zA-Z_]{1,64})';

    /**
     * Regex fragment for acceptable user-formatted variant of a nickname.
     *
     * This includes some chars such as underscore which will be removed
     * from the normalized canonical form, but still must fit within
     * field length limits.
     *
     * Not guaranteed to be valid after normalization; run the string through
     * Nickname::normalize() to get the canonical form, or Nickname::isValid()
     * if you just need to check if it's properly formatted.
     *
     * This, INPUT_FMT and CANONICAL_FMT should not be enclosed in []s.
     */
    const DISPLAY_FMT = '[0-9a-zA-Z_]{1,64}';

    /**
     * Simplified regex fragment for acceptable full WebFinger ID of a user
     *
     * We could probably use an email regex here, but mainly we are interested
     * in matching it in our URLs, like https://social.example/user@example.com
     */
    const WEBFINGER_FMT = '(?:\w+[\w\-\_\.]*)?\w+\@' . URL_REGEX_DOMAIN_NAME;

    /**
     * Regex fragment for checking a canonical nickname.
     *
     * Any non-matching string is not a valid canonical/normalized nickname.
     * Matching strings are valid and canonical form, but may still be
     * unavailable for registration due to blacklisting et.
     *
     * Only the canonical forms should be stored as keys in the database;
     * there are multiple possible denormalized forms for each valid
     * canonical-form name.
     *
     * This, INPUT_FMT and DISPLAY_FMT should not be enclosed in []s.
     */
    const CANONICAL_FMT = '[0-9a-z]{1,64}';

    /**
     * Maximum number of characters in a canonical-form nickname.
     */
    const MAX_LEN = 64;

    /**
     * Regex with non-capturing group that matches whitespace and some
     * characters which are allowed right before an @ or ! when mentioning
     * other users. Like: 'This goes out to:@mmn (@chimo too) (!awwyiss).'
     *
     * FIXME: Make this so you can have multiple whitespace but not multiple
     * parenthesis or something. '(((@n_n@)))' might as well be a smiley.
     */
    const BEFORE_MENTIONS = '(?:^|[\s\.\,\:\;\[\(]+)';

    const CHECK_USED    = true;
    const NO_CHECK_USED = false;

    /**
     * Normalize an input $nickname, and normalize it to its canonical form.
     * The canonical form will be returned, or an exception thrown if invalid.
     *
     * @throws NicknameException              (base class)
     * @throws NicknameBlacklistedException
     * @throws NicknameEmptyException
     * @throws NicknameInvalidException
     * @throws NicknamePathCollisionException
     * @throws NicknameTakenException
     * @throws NicknameTooLongException
     */
    public static function normalize(string $nickname, bool $check_already_used = self::NO_CHECK_USED): string
    {
        if (mb_strlen($nickname) > self::MAX_LEN) {
            // Display forms must also fit!
            throw new NicknameTooLongException();
        }

        $nickname = trim($nickname);
        $nickname = str_replace('_', '', $nickname);
        $nickname = mb_strtolower($nickname);
        $nickname = Normalizer::normalize($nickname, Normalizer::FORM_C);

        if (mb_strlen($nickname) < 1) {
            throw new NicknameEmptyException();
        } elseif (!self::isCanonical($nickname) && !filter_var($nickname, FILTER_VALIDATE_EMAIL)) {
            throw new NicknameInvalidException();
        } elseif (self::isReserved($nickname) || Common::isSystemPath($nickname)) {
            throw new NicknameReservedException();
        } elseif ($check_already_used) {
            $actor = self::isTaken($nickname);
            if ($actor instanceof GSActor) {
                throw new NicknameTakenException($actor);
            }
        }

        return $nickname;
    }

    /**
     * Nice simple check of whether the given string is a valid input nickname,
     * which can be normalized into an internally canonical form.
     *
     * Note that valid nicknames may be in use or reserved.
     *
     * @return bool True if nickname is valid. False if invalid (or taken if $check_already_used == true).
     */
    public static function isValid(string $nickname, bool $check_already_used = self::CHECK_USED): bool
    {
        try {
            self::normalize($nickname, $check_already_used);
        } catch (NicknameException $e) {
            return false;
        }

        return true;
    }

    /**
     * Is the given string a valid canonical nickname form?
     */
    public static function isCanonical(string $nickname): bool
    {
        return preg_match('/^(?:' . self::CANONICAL_FMT . ')$/', $nickname);
    }

    /**
     * Is the given string in our nickname blacklist?
     */
    public static function isReserved(string $nickname): bool
    {
        $reserved = Common::config('nickname', 'reserved');
        if (!$reserved) {
            return false;
        }
        return in_array($nickname, $reserved);
    }

    /**
     * Is the nickname already in use locally? Checks the User table.
     *
     * @return null|GSActor Returns GSActor if nickname found
     */
    public static function isTaken(string $nickname): ?GSActor
    {
        $found = DB::findBy('local_user', ['nickname' => $nickname]);
        if ($found instanceof LocalUser) {
            return $found->getProfile();
        }

        $found = DB::findBy('local_group', ['nickname' => $nickname]);
        if ($found instanceof LocalGroup) {
            return $found->getProfile();
        }

        return null;
    }
}


code:
<?php

// {{{ 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/>.
// }}}

/**
 * Base class for Transports
 *
 * @package   GNUsocial
 * @category  Util
 *
 * @author    Hugo Sales <hugo@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Notification;

abstract class AbstractTransport
{
    /**
     * Get the display name of this transport
     */
    abstract public function getName(): string;

    /**
     * Get the identifier used in code for this transport
     */
    abstract public function getIdentifier(): string;

    /**
     * Send a given Notification through this transport
     */
    abstract public function send(Notification $n): bool;

    /**
     * Get the display help message for one of the Notification-constants type
     */
    public function getHelpMessage(int $t): string
    {
        switch ($t) {
        case Notification::NOTICE_BY_FOLLOWED:
            return _m('Send me alerts of mentions by those I follow through {name}', ['{name}' => $this->getName()]);
        case Notification::MENTION:
            return _m('Send me alerts of mentions through {name}', ['{name}' => $this->getName()]);
        case Notification::REPLY:
            return _m('Send me alerts of replies to my notice through {name}', ['{name}' => $this->getName()]);
        case Notification::FOLLOW:
            return _m('Send me alerts of new follows through {name}', ['{name}' => $this->getName()]);
        case Notification::FAVORITE:
            return _m('Send me alerts of new favorites on my notices through {name}', ['{name}' => $this->getName()]);
        case Notification::NUDGE:
            return _m('Send me alerts when someone calls for my attention through {name}', ['{name}' => $this->getName()]);
        case Notification::DM:
            return _m('Send me alerts of new direct messages through {name}', ['{name}' => $this->getName()]);
        default:
            throw new \InvalidArgumentException('Given an invalid Notification constant value');
        }
    }

    /**
     * Get the display label message for one of the Notification-constants type
     */
    public function getLabelMessage(int $t): string
    {
        switch ($t) {
        case Notification::NOTICE_BY_FOLLOWED:
            return _m('Notify me of new notices');
        case Notification::MENTION:
            return _m('Notify me of mentions');
        case Notification::REPLY:
            return _m('Notify me of replies');
        case Notification::FOLLOW:
            return _m('Notify me of new follows');
        case Notification::FAVORITE:
            return _m('Notify me of new favorites');
        case Notification::NUDGE:
            return _m('Notify me when nudged');
        case Notification::DM:
            return _m('Notify of new DMs');
        default:
            throw new \InvalidArgumentException('Given an invalid Notification constant value');
        }
    }
}


code:
<?php

// {{{ 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@fc.up.pt>
 * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace App\Util\Notification;

use App\Entity\Profile;

class Notification
{
    public const NOTICE_BY_FOLLOWED = 1;
    public const MENTION            = 2;
    public const REPLY              = 3;
    public const FOLLOW             = 4;
    public const FAVORITE           = 5;
    public const NUDGE              = 6;
    public const DM                 = 7;

    /**
     * One of the above constants
     */
    private int $type;

    /**
     * Who caused this notification
     */
    private Profile $profile;

    public function __construct(int $type, Profile $profile)
    {
        $this->tyoe    = $type;
        $this->profile = $profile;
    }
}


code:
<?php

// {{{ 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/>.
// }}}

namespace App\Util\ds;

use Ds\Deque;
use Functional as F;

class RingBuffer implements \Serializable, \ArrayAccess
{
    private int $capacity;
    private Deque $elements;

    public function __construct(int $c)
    {
        $this->capacity = $c;
        $this->elements = new Deque();
    }

    public function add($e)
    {
        if ($this->capacity !== 0 && $this->elements->count() >= $this->capacity) {
            $this->elements->shift();
        }
        $this->elements->unshift($e);
    }

    public function remove(int $index)
    {
        return $this->elements->remove($index);
    }

    public function get(int $index)
    {
        return $this->elements->get($index);
    }

    // ------ Serialization
    public function serialize()
    {
        return serialize($this->capacity) . serialize($this->elements);
    }

    public function unserialize($data)
    {
        list($this->capacity, $this->elements) = F\map(explode(';', $data), F\ary('unserialize', 1));
    }
    // ------ End Serialization

    // ------ Array interface
    public function offsetSet($index, $value)
    {
        if (is_null($index)) {
            $this->add($value);
        } else {
            $this->elements->set($index, $value);
        }
    }

    public function offsetExists($index)
    {
        return is_int($index) && $index >= 0 && $index < $this->elements->count();
    }

    public function offsetUnset($index)
    {
        $this->elements->remove($index);
    }

    public function offsetGet($index)
    {
        return $this->offsetExists($index) ? $this->get($index) : null;
    }
    // ------ End Array interface
}


branch: master


code:
<!DOCTYPE html>

            {% block meta %}
                  <meta charset="UTF-8">
                  <meta name="viewport" content="width=device-width, initial-scale=1">
            {% endblock %}
            <title>
                  {% block title %}{% endblock %}
            </title>
            {% block stylesheets %}
                  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@800&family=Open+Sans&display=swap" rel="stylesheet">
                  <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/base.css') }}" media="screen and (min-width: 1300px)">
                  <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/base_mid.css') }}" media="screen and (max-width: 1300px)">
                  <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/base_small.css') }}" media="screen and (max-width: 750px)">
                  <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/reset.css') }}">
            {% endblock %}

            {% block icons %}
                  <svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                        <defs>
                              <symbol id="icon-attach" viewbox="0 0 32 32">
                                    {{ source('@public_path'~asset('assets/icons/attach.svg')) }}
                              </symbol>
                              <symbol id="icon-heart" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/heart.svg') }}
                              </symbol>
                              <symbol id="icon-logo" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/logo.svg') }}
                              </symbol>
                              <symbol id="icon-drop" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/drop.svg') }}
                              </symbol>
                              <symbol id="icon-reply" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/reply.svg') }}
                              </symbol>
                              <symbol id="icon-menu" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/menu.svg') }}
                              </symbol>
                              <symbol id="icon-repeat" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/repeat.svg') }}
                              </symbol>
                              <symbol id="icon-search" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/search.svg') }}
                              </symbol>
                              <symbol id="icon-avatar" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/avatar.svg') }}
                              </symbol>
                              <symbol id="icon-checkmark" viewbox="0 0 32 32">
                                    {{ source('@public_path/assets/icons/checkmark.svg') }}
                              </symbol>
                        </defs>
                  </svg>
            {% endblock %}

            {% block header %}

                              {% block left %}
                              {% endblock left %}

                                          <svg class="icon icon-logo">
                                                <use xlink:href="#icon-logo"></use>
                                          </svg>
                                           {{ config('site', 'name') }}

                                    <input type="checkbox" id="toggle-right" class="larger">

            {% endblock header%}

            {% block nav %}{% endblock %}
            {% block body %}{% endblock %}
            {% block javascripts %}{% endblock javascripts%}


code:
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Admin Configuration page!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
        <style>
          body {
              background-color: #333;
              color: #ddd;
          }
        </style>
    </head>
    <body>
        {% block body %}{% endblock %}
        {% block javascripts %}{% endblock %}

        {{ form(form) }}

        {% if old_value is defined and default is defined %}
            Old value: {{ old_value }} <br>
            Default value: {{ default }}
            Configuration dumped to <code> social.local.yaml </code>
            Backup of previous configuration in <code> social.local.yaml.back </code> will be overriden with the next change
        {% endif %}
    </body>
</html>


code:
{% extends 'left/left.html.twig' %}

{% block meta %}
      {{ parent() }}
{% endblock %}

{% block title %}{% endblock %}

{% block stylesheets %}
      {{ parent() }}
      <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/doc/doc.css') }}" media="screen and (min-width: 1300px)">
      <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/doc/doc_mid.css') }}" media="screen and (max-width: 1300px)">
      <link rel='stylesheet' type='text/css' href="{{ asset('assets/css/doc/doc_small.css') }}" media="screen and (max-width: 750px)">
{% endblock %}

{% block header %}
      {{ parent() }}
{% endblock %}

{% block left %}
      {{ parent() }}
{% endblock %}

{% block javascripts %}{% endblock %}


code:
{% extends 'doc/faq/faq.html.twig' %}

{% block title %}Help{% endblock %}

{% block body %}
{{ parent() }}
{% endblock %}

{% block markdown %}
      {% apply markdown_to_html %}
      ## Bugs
      If you think you've found a bug in the underlying [GNU social](https://gnu.io/social/) software, or if there's a new feature you'd like to see, add it into the [GNU social task list](https://git.gnu.io/gnu/gnu-social/issues). Don't forget to
      check the list of existing bugs to make sure it hasn't already been reported! ## Post a notice If you have a question about how to do something, just post a notice with your question, preferrably tagged with #NewHere. Watch your inbox for replies.
      ## Post a notice
      If you have a question about how to do something, just post a notice with your question, preferrably tagged with #NewHere. Watch your inbox for replies.
      {% endapply %}
{% endblock %}


code:
{% extends 'doc/base.html.twig' %}

{% block title %}FAQ{% endblock %}

{% block header %}{{ parent() }}{% endblock %}

{% block body %}

                              FAQ

                              Contact

                              Tags

                              Groups

                              OpenID

                  {% block markdown %}
                        {% apply markdown_to_html %}
                        ## What is this site?
                        This is a social network, running the GNU social software.

                        You can use it to make connections between friends, family and colleagues -- writing short notices about yourself, where you are, and what you're doing, and those notices will be sent to all your contacts.

                        In the future, we'll be adding support for photo, video and file sharing, as well as events, better contact management and mobile devices.

                        ## Help
                        GNU social is a **social service**. Users can post short status messages which are shared to their friends and colleagues on the service and (optionally) onto the Web.

                        You can also share other types of data, like bookmarks, event invitations, polls, and questions.

                        If you'd like to try it out, first register a new account. Then, on the public timeline, enter your message into the textbox at the top of the page, and click "Send". It will go out on the public timeline and to anyone who is subscribed to your notices.

                        To subscribe to other people's notifications, go to their profile page and click the "subscribe" button. They'll get a notice that you're now subscribed to their notifications.
                        {% endapply %}
                  {% endblock %}

{% endblock %}


code:
{% extends 'doc/faq/faq.html.twig' %}

{% block title %}Help{% endblock %}
{% block body %}
{{ parent() }}
{% endblock %}

{% block markdown %}
      {% apply markdown_to_html %}
      ## Groups
      Users on GNU social can create groups that other users can join. Groups can be a great way to share information and entertainment with a group of people who have a common interest or background; who work together on a team; or who have a particular knowledge or skill.

      You can find out about groups on the server on the Groups page. You can join a group by clicking on the "Join" button either in the group list or on the group's home page.

      ## Starting a new group
      You can start a new group for friends and colleagues. Note that all groups are free for anyone to join.

      To start a new group, use the new group tool and fill out the form. Describe your group as best you can if you want people to be able to find it.

      When choosing the nickname for your group, try to keep it short. The nickname is sometimes included in messages to and from the group, so the less chars the better. Try using acronyms for organizations, or airport codes for places (like 'pdx' instead of 'portland').

      ## Sending messages to a group
      You can send a message to a group using the syntax "!groupname" anywhere in the message. If you have more than one group named, the notice will go to each group. Only members can send notices to a group, and groups do not respond to direct messages (DMs).

      You can also select the group from the "To:" drop down when posting.

      You can make a group message private by clicking the "private" button before posting.

      ## Receiving messages
      New group messages will appear in your inbox, and will also come to your phone or IM client if you've set them up to receive notices.

      ## Private groups
      The administrator can make a group private. For a private group, all notices will marked as private for group members only. Also, administrators will have to approve all new members to the group.

      Private groups are visible in the group directory.
      {% endapply %}
{% endblock %}


code:
{% extends 'doc/faq/faq.html.twig' %}

{% block title %}Help{% endblock %}

{% block body %}
{{ parent() }}
{% endblock %}

{% block markdown %}
      {% apply markdown_to_html %}
      ## Openid
      GNU social supports the OpenID standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See Wikipedia's OpenID article for more information.)

      If you already have an account on GNU social, you can login with your username and password as usual. To use OpenID in the future, you can add an OpenID to your account after you have logged in normally.

      There are many Public OpenID providers, and you may already have an OpenID-enabled account on another service.

      - **On wikis:** If you have an account on an OpenID-enabled wiki, like Wikitravel, wikiHow, Vinismo, AboutUs or Keiki, you can log in to GNU social by entering the full URL of your user page on that other wiki in the box above. For example, http://kei.ki/en/User:Evan.

      - **Yahoo!:** If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form https://me.yahoo.com/yourusername.

      - **AOL:** If you have an account with AOL, like an AIM account, you can log in to GNU social by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form http://openid.aol.com/yourusername. Your username should be all lowercase, no spaces.

      - **Blogger, Wordpress.com, LiveJournal, Vox:** If you have a blog on any of these services, enter your blog URL in the box above. For example, http://yourusername.blogspot.com/, http://yourusername.wordpress.com/, http://yourusername.livejournal.com/, or http://yourusername.vox.com/.

      Additionally, once you have an account on GNU social, you can use your profile's URL (https://gnusocial.org/yourusername) as an OpenID elsewhere as well.

      {% endapply %}
{% endblock %}


code:
{% extends 'doc/faq/faq.html.twig' %}

{% block title %}Help{% endblock %}

{% block body %}
{{ parent() }}
{% endblock %}

{% block markdown %}
      {% apply markdown_to_html %}
      ## Tags
      GNU social supports tags to help you organize your activities here. You can use tags for people and for notices.

      ## Tagging a notice
      You can tag a notice using a hashtag; a # character followed by letters and numbers as well as '.', '-', and '_'. Note that accented latin characters are not supported, and non-roman scripts are right out.

      The HTML for the notice will link to a stream of all the other notices with that tag. This can be a great way to keep track of a conversation.

      ## Tagging yourself
      You can also add tags for yourself on your profile settings page or by using the edit tags button on your profile page. Use single words to describe yourself, your experiences and your interest. The tags will become links on your profile page to a list of all the users on the site who use that same tag. It can be a nice way to find people who are related to you geographically or who have a common interest.

      ## Tagging others
      You can also tag other users by using the edit tags button next to their profile. Such tags are called people tags. Once you have created a people tag, you can add or remove users from it using the tag's edit form. This makes it easy to organize your subscriptions into groups and sort through them separately. Also, it will let you create custom lists of people that others can subscribe to.

      You can also send a notice "to the attention of" your subscribers whom you've marked with a particular tag (note: not people who've marked themselves with that tag). "@#family hello" will send a notice to all your subscribers you've marked with the tag 'family'.

      ## Private and public people tags
      A private people tag is only visible to the creator, it cannot be subscribed to, but the timeline can be viewed. To create a new private prepend a '.' to the tag in the tags editing box. To set an existing public tag as private or vice-versa, go to the tag's edit page.
      {% endapply %}
{% endblock %}