forked from GNUsocial/gnu-social
[DOCUMENTATION][REFACTOR] Add documentation to all flagged function and do some small cleanup
This commit is contained in:
parent
67d4702743
commit
e8feb2ae84
@ -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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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'),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)) {
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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 = [];
|
||||
|
Loading…
Reference in New Issue
Block a user