[DOCUMENTATION][REFACTOR] Add documentation to all flagged function and do some small cleanup

This commit is contained in:
Hugo Sales 2020-11-06 19:47:15 +00:00 committed by Hugo Sales
parent 9cc7b6adf5
commit 5cced1c9ed
21 changed files with 363 additions and 92 deletions

View File

@ -28,6 +28,7 @@ use App\Core\Log;
use App\Entity\Avatar;
use App\Entity\File;
use App\Util\Common;
use App\Util\Exception\ClientException;
use Component\Media\Exception\NoAvatarException;
use Exception;
use Symfony\Component\Asset\Package;
@ -39,6 +40,9 @@ use Symfony\Component\HttpFoundation\Response;
abstract class Utils
{
/**
* Perform file validation (checks and normalization) and store the given file
*/
public static function validateAndStoreFile(SymfonyFile $sfile,
string $dest_dir,
?string $title = null,
@ -87,7 +91,14 @@ abstract class Utils
return $response;
}
public static function error($except, $id, array $res)
/**
* Throw a client exception if the cache key $id doesn't contain
* exactly one entry
*
* @param mixed $except
* @param mixed $id
*/
private static function error($except, $id, array $res)
{
switch (count($res)) {
case 0:
@ -96,23 +107,15 @@ abstract class Utils
return $res[0];
default:
Log::error('Media query returned more than one result for identifier: \"' . $id . '\"');
throw new Exception(_m('Internal server error'));
throw new ClientException(_m('Internal server error'));
}
}
public static function getAvatar(string $nickname)
{
return self::error(NoAvatarException::class,
$nickname,
Cache::get("avatar-{$nickname}",
function () use ($nickname) {
return DB::dql('select a from App\\Entity\\Avatar a ' .
'join App\Entity\GSActor g with a.gsactor_id = g.id ' .
'where g.nickname = :nickname',
['nickname' => $nickname]);
}));
}
/**
* Get the file info by id
*
* Returns the file's hash, mimetype and title
*/
public static function getFileInfo(int $id)
{
return self::error(NoSuchFileException::class,
@ -126,14 +129,62 @@ abstract class Utils
}));
}
public static function getAttachmentFileInfo(int $id)
// ----- Attachment ------
/**
* Get the attachment file info by id
*
* Returns the attachment file's hash, mimetype, title and path
*/
public static function getAttachmentFileInfo(int $id): array
{
$res = self::getFileInfo($id);
$res['file_path'] = Common::config('attachments', 'dir') . $res['file_hash'];
return $res;
}
public static function getAvatarFileInfo(string $nickname)
// ----- Avatar ------
/**
* Get the avatar associated with the given nickname
*/
public static function getAvatar(?string $nickname = null): Avatar
{
$nickname = $nickname ?: Common::userNickname();
return self::error(NoAvatarException::class,
$nickname,
Cache::get("avatar-{$nickname}",
function () use ($nickname) {
return DB::dql('select a from App\\Entity\\Avatar a ' .
'join App\Entity\GSActor g with a.gsactor_id = g.id ' .
'where g.nickname = :nickname',
['nickname' => $nickname]);
}));
}
/**
* Get the cached avatar associated with the given nickname, or the current user if not given
*/
public static function getAvatarUrl(?string $nickname = null): string
{
$nickname = $nickname ?: Common::userNickname();
return Cache::get("avatar-url-{$nickname}", function () use ($nickname) {
try {
return self::getAvatar($nickname)->getUrl();
} catch (NoAvatarException $e) {
}
$package = new Package(new EmptyVersionStrategy());
return $package->getUrl(Common::config('avatar', 'default'));
});
}
/**
* Get the cached avatar file info associated with the given nickname
*
* Returns the avatar file's hash, mimetype, title and path.
* Ensures exactly one cached value exists
*/
public static function getAvatarFileInfo(string $nickname): array
{
try {
$res = self::error(NoAvatarException::class,
@ -154,24 +205,4 @@ abstract class Utils
return ['file_path' => $filepath, 'mimetype' => 'image/svg+xml', 'title' => null];
}
}
public static function getAvatarUrl(?string $nickname = null)
{
if ($nickname == null) {
$user = Common::user();
if ($user != null) {
$nickname = $user->getNickname();
} else {
throw new Exception('No user is logged in and no avatar provided to `getAvatarUrl`');
}
}
return Cache::get("avatar-url-{$nickname}", function () use ($nickname) {
try {
return self::getAvatar($nickname)->getUrl();
} catch (NoAvatarException $e) {
}
$package = new Package(new EmptyVersionStrategy());
return $package->getUrl(Common::config('avatar', 'default'));
});
}
}

View File

@ -38,10 +38,14 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
class Posting extends Module
{
public function onStartTwigPopulateVars(array &$vars)
/**
* HTML render event handler responsible for adding and handling
* the result of adding the note submission form, only if a user is logged in
*/
public function onStartTwigPopulateVars(array &$vars): bool
{
if (($user = Common::user()) == null) {
return;
return Event::next;
}
$actor_id = $user->getId();
@ -79,9 +83,20 @@ class Posting extends Module
return Event::next;
}
/**
* Store the given note with $content and $attachments, created by
* $actor_id, possibly as a reply to note $reply_to and with flag
* $is_local. Sanitizes $content and $attachments
*/
public static function storeNote(int $actor_id, string $content, array $attachments, bool $is_local, ?int $reply_to = null, ?int $repeat_of = null)
{
$note = Note::create(['gsactor_id' => $actor_id, 'content' => $content, 'is_local' => $is_local, 'reply_to' => $reply_to, 'repeat_of' => $repeat_of]);
$note = Note::create([
'gsactor_id' => $actor_id,
'content' => Security::sanitize($content),
'is_local' => $is_local,
'reply_to' => $reply_to,
'repeat_of' => $repeat_of,
]);
$files = [];
foreach ($attachments as $f) {
$nf = Media::validateAndStoreFile($f, Common::config('attachments', 'dir'),

View File

@ -32,9 +32,16 @@ use Symfony\Component\HttpFoundation\Request;
class Favourite extends Module
{
/**
* HTML rendering event that adds the favourite form as a note
* action, if a user is logged in
*/
public function onAddNoteActions(Request $request, Note $note, array &$actions)
{
$user = Common::user();
if (($user = Common::user()) == null) {
return Event::next;
}
$opts = ['note_id' => $note->getId(), 'gsactor_id' => $user->getId()];
$is_set = DB::find('favourite', $opts) != null;
$form = Form::create([
@ -42,6 +49,8 @@ class Favourite extends Module
['note_id', HiddenType::class, ['data' => $note->getId()]],
['favourite', SubmitType::class, ['label' => ' ']],
]);
// Form handler
$ret = self::noteActionHandle($request, $form, $note, 'favourite', function ($note, $data) use ($opts) {
$fave = DB::find('favourite', $opts);
if (!$data['is_set'] && ($fave == null)) {
@ -53,9 +62,11 @@ class Favourite extends Module
}
return Event::stop;
});
if ($ret != null) {
return $ret;
}
$actions[] = $form->createView();
return Event::next;
}

View File

@ -32,9 +32,16 @@ use Symfony\Component\HttpFoundation\Request;
class Repeat extends Module
{
/**
* HTML rendering event that adds the repeat form as a note
* action, if a user is logged in
*/
public function onAddNoteActions(Request $request, Note $note, array &$actions)
{
$user = Common::user();
if (($user = Common::user()) == null) {
return Event::next;
}
$opts = ['gsactor_id' => $user->getId(), 'repeat_of' => $note->getId()];
try {
$is_set = DB::findOneBy('note', $opts) != null;
@ -47,6 +54,8 @@ class Repeat extends Module
['note_id', HiddenType::class, ['data' => $note->getId()]],
['repeat', SubmitType::class, ['label' => ' ']],
]);
// Handle form
$ret = self::noteActionHandle($request, $form, $note, 'repeat', function ($note, $data, $user) use ($opts) {
$note = DB::findOneBy('note', $opts);
if (!$data['is_set'] && $note == null) {
@ -63,6 +72,7 @@ class Repeat extends Module
}
return Event::stop;
});
if ($ret != null) {
return $ret;
}

View File

@ -44,23 +44,43 @@ class Reply extends Module
$r->connect('note_reply', '/note/reply/{reply_to<\\d*>}', [self::class, 'replyController']);
}
/**
* HTML rendering event that adds the reply form as a note action,
* if a user is logged in
*/
public function onAddNoteActions(Request $request, Note $note, array &$actions)
{
if (($user = Common::user()) == null) {
return Event::next;
}
$form = Form::create([
['content', HiddenType::class, ['label' => ' ', 'required' => false]],
['attachments', HiddenType::class, ['label' => ' ', 'required' => false]],
['note_id', HiddenType::class, ['data' => $note->getId()]],
['reply', SubmitType::class, ['label' => ' ']],
]);
// Handle form
$ret = self::noteActionHandle($request, $form, $note, 'reply', function ($note, $data) {
if ($data['content'] !== null) {
// JS submitted
// TODO DO THE THING
// TODO Implement in JS
$actor_id = $user->getId();
Posting::storeNote(
$actor_id,
$data['content'],
$data['attachments'],
$is_local = true,
$data['reply_to'],
$repeat_of = null
);
} else {
// JS disabled, redirect
throw new RedirectException('note_reply', ['reply_to' => $note->getId()]);
}
});
if ($ret != null) {
return $ret;
}
@ -68,6 +88,9 @@ class Reply extends Module
return Event::next;
}
/**
* Controller for the note reply non-JS page
*/
public function replyController(Request $request, string $reply_to)
{
$user = Common::ensureLoggedIn();
@ -88,7 +111,14 @@ class Reply extends Module
if ($form->isSubmitted()) {
$data = $form->getData();
if ($form->isValid()) {
Posting::storeNote($actor_id, $data['content'], $data['attachments'], $is_local = true, $data['reply_to'], null);
Posting::storeNote(
$actor_id,
$data['content'],
$data['attachments'],
$is_local = true,
$data['reply_to'],
$repeat_of = null
);
} else {
throw new InvalidFormException();
}

View File

@ -45,8 +45,13 @@ use Symfony\Component\HttpFoundation\Request;
class AdminPanel extends Controller
{
/**
* Handler for the site admin panel section. Allows the
* administrator to change various configuration options
*/
public function site(Request $request)
{
// TODO CHECK PERMISSION
$defaults = Common::getConfigDefaults();
$options = [];
foreach ($defaults as $key => $inner) {

View File

@ -25,6 +25,9 @@ use Symfony\Component\Validator\Constraints\NotBlank;
class Security extends Controller
{
/**
* Log a user in
*/
public function login(AuthenticationUtils $authenticationUtils)
{
if ($this->getUser()) {
@ -44,6 +47,10 @@ class Security extends Controller
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
/**
* Register a user, making sure the nickname is not reserved and
* possibly sending a confirmation email
*/
public function register(Request $request,
EmailVerifier $email_verifier,
GuardAuthenticatorHandler $guard_handler,

View File

@ -65,6 +65,9 @@ use Symfony\Component\HttpFoundation\Request;
class UserPanel extends AbstractController
{
/**
* Local user personal information panel
*/
public function personal_info(Request $request)
{
$user = Common::user();
@ -85,6 +88,9 @@ class UserPanel extends AbstractController
return ['_template' => 'settings/profile.html.twig', 'prof' => $form->createView()];
}
/**
* Local user account information panel
*/
public function account(Request $request)
{
$user = Common::user();
@ -103,6 +109,9 @@ class UserPanel extends AbstractController
return ['_template' => 'settings/account.html.twig', 'acc' => $form->createView()];
}
/**
* Local user avatar panel
*/
public function avatar(Request $request)
{
$form = Form::create([
@ -160,6 +169,9 @@ class UserPanel extends AbstractController
return ['_template' => 'settings/avatar.html.twig', 'avatar' => $form->createView()];
}
/**
* Local user notification settings tabbed panel
*/
public function notifications(Request $request)
{
$schema = DB::getConnection()->getSchemaManager();

View File

@ -1,6 +1,7 @@
<?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
@ -15,6 +16,7 @@
//
// 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\Core;
@ -125,6 +127,10 @@ abstract class Cache
return self::$pools[$pool]->delete($key);
}
/**
* Retrieve a list from the cache, with a different implementation
* for redis and others, trimming to $max_count if given
*/
public static function getList(string $key, callable $calculate, string $pool = 'default', int $max_count = -1, float $beta = 1.0): array
{
if (isset(self::$redis[$pool])) {
@ -149,6 +155,7 @@ abstract class Cache
self::$redis[$pool]->lPush($key, ...$res);
}
}
self::$redis[$pool]->lTrim($key, 0, $max_count);
return self::$redis[$pool]->lRange($key, 0, $max_count);
} else {
$keys = self::getKeyList($key, $max_count, $beta);
@ -160,6 +167,9 @@ abstract class Cache
}
}
/**
* Push a value to the list, if not using redis, get, add to subkey and set
*/
public static function pushList(string $key, mixed $value, string $pool = 'default', int $max_count = 64, float $beta = 1.0): void
{
if (isset(self::$redis[$pool])) {
@ -179,6 +189,9 @@ abstract class Cache
}
}
/**
* Delete a whole list at $key, if not using redis, recurse into keys
*/
public static function deleteList(string $key, string $pool = 'default'): bool
{
if (isset(self::$redis[$pool])) {
@ -192,6 +205,9 @@ abstract class Cache
}
}
/**
* On non-Redis, get the list of keys that store a list at $key
*/
private static function getKeyList(string $key, int $max_count, string $pool, float $beta): RingBuffer
{
// Get the current keys associated with a list. If the cache

View File

@ -57,6 +57,9 @@ class Controller extends AbstractController implements EventSubscriberInterface
}
}
/**
* Symfony event when it's searching for which controller to use
*/
public function onKernelController(ControllerEvent $event)
{
$controller = $event->getController();
@ -69,6 +72,9 @@ class Controller extends AbstractController implements EventSubscriberInterface
return $event;
}
/**
* Symfony event when the controller result is not a Response object
*/
public function onKernelView(ViewEvent $event)
{
$request = $event->getRequest();
@ -99,6 +105,9 @@ class Controller extends AbstractController implements EventSubscriberInterface
return $event;
}
/**
* Symfony event when the controller throws an exception
*/
public function onKernelException(ExceptionEvent $event)
{
$except = $event->getThrowable();

View File

@ -48,6 +48,9 @@ abstract class DB
self::$em = $m;
}
/**
* Perform a Doctrine Query Language query
*/
public static function dql(string $query, array $params = [])
{
$q = new Query(self::$em);
@ -58,6 +61,11 @@ abstract class DB
return $q->getResult();
}
/**
* Perform a native, parameterized, SQL query. $entities is a map
* from table aliases to class names. Replaces '{select}' in
* $query with the appropriate select list
*/
public static function sql(string $query, array $entities, array $params = [])
{
$rsm = new ResultSetMappingBuilder(self::$em);
@ -69,15 +77,23 @@ abstract class DB
foreach ($params as $k => $v) {
$q->setParameter($k, $v);
}
// dump($q);
// die();
return $q->getResult();
}
private static array $find_by_ops = ['or', 'and', 'eq', 'neq', 'lt', 'lte',
/**
* A list of possible operations needed in self::buildExpression
*/
private static array $find_by_ops = [
'or', 'and', 'eq', 'neq', 'lt', 'lte',
'gt', 'gte', 'is_null', 'in', 'not_in',
'contains', 'member_of', 'starts_with', 'ends_with', ];
'contains', 'member_of', 'starts_with', 'ends_with',
];
/**
* Build a Doctrine Criteria expression from the given $criteria.
*
* @see self::findBy for the syntax
*/
private static function buildExpression(ExpressionBuilder $eb, array $criteria)
{
$expressions = [];
@ -100,6 +116,13 @@ abstract class DB
return $expressions;
}
/**
* Query $table according to $criteria. If $criteria's keys are
* one of self::$find_by_ops (and, or, etc), build a subexpression
* with that operator and recurse. Examples of $criteria are
* `['and' => ['lt' => ['foo' => 4], 'gte' => ['bar' => 2]]]` or
* `['in' => ['foo', 'bar']]`
*/
public static function findBy(string $table, array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
{
$criteria = array_change_key_case($criteria);
@ -113,6 +136,9 @@ abstract class DB
}
}
/**
* Return the first element of the result of @see self::findBy
*/
public static function findOneBy(string $table, array $criteria, ?array $orderBy = null, ?int $offset = null)
{
$res = self::findBy($table, $criteria, $orderBy, 1, $offset);
@ -123,18 +149,21 @@ abstract class DB
}
}
/**
* Intercept static function calls to allow refering to entities
* without writing the namespace (which is deduced from the call
* context)
*/
public static function __callStatic(string $name, array $args)
{
foreach (['find', 'getReference', 'getPartialReference', 'getRepository'] as $m) {
// TODO Plugins
$pref = '\App\Entity\\';
if ($name == $m && Formatting::startsWith($name, $pref) === false) {
$args[0] = $pref . ucfirst(Formatting::snakeCaseToCamelCase($args[0]));
}
}
if (isset($args[0]) && is_string($args[0])) {
$args[0] = preg_replace('/Gsactor/', 'GSActor', $args[0] ?? '');
// TODO Plugins
// If the method is one of the following and the first argument doesn't look like a FQCN, add the prefix
$pref = '\App\Entity\\';
if (in_array($name, ['find', 'getReference', 'getPartialReference', 'getRepository'])
&& preg_match('/\\\\/', $args[0]) === 0
&& Formatting::startsWith($args[0], $pref) === false) {
$args[0] = $pref . ucfirst(Formatting::snakeCaseToCamelCase($args[0]));
$args[0] = preg_replace('/Gsactor/', 'GSActor', $args[0]);
}
return self::$em->{$name}(...$args);

View File

@ -25,8 +25,18 @@ use App\Core\DB\DB;
use App\Util\Formatting;
use DateTime;
class Entity
/**
* Base class to all entities, with some utilities
*/
abstract class Entity
{
/**
* Create an instance of the called class or fill in the
* properties of $obj with the associative array $args. Doesn't
* persist the result
*
* @param null|mixed $obj
*/
public static function create(array $args, $obj = null)
{
$class = get_called_class();
@ -43,12 +53,21 @@ class Entity
return $obj;
}
/**
* Create a new instance, but check for duplicates
*/
public static function createOrUpdate(array $args, array $find_by)
{
$table = Formatting::camelCaseToSnakeCase(get_called_class());
return self::create($args, DB::findBy($table, $find_by)[0]);
return self::create($args, DB::findOneBy($table, $find_by));
}
/**
* Remove a given $obj or whatever is found by `DB::findBy(..., $args)`
* from the database. Doesn't flush
*
* @param null|mixed $obj
*/
public static function remove(array $args, $obj = null)
{
$class = '\\' . get_called_class();

View File

@ -46,6 +46,9 @@ abstract class Form
self::$form_factory = $ff;
}
/**
* Create a form with the given associative array $form as fields
*/
public static function create(array $form,
?object $target = null,
array $extra_data = [],
@ -79,11 +82,19 @@ abstract class Form
return $fb->getForm();
}
/**
* Whether the given $field of $form has the `required` property
* set, defaults to true
*/
public static function isRequired(array $form, string $field): bool
{
return $form[$field][2]['required'] ?? true;
}
/**
* Handle the full life cycle of a form. Creates it with @see
* self::create and inserts the submitted values into the database
*/
public static function handle(array $form_definition, Request $request, object $target, array $extra_args = [], ?callable $extra_step = null, array $create_args = [])
{
$form = self::create($form_definition, $target, ...$create_args);

View File

@ -234,6 +234,15 @@ abstract class I18n
];
}
/**
* Format the given associative array $messages in the ICU
* translation format, with the given $params. Allows for a
* declarative use of the translation engine, for example
* `formatICU(['she' => ['She has one foo', 'She has many foo'],
* 'he' => ['He has one foo', 'He has many foo']], ['she' => 1])`
*
* @see http://userguide.icu-project.org/formatparse/messages
*/
public static function formatICU(array $messages, array $params): string
{
$res = '';
@ -293,10 +302,12 @@ abstract class I18n
*
* @todo add parameters
*/
function _m(): string
function _m(...$args): string
{
$domain = I18n::_mdomain(debug_backtrace()[0]['file']);
$args = func_get_args();
// Get the file where this function was called from, reducing the
// memory and performance inpact by not returning the arguments,
// and only 2 frames (this and previous)
$domain = I18n::_mdomain(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)[0]['file'], 2);
switch (count($args)) {
case 1:
// Empty parameters, simple message

View File

@ -75,8 +75,10 @@ class TransExtractor extends AbstractFileExtractor implements ExtractorInterface
],
];
// TODO probably shouldn't be done this way
// {{{Code from PhpExtractor
// See vendor/symfony/translation/Extractor/PhpExtractor.php
//
const MESSAGE_TOKEN = 300;
const METHOD_ARGUMENTS_TOKEN = 1000;
const DOMAIN_TOKEN = 1001;
@ -143,6 +145,9 @@ class TransExtractor extends AbstractFileExtractor implements ExtractorInterface
}
}
/**
* {@inheritdoc}
*/
private function skipMethodArgument(\Iterator $tokenIterator)
{
$openBraces = 0;
@ -284,6 +289,9 @@ class TransExtractor extends AbstractFileExtractor implements ExtractorInterface
}
}
/**
* Store the $message in the message catalogue $mc
*/
private function store(MessageCatalogue $mc, string $message,
string $domain, string $filename, ?int $line_no = null)
{
@ -293,6 +301,11 @@ class TransExtractor extends AbstractFileExtractor implements ExtractorInterface
$mc->setMetadata($message, $metadata, $domain);
}
/**
* Calls `::_m_dynamic` from the class defined in $filename and
* stores the results in the catalogue. For cases when the
* translation can't be done in a static (non-PHP) file
*/
private function storeDynamic(MessageCatalogue $mc, string $filename)
{
require_once $filename;

View File

@ -24,8 +24,16 @@ use App\Util\Common;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
class Module
/**
* Base class for all GNU social modules (plugins and components)
*/
abstract class Module
{
/**
* Serialize the class to store in the cache
*
* @param mixed $state
*/
public static function __set_state($state)
{
$class = get_called_class();
@ -36,6 +44,10 @@ class Module
return $obj;
}
/**
* Handle the $form submission for the note action for note if
* $note->getId() == $data['note_id']
*/
public static function noteActionHandle(Request $request, Form $form, Note $note, string $form_name, callable $handle)
{
if ('POST' === $request->getMethod() && $request->request->has($form_name)) {

View File

@ -60,6 +60,9 @@ class ModuleManager
protected array $modules = [];
protected array $events = [];
/**
* Add the $fqcn class from $path as a module
*/
public function add(string $fqcn, string $path)
{
list($type, $module) = preg_split('/\\\\/', $fqcn, 0, PREG_SPLIT_NO_EMPTY);
@ -69,6 +72,9 @@ class ModuleManager
$this->modules[$id] = $obj;
}
/**
* Container-build-time step that preprocesses the registering of events
*/
public function preRegisterEvents()
{
foreach ($this->modules as $id => $obj) {
@ -83,6 +89,9 @@ class ModuleManager
}
}
/**
* Compiler pass responsible for registering all modules
*/
public static function process(?ContainerBuilder $container = null)
{
$module_paths = array_merge(glob(INSTALLDIR . '/components/*/*.php'), glob(INSTALLDIR . '/plugins/*/*.php'));
@ -113,6 +122,11 @@ class ModuleManager
file_put_contents(CACHE_FILE, "<?php\nreturn " . var_export($module_manager, true) . ';');
}
/**
* Serialize this class, for dumping into the cache
*
* @param mixed $state
*/
public static function __set_state($state)
{
$obj = new self();
@ -121,6 +135,10 @@ class ModuleManager
return $obj;
}
/**
* Load the modules at runtime. In production requires the cache
* file to exist, in dev it rebuilds this cache
*/
public function loadModules()
{
if ($_ENV['APP_ENV'] == 'prod' && !file_exists(CACHE_FILE)) {

View File

@ -232,6 +232,11 @@ class Note extends Entity
return null;
}
/**
* Whether this note is visible to the given actor
*
* @param mixed $a
*/
public function isVisibleTo(/* GSActor|LocalUser */ $a): bool
{
$scope = NoteScope::create($this->scope);

View File

@ -75,6 +75,10 @@ class Kernel extends BaseKernel
}
}
/**
* Symfony framework function override responsible for registering
* bundles (similar to our modules)
*/
public function registerBundles(): iterable
{
$contents = require $this->getProjectDir() . '/config/bundles.php';
@ -90,6 +94,11 @@ class Kernel extends BaseKernel
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'));
@ -118,6 +127,9 @@ class Kernel extends BaseKernel
}
}
/**
* Configure HTTP(S) route to controller mapping
*/
protected function configureRoutes(RoutingConfigurator $routes): void
{
$config = \dirname(__DIR__) . '/config';
@ -131,6 +143,10 @@ class Kernel extends BaseKernel
}
}
/**
* 'Compile-time' step that builds the container, allowing us to
* define compiler passes
*/
protected function build(ContainerBuilder $container): void
{
parent::build($container);

View File

@ -23,7 +23,12 @@ use App\Util\Exception\ServerException;
abstract class Bitmap
{
public static function _do(int $r, bool $instance)
/**
* 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();

View File

@ -89,6 +89,15 @@ abstract class Common
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) {
@ -114,32 +123,9 @@ abstract class Common
}
}
// function array_diff_recursive($arr1, $arr2)
// {
// $outputDiff = [];
// foreach ($arr1 as $key => $value) {
// // if the key exists in the second array, recursively call this function
// // if it is an array, otherwise check if the value is in arr2
// if (array_key_exists($key, $arr2)) {
// if (is_array($value)) {
// $recursiveDiff = self::array_diff_recursive($value, $arr2[$key]);
// if (count($recursiveDiff)) {
// $outputDiff[$key] = $recursiveDiff;
// }
// } else if (!in_array($value, $arr2)) {
// $outputDiff[$key] = $value;
// }
// } else if (!in_array($value, $arr2)) {
// // if the key is not in the second array, check if the value is in
// // the second array (this is a quirk of how array_diff works)
// $outputDiff[$key] = $value;
// }
// }
// return $outputDiff;
// }
/**
* A recursive `array_diff`, while PHP itself doesn't provide one
*/
public function array_diff_recursive(array $array1, array $array2)
{
$difference = [];