2266 Commits

Author SHA1 Message Date
539104ec33 [PLUGIN][Pinboard] Refactor and cleanup code 2022-04-01 00:17:57 +01:00
74ffd261b8 [PLUGIN][Pinboard] Implement tag handling 2022-04-01 00:16:04 +01:00
ca9945a4be [ENTITY][Actor][COMPONENT][Tag] Add Actor->getNoteTags(?string $note_type) which gets a cached list of NoteTags for notes of type $note_type for the actor 2022-04-01 00:11:01 +01:00
08587b6942 [COMPONENT][Link][Tag] Refactor to make it easier to create links or tags from other places 2022-04-01 00:09:25 +01:00
1664293cf7 [PLUGIN][Pinboard] Change token to user user ID rather than nickname, to avoid complications with it possibly changing 2022-03-31 22:06:37 +01:00
94ab4ce8c4 [PLUGIN][Pinboard] Invalidate token and it's cache when actor information is changed via ActorForms 2022-03-31 03:47:14 +01:00
dd70de20da [PLUGIN][Pinboard] Implement token authentication and settings page, allowing the user to enable, disable, refresh or consult their token 2022-03-31 03:29:31 +01:00
ded9c86054 [CORE][DB] Add DB::refetch, which refetches an entity from the database, so it's managed and definitely up to date (use when wanting to update entities from cache) 2022-03-31 03:29:31 +01:00
20e07c9140 [CORE][DB] Make DB::dql return an object rather than an array if limit 1 is specified 2022-03-31 03:29:31 +01:00
4e2f6545ec [COMPONENT][Person][PLUGIN][WebHooks] Rename person settings section from 'others' to 'api' 2022-03-31 03:29:31 +01:00
f6a8f44420 [COMPONENT][Person][TEMPLATES] Move persosn settings template from core to the component 2022-03-31 03:29:31 +01:00
fd71d6ee7d [PLUGIN][UnboundGroup] Finish implementation 2022-03-29 00:57:41 +01:00
dfc5918c2c [PLUGIN][ActivityPub] Federate out Service information in Activities 2022-03-28 23:54:19 +01:00
83599ef866 [CORE][Modules][Plugin] version should be static 2022-03-28 23:54:18 +01:00
fa82306f6f [COMPONENT][Posting] Blog posts should be Articles by default 2022-03-28 23:54:18 +01:00
10f71e9fed [UI][TEMPLATES] Fix note text template. Use rendered content directly 2022-03-28 23:23:07 +01:00
e2501ee927 [PLUGIN][Pinboard] Implement remaining API endpoints, restructure, fix template 2022-03-28 23:23:07 +01:00
a9665177ea [PLUGIN][Blog] Move to plugins, mistakenly was in components 2022-03-28 20:59:16 +01:00
41861d284c [COMPONENT][Circle] Correct self tags settings text 2022-03-28 20:59:16 +01:00
bd868a2675 [PLUGINS][Pinboard] Add initial implementation of Pinboard API, lacking authentication, tags and feed endpoints 2022-03-28 20:59:16 +01:00
87e35716c1 [UTIL] Add Formatting::explode(array , string ) 2022-03-28 20:59:16 +01:00
dac94f53cd [CORE][Entity] Rename createOrUpdate to 'checkExistingAndCreateOrUpdate', remove update feature from 'create' and add 'createOrUpdate' and fix users 2022-03-28 20:59:15 +01:00
b10c359dec [DEPENDENCIES] Update dependencies 2022-03-28 20:59:15 +01:00
483983790a [CORE][Router] Rename \App\Core\Router\Router to \App\Core\Router and merge \App\Core\Router\RouteLoader with \App\Core\Router 2022-03-28 20:59:15 +01:00
60af9f5e9b [CORE][Queue] Rename App\Core\Queue\Queue to App\Core\Queue 2022-03-28 20:59:15 +01:00
abe35428da [CORE][DB] Rename App\Core\DB\DB to App\Core\DB 2022-03-28 20:59:14 +01:00
ca5520edbf [PLUGIN][WebHooks] Add hook for subscriptions 2022-03-28 20:59:14 +01:00
e3e14c53ef [PLUGIN][ActivityPub] Model/Note->toJson federate the url, even though it's the same as the id 2022-03-28 20:59:14 +01:00
be33c20614 [PLUGIN][ActivityPub] Improve flexibility of Type layer, accomodate more elaborate understanding of Group Announces after FEP-2100 development 2022-03-28 20:58:48 +01:00
7305a725cb [PLUGIN][UnboundGroup] First steps on implementing AP FEP-2100 2022-03-28 20:57:43 +01:00
fd4c3b0e68 [PLUGIN][Embed][Test] Move Test to correct location 2022-03-28 20:53:50 +01:00
16f51e5143 [COMPONENT][Notification] ->getSubscribers() should not be pre-included
Notification bug fix on Subscription component
Correct docblock
2022-03-28 20:53:19 +01:00
ba4230447e [COMPONENT][Group] Add orderBy to query, as otherwise the feed order is wrong 2022-03-28 20:49:28 +01:00
7463044971 [COMPONENT][Circle] Ensure strict typing on getter 2022-03-28 20:48:29 +01:00
7027633ed5 [PLUGIN][WebHooks] Make request method configurable
This way, PUT can be used, which doesn't seem to be the standard, so isn't the default, but which makes sense to me, as it doesn't have a response, which we don't care about anyway
2022-03-24 00:51:00 +00:00
48b42c539c [PLUGINS][WebHooks] Use ActivityPub to serialize the activity, so the object is included 2022-03-24 00:51:00 +00:00
d41a67a9f9 [PLUGIN][WebHooks] Add WebHooks plugin, which allows for sending a POST request to an external resource when a notification or a follow occurs 2022-03-24 00:51:00 +00:00
13f22c911c [COMPONENT][Notification] Feed: Fix typo in query 2022-03-23 16:09:13 +00:00
56b8710b26 [PLUGIN][ActivityPub][Notification] Fix some issues with targetting 2022-03-23 13:23:44 +00:00
e63c310d70 [COMPONENT][Notification] Always pre-add Actor subscribers when notifying 2022-03-23 13:23:44 +00:00
03f449035a [PLUGIN][ActivityPub][Model][Activity] Sometimes we don't have a local, move on with encapsulated 2022-03-23 13:23:44 +00:00
8808195a80 [PLUGIN][ActivityPub][Test] Test @language handling 2022-03-23 13:23:44 +00:00
45344c80d1 [PLUGIN][ActivityPub][Model][Note] Fix @language handling 2022-03-23 13:23:43 +00:00
7eddbd343d [PLUGIN][ActivityPub][Test] Add Like{Note} fixture 2022-03-23 13:23:43 +00:00
259d2da05a [CORE][Controller] Add default handler for when using http methods 2022-03-23 13:23:43 +00:00
2f7fdf6ee4 [PLUGIN][ActivityPub][Test] Activity: Create Page
Fixed a couple of bugs
2022-03-19 22:21:35 +00:00
6955872e05 [PLUGIN][ActivityPub][Model][Activity] toJson: When in activity context, use object's context if available 2022-03-19 22:20:32 +00:00
23e88b30a6 [COMPONENT][Blog] This is not used for replies 2022-03-19 22:18:33 +00:00
60713878f0 [TESTS] Load languages prior to remaining fixtures 2022-03-19 22:18:00 +00:00
06c67b31c2 [PLUGIN][ActivityPub][Model][Note] toJson: Respect source attribute and @language from context 2022-03-19 18:01:25 +00:00
a08b661779 [COMPONENT][Group] Cast integer string to int when getting group from context 2022-03-19 18:01:25 +00:00
0649a5154c [PLUGIN][ActivityPub][Test][Model][Note] fromJson 2022-03-19 18:01:24 +00:00
91fecd77ba [TOOLS][DOCKER] Use a more robust way to check for database availability 2022-03-19 17:20:12 +00:00
e22fe55bbe [TOOLS] Add .well-known/acme-challenge/ root certbot to nginx container, to allow certbot certificate renewals 2022-03-19 07:32:01 +00:00
dd62825169 [PLUGIN][ActivityPub][Model][Note] fromJson: Respect source attribute and @language from context 2022-03-15 17:49:09 +00:00
27706d63f4 [PLUGIN][OAuth] Fix login for OAuth 2022-03-14 21:41:22 +00:00
20f690c532 [TESTS] Fix a couple of issues from last changes 2022-03-14 18:37:39 +00:00
888c3798b7 [COMPONENT][Notification] Make logic more generic and robust
Fixed various bugs

Some important concepts to bear in mind:

* Notification: Associated with activities, won't be reconstructed
together with objects, can be thought of as transient

* Attention: Associated with objects, will be reconstructed with them, can
be thought as persistent

* Notifications and Attentions have no direct implications.

* Mentions are a specific form of attentions in notes, leads to the creation of Attentions.

Finally,

Potential PHP issue detected and reported: https://github.com/php/php-src/issues/8199
`static::method()` from a non static context (such as a class method) calls `__call`, rather than
the expected `__callStatic`. Can be fixed by using `(static fn() => static::method())()`, but the
usage of the magic method is strictly unnecessary in this case.
2022-03-14 11:37:09 +00:00
e1cceac150 [CORE][Form][TESTS] Fix FormTest::handle 2022-03-13 18:53:53 +00:00
63ef9292f3 [DEPENDENCIES] Update dependencies 2022-03-13 18:17:32 +00:00
cbae649991 [PLUGIN][ActivityPub][TESTS] Move ActivityPub test fixtures to new facility 2022-03-13 18:11:11 +00:00
1d8bba3949 [TESTS][MODULES] Move Test Fixtures to tests/fixtures folder and add support for loading fixtures from components and plugins 2022-03-13 18:00:21 +00:00
18864ca9fa [CONTROLLER][Security] Override the _next form field in Security->register to redirect to login page 2022-03-13 16:01:51 +00:00
390c532456 [PLUGIN][ActivityPub][Tests] Create Actor Tests 2022-03-13 16:00:35 +00:00
636cb681d6 [PLUGIN][ActivityPub][Tests] Create a TestCase for the plugin 2022-03-13 15:54:14 +00:00
7d84323df4 [PLUGIN][ActivityPub][Tests] Add some fixtures for GNU social's 2022-03-13 15:53:21 +00:00
2d7850ccfb [PLUGIN][ActivityPub][Tests] Borrow test fixtures from Lemmy 2022-03-13 15:52:48 +00:00
d8108dbc32 [COMPONENT][Posting] Fix request handling issues that resulted from splitting creation and controller 2022-03-13 15:52:48 +00:00
cf05d3dbb0 [ENTITY][TESTS] Fix Note->isVisibleTo with and associated test 2022-03-13 15:03:03 +00:00
eb3c848fc8 [TOOLS][TESTS] Ensure database schema is up to date in tests 2022-03-13 14:22:18 +00:00
5c708af272 [CORE][Form] Remove unweildy return of form errors from Form::handle 2022-03-13 14:19:56 +00:00
8433771465 [TOOLING][TESTS] Allow specifying any phpunit flag when invoking make
Examples:
  make test -- --filter 'method'
  make test -- directory
2022-03-10 01:23:36 +00:00
0ce5eba355 [PLUGINS][Favourite][RepeatNote][DeleteNote][WebMonetization] Make use of 'activitypub_handler' more readable 2022-03-10 00:40:54 +00:00
9a9eed1457 [CORE][Router][Form] Add Router::sanitizeLocalURL and use it in Form::forceRedirect 2022-03-09 20:51:42 +00:00
f540711948 [CORE][GNUsocial] Remove Session parameter, as it's no longer a service. Use session from Request 2022-03-09 20:51:42 +00:00
c870fd44e3 [PLUGIN][Embed] Fix test folder name, so Symfony doesn't attempt to autowire it 2022-03-09 20:51:42 +00:00
c30fcead74 [DEPENDENCIES] Move from Symfony 5.4 to 6 and update all other packages, where applicable 2022-03-09 20:51:42 +00:00
301421ea15 [SECURITY][EVENT] Remove deprecated uses of Symfony Guard. Add LoginSucess and LoginFailure events 2022-03-09 20:51:16 +00:00
4d77f3497d [COMPONENT][Person][TESTS] Fix Controller/PersonSettingsTest 2022-03-09 14:24:50 +00:00
f735e6b31c [TESTS] Fix Util/CommonTest 2022-03-09 14:24:50 +00:00
893d299e29 [UTIL][Common] Respect detect language setting
Minor bug fix
2022-03-09 14:24:50 +00:00
d857baa0f1 [TESTS] Fix Twig/ExtensionTest 2022-03-09 01:43:58 +00:00
0441f030ab [COMPONENT][Group][TESTS] Fix Entity/GroupTest 2022-03-09 01:43:51 +00:00
cac68a6372 [TESTS] Fix Entity/NoteTest 2022-03-09 01:42:11 +00:00
28453c585f [COMPONENT][Attachment][TESTS] Fix Entity/AttachmentThumbnailTest 2022-03-09 01:42:11 +00:00
5c7b079df5 [COMPONENT][Attachment][Controller] Security fix: We were not ensuring that attachment was related to note 2022-03-09 01:42:11 +00:00
47f03d4c9f [COMPONENT][Attachment][TESTS] Fix Entity/AttachmentTest 2022-03-09 01:42:06 +00:00
cc4f967186 [TESTS] Fix Circle SelfTags Setting test 2022-03-09 01:40:35 +00:00
ff06a2656a [COMPONENT][Group][Entity] Useless URI column removed
Add table to Makefile backup
2022-03-09 01:40:34 +00:00
d5fd7da707 [TESTS] Fix Core/RouterTest 2022-03-09 01:40:34 +00:00
1bdeac7076 [TESTS] Fix Core/DB/UpdateListenerTest 2022-03-09 01:40:34 +00:00
e67ed58286 [TESTS] Temporarily Disable Controller/AdminTest: It seems we are repeating values arbitrarily - specially in plugins, and the generated file is just nonsense overall really, wrong sections and stuff 2022-03-09 01:40:34 +00:00
487791d606 [TESTS] Fix Core/ControllerTest 2022-03-09 01:40:33 +00:00
813e66e83e [TESTS] Fix Core/CacheTest 2022-03-09 01:40:33 +00:00
88ace68627 [TESTS] Fix Controller/FeedsTest 2022-03-09 01:40:33 +00:00
416665d830 [COMPONENT][Attachment][TESTS] Fix Controller/AttachmentTest 2022-03-09 01:40:09 +00:00
808a3b219e [TESTS] Specify non-null fields for use of creating actors in tests 2022-03-09 01:37:11 +00:00
df40dd7c66 [TESTS] Add support for loading test suites from plugins and components 2022-03-09 01:37:11 +00:00
afa8443949 [TESTS] Fix some failing tests broken by restructuring and dependency updates 2022-03-09 01:37:11 +00:00
46de2d47e9 [TOOLS] Add explicit return types to fix deprecation warnings raised by PHPUnit 2022-03-09 01:37:10 +00:00
372cf91fbc [TOOLS][TESTS] Split tests into different test suites 2022-03-09 01:37:10 +00:00
9c9e86649a [TESTS] Fix Controller/SecurityTest 2022-03-09 01:37:10 +00:00
a37ce86d05 [TESTS] Fix DataFixtures 2022-03-07 15:26:27 +00:00
9a0c74cb0c [CORE][SECURITY] Replicate 'next' form submission feature on login form 2022-03-07 15:26:27 +00:00
46c91a4b39 [I18N] Fix use of string concatenations in translations 2022-03-07 15:26:26 +00:00
3f14ad0f69 [COMPONENT][Posting][FORM] Refactor Posting form to use a form action with a separate controller and the new Form::forceRedirect 2022-03-07 15:26:26 +00:00
6ddc176faf [CORE][Form] Add facilities for automattically adding a _next field to all forms, which can be customized by the in Form::create and defaults to the current URL. Usage of RedirectedException should mostly be replaced with Form::forceRedirect 2022-03-07 15:26:26 +00:00
d629976322 [UTIL][Notification] Remove deprecated code 2022-03-07 15:26:24 +00:00
1a0c9e720f [COMPONENT][FreeNetwork] Start using queues
[COMPONENT][Notification] Start using queues
[PLUGIN][ActivityPub] Start using queues
2022-03-05 14:23:08 +00:00
6fa5ec3218 [CORE][Queue] Fix some minor issues 2022-03-05 14:22:44 +00:00
626b4263f1 [PLUGIN][ActivityPub][Model][Actor] Fix internal logic for updating
Actors
2022-03-05 14:19:12 +00:00
1daa314c55 [COMPONENT][Posting][FORM] Refactor Posting form to use a form action with a separate controller and the new Form::forceRedirect 2022-03-04 15:16:19 +00:00
7814697f82 [UTIL][EXCEPTION] Forward given status code in RedirectException 2022-03-04 15:15:04 +00:00
7a8d67f1e2 [CORE][Controller] Fix bug where a JSON request could not recieve a redirect response 2022-03-04 15:14:05 +00:00
94449c9153 [CORE][Form] Add facilities for automattically adding a _next field to all forms, which can be customized by the in Form::create and defaults to the current URL. Usage of RedirectedException should mostly be replaced with Form::forceRedirect 2022-03-04 15:12:35 +00:00
7c9b01c516 [UTIL][Common] Add Common::getRequest 2022-03-04 15:09:39 +00:00
6cae6c925d [TOOLS] Keep feed table in delete content Make rule 2022-03-01 18:12:58 +00:00
12fb876a6d [PLUGIN][ActivityPub][Model][Activity] No @context to exclude when object is not embedded. 2022-03-01 18:00:24 +00:00
7ca4330f17 [TEMPLATES] Tweak note complimentary info to not output empty <span>s 2022-03-01 17:58:53 +00:00
802a8d124a [TOOLS] For delete content, restore local_groups and actor_subscriptions 2022-03-01 17:57:39 +00:00
87354c06bf [TEMPLATES] For note complementary info, compare identity by the ID, rather than nickname, which is not unique 2022-03-01 17:39:14 +00:00
5600218924 [TWIG][I18N] Remove unnecessary wrappers for translation functions, use them directly 2022-03-01 17:25:51 +00:00
90f9378bca [TEMPLATES] Use transList and trans function for note complimentary info 2022-03-01 13:46:06 +00:00
070f53c10e [TWIG][I18N] Add transList function, which uses _m_list 2022-03-01 13:46:01 +00:00
f73e9c12ba [CORE][I18n] Add I18n::_m_list, which formats an array of elements into a list. Limited to 5 elements, as that should be enough (tm) and ICU doesn't support this natively 2022-03-01 13:45:40 +00:00
fc203e2e38 [TWIG][TEMPLATES] Rename transchoice to trans and make it more generic 2022-03-01 13:45:11 +00:00
b3374333f3 [TEMPLATES][I18N] Fixup use of trans filter, in favour of trans tags. These are much more flexible and facilitate parameterized translations, rather than using concats. The only appropriate use of the trans filter is when a whole string in a variable needs to be translated (which should probably be avoided anyway) 2022-03-01 13:16:11 +00:00
0b864e85fd [TEMPLATES] Fixup uses of deprecated noteView, in favour of new NoteFactory facility 2022-03-01 11:23:39 +00:00
a9a60bbd92 [COMPONENT][Posting] Clarify use of cache in note replies when posting 2022-03-01 11:19:47 +00:00
4cc4d06b11 [CORE][Cache] Fix bug where empty lists must be stored as a string in Redis (not supported natively), so we can't directly push to it, but the key still exists 2022-03-01 11:07:21 +00:00
f8c1b0f71d [TOOLS] Add Make rule to delete content, but keep actors and sequences 2022-02-28 23:37:16 +00:00
43ae3add43 [TEMPLATE] Update uses of NoteFactory macro, to pass the values seperately, rather than inside a converstation key 2022-02-28 15:48:47 +00:00
d5f90a1206 [ENTITY][Note][CONFIG] Use getListPartialCache for getReplies. Add feeds/cached_replies config entry to control how many replies get cached 2022-02-28 15:47:38 +00:00
85ce6bfd41 [CORE][Cache] Add getListPartialCache, which allows for fetching a list and backing only a portion of it in the cache (useful for feeds and replies to notes, for instance) 2022-02-28 15:47:38 +00:00
46c4bd9099 [COMPONENT][Conversation] Sort conversation correctly 2022-02-28 15:47:38 +00:00
35f3781a32 [COMPONENT][Collection] Add mechanism for specifying the ordering of note and actor queries 2022-02-28 15:47:38 +00:00
45c7888676 [TOOLS] Run CS-Fixer on whole project 2022-02-28 15:47:37 +00:00
255c44bbf0 [ENTITY][LocalUser] Don't use FILTER_SANITIZE_EMAIL, use just want to validate. Up to the user to fix any errors. Use setter, rather than duplicate it's code 2022-02-28 15:47:37 +00:00
5188a473d0 [TOOLS] Fix errors reported by PHPStan 2022-02-28 15:47:37 +00:00
8c15d21591 [TOOLS] Add update-dependencies and update-autocode Make rules 2022-02-28 15:47:37 +00:00
df640f60d2 [DEPENDENCIES] Update dependencies 2022-02-28 15:47:37 +00:00
6e85a4adbb [CONFIG] Change default config to make media files (attachments and their thumbnails) to a subfolder to file, so cleanup scripts can avoid files meant to be persistent (plugin files, certificates) 2022-02-28 15:47:37 +00:00
eccf21edef [TOOLS][PLUGINS][OAuth2] Add mechanism to allow plugins to have an install script. Add script for generating keys for OAuth 2022-02-28 15:47:32 +00:00
9b86794cda [CSS] Details inside another details (accordion widget) will represent their 'open/close feedback arrows' properly now 2022-02-28 13:09:12 +00:00
077975136e [CARDS][Note] Both 'in conversation' and 'in reply to' link to note's conversation. The former anchors it's id, while the latter it's parent id 2022-02-28 12:43:40 +00:00
5495a3c5ec [ENTITY][Note] NoteType now becomes a varchar as predicted 2022-02-27 02:04:48 +00:00
a9b34b75b6 [PLUGIN][TreeNotes] Correct cache issues and iterate functionality
- Replies ordering now correct
- Replies count added
- Posting adds new replies to cache (when concerning replies cache is not empty) and increments replies count
- Configuration to specify number of in-tree replies shown added
- TreeNotes templates was moved from core to plugin
- Button to read more replies was added
2022-02-27 01:46:25 +00:00
2f539d176d [TWIG] Implement transchoice for ICU plural translations 2022-02-27 00:44:23 +00:00
d4c908c194 [CORE][Cache] Implement listPushRight 2022-02-27 00:44:23 +00:00
b630d530f4 [PLUGIN][ActivityPub][Postman] JSON_UNESCAPED_SLASHES
Only record webfinger matches for acct
2022-02-25 13:52:56 +00:00
26a50618b0 [CARDS][Note] Notification targets are now used as target info, instead of previous reply dependant implementation [COMPONENTS][Group] Feed title is applied to GroupFeed view 2022-02-25 13:12:16 +00:00
d5731e6351 [COMPONENT][Notification] Consider attention properly in notes 2022-02-25 13:12:16 +00:00
f5e92de62d [PLUGIN][ActivityPub][Util][Explorer] Simplify fetching Actor by URI 2022-02-25 13:12:14 +00:00
7c80277436 [CSS] Fix header position on >1080p displays 2022-02-24 19:16:41 +00:00
4754593cde [PLUGIN][ActivityPub][Model][Activity] If the object is wrapped in an activity, exclude the @context 2022-02-24 19:07:46 +00:00
d12038a9f8 [CSS] Complete refactor, removing all useless rules, squashing related separate files, and limiting folder depth 2022-02-24 19:05:14 +00:00
af02bc7b32 [PLUGIN][ActivityPub][Model][Note] Replace our directMessage extension with LitePub's 2022-02-23 22:27:32 +00:00
bc3d5245f5 [PLUGIN][ActivityPub][Model][Note] Handle Mentions properly 2022-02-23 22:27:32 +00:00
f3c2e49e3f [PLUGIN][ActivityPub] Correct @context 2022-02-23 22:27:30 +00:00
05b7f2c28b [CORE][DB] Remove doc from deprecated DB::merge and add about DB::refresh 2022-02-23 17:42:20 +00:00
338ea0ea58 [COMPONENTS][Group] Group view template now extends the Collection of Notes view instead of trying to reinvent the wheel [COMPONENTS][Conversation] Replaced deprecated DB::merge with DB::persist 2022-02-23 17:01:41 +00:00
57a07ef74f [COMPONENT][FreeNetwork] Add to Search the query expression 2022-02-21 04:53:12 +00:00
c380cbd846 [COMPONENT][FreeNetwork] Mention and Group tags in notes are handled differently 2022-02-21 04:52:30 +00:00
7678e155d9 [COMPONENT][Search] Ensure title is set when saving as feed 2022-02-21 04:49:08 +00:00
59380ed2ac [COMPONENT][Collection] If invalid term, just perform a regular search for it 2022-02-21 04:48:18 +00:00
1e310aa124 [PLUGIN][ActivityPub][FreeNetwork] DB::findBy won't work if not commited first 2022-02-20 15:01:49 +00:00
José Marques
8b0e9c7890 [UTIL][Form][ActorForms] Fix Full Name validate: Tried to mb_strln on null
If the trim(string) is empty, then store null without further ado
2022-02-20 05:03:55 +00:00
f1caabd296 [CARDS][Note] Note factory template macro created, allows Notes to be represented with completely different macros/blocks, possible to extend types through additional events. Compact Notes have a max height, content can be scrolled by [CSS] Avatars, and Embed attachments now have a max-block-size which acts independently of image orientation 2022-02-20 05:03:54 +00:00
a71c16d654 [COMPONENTS][Posting] Fixed issue where an embed attachment would violate Note's conversation_id not null constraint
Conversation was only assigned after storing Note's attachments
2022-02-20 05:03:41 +00:00
ecfd6b5ad2 [PLUGIN][ActivityPub][Model][Note] Sometimes content is explicitely null 2022-02-20 05:03:40 +00:00
496701ce73 [PLUGIN][ActivityPub][Inbox] Add event for notifications triggered by AP Inbox 2022-02-20 05:03:40 +00:00
e86dbad6d6 [COMPONENT][Notification] Don't explode with understandable duplicate notifications
This is common when a duplicate federation request is received
2022-02-20 05:03:40 +00:00
6f3e760c63 [PLUGIN][ActivityPub][Inbox] Separate handler by method 2022-02-20 05:03:40 +00:00
51cccd0155 [PLUGIN][ActivityPub] Simplify DB usage 2022-02-20 05:03:40 +00:00
9523927b8e [PLUGIN][ActivityPub][Model][Note] There may be no attachments, nor tags, nor to, nor cc 2022-02-19 05:46:48 +00:00
ebbd8bf1e4 [PLUGIN][ActivityPub][HTTPSignatures] Fix wrong assumption that sha512 is used in hs2019 2022-02-19 04:49:50 +00:00
7a59d5a002 [PLUGIN][ActivityPub][HTTPSignatures] Validate draft-cavage-http-signatures-11 2022-02-19 04:49:50 +00:00
52ae5fa690 [PLUGIN][ActivityPub][Inbox] Improve logs 2022-02-19 04:49:50 +00:00
99f7e7cd79 [PLUGIN][ActivityPub][Model][Note] Handle group scope properly 2022-02-19 04:49:50 +00:00
27635d8ec2 [PLUGIN][ActivityPub][Model][Note] Add name property as note title 2022-02-19 04:49:49 +00:00
0a741903a1 [PLUGIN][ActivityPub][Model][Note] Federate content out 2022-02-19 04:49:49 +00:00
8f60fc4685 [PLUGIN][ActivityPub][Model][Note] Federate attentions out 2022-02-19 04:49:49 +00:00
8cf60275e6 [PLUGIN][ActivityPub][Model][Note] Add support to Pages 2022-02-19 04:49:49 +00:00
75837af412 [CSS] Replacing problematic special Unicode glyphs
A browser will use Unicode glyphs from other font families if the glyph in question is not present for the current typeface. This leads to unnerving situations, whereby setting content through pseudo-selectors will cause text to misalign. And no, line-height won't make a difference in this case. This happens because fonts have different heights. Another reason may reside on CSS3 having pseudo selectors but not really having a proper spec for them to begin with.
2022-02-19 04:01:47 +00:00
03a475b642 [TWIG] Form layout is all new, since extending form_div_layout.html.twig was quite limiting
[COMPONENTS][Posting] It is now visible on Actor profiles [COMPONENTS][Search] Overall rework of search results template, there's also additional help text added [CSS] Header no longer translucent, font sizes yet more consistent, replies marker less pronounced, and font hierarchy is now applied in both weight and size
2022-02-19 04:01:47 +00:00
b69f4a46c5 [COMPONENT][Posting] Page should flush with a different notification 2022-02-16 19:35:27 +00:00
b6ed0b4c6c [CORE][Actor] Fix generic profile route 2022-02-16 18:53:08 +00:00
cee2d143c9 [COMPONENT][Posting] Add storeLocalPage
Minor refactoring and bug fixing
2022-02-16 18:53:08 +00:00
2d5fac7a89 [COMPONENT][Notification] Re-introduce the concept of note attention
Minor refactoring and bug fixing
2022-02-16 18:53:08 +00:00
e70acd5c3b [UTIL][HTML] HTML abstraction class was extended with a more specialised Heading class
This little abstraction layer made it a bit easier to add a different title to a Note or Actor Feed Collection template, from whichever controller that uses it. Please, bear in mind, that abstract templates such as those found in Components\Collection, may act in a very 'declarative' way upon using them. This makes it difficult to dynamically choose what type of header is used without undergoing a mining operation in the likes of a pyramid of doom. Hence, this _little_ change.
2022-02-16 18:53:08 +00:00
f66e178dfc [CORE][Actor][Settings] Fix nickname change and refactor Core Form::handle so it's harder to repeat these mistakes again
Minor improvements on actor->getLocal
2022-02-16 18:53:07 +00:00
397b54a207 [PLUGIN][Bundles] Refactor BlogCollections to Bundles 2022-02-16 18:53:07 +00:00
33e1d3eb20 [COMPONENT][Conversation] Use Router::url's _fragment for anchor 2022-02-16 18:53:07 +00:00
54b9ec48b4 [COMPONENT][Collection][FeedController] Fix group scope, we should use the IN context actor to check the group 2022-02-16 18:53:07 +00:00
40590bbd11 [COMPONENT][Group] Restore settings functionality 2022-02-16 18:53:07 +00:00
5b94973079 [COMPONENTS][Posting] Form is no longer added to RightPanel if not on a feed|conversation|groups route 2022-02-16 18:53:07 +00:00
9d9abf8afb [CARDS][Note] Removed incorrect aria attributes, polished Note card 2022-02-16 18:53:06 +00:00
be0a2d27e2 [COMPONENT][Blog] Initial support for in group blogs 2022-02-16 18:53:06 +00:00
bf23ae2dcf [ENTITY][Note] Some notes aren't exactly just a note but rather a Page, or further (like happening or poll), this is only initial support for that
It prolly will become a varchar instead of an enum, so plugins can add their own note types
2022-02-16 18:53:06 +00:00
33e768c298 [COMPONENT][Group][Controller] Separate feed from other group features 2022-02-15 17:13:16 +00:00
3f9c86f0df [COMPONENT][Group] More flexible member roles than only isAdmin
Refactor terminology of canAdmin to match current roles system
2022-02-14 05:02:10 +00:00
bc63c3727a [COMPONENT][GROUP] Allow to create a group as private and prioritise group scope on Posting in that context 2022-02-14 05:02:09 +00:00
090a087832 [COMPONENT][Group] Check nickname on register 2022-02-14 01:21:40 +00:00
262b14a977 [COMPONENT][Collection] Organisation no longer is an actor type but rather a type of Actor Group 2022-02-14 00:41:57 +00:00
10d1a7ed2a [PLUGIN][ActivityPub] Implement Group Inbox POST 2022-02-13 23:15:00 +00:00
3ae8f8213f [GROUP] Notifity group subscribers of new activity concerning the group 2022-02-13 23:15:00 +00:00
66323c5a73 [PLUGIN][ActivityPub] Fix several issues with target and notifications inserted by AP 2022-02-13 23:14:59 +00:00
56c884026f [COMPONENT][Notification] We must record remote notifications because of feeds 2022-02-13 23:14:59 +00:00
62bf788b90 [CORE][Note] Implement private group scope properly 2022-02-13 23:14:59 +00:00
6500e99b69 [COMPONENT][Posting] Respect context actor concerning visibility and In sorting 2022-02-13 23:14:58 +00:00
cda1568db5 [TEMPLATES][Cards][Blocks] Provide both actor uri and url, as well as full mention guidance 2022-02-13 23:14:58 +00:00
1f2638d15a [ROUTES] Remove ActorCircle holdover route 2022-02-11 15:31:47 +00:00
6b4fa8c303 [COMPONENT][Notification] Additional check to avoid unnecessary notifications 2022-02-11 15:31:47 +00:00
17733f32d6 [PLUGIN][ActivityPub] Implement Group Outbox
Fix various minor issues
2022-02-11 10:06:01 +00:00
fb3e900b28 [CORE] Add CONFIG_ prefix to environment whitelist
Fixed minor issues with Commong:config of env not being included and ported to local social yaml

Fixed some regressions introduced with [CORE] Unset sensitive information from the environment
2022-02-11 10:05:58 +00:00
416451a519 [CORE][Actor] Simplify logic so more is reused between different types of actors
Minor bug fixes
2022-02-11 00:27:03 +00:00
1f1524c2b3 [GROUP] Simplify logic by making Actor::Organisation a type of Actor::Group
Some minor bug fixes
2022-02-11 00:26:43 +00:00
35e907f7b2 [CARDS][Note] Note's 'in reply to' information added, overall polish of feeds templates and proper titles added for every single section that makes up a note 2022-02-09 18:49:34 +00:00
79bb258ba6 [CSS] Further dialing of sizes and media queries for a better mobile UX 2022-02-08 17:14:28 +00:00
80dfea6812 [CARDS][Note] Note's actions are now inside the same div as Note's complementary info, overall footprint of replies diminished 2022-02-08 17:01:58 +00:00
f6b19d2a0f [CARDS][Note] Note's actions are now inside the same div as Note's complementary info, overall footprint of replies diminished 2022-02-08 16:13:46 +00:00
67a2387b31 [CARDS][Note] Removed note's complementary info related to the current user everywhere, which was criticized from being redundant 2022-02-08 15:19:33 +00:00
5d0b8930e1 [COMPONENTS][Conversation] Removed redundant complementary information from notes replied to 2022-02-08 14:43:39 +00:00
22741702bf [CSS] Replaced .section-details-subtitle summary, .section-details-title summary outline to a border, since Firefox ESR doesn't apply border-radius to outline 2022-02-08 14:22:52 +00:00
ba131bdb16 [CSS] Background noise is back, default_theme directory hierarchy simplified
[PLUGINS][Oomox] Fixed issue where resetting colours when no entity was present would lead to an error (it expected an entity, but NULL was given)
2022-02-08 14:12:59 +00:00
7b0667109d [CARDS][Note] Note actions are now displayed at the end
Due to space constraint on mobile screens, prior actions placement proved to be a problem. Additionally, note replies are now separated from their parent, allowing more horizontal space to be used if necessary/more reply depth to be presented in a reasonable fashion.
2022-02-08 01:26:25 +00:00
5cd3bc3206 [CSS] Touch devices are now able to scroll horizontally on note author information 2022-02-08 00:30:15 +00:00
79d022e850 [CSS] Fixing note attachments padding, height and allowing their wrap when limited space is available 2022-02-08 00:18:24 +00:00
cb393ca554 [CARDS][Note] Fix note replies from calling note macro as if it was still part of the same template 2022-02-08 00:05:51 +00:00
99593a19ef [CSS] Default theme polish work, more consistent font sizes and improved dark theme colors 2022-02-07 23:54:29 +00:00
9a53f94b77 [TWIG] Replaced getRightPanelBlocks with addRightPanelBlock, provides more control on block placement
[COMPONENTS][RightPanel] Refactored template, improved clarity, and added Posting form related macros

[PLUGINS][NoteTypeFeedFilter] Removed icons from template, added them through CSS to further improve performance
2022-02-07 20:29:14 +00:00
d6666cf209 [CSS] Aligned details marker arrows 2022-02-07 02:46:08 +00:00
b3d582f665 [PLUGINS][AttachmentCollections] Fixed "Error: Expected Doctrine\ORM\Query\Lexer::T_IDENTIFIER, got 'Plugin\AttachmentCollections\Entity\AttachmentCollection'"
[TWIG] Cards are now divided into blocks and macros, additional macros done, attachments page no longer inside cards directory
[CARDS][Navigation] Now using macros to create section, details, and nav elements
2022-02-07 01:54:04 +00:00
2b9f70f89f [PLUGINS][BlogCollections] Entities and base plugin and controller done 2022-02-07 01:52:35 +00:00
e0ceddc2e6 [CSS] Replaced fooobar:not([foo=bar], [foo2=bar2]) rule, as Firefox ESR 78.x doesn't support that specific syntax 2022-02-04 21:12:22 +00:00
81f6d496c6 [PLUGIN][OAuth2] Fix some static issues 2022-02-04 19:56:17 +00:00
4dd976eb22 [ENTITY][Note] Added function getRenderedSplit, return an array of paragraphs/line breaks
[PLUGINS][Favourite] Foreign keys now properly defined on schema

[CARDS][Note] Note text is now hidden by default if too many paragraphs/line breaks are present, BlogCollection plugin will certainly need this feature
2022-02-04 16:07:24 +00:00
Bruno Aleixo
fb76775716 [TOOLS][COMPONENTS][CORE] Ran cs-fixer on all files 2022-01-30 16:41:54 +00:00
Bruno Aleixo
162b01e2c5 [CORE] Unset sensitive information from the environment 2022-01-30 16:39:43 +00:00
afd1211852 [CSS] Using accent-color rule to stylize checkbox 2022-01-28 23:15:01 +00:00
8f8070036c [CSS] Eliminated repeated rules, improved icon alignment, and removed checkbox and radio custom styling
Browser specific quirks made it impossible to stylize checkbox and radio buttons. High DPI, custom default font sizes and/or custom GTK themes make it very difficult to keep it consistent.
2022-01-28 18:21:04 +00:00
2e6f91f34e [FORM][ActorForms] Fullname length is now validated prior to being set 2022-01-27 17:53:02 +00:00
5036b72a71 [ENTITY][Actor] Nickname is lower case transformed when generating 'actor_view_nickname', making sure that actor pages are linked accordingly 2022-01-27 17:19:50 +00:00
a17a514bfd [CONTROLLER][Security] Further sanity checks and validation done on email entry 2022-01-27 17:08:20 +00:00
1576d253a5 [CONTROLLER][UserPanel] Email is now sanitized and validated before calling corresponding setter 2022-01-27 16:59:43 +00:00
64a698d255 [COMPONENTS][Search] Polished search template for a clearer header hierarchy 2022-01-27 02:17:41 +00:00
ab6dabf4f7 [CSS] Fix issue where panels wouldn't scroll independantly 2022-01-27 01:53:30 +00:00
222e1fbb2b [PLUGINS][AttachmentShowRelated] Replacing h2 with span, its supposed to be complementary content, not main 2022-01-27 01:13:18 +00:00
117549bf1e [PLUGINS][Favourite] Remove favourite action properly removes note_favourite Entity now [COMPONENTS][Collection] Simplyfying feed-action-details template section
[COMPONENTS] Documentation work [PLUGINS] Documentation work
2022-01-27 00:54:27 +00:00
adf484f58a [COMPONENTS][Posting] No error to ignore was reported on line 161, removed ignore
[PLUGINS][Directory] Further documentation work

[CORE][Controller] Separating workflows, setting proper return types

[TWIG][Security] Removing unused stylesheet calls
2022-01-26 20:54:55 +00:00
16e7d6cff7 [COMPONENTS] Documenting methods with high cognitive complexity, specifically in Group and Posting components
[PLUGINS][Directory] Updating docs, @params weren't set correctly
2022-01-26 20:01:37 +00:00
6a5312aca9 [CORE][GNUsocial] social.local.yaml is now updated with the proper node name 2022-01-26 18:46:31 +00:00
14bb1b2876 [COMPONENTS][Conversation] Note being replied to now appears before Posting's own form, RightPanel is also open by default on smaller screens when the current route is 'conversation_reply_to' 2022-01-25 19:18:42 +00:00
c7c5fe7979 [PLUGIN][OAuth2] Add 'me' field to token responses 2022-01-25 16:07:39 +00:00
fa0d02a9ac [PLUGIN][OAuth2] Start adding OAuth2 support with client registration
This hardcodes the user, and has some other issues, so it is not yet
complete.

We follow mastodon's spec for automatic client registration, available
at both `/api/v1/apps` and a more reasonable `/oauth/client`. This
accepts a JSON POST with the client info and returns JSON with a
`client_id` and a `client_secret`, to be used with `/oauth/authorize`
and `/oauth/token`. It also, seemingly, requires returning an `id`
with unclear purpose.

The `/oauth/token` endpoint doesn't currently return a `me` field.
2022-01-25 13:35:44 +00:00
4736146b80 [TOOLS] Update autocode, allow for abstract entity classes, derive namespace from file rather than using 'get_declared_classes' 2022-01-25 13:35:44 +00:00
e3bfb1ebc5 [CSS] .note-info text will automatically crop when no space is available, on hover will show contents 2022-01-25 00:02:38 +00:00
ee04571f4d [TWIG] Various fixes related to header elements hierarchy
Widgets shouldn't have a header element from here forward, since their location varies
2022-01-23 19:46:47 +00:00
bf07fa1ade [COMPONENTS][Collection] Added PrependActorsCollection event [COMPONENTS][Group] Added getGroupCreateForm, used in PrependActorsCollection event to build create a new Group form view
[COMPONENTS][LeftPanel] Removed onEndShowStyles event since the corresponding CSS needed is now consolidated into the default_theme itself [COMPONENTS][RightPanel] Deleted components/RightPanel/RightPanel.php, since its only method (onEndShowStyles) wasn't needed anymore
2022-01-23 19:07:39 +00:00
e4a3438d55 [CORE][I18n] Fixing 'file_get_contents(): Argument #1 () must be of type string, Symfony\Component\Finder\SplFileInfo given' error by using Symfony's Finder to iterate through existing files 2022-01-23 19:07:39 +00:00
6b1c6f603e [CORE][ActorLocalRoles] Improve Roles 2022-01-22 18:47:56 +00:00
5f243f68be [DEPENDENCIES] Add symfony/psr-http-message-bridge 2022-01-21 22:05:34 +00:00
68c3204e71 [DEPENDENCIES] Update dependencies 2022-01-21 22:05:34 +00:00
559f6d650b [COMPONENT][Language] Fix template name in language sorting 2022-01-21 22:05:34 +00:00
3d9edd1db8 [COMPONENTS][LeftPanel] Edit feeds page polish, existing links are shown in a grid, saving space
[COMPONENTS][Collection] Fixing details summary class [PLUGINS][WebMonetization] Fixing widget details summary class
2022-01-21 22:05:34 +00:00
402300fe93 [COMPONENTS][Search] Fixing Search form incorrect class names 2022-01-21 22:05:34 +00:00
e2e1b0172d [COMPONENTS][Collection] Actors view template ordering section polished [PLUGINS][RepeatNote] Note to be repeated now uses full note card
[CSS] Simplyfying rules, re-ordering and removing unnecessary and costly 'display: flex' rules
[CARDS][Note] Minimal note macro has info inside the note itself now, since horizontal space is limited
2022-01-21 22:05:33 +00:00
f731850f5c [CSS] .section-widget class and derivatives replaced as .frame-section, since a widget implies a simple element with a specific function 2022-01-21 22:05:33 +00:00
7d546e8901 [CSS] Improved performance, reduced padding [COMPONENTS][LeftPanel] Consolidated CSS into base.css [COMPONENTS][RightPanel] Consolidated CSS into base.css [PLUGINS][WebMonetization] Replaced fieldset with section
Accessibility tests failed if the fieldset had no legend, since it
wasn't really neeeded, it was replaced as another element.
2022-01-21 22:05:33 +00:00
bdeb3bcff5 [PLUGIN][ActivityPub] Federate Actor of types other than Person
Fix some other minor bugs
2022-01-21 22:05:31 +00:00
25b2847201 [TOOLS][AYY1] Improve accesibility testing to save images and compare the differences against a reference (tests/screenshots/ 2022-01-21 21:03:09 +00:00
23d45ffab7 [UTIL][Formatting] Mention prefix was hardcoded, fixed. 2022-01-21 21:03:09 +00:00
b253ce5e70 [DOCS][Design] Add guidelines menu entry 2022-01-21 21:03:09 +00:00
c4f9e58e8d [COMPONENTS][Attachment] Fixed typo on attachmentShowWithNote, where the template called was somehow replaced with a child of it 2022-01-21 21:03:09 +00:00
6ab740d780 [COMPONENT][Search][UI] Fix template, which included the search builder form inside the search form, chaos ensuing 2022-01-21 21:03:09 +00:00
de795b78f9 [DOCKER][DEPENDENCIES] Restructure PHP Dockerfile to install each package in separate layers and add WikiMedia texvc 2022-01-21 21:03:09 +00:00
29d498770c [COMPONENTS][Group] Create a group route added, template polished
[COMPONENTS][Circle] Removed any Group related route from shouldAddToRightPanel event
[CARDS][Profile] Block should now allow inline long nicknames to not
break
2022-01-21 21:03:09 +00:00
d7039b1c5c [COMPONENTS][Group] Create a group route added, template polished
[COMPONENTS][Circle] Removed any Group related route from shouldAddToRightPanel event
[CARDS][Profile] Block should now allow inline long nicknames to not
break
2022-01-21 21:03:08 +00:00
1856af68b3 [PLUGIN][RepeatNote][COMPONENTS][Posting] Review and fix RepeatNote. Handle attachment lives in Posting 2022-01-21 21:03:08 +00:00
9bd1f42843 [TOOLS] Use sudo to remove files 2022-01-21 21:03:08 +00:00
145c88d43f [ENTITY][Note] Only attempt to find mentions if we have content 2022-01-21 21:03:08 +00:00
4717dde12e [TWIG][I18N] Improve base template facilitate translations of the accessibility panel text 2022-01-21 21:03:08 +00:00
c028a601a5 [COMPONENTS][Group] Create a group route added, template polished
[COMPONENTS][Circle] Removed any Group related route from shouldAddToRightPanel event
[CARDS][Profile] Block should now allow inline long nicknames to not
break
2022-01-21 21:03:08 +00:00
692ecf1c99 [TWIG] Improved templates HTML structure, removed unnecessary element nesting, and refactored content sectioning
[COMPONENTS][Search] Refactored widget event as 'PrependRightPanel' (making it able to accomodate more generic blocks)
2022-01-21 21:03:08 +00:00
242fe3fd6e [PLUGINS][PinnedNotes] Replacing arbitary size values with common variables 2022-01-21 21:03:08 +00:00
dbdf1d9b0b [CSS] Fixed footer responsiveness, since its content wouldn't wrap up from insuficient space for all of its content 2022-01-21 21:03:08 +00:00
7daa61500d [COMPONENTS][Collection] Notes collection template now has a default title
[CARDS][Note] Removed note actions from minimal note block
2022-01-21 21:03:07 +00:00
077cbcf424 [TWIG] Improved accessibility menu
[COMPONENTS][RightPanel] Content form row is now preceeded by the content type form row
2022-01-21 21:03:07 +00:00
04431885aa [PLUGIN][PinnedNotes] Fix ActivityPub config 2022-01-21 21:03:07 +00:00
b8a35f9d6d [PLUGIN][WebMonetization] Fix ActivityPub config 2022-01-21 21:03:07 +00:00
184d0246a5 [COMPONENTS][RightPanel] AppendRightPanelBlock event refactored,
replaced with src/Twig/Rintime::getRightPanelBlocks
[COMPONENTS] Re-ordered onAppendRightPanelBlock event calls arguments for improved consistency across events
2022-01-21 21:03:07 +00:00
da7ae5e1f5 [TESTS][A11Y] Login and check all user pages for accessibility 2022-01-21 21:03:07 +00:00
9e4aed84f8 [PLUGIN][LatexNotes] add LaTeX support for notes 2022-01-21 21:03:07 +00:00
db42ade2b6 [PLUGIN][MarkdownNotes] add markdown support for notes 2022-01-21 21:03:07 +00:00
06d11d8337 [PLUGINS[WebMonetization] Basic activityPub support 2022-01-21 21:03:07 +00:00
148dd6db50 [PLUGINS][PinnedNotes] Basic activityPub support 2022-01-21 21:03:06 +00:00
21c7912702 [PLUGIN][Pinned Notes] Allow user to pin his notes 2022-01-21 21:03:06 +00:00
f7cbfbff8c [COMPONENT][Collection] Add event to render html before drawing feed 2022-01-21 21:03:06 +00:00
3f0d996dc9 [COMPONENT][Tag] Fix event handling 2022-01-21 21:03:06 +00:00
9e891ed020 [TOOLS][PHPStan] Ignore errors due to lack of namespace in CodeCeption acceptance tester 2022-01-21 21:03:06 +00:00
6c6c0270c5 [TESTS][CodeCeption] Update acceptance tests to remove accesibility test kludge 2022-01-21 21:03:06 +00:00
a59997b41f [TOOLS][DOCKER][A11Y] Switch to Pa11y CI and don't run it 'integrated' with codeception, since there was no good way to share cookies 2022-01-21 21:03:06 +00:00
d542be1df4 [ACCESSIBILITY] Fix regressions in panel checkboxes and accessibility menu accesskeys
Accessibility menu accesskey regressions introduced with [ACCESSIBILITY][BASE] Accessibility menu was unreachable.
2022-01-13 19:47:41 +00:00
eff9318c1d [UTIL][Formatting] Mention title is not always defined 2022-01-13 18:07:19 +00:00
fa9df9962e [COMPONENTS][Conversation] Route 'conversation_mute' now has the
corresponding conversation view embedded, user is also redirected
properly
[PLUGINS][Favourite] Fixed typo
2022-01-13 17:47:47 +00:00
859bf0c0bf [CONTROLLER][UserPanel] Notification settings panel debug information added for future reference 2022-01-13 16:24:23 +00:00
d29e28b829 [CORE][Entity] Replaced get_called_class() calls with static::class since the former is deprecated 2022-01-13 16:24:12 +00:00
14b03c7137 [UI][UserPanel] Do not try to re-set an unchanged nickname 2022-01-12 17:46:13 +00:00
480f570238 [CORE][SECURITY][HTML] Refactor Security::sanitize to HTML::sanitize
Update composer dependencies, move more general deps from ActivityPub to Core
2022-01-12 17:12:58 +00:00
968b1751fd [CSS] Further styling optimizations, compacting common rules. Removed select dropdown images, since they are no longer required 2022-01-12 16:42:33 +00:00
c8daa82c1d [TWIG] Replaced base.css @import with HTML link imports
According to 'High Performance Web Sites' (ISBN 10: 0596529309), @import has a negative impact on web page performance. Since all imports dependant on base.css will only start downloading after that one is fully gathered.
2022-01-12 16:32:47 +00:00
600a1511cb [TWIG] Removed all instances were 'arrow-down' icon was called in twig templates, replaced it's intended feedback by using :after pseudo-selector within CSS
In user panel render time reduced dramatically, by ~70ms. Said icon was requested by twig >10 times.
2022-01-12 16:07:28 +00:00
59b8bdf99b [PLUGIN][ActivityPub] Provide ActivityStreams 2.0 responses for every Collection
Implemented ActivityPub Outbox
2022-01-11 20:30:25 +00:00
f3a7e8f04d [TOOLS] Remove CodeCeption files from composer autoload-dev, since they're not available before installing, and ignore errors in PHPStan 2022-01-10 23:14:43 +00:00
65504b72bb [TOOLS] Setup pa11y to run on pages after codeception 2022-01-10 23:09:39 +00:00
José Marques
d713429d88 [CORE][Nickname] Properly set nickname for existing accounts 2022-01-10 11:33:13 +00:00
1056bc661f [COMPONENT][FreeNetwork] Restore Galaxy feed 2022-01-10 10:29:55 +00:00
f40eb3955f [TOOLS] Update makefile to add an acceptance and accesibility testing target 2022-01-09 14:45:33 +00:00
b2b445d21e [TOOLS][DOCKER] Add pa11y and nginx container to tooling toolchain 2022-01-09 14:44:56 +00:00
528f6df240 [TOOLS][PHPStan] Ignore autogenerated Codeception classes 2022-01-09 14:44:08 +00:00
894c78bf99 [TOOLS] Keep git in docker image, since we use patched composer packages, temporarily 2022-01-09 14:43:33 +00:00
38baa192d8 [DEPENDENCIES][TOOL] Add codeception and bootstrap it for acceptance and accessibility testing 2022-01-09 14:42:16 +00:00
a697399a6f [PLUGIN][DeleteNote][Favourite][ProfileColor][RepeatNote][WebMonetization] Refactor, cleanup and cache results 2022-01-09 12:29:34 +00:00
cdf1d67d0f [CORE][Cache] Workaround to redis not allowing empty lists 2022-01-09 12:29:34 +00:00
06ece5b72e [COMPONENT][Collection] Only run queries if the criteria is not empty 2022-01-09 12:29:34 +00:00
da6d3bd351 [COMPONENT][Collection] Use current locale 2022-01-09 12:29:34 +00:00
c835fc6aca [COMPONENT][Collection][Feed][Attachment][Feed][Language][Tag] Refactor and consolidate Search and Feed query mechanisms into Collection. Remame 'onSearch' events to 'onCollectionQuery' 2022-01-09 12:29:34 +00:00
57604b3851 [PLUGIN][NoteTypeFilter] Always show filtering options 2022-01-09 12:29:34 +00:00
b1abd81aca [DEPENDENCIES] Update dependencies 2022-01-08 00:11:12 +00:00
5cfed3d536 [TWIG] Display errors in templates that display forms with form_start 2022-01-08 00:11:08 +00:00
0758d6145b [COMPONENT][Collection][CONTROLLER][Collection] Use null-safe calls to attempt to get a language 2022-01-08 00:07:32 +00:00
d17f276419 [COMPONENTS][Conversation] Added missing foreign keys to ConversationMute Entity
Fixed 'is_muted' variable check logic that impeded the Conversation from being muted
2022-01-07 21:14:51 +00:00
fc57b3290e [COMPONENTS][Search] Polished results page HTML view 2022-01-07 21:14:51 +00:00
1438433859 [PLUGINS][NoteTypeFeedFilter] Polish feed actions HTML, adding proper anchor titles and better user feedback when a filter in applied
[COMPONENTS][Collection] Notes feed template HTML polish to accomodate changes needed for NoteTypeFeedFilter
2022-01-07 21:14:51 +00:00
cb1dc4c10f [PLUGIN][WebMonetization] Adding Web Monetization plugin which allows for donations using the Web Monetizations protocol 2022-01-07 14:55:35 -03:00
9cf8970603 [TEMPLATES][Base] AppendToHead event added to base template 2022-01-07 14:53:55 -03:00
c3d58c350e [COMPONENTS][Collections] Iterating documentation 2022-01-07 09:23:37 -03:00
e056920de4 [COMPONENT][Subscription] Fix Notifications 2022-01-06 12:13:11 +00:00
0c245fcb6e [COMPONENTS][Subscription] Subscribe Actor action implemented
[TWIG] AddProfileAction event added
[CARDS][Profile] Refactor and restyling to accomodate Actor actions
2022-01-06 12:13:10 +00:00
0d1ab2c9cf [SECURITY][Register] New users should have their current browser language set as first language preference 2022-01-05 04:19:35 +00:00
3f8fab0021 [PLUGIN][Favourite] Fix routes 2022-01-05 04:19:35 +00:00
cd6ce3542e [COMPONENT][Circle] Move circles to a component, various bug fixes
Mention links are now correct
2022-01-05 04:19:22 +00:00
627d92b290 [COMPONENT][Tag] Improve Note Tag Handling and start extracting Circles logic out of the plugin, various bug fixes 2022-01-05 01:30:02 +00:00
ee007befa4 [COMPONENT][Posting] DB::Flush after Notification and fix minor issues with In targets 2022-01-05 01:30:01 +00:00
9df9c6a19c [COMPONENT][Collection] Make MetaCollectionPlugin a trait and abstract collection delete and name update 2022-01-05 01:30:00 +00:00
754135743e [COMPONENT][Subscription] Move respective routes to component 2022-01-05 01:29:27 +00:00
5a0bbfc795 [UTIL][Common][I18N] Use actor's preferred language for _m and utility to retrieve current language even when no actor is logged in 2022-01-05 01:29:26 +00:00
6247dd4c1a [COMPONENT][RightPanel] Display form errors 2022-01-04 18:58:32 +00:00
de8eab2cf8 [CORE][FORM][FormTypeNonceExtension] Add a nonce to all forms with a CSRF token 2022-01-04 18:58:32 +00:00
b7e4f79ccc [CORE][Cache] Add Cache::incr which increments a value at , atomically, in the case of Redis 2022-01-04 18:58:32 +00:00
a5b5362be2 [DOCS][Designer] General guidelines for styling initiated
Added wireframes of default page, dividing page into 4 distinct general
areas.

Added CSS classes reference table.
2022-01-04 00:02:21 +00:00
d444ea7963 [COMPONENT][Conversation] Refactor and fix Conversation component 2022-01-03 21:20:27 +00:00
a729a8eddb [COMPONENT][Collection] Pass current actor and their top language to query, if not otherwise specified 2022-01-03 21:20:26 +00:00
a8a8cc4046 [COMPONENT][Posting] Plumb in reply_to and redirecto to GET from 2022-01-03 21:20:26 +00:00
7d38c927e1 [ENTITY][Note][CACHE] Consolidate cache keys to helper function and add ensureCanInteract 2022-01-03 21:20:26 +00:00
135bf8bc68 [COMPONENTS][Conversation] Documented respective Controller
[PLUGINS][TreeNotes] Documentation added, feedFormatTree explained in detail
2022-01-03 19:26:17 +00:00
5a31258190 [COMPONENTS][Conversation] Further documentation work, expected arguments explained in more detail 2022-01-03 19:26:17 +00:00
f5fc7b6cd1 [CORE][Controller] Add facility for either returning null or throwing, from Controller->{int,string,bool} 2022-01-03 18:02:33 +00:00
141c5f6785 [COMPONENT][Collection][CONTROLLER][Collection] Add utility method to call Feed::query in Collection, which handles getting and passing the page 2022-01-03 18:02:33 +00:00
07b65584ff [COMPONENTS][Posting] Replaced is_int() with is_numeric(), casting target to int when using Actor getter
From php-stan: is int with string evaluates to false

Thus, the change was made in order to behave as intended
2022-01-03 17:58:48 +00:00
4ae160b0f8 [PLUGINS][AttachmentShowRelated] Fix onEndShowStyles condition to add proper stylesheet to array 2022-01-03 17:51:19 +00:00
a622b175bc [DEPENDENCIES] Update dependencies 2022-01-03 16:38:51 +00:00
9ea230d12b [COMPONENT][Subscription] Implement subscription handlers 2022-01-03 02:23:06 +00:00
fe087b2217 [PLUGIN][ActivityPub] Accept Undo Follow 2022-01-03 02:23:06 +00:00
a9ea49d34c [TOOLS][DOC] Add documentation to functions flagged by doc-check 2022-01-03 02:23:06 +00:00
9e0a2dd4a0 [TOOLS] Fix errors found by PHPStan 2022-01-03 02:23:06 +00:00
8fa04bb47d [EVENT][AddFeedActions] Add bool param which denotes whether the feed is empty. [PLUGIN][NoteTypeFeedFilter] Don't show filters if the feed is empty 2022-01-03 02:23:06 +00:00
d5a6fa924b [COMPONENT][Conversation][ENTITY][ConversationMute] Rename Conversation{Block,Mute} 2022-01-03 02:23:05 +00:00
ba0b0629b7 [TOOLS] Fix deprecations in php-doc-check by providing my own implementation 2022-01-03 02:18:44 +00:00
27276ba379 [CONFIG] Rename streams/notes_per_page to feeds/entries_per_page 2022-01-03 02:18:44 +00:00
ea5a4df1a4 [UI][PLUGIN][Directory] Add UI for Directory listing ordering. This uses a GET parameter, which subits the field in order_by and the operator in order_op. Using order_by=filed^ is still supported 2022-01-03 02:18:44 +00:00
6cfb69d64b [COMPONENT][Subscription] Start component 2022-01-03 02:18:44 +00:00
5fa8056899 [COMPONENT][Collection] Refactoring: Further work in abstracting collections 2022-01-03 02:18:43 +00:00
def5f36c25 [PLUGIN][ActivityPub][Inbox] Accept Follow Activity
Improve how Core Activity is handled in general
2022-01-02 23:50:16 +00:00
afb7ae0f75 [CORE][Util][Exception] Add log context to BugFoundException 2022-01-02 23:50:15 +00:00
064288e33b [CSS] Applying correct border-radius to note-info and adding a greater margin on note-complementary, in case its the last of type 2022-01-02 23:50:15 +00:00
c7ea56d571 [CSS] Fixing checkboxes from displaying background cropped 2022-01-02 23:50:15 +00:00
17b46b9aeb [CSS] Fixed radio button, it didnt change its own background upon being checked, making it invisible 2022-01-02 23:50:15 +00:00
28424402ec [CONTROLLER][CollectionController] Refactored methods names and form
titles
[PLUGINS][AttachmentCollections] Renamed respective Controller to
differentiate it between The Controller and itself, renamed templates
and removed unnecessary HTML from templates
2022-01-02 23:50:15 +00:00
7ad39fdc83 [PLUGINS][Repeat] Added onNoteDeleteRelated event
Using DB::merge to increment attachment lives when repeating a note,
since it's getting deprecated in the future, an alternative needs to
replace it here
2022-01-02 23:50:15 +00:00
d5080890ac [PLUGINS][Favourite] Added onNoteDeleteRelated event
All favourite entities are now removed from note_favourite table when the respective note
is deleted. Documented the favourNote and unfavourNote methods
2022-01-02 23:50:15 +00:00
f42e91d2bc [CORE][Controller] Allow plugins to override redirect responses 2022-01-02 23:50:14 +00:00
362fc6c7dd [CORE][Controller] Set some safe default headers for every response 2022-01-02 23:50:14 +00:00
046731a05a [COMPONENT][Avatar] Save title if possible, delete correctly, no early flushes
[PLUGIN][ActivityPub] Minor bug fixes in Actor translation
2022-01-02 23:50:14 +00:00
d27e8610d6 [CORE][DB] DB:removeBy Accept class name instead of table. 2022-01-02 23:50:14 +00:00
b7574500f8 [COMPONENT][FreeNetwork] Set discovery cors enabled by default 2022-01-01 23:57:28 +00:00
6ea45df3b8 [COMPONENT][FreeNetwork] Set JRD as the default mimetype for .well-know/host-meta 2022-01-01 23:57:28 +00:00
d6cd33019d [UI][PLUGIN][Directory] Add sort options to UI 2022-01-01 22:18:18 +00:00
5662210a2d [UI][PLUGIN][Directory] Use a single template for all results 2022-01-01 22:18:18 +00:00
b1fbf7d6ef [PLUGIN][Directory] Add option to order by subscribers 2022-01-01 22:18:18 +00:00
9f11d270f4 [PLUGIN][Directory] Fix opposite sort order 2022-01-01 22:18:18 +00:00
e7940a21ee [PLUGINS][TreeNotes] Feed only shows each note and its respective direct
replies, conversation shows whole tree

[COMPONENTS][Feed] Added request to FormatNoteList event

Every single Note that was provided to FeedController::postProcess is
shown. This means, that even though the Feed is formatted to show only a
Note and its respective direct replies, those same replies are shown
individually again (and they get the chance to show their own direct
replies).

The Note list provided to FormatNoteList is reversed, and for every
index, the respective Note replies are filtered out of the original list.
The replies are then added as leafs of the current Note and added to the tree.
2022-01-01 21:42:47 +00:00
f6311debbf [PLUGIN][Directory] Refactor directory controller, so it's hopefully clearer what's happening 2022-01-01 20:50:04 +00:00
175c98b043 [PLUGIN][Directory] Add options to sort by nickname, created, modified and activity, ascending or descending 2022-01-01 20:49:17 +00:00
acc84d757c [CORE][Controller] Make Controller->{int,bool,string} functions return null if the GET parameter doesn't exist 2022-01-01 20:03:40 +00:00
fc76a00908 [PLUGIN][Directory] Rename actor to people, as it's what's actually interacted with 2022-01-01 20:02:32 +00:00
1f01923aa1 [COMPONENT][Conversation] Implement Conversation muting 2022-01-01 10:32:07 +00:00
1a99762699 [COMPONENT][Posting][Notification] Move group inbox message creation to Notification component 2022-01-01 10:32:07 +00:00
f346cd8167 [COMPONENT][Language][Tag] Update usage of Functional::cartesian_product 2021-12-31 21:03:25 +00:00
7aa90954eb [UTIL][Functional] Update Functional::cartesian_product to pass an array of collections first and a separator after 2021-12-31 21:03:25 +00:00
0050371de7 [PLUGIN][NoteTypeFeedFilter][MediaFeed][COMPONENT][Feed] Rename MediaFeed to NoteTypeFeedFilter and add support for filtering by more types, moving functionality from Feed component 2021-12-31 21:03:25 +00:00
b7872ba4ee [CORE][Controller][FeedController] Use controller instance for filtering, making FeedControler->postProcess function non-static 2021-12-31 21:03:17 +00:00
ba078b7b76 [ENTITY][Note] Caching note replies query
[COMPONENTS][Conversation] Reply route will now present a more suitable title
2021-12-31 17:54:06 +00:00
d7b46735ac [CARDS][Note] Removing unnecessary elements on replies block, and fixing gaps between note-info and the note border itself 2021-12-31 17:02:18 +00:00
6dd31926ad [COMPONENTS][Conversation] reply_add route nows shows the given Note entire conversation, the Note itself is highlighted in the conversation 2021-12-30 18:34:47 +00:00
34cc010136 [CARDS][Note] Separating complementary info as its own block 2021-12-30 18:34:47 +00:00
9a6bdf74dc [COMPONENT][Feed] Add way of filtering the notes on an arbitrary feed with a types GET parameter, that accepts {,!}<[media,text,link,tags]...> 2021-12-30 18:28:57 +00:00
0ae24f6088 [CORE][Controller][FeedController] Use controller instance for filtering, making FeedControler->postProcess function non-static 2021-12-30 18:28:57 +00:00
5f4968ac05 [ENTITY][Note][CACHE] Store lists as lists in the cache 2021-12-30 18:28:57 +00:00
2e0bfc0bcd [TOOLS] Run CS-Fixer on all files 2021-12-30 18:28:57 +00:00
2dbc35fcc3 [DEPENDENCIES] Update dependencies 2021-12-30 18:28:57 +00:00
8831276489 [TOOLS] Fix errors reported by PHPStan 2021-12-30 18:28:57 +00:00
5229d4cd8c [COMPONENT][Notification] Remove nickname GET parameter 2021-12-30 18:28:56 +00:00
cbb70a5054 [COMPONENT][Feed] Refactor Feed component 2021-12-30 18:28:56 +00:00
f16df759a9 [Components][Posting] Posting section title set accordingly if in 'reply_add' route 2021-12-30 16:18:16 +00:00
2c31f2e440 [PLUGIN][Actor Circles] Actor Circles plugin, allow user to create a custom feed of actors 2021-12-30 12:18:16 -03:00
85e31c684d [MODULES][Collection] Fixing mistakes 2021-12-30 12:16:29 -03:00
bdd8cbf36d [COMPONENT][Right Panel] Send request to AppendRightPanelBlock event 2021-12-30 12:14:41 -03:00
d7f70d288d [MODULES][Collection] Abstracting Collections 2021-12-29 21:56:45 -03:00
49d247aec2 [COMPONENTS][Feed] Styling for the empty feed page added 2021-12-29 19:31:28 +00:00
f28ed5e359 [CSS] .note-complementary-info now smaller in general 2021-12-29 19:02:06 +00:00
6b82708968 [CARDS][Note] AppendCardNote event overhaul
[PLUGINS][Favourite] Added complementary information on user action

[COMPONENTS][Conversation] Fixed AppendCardNote issue where cached query
would only act on Notes with depth > 1
2021-12-29 18:47:12 +00:00
836560f55f [CARDS][Note] AppendCardNote event overhaul
[PLUGINS][Favourite] Added complementary information on user action

[COMPONENTS][Conversation] Fixed AppendCardNote issue where cached query
would only act on Notes with depth > 1
2021-12-29 18:35:12 +00:00
0caec6ab9e [COMPONENT][ACTOR CIRCLE] fixing template text 2021-12-29 14:13:25 -03:00
01d5e84a08 [COMPONENT][ACTOR CIRCLE] mention self tag circle with @#self_tag 2021-12-29 14:13:25 -03:00
f9bc1c790f [PLUGIN][AttachmentCollections] Dark mode 2021-12-29 10:52:04 -03:00
25120c6630 [PLUGIN][AttachmentCollections] Responsive pages 2021-12-29 10:52:04 -03:00
137723e59a [PLUGIN][ActivityPub][Favourite][Repeat][Delete] Document event handlers 2021-12-28 22:57:49 +00:00
8274e93ed5 [PLUGINS][DeleteNote] Added documentation, stating the scope of what deleting a Note means 2021-12-28 22:43:04 +00:00
ce3c6a7f23 [PLUGINS][RepeatNote] Added documentation (not for ActivityPub related functions) 2021-12-28 21:51:27 +00:00
846ec37cd9 [COMPONENT][Notification] Add event to decide whether local actors should be bothered 2021-12-28 18:45:18 +00:00
4d8e39bf69 [PLUGIN][RepeatNote] Do not notify about clone note
Fix redirecion after action
Fix typo in activitypub handler
2021-12-28 18:30:27 +00:00
182c6265a3 [PLUGINS][RepeatNote] Add onFilterNoteList event
Filters repeats out of Conversations, and replaces a repeat with the original Note on Actor feed

Added isNoteRepeat to plugin's entity, which returns true if a given
Note is a repeat of another Note
2021-12-28 17:37:01 +00:00
1d1d169a5c [PLUGIN][ActivityPub] Support federation of Tombstones 2021-12-28 17:10:20 +00:00
9cda64f275 [COMPONENT][Notification] Use _m() in reason 2021-12-28 16:36:52 +00:00
3e83387e98 [PLUGIN][DeleteNote] Support ActivityPub 2021-12-28 16:22:38 +00:00
9585472679 [ENTITY][Actor] Basic check if can admin for remote actors 2021-12-28 15:38:41 +00:00
b7c82b9dcb [PLUGIN][DeleteNote] Ensure permissions properly 2021-12-28 15:38:39 +00:00
5c2b46a71d [COMPONENT][Link] Wrap delete operations in transactions 2021-12-28 06:56:05 +00:00
46d121ef7b [COMPONENT][Attachment] Wrap delete operations in transactions and correct sequence of deletation 2021-12-28 06:56:05 +00:00
bf4a0008ef [ENTITY][Note] GNU social uses Tombstones for deleted notes instead of fully removing them.
Various corrections.
2021-12-28 06:56:05 +00:00
bb4149e092 [PLUGIN][AttachmentCollections] Restore functionality
Some minor corrections
2021-12-28 04:43:13 +00:00
a03429ba03 [PLUGIN][DeleteNote] Delete Note action implemented
Replaces Note content with a tombstone, removes any attachment relations and decrements their lives (possibly even removing the attachment), and creates the respective activity
2021-12-27 22:33:36 +00:00
f5b06e2c7e [UTIL][Formatting] Fix group mentions 2021-12-27 21:38:20 +00:00
c40e38c5ba [TOOLS] Fix errors found by PHPStan 2021-12-27 20:37:16 +00:00
d74a9ad373 [ENTITY][Subscription] Add Subscription::cacheKeys 2021-12-27 20:37:16 +00:00
76440961ca [CORE][DB] Add option to findOneBy to return null rather than throw 2021-12-27 20:37:16 +00:00
8796885fa0 [COMPONENT][Tag] Remove '.' from tag regex 2021-12-27 20:37:16 +00:00
5c10448080 [COMPONENT][Group] Add group subscribe button 2021-12-27 20:37:16 +00:00
559ec3df39 [TWIG] Fix login template 2021-12-27 19:09:32 +00:00
20d89f0f24 [COMPONENTS][Avatar] Make sure dimension values are integers
[CARDS][Note] Fix assumed avatar dimension values
2021-12-27 19:08:55 +00:00
19975b8d8d [COMPONENTS][Avatar] Make sure dimension values are integers
[CARDS][Note] Fix assumed avatar dimension values
2021-12-27 19:08:51 +00:00
65a3d738ca [PLUGIN][AttachmentCollections] Make it look good 2021-12-27 15:38:47 -03:00
7ddfe92773 fix: redirect 2021-12-27 15:35:09 -03:00
e932ff43d0 [PLUGIN][AttachmentCollections] changes path name to be the same as the one introduced in c4dacd7626 2021-12-27 15:31:19 -03:00
672df5165c [PLUGIN][AttachmentCollections] Fixing forms submission 2021-12-27 15:25:20 -03:00
72a19d7eac [COMPONENT][Posting] Fix error around mentions of actors that don't exist 2021-12-27 17:35:33 +00:00
b84315c95b [TOOLS] Fix errors reported by PHPStan at level 4 2021-12-27 17:35:33 +00:00
edd996d281 [DEPENDENCIES] Update dependencies, including PHPStan to dev version 2021-12-27 17:35:33 +00:00
cf2f87fc1d [AUTOGENERATED] Update autogenerated code 2021-12-27 17:35:33 +00:00
c9d05d71f5 [COMPONENT][Group] Fix group creation, Refactor related entities to inside the component
Other minor bug fixes and improvements
2021-12-27 17:28:03 +00:00
d03572e366 [PLUGIN][Directory] Make it list groups 2021-12-27 17:10:58 +00:00
de148c1f78 [COMPONENT][Avatar][Controller] Implement multiple dimensions 2021-12-27 05:08:29 +00:00
2a902d6a7e [ASSETS][css][sections] rename profile-avatar to avatar 2021-12-27 05:08:29 +00:00
195618801b [TEMPLATES][Cards][Note] Fix some issues with note minimal 2021-12-27 05:08:29 +00:00
80afc0fa6c [TEMPLATES][Cards][Profile] Provide both actor uri and url, as well as full mention guidance 2021-12-27 05:08:27 +00:00
eb761609aa [ENTITY][Note] If note is a reply to, notify reply's actor 2021-12-27 04:56:00 +00:00
c4dacd7626 [COMPONENT][Attachment] Vinculate note information with attachment controllers
Various minor bug fixes
2021-12-27 04:56:00 +00:00
fd44bc3ac5 [CARDS][Note] Minimize calls between different tables
For instance, the actor_url was set using note.getActor().getUrl() instead of using the actor from the start (since actor was needed in other setters as well).
2021-12-27 03:06:35 +00:00
65676d3980 [CARDS][Note] Fix attachment page from retrieving image dimensions
Attachments may use only a specific block, not the full note macro itself. Since this is the case, the actor needs to be retrieved for the minimal macro note.
2021-12-27 03:06:35 +00:00
ea42ba9f26 [CARDS][Profile] Fix bio
Was using 'hasBio()' instead of 'getBio()'
2021-12-27 03:06:35 +00:00
a1a6f5f4fd [TOOLS] Add warning to update code in bin/generate_entity_fields 2021-12-27 03:06:35 +00:00
93276ce8d0 [AUTOGENERATED] Update autogenerated code 2021-12-27 03:06:30 +00:00
0df423e84b [TOOLS] Update bin/generate_entity_fields so it defaults nullable variables to null and handles null in varchars 2021-12-27 03:03:57 +00:00
7eff22d548 [TOOLS] Fix errors reported by updated PHPStan 2021-12-27 03:03:57 +00:00
52e2231661 [DEPENDENCIES] Update PHPStan and other dependencies 2021-12-27 03:03:57 +00:00
9d3c01312f [CARDS][Note] Fix assumed avatar dimension values 2021-12-27 03:03:53 +00:00
ce23660dba [PLUGIN][ImageEncoder] Only show thumbnails if they exist 2021-12-27 03:02:21 +00:00
58715f1733 [PLUGIN][ImageEncoder] If vips doesn't support, don't throw exception, just let other plugin try 2021-12-27 03:02:20 +00:00
838510ced2 [CARDS][Navigation] Replaced footer to nav
Since the footer is inside a section, it couldn't be a footer element
2021-12-27 03:02:20 +00:00
b30198413c [PLUGINS][Oomox] Add rel to response headers 2021-12-26 21:26:04 +00:00
7402e749cb [COMPONENTS][Feed] Removed unnecessary ARIA 2021-12-26 21:19:33 +00:00
18cfcc0796 [ICONS] Removed XML processing instructions in svg files 2021-12-26 21:19:06 +00:00
045ff6fb68 [PLUGINS][XMPPNotifications] Fix typo 2021-12-26 21:19:06 +00:00
fec1861b80 [CONTROLLER][Note] Respect note scope 2021-12-26 21:19:06 +00:00
d891089945 [PLUGIN][StoreRemoteMedia] Let the user decide the max file size to download 2021-12-26 21:19:04 +00:00
0c421116a6 [ENTITY][Note] Relive isVisibleTo method 2021-12-26 19:50:21 +00:00
78cc9c4659 [PLUGINS][Repeat] Repeat now added has a reply and conversation of original note 2021-12-26 19:16:57 +00:00
e10a38a3e2 [CSS] Align page header on ultrawide screens 2021-12-26 19:16:57 +00:00
feb2631f00 [CARDS][Note] Add permalink to extra note actions 2021-12-26 19:16:57 +00:00
a1d9909379 [CORE][VisibilityScope] Use enum type instead of Bitmap 2021-12-26 19:16:56 +00:00
6f0d9add08 [DEPENDENCIES] Run composer after php version bump from 8.0 to 8.1 2021-12-26 19:16:17 +00:00
6883e51fc8 [DOCKER] Force docker php to use proper PHP executable 2021-12-26 19:16:16 +00:00
3d9141f4ce [COMPONENT][Tag] Allow searching for actor circles with {actor,people}-{circle,list}:#tag 2021-12-26 19:16:16 +00:00
4df80be095 [ENTITY][Actor] Set default null values 2021-12-26 19:16:16 +00:00
d37f38a1ea [ENTITY][Note] A note by default isn't a reply 2021-12-26 19:16:16 +00:00
dd268ba8db [CARDS][Profile] Use Actor::hasBio() instead of getter 2021-12-26 19:16:16 +00:00
8e7c94fe1d [COMPONENT][Attachment] Entity should have default refCount value, every attachment starts with 1 life 2021-12-26 19:16:16 +00:00
94e216a943 [COMPONENT][Conversation] remove early flush in utility function 2021-12-26 19:16:16 +00:00
fdf506b9f9 [CARDS][Note] Fix structure to break content in a controlled manner
[CSS] Responsive feed styling work

Note info content will now break as expected, useless space trimmed down to accomodate smaller screens.
2021-12-26 19:16:16 +00:00
726613cd96 [ENTITY][ActorCircle][COMPONENT][Tag] Add fields to ActorCircle and add or remove target to actor circle when they add or remove a selftag 2021-12-26 19:16:15 +00:00
000ec680e6 [CORE][TOOL] Minor fixes and run cs-fixer 2021-12-26 19:16:15 +00:00
97243151fa [TOOL] CS-fixer does not accept a list of files, so we need to run it on each file individually 2021-12-26 19:16:15 +00:00
c79b1e4c94 [AUTOGENERATED] Update auto generated code 2021-12-26 19:16:15 +00:00
68076d73dd [TOOLS] Update bin/generate_entity_fields so it automatically truncates values in setters 2021-12-26 19:16:15 +00:00
29bb11e8bc [TOOLS] Don't fail commit when checking tools fail 2021-12-26 19:16:15 +00:00
ec28f23025 [TOOLS] Run CS-fixer on all files 2021-12-26 19:16:15 +00:00
5e42723624 [ENTITY][Note] Include reply_to's targets in child's 2021-12-26 19:16:14 +00:00
f5f7fc6056 [PLUGIN][Favourite][Repeat] Add notification target getter 2021-12-26 19:16:14 +00:00
625618b4e0 [PLUGIN][Favourite][Repeat] Fix incorrect use of RedirectException 2021-12-26 19:16:14 +00:00
91f8c86efa [PLUGIN][ActivityPub] Support and federate scopes 2021-12-26 19:16:12 +00:00
21f585ef7e [COMPONENT][Language] Do not exclude notes without language from the feeds 2021-12-26 16:48:36 +00:00
9d5e149dec [COMPONENT][Feed] Correct ordering 2021-12-26 16:48:35 +00:00
19502050e0 [TOOLS] Add remove-file and minor corrections to nuke everything 2021-12-26 16:48:35 +00:00
3e13765f62 [CORE][SCOPE] Implement basic visibility in feeds 2021-12-26 16:48:34 +00:00
d4bc1d097d [ENTITY][NoteTag][COMPONENT][Language] Add language to NoteTag and minor corrections 2021-12-26 06:22:30 +00:00
78fddaf86a [PLUGIN][ActivityPub] Notify mentions in tags 2021-12-26 06:22:28 +00:00
9d0b39e680 [PLUGIN][ActivityPub] Support tags in notes 2021-12-25 18:04:31 +00:00
36483a6ecd [COMPONENT][Link] Ignore html anchors that include mention class 2021-12-25 18:04:30 +00:00
0d5e545a6e [TWIG] Replaced unused markup classes 2021-12-25 17:59:49 +00:00
3275a989db [CSS] Style paging info 2021-12-25 17:48:07 +00:00
ab640b110b [COMPONENTS][Feed] Remove 'feed' from header
When not logged in, the word would be repeated
2021-12-25 17:47:12 +00:00
7891461d36 [PLUGINS][AttachmentCollections] Fix template
[PLUGINS][AttachmentShowRelated] Fix template

[CSS] Fix section-subtitle-details children padding issues

[COMPONENTS][Posting] Remove unnecessary colon

Overall polish of existing plugins templates
2021-12-25 17:31:16 +00:00
ce3b677833 [CONFIG][Twig] Replaced form theme to a custom one
[CARDS][Forms] Added custom social form theme

[CSS] Replaced/added new classes to be used with forms

Base form theme created, can be extended to create complex blocks to be
called by twig when rendering a specific form.
2021-12-25 16:19:46 +00:00
8651bd44c2 [PLUGINS][ProfileColor] Fix callable argument when retrieving cache data 2021-12-25 15:31:24 +00:00
6ada5e60d2 [TOOLS] Make PHP-CS-Fixer and PHP-Doc-check run inside the tooling container 2021-12-25 15:20:21 +00:00
af3d278fde [PLUGIN][AttachmentCollections] Prevent user from appending stuff in a collection (s)he doesn't own 2021-12-25 11:27:00 -03:00
1e965157de [PLUGIN][AttachmentCollections] Iterate documentation 2021-12-25 11:25:50 -03:00
b604ee3146 [COMPONENT][Posting][Tag][Group][Conversation][RightPanel] Rename posting_form.to to posting_form.in, fill in with current group. Refactor context_actor 2021-12-25 11:24:41 +00:00
0f54d2121e [CONTROLLER][Feeds] In Home feed, include specifically subscribed-{person,group,business,organization}, but allow querying for subscribed or subscribed-actor{,s} 2021-12-25 10:12:38 +00:00
dad322e577 [TWIG][Templates] check if variable is defined in twig before using it
fixes error in /actor/X, where there's no page_title defined
2021-12-24 11:44:12 -03:00
de89cffc34 [COMPONENT][Feed] Restrict non-public feeds to logged in users 2021-12-24 14:39:23 +00:00
82e6e95b6a [PLUGIN][AttachmentCollections] Add Attachment Collection plugin which allow users to save attachments in collections 2021-12-24 11:29:37 -03:00
63f9c6341e [COMPONENT][RightPanel] Sends request object in the AppendRightPanelBlock event 2021-12-24 10:20:55 -03:00
1947e99430 [DOC] Add documentation to methods flagged by doc-check 2021-12-24 09:34:13 +00:00
dabf5576d3 [CONTROLLER][Feeds] Implement query for home feed: note-from:subscribed 2021-12-24 09:27:24 +00:00
e3efd25b43 [PLUGIN][ActivityPub] Fix typo in getObjectByUri 2021-12-24 02:46:45 +00:00
7407028891 [PLUGIN][RepeatNote] Fix getRepeatNotes query 2021-12-24 02:46:45 +00:00
10e7c71b6e [COMPONENT][Tag] Do not perform DB::flush in an event 2021-12-24 02:46:45 +00:00
8cd703d68b [COMPONENT][Link] Even if everything else in Posting fails, no reason to discard the finding about the Link 2021-12-24 02:46:45 +00:00
671c3968e2 [TWIG][Templates] Rename inconsistent CSS classes
[CSS] Font size hierarchy refactor
[PLUGINS][MediaFeed] Renamed BeforeFeed event

Type scale hierarchy redone. Bigger line height added, making it easier
to click on links and separate contents.

Feed title added. AddFeedActions replaces BeforeFeed event.
MediaFeed links will now show an icon to the right of the feed title,
smaller footprint and more consistent with the overall design.
2021-12-24 02:46:44 +00:00
f10b3bb05c [PLUGINS][MediaFeed] Fixed template path typos 2021-12-24 02:46:44 +00:00
1e7a285ded [TOOL] Add force-nuke-everything make rule, which deletes the whole database and caches 2021-12-24 02:46:44 +00:00
333e71ed50 [COMPONENT][Group] Add group settings, with support for editing the group's personal info and self tags 2021-12-24 02:46:43 +00:00
a36bdf9719 [UI][I18N] Add missing translation tags in templates/cards/navigation/view.html.twig 2021-12-24 02:46:43 +00:00
69565e3f49 [COMPONENT][Tag] Add missing required route paramter nickname 2021-12-24 02:46:43 +00:00
2e69eac63e [CONTROLLER][UserPanel][UTIL][FORM][ActorForms] Move UserPanel::personalInfo to ActorForms::personalInfo 2021-12-24 02:46:43 +00:00
1e6bc5b6ab [CORE][Form] Fix Form::handle and report back any errors 2021-12-24 02:46:42 +00:00
59abffe744 [ENTITY][Actor][DOC] Add explanation in Actor::__call 2021-12-24 02:46:42 +00:00
be197bc82b [ENTITY][LocalUser] Fix LocalUser::setNicknameSanitizedAndCached so it updates the actor nickname and propagates the exceptions 2021-12-24 02:46:42 +00:00
764a30695d [ENTITY][ActorTag][Actor][Activity] Add Actor::getActorCircles 2021-12-24 02:46:42 +00:00
95783d6109 [CONTROLLER][UserPanel][COMPONENT][Tag] Re-add way of adding self tags, but in a more reusable (and less buggy) way 2021-12-24 02:46:41 +00:00
072caad845 [CONTROLLER][UserPanel] Remove self tags settings from profile settings section, so it can be abstracted and reused 2021-12-24 02:46:41 +00:00
774b33a522 [DEPENDENCIES] Update dependencies and Symfony to 5.4 LTS 2021-12-24 02:46:41 +00:00
80ebd6fb7b [ENTITY][Actor] Add helper function for checking if the current actor can admin another 2021-12-24 02:46:40 +00:00
63679426b6 [UTIL][HTML] Allow <b> and <hr> tags 2021-12-24 02:46:40 +00:00
a28c0da4af [COMPONENT][Language][Settings] Factor out language settings into the language component 2021-12-24 02:46:40 +00:00
2acf0bcbb6 [TEMPLATE][Settings] Factor out settings macros 2021-12-24 02:46:40 +00:00
1682b04e74 [COMPONENT][Group][CONTROLLER][Actor] Move group related stuff to it's own component 2021-12-24 02:46:39 +00:00
67f09d4e50 [CONTROLLER][Actor] Handle case where there isn't a logged in user 2021-12-24 02:46:39 +00:00
4b98200ecd [UI][CARD][Note] Add Conversation link 2021-12-24 02:46:39 +00:00
3c79d82b80 [PLUGIN][ActivityPub][Note] Support inReplyTo 2021-12-24 02:46:39 +00:00
b05106e7f9 [COMPONENT][Conversation] Minor corrections and don't store URI in DB 2021-12-24 02:46:38 +00:00
e04d927fe9 [COMPONENT][Feed][Conversation] Add note-conversation: 2021-12-24 02:46:36 +00:00
17b7ef13a0 [COMPONENT][Conversation] Remove Feed list entry
Notifications feed is enough
2021-12-24 00:47:36 +00:00
e743a17883 [COMPONENT][Notification] Introduce Notifications Feed 2021-12-24 00:47:36 +00:00
2004f1883a [COMPONENT][FreeNetwork] Move feeds that only make sense with FreeNetwork enabled to this component 2021-12-24 00:47:34 +00:00
7d8cce3b27 [COMPONENT][Feed] Correct queries and introduce new feeds
Refactor feeds and search to use a common query builder
2021-12-24 00:44:39 +00:00
1865d2b41e [ActivityPub][Postman] Fill To and CC with mentions 2021-12-24 00:42:02 +00:00
48b2c8c04e [COMPONENTS][Conversation] Local Conversations done
[COMPONENTS][Posting] Call Conversation::assignLocalConversation upon
creating a new note

By using the AddExtraArgsToNoteContent event upon posting a Note, an
extra argument ('reply_to') is added before storing the aforementioned Note.
When storeLocalNote eventually creates the Note, the corresponding
Conversation is assigned.
2021-12-24 00:42:02 +00:00
3ca7a35158 [COMPONENT][Conversation] added onProcessNoteContent event
If the source lacks capability of sending the reply_to metadata, it might be on the note content itself as a reference.

[ENTITY][Note] Documenting conversation/reply related functions. The entity Note reply_to shouldn't be trusted toknow whether or not the Note is a Conversation root. This will happen if a known remote user replies to an unknown remote user - within a known conversation.
2021-12-24 00:42:02 +00:00
c83ae76a68 [COMPONENTS][Conversation] Conversation entity moved to respective component, URI column added
Route for conversation added and Conversation Controller created.

[CONTROLLER][Conversation] Created ConversationShow function, will be used to render the conversation route page

[ENTITY][Note] Conversation id column added, this way a Note can have a direct relation with its respective conversation.
2021-12-24 00:42:01 +00:00
c494928b46 [PLUGIN][MediaFeed] Remove unused import 2021-12-24 00:41:53 +00:00
5115145901 [PLUGIN][MediaFeed] Iterate Documentation 2021-12-24 00:41:44 +00:00
d04b68a3ce [PLUGIN][MediaFeed] Add Media plugin which filters a feed by notes containing media 2021-12-22 11:13:06 +00:00
fb64444325 [UI][CORE][COMPONENT][Search] Refactor templates
Just DRY
2021-12-21 14:38:58 -03:00
dd5d46c556 [CORE] Add option to filter notes even when there's no actor 2021-12-21 14:33:19 -03:00
ce91826d31 [CORE][UI] Added the possibility to draw stuff before feed starts 2021-12-21 14:29:05 -03:00
012e0665b0 [COMPONENT][Search] Only display search subscribe field if a user is logged in 2021-12-21 16:45:59 +00:00
af122df6e1 [CONTROLLER][Actor] Fix actor view route 2021-12-21 16:45:59 +00:00
8b5286c383 [COMPONENT][Notification] Do not re-render content just to grab attentions
Other minor improvements and bug fixes
2021-12-21 16:05:24 +00:00
e2c0505620 [ActivityPub][Inbox] Add request to debug logs 2021-12-21 15:52:41 +00:00
e7dcea3f26 [CONTROLLER][Actor] Add group notes query and fixup template 2021-12-21 12:47:16 +00:00
6a8144003f [ENTITY][Actor] Add missing 'break' statements in switches in getUr{i,l} 2021-12-21 12:46:42 +00:00
25900d38bd [UTIL][EXCEPTION][RedirectException] Remove hack that would attempt to generate a URL inplace 2021-12-21 12:46:02 +00:00
027adc97b2 [CONTROLLER][ActorController] Make ActorController extend the FeedController, so notes get filtered 2021-12-21 12:45:15 +00:00
7f65b23074 [DB] Allow mentioning 'note' in a DQL query (in quotes), in order to be able to search in the activity table (previously would be replaced by the class name) 2021-12-21 12:44:34 +00:00
85735222cb [COMPONENT][Posting] When a group is mentioned, add that note to the group inbox 2021-12-21 12:43:28 +00:00
d6d5926b6e [TOOLS][DOC] Add missing documentation, as flagged by doc-check 2021-12-21 12:24:23 +00:00
da8c41e094 [TOOLS] Fix errors found by PHPStan 2021-12-21 12:17:51 +00:00
fa863d9e03 [CONTROLLER][ENTITY][Actor] Add way of creating a group that doesn't exist 2021-12-21 12:12:03 +00:00
88a137fb15 [ROUTES] Rename actor routes and add route for groups 2021-12-21 12:10:51 +00:00
87aa9360a3 [CORE][ActorController] Refactor actor related controllers (Actor, Subscribers, Subscriptions) to remove duplicated code 2021-12-21 12:10:08 +00:00
315fd95b94 [COMPONENT][Posting] Add facility to allow mentioning groups that don't yet exist 2021-12-21 12:07:54 +00:00
36976d8fe7 [DOCKER] Update configuration script to include the worker container 2021-12-20 20:39:54 +00:00
e27f2dd202 [ENTITY] Remove Group entity, as groups are actors 2021-12-20 20:20:25 +00:00
918e6823a9 [ENTITY][Actor] Init Actor's class variable homepage, bio, and location to null
The template cards/profile/view.html.twig tries to access the bio variable before it's initialized, an is null check was already in place. However, even then, the variable needs to be init beforehand. The same change was applied to homepage and location since they might lead to similar issues.
2021-12-20 16:31:26 +00:00
622057ba0d [CONTROLLER][Feeds] Added should_format field on returned array
FeedController will only handle FormatNoteList if the should_format field is true.

This change was made to make the replies route feed possible, this route is added by the Conversation component. Since a reply isn't a conversation root, if the FeedController handled the FormatNoteList event, this feed wouldn't have any notes to display.
2021-12-20 15:39:23 +00:00
23f94ac961 [CORE][Controller] Define html format as default 2021-12-20 13:32:49 +00:00
1832397363 [ActivityPub] Include recent actor type attribute in its creation
Improve debug logs
2021-12-20 13:32:49 +00:00
ed67da89dc [TAGS] Fix some minor logic issues with Actor Tags and Circles 2021-12-20 13:32:49 +00:00
a9feb79825 [SECURITY] New actors are Person user on register by default 2021-12-20 13:32:49 +00:00
630e22579e [PLUGIN][RepeatNote] Add support for onGSVerbToActivityStreamsTwoActivityType 2021-12-20 13:32:48 +00:00
8d1e000574 [PLUGIN][Favourite] Add support for onGSVerbToActivityStreamsTwoActivityType 2021-12-20 13:32:48 +00:00
a9c73a8f33 [PLUGIN][ActivityPub] Add mention tags 2021-12-20 13:32:48 +00:00
a005a7bcea [PLUGIN][ActivityPub] Add getUriByObject, so that we can construct activities referring to other known objects (local or foreign) 2021-12-20 13:32:46 +00:00
57beb178cc [Notification][ENTITY][Activity] Avoid including sender in notification targets 2021-12-19 19:04:05 +00:00
93fa7eb0b5 [ENTITY][Note] Language in notes is optional 2021-12-19 19:04:05 +00:00
1d09a02ad6 [FreeNetwork][ActivityPub] Sometimes remote Actors report empty full names in not very explicit manners 2021-12-19 19:04:05 +00:00
a81ac673ac [CORE][ENTITY] Rename 'Entity::getWithPK' to 'Entity::getByPK' 2021-12-19 19:04:01 +00:00
330b6b49a2 [COMPONENT][Posting] Add support for posting with empty content. At least one of content and attachments must be provided 2021-12-16 11:08:53 +00:00
5dca5568b7 [CORE][Cache] Fix wrong return type in Cache::delete 2021-12-16 11:01:23 +00:00
1b45fb251a [COMPONENT][Search][Attachment][DOC] Add doocumentation to search related functions 2021-12-16 10:52:06 +00:00
24291a268a [CORE][Cache][DOCS] Add documentation to complex private function 'Cache::redisMaybeRecompute' 2021-12-16 10:47:01 +00:00
b0d5ce8aab [COMPONENT][Search] Don't explode if provided an empty search query 2021-12-16 10:41:29 +00:00
a16d31b70e [COMPONENT][Search] Add support for searching in notes' contents 2021-12-16 10:39:36 +00:00
060c3f01a6 [CACHE][CONFIG] Disable early recompute by default 2021-12-13 15:49:53 +00:00
6d8679d86b [CORE] Fix resetting the config to the default values 2021-12-13 15:49:52 +00:00
ff38efd5f1 [PLUGIN][ProfileColor] Cache results 2021-12-13 15:49:52 +00:00
3ba7e1804b [CORE][Cache] Add fast path for redis cache interactions 2021-12-13 15:49:52 +00:00
10ddbf692a [ENTITY][LocalUser] Make more use of caching and factor out the cache keys 2021-12-12 16:21:09 +00:00
f6a8ee86b6 [COMPONENT][Search] Add support for searching for a given actor type 2021-12-11 22:21:31 +00:00
4be226edd5 [COMPONENT][Search] Make search title field not required 2021-12-11 22:20:25 +00:00
1e8eea0434 [ENTITY][Actor] Add type field, which denotes whether the actor is a person, org, group, business or bot 2021-12-11 22:19:37 +00:00
e62896b84e [UTIL][FormFields] Allow specifying a null actor in the language field, for when there isn't a logged in user 2021-12-11 22:18:31 +00:00
6b38972cca [COMPONENT][Search] Add support for searching for notes with media or with text
`note-types:media` will search for notes with an associated attachment or no text
2021-12-11 20:59:13 +00:00
bad5efe819 [COMPONENT][Search] Be explicit about including : 2021-12-11 20:56:47 +00:00
01470ee664 [COMPONENT][Search] Add facility for supporting searching for note types 2021-12-11 19:33:30 +00:00
d667c3a453 [COMPONENTS][Search][UI] Add options to filter by note or actor type. Reorganize UI 2021-12-11 17:48:40 +00:00
e8ddca6b06 [COMPONENT][LeftPanel] Add previous title to error message when a duplicate feed is found 2021-12-11 16:50:52 +00:00
682f9aa611 [COMPONENT][LeftPanel] Ensure given url corresponds to a feed 2021-12-11 10:50:29 +00:00
dbc8bf2ae1 [COMPONENT][Search][LeftPanel] Add way of adding a search result as a left panel feed 2021-12-11 10:49:57 +00:00
9afe6ecfac [COMPONENT][Search] Add search query builder 2021-12-10 21:19:21 +00:00
fceb014606 [COMPONENT][Language] Add support for searching for multiple languages at the same time 2021-12-10 21:15:12 +00:00
6a9388a789 [UTIL][FormFields] Add option language form field so the user can provide no selection 2021-12-10 21:03:37 +00:00
ab5e074d9e [DEPENDENCIES] Update lstrojny/functional-php 2021-12-10 13:54:10 +00:00
ada94a98e2 [COMPONENT][Search] Move search form to utility function. Add search form and search builder forms to search results page 2021-12-10 13:53:23 +00:00
8beb9682ee [COMPONENTS][Search][Language] Move language search features to the language component. Add support for searching for notes from people with a given language 2021-12-10 13:53:23 +00:00
c720ce7daf [UTIL][Functional] Add cartesianProduct while it doesn't get merged upstream to \Functional 2021-12-10 13:53:23 +00:00
dd33720957 [TWIG] Add 'dd' and 'die' functions to twig 2021-12-10 12:06:47 +00:00
33fba0d970 [COMPONENT][Language][ENTITY][ActorLanguage] Refactor cache keys in ActorLanguage. Add ActorLangauge::getActorRelatedLanguagesIds and use it in note filtering in the Language component 2021-12-10 10:19:23 +00:00
5dd9e5a3d7 [PLUGIN][RepeatNote] Add Notifications and ActivityPub support
Minor bug fixes
2021-12-10 04:05:41 +00:00
0ee4cc7709 [PLUGIN][Favourite] Notify when actor favours a note 2021-12-10 04:05:35 +00:00
60d31e097e [COMPONENT][Notification] Support empty array of targets 2021-12-10 04:05:34 +00:00
b89f57ce93 [ENTITY][Note] Language can be null 2021-12-10 04:05:34 +00:00
4992ff153b [PLUGIN][RepeatNote] Fill activity log and fix some bugs
Refactored the plugin.
2021-12-10 04:05:26 +00:00
01689edc66 [PLUGIN][Favourite] Report already favoured or unfavoured 2021-12-10 04:05:22 +00:00
0c11fe413c [COMPONENT][Tag] We don't always have information about if a tag is canonical 2021-12-10 04:05:22 +00:00
dcc37b055d [COMPONENT][Link] Remove relation to note when note is removed
Moved entity NoteToLink to the component
2021-12-10 04:04:56 +00:00
dcc867dad7 [COMPONENT][Attachment] Delete related 2021-12-10 02:46:25 +00:00
5f167517ad [ENTITY][Note] Add deleter 2021-12-10 02:46:25 +00:00
b1585f0ef2 [ENTITY][Actor][ActorTag] Ensure only one copy of each tag is inserted 2021-12-09 22:23:17 +00:00
45d1ca88a6 [COMPONENT][Tag] Ensure only one copy of each tag is inserted 2021-12-09 22:22:31 +00:00
ab9dd1db77 [CACHE][ENTITY][Actor] Refactor Actor so that all cache keys are kept in one cacheKeys function, so that we can more easily be certain there are no mismatches in cache keys between gets and deletes 2021-12-09 21:59:49 +00:00
4d2230ff43 [COMPONENT][Language] Add Language component and implement language based note filtering 2021-12-09 21:39:00 +00:00
1be4b3d481 [EVENT][FilterNoteList] Use an in-out parameter, so multiple events can act on this 2021-12-09 21:39:00 +00:00
3405312a5b [COMPONENT][Search] Add way of searching for only notes or poeple with a given language 2021-12-09 21:39:00 +00:00
f547fd3bb9 [CONTROLLER][FeedController] Fix return value, from notes to notes_out 2021-12-09 19:06:18 +00:00
c137a484af [ENTITY][ActorTag] Fix url in actor tag 2021-12-09 16:34:07 +00:00
26b95fae96 [PLUGIN][StemWord] Remove the country part from the code. Ignore if no stemmer is found for the given language 2021-12-09 16:23:09 +00:00
659ea5cd1f [COMPONENT][Search] Use correct template for displaying actors 2021-12-09 16:23:09 +00:00
bc3e6ac704 [COMPONENT][Search] Fix searching for actors 2021-12-09 16:23:09 +00:00
139da2c07f [COMPONENT][Search][Tag] Add support for searching for actors or notes with a language. Use leftJoins, rather than inner joins 2021-12-09 16:22:59 +00:00
774e32f834 [PLUGINS][TreeNotes] Working, however feed is still not formatted
[ENTITY][Note] Fix for getReplies()
2021-12-09 00:15:47 +00:00
64122a9612 [PLUGIN][Favourite] Add ActivityPub Inbox support 2021-12-08 23:24:25 +00:00
5025901c86 [PLUGIN][Favourite] Log changes into Activity 2021-12-08 23:24:25 +00:00
480a42cca5 [PLUGIN][ActivityPub] Introduce ActivitypubObject. Beware, inside the plugin, an Object can never be an Activity.
Many bug fixes and other major changes (interface changed, see EVENTS.md)
2021-12-08 23:24:23 +00:00
b1227d36f1 [CARDS][Note] In conversation time ago information added 2021-12-08 22:48:04 +00:00
df92b0d225 [COMPONENTS][Conversation] Refactored Reply plugin into Conversation component
[PLUGIN][TreeNotes] TODO: think it is broken, perhaps a problem of the conversation arguments passed in note card template
2021-12-08 22:48:04 +00:00
e9dfa0f08c [COMPONENT][Attachment][Posting] Move onHashFile from Posting to Attachment. Remove onGetAllowedThumbnailSizes 2021-12-08 20:42:29 +00:00
614e02b4c6 [PLUGIN][TagBasedFiltering] Add to user settings page and split adding tags from note/actor from editing blocked 2021-12-08 19:57:36 +00:00
d52a043705 [TWIG] Restructure user settings template, making it much easier to use and extend 2021-12-08 19:57:35 +00:00
6627006e61 [COMPONENT][Avatar] Move avatar settings template from core to component 2021-12-08 19:57:29 +00:00
870f866c23 [CARDS][Navigation] Section styling more consistent and can now be selectively hidden by user
[COMPONENTS][Right] Changed additional options div class names
2021-12-08 15:16:30 +00:00
a285128dab [COMPONENTS][Posting] Added language help text
[COMPONENTS][Tag] Added Posting form canonical tags field help text
2021-12-08 14:28:58 +00:00
a4f18b937e [CSS] Forced to re-add webkit mask image prefix for checkbox styling 2021-12-08 13:44:06 +00:00
3b8a3e953d [CONTROLLER][FeedController] Make post processing happen more automatically, reducing noise in individual controllers. Now it's enough to simply extends App\Core\Controller\FeedController, to implement a feed 2021-12-08 10:20:37 +00:00
7783922b2e [CONTROLLER][PLUGIN][Directory][Favourite][Reply][CORE][FeedController] Refactor to new FeedController 2021-12-07 23:34:32 +00:00
ba87944732 [COMPONENT][CONTROLLER][Search][CORE][FeedController] Use new FeedController base class 2021-12-07 21:07:37 +00:00
4c0210fb00 [CORE][FeedController][CONTROLLER][Feeds] Refactor feed filtering into base class 2021-12-07 21:06:39 +00:00
b8e9c2ce41 [COMPONENT][Search] Add title so it's clearer these are search results 2021-12-07 20:36:25 +00:00
f9fedfb131 [COMPONENT][Search] Fix search 2021-12-07 20:26:39 +00:00
c131e47176 [COMPONENT][Tag] Remove wrong canonicalization of tags in tag feed controller. Fix display of original tags 2021-12-07 20:10:59 +00:00
c093eb9089 [TWIG] Update instanceof filter to be able to check for native types 2021-12-07 19:52:27 +00:00
26a324ee4b [TWIG][Templates] Sort languages settings template polish 2021-12-07 18:46:58 +00:00
c21d4d1811 [CSS] Added webkit required vendor specific prefix for mask image 2021-12-07 18:46:07 +00:00
b0fea51251 [TWIG][Templates] Clearer visual feedback that a note is a reply 2021-12-07 16:17:49 +00:00
4657a1d6a5 [COMPONENTS][Tag] Fix actor tag template
[PLUGINS][RelatedTags] Fix actor tag template
2021-12-07 15:35:27 +00:00
0bac6a229e [COMPONENTS][RightPanel] Added 'Additional options' details on posting form
[CSS] Trimming down wasted space and vendor related prefixes

[TWIG][Templates] Added main navigation to navigation card, removed note car language short display
2021-12-07 15:33:02 +00:00
92314403bb [CSS] Preventing note actions extra from overflowing 2021-12-06 23:38:38 +00:00
546c5c84fd [COMPONENTS][Tag] Actor/Note tag template polished
[PLUGINS][RelatedTags] Disable rendering of template when no results are found
2021-12-06 22:22:23 +00:00
ef435b824b [CSS] You can hide Posting section on command now 2021-12-06 21:07:45 +00:00
a6af3a9b7a [CSS] Removed unnecessary box-shadow rules that slowed down performance (see edit feeds page) 2021-12-06 20:56:06 +00:00
cd607ce6ce [CSS] Note actions extra line height removed, margin-bottom used instead
[CSS] Anchors use underline again on hover
2021-12-06 20:47:56 +00:00
6303f480f7 [TWIG] Note actions extra structural changes to reflect standard note actions styling
[CSS] Note actions extra details styling done
2021-12-06 20:35:44 +00:00
cb276aee81 [CSS] Screen media queries are now clearly documented and calculated using various takes on the Van de Graaf Canon and Tschichold’s recommended 2:3 page-size ratio 2021-12-06 19:07:59 +00:00
330e09f2d3 [PLUGIN][ActivityPub] Only store a new object if there were no previous activities with it before 2021-12-05 21:09:30 +00:00
5196b669b9 [PLUGIN][ActivityPub] Add attachment support to Notes 2021-12-05 21:04:20 +00:00
63bf93d7f3 [CORE][Entity][Note] Language can be null 2021-12-05 21:03:13 +00:00
19b8a7648e [PLUGIN][ActivityPub] Implement Avatar support 2021-12-05 20:08:14 +00:00
2a161c9c66 [PLUGIN][TagBasedFiltering] Block actor tags, but don't block notes from the current actor 2021-12-05 19:18:57 +00:00
259e07b259 [ENTITY][ActorTag][ActorTagBlock] Add 'use_canonical' column 2021-12-05 17:55:49 +00:00
9f445632b2 [PLUGIN][TagBasedFiltering] Expand to allow filtering by actor tags 2021-12-05 17:55:49 +00:00
e29e1cc87c [ENTITY] Rename 'getFrom' to 'getBy' 2021-12-05 17:55:46 +00:00
c40866ecf6 [PLUGIN][TagBasedFiltering] Add TagBasedFiltering plugin, which allows filtering feeds by note tags and (soon) actor tags 2021-12-05 17:54:58 +00:00
4f669d4e01 [ENTITY][NoteTag][Language] Add convinience cache getters 2021-12-05 17:54:58 +00:00
a47a01abee [ENTITY][ActorTagBlock][NoteTagBlock] Add ActorTagBlock and NoteTagBlock 2021-12-05 17:54:58 +00:00
6b719daa14 FIXUP POSTING TAG COMP 2021-12-05 17:54:58 +00:00
8a495bd714 [CONTROLLER][Feeds][EVENT] Refactor and add 'FilterNoteList' event 2021-12-05 17:54:58 +00:00
2f5bde913c [COMPONENT][Posting][Tag] Add mechanism for adding extra fields to and handling the data from the Posting form. Add 'use canonical tag' field 2021-12-05 17:54:58 +00:00
314859b775 [ENTITY][NoteTag] Add 'use_canonical' column, which indicates whether the user wanted to canonicalize the tag or not (for themselves, the canonical field is still filled, for blocks) 2021-12-05 17:54:58 +00:00
1457aa8220 [UI][PLUGIN][DeleteNote][EVENT] Add 'AddExtraNoteActions' event, which can be leveraged to add extra actions on each note, but which are normally collapsed 2021-12-05 17:54:58 +00:00
969df371dd [COMPONENT][Avatar] Small refactor 2021-12-05 17:54:58 +00:00
9512890264 [PLUGIN][ActivityPub] Implement Actor Update
Diverse minor bug fixes
2021-12-05 03:11:08 +00:00
9506909e7a [COMPONENT][FreeNetwork] Iterate documentation 2021-12-04 21:05:09 +00:00
778cb57d83 [PLUGIN][ActivityPub] Finish base ActivityStreams 2.0 interface
Instructions below

To extend an Activity properties do:

public function onActivityPubValidateActivityStreamsTwoData(string $type_name, array &$validators): bool {
    if ($type_name === '{Type}') {
        $validators['attribute'] = myValidator::class;
    }
    return Event::next;
}

The Validator should be of the form:

use ActivityPhp\Type;
use ActivityPhp\Type\Util;
use Plugin\ActivityPub\Util\ModelValidator;

class myValidator extends ModelValidator
{
    /**
     * Validate Attribute's value
     *
     * @param mixed $value from JSON's attribute
     * @param mixed $container A {Type}
     * @return bool
     * @throws Exception
     */
    public function validate($value, $container): bool
    {
        // Validate that container is a {Type}
        Util::subclassOf($container, Type\Extended\Object\{Type}::class, true);

        return {Validation Result};

To act on received activities do:

public function onActivityPubNew{Type}(&$obj): bool {

To add information to Activities being federated by ActivityPub do:

public function ActivityPubAddActivityStreamsTwoData(string $type_name, &$type): bool {

To implement an ActivityStreams 2.0 representation do:

public function onActivityPubActivityStreamsTwoResponse(string $route, arrray $vars, ?TypeResponse &$response = null): bool {
        if ($route === '{Object route}') {
                $response = ModelResponse::handle($vars[{Object}]);
                return Event::stop;
        }
        return Event::next;
}
2021-12-04 21:05:07 +00:00
044649c745 [PLUGIN][VideoEncoder] Some videos don't have images (video stream), only audio, handle that 2021-12-03 03:32:44 +00:00
4501b7e85e [CONTROLLER][UserPanel] Re-organised all settings forms. Added email, password, language forms separated from account or personal account info
[CORE][Form] Better PHPDoc and used is_null() for checks

[ENTITY][LocalUser] Add setNicknameSanitisedAndCached

[UTIL][Exception] Better NicknameNotAllowedException default message
2021-12-03 03:32:43 +00:00
e16fade490 [CONFIGURATION] Add webp to attachments:supported whitelist 2021-12-03 03:32:43 +00:00
582519e13e [COMPONENT][Attachment] Do not show download links for non-local attachments 2021-12-03 03:32:43 +00:00
ff5f346fec [PLUGINS][Oomox] Further checks done when handling form requests. Improved documentation, fixed typos and diminished repeated calls 2021-12-03 03:32:38 +00:00
ba632b4514 [CSS] Note content text decoration underline set on anchor links 2021-12-03 01:19:09 +00:00
b66873e289 [PLUGIN][StoreRemoteMedia] Do not save empty files 2021-12-03 01:16:57 +00:00
70ed04a7db [COMPONENT][Link] Fix some minor issues with empty headed links, typo in event handler's name, and refactor entity to inside component 2021-12-03 00:46:52 +00:00
56d653d980 [CSS] Adding margin-left to .note-author-nickname 2021-12-02 22:55:01 +00:00
bded039282 [PLUGIN][Embed] try catch absence of favicon 2021-12-02 22:49:27 +00:00
d802af6d91 [COMPONENTS] Accesskey highlight class set 2021-12-02 22:44:58 +00:00
a1b002659f [CSS] Focusable elements only show accent inset shadow when focused by keyboard, not by any other way 2021-12-02 22:40:27 +00:00
2e11001b8f [PLUGIN][Embed] Fix links 2021-12-02 22:21:49 +00:00
e5f09a26d4 [CSS] Ultra widescreen dimensions fix 2021-12-02 22:21:49 +00:00
6ce78141a8 [CONTROLLER][UserPanel] Actor nickname cache is updated 2021-12-02 22:21:47 +00:00
5130e7e70f [COMPONENT][Attachment] Update routes to use /object/ namespace 2021-12-02 21:26:06 +00:00
2445b5318d [PLUGIN][Embed] Make it work when content-length header is not provided 2021-12-02 21:26:06 +00:00
bfec10fc95 [COMPONENT][Attachment][Entity][Attachment] getThumbnail can be null 2021-12-02 21:26:05 +00:00
2967b544f5 [CONTROLLER][UserPanel] Nickname now normalized 2021-12-02 20:49:10 +00:00
12557a1e16 [PLUGINS][Oomox] Reset theme colors added 2021-12-02 20:26:27 +00:00
863cfbdedc [CONTROLLER] Old password input type set to password
[CONTROLLER] Notification form fields requirement set to false
2021-12-02 19:32:39 +00:00
f5ec099e9a [COMPONENT][Tag][Search] Fix typo in event handler name mismatch 2021-12-02 19:17:37 +00:00
772ec6efcf [UI] Set html img width and height attributes for avatar 2021-12-02 19:13:33 +00:00
92d5f3ec1e [COMPONENTS][Avatar] Delete correct cache key on avatar update 2021-12-02 19:13:23 +00:00
d915b4b628 [CSS] Note language note view polish 2021-12-02 18:36:18 +00:00
f35dbbd8c1 [COMPONENTS][Search] Polished template
[COMPONENTS][Search] Fix event typo
2021-12-02 16:37:17 +00:00
5cd96669fd [COMPONENTS] LeftPanel and RightPanel checkbox hack trick
The anchor used for the accessibility menu preceded the left/right panel sections, rendering the checkbox useless
2021-12-02 15:49:09 +00:00
9482bb2254 [CSS] Avatar max-height defined 2021-12-02 15:30:24 +00:00
eeaad19754 [Attachment] Move Controller and Entities to a Component
There's no problem in having the templates in the core
2021-12-02 15:14:07 +00:00
37ef8cddfa [ENTITY][Note] Add getLanguageLocale() and getNoteLanguageShortDisplay()
[CARDS][Note] Render note's language short display

[PLUGINS] Bring back titles to Reply, Repeat and Favourite actions
2021-12-02 15:05:49 +00:00
d044039272 [FreeNetwork] Initial multi-protocol support 2021-12-02 14:23:21 +00:00
dbaee08038 [FreeNetwork] Move mentions logic from AP to FN and handle local webfinger mentions properly 2021-12-02 11:12:04 +00:00
53c46127c1 [ActivityPub][Explorer] Store remote's url properly 2021-12-02 11:12:03 +00:00
30f3e2c462 [FEEDS][Home] Fix bug nickname is not unique, but it is for local users 2021-12-02 11:12:03 +00:00
d64bd17422 [COMPONENTS][LeftPanel] Fix template filename 2021-12-01 22:19:00 +00:00
eeb42ef8ea [CONTROLLER][Actor] Provide template with all notes by actor
[ENTITY][Note] Add getAllNotesByActor

[TWIG] Actor profile page now renders all notes by the actor
2021-12-01 21:41:41 +00:00
6bcd42a3a7 [TWIG][Base] Remove user has to be logged in condition when rendering right panel. 2021-12-01 21:15:31 +00:00
5d8bd6c74a [TWIG][Note] Add single note view 2021-12-01 21:07:14 +00:00
6f543ccc06 [ActivityPub][Model][Activity] Translate including objects 2021-12-01 20:53:51 +00:00
e7ee558f4a [TWIG] Fix empty accessibility menu anchor, accesskey anchors placement
[CSS] Dark theme hover/focus border, settings page form visibility
2021-12-01 20:29:11 +00:00
030c966b74 [DOCKER][Accessibility] Add pa11y docker image for automated accessibility tests
TODO: Testing within local social container
2021-12-01 20:29:06 +00:00
dcbb3488c4 [PLUGINS][Reply] Fix onAppendCardNote return 2021-12-01 19:47:45 +00:00
fa7fa81e35 [ICONS][Edit] Add icon class twig block
[CSS] Move edit feed link aligment rules to base.css
2021-12-01 19:47:45 +00:00
4155926ebf [CSS] Removing arbitrary unit values 2021-12-01 19:47:44 +00:00
db0909a1c7 [CSS] Fix color swatch styling, simplified rules 2021-12-01 19:47:44 +00:00
74bab8e7aa [COMPONENTS][Left][CONTROLLER][EditFeeds] Add missing action and method to <form> 2021-12-01 19:47:44 +00:00
b7fe924bdd [TOOLS][DOCS] Add missing doc blocks, as signaled by doc-checker 2021-12-01 19:47:44 +00:00
d58483a6ca [TOOLS] Cleanup PHPStan warnings 2021-12-01 19:47:43 +00:00
0b57b20d38 [UI][PLUGIN][Reply][Favourite][ENTITY][Feed] Remove replies and favourite links from navigation/view.html.twig and add them to the feeds section 2021-12-01 19:47:43 +00:00
36613a826d [CONTROLLER][PLUGIN][Reply] Move reply controller to it's plugin 2021-12-01 19:47:43 +00:00
73981030fa [CONTROLLER][COMPONENT][Left] Move edit feeds controller to the Left component 2021-12-01 19:47:43 +00:00
475bb1a033 [UTIL][EXCEPTION][BugFoundException] Use only the relative path, for convenience and to avoid potentially leaking the sysadmin's user 2021-12-01 19:47:42 +00:00
d4c77925d2 [CORE][DB][ENTITY][Actor] Make DB::dql return a chunked array if selecting multiple entities, remove partitioning from callsite
`DB::dql('select a, b, from a join b')` would previously return `[a,
b, a, b, ...]` (or even `[b, a, b, a, ...]`), and now will return
`[[a, a, ...], [b, b, ...]]`. The issue would be further compounded
when selecting even more entities, where the order would be
unpredictable
2021-12-01 19:47:42 +00:00
6c7f69dd94 [ENTITY][Actor] Partition the results of the joint query into a separate list of ActorsCircles and ActorTags, as desired 2021-12-01 19:47:42 +00:00
a3e5f7646c [CONTROLLER][UserPanel] Use only the ActorTags in the settings 2021-12-01 19:47:42 +00:00
3fbd2cd2b9 [PLUGIN][RelatedTags] Remove duplicate results 2021-12-01 19:47:41 +00:00
4f7e243bee [CORE][DB] Don't do column renaming if a list of entities is specified 2021-12-01 19:47:41 +00:00
ed5f6b6eed [COMPONENTS][Search][UI] Use base template and macros to display search results 2021-12-01 19:47:41 +00:00
424df54a1b [ActivityPub] Add HTTP Signatures 2021-12-01 19:47:41 +00:00
123544fa50 [ActivityPub] Port Postman 2021-12-01 19:47:40 +00:00
df3fbbc9e7 [ActivityPub] Add ActivityToType
Minor bug fixes
2021-12-01 19:47:39 +00:00
4ddd00a091 [NOTIFICATION] Add FreeNetwork distribution 2021-11-30 00:48:46 +00:00
84217ec866 [CSS] Fix multiple select box styling 2021-11-29 23:12:10 +00:00
f92c00c7aa [CONTROLLER][Feeds] Add block prefixes to distinguish form groups
[TWIG] Divide form into various groupings for clearer representation
2021-11-29 23:12:09 +00:00
c8ba81897c [PLUGINS][Reply] getReplyToNote performance improvement
[CSS] Replies padding fix
2021-11-29 23:12:09 +00:00
01078e20fb [PLUGINS][Reply] Fix return on getReplyToNote
The array contained an object, the result was within that object and not the object itself.
2021-11-29 23:12:09 +00:00
db33800ade [PLUGINS][Reply] Array key 'reply_to' checked if it exists.
Plugin\Reply\Entity\NoteReply::getReplyToNote lacked a check to make sure the 'reply_to' key existed within the array resulting from the query.
2021-11-29 23:12:09 +00:00
98568b6f53 [ENTITY][Actor][ActorTag] Make Actor->getSelfTags and Actor->getOtherTags return [ActorCircle[], ActorTag[]], rather than ActorCrircle alone 2021-11-29 23:12:07 +00:00
3477ad5efc [PLUGINS][RelatedTags] Add related tags plugin and needed infrastructure. Initial work on pinned content 2021-11-29 22:42:51 +00:00
3227018963 [CORE][DB] Allow specifying the entites to be retrieved, as when using renaming, tables in join would attempt to be selected 2021-11-29 22:42:50 +00:00
5c3d561a67 [COMPONENTS][Tag] Refactor Tag and add self tag stream 2021-11-29 22:42:50 +00:00
6680772e47 [UI][I18N][UTIL][FormFields][Language][COMPONENTS][Posting][PLUGINS][Reply] Factor out translation from FormFields::language and remove help text in cases from Posting and Reply 2021-11-29 22:42:50 +00:00
fc81f7301c [CORE][DB][ENTITY][Actor] Add DB::removeBy and use it in Actor->setSelfTags 2021-11-29 22:42:50 +00:00
66ff3c594d [UTIL][Formatting] Fix wrong event name 2021-11-29 22:42:49 +00:00
8f5b404941 [CACHE] Switch to 'empty' rather than 'is_null' is Cache::getHashMapKey, as it may return null or false 2021-11-29 22:42:49 +00:00
f986f59424 [ENTITY][ActorTag] Add 'canonical' field to actor_tag 2021-11-29 22:42:49 +00:00
798a5f3796 [PLUGINS][Oomox] HTML's input[type='color'] doesn't support alpha values. Feature to apply custom shadow color removed as a result. 2021-11-29 22:42:48 +00:00
7df2783686 [CSS] Fixed edit feed icon styling. 2021-11-29 22:42:48 +00:00
6ec0b9f077 [CSS] User panel CSS fixes.
[PLUGINS][Oomox] Reformatted file.
2021-11-29 22:42:48 +00:00
04257c5fd9 [PLUGINS][Oomox] Resulting forms are now aware of user defined colours and fallback to defaults. 2021-11-29 22:42:48 +00:00
3da524af58 [PLUGINS][Oomox] WIP Settings for both the light and dark themes. 2021-11-29 22:42:47 +00:00
56526c9ba6 [ActivityPub][Inbox] Restore Create Note Functionality
Minor bug fixes
2021-11-29 22:42:46 +00:00
7145dba8af [PLUGINS][Oomox] getEntity added.
[CORE][Cache] exists added.
2021-11-27 15:14:17 +00:00
7b9d388a44 [NOTIFICATION] Implement Target Collector 2021-11-27 15:14:15 +00:00
51994406da [CORE][ENTITY] Properly port ProfileTag, ProfileTagSubscription and ProfileList as ActorTag, ActorTagSubscription and ActorCircle 2021-11-27 04:17:18 +00:00
11d2cfb9ed [UI][FEEDS][ENTITY][Feed] Add way to customize the feeds that are displayed in the left panel. The user can add, delete, reoder and rename them 2021-11-26 23:35:10 +00:00
cdc8886bb8 [CORE][DB] Rename parameters 'orderBy' to 'order_by' 2021-11-26 23:34:37 +00:00
42f40f9ebe [CORE][CONTROLLER][Network] Refactor term 'network' into 'feeds' 2021-11-26 23:34:37 +00:00
0ab8febab3 [CORE][DB] Document magic methods 2021-11-26 15:11:51 +00:00
d46a6163a0 [PLUGINS][Oomox] Hotfix: cache wasn't set. Proper labels. 2021-11-26 15:01:30 +00:00
180ae15647 [PLUGINS][Oomox] User theme CSS file is served. Settings page needs polish, and option to revert changes. 2021-11-26 14:45:28 +00:00
7fa2418e54 [PLUGINS][Oomox] Theme colours WIP. 2021-11-26 14:45:28 +00:00
6cd7be6abc [UI][CONTROLLER][ENTITY][DOCS] Refactor term 'timeline' into 'feed' 2021-11-26 13:05:23 +00:00
283820a4a5 [UI][ENTITY][Language] Use list rather than hashmap so actor language ordering is preserved 2021-11-26 12:30:21 +00:00
04e6b2fb53 [ENTITY][Language] Properly handle context actor language 2021-11-26 12:29:27 +00:00
d7a71ebe33 [UI][ENTITY][Language] Extend base template for user panel language sorting 2021-11-26 12:28:03 +00:00
6919f38592 [UI][COMPONENT][Tag] Use base template and properly display notes in tag stream 2021-11-26 11:59:11 +00:00
c178054433 [COMPONENT][Tag] Add stream for multiple tags 2021-11-26 11:48:35 +00:00
edf1b30e89 [TOOLS][DOCS] Add missing doc blocks 2021-11-25 23:16:04 +00:00
b1262919da [TOOLS] Fix (most) issues found by PHPStan 2021-11-25 23:08:30 +00:00
8fd02ef152 [TOOLS][COMPONENT][FreeNetwork][PLUGIN][ActivityPub] Temporarily exclude FreeNetwork and ActivityPub from PHPStan analysis, since it's a WIP 2021-11-25 20:37:53 +00:00
98b719dca3 [COMPONENTS][Tag] Split tag into words and stem each 2021-11-25 20:37:53 +00:00
4571b18c60 [DEPENDENCIES] Update dependencies 2021-11-25 20:37:53 +00:00
a64c488e21 [PLUGINS] Add StemWord plugin, which stems words, given a language. Currently used for tags 2021-11-25 20:37:53 +00:00
2d057024b9 [TAGS][ENTITY][Note] Properly store the note language, pass it along when rendering content. Add mechanism for stemming tags, with fallback to simply slug-ifying them 2021-11-25 20:37:53 +00:00
f837df5753 [CACHE] Add way of calculating hash map if key is not found 2021-11-25 20:37:53 +00:00
e64fd5aaf6 [TOOLS] Exclude src/PHPStan from test coverage 2021-11-25 20:37:53 +00:00
c1779dc12d [UI] Don't display short language form in user panel language selection 2021-11-25 20:37:53 +00:00
0194b6b14c [TESTS] Fix tests by adding missing is_local columns and by login in the admin user in the admin panel test 2021-11-25 20:37:52 +00:00
d9544c6edb [CORE][SECURITY] Move to the new authentication format, for Symfony 5.3 2021-11-25 20:37:45 +00:00
05758c999f [ENTITY][Actor] fix typo in findRelativeActors query 2021-11-25 02:07:12 +00:00
d9c0a72e36 [CONFIG] Replaced form theme used since it was too opinionated. 2021-11-24 14:03:50 +00:00
477a5cb92d [CSS] Fixed all buttons/select/input display rules from being overriden. 2021-11-24 12:46:32 +00:00
d8147cbd2d [PLUGINS][AttachmentShowRelated] Fixed note template error. Fixed if statement.
[CORE][Controller] Commented CSP out.
2021-11-24 12:46:32 +00:00
8edaabbabf [TWIG][Settings] WIP Refactoring of user panel. Now using macros to diminish repeated code.
[CONTROLLER][UserPanel] Replaced form names. More readable.
2021-11-24 12:46:31 +00:00
d5fc2cac8a [TOOLS] Make Makefile rule database-force-nuke stop and restart the worker container 2021-11-24 12:46:31 +00:00
30f4131f5d [TESTS] Add missing 'is_local' to actors in data fixtures 2021-11-24 12:46:31 +00:00
206856e1ba [FORM][FormFields] Add way of specifying attributes for password fields (namely form autocomplete=new-password) 2021-11-24 12:46:31 +00:00
3a5e52ee0d [CORE][SECURITY] Move to the new authentication format, for Symfony 5.3
Keep using (deprecated) Guard
2021-11-24 12:46:26 +00:00
b4ce77320e [TESTS] Fix remaining tests, back to 100% passed. Some minor semantic changes 2021-11-20 21:33:25 +00:00
c68d7ae406 [CONTROLLER][UserPanel] Make function names camelCase 2021-11-20 21:33:25 +00:00
7e4a971fac [ENTITY][LocalUser][CACHE] Remove bad keys from getByNickname cache key 2021-11-20 21:33:25 +00:00
0e104a9701 [UTIL][HTML][TESTS] Fix test and implementation and expand HTML generation utilities 2021-11-20 21:33:25 +00:00
fe755f7c42 [UTIL][FormFields] Accomodate use of FormFields::repeated_password without a 'required' option 2021-11-20 21:33:23 +00:00
1dd86a2302 [TESTS] Many tests fixes (already fixed the dependant code) 2021-11-20 21:32:25 +00:00
d254147988 [CSS] Note attachments fixes, fancy note-complementary accents. 2021-11-17 17:28:46 +00:00
0aa43783e8 [PLUGIN][Repeat] Repeat now repeats attachments as it should.
[COMPONENTS][Posting] Now accepts attachments already processed.
[ENTITY][Note] Added getAttachmentsWithTitle().
2021-11-17 17:14:15 +00:00
8077bdb0b5 [CORE][Controller] CSP default-src changed to 'self' to allow internal redirects. 2021-11-17 01:29:36 +00:00
1d31bd651e [CORE][Controller] Added Content-Security-Policy response header. 2021-11-17 00:49:23 +00:00
e1b9ab4b9a [TWIG] Added CSP in base template meta tag. This isn't optimal. 2021-11-16 23:48:12 +00:00
f07dce4604 [UTIL][Form] Fix bug with repeated_password 2021-11-16 23:27:29 +00:00
89d36a68e5 [ENTITY][Actor] Add is_local, it's common to depend, and this makes it much faster, with a low space cost 2021-11-16 23:26:20 +00:00
f1a30ac0e6 [CSS] Added a top margin for buttons. 2021-11-16 19:40:07 +00:00
2561823550 [PLUGIN][Repeat] Fixed corner case where the user would return to repeat form page and try to repeat the note again. 2021-11-16 19:39:03 +00:00
acc43a276b [PLUGIN][Reply] User's own replies wont display their own nickname on rendering the original note. 2021-11-16 19:36:17 +00:00
0dd4b62ded [TOOLS][DOCKER] Add worker container and script, which handles the queues 2021-11-15 19:25:53 +00:00
5e4ada7b78 [DEPENDENCIES] Update symfony to 5.3, which fixes a bug with the doctrine postgres message worker 2021-11-15 19:25:53 +00:00
b8b19abed2 [ENTITY][AttachmentThumbnail] Do not attempt to check if a file exists if the path is null 2021-11-15 19:25:52 +00:00
5cafc80d01 [UTIL][FormFields] Refactor duplicated language choice form entry logic 2021-11-15 19:25:52 +00:00
e6c0db9ee1 [CORE][Cache] Allow retrieving multiple keys from a hashmap 2021-11-15 19:25:52 +00:00
587d701d11 [CONTROLLER][Network] Temporarily remove replies from streams, following planned changes to the Reply plugin 2021-11-15 19:25:52 +00:00
e9cc760ca8 [COMPONENTS][Posting][Right] Fix TypeError (which somehow only popped up in tests) caused by assigning a FormView to an out array param 2021-11-15 19:25:52 +00:00
9dbbc9e18e [PLUGIN][Repeat] Fixed bug where checks dependant on user being logged in were attempted. 2021-11-15 19:25:52 +00:00
6e90e51f0c [PLUGIN][CONTROLLER][Repeat] Fixed Repeat controller to update note_repeat table on removal. 2021-11-15 19:25:52 +00:00
b71e869843 [PLUGIN][Repeat] onAppendCardNote added. getNoteRepeats implemented. 2021-11-15 19:25:52 +00:00
774eb49af4 [UI][CONTROLLER][UserPanel][ENTITY][ActorLanguage][Language][Actor] Add interface to allow user to select thier preferred languages and to order them. Rename ActorLanguage::order to ordering 2021-11-15 19:25:52 +00:00
83fc31b485 [CSS] Polished light theme. 2021-11-15 19:25:51 +00:00
2d941cbb16 [CSS] Fixed Chromium from overriding the page stylesheet on fieldset element. 2021-11-15 19:25:51 +00:00
337fe272a3 [CSS] Re-arranged CSS rules, overall refactor to minimize duplications. 2021-11-15 19:25:51 +00:00
a23f7d5aa3 [CSS] Removed 'colors.css', colors are declared within their respective classes/ids instead. 2021-11-15 19:25:51 +00:00
d5e6fd603d [CONTROLLER][UserPanel] Fix 'could not convert IntergetType to string' error 2021-11-15 19:25:51 +00:00
1abd28c949 [UI][TWIG][CONTROLLER][UserPanell] Add way to expand details with a GET parameter. Implement it in UserPanel 2021-11-15 19:25:51 +00:00
c509692102 [ENTITY][AttachmentThumbnail] Uncache when deleting, cleanup code and ensure the biggest thumbnail is used when the original is not avaliable 2021-11-15 19:25:51 +00:00
a3074662b8 [TESTS] Remove 'reply_to' in note creation in data fixtures 2021-11-15 19:25:51 +00:00
eab6de3609 [TESTS][Security] Fix SecurityTest. Remove nickname normalization on register (a plugin can handle that). Move from filter_var(FILTER_VALIDATE_EMAIL) as it does not support dotless domains 2021-11-15 19:25:50 +00:00
bf5ffe7d3d [CACHE][TEST] Fix errors in cache implementation found by tests 2021-11-15 19:25:44 +00:00
98352cfece [TESTS] Properly provide both createClient and bootKernel in test base class 2021-11-11 12:39:36 +00:00
2239845a00 [CONTROLLER][Security] Remove nickname normalization when trying to login 2021-11-11 12:39:36 +00:00
a1c78696f7 [CORE][Actor][Posting] Fixup Actor::getPreferredLanguageChoices following changes in how the data is cached 2021-11-11 12:39:36 +00:00
316723075b [TESTS] Reload the initial table values after loading the data fixtures (as this purges the DB) 2021-11-11 12:39:36 +00:00
f39f800a8e [TESTS] Add now-required content-type to notes created in data fixtures 2021-11-11 12:39:35 +00:00
f667b558f7 [TESTS] Fix SecurityTest
This test was broken by changes in the routing and in the templates.
However, this revealead a potential open redirect and duplicated code
in the Reply and Favourite plugins
2021-11-11 12:39:28 +00:00
dea9aa4dcf [CORE][Router] Remove duplicate service for URL generation, as that is actually the same object 2021-11-11 12:38:09 +00:00
420ebcda26 [TESTS] Run tests without runuser as it's generating very weird permission errors. This is temporary (TM) 2021-11-11 12:38:09 +00:00
d0f9fde7c2 [PLUGIN][Reply] WIP. Note complementary info now shows who has replied on the original note! 2021-11-10 15:44:28 +00:00
f2f1bdc145 [PLUGIN][Reply] Separated replies from Note table.
[PLUGIN][Repeat] Deleted unnecessary card note template, info now to
appended at the end of note.
[PLUGIN][TreeNotes] WIP to accomodate reply plugin changes.
[TWIG][Runtime] Removed getAdditionalTemplateVars event.
2021-11-10 13:29:53 +00:00
7d8819a3da [DB][CONTROLLER][Network][ENTITY][Note] Fix Note::getAllNotes 2021-11-09 23:38:37 +00:00
c3705112ba [DEPENDENCIES] Update dependencies 2021-11-09 23:38:37 +00:00
98b1b7072c [TOOLS] Fix running tests without filters 2021-11-09 23:38:37 +00:00
f0c532340e [DB] Fix uses of DB::sql, to remove the deprecated second entities parameter 2021-11-08 20:35:38 +00:00
767b2035e7 [ENTITY][ActorLanguage] Remove duplicate specification of 'not null' in table schema 2021-11-08 20:32:10 +00:00
1d84f1629e [CORE][DB] Make DB::sql not error when selecting from tables with columns of the same name and remove the second enitites paramter, calculating it internally instead 2021-11-08 20:32:10 +00:00
f98afd15ce [ENTITY] Refactor Follow as Subscription 2021-11-08 16:14:23 +00:00
68c6dd1ba9 [CORE][Cache] Use hashmaps to store language related items 2021-11-08 16:08:04 +00:00
2eb31952bc [ENTITY][Language] Use varchar as char leads to a padded string, which isn't helpful 2021-11-08 16:08:03 +00:00
2cf3a0b4e6 [COMPONENT][Posting] Display short language signifier rather than the full name for the first preffered language 2021-11-08 16:08:03 +00:00
705bf815ab [CACHE] Add partial implmentation for caching hashmaps (non-list arrays, i.e. array<string,string>). No non-redis-fallback yet 2021-11-08 16:08:03 +00:00
cd470cbf93 [CORE] Fix loading of settings from modules 2021-11-08 16:08:03 +00:00
019ad794d1 [Posting] Add dropdown with language choice, with preferred choice according to user choice and context (group, etc) 2021-11-08 16:08:02 +00:00
9444c34071 [ENTITY][Actor][ActorLanguage][Language] Remove Actor::preferred_lang_id. Add ActorLanguage::order. Add Language::{short_display,long_display}. Instead of an actor having a single preffered language, the entries in ActorLanguage should be used, sorted by ActorLanguage::order 2021-11-08 16:08:02 +00:00
2bd05fbd47 [TOOLS][COMMAND] Add 'app:populate_initial_values' command, which inserts values into the language table if it does not yet contain values. Add database-force-nuke to Makefile, which does all the steps necessary to reset the database 2021-11-08 16:08:02 +00:00
1960f6944f [CORE][DB] Fix error in regex that prevented selecting collumns that are named the same way a column is 2021-11-02 11:14:59 +00:00
839fa070c7 [CORE][Posting] Default Posting language to site language, if the user hasn't selected one 2021-11-02 11:14:59 +00:00
d9265c5402 [TEMPLATES][Profile] Use URI instead of URL 2021-11-02 08:34:44 +00:00
c4088e221f [CSS] Very important colour change. 2021-11-01 22:09:10 +00:00
cf09b48e92 [PLUGINS][Repeat] Added note_repeat entity, fixed visual discrepancies, and completed the expected functionality.
[ENTITY][Note] Removed repeat_off from table. It is now part of the Repeat plugin.
2021-11-01 21:19:56 +00:00
73e772e576 [TOOLS] Allow specifying a list of filters when running tests with make. When running make test foobar only the test foobar is executed (read the documentation for phpunit --filter) 2021-11-01 12:16:50 +00:00
c862c9bf18 [ActivityPub] Make remote mentions great again 2021-11-01 12:16:46 +00:00
712d1739e4 [UTIL][Formatting] Make local mentions great again 2021-11-01 12:16:29 +00:00
91dd6e1428 [Controller][Security] Fullname is not setup automatically upon registering anymore.
[ENTITY][Actor] Changes to accomodate fullname from potentially being null.
[ENTITY][Note] Changes to accomodate fullname from potentially being null.
2021-10-29 22:05:10 +01:00
ac8513741d [CSS] New themes! No images used anymore. 2021-10-29 22:01:28 +01:00
eb5bc36390 [CSS] Fixed widgets from overflowing. 2021-10-29 18:14:39 +01:00
7fef18e95a [COMPONENTS][Posting] LocaleType::class doesn't have a default attribute, replaced with 'preferred_choices' instead. 2021-10-29 17:46:25 +01:00
9a23e03330 [PLUGINS][Repeat] Repeat template override WIP. 2021-10-29 17:26:20 +01:00
0f358a9c5d [CORE][Posting] Add language choice field to the note posting block 2021-10-28 17:37:00 +01:00
44454ac28a [UTIL][TemporaryFile] Ensure resource is neither false nor null when attempting to cleanup, otherwise getRealPath returns false and we get sad 2021-10-28 17:36:02 +01:00
458c09485a [CORE][ENTITY] Move preferred language setting from [LocalUser] to [Actor], make [Language] language unique and make [Note] content_type not null 2021-10-28 17:34:01 +01:00
e6c5312025 [CORE][GSFile] Add check_is_supported_mimetype option to GSFile::storeFileAsAttachment 2021-10-28 17:29:57 +01:00
4d9a5aae5a [ActivityPub] Always explicitly compare the results of Event::handle to the constants next or stop 2021-10-28 17:28:02 +01:00
86ac5c52a1 [TESTS] Add to the supported configuration list, the mimetypes of all sample-upload files 2021-10-28 17:26:57 +01:00
8238980395 [TOOLS] Allow specifying a list of filters when running tests with make. When running make test foobar only the test foobar is executed (read the documentation for phpunit --filter) 2021-10-28 17:24:43 +01:00
a55d60d880 [DEPENDENCIES] Update dependencies 2021-10-28 14:31:56 +01:00
c352e40518 [CORE][Controller] Allow plugins to override the Twig templates 2021-10-28 14:29:34 +01:00
18aeeb3850 [ENTITY] Add a [Language] table, which has a char 64 field that maps to an int. This is then used in [Note], [LocalUser] and [ActorLanguage] 2021-10-28 11:26:34 +01:00
3388e0e8f1 [TWIG][Cards] Note template fully refactored. Template has now macros for different views for the card.
[PLUGINS][Repeat] WIP: Action added.
[PLUGINS][Favourite] Changes to accomodate note card template refactoring.
[CSS] Fixed textarea from being resized horizontally.
2021-10-27 20:44:51 +01:00
d47f125894 [PLUGINS][Favourite] Refactored redirection to previous url. User is now unable to do invalid actions (ex. favour an already favourited note).
[PLUGINS][ActivityPub] Fixed favour route id to be more consistent.
2021-10-27 20:44:50 +01:00
e54e55dfbf [EXCEPTION][RedirectException] You are now able to redirect to a specific url. 2021-10-27 20:44:50 +01:00
3e2fefa8af [TWIG][Cards] Fullname is now displayed as the note author, nickname as an identification.
[CONTROLLER][Security] Fullname is set on resgistration to enable it to be shown by default in notes.
[CONTROLLER][UserPanel] Fullname extra step added.
[CSS] Fullname and nickname representation work.
2021-10-27 20:44:50 +01:00
51c984849f [ActivityPub] Port Explorer 2021-10-27 04:22:19 +01:00
5189269e5b [FreeNetwork] Port Discovery 2021-10-27 04:22:18 +01:00
3cdaf6671a [CORE][HTTPClient] Add some shortcut functions inspired by pre-v3 2021-10-27 04:22:18 +01:00
3227e1f919 [CORE][Log] Document magic methods 2021-10-27 04:22:18 +01:00
45f65baf96 [PLUGINS][Embed] Polished embed template in order to better utilize space.
[CSS][Feed] Embed related additions.
2021-10-27 04:19:34 +01:00
0407ac38cf [AudioEncoder] Introduce basic audio plugin to provide an attachment template and duration metadata for audio mimetype 2021-10-27 04:19:34 +01:00
364c14ef2c [COMPONENT][Link] Fix mistake where only the first URL was matched and the match included the preceeding whitespace character 2021-10-27 04:19:34 +01:00
ebf675ec59 [Plugins][FAVOURITE] Redirect added. Only redirects from the route the user came from, not the anchored note. To be added. Further corner cases fixed. 2021-10-27 04:19:33 +01:00
56ba7bd845 [ImageEncoder][VideoEncoder] Properly decide when to take action 2021-10-27 04:19:33 +01:00
bccafd0d7b [CORE][GSFile] Respect mimetype whitelist and extensions blacklist before saving files 2021-10-27 04:19:33 +01:00
b7d9da8ae6 [Posting] Add Content Length constraint to form validation 2021-10-27 04:19:33 +01:00
8038fdbce9 [UTIL][Common] Added Common::getUploadLimit().
[COMPONENT][Posting] Update Posting to warn the user of submtting attachments too large.
2021-10-27 04:19:32 +01:00
656c2c7812 [SECURITY][Authenticator] fix wrong route id on after login redirection 2021-10-27 04:19:32 +01:00
60b15ea79d [Plugins][FAVOURITE] No longer a form, a link to a new page is provided instead. The amount of forms per page were blocking rendering for the majority of its duration. 2021-10-27 04:19:32 +01:00
a6d5752748 [TOOLS][PHPStan] Make a standalone phpstan executable, which executes inside the docker container 2021-10-27 04:19:31 +01:00
299e893ca9 [TOOLS][PHPStan][DocCheck] Fix errors found by PHPStan and Doc Check 2021-10-27 04:19:31 +01:00
769b901060 [TOOLS] Add doc-check target to Makefile 2021-10-27 04:19:31 +01:00
dff5647b97 [DB][Note] Add langauge field to notes 2021-10-27 04:19:31 +01:00
8a10fec31d [CONTROLLER][UserPanel] Make all fields in settings not required 2021-10-27 04:19:30 +01:00
2694d83ae4 [TOOLS][CS-FIXER] Run new PHP CS Fixer config. Notably, adds strict_types 2021-10-27 04:19:30 +01:00
028ea79fff [CORE][Router] Properly act on Accept headers 2021-10-27 04:19:30 +01:00
eli
99fd2f725b [Core][Util] Hotfix: In App\Core\DB\DB persistWithSameId, casting the id to an int. In App\Util\HTML html, tag is now evaluated beforehand, making sure it's a string. 2021-10-27 04:19:30 +01:00
eli
f78cfed41a [TWIG][CSS] Template refactoring, classes and their respective CSS is now more consistent. CSS variables are back. 2021-10-27 04:19:29 +01:00
8fdc52636f [ActivityPub] Port RSA 2021-10-27 04:19:29 +01:00
8544fe157b [FreeNetwork] First steps porting webfinger/lrdd to v3, GET webfinger requests already have a basic result 2021-10-27 04:19:29 +01:00
44cf1fa24c [UTIL][Nickname] Fix some parameters issues found with strict types 2021-10-27 04:19:29 +01:00
5eefea7a29 [TOOLS] Update Makefile to support both the new and old container naming conventions, as of docker-compose v2 2021-10-27 04:19:28 +01:00
b524c5bc90 [TOOLS][PHPStan] Add missing toString on the call to DB::filterName, as it seems something changed in some update 2021-10-27 04:19:28 +01:00
b65ee4c21d [TOOLS][CS-FIXER] Fix incorrect transformation 2021-10-27 04:19:28 +01:00
9109c61af5 [TOOLS][CS-FIXER] Run new PHP CS Fixer config. Notably, adds strict_types 2021-10-27 04:19:28 +01:00
8ef2d3339f [COMPONENTS][Search] Slightly refactor parser, since the inline lambda was somewhat complex (and cs-fixer kept moving the comment, so doc-checker complained) 2021-10-27 04:19:27 +01:00
703e66fd2e [TOOLS] Update PHP CS fixer and adjust configuration 2021-10-27 04:19:27 +01:00
7aad58b440 [DEPENDENCIES] Update composer dependencies 2021-10-27 04:19:27 +01:00
ce0cfa7a63 [COMPONENT][Search] Update Search to be able to search for either notes or actors 2021-10-27 04:19:27 +01:00
d575f8aef5 [UTIL][Formatting] Make it possible to supply string|array to either or both argument of Formatting::{starts,ends}With 2021-10-27 04:19:26 +01:00
99ab24ec23 [CORE][Controller] Allow routes without text/html response
Improve GET getters
2021-10-27 04:19:23 +01:00
03f6029ce5 [SECURITY] Fix nickname validation and properly allow email auth 2021-10-18 13:22:55 +01:00
071b769997 [CORE][Util][Common] Correct behaviour for absense of value 2021-10-18 13:22:45 +01:00
517ed953f2 [FreeNetwork] First step towards de-duplication mechanism for federation
Refactored AS2 inside AP; [ENTITY][Activity] went from core to AP
Webfinger plugin will be part of FreeNetwork component
2021-10-06 11:48:22 +01:00
eli
bd5c426046 [TWIG] Removing unnecessary CSS classes. Removing network/feed CSS preloading. 2021-10-06 00:45:37 +01:00
eli
3cb6563c40 [TWIG][EndShowStyles] Route as an event argument 2021-10-06 00:34:27 +01:00
eli
6412b632ab [CSS] Note actions size further compatibility work. 2021-10-05 23:36:15 +01:00
eli
e467cf5ec2 [CSS] Fixed: note actions size. 2021-10-05 23:29:46 +01:00
eli
31857e1eab [CSS] Fixed: User panel hr elements styling. 2021-10-05 23:00:16 +01:00
eli
1d6b22551b [CSS] Fixed: Body text colors weren´t applied. 2021-10-05 22:51:46 +01:00
eli
d4513e3597 [CSS] Browser compatibility improvements. 2021-10-05 22:44:22 +01:00
eli
78dd7137f8 [PLUGIN] ProfileColor color settings can be null. 2021-10-05 19:12:51 +01:00
eli
a268aee53a [CSS][PLUGIN] ProfileColor now handles both the background and foreground colors. Various button CSS fixes. 2021-10-05 19:04:30 +01:00
eli
5373655170 [CSS] Firefox ESR doesnt know what a colour is. 2021-10-05 16:09:01 +01:00
eli
2a2331d692 [CSS] Colours by classes. Refactored all CSS. 2021-10-05 16:03:17 +01:00
eli
caa04525bd [CSS] Fixed embed width. 2021-10-04 17:16:53 +01:00
eli
1705b543d3 [PLUGIN] Delete note action added. If the author of a note is logged in, the action will be added to that note. Once the author presses it, the note will be deleted from the DB. 2021-10-04 17:11:44 +01:00
eli
70d1521a2b [CSS] Fix: article header was being selected has the page header. 2021-10-04 15:30:01 +01:00
eli
17a13b3f20 [CSS] Fix: Left and right panel checkbox border when active. 2021-10-04 12:31:06 +01:00
eli
0dfb41230e [CSS] Fix: Instance name centered. 2021-10-04 12:20:31 +01:00
eli
80d1be323d [COMPONENTS][Search][CSS] Extra header forms now accessible through a details element. Re-organizing templates structure. 2021-10-04 12:17:14 +01:00
eli
d38ad60c76 [COMPONENTS][CSS] Adding components styling properly. 2021-10-01 17:25:51 +01:00
eli
441c411efe [CSS] Complete re-organization of stylesheets. 2021-10-01 16:37:28 +01:00
19c2a91232 [TOOLS][PHPStan][ProfileColor][ENTITY][TEST][AttachmentThumbnail] Fix issues reported by phpstan 2021-09-27 19:50:56 +01:00
de984ac8e1 [COMPONENTS][Search][Tag] Implement basic search functionality that allows only searching through note tags, currently 2021-09-27 19:50:56 +01:00
1107d8257d [TWIG] Add instanceof test
Use with:
{% if var is instanceof(Namespace\Class) %}
2021-09-27 19:50:56 +01:00
2d8b220e92 [CORE][Controller] Make Controller abstract, handle an optional non static method and use static::class rather than get_called_class 2021-09-27 19:50:56 +01:00
4fef97f930 [CSS] Profile text mix blend difference with background. Browser's cache
was playing tricks on me and wouldn't display the changes accordingly.
2021-09-27 19:40:34 +01:00
ab26162217 [CARDS][Profile] No longer ids, now classes (CSS). Profile info nickname for instance. The inversion filter now actually works. 2021-09-25 22:30:46 +01:00
f8c108bdf3 [CSS] Profile text color doesn't blend in anymore. 2021-09-25 21:42:23 +01:00
fdd43f4b11 [CSS] More consistent padding across input elements. 2021-09-25 21:12:07 +01:00
5249ccfc68 [Posting][CSS] Right panel form render simplified. 2021-09-25 21:02:11 +01:00
3001f91918 [CSS] Very slight shadow on key elements. 2021-09-25 20:36:07 +01:00
ff26831d1e [CSS] Note action icons back to a sane size. 2021-09-25 19:59:06 +01:00
20ae2dba5d [CSS] Reverting desktop side margins. All margins, sizes, radius, etc were redone. 2021-09-25 19:40:18 +01:00
4a17adc182 [PLUGINS][ProfileColor] Actors are now shown with their own colors for other. 2021-09-25 19:40:17 +01:00
7b8eb3fda9 [PLUGINS][ProfileColor] Current color is now selected by default. Not found exception is now handled. 2021-09-25 19:40:17 +01:00
a681acae67 [ENTITY][AttachmentThumbnail] Every image should have width and height attributes 2021-09-25 19:40:11 +01:00
808da203ad [PLUGINS][ProfileColor] Settings page render problem fixed. ColorType given data wasn't a string. 2021-09-25 12:51:25 +01:00
57b94af9f6 [PLUGINS][ProfileColor] Re-organized plugins templates and assets. ProfileColor plugin fixed. 2021-09-25 11:17:56 +01:00
765cf66ff2 [CSS] Simplified window resize media queries. Desktop view's left and right margins were wrong. Some embedded links with empty blocks had padding on them again, fixed. 2021-09-25 09:02:13 +01:00
321d5b4be8 [CSS] Embedded width didn't conform to note's own properly. 2021-09-23 17:22:44 +01:00
fe77dc0996 [CSS] Resetting headers browser styling. Fixing embedded links styling inconsistencies. 2021-09-23 17:15:11 +01:00
3268559f9a [AttachmentThumbnail] Use other thumbnail when requested isn't available and there's no original file 2021-09-23 16:19:50 +01:00
b6d80003d8 [CSS] Note attachments layout fix. Added a very small radius to avatars. 2021-09-23 16:18:23 +01:00
3ef1077f90 [DOCKER] TODOify mail server certificate 2021-09-23 15:47:51 +01:00
be8610a7a1 [CSS] Note attachments uses a grid layout now. 2021-09-23 15:39:14 +01:00
af3531f1c7 [CORE][GSFile] Add type annotation to the $encoders used in attachment handling 2021-09-23 14:54:21 +01:00
bb81f1f717 [Embed] Use new attachment route format 2021-09-23 14:54:21 +01:00
6a2c3eb711 [ImageEncoder] Ensure proper memory limits are used when loading images from disk 2021-09-23 14:54:21 +01:00
1c1bef76ef [Cards][Navigation] Navigation cards! Left panel now composed entirely of cards. 2021-09-23 14:46:18 +01:00
21e598d877 [ENTITY][Actor] Fix issue with deleting a self tag 2021-09-23 14:34:34 +01:00
4820a863a9 [Avatar] Fix cache usage and other minor bugs 2021-09-23 14:34:34 +01:00
64c881173b [Cards][Profile] Fixing nested anchor tags. 2021-09-23 13:53:02 +01:00
a439b7130e [Cards][Profile] Re-organizing elements. 2021-09-22 17:20:06 +01:00
a557ba0224 [Timelines][Plugins][Left] Plugins now provide their path ID. Feed redone as it's own independant template. 2021-09-22 16:47:06 +01:00
05f16a3084 [ENTITY][AttachmentThumbnail] Now thumbnails are always only available in three sizes: small, medium, big
Commit jointly produced with eli (Eliseu Amaro)

Breaking change: Entity changed to only store the tinyint referring to the size stored

With this, the logic was simplified and now it's not possible to make an
instance produce unnecessary thumbs. The aspect ratio is preserved and
thus the thumbs will always look nice. New configuration was added to
maintain flexibility.
2021-09-22 15:13:46 +01:00
7beb5c2995 [GSFile] Fix sanitize configuration 2021-09-22 15:13:46 +01:00
c1e7d486a3 [Posting] Fix bug with early DB::flush that would discard attachments relations 2021-09-22 15:13:44 +01:00
3f618c2674 [TWIG][Cards][Profile] Card templates! Profile now has a card template to be used everywhere you need to show quick actor information. 2021-09-22 14:11:59 +01:00
c6082bab10 [COMPONENT][Tag] Update tag stream to use new pagedStream format 2021-09-21 16:39:36 +01:00
69e7dc44bd [ENTITY][Note] Change isVisibleTo to allow for not supplying an actor 2021-09-21 16:38:50 +01:00
879f54c772 [CACHE] Filter notes by scope in pagedStream
This currently does not return a fixed number of notes per page. Fixing this is left as an exercise to the reader
2021-09-21 16:37:51 +01:00
14c173df7a [CACHE][COMPONENT][Tag] Add generic Cache::pagedStream and use it for the tag stream. Note that it doesn't respect scope yet 2021-09-21 15:35:07 +01:00
6cd86cac25 [COMPONENT][Tag] Add tag stream, with paging 2021-09-21 11:04:27 +01:00
2f3f7b8469 [COMPONENTS][Posting][Link][Tag] Pass the note, not just the id in ProcessNoteContent 2021-09-21 11:04:27 +01:00
7926f18f93 [CONFIG] Add streams:notes_per_page config value 2021-09-21 11:04:27 +01:00
6715a036e9 [CACHE] Add way to fetch limit,offset values from a list 2021-09-21 11:04:26 +01:00
15a87055a6 [DOCKER][Redis] Add way to override Redis config. Default to only one database 2021-09-21 11:04:26 +01:00
ce80065775 [DB] Add mechanism for specifying limit and offset in dql query 2021-09-21 11:04:26 +01:00
91fd7d1cfa [CONFIG][CORE] Fix bug in overriding default config 2021-09-21 11:04:14 +01:00
8bb6285522 [ENTITY][Note] A note may have no content 2021-09-20 17:06:21 +01:00
b7298eaa44 [ENTITY] ActorTag: use getter for tag name on toString 2021-09-20 17:05:50 +01:00
9e4c43e8fd [ActivityStreamsTwo] Further work on routes and use render event on note's content 2021-09-20 17:05:04 +01:00
7813723ca1 [ActivityPub] Inbox must work without actor, specify source for AS2 2021-09-20 17:03:23 +01:00
958cbffb91 [Posting] Add text/html content type, must actually treat it 2021-09-20 17:02:35 +01:00
85969a8cff [Avatar] Add default avatar route and improve url getter 2021-09-20 17:01:36 +01:00
0ef151edac [CSS] Now properly following Van de Graaf / Tschichold Page Construction Canon. 2021-09-20 16:06:57 +01:00
543853c374 [PLUGINS][Actor][Profile] Current user profile in line with other users. Directory plugin actor template standardized. 2021-09-20 14:39:11 +01:00
8fe8687c5b [UTIL][HTML] Move to a unified array 2021-09-20 13:28:18 +01:00
e8f4563633 [UTIL][Formatting][COMPONENT][Link][Tag] Refactor code from formatting into Link and Tag, where appropriate. Drop 'perfect url regex' as the one used in v2 is better 2021-09-20 13:28:18 +01:00
556b8f8265 [ENTITY][NoteTag] Add 'canonical' field to tag 2021-09-20 13:27:27 +01:00
04174bc56d [UTIL][UI] Change how plaintext notes are rendered to be split into paragraphs. Remove span around tags 2021-09-20 13:27:27 +01:00
51c7e10483 [UTIL][HTML] Allow specifying options: [raw => bool], whether to escape the provided inner HTML or not. Use with care 2021-09-20 13:27:21 +01:00
baeb1dde7a [Posting][CSS] Fix for qtwebengine and gecko not displaying background color on input elements.
Minor bug fixes.
2021-09-20 12:49:05 +01:00
67d62cf37b [TWIG] Remove kludge event TwigPopulateVars 2021-09-18 07:27:35 +01:00
fda998e335 [Avatar] We definitely don't need an event to retrieve avatar urls 2021-09-18 07:26:10 +01:00
2bd19fa087 [NOTE][Posting] Revert regressions introduced with c90efe2c52
Entity Note: It doesn't make sense to handle attachments on Note::create.
Attachments exist out of Notes, they are a thing on their own.
Furthermore, they aren't always handled the same, they most definitely
aren't always uploaded files.

FileQuota: It doesn't make sense to check if a file is greater than max
allowed upload size here. The plugin ensures a user is inside his
allowed quota, it's ignorant to anything else. Whether a file respect
max upload is a core thing that must be handled directly in the Posting
component. TODO: The configuration regarding user and monthly quota
must become FileQuotaPlugin settings and be removed from core.

c90efe2c52 - [UI] Add mechanism for rendering note contents in different formats. Implement plaintext rendering. Use rendered field for note content, rather than the content itself
2021-09-18 05:12:18 +01:00
941cbe6599 [Actor] Refactor GSActor into Actor 2021-09-18 05:12:17 +01:00
6c899b7b61 [Plugins][Components] Fixed issue where right panel form was rendered multiple times in actors plugin page. Actors page done. 2021-09-18 05:12:16 +01:00
e4b650be46 [PLUGINS][Settings] Removed unused templates. Directory plugin templates now using actual templates. Notification settings using details element now. 2021-09-18 05:12:16 +01:00
d6f31d102a [CORE][ActivityStreamsTwo][ActivityPub] Set all routes
Allow global routes to act for every actor
Fix Favoured stream query
2021-09-18 05:12:15 +01:00
738168461c [CSS] Note avatar hotfix. 2021-09-18 05:12:14 +01:00
447372d7f6 [CSS] Note actions re-alignment to previous position. Simplified visuals. 2021-09-18 05:12:13 +01:00
f4ac49e7c7 [CSS] Note avatar now bigger. Note actions and avatar now on the side of note. 2021-09-18 05:12:13 +01:00
2de071ca7e [CSS] Note attachments rules simplified. Proper resizing on smaller views. 2021-09-18 05:12:12 +01:00
a4a7039786 [CSS] Less redraws on details element hover feedback. 2021-09-18 05:12:11 +01:00
380eec5eb0 [CSS] Fixed inconsistent side panels font size. Note's view improvements. 2021-09-18 05:12:11 +01:00
b8e66aa9bf [CSS] Scrollable panels. Attachments width is retained on view now. 2021-09-18 05:12:10 +01:00
42f9a6a79c [CSS][Attachments] Attachments page view done. Standardized left and right panels sections. Applying styling through key classes to be defined in docs. 2021-09-18 05:12:10 +01:00
eda3a5ffb6 [DOCUMENTATION][DB] Fix database examples and documentation 2021-09-18 05:12:09 +01:00
ee7721da96 [DOCUMENTATION] Add documentation on developer tools 2021-09-18 05:12:09 +01:00
b177cb69e7 [HTML][SECURITY] Harden implmentation of HTML generation 2021-09-18 05:12:08 +01:00
030f8afdf5 [ASSETS][FONTS] Poppins and Opens Sans are now used. Better legibility for smaller sizes. 2021-09-18 05:12:07 +01:00
c0c7eb32dc [DEPENDENCIES] Update composer dependencies 2021-09-18 05:12:07 +01:00
8f0a3e4977 [UI] Add mechanism for rendering note contents in different formats. Implement plaintext rendering. Use rendered field for note content, rather than the content itself 2021-09-18 05:12:06 +01:00
f344ed376c [ATTACHMENTS][Embed][UI] Allow plugins to provide a title for an attachment, if a note has none, implement such a mechanism in Embed and cache the result, since it is potentially costly 2021-09-18 05:12:06 +01:00
15a2a69274 [ROUTER] Add option is_system_path, to allow specifying that a route, such as gsactor_view_nickname should not be considered a system path, when checking for the collision of nicknames 2021-09-18 05:12:05 +01:00
e563c393f8 [ROUTER] Add mechanism for sorting the order in which core routes are loaded 2021-09-18 05:12:05 +01:00
b26f3bca14 [BOOTSTRAP] Remove duplicate constant definition 2021-09-18 05:12:04 +01:00
e9d809d441 [Security][Exception] Security exception handling, login and register. TODO EmailNotFoundException and NicknameNotFoundException. 2021-09-15 14:48:06 +01:00
26af284353 [CONTROLLER][SECURITY] Registration feedback. The flashError works. However, Symfony's Exception error page is viewed upon trying to register. 2021-09-15 14:48:06 +01:00
efafc9f7eb [TWIG][NOTE] Note's author avatar size defined. 2021-09-15 14:48:06 +01:00
2cbdd43660 [TWIG][BASE] Using preload for main stylesheets. This ensures they are available earlier and are less likely to block the page's render, improving performance. 2021-09-15 14:48:06 +01:00
0f1bce67a1 [CSS][FONTS] Added a lighter font, mobile optimizations were needed. All icons are more consistent in size. Forms are more consistent in margins, paddings and grouping. 2021-09-15 14:48:06 +01:00
365edbaff0 [ActivityStreamsTwo] Initial Actor support
Various bug fixes
2021-09-15 10:26:53 +01:00
1f3a6fe6ac [TESTS] Fix and/or temporarily disable failing tests. We'll get back to this 2021-09-14 13:36:30 +01:00
bebf4fdbce [I18n][Posting] Move ':' to inside the translate call 2021-09-14 13:13:45 +01:00
1adde913c6 [PHPStan] Only run custom PHPStan extensions if environment vairable PHPSTAN_BOOT_KERNEL is defined (since it requires having the whole social setup available) 2021-09-14 13:13:45 +01:00
a21b0afb70 [ASSETS] Removed unused fonts. Variable fonts now used. 2021-09-14 13:13:45 +01:00
7c465bba5f [NOTE] Add mimetype to notes 2021-09-14 13:13:45 +01:00
c69b28d894 [CORE] Fix Undefined array key 0 in DB::filterTableName 2021-09-14 13:13:45 +01:00
404442ebda [BASE] Importing fonts through CSS instead, minimizing content blocking. Removed legibility optimizations in font rendering. Removed unused font rules. 2021-09-14 13:13:45 +01:00
8887efe305 [THEME][DARK] Background image is 73% smaller, added noise to diminish the banding in Firefox. 2021-09-14 13:13:45 +01:00
b74d944ae3 [TOOLS][PHPStan] Raise PHPStan level to 3 and fix new errors 2021-09-14 13:13:45 +01:00
9d7f43cd28 [TOOLS][PHPStan][TESTS][Docker] Rework testing Docker container into a more generic tooling container. Keep services up and run coverage and phpstan as commands, for performance and ease of use 2021-09-14 13:13:44 +01:00
9742a07bae [TOOLS][git] Add option to skip pre-commit steps by defining one of SKIP_ALL, SKIP_CS_FIX, SKIP_DOC_CHECK, SKIP_PHPSTAN variables before the git commit command
Example:
`SKIP_PHPSTAN=1 git commit`
2021-09-14 13:13:44 +01:00
f81bf4a257 [TOOLS][PHPStan] Add mechanism for initializing the whole application 2021-09-14 13:13:44 +01:00
ddb9702b1c [TOOLS][PHPStan] Add support for deducing the correct entity type from a table name. Needs refactoring and can only run inside container, as it connects to the database itself 2021-09-14 13:13:44 +01:00
4e30e5aad9 [TOOLS] Fix all issues found by PHPStan level 2 2021-09-14 13:13:44 +01:00
4b1780a2ee [ActivityStreamsTwo] Introduce a structure for data representation in ActivityStreams 2.0
Type factory borrowed from landrok/activitypub
2021-09-14 13:13:44 +01:00
043c5da58b [THEME][BASE] New and better light theme. 2021-09-14 13:13:44 +01:00
26aec5f626 [BASE][NOTE][CSS] Further screen reader cues for note replies. 2021-09-14 13:13:44 +01:00
9e051273f7 [BASE][NOTE][CSS] Notes are now clearly delimited to visually impaired users. Screen readers will notify when a note begins as well as each reply. Focused elements now provide the same feedback everywhere. 2021-09-14 13:13:43 +01:00
60af533fa4 [BASE][CSS] Accessibility menu fix when logged out. Proper selector for accesskey used now, ".accessibility-target". 2021-09-14 13:13:43 +01:00
c4b328c03b [LEFT][CSS] Fix scrolling when tabbing, navigating through notes is more obvious now. Timeline navigation fix for screen readers to know that the navigation title matters. 2021-09-14 13:13:43 +01:00
3abf71b707 [CSS] There's no need to tab again after selecting accessibility menu! 2021-09-14 13:13:43 +01:00
8123086881 [Embed] Fix normalizeEmbedLibMetadata for thumbnails starting with '/' 2021-09-14 13:13:43 +01:00
3c1a9ba3fa [CSS] Fixed tabbing through accessibility menu. Was invisible when going throught it without a previous target. 2021-09-14 13:13:43 +01:00
b0b3ae237a [TWIG][BASE] Fixed taborder, Orca will continue to read the whole header though. Navigation should be more clear for each timeline. 2021-09-14 13:13:43 +01:00
61071a6821 [PLUGINS][Reply][Repeat][Favourite] Base template semantic rework. Reply, repeat and favourite respective classes are now assigned in their NoteHandlerPlugin. 2021-09-14 13:13:43 +01:00
ddd2ffe26a [TWIG] Add extension to check if Firefox is being used 2021-09-14 13:13:43 +01:00
0e50f0692e [ACCESSIBILITY][BASE] Accessibility menu was unreachable. 2021-09-14 13:13:42 +01:00
1614b8c8fe [ACCESSIBILITY][BASE] Workaround Firefox amazing keybindings. 2021-09-14 13:13:42 +01:00
681144b380 [PLUGINS][Favourite][CSS] Favourite label now shows whether it's favourited or not already. 2021-09-14 13:13:42 +01:00
0cecc67376 [COMPONENT][Posting][Link] Create ProcessNoteContent event. Move link extraction to Link component. Cleanup Posting 2021-09-14 13:13:42 +01:00
add8f4a52f [TOOLS] Fix all errors found by PHPStan level 1 2021-09-14 13:13:42 +01:00
0da6ff05ed [TOOLS] Add config file and raise PHPStan to level 1 2021-09-14 13:13:42 +01:00
277a080d7c [EXCEPTION] Add NotImplementedException 2021-09-14 13:13:42 +01:00
eb833b62e2 [TOOLS] Fix all level 0 errors found by PHPStan and move constant definition to bootstrap file 2021-09-14 13:13:42 +01:00
0eb0236feb [TOOLS] Run PHPStan on the whole codebase on each commit 2021-09-14 13:13:42 +01:00
ecb1064d08 [DEPENDENCIES] Add PHPStan as a dev dependency 2021-09-14 13:13:42 +01:00
6ac37bc7fb [DEPENDENCIES] Update dependencies 2021-09-14 13:13:41 +01:00
f65e2b90f2 [CORE] Fix use of Exception class without import 2021-09-14 13:13:41 +01:00
cbbef90752 [UTIL][CONFIG] Ensure setConfig uses a locals key 2021-09-14 13:13:41 +01:00
4916c8cbda [CONTROLLER][AdminPanel] Add missing Exception import 2021-09-14 13:13:41 +01:00
bfd0acacd1 [TESTS] Fix SecurityTest breakage following UI changes 2021-09-14 13:13:41 +01:00
e8ae0b74e0 [CORE][Controller] Switch order for content negotiation: allow events to take precedence. Bring back default JSON response 2021-09-14 13:13:41 +01:00
bc5ddc52ea [UI] Remove stray template file foo.html.twig 2021-09-14 13:13:41 +01:00
c612fe6df5 [PLUGINS][Reply][CSS] Help text added. Styling reply page done. 2021-09-14 13:13:41 +01:00
9f6acc04aa [CSS] Light theme panels background image fix. Input elements with proper padding. 2021-09-14 13:13:41 +01:00
7a0e256557 [CSS] Light theme! 2021-09-14 13:13:40 +01:00
727133b6ed [IMAGES][CSS] Dropdown image added for use in select boxes background. Fixed select box background color from being the one used by the system, dark theme inconsistencies found. 2021-09-14 13:13:40 +01:00
e6449bfe96 [CONFIG][TWIG] Selected a new default form theme. Forms shouldn't have class-less divs now. 2021-09-14 13:13:40 +01:00
1f792a0183 [CSS] Links should be aligned with content now. Underlined in main content by default. Highlighted when focused. 2021-09-14 13:13:40 +01:00
bdde047dfa [PLUGINS][Embed] Embed references a table that does not exist. It seems to mean attachment_embed, rather than link_to_attachment 2021-09-14 13:13:40 +01:00
cd89106fc0 [EXCEPTION][RedirectException] Add prevention for open redirects by default and ensure we can redirect to the same page 2021-09-14 13:13:40 +01:00
16cde6dfd7 [PLUGINS][Reply] Redirect back to previous URL on note reply. Move controller to own class
This should be safe against open redirects, as it doesn't allow redirecting to other domains
2021-09-14 13:13:40 +01:00
0a7fd9c460 [CORE][MODULES][NoteHandlerPlugin] Add missing import for Event 2021-09-14 13:13:40 +01:00
1d5fd1aefa [CORE][Controller] Fix Controller::__invoke so it actually passes on the route parameters to the controller method. Add Controller->string, which gets a query parameter as a string value, or null if not set 2021-09-14 13:13:40 +01:00
330143e549 [ROUTER][DOCUMENTATION] Add Router::isAbsolute, add documentation to Router::url and `s/setRouter/serServices/ 2021-09-14 13:13:39 +01:00
61d95265a9 [PLUGINS][Repeat] Delete note repeat rather than the original Note. Don't fetch when we want a simple count 2021-09-14 13:13:39 +01:00
be27a10244 [EVENTS] Rename event RouteInFormat to ControllerResponseInFormat 2021-09-14 13:13:39 +01:00
f371443884 [CSS] Note's view now properly handles replies. 2021-09-14 13:13:39 +01:00
6fdec483cd [Reply] RedirectionException wasn't allowing other handlers to do their job. 2021-09-14 13:13:39 +01:00
85db9464ca [Reply] Fixed reply plugin action, there was no need to query the database when handling. 2021-09-14 13:13:39 +01:00
f000532b7e [Favourite][Reply][Repeat] The respective svg for note actions is assigned. Repeat note handler needs work. "An exception has been thrown during the rendering of a template ("No value in table note matches the requested criteria")." exception thrown on repeat. 2021-09-14 13:13:39 +01:00
f9f4f179bb [Security][Right] login
and register padding fix. Select boxes styling done.
2021-09-14 13:13:39 +01:00
983e0303a5 [ROUTER] Sort routes so that the one with a smaller list of Accept types matches first
This requires a copy, but gets cached, so it's the ideal place to do it.

Note that only routes that match the incoming Accept match anyway, so the order between those with different accept types is not relevant
2021-09-14 13:13:39 +01:00
45734d882c [CONFIG] Make it possible to write module configuration in a config.{php,yml,yaml,xml} file and set each value as properties in the module object 2021-09-14 13:13:38 +01:00
6ef07e04d1 [Posting][CSS] Right panel form uses a select box instead of radio buttons. Hover and focus of <a> elements using just an underline. Note author and actions padding redone. File-picker font is now correct. Left panel hierarchy should now be more clear. 2021-09-14 13:13:38 +01:00
7dc390ca1c [TWIG][CSS] Shortcuts now work in small screen sizes. Header icons further polish. Profile navigation structural rework. 2021-09-14 13:13:38 +01:00
ca2eff2906 [CSS] Visual feedback from shortcuts polished. 2021-09-14 13:13:38 +01:00
414b33f97b [TWIG][CSS] Shortcut menu done. Can access various panel with proper visual feedback. 2021-09-14 13:13:38 +01:00
e73af2b887 [TWIG][CSS] Left and Right panels now using a checkbox trick to control their visibility. Details element couldn't be properly controlled by CSS without breaking accessibility. 2021-09-14 13:13:38 +01:00
6e6b2ea87b [CSS][Icons] Icons width and height wasn't consistent, problem lied within the SVGs themselves. 2021-09-14 13:13:38 +01:00
b8bb845e24 [TWIG][CSS][Accessibility] Note view accessibility improvements. Left and right panels icon rework (width and height weren't correct, position needs further work). 2021-09-14 13:13:38 +01:00
d2760f1250 [TWIG][CSS][Accessibility] Base template accessibility improvements. Applying more semantic HTML5 tags and aria to describe content actions. 2021-09-14 13:13:38 +01:00
671e2d6a9d [CSS] Fixed Favourite button background size and colour. Fixed right panel incorrect font size. 2021-09-14 13:13:37 +01:00
8880af8197 [ActivityStreamsTwo] Introduce a structure for data representation in ActivityStreams 2.0
Type factory borrowed from landrok/activitypub
2021-09-14 13:13:37 +01:00
e4aa3ae968 [NOTE] Add route and controller 2021-09-14 13:13:37 +01:00
fd3b57dc24 [CORE][Controller] Make it possible for plugins to add different content-types to existing routes 2021-09-14 13:13:37 +01:00
8e45637277 [DOCS][User] Elaborate on what is GNU social and IndieWeb 2021-09-14 13:13:37 +01:00
626f50080b [MODULES] Use snake_case for module config keys 2021-09-14 13:13:37 +01:00
033c4db914 [MODULES] Add function to defer module initialization and cleanup to plugin and component. Add example in Avatar component
Forward onInitializeModule to onInitializePlugin if the component is a plugin
2021-09-14 13:13:37 +01:00
bda839be7b [MODULES] Add InitiializeModule and CleanupModule events, similar to v2 2021-09-14 13:13:37 +01:00
1ee8df1494 [DOCS][Developer] Elaborate on implementing and configuring a module 2021-09-14 13:13:37 +01:00
141f919ca7 [CONFIG][TESTS] Fix error found by AdminPanel test 2021-09-14 13:13:36 +01:00
4d3da08b1e [CONFIG] Add example module configuration 2021-09-14 13:13:36 +01:00
c71a4b06ef [CONFIG] Make it possible to write module configuration in a config.{php,yml,yaml,xml} file and set each value as properties in the module object 2021-09-14 13:13:36 +01:00
3587b8dc1d [CONFIG] Refactor configuration loading 2021-09-14 13:13:36 +01:00
c94ef26617 [TESTS] Fix namespace on Controller Security test 2021-09-14 13:13:36 +01:00
56e5d5c4a0 [TESTS] Fix broken tests and expand tests around Attachments 2021-09-14 13:13:36 +01:00
f1bd4db495 [TESTS] Fix Common test 2021-09-14 13:13:36 +01:00
2fdd0b0820 [TESTS][DataFixtures] Use Temporary file instead of an ad-hoc solution to copy the upload files 2021-09-14 13:13:36 +01:00
9739cc5f21 [Posting] Respect process_links setting 2021-09-14 13:13:36 +01:00
ab142ab52d [FileQuota] Update plugin to respect the new entities 2021-09-14 13:13:35 +01:00
2b457655ea [CORE] Fix path configuration 2021-09-14 13:13:35 +01:00
e7b985a460 [FIXTURES] Catch any exception, we don't have VIPS-related only 2021-09-14 13:13:35 +01:00
aa8412f607 [TESTS][Forms] Respect new naming conventions 2021-09-14 13:13:35 +01:00
9067bd8785 [TESTS] remove accidentally duplicate sample upload 2021-09-14 13:13:35 +01:00
415089914f [VideoEncoder] Port plugin to v3 properly 2021-09-14 13:13:35 +01:00
5107e06fae [DOCS][Developer] Paradigms: Update is_null based on poll votes
Everything around the use we give to php's null is about the state of having a value or not. Thus, using `is_null` always is the less bad option.
2021-09-14 13:13:35 +01:00
2b7232891e [ImageEncoder] Make plugin respect instance config and use the new core interface 2021-09-14 13:13:35 +01:00
f9079784c4 [ENTITY][AttachmentThumbnail] Improve the way EncoderPlugins participate in the thumbnail process 2021-09-14 13:13:35 +01:00
e4b2821657 [TWIG][Attachments] Don't throw event with mimetype in its name 2021-09-14 13:13:35 +01:00
bc1fb007aa [Core][GSFile] Improve the way EncoderPlugins participate in the file sanitization process 2021-09-14 13:13:34 +01:00
210f895e74 [ENTITY][Attachment] Respect rfc6838#section-4.2 mimetype length 2021-09-14 13:13:34 +01:00
e80ad2d87b [TESTS][Controller][AdminTest] Update int tests to use attachment file_quota instead, as we deleted attachment max width 2021-09-14 13:13:34 +01:00
b0b4f37078 [CONFIG] Add setting for attachment sanitization 2021-09-14 13:13:34 +01:00
2a3db65216 [CONTROLLER][Attachment] Some attachments may not have dimensions 2021-09-14 13:13:34 +01:00
450dbfb98f [DOCUMENTATION] Update documentation regarding route accept formats 2021-09-14 13:13:34 +01:00
26bf78360b [ROUTER][UTIL] Allow specifying the Accept format for a route 2021-09-14 13:13:34 +01:00
86bdc398c5 [DOCKER] Update PHP docker container to include ffmpeg, for the VideoEncoderPlugin 2021-09-14 13:13:34 +01:00
811caaadf9 [MODULES][PLUGINS] Move noteActionHandle utility to NoteHandlerPlugin which plugins which handle actions on notes should extend 2021-09-14 13:13:34 +01:00
458b6d0009 [UI] Rename all forms to more specific names, to avoid form name collisions 2021-09-14 13:13:33 +01:00
6af1383e07 [UTIL][Form] Disallow using very generic form names, as they can collide with other forms in the same page 2021-09-14 13:13:33 +01:00
a7d5b5599c [TESTS] Fix LocalUserTest, i forgor to boot the kernel 2021-09-14 13:13:33 +01:00
e278efe61d [TESTS] Fix tests broken with rebased commits 2021-09-14 13:13:33 +01:00
8e12f5ee59 [TESTS] Raise test coverage for Link to 100% 2021-09-14 13:13:33 +01:00
c78032f1fa [TESTS] Raise test coverage of LocalGroup to 100% 2021-09-14 13:13:33 +01:00
cfc89d8a25 [TESTS] Raise test coverage of LocalUser to 100% 2021-09-14 13:13:33 +01:00
a2e302efb4 [TESTS] Raise GSActor test coverage to 100% 2021-09-14 13:13:33 +01:00
043e179c23 [TESTS][Attachment][AttachmentThumbnail][GSFile] Reorganize tests and raise test coverage to 100% 2021-09-14 13:13:33 +01:00
4cd3924cc1 [ATTACHMENTS][AttachmentThumbnail] Fix implementation of predictScalingValues and small fixes 2021-09-14 13:13:32 +01:00
2ccbbd53a6 [TESTS] Add code coverage annotations to entities 2021-09-14 13:13:32 +01:00
e7699b3245 [TESTS] Raise test coverage for Note to 100% 2021-09-14 13:13:32 +01:00
79215bc439 [Note] Fix scope check for group notes, move away from SQL, to DQL 2021-09-14 13:13:32 +01:00
e392a9c90c [TESTS][DataFixtures] Add user, self follows, group member and group scope note 2021-09-14 13:13:32 +01:00
365afb7ba8 [TOOLS] Disable command echo in Makefile 2021-09-14 13:13:32 +01:00
4b2a92d052 [UI][Attachment] Use Attachment methods to get the proper URL, rather than crafting it in a template 2021-09-14 13:13:32 +01:00
6799052ff5 [ATTACHMENTS] Ensure thumbnail dimensions are bounded and change way cropping is implemented 2021-09-14 13:13:32 +01:00
f67173061b [VideoEncoder] Add plugin composer dependency php-ffmpeg/php-ffmpeg 2021-09-14 13:13:32 +01:00
ac45008240 [Embed] Move composer dependency embed/embed from core to plugin 2021-09-14 13:13:31 +01:00
b50f11a040 [ENTITY][Link] self urls can't be considered a remote url 2021-09-14 13:13:31 +01:00
177801c81b [Embed][StoreRemoteMedia] Re-add {white,black}list check config 2021-09-14 13:13:31 +01:00
de444a2a5a [Posting] Fix wrong usage of DB::findBy 2021-09-14 13:13:31 +01:00
e40c7b0509 [DOCS][Developer] Recommend reading the tests cases for when the doc doesn't cover 2021-09-14 13:13:31 +01:00
5c8677304c [DOCS][Developer] Update storage documentation 2021-09-14 13:13:31 +01:00
78f4ccb576 [ImageEncoder] Fix ImageSanitization, it should never modify the input image 2021-09-14 13:13:31 +01:00
ca71e57593 [CSS] Hotfix: Figure captions do not overflow. 2021-09-14 13:13:31 +01:00
41b42407cd [Posting] Store uploaded filenames as titles 2021-09-14 13:13:31 +01:00
036e9cb58e [Avatar] Preserve uploaded filename and use Avatar's own route instead of attachment 2021-09-14 13:13:30 +01:00
f70eb8f12d Remove weird empty template 2021-09-14 13:13:30 +01:00
6166afeec6 [TWIG][CSS] Left and right panels accessibility improvements. Tabindex is now properly set up. When panels have the attribute open but aren't focused (keyboard navigation out of panel, into main content) they aren't displayed anymore. 2021-09-14 13:13:30 +01:00
e2e6c7a5bf [TESTS] Hot-fix Security controller tests, broken with ongoing form rendering changes 2021-09-14 13:13:30 +01:00
c81795eb96 [TWIG][CSS] More consistent classes. Checkbox styling done. Register and Login pages now done. 2021-09-14 13:13:30 +01:00
44a581f0f6 [UTIL][FORM] Password form now shows the proper HTML class, and it's respective label. 2021-09-14 13:13:30 +01:00
87d7318de5 [CONTROLLER][SECURITY] Added class names. Help labels for each form element. Each form element now has a proper block prefix (the resulting HTML won't simply concatenate the form's name to the element's label). 2021-09-14 13:13:30 +01:00
ab10cd4121 [DOC] Fixed installation.md typos. 2021-09-14 13:13:30 +01:00
ebee70621b [FORM] Fix bug where options were passed in the data parameter 2021-09-14 13:13:30 +01:00
1419035076 [DOCS][Developer] Add an introduction 2021-09-14 13:13:29 +01:00
afd00fbdc5 [DOCS][Developer] httpclient: Add an example of lazyness care 2021-09-14 13:13:29 +01:00
31ca5cb35e [DOCS][Developer] Paradigms: apply XRevan86 fixes and remove the return types section, we must revisit it later.. 2021-09-14 13:13:29 +01:00
54e03d49d4 [DOCS][Developer] Remove low level index, we will approach these themes in another manner
Fix some broken links
2021-09-14 13:13:29 +01:00
c8e00e4187 [TOOLS] Add make command 'database-force-schema-update' to update the database schema and 'redis-shell' 2021-09-14 13:13:29 +01:00
8ca61eea77 Duplicate public/assets/css/bg.jpg history in tests/sample-uploads/attachment-lifecycle-target.jpg history. 2021-09-14 13:13:29 +01:00
6d7a0dbc92 [TESTS][TOOLS] Always stop containers regardless of test success 2021-09-14 13:13:29 +01:00
5410f22060 [DEPENDENCIES] Update dependencies 2021-09-14 13:13:29 +01:00
4f880eb761 [PLUGINS][ENTITY][Cover][ProfileColor] Clean up after interns and move entity defintions to be inside the corresponding plugin, rather than in core 2021-09-14 13:13:29 +01:00
5237364a21 [TESTS] Raise test coverage for GSFile to 100% 2021-09-14 13:13:28 +01:00
300eccfd17 [TESTS] Raise test coverage for Attachment controller to 100% 2021-09-14 13:13:28 +01:00
2351e7c6d1 [CORE][GSFile] Use pathinfo rather than regular expressions and don't attempt to persist an already persisted entity 2021-09-14 13:13:28 +01:00
3843348c1b [CONTROLLER][Attachment] Small refactor and add testing annotation 2021-09-14 13:13:28 +01:00
e2caf19b67 [TESTS] Remove copied upload test files, if upload failed 2021-09-14 13:13:28 +01:00
aef61e4c73 [TESTS] Add coverage ignore flags to trivial methods 2021-09-14 13:13:28 +01:00
fe86735b8b [DB][DOCUMENTATION] Explain limit: 2 in findOneBy 2021-09-14 13:13:28 +01:00
c3db2f60d2 [UTIL][EXCEPTIONS] Introduce NotStoredLocallyException 2021-09-14 13:13:28 +01:00
6445a616a8 [ENTITY][Attachment] Raise mimetype max length to 64 characters and ensure we don't attempt to store more than that 2021-09-14 13:13:28 +01:00
d4d4f4e950 [TESTS] Cleanup attachment test data 2021-09-14 13:13:27 +01:00
6e6d1a946f [TESTS] Remove MediaFileTest and move setup code to media data fixture 2021-09-14 13:13:27 +01:00
53f89ade85 [TESTS] Move Media fixtures to their own file, for organization 2021-09-14 13:13:27 +01:00
74d1874991 [DOCUMENTATION][Entity] Improve documentation on Entity::getWithPK, explaining the ways it can be used 2021-09-14 13:13:27 +01:00
060a5abef1 [ENTITY][Link] Sometimes URLs don't work, handle that 2021-09-14 13:13:27 +01:00
1906d4f276 [Embed] Add UI element and fix some bugs 2021-09-14 13:13:27 +01:00
061c953eac [TWIG] Add Links representation to notes 2021-09-14 13:13:27 +01:00
ef1a9ce3b1 [ImageEncoder] Handle VIPS unsupported image type 2021-09-14 13:13:27 +01:00
f690bc06ae [ATTACHMENT] Some attachments don't have thumbnails and that's okay 2021-09-14 13:13:27 +01:00
b4a03b814f [CORE][GSFile] ensureFilenameWithProperExtension: extension isn't an I/O param 2021-09-14 13:13:26 +01:00
728f8d8fb8 [ENTITY][Note] Add getter for note links 2021-09-14 13:13:26 +01:00
9e4cac0123 [ENTITY] Refactor RemoteURL entities to Link
RemoteURL was being an awfully confusing term.
2021-09-14 13:13:26 +01:00
fb28a3656a [Embed] Retrieve remote thumbnails and other improvements 2021-09-14 13:13:26 +01:00
7a0a6f1f22 [Embed] Fix usage of EmbedLib
Fix other minor bugs
2021-09-14 13:13:26 +01:00
968e3431e1 [Attachment] Sometimes we can't provide download of original file 2021-09-14 13:13:26 +01:00
4cc4523632 [Posting] Re-add original file to attachment on upload, if it was previously removed 2021-09-14 13:13:26 +01:00
d076781c74 [AttachmentToNote][Attachment] Add title getter to Attachment 2021-09-14 13:13:26 +01:00
5fd91bf3a2 [TESTS][Twig] Fix ExtensionTest->testIconsExtension 2021-09-14 13:13:26 +01:00
bac18715c5 [StoreRemoteMedia] Implement the first version of it in v3 2021-09-14 13:13:25 +01:00
63cbf4052f [UTIL] Common::config wasn't a proper port from v2, it has to accept one argument only as well 2021-09-14 13:13:25 +01:00
33cc9386d2 [RemoteURLToAttachment] Fix primary key, relation is: One Attachment Has Many URLs, One URL Has One Attachment 2021-09-14 13:13:25 +01:00
78c5c4b084 [Attachment] Allow to delete the associated file 2021-09-14 13:13:25 +01:00
508f1f8796 [MODULES] Add module configuration 2021-09-14 13:13:25 +01:00
de8a2f579c [CORE][Event] Fix bug on handler Log 2021-09-14 13:13:25 +01:00
4ecdeac6a3 [CORE][Entity] Compare with object properties when creating/updating, instead of class 2021-09-14 13:13:25 +01:00
75f70f8182 [ENTITY][Posting] Remove GSActorToRemoteURL, Fix URL database store 2021-09-14 13:13:25 +01:00
fbbbde4275 [AttachmentShowRelated] Bug fix after re-introduction of NoteActions 2021-09-14 13:13:25 +01:00
df62d7e4f4 [CSS][Assets] Minified header icons. Reverted footer links position due to Firefox's corageous interpretation of a fixed element's position. 2021-09-14 13:13:25 +01:00
5de0704de2 [Favourite][TWIG][CSS] Favourite shows alt-text and properly sets colours. Titles translated on the right panel. 2021-09-14 13:13:24 +01:00
f5175cc59d [ATTACHMENTS] Always store in the same location 2021-09-14 13:13:24 +01:00
3f61537140 [ENTITY] Split Attachment in various new entities
Remove Attachment Scope
Fixed some minor bugs

Scope will be implemented later in v3. It doesn't make sense to have
the scope handling being per attachment. Different actors can post
the same attachment with different scopes. The attachment controller
will assume the highest level of scope applied to the attachment and
the rest will be handled at the note level.

Motivation:
* Remove title from attachment, as it's part of the relation between attachment and note.
* Remove actor from attachment, many actors may publish the same attachment.
* Remove is_local from attachment,  as it's part of the relation between attachment and note.
* Remove remote_url from attachment, different urls can return the same attachment.

Addition:
* Attachment now has a lives attribute,  it's a reference counter with a nicer name
* GSActorToAttachment
* GSActorToRemoteURL
* RemoteURL
* RemoteURLToNote
* RemoteURLToAttachment
* AttachmentToNote now has a title attribute
2021-09-14 13:13:24 +01:00
a7c8da0534 [FileQuota] We'll get back to this plugin later 2021-09-14 13:13:24 +01:00
39006fb6b5 [DB][Attachments] Use count function rathar than fetch and count, rename to refCount, rather than countDepencies 2021-09-14 13:13:24 +01:00
8880405dee [DEPENDENCIES] Update dependencies 2021-09-14 13:13:24 +01:00
809bf00aa9 [TemporaryFile][TESTS] Throw on attempt to write to null resource and fix tests 2021-09-14 13:13:24 +01:00
c24c32334d [TESTS] Add test for JSON response and invalid accept format 2021-09-14 13:13:24 +01:00
6728dd40b0 [ENTITY] Add JsonSerializable interface to Entity base class and implement it for the Note class 2021-09-14 13:13:24 +01:00
2851b899b8 [TESTS] Add test annotations to core classes 2021-09-14 13:13:23 +01:00
21a5bbe639 [CORE][Controller] Fix JSON response and add test annotations 2021-09-14 13:13:23 +01:00
061a85d6b3 [EVENTS] Change FormatNoteList do separate in and out arguments
This is necessary due to some weird problem which I wasn't able to figure out (but which doesn't matter)
that somehow causes the event to be called twice during testing, and thus the function was exploding
2021-09-14 13:13:23 +01:00
57f43108bb [TESTS] Fix Entity test in accordance with the changes to createOrUpdate 2021-09-14 13:13:23 +01:00
b5de80303a [TEST] Raise test coverage for UserPanel to 100% 2021-09-14 13:13:23 +01:00
480665afc8 [CONTROLLER][UserPanel] Finish implementation of ::notifications so it actually saves the values in the database 2021-09-14 13:13:23 +01:00
cdf3426567 [CONTROLLR][UserPanel][PLUGINS] Add submit button to notification settings for each transport 2021-09-14 13:13:23 +01:00
afbcb179b2 [CONTROLLER][AdminPanel] Further ensure form validity 2021-09-14 13:13:23 +01:00
38a331220f [CORE][Log] Add Log::unexpected_exception utility which logs and throws a ServerException 2021-09-14 13:13:23 +01:00
085e880631 [CORE][Entity] Fix implementation of createOrUpdate so it doesn't throw NotFoundException if trying to create an object
This was previously done because we wanted to notify the callee that
and entity existed but not with the provided contents. With the change
of return value, with a bool $is_update, this is no longer a problem.
2021-09-14 13:13:22 +01:00
662ad8e9cf [TESTS] Update PHPUnit config to start executing tests by last failure. This doens't seem to actually work currently, though 2021-09-14 13:13:22 +01:00
11e52bcb27 [TOOLS] Add utility commands to the Makefile 2021-09-14 13:13:22 +01:00
1b623a85ae [UTIL][FormFields] Move FormFields class to Util\Form namespace 2021-09-14 13:13:22 +01:00
1647c5391f [Favourite][TWIG][CSS] Favourite now works. 2021-09-14 13:13:22 +01:00
e15044fe36 [CSS] Overall typography resizing to provide proper text hierarchy. Radio buttons polish, notes author box is now smaller. Better organization of CSS rules according to their filename and thus, their aim. 2021-09-14 13:13:22 +01:00
1962a004aa [CSS] Fixed top margin, footer elements are now actually footer elements. 2021-09-14 13:13:22 +01:00
e180143e20 [TWIG][CSS] Buttons are now the correct size in all contexts. Right panel create a notice section re-structure. 2021-09-14 13:13:22 +01:00
cb21599cef [TWIG][CSS] Feedback provided to active page. Removed top accent border on notes. 2021-09-14 13:13:22 +01:00
68cf6dee65 [FAQ][CSS] All FAQ pages stylized, minor structure changes throughout. 2021-09-14 13:13:22 +01:00
10f930ad4b [TWIG][CSS] Left panel text hierarchy fix (no <hr> element between timeline navigation and its items). Default CSS fixes for buttons and input file selectors. 2021-09-14 13:13:21 +01:00
0f032c257b [CSS] Input file elements polish. It should resize properly and provide feedback when selected (not a thing in Firefox ESR yet). 2021-09-14 13:13:21 +01:00
bac6d2a490 [CSS] Base CSS polish, reset CSS additions for better cross-browser compatibility. 2021-09-14 13:13:21 +01:00
a77f51dd06 [Avatar] Delete attachment only if safe 2021-09-14 13:13:21 +01:00
21362d1e4d [ATTACHMENTS] Add dependencies counter 2021-09-14 13:13:21 +01:00
95fd86f8dc [CSS] Fixed background incorrect sizing. 2021-09-14 13:13:21 +01:00
d0b2cccb63 [CSS] Mesh gradient works as intended with no banding. 2021-09-14 13:13:21 +01:00
0c612bddbe [Avatar] Store as regular attachments 2021-09-14 13:13:21 +01:00
b355f0d590 [DEV] Add PsySH REPL 2021-09-14 13:13:20 +01:00
3334aca7b9 [Avatar] Move entity from core to component 2021-09-14 13:13:20 +01:00
fb6aa78ae8 [CORE][GSFile] Allow storing files under /file 2021-09-14 13:13:20 +01:00
e688bf8aed [TESTS] Ignore coverage of ResetPasswordRequest, as it uses mailing functionality. We may want to introduce this test in the future 2021-09-14 13:13:20 +01:00
9ad2cb5e66 [UTIL][FormFields] Allow specifying whether a password is required and provide placeholder text 2021-09-14 13:13:20 +01:00
5aedf64e5b [ROUTES] Remove individual settings pages, as they got merged 2021-09-14 13:13:20 +01:00
27f2fbdade [ENTITY] Refactor LocalUser::changePassword for easier use 2021-09-14 13:13:20 +01:00
19318b3163 [UTIL][EXCEPTION] Add AuthenticationException, representing an auth error, status code 401 unauthorized 2021-09-14 13:13:20 +01:00
a1592656e0 [TESTS] Change format of data fixtures to allow creating more users 2021-09-14 13:13:20 +01:00
2dd3511149 [CORE][DB] Specify desired case in array_change_case, for clarity 2021-09-14 13:13:19 +01:00
0ae67d96ea [TESTS] Raise test coverage for AdminPanel to 100% 2021-09-14 13:13:19 +01:00
b77e35ef09 [SECURITY] Do not require email when in development 2021-09-14 13:13:19 +01:00
e8ef777fb2 [DOCS][Dev] Add Debugging and Testing 2021-09-14 13:13:19 +01:00
8240591bd4 [TESTS] Fixup Security controller tests to match new UI 2021-09-14 13:13:19 +01:00
fe7c2b5115 [TESTS] Raise test coverage for AdminPanel controller to 100% 2021-09-14 13:13:19 +01:00
e0a0df502e [TESTS] Raise test coverage for Attachment controller to 100% 2021-09-14 13:13:19 +01:00
29d77b446f [TESTS] Add a sample image to the test dataset 2021-09-14 13:13:19 +01:00
647e4c03b3 [CORE][GSFile] Ensure files are stored inside the configured storage folder, with a relative filename in the database 2021-09-14 13:13:19 +01:00
51f8f004b3 [CORE][CONTROLLER] Add TODO to Controller base class as to where our custom exception pages would be implemented 2021-09-14 13:13:18 +01:00
0b80727769 [CORE][ENTITY] Allow create'ing will null values 2021-09-14 13:13:18 +01:00
77742c56c3 [ImageEncoder] Don't print_r the exception as that leads to an OOM error 2021-09-14 13:13:18 +01:00
4aee27d3a6 [CONFIG] Ensure consistency in config file 2021-09-14 13:13:18 +01:00
229a516fd2 [TESTS] Make PHPUnit exit on first fail and some other tweaks 2021-09-14 13:13:18 +01:00
855d427442 [UTIL][FORM] Create a utility class that defines common form fields, such as passwords 2021-09-14 13:13:18 +01:00
dfc97d2607 [TESTS] Raise test coverage for Controller/Security to 100% 2021-09-14 13:13:18 +01:00
dc2b9f940e [CORE][GSFile] Assert that the destination folder where to store the attachment is inside the INSTALLDIR and store the filepath in the database 2021-09-14 13:13:18 +01:00
8139a21eb9 [TESTS] Add coverage ignore to TemplateController and ResetPassword (as it requires sending emails) 2021-09-14 13:13:18 +01:00
518995d155 [CONTROLLER][Attachment] Assert that the supplied is positive and add documentation 2021-09-14 13:13:17 +01:00
88dd9e542f [CORE][GSFile] Change actor_id paramenter of validateAndStoreFileAsAttachment to optional and reorder them and their usages 2021-09-14 13:13:17 +01:00
7bd88bd101 [AUTOGENERATED] Update entity fields for ResetPasswordRequest entity 2021-09-14 13:13:17 +01:00
471dc52c92 [CORE] Add repositories, as needed by the Reset Password Bundle 2021-09-14 13:13:17 +01:00
0c54a3297f [UTIL] Add a class that defines commonly used form fields 2021-09-14 13:13:17 +01:00
ccd5ebf8e4 [CORE] Add passowrd reset and forgot password functionality 2021-09-14 13:13:17 +01:00
c3d2f04841 [DEPENDENCIES] Add symfonycasts/reset-password-bundle as a dependency 2021-09-14 13:13:17 +01:00
769fff2448 [CORE][SECURITY][EMAIL] Move email confirmation functionality to it's own static wrapper, in preparation for adding password reset functionality 2021-09-14 13:13:17 +01:00
e27823ae6c [CONTROLLER][Security] Refactor and make clearer errors with duplicate nicknames and emails. Return notes as a callable, since they're not used in the default template, in the login and register pages 2021-09-14 13:13:17 +01:00
32ca61e214 [TESTS][Queue] Add @codeCoverageIgnore to select queueing functions 2021-09-14 13:13:16 +01:00
41e4e2de0e [TESTS][Router] Add tests for Router and use named paramenters, as we can since PHP8 2021-09-14 13:13:16 +01:00
6d22932092 [ENTITY][LocalUser] Add method 'findByNicknameOrEmail' 2021-09-14 13:13:16 +01:00
e6d20bd30d [CORE][Controller][TESTS] Fix issue that arrises in tests where the Accept header is not specified 2021-09-14 13:13:16 +01:00
d07cb79844 [EXCEPTION] Add Email related exceptions 2021-09-14 13:13:16 +01:00
e250edf7fd [UTIL][Nickname] Refactor Nickname and extract a validate function, as we'll want to perform normalization in select cases in the future, likely as a plugin 2021-09-14 13:13:16 +01:00
1521d0d823 [UI] Add TODO annotation to login template, since it's possible to login using email, so the fonrm field names should be updated 2021-09-14 13:13:16 +01:00
f904b76ce7 [TESTS][DataFixtures] Add password and email to created test user 2021-09-14 13:13:16 +01:00
efacf6da56 [TESTS][Twig] Add @codeCoverageIgnore to select methods, as these are simple wrappers 2021-09-14 13:13:16 +01:00
1d40c5cdb3 [TESTS][DB] Update test to reflec fact that GSActor no longer has a normalized_nickname field 2021-09-14 13:13:15 +01:00
6b4b3e90fb [TESTS][Nickname] Update tests to reflect new usage. Normalization functionality will be moved to a plugin 2021-09-14 13:13:15 +01:00
adf0897527 [TESTS][TemporaryFile] Update test to reflect new usage 2021-09-14 13:13:15 +01:00
e77498ac19 [ENTITY] Remove 'normalized_nickname' field from GSActor as that feature will be moved to a plugin 2021-09-14 13:13:15 +01:00
2f69579ddb [TESTS][DOCUMENTATION][Module] Add documentation and exclude method from testing in Module base class 2021-09-14 13:13:15 +01:00
7a2574eafb [CORE][TemporaryFile] Add option to specify attempts and better handle when reaching the attemp limit without being able to create a file 2021-09-14 13:13:15 +01:00
f5fb4ed3c3 [TESTS] Add tests for GSFile 2021-09-14 13:13:15 +01:00
af5526d720 [DB] Refactor findOneBy method 2021-09-14 13:13:15 +01:00
7263752b18 [TESTS][DOCUMENTATION] Add documenation for the list events command and exclude it from unit testing 2021-09-14 13:13:15 +01:00
1c09aefd5a [CONFIG] Make password length limits configurable 2021-09-14 13:13:14 +01:00
6a74102d52 [TESTS] Exclude Data Fixtures from testing, as that happens before testing 2021-09-14 13:13:14 +01:00
de5650e98e [TESTS] Exclude class Security from testing, as it's a simple wrapper 2021-09-14 13:13:14 +01:00
d609dafdbc [TESTS] Raise test coverage for ModuleManager to 100% 2021-09-14 13:13:14 +01:00
bd321f05a7 [TESTS] Exclude HTTPClient from testing, as it's a simple wrapper and we don't want to be performing HTTP requests in tests, for speed and reliability 2021-09-14 13:13:14 +01:00
2ee99e5176 [TESTS] Raise test coverage for Form to 100% 2021-09-14 13:13:14 +01:00
0f634e86c7 [TESTS] Revert exposing Redis docker container ports, as this conflicts with the actual instance, and was intended for testing 2021-09-14 13:13:14 +01:00
b111870853 [TESTS][EVENTS] Raise test coverage for Event class to 100% 2021-09-14 13:13:14 +01:00
871422e6b6 [TESTS][ENTITY] Raise test coverage for Entity class to 100% 2021-09-14 13:13:14 +01:00
3b18853ff1 [TESTS][LOG] Raise test coverage for Log class to 100% 2021-09-14 13:13:14 +01:00
f98ce1c3d0 [TESTS] Ignore GNUsocial class from tests, as it simply pipes objects around 2021-09-14 13:13:13 +01:00
3cc1756d1b [FORM][DOCUMENTATION] Add documentation to Form class 2021-09-14 13:13:13 +01:00
f9a022745e [EVENT] Fixup implementation, as imformed by tests 2021-09-14 13:13:13 +01:00
d22711504c [ENTITY] Fixup implementation, as imformed by tests 2021-09-14 13:13:13 +01:00
8317c612ff [DB] Handle using methods with class name as well as table name and add lookup methods 2021-09-14 13:13:13 +01:00
747b464c7d [TESTS] Add missing tests for Common 2021-09-14 13:13:13 +01:00
9ce30751af [TESTS] Expand and fix cache tests 2021-09-14 13:13:13 +01:00
bfe9c6c9c4 [TESTS] Add ignore annotations to code paths that serve as hooks in DependencyInjection 2021-09-14 13:13:13 +01:00
28c010fcc7 [TESTS][CACHE] Fixup errors found in cache implementation by testing. Ensure the newest values are kept, in pushList with max_count 2021-09-14 13:13:12 +01:00
e2f61b05d8 [ENTITY] Fix foreign key type in Cover entity, as found by tests 2021-09-14 13:13:12 +01:00
a8081854c2 [TESTS] Exclude Routes from testing, as well as, temporarily, src/Security 2021-09-14 13:13:12 +01:00
88f1437ee7 [TESTS] Use vendor/bin/simple-phpunit for running the tests, as it provides the appropriate polyfills 2021-09-14 13:13:12 +01:00
082656d1ae [LEFT][RIGHT][CSS] Panels now occupy full page in smaller screen sizes. 2021-09-14 13:13:12 +01:00
bd9e86afe0 [RIGHT][CSS] Right panel now shows an intuitive icon for other note options available. 2021-09-14 13:13:12 +01:00
625c056f30 [TWIG][CSS] Overall CSS optimizations. Image gradients are now used, 64x64 px. 2021-09-14 13:13:12 +01:00
c8a8e94d48 [SETTINGS][PLUGIN][CONTROLLER] Populate profile tabs event added. Settings base template populated with such event for plugins and components.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:12 +01:00
6dd0292397 [SETTINGS][ROUTES][CONTROLLER] Settings overhaul, refactoring to accommodate new global settings view. WIP, component settings aren't shown at the moment.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:12 +01:00
eed74972c5 [SETTINGS][TWIG][CSS] Settings WIP. Form polished, dropdowns need styling.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:12 +01:00
d12f97c623 [BASE][CSS] Snappier and consistent animations.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:11 +01:00
10d7462d02 [TWIG][SETTINGS] WIP. Settings navigation early sketch.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:11 +01:00
a6f5c61aef [CSS] <figcaption> now has top padding for better legibility, hover now resizes it's element accordingly.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:11 +01:00
d16614982a [TWIG][CSS] Panels are fixed and base content acts accordingly on all sizes.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:11 +01:00
7b6a44cfbb [DOCS][Dev] Add HTTP Client 2021-09-14 13:13:11 +01:00
c99e447308 [DOCS][Dev] Add Security 2021-09-14 13:13:11 +01:00
367cc5c5c7 [DOCS][Dev] Add Queues 2021-09-14 13:13:11 +01:00
27fb2da1d0 [DOCS][Dev] Add Internationalisation 2021-09-14 13:13:11 +01:00
3dffbdd0b7 [DOCS][Dev] Add Attachments 2021-09-14 13:13:11 +01:00
2473c9afa9 [DOCS][Dev] Configure search 2021-09-14 13:13:10 +01:00
70ab7e7af7 [DOCS][Dev] Add Logging 2021-09-14 13:13:10 +01:00
ef827db77c [DOCS][Dev] Add Templates 2021-09-14 13:13:10 +01:00
ce39f6ca4a [DOCS][Dev] Add Routes and Controllers 2021-09-14 13:13:10 +01:00
0c6088225f [DOCS][Dev] Add Cache 2021-09-14 13:13:10 +01:00
b60a07f270 [DOCS][Dev] Cleanup src directory 2021-09-14 13:13:10 +01:00
9baf3a3124 [DOCS][Dev] Add database chapter 2021-09-14 13:13:10 +01:00
ad49988e0b [DOCS][DEV] Add events 2021-09-14 13:13:10 +01:00
2be4aeaab2 [DOCS][Paradigms] Elaborate on Null, Set and Void 2021-09-14 13:13:10 +01:00
c020958690 [DOCS] Write exceptions chapter 2021-09-14 13:13:09 +01:00
75bbf6c728 [TWIG][CSS] Final fix for panels, resizing works as intended, content is wrapped when no space is available. Simpler rules throughout.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
ce06c1dfd2 [TWIG][CSS] Panels weren't "flexing" properly, in smaller sizes the right panel form would break.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
e546721ca1 [TWIG][CSS] Fixed right panel buttons, fix issue where the form was invalid on send.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
203b16c5bf [TWIG][CSS] Right panel WIP. Create a note now looks better, need to style in some way the default user agent buttons without removing features (e.g. current file chosen).
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
4805550c66 [TWIG][CSS] Left panel profile section done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
9d50289c3f [TWIG][CSS] Register and Login styling done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
5e31e3290d [CSS] Fixing poor responsiveness to main nav element hover animation.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
e0a14f0b86 [CSS][TWIG] Left panel HTML nesting to better aid screen readers.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
cd05589f67 [CSS][TWIG] Panels icons are now hidden on desktop view to stop possible inconsistencies on window resizing. Left Panel polish.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:09 +01:00
2bae14198b [CSS][TWIG] Details element is now shown by default on larger screens, and hidden on smaller screens.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:08 +01:00
e5649d82ba [CSS][TWIG] Timeline navigation is now a part of the left panel, this way more items could be displayed at a time. Left panel styling WIP.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:08 +01:00
22f394f28f [TWIG] Microformats added to timeline, note's view and media. Should be backwards compatiblr as well.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:08 +01:00
03fb7b43e8 [CSS] Fixed accessibility issues with header and panel elements. Left, instance and right elements are now properly ordered when focused.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:08 +01:00
8bff04dae1 [CSS] Fixed incorrect Chromium based browsers improper main content view. Flex item order (shouldn't be a problem, but it was) and padding for the container class was the root issue.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:08 +01:00
7f561376ac [CSS] Main container 'order' attribute was somehow a problem in Chromium. 2021-09-14 13:13:08 +01:00
8b5f08a4bd [CSS] Fixed top content margin. 2021-09-14 13:13:08 +01:00
c06e666816 [TWIG][CSS] Accessibility improvements, all general links provide proper feedback now. Base theme CSS progress, page now looks more as it should. Panels WIP. 2021-09-14 13:13:08 +01:00
98e7e94dfa [TWIG][CSS][ICONS] New profile and notice creation panel, alternative text set correctly for header icons. WIP in base styling and panels. 2021-09-14 13:13:07 +01:00
868dbbd44e [TWIG][CSS] Found solution to show details content by default in desktop view. However, post_form is being rendered multiple times, it was already rendered in the Posting Component. Needs to be fixed. 2021-09-14 13:13:07 +01:00
9519891b92 [TWIG][CSS] Panels are now animated when shown, main content width as well as panel's is now correctly configured. WIP: displaying side panels by default when space is available, "details" element state cannot be manipulated through CSS only, need a better solution. 2021-09-14 13:13:07 +01:00
95c81cc741 [TWIG][CSS] Panels functionality works as intended, size needs to be worked upon. 2021-09-14 13:13:07 +01:00
effb4e6f40 [TWIG][CSS] Complete HTML refactoring, now using details element instead of a checkbox trick per panel. Using a "grid" akin to IBM's Carbon design guidelines. 2021-09-14 13:13:07 +01:00
3de10192ef [TWIG][CSS] Timeline WIP. Creating a notice is now done on the right panel. HTML structure improvements, such as the timeline divided from the navigation div. 2021-09-14 13:13:07 +01:00
a3f50e6732 [TWIG][CSS] Base template structural refactoring to accommodate true canons of page construction. CSS WIP to reflect these changes.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:07 +01:00
0b9cc721c4 [TWIG][CSS] Fixed right panel buttons, fix issue where the form was invalid on send.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:07 +01:00
c7a28876aa [TWIG][CSS] Right panel WIP. Create a note now looks better, need to style in some way the default user agent buttons without removing features (e.g. current file chosen).
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:07 +01:00
fc3ac1c2f4 [TWIG][CSS] Left panel profile section done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
14358b4b95 [TWIG][CSS] Register and Login styling done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
0c2272e094 [CSS] Fixing poor responsiveness to main nav element hover animation.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
d45ef53efd [CSS][TWIG] Left panel HTML nesting to better aid screen readers.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
1037d3397b [CSS][TWIG] Panels icons are now hidden on desktop view to stop possible inconsistencies on window resizing. Left Panel polish.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
db8a1b125f [CSS][TWIG] Details element is now shown by default on larger screens, and hidden on smaller screens.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
15cdcd3344 [CSS][TWIG] Timeline navigation is now a part of the left panel, this way more items could be displayed at a time. Left panel styling WIP.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
701b4967b4 [TWIG] Microformats added to timeline, note's view and media. Should be backwards compatible as well.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
cab7ba8e79 [CSS] Fixed accessibility issues with header and panel elements. Left, instance and right elements are now properly ordered when focused.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:06 +01:00
0620923ac6 [CSS] Fixed incorrect Chromium based browsers improper main content view. Flex item order (shouldn't be a problem, but it was) and padding for the container class was the root issue.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:05 +01:00
1c4568d064 [CSS] Main container 'order' attribute was somehow a problem in Chromium. 2021-09-14 13:13:05 +01:00
580f2d9962 [CSS] Fixed top content margin. 2021-09-14 13:13:05 +01:00
bfd0fa74f2 [TWIG][CSS] Accessibility improvements, all general links provide proper feedback now. Base theme CSS progress, page now looks more as it should. Panels WIP. 2021-09-14 13:13:05 +01:00
a44e81a1ed [TWIG][CSS][ICONS] New profile and notice creation panel, alternative text set correctly for header icons. WIP in base styling and panels. 2021-09-14 13:13:05 +01:00
f687c7b315 [TWIG][CSS] Found solution to show details content by default in desktop view. However, post_form is being rendered multiple times, it was already rendered in the Posting Component. Needs to be fixed. 2021-09-14 13:13:05 +01:00
b899c9fb2a [TWIG][CSS] Panels are now animated when shown, main content width as well as panel's is now correctly configured. WIP: displaying side panels by default when space is available, "details" element state cannot be manipulated through CSS only, need a better solution. 2021-09-14 13:13:05 +01:00
2084ae350e [TWIG][CSS] Panels functionality works as intended, size needs to be worked upon. 2021-09-14 13:13:05 +01:00
3812a2d04d [TWIG][CSS] Complete HTML refactoring, now using details element instead of a checkbox trick per panel. Using a "grid" akin to IBM's Carbon design guidelines. 2021-09-14 13:13:05 +01:00
ee3ed5f096 [TWIG][CSS] Timeline WIP. Creating a notice is now done on the right panel. HTML structure improvements, such as the timeline divided from the navigation div. 2021-09-14 13:13:05 +01:00
d3c47a2557 [TWIG][CSS] Base template structural refactoring to accommodate true canons of page construction. CSS WIP to reflect these changes.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-09-14 13:13:04 +01:00
80cde06f4b [MEDIA][Thumbnail] Fix non-instantiated variable 2021-09-14 13:13:04 +01:00
dc3801f6ae [DOCS][Dev] Write paradigms 2021-09-14 13:13:04 +01:00
cb7fa0a081 [SECURITY] Remove aggressive normalization of nicknames. This will be moved to a plugin in the future and we'll open an RFC, as discussed 2021-09-14 13:13:04 +01:00
4ec9b910c2 [TOOLS] Fix pre commit hook to allow for partial file commits (git add -p/git reset -p) 2021-09-14 13:13:04 +01:00
a5348f2427 [MEDIA][AttachmentThumbnail] Add mimetype to Entity 2021-09-14 13:13:04 +01:00
41dcef3c7b [Media] EncoderPlugins should handle the views that concern them
Ensure the intended filetypes and mimetypes during Vips conversions (part 2)
Sanitize Attachments instead of Validate (part 2)
Various bug fixes
2021-09-14 13:13:04 +01:00
861732176e [Media] Support any kind of thumbnails in the Core
Sanitize Attachments instead of Validate (part 1)
Ensure the intended filetypes and mimetypes during Vips conversions (part 1)
Various bug fixes
2021-09-14 13:13:03 +01:00
481e953cde [Media] File quota should be triggered by the Core 2021-09-14 13:13:03 +01:00
488e700fab [POSTING] Make it possible for plugins to change the placeholder string 2021-09-14 13:13:03 +01:00
72dcff22f7 [MEDIA] We need permissions to run chmod in directories
It's better to have in the filename data that we couldnt't otherwise rapidly retrieve
2021-09-14 13:13:03 +01:00
6ecdaa5d72 [MEDIA] Fix database misses on thumbnail retrieval
We were storing the real scaling values instead of the predictions, but the core is never able to pre-compute the real values generated by the encoding plugins so, we have to rely on our own aproximation function ported from v2
2021-09-14 13:13:03 +01:00
c8cf8c3f13 [FILE][TemporaryFile] Fix various issues now that we also have Symfony's file abstractions 2021-09-14 13:13:03 +01:00
6c0f3a336e [DOCS][Developer] Adopt a top-down approach
Minor corrections to the overview
2021-09-14 13:13:03 +01:00
8817613016 [CORE][GNUsocial] Fix undefined property typo 2021-09-14 13:13:03 +01:00
6cf7693f14 [DOCS][User][SysAdmin] Add thomask as an author as we're starting this from his unofficial docs 2021-09-14 13:13:03 +01:00
d5ab382485 [DOCS] Add designer book. 2021-09-14 13:13:02 +01:00
c33a65c45e [DOCKER][nginx] Removing default nginx config through docker/nginx/domain.sh. The default config conflicts with 'localhost' server_name. 2021-09-14 13:13:02 +01:00
b42128014e [DOCS] Elaborate initial architecture page 2021-09-14 13:13:02 +01:00
9f553707ba [CORE] Proxies: constant HEADER_X_FORWARDED_ALL is deprecated
Give ENV preference over SERVER
2021-09-14 13:13:02 +01:00
687b2e2bc7 [UTIL] Add utility to flatten the result of note queries 2021-09-14 13:13:02 +01:00
74f477489b [TESTS] Raise test coverage for App\Controller\Network to 100% and fixup related code 2021-09-14 13:13:02 +01:00
c5d4b7ecac [UI] Remove margin in timeline container 2021-09-14 13:13:02 +01:00
86400ce815 [UTIL] Provide static access to current request and utilities in Common 2021-09-14 13:13:02 +01:00
9198797aea [CORE] Throw more meaningfull error when method doesn't exist in Security and Entity 2021-09-14 13:13:02 +01:00
6d93b6fb32 [TESTS] Raise App\Core\DB\DB test coverage to 100% and fix issues found 2021-09-14 13:13:01 +01:00
52edaa319b [TESTS] Raise App\Core\DB\UpdateListener test coverage to 100% 2021-09-14 13:13:01 +01:00
d49541629d [TESTS] Change relevant tests to use GNUsocialTestCase, so they can access all the needed features 2021-09-14 13:13:01 +01:00
b9fbed2e3d [CORE] Clarify message when calling non existent method in Entity 2021-09-14 13:13:01 +01:00
45f54e615c [TESTS] Merge datafixtures to allow for using the correct ID in notes, and add group_inbox 2021-09-14 13:13:01 +01:00
e1a1d01844 [TESTS] Add GNUsocialTestCase, which initializes our infrastructure when bootKernel is called 2021-09-14 13:13:01 +01:00
d31c3b1784 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:13:01 +01:00
10c79bcafe [TOOLS][TESTS] Add coverage ignore tag to autogenerated code 2021-09-14 13:13:00 +01:00
de0c35d5a6 [TOOLS][TESTS] Add a data fixture with example notes, for testing 2021-09-14 13:13:00 +01:00
fbe0f36a53 [TESTS] Expand test coverage for App\Util\Forms\ArrayTransformer, App\Util\Notification and App\Twig\Runtime 2021-09-14 13:13:00 +01:00
6591d78a9c [TWIG] Remove unused Twig function 'get_note_other_content' 2021-09-14 13:13:00 +01:00
f0c6aa761b [TESTS] Add ignored files and folders to config 2021-09-14 13:13:00 +01:00
c84fcc608f [TESTS] Raise App\Util\TemporaryFile test coverage to 100% 2021-09-14 13:13:00 +01:00
ffa3774c46 [TOOLS][TESTS] Make tests run as www-data 2021-09-14 13:13:00 +01:00
fc9b934bbc [UTIL] Fix App\Util\TemporaryFile, adding default options and preventing warning on not enough permission 2021-09-14 13:12:59 +01:00
01a659e5b3 [TESTS] Raise test coverage for NicknameTest to 100% 2021-09-14 13:12:59 +01:00
31b6211bd0 [TOOLS][TESTS] Add data fixtures, which populate the database with users used for testing 2021-09-14 13:12:59 +01:00
ec1081ed43 [UTIL] Rename and rewrite isTaken to checkTaken 2021-09-14 13:12:59 +01:00
bd249b508b [ENTITY] Add 'normalized_nickname' to GSActor, the result of Nickname::normalize, so we can make sure we don't have very similar nicknames duplicated 2021-09-14 13:12:59 +01:00
f2727f9327 [DEPENDENCIES] Add doctrine/doctrine-fixtures-bundle, which allows populating the database in the testing environment 2021-09-14 13:12:59 +01:00
d2020eb7d8 [TOOLS][TESTS] Adjust configuration for testing environment 2021-09-14 13:12:59 +01:00
a49ee453ab [TESTS] Raise App\Util\HTML test coverage to 100% 2021-09-14 13:12:58 +01:00
fbea08ca9b [TESTS] Raise App\Util\Bitmap test coverage to 100% 2021-09-14 13:12:58 +01:00
abda73b8e3 [TESTS] Raise App\Util\Common test coverage to 100% 2021-09-14 13:12:58 +01:00
3fab198c04 [ENTITY] Add uniqueness constraint to Attachment::file_hash 2021-09-14 13:12:58 +01:00
4f936108a1 [ATTACHMENTS] Don't store an attachment if it's a dupplicate, reuse it 2021-09-14 13:12:58 +01:00
2b83a4b627 [UTILS][TemporaryFile] Change way TemporaryFile takes arguments and it's internal implementation 2021-09-14 13:12:58 +01:00
3b39046a38 [UTIL] Fix bugs found in App\Util\Formatting by tests 2021-09-14 13:12:58 +01:00
15a8f8ab7f [TESTS] Add tests increasing coverage of App\Util\Formatting to 100% 2021-09-14 13:12:58 +01:00
5d326bd18e [TESTS] Move testing container's DB and Redis to different ports, so it can be used at the same time as the regular containers 2021-09-14 13:12:58 +01:00
ee97cc5b00 [TESTS] Add test container with Xdebug and allow for generation of coverage reports with 'make test' 2021-09-14 13:12:57 +01:00
091f4b5194 [ATTACHMENTS] Do not create thumbnails for attachments with mimetype different from 'image|video' 2021-09-14 13:12:57 +01:00
6a999b8237 [Embed] Do not create AttachmenThumbnail 2021-09-14 13:12:57 +01:00
b6d7d46719 [ENTITY] Fix entity->has to access private properties with closure bindTo 2021-09-14 13:12:57 +01:00
da3754efba [CSS][Network] Minor fixes concerning attachment representation (centering and width) 2021-09-14 13:12:57 +01:00
1e7d8cac9a [ATTACHMENTS] Follow URL redirects and don't duplicate attachments 2021-09-14 13:12:57 +01:00
adb6680a01 [ATTACHMENTS] Respect config for smart crop 2021-09-14 13:12:57 +01:00
ab060332f0 [Posting] Don't sanitize on storage
We prefer to have the original input in database and sanitize on output when appropriate
2021-09-14 13:12:57 +01:00
8f43c8b405 [AUTOGENERATED] Update auto generated code 2021-09-14 13:12:57 +01:00
4fcccb1d1c [CORE][ImageEncoder] Add width and height back in attachment entity and allow for differently sized thumbs
The strategy adopted involves predicting the thumb size as we did in v2 before having vips resize
2021-09-14 13:12:56 +01:00
1c9f807595 [Embed] Fix some bugs and change AttachmentEmbed::url to ::media_url 2021-09-14 13:12:56 +01:00
c9090e6cee [ATTACHMENTS][GSFile] Rename ValidateAndStore functions 2021-09-14 13:12:56 +01:00
a3860e6257 [Embed] Local config 2021-09-14 13:12:56 +01:00
186e31e20d [DEPENDENCIES] Add ext-curl 2021-09-14 13:12:56 +01:00
6d31945401 [ATTACHMENTS] In sendFile, check that file exists or show a custom exception 2021-09-14 13:12:56 +01:00
38cf8f8efe [ENTITY] Change foreign key definition to new format for cover and profile_color tables 2021-09-14 13:12:56 +01:00
cfe842b487 [DOCUMENTATION] Add database diagram to developer documentation 2021-09-14 13:12:56 +01:00
06b236374d [CSS][Network] Improve attachment representation
Part of why we have thumbnails with a specific size is so that they are in the proper theme size
2021-09-14 13:12:56 +01:00
612c809469 [CSS][Left] Vertical scroll on left menu when it doesn't fit the screen 2021-09-14 13:12:55 +01:00
7a2f5e352b [CSS][Network] Fix horizontal menu on smaller screens 2021-09-14 13:12:55 +01:00
837f644458 [UTIL][Formatting] Add twigRenderFile 2021-09-14 13:12:55 +01:00
e9a96f1c9b [DOCUMENTATION] Add high level code walkthrough to developer docs 2021-09-14 13:12:55 +01:00
353595eb97 [Posting] Add missing default visibility option 2021-09-14 13:12:55 +01:00
5067bcd074 [ImageEncoder][FileQuota] Move quota enforcement to it's own plugin, so it can be easily shared and disabled 2021-09-14 13:12:55 +01:00
5bd16a509e [TEMPLATES] Fix identation 2021-09-14 13:12:55 +01:00
8cb64ede7f [AttachmentShowRelated] Move Attachment related to plugin 2021-09-14 13:12:55 +01:00
29a215534d [SECURITY] We can't really show a stream for this, was a nice concept, but not properly doable without requiring JS 2021-09-14 13:12:55 +01:00
d11615ded6 [Avatar][Embed] Change use of TemporaryFile::getPath to getRealPath 2021-09-14 13:12:55 +01:00
aad4bddedd [ImageEncoder] Implement attachment validation for images. This limits the maximum dimensions of the file, enforces a per file, per user and per user-month size quota and fixes the title if it's a filename, by replacing the extension to the new one 2021-09-14 13:12:54 +01:00
7509913fcf [ENTITY] Add field size to attachments, used for quota calculations 2021-09-14 13:12:54 +01:00
29457ef50d [UTIL] Add option for setting a file suffix on TemporaryFile constructor and add missing TemporaryFileException 2021-09-14 13:12:54 +01:00
a165e533bb [CONFIG] Add attachments/max_{width,height} config option, which is used as maximum dimensions when validating attachments 2021-09-14 13:12:54 +01:00
8ad8546aab [DEPENDENCIES] Add oroinc/doctrine-extensions, which provides cross database platform date functions 2021-09-14 13:12:54 +01:00
a0b820fd76 [CSS] Refactor Right Panel style 2021-09-14 13:12:54 +01:00
2d8d8ffb48 [TEMPLATES] Add dynamic blocks to right panel 2021-09-14 13:12:54 +01:00
4bd081ad27 [CSS] Fix invisible checkboxes 2021-09-14 13:12:54 +01:00
110c2572a4 [TEMPLATES] Minor refactoring, extending left was weird 2021-09-14 13:12:54 +01:00
e9b2b18093 [Avatar] Implement avatar deletion 2021-09-14 13:12:53 +01:00
2ec7059076 [AVATAR] Move avatar settings page to Avatar component 2021-09-14 13:12:53 +01:00
ced610d942 [Embed] Use Formatting utilities rather than substr and such 2021-09-14 13:12:53 +01:00
9008bee558 [Embed] Add docblock to handle function to pass pre-commit hook 2021-09-14 13:12:53 +01:00
88e5cec8fc [ENTITY][GSActor] Fix getAvatarUrl method 2021-09-14 13:12:53 +01:00
cbd383f92d [RIGHT] Introduce component abstraction 2021-09-14 13:12:53 +01:00
143b2460e9 [VIEWS] Minor proofreading of templates and css 2021-09-14 13:12:53 +01:00
aea8639d44 [ATTACHMENTS] Restrict thumbnail generation to allowed sizes. Defaults to only configured sizes, but can be extended with the event 'GetAllowedThumbnailSizes'. The intention is to prevent DoS attacks, since handling a thumbnail request is a relatively slow process 2021-09-14 13:12:53 +01:00
4f6f1941da [UI] Fix 'hide_attachments is not defined' error 2021-09-14 13:12:53 +01:00
436528172c [ATTACHMENTS] Add controller and templates for the attachment show page, which shows extra info about an attachment, such as related notes and tags 2021-09-14 13:12:53 +01:00
be91fb754d [CORE] Typo in GSFile and slight Twig weirdness in base template 2021-09-14 13:12:52 +01:00
b1e514832b [Embed][ENTITY] Fix embed route and use attachment_view rather than _show. Rename Entity::have to Entity::has, because grammar 2021-09-14 13:12:52 +01:00
30107de079 [Embed] Fix plugin. Only attempt to show an image, if we have one 2021-09-14 13:12:52 +01:00
2adb3c3521 [ATTACHMENTS] Add event 'AttachmentFileInfo' to allow a plugin to override the file displayed 2021-09-14 13:12:52 +01:00
708a910870 [UTIL] Fix remove affix utilities, so they only try to remove an affix if the string starts/ends with it 2021-09-14 13:12:52 +01:00
8988d89192 [CONFIG] Add attachment related parameters to default config 2021-09-14 13:12:52 +01:00
a7ef2babe6 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:12:52 +01:00
ece3c11e1b [DB] Fix error in config/services.yaml where the wrong namespace was used for the UpdateListener 2021-09-14 13:12:52 +01:00
fb220e82ed [TOOLS] Use \DateTimeInterface rather than DateTimeInterface 2021-09-14 13:12:52 +01:00
bb1ba11fcf [AUTOGENERATED] Run bin/generate_entity_fields for the Embed plugin 2021-09-14 13:12:51 +01:00
4cd152f640 [Embed] Review and port v2 code 2021-09-14 13:12:51 +01:00
7adc198a52 [ENTITY] Add meta method 'have*' to Entity base class, which checks if a field 'isset' 2021-09-14 13:12:51 +01:00
34059a8d3d [ATTACHMENTS][EVENT] Add onHashFile event, which can be used to deduplicate files
Currently, we simply hash the contents of the file with sha256, but in the future we can use something smarter,
which could find visual feature similarity between images
2021-09-14 13:12:51 +01:00
60a9085e56 [FORMATTING] Add utilities to remove affixes from strings 2021-09-14 13:12:51 +01:00
b647e31495 [DEPENDENCIES] Add 'embed/embed', 'nyholm/psr7' and 'symfony/dom-crawler' 2021-09-14 13:12:51 +01:00
9a7f1358c2 [ENTITY] Add utils to Attachment and AttachmentThumbnail to get the corresponding URL and html representation parameters 2021-09-14 13:12:51 +01:00
2a74dced22 [UTIL] Add TemporaryFile class, courtesy of Alexei Sorokin from v2, a class that ensures files stored in /tmp are removed, as it doesn't happen in some cases 2021-09-14 13:12:51 +01:00
7dab063a72 [PLUGINS] Remove scripts. These will need to be implemented with Commands 2021-09-14 13:12:48 +01:00
3d8c348cb8 [Emebed] Add Embed plugin and initial cleanup 2021-09-14 13:12:24 +01:00
2d91095260 [UI] Use event 'ShowAttachment' to permit plugins like Embed to alter the representation 2021-09-14 13:12:23 +01:00
94edad43d9 [UTIL] Add method to validate url 2021-09-14 13:12:23 +01:00
5c78def973 [HTTPClient] Add utility functions for all HTTP methods 2021-09-14 13:12:23 +01:00
1b8f5b7bf0 [DB] Make DB::findOneBy throw a different exception if two values are found 2021-09-14 13:12:23 +01:00
e94df546c3 [Posting] Extract and store URLs from note content. Introduce 'AttachmentStoreNew' event 2021-09-14 13:12:23 +01:00
ae0e410986 [TESTS] Fix Nickname test 2021-09-14 13:12:23 +01:00
8dff0b1d0c [Favourite] Make twig event operate on array rather than string 2021-09-14 13:12:23 +01:00
7a90e844b7 [SECURITY][DB] Make user register 'atomic', by using a single transaction for inserting all objects, to avoid partial inserts 2021-09-14 13:12:23 +01:00
77655c1248 [TESTS] Fix wrong namespace in tests 2021-09-14 13:12:23 +01:00
aeabf64051 [PLUGIN] Change base class from Module to Plugin for all plugins 2021-09-14 13:12:23 +01:00
a66118aee7 [AVATAR] Display avatar rounded 2021-09-14 13:12:22 +01:00
3afa872cec [UI] Use thumbnail path for thumbs 2021-09-14 13:12:22 +01:00
e1995f44ce [ATTACHMENTS] Move thumbnail controller to core and cleanup 2021-09-14 13:12:22 +01:00
3a7e92ed01 [ImageEncoder] Change preferred type to always be WEBP 2021-09-14 13:12:22 +01:00
1b1b3007db [AVATAR] Display avatar as round on the default theme 2021-09-14 13:12:22 +01:00
e6c6e7afd7 [AVATAR] Fix JS cropping script and save square image, in case other themes need it 2021-09-14 13:12:22 +01:00
d2bb5bba14 [ImageEncoder] Move DB::persist call to AttachmentThumbnail 2021-09-14 13:12:22 +01:00
0c8c5a4b87 [ImageEncoder] Fix error when not providing a width and/or height 2021-09-14 13:12:22 +01:00
e385a9ac29 [ATTACHMENTS] Even more further refactoring
Introduce Encoder plugins Instead of abstract upload and thumb modules

Ported attachment routes. In v3 thumbnail depends on existing attachment
so route updated accordingly.
2021-09-14 13:12:18 +01:00
0eaccc32fe [ATTACHMENTS] Further refactoring
Some key points:
- Components and Plugins shouldn't extend Module directly
- Avatars should be fetched via GSActor ID, not by nickname as that isn't unique
- Avatar now is a separate Component
- Common file utilities are now to be placed in Core\GSFile, this will
  handle storage and trigger validation
- Some bug fixes
2021-09-14 13:11:51 +01:00
cdef6858ce [ENTITY] Cache AttachmentThumbnail query result 2021-09-14 13:11:51 +01:00
65999bd183 [Poll] Move tables from core to plugin 2021-09-14 13:11:51 +01:00
716ca063d5 [ImageThumbnail] Finish image thumbnailing functionality 2021-09-14 13:11:51 +01:00
19850b5e0d [DB] Fix bug in custom criteria format wrangling 2021-09-14 13:11:51 +01:00
e834ac2c8d [DEPENDENCIES] Add php-vips 2021-09-14 13:11:51 +01:00
1d6d20aacb [TOOLS] In pre-commit hook, only run php-doc-check if some PHP file changed 2021-09-14 13:11:51 +01:00
081b0de919 [DOCKER] Add VIPS PHP extension to Docker build 2021-09-14 13:11:51 +01:00
4358656c55 [ImageThumbnail] Implement image resizing with Intervention/Image 2021-09-14 13:11:50 +01:00
ee87961d96 [ImageThumbnail] Structure of plugin to generate thumbnails for image attachments 2021-09-14 13:11:50 +01:00
7284c833a6 [DB] Allow DQL queries with table name rather than entity 2021-09-14 13:11:50 +01:00
0d01f1d060 [CORE] Add functionality to App\Core\Controller to get and validate GET parameters 2021-09-14 13:11:50 +01:00
c053e8da8e [Posting] Rename file to attachment and cache result of actor tag query 2021-09-14 13:11:50 +01:00
5579f4fa5d [MEDIA] Rename File to Attachment 2021-09-14 13:11:50 +01:00
dc5bdfa1fb [CORE] Rename NoteScope to VisibilityScope, as it will be used for attachment visbility too 2021-09-14 13:11:50 +01:00
9659762726 [DB] Add table map which allows using table names rather than entities in Doctrine operations 2021-09-14 13:11:50 +01:00
c44443b52c [AUTOGENERATED] Update autogenerated code 2021-09-14 13:11:50 +01:00
483ac38888 [Directory] Fixup directory plugin 2021-09-14 13:11:49 +01:00
cc9f2d6ff2 [DOCUMENTATION] Three books instead of only one
The User one is intended to illustrate the various common use cases, possibilities regarding customization and introduce the existing functionalities.

The Administrator one explains the step by step of how to install and maintain a GNU social instance, be it as node of The Free Network or as an intranet social network in a company setting.

The Developer one starts by introducing the Modules system and architecture, then the plugin development process and finally the exciting internals of GNU social for those looking forward to make the most advanced contributions.
2021-09-14 13:11:49 +01:00
e7ed325ac6 [MEDIA] Refactor File as Attachment 2021-09-14 13:11:49 +01:00
566c7694b5 [CONFIG][TWIG] Move twig config to php code to add support for placing templates in modules (plugins and components) 2021-09-14 13:11:49 +01:00
a220d07b0f [Directory] Move templates from core to plugin 2021-09-14 13:11:49 +01:00
ed59cce0f9 [COMPOSER][Media] Add example composer.json in plugins/Media 2021-09-14 13:11:49 +01:00
299bc5b551 [TWIG] Add way to launch events from TWIG, capture service and add way to render from a string 2021-09-14 13:11:49 +01:00
1b7c48c732 [LEFT][EVENT] Add event allowing plugins to add links to the left panel 2021-09-14 13:11:49 +01:00
8e56dbb95c [Favourite] Move table and left panel links to plugin 2021-09-14 13:11:49 +01:00
5f4815b12f [DEPENDENCIES] Add wikimedia/composer-merge-plugin 2021-09-14 13:11:48 +01:00
1687ecaa24 [FAVOURITE] Temporary commit to fix exception in timeline 2021-09-14 13:11:48 +01:00
974bbe164e [Favourite] Move controller to plugin 2021-09-14 13:11:48 +01:00
00d71e7035 Rename FFmpeg to VideoThumbnail FIXME 2021-09-14 13:11:48 +01:00
ccf3aa990a [MODULES] Introduce the concept of abstract modules to V3
Introduce placeholder for abstract upload and thumb modules
Temporarily supress some bugs
2021-09-14 13:11:45 +01:00
539f1861a6 [NETWORK] Fix big brain bug 2021-09-14 13:10:55 +01:00
ea802621aa [ENTITY] Remove extraneous File_thumbnail.php file 2021-09-14 13:10:55 +01:00
55b06705d5 [SCRIPTS] pre-commit now has variables double quoted 2021-09-14 13:10:55 +01:00
9997b231d4 [INSTALL] Elaborate on localhost installation 2021-09-14 13:10:54 +01:00
f8199159c3 [FFmpeg] Copy FFmpeg plugin from v2 2021-09-14 13:10:54 +01:00
155f30e816 [TOOLS] Fix missing exported variable in bin/configure 2021-09-14 13:10:54 +01:00
3b901745d5 [Embed][StoreRemoteMedia][Media] Copy and cleanup plugins from v2 2021-09-14 13:10:54 +01:00
fe478c6104 [Media] Copy media subsystem from v2 and roughly structure it for v3 2021-09-14 13:10:54 +01:00
a38ee03f18 [TOOLS][DOCKER] Make sure composer doesn't require interaction when installing 2021-09-14 13:10:54 +01:00
ad67358c3b [ActivityPub] Remove ActivityPub plugin until we're ready to work on it, as it needs significant work 2021-09-14 13:10:51 +01:00
e2e561e663 [DB][FKEY] Temporarily disable foreign key mapping, as there seems to be a bug in doctrine, which is under investigation 2021-09-14 13:10:23 +01:00
faa362e2e2 [DB] Remove unique constraint from GSActor.nickname and fix register and related functionality 2021-09-14 13:10:23 +01:00
68de1b09b1 [NICKNAME] Don't throw when normalizing reserved nicknames 2021-09-14 13:10:23 +01:00
78548365da [WEB] Fix translations and small inconsistency when opening on web 2021-09-14 13:10:23 +01:00
8b5bd40421 [TESTS] Add tests to all relevant methods under App\Util and fix errors that popup 2021-09-14 13:10:23 +01:00
57dad5eca4 [NICKNAME] Add nickname min length config and check it in Nickname::normalize 2021-09-14 13:10:23 +01:00
92d0848280 [DEPENDENCIES] Add jchook/phpunit-assert-throws and update dependencies 2021-09-14 13:10:23 +01:00
5afd2c9843 Duplicate src/Util/Exception/NicknameTooLongException.php history in src/Util/Exception/NicknameTooShortException.php history. 2021-09-14 13:10:23 +01:00
dc2a453e94 [TESTS] Add tests of Common and fix small oddities that pop up 2021-09-14 13:10:22 +01:00
b387ea9aa0 [TESTS] Fix error when testing cold redis cache 2021-09-14 13:10:22 +01:00
88e4044d02 [TESTS] Add tests for the bitmap utility and fix implementation 2021-09-14 13:10:22 +01:00
8d25859de7 [LOG] Only try to log if setup, so logs can be disabled, in tests, for instance 2021-09-14 13:10:22 +01:00
7e648a296f CACHE] Fix cache implementation with the help of tests and remove premature optimization for non-redis list caching
This complicated the code significantly and likely didn't help that much, if at all. The recommended setup is using Redis, anyway, which is plenty optimized
2021-09-14 13:10:22 +01:00
c539f17ba5 [TESTS] Add App\Core\Cache test 2021-09-14 13:10:22 +01:00
121faccb22 [DEPENDENCIES] Update dependencies, including redis-polyfill, to be able to implement a test 2021-09-14 13:10:22 +01:00
eb951b49bf [TESTS] Update PHPUnit configuration and upgrade to version 9.5 2021-09-14 13:10:22 +01:00
ff771223af [DOCKER][TOOLS] Add option to bin/configure to use a prebuilt PHP container (https://hub.docker.com/repository/docker/gsocial/php) 2021-09-14 13:10:22 +01:00
c8b2a7a2e0 [TESTS] Fix deprecations 2021-09-14 13:10:21 +01:00
ec9a9cec13 [DOCKER] Fix default docker-compose file 2021-09-14 13:10:21 +01:00
1028f05cd8 [TESTS] Fix unkept unit tests 2021-09-14 13:10:21 +01:00
2d70f484f2 [TEST] Fix translation test 2021-09-14 13:10:21 +01:00
f5a6e2f047 [DEPENDENCIES] Upgrade to Symfony 5.2 to get my upstream ICU translation feature 2021-09-14 13:10:21 +01:00
3b897abddb [DOCUMENTATION] Add documentation on installing without docker and other topics 2021-09-14 13:10:21 +01:00
12347af6bc [DOCUMENTATION] Add documentation on installing with Docker 2021-09-14 13:10:21 +01:00
c66801a5c4 [DOCTRINE][CONFIGURATION] Add new required Doctrine DBAL parameter, for testing environments 2021-09-14 13:10:21 +01:00
03a0df987e [DEPENDENCIES] Update all dependencies 2021-09-14 13:10:21 +01:00
2174f288d1 [DB] Fix Doctrine errors due to lack of column uniqueness
So, Doctrine doesn't like that `GSActorTag.tag` is not unique, even
though composite key `[tagger, tag]` is. `tag` can't unique, but
doctrine doesn't understand this. This seems like a Doctrine bug that
should be investigated. For now we'll just not mark it as a foreign
key
2021-09-14 13:10:20 +01:00
2e490756b9 [DOCKER][MAIL] Temporarily disable mail container 2021-09-14 13:10:20 +01:00
1773ab7af2 [DOCKER][MAIL] Update config and change the way mail docker handles it, so the edits aren't visible from the outside, polluting the git staging area 2021-09-14 13:10:20 +01:00
b3623329e3 [DOKER][MAIL][BOOTSTRAP] Make bootstrap generate separate certificates for the web root and the mail server 2021-09-14 13:10:20 +01:00
b824a0425e [DOCKER] Remove quotes from docker env files, as docker (or at least docker-compose) include them in the actual value 2021-09-14 13:10:20 +01:00
c894a4faa4 [DB] Make Note.source reference NoteSource.code, the primary key 2021-09-14 13:10:20 +01:00
a4a1a21403 [DOCKER] Rename docker-compose.yaml, so there can still be a default one on the repo, while the previous name can be used by the configurator without having problems with git 2021-09-14 13:10:20 +01:00
0e9737ee39 [TOOLS][DOCKER] Rewrite the configuration script to use whiptail/dialog, and refactor 2021-09-14 13:10:20 +01:00
Angelo D. Moura
8a48236d2d [UI][NOTE][MARKDOWN] Add markdown support to the notes 2021-09-14 13:10:20 +01:00
up201706832
8cc0360298 [REPLY] Fixed CSS for reply form, making it now usable 2021-09-14 13:10:19 +01:00
up201706832
f9443f1e87 [AUTH][REMEMBER-ME] Changed name of column in rememberme_token database table to fix bug 2021-09-14 13:10:19 +01:00
Daniel
15454cab7f [ProfileColor] Added profile color css 2021-09-14 13:10:19 +01:00
Daniel
d17582094d [ProfileColor] Visualize profile color 2021-09-14 13:10:19 +01:00
Daniel
afae038cff [ProfileColor] Added Profile Color entity, color form and db store/load to color settings controler 2021-09-14 13:10:19 +01:00
Daniel
20a5005e1d [ProfileColor] Added plugin base, controller and settings template 2021-09-14 13:10:19 +01:00
Daniel
746bf70e7d [REVERSEFAV] Added css to make Reverse favs label in one line 2021-09-14 13:10:19 +01:00
Daniel
ec85b5b96b [REVERSEFAV] Fixed typo 2021-09-14 13:10:19 +01:00
Daniel
f04923405f [REVERSEFAV] Added reverse favorourites stream/template 2021-09-14 13:10:19 +01:00
João Brandão
5516a77b33 [UI][TIMELINES] Refactored query for public stream 2021-09-14 13:10:19 +01:00
Daniel
c36436c1a1 [UI][TIMELINES] Fix undefined main_nav_tabs in logged out view 2021-09-14 13:10:18 +01:00
Angelo D. Moura
98145f4f1a [Directory] Actors stream now includes a link to groups stream 2021-09-14 13:10:18 +01:00
Angelo D. Moura
d4c0f33be4 [Directory] Lint fix - missed a coma 2021-09-14 13:10:18 +01:00
Angelo D. Moura
77d7fcc138 [Directory] Finished implementing groups stream as a plugin 2021-09-14 13:10:18 +01:00
Angelo D. Moura
fe170ff508 [Directory] Finished implementing groups stream using Directory plugin 2021-09-14 13:10:18 +01:00
Angelo D. Moura
098fdb3361 [Directory] Changed the route so the plugin Directory doesn't take over - query is not working for some reason 2021-09-14 13:10:18 +01:00
Angelo D. Moura
eb1ef4dd0f [Directory] Added the route, controller and a blank template file - something is broken 2021-09-14 13:10:18 +01:00
Angelo D. Moura
4f85efa071 [Directory] Actors are now organized by nickname 2021-09-14 13:10:18 +01:00
Angelo D. Moura
505e60d89f [Directory] Actors are no longer related with notes, and the logged in user now shows on the stream 2021-09-14 13:10:18 +01:00
Angelo D. Moura
2d91f3d0b3 [Directory] Changed the title of the template and add a css rule for actor-bio 2021-09-14 13:10:17 +01:00
Angelo D. Moura
0c5941f515 [Directory] Almost finishied creating the /actors stream - problems with the css files 2021-09-14 13:10:17 +01:00
Angelo D. Moura
517cba3510 [Directory] Add the route, controller function, and blank template file 2021-09-14 13:10:17 +01:00
Daniel
0289888397 [Directory] Add documentation 2021-09-14 13:10:17 +01:00
Daniel
019e3d91e4 [Directory] Moved /actors stream to directory plugin 2021-09-14 13:10:17 +01:00
Daniel
6c1b1323a0 [Cover] Remove of cover form 2021-09-14 13:10:17 +01:00
Daniel
b79c8b092d [Cover] Added cover route verifications 2021-09-14 13:10:17 +01:00
Daniel
4eafcd5058 [Cover] Removed commented code 2021-09-14 13:10:17 +01:00
Daniel
fdc2bc39d0 [Cover] Input restrictions, Code cleanup 2021-09-14 13:10:17 +01:00
Daniel
4949abac9d [Cover] Added cover css, changed cover settings route name 2021-09-14 13:10:16 +01:00
Daniel
e2ce1a8070 [Cover] Added temporary css 2021-09-14 13:10:16 +01:00
Daniel
aeec9149fc [Cover] Cover route, cover now renders 2021-09-14 13:10:16 +01:00
Daniel
cf8b3b7b73 [Cover] Added TWIG vars for profile plugins 2021-09-14 13:10:16 +01:00
Daniel
1cfe64cc25 [Cover] Added Cover Entity, updated form handler
Basically the same as the avatar
2021-09-14 13:10:16 +01:00
Daniel
7739518717 [Cover] Started implementing Cover plugin: base class, route, base templates, added tabs in profile template 2021-09-14 13:10:16 +01:00
Diogo Machado
f18a2a4bb6 [STATIC ANALYSIS] Started removal process for the errors found by PHPStan 2021-09-14 13:10:16 +01:00
Pastilhas
f319ccea78 [DOCKER][MAIL] Fixed hash command
Also added permissions to start.sh
2021-09-14 13:10:16 +01:00
margarida
f3c3d80892 [TOOLS][DOCKER] Add mail setup to configure 2021-09-14 13:10:16 +01:00
margarida
c36259f7c1 [TOOLS][DOCKER] Changed script to write docker-compose.yaml 2021-09-14 13:10:15 +01:00
margarida
6cb6eeb8a3 [TOOLS][DOCKER] Change dialog method to command substitution and redirection and add way of finding git's root 2021-09-14 13:10:15 +01:00
margarida
a8d211cdbf [TOOLS][DOCKER] Added input verfication 2021-09-14 13:10:15 +01:00
margarida
152d173e69 [TOOLS][DOCKER] Added first version of configuration shell script 2021-09-14 13:10:15 +01:00
João Brandão
85666b195b [UI] Visual restructure of login/register pages 2021-09-14 13:10:15 +01:00
João Brandão
7c7a03cfe1 [UI] Show public stream on login/register pages 2021-09-14 13:10:15 +01:00
up201706832
9351039a3b [UI] Extracted public stream on login/register pages to a twig template 2021-09-14 13:10:15 +01:00
João Brandão
ac480e5018 [UI] Visual restructure of login/register pages 2021-09-14 13:10:15 +01:00
Pastilhas
5b088dabc2 [DOCKER][MAIL] Fixed variable expansion in run 2021-09-14 13:10:15 +01:00
Pastilhas
d291a8dae5 [DOCKER][MAIL] Cleanup opendkim.conf
Also improved consistency in other files
2021-09-14 13:10:14 +01:00
Pastilhas
67483e415c [DOCKER][MAIL] Removed unsued files, modified dovecot.conf 2021-09-14 13:10:14 +01:00
Pastilhas
19ed10078f [DOCKER][MAIL] User is now created on setup and Dockerfile
Continuation of previous commit
2021-09-14 13:10:14 +01:00
Pastilhas
2c3599721e [DOCKER][MAIL] Removed unused config files and scripts
Now user is created on setup and dockerfile
2021-09-14 13:10:14 +01:00
Pastilhas
0518bc2c6b [DOCKER][MAIL] Substituted supervisord for s6 2021-09-14 13:10:14 +01:00
Pastilhas
def5e06a28 [DOCKER][MAIL] Moved and modified setup.sh 2021-09-14 13:10:14 +01:00
Pastilhas
93939d8b25 [DOCKER][MAIL] Switched named volume to shared volume and changed env vars to env file 2021-09-14 13:10:14 +01:00
Pastilhas
0137a68ccb [DOCKER][MAIL] Improved exec.sh 2021-09-14 13:10:14 +01:00
Pastilhas
cea170ed18 [DOCKER][MAIL] Fixed small bug with ssl certificates 2021-09-14 13:10:14 +01:00
Pastilhas
11dbbef351 [DOCKER][MAIL] Fixed small bugs in config and scripts 2021-09-14 13:10:14 +01:00
Pastilhas
c2e6e3706f [DOCKER][MAIL] New config files 2021-09-14 13:10:13 +01:00
Pastilhas
419a2ceb1a [DOCKER][MAIL] Fused services into single container 2021-09-14 13:10:13 +01:00
Pastilhas
86a95def01 [DOCKER][MAIL] Changed directory path 2021-09-14 13:10:13 +01:00
Pastilhas
bc1d85de56 [DOCKER][MAIL] Setup docker mail server 2021-09-14 13:10:13 +01:00
Pastilhas
250235b1be [DOCKER][MAIL] Added docker mailserver setup 2021-09-14 13:10:13 +01:00
Daniel
1747e14824 [Poll] Removed/refactored unnecessary files, changed redirect to default parameters in new poll route 2021-09-14 13:10:13 +01:00
Daniel
c0d363e317 [Poll] Restructured templates, added misssing poll related css 2021-09-14 13:10:13 +01:00
Daniel
7a299162e1 [Poll] Polls now have an associated note, poll templates, start_show_styles event, started css 2021-09-14 13:10:13 +01:00
Daniel
090c593a61 [Poll] Started testing with note integration 2021-09-14 13:10:13 +01:00
Daniel
50ec306243 [Poll] Added file headers 2021-09-14 13:10:12 +01:00
Daniel
ffb4b9df23 [Poll] Added modified param for Poll/Poll response, added PollTest 2021-09-14 13:10:12 +01:00
Daniel
f34fb9c7b9 [Poll] Added variable num of options
not sure if it is the right way to do it
2021-09-14 13:10:12 +01:00
Daniel
cdbf7da8be [Poll] Added templates, response counting 2021-09-14 13:10:12 +01:00
Daniel
27a0c43f7b [Poll] Store poll response to DB 2021-09-14 13:10:12 +01:00
Daniel
3725818e4f [Poll] Added New Route, RespondPoll, Poll Response, PollResponseForm 2021-09-14 13:10:12 +01:00
Daniel
dbb55362c8 [Poll] Fixed ShowPoll route, moved Poll Entity, created NewPollForm
Entity was temporarily moved to src/Entity in order to load from DB, since it is yet no possible to do that from Plugin
2021-09-14 13:10:12 +01:00
Daniel
03f02bed4d [AUTOGENERATED][Poll] Add auto generated code for poll entity and new route 2021-09-14 13:10:12 +01:00
Daniel
5978a069e9 [Poll] Started porting Poll Plugin 2021-09-14 13:10:12 +01:00
margarida
c26ee45bf3 [TOOLS][DOCKER] Added input verfication 2021-09-14 13:10:11 +01:00
margarida
987d29a674 [TOOLS][DOCKER] Added first version of configuration shell script 2021-09-14 13:10:11 +01:00
Daniel
95f95d2dd8 [TESTS] Added unit tests 2021-09-14 13:10:08 +01:00
Angelo D. Moura
d53fef09a8 [TWIG] Moves the SVG custom function to an extension and change the test regex 2021-09-14 13:06:58 +01:00
Angelo D. Moura
d2208d15d8 [TWIG][TESTS] Update IconsExtension test 2021-09-14 13:06:58 +01:00
Angelo D. Moura
5acfda8ae5 [TWIG] Add SVG icon embed function 2021-09-14 13:06:58 +01:00
Diogo Machado
fbc85086fd [DB][TESTS] Implement Doctrine event listener to update timestamps on modification, and related tests 2021-09-14 13:06:58 +01:00
Daniel
c3aa2ae400 [FORM] Implement ActorArrayTransformer 2021-09-14 13:06:57 +01:00
279cfcd058 [PLUGIN][Favourite] Move favourite table definition to inside the plugin, as it is now supported 2021-09-14 13:06:57 +01:00
417e2f351b [SchemaDef] Finish association mapping implementation 2021-09-14 13:06:57 +01:00
1d42c7a835 [DB][NoteLocation] Add missing multiplicity to column 2021-09-14 13:06:57 +01:00
4e4d4dfdc5 [DB] Fix typo in table definitions and fix name of GSActorCircle table 2021-09-14 13:06:57 +01:00
481027b09b [AUTOGENERATED][DB][File][GroupJoinQueue] Update autogenerated code and add select fields as specified in the previous commit 2021-09-14 13:06:57 +01:00
1712782cc5 [DB] Change foreign key specification to new format 2021-09-14 13:06:57 +01:00
ea0aca4b00 [SCHEMADEF] Add preliminary support for foreign keys 2021-09-14 13:06:57 +01:00
9cd5560081 [DOCKER] Bump to PHP version 8 2021-09-14 13:06:57 +01:00
e2e53d9a2a [AUTOGENERATED] Update auto generated code in entities 2021-09-14 13:06:56 +01:00
b27bda6a7c [TOOLS] Use GSActor rather than Gsactor in autogenerated code 2021-09-14 13:06:56 +01:00
460712e15e [GIT] Change my email to the new one in all files and bump copyright year 2021-09-14 13:06:56 +01:00
f95f69c778 Add some missing documentation to ActivityPub 2021-09-14 13:06:56 +01:00
74e586182d [DB] Merge definition of SchemaDefDriver with SchemaDefPass for clarity 2021-09-14 13:06:56 +01:00
fc015c6fdf [NETWORK][TreeNotes] Add TreeNotes plugin which takes over the responsibility of displaying a conversation as a tree, in order to reduce the number of queries 2021-09-14 13:06:56 +01:00
5c53889739 [Reply] Fix bug where wrong variable is used when replying to a note 2021-09-14 13:06:56 +01:00
Hugo Sales
ce3cae0ef7 [DEPENDENCIES] Update all dependencies 2021-09-14 13:06:56 +01:00
Hugo Sales
eefdf74658 Ensure group table name is quoted, as it's a reserved word in postgreSQL 2021-09-14 13:06:56 +01:00
Hugo Sales
ab252accb2 Small update to php dockerfile 2021-09-14 13:06:56 +01:00
Hugo Sales
411884bfc5 [DEPENDENCIES] Update dependencies 2021-09-14 13:06:55 +01:00
1b946586e1 [DOCUMENTATION] Add mdBook stub 2021-09-14 13:06:55 +01:00
Hugo Sales
ebfd41ba0d [CONFIGURATION] Remove the individual language settings from social.yaml, as these are not something that changes at runtime 2021-09-14 13:06:55 +01:00
Hugo Sales
3301770642 [COMMAND] Change the way ListEventsCommand outputs the results to use the output interface and add some formatting 2021-09-14 13:06:55 +01:00
Hugo Sales
e8feb2ae84 [DOCUMENTATION][REFACTOR] Add documentation to all flagged function and do some small cleanup 2021-09-14 13:06:55 +01:00
Hugo Sales
67d4702743 [HOOKS] Update pre-commit script to check for missing documentation in functions 2021-09-14 13:06:55 +01:00
Hugo Sales
de8fa87079 [DEPENDENCIES] Update dependencies 2021-09-14 13:06:55 +01:00
Hugo Sales
8ce1c1cee6 [DEPENDENCIES] Update dependencies 2021-09-14 13:06:55 +01:00
Hugo Sales
1949e0b987 [EVENT] Rename event names to camel case to make finding handlers easier 2021-09-14 13:06:55 +01:00
Hugo Sales
0b759da780 [REGISTER] Add self follow when registering, fixing the '-1 followers' bug 2021-09-14 13:06:54 +01:00
Hugo Sales
089c710711 [SETTINGS][NOTIFICATIONS] Fix error when displaying the user notification settings page 2021-09-14 13:06:54 +01:00
Hugo Sales
221829c6fd [Reply] Fix missing use statement 2021-09-14 13:06:54 +01:00
Hugo Sales
be324bb390 [CONTROLLER] Fix use of undefined variable 2021-09-14 13:06:54 +01:00
Hugo Sales
74350becc5 [POSTING] Fix missing use statement 2021-09-14 13:06:54 +01:00
Tiago Magalhaes
61ebeca706 [CORE] made configure script explicitly fail when bootstrap.env is not present 2021-09-14 13:06:54 +01:00
Hugo Sales
44f51ce715 [CONFIG][CACHE] Move cache configuration from environment variables to the configuration file 2021-09-14 13:06:54 +01:00
Hugo Sales
6cf30f3f65 [UI] Make configured instance name show in UI, fix repeat icon 2021-09-14 13:06:54 +01:00
Hugo Sales
e949dd654a [CONFIG] Various fixes to use new configuration format 2021-09-14 13:06:54 +01:00
Hugo Sales
c4c693b283 [CONFIG] Fix error on missing or empty local configuration 2021-09-14 13:06:53 +01:00
Hugo Sales
dd40255c4a [CONFIG][DB] Remove config from the database, put it in yaml, so it can be baked into the container 2021-09-14 13:06:53 +01:00
Hugo Sales
d1ea9d2fdf [DEPENDENCIES] Update dependencies 2021-09-14 13:06:53 +01:00
Hugo Sales
ebb33cd6f7 [CSS][UI] Update CSS to fix reply note action (icon swap needed) and remove duplication 2021-09-14 13:06:53 +01:00
Hugo Sales
46f1cf9529 [CORE] Add missing use statement in module base class 2021-09-14 13:06:53 +01:00
Hugo Sales
179d7f3335 [UTILS] Make bitmap not use a static class var and set object properties as lowercase 2021-09-14 13:06:53 +01:00
Hugo Sales
147ff89e74 [NoteAction] Refactor duplicated code out to base class 2021-09-14 13:06:53 +01:00
Hugo Sales
a6c24393b5 [NOTE] Add isVisibleTo 2021-09-14 13:06:53 +01:00
Hugo Sales
0c0b00da93 [Directory] Add missing use statement 2021-09-14 13:06:53 +01:00
Hugo Sales
29082f4aa9 POSTING remove REPLY route 2021-09-14 13:06:52 +01:00
Hugo Sales
63d2d58e9e [EXCEPTION] Make findOne return NotFoundException 2021-09-14 13:06:52 +01:00
Hugo Sales
c07a0cdcd5 [EXCEPTION] Add base class to invalid form exception and add URL arguments to redirect exception 2021-09-14 13:06:52 +01:00
Hugo Sales
0e332b718e [SECURITY] Fix getRoles 2021-09-14 13:06:52 +01:00
Hugo Sales
c0ce25c352 [MODULES] Fix module manager dev-mode rebuild 2021-09-14 13:06:52 +01:00
Hugo Sales
0a56061639 [UI] Only show note action buttons if a user is logged in 2021-09-14 13:06:52 +01:00
Hugo Sales
2164f21834 [Controller] Fix exception handler to recurse on the exception's previous (in some contexts, RedirectException gets wrapped) 2021-09-14 13:06:52 +01:00
Hugo Sales
ffcf909bda [UTIL] Update bitmap base class, making using easier 2021-09-14 13:06:52 +01:00
Hugo Sales
a248f23cef [Reply] Move reply functionality to a plugin 2021-09-14 13:06:52 +01:00
Hugo Sales
72208b066c [NoteActions] Refactor note actions and fix bug in favourite 2021-09-14 13:06:51 +01:00
Hugo Sales
4c15271d36 [UI] Display error when submitted form is invalid 2021-09-14 13:06:51 +01:00
Hugo Sales
5fc7647c40 [MODULE][DB] Added support for loading entity definitions from modules 2021-09-14 13:06:51 +01:00
Hugo Sales
7de1654f9a FIXUP WITH DOCTRINE DEV 2021-09-14 13:06:51 +01:00
Hugo Sales
41f90f07b1 [Bridge] Replace zero dates with CURRENT_TIMESTAMP 2021-09-14 13:06:51 +01:00
Hugo Sales
6a2a0d4e66 [TOOLS] Add support for updating autocode in modules 2021-09-14 13:06:51 +01:00
Hugo Sales
cc758f6a8e [AUTOGENERATED] Update autogenerated code in module entities 2021-09-14 13:06:51 +01:00
Hugo Sales
a9b6bc78a6 [CONFIG][DEV][DOCTRINE] Add doctrine stacktrace tracking in dev mode 2021-09-14 13:06:51 +01:00
Hugo Sales
b906dde059 [EXCEPTION][UI][UX] Add RedirectException, which can be thrown anywhere to redirect somewhere, and an exception handler 2021-09-14 13:06:51 +01:00
Hugo Sales
420b4767b2 [UI][NOTE] Add reply to in UI 2021-09-14 13:06:51 +01:00
Hugo Sales
9573cab4cb [Posting] Fix form name and remove unused recycle route and controller 2021-09-14 13:06:50 +01:00
Hugo Sales
9865798766 [Media] Add cache control directive to all files served 2021-09-14 13:06:50 +01:00
Hugo Sales
6b1689e1df [Repeat][Favourite] Only display action buttons if logged in (instead of forcing login) 2021-09-14 13:06:50 +01:00
Hugo Sales
7af424b64a [DB] Add rendered collumn to note table, so we can preserve microtags from other services 2021-09-14 13:06:50 +01:00
Hugo Sales
460cbbd4c9 [DB] Fix local_user table to use a numeric id, since the username is editable 2021-09-14 13:06:50 +01:00
rainydaysavings
a69c9a4f25 [TWIG] Improving view template structure 2021-09-14 13:06:50 +01:00
rainydaysavings
099c4220d2 [TWIG] Adding active rules 2021-09-14 13:06:50 +01:00
rainydaysavings
244aa4af08 [UI] CSS polish all around 2021-09-14 13:06:50 +01:00
rainydaysavings
492360ceeb [CONTROLLER][ROUTE] Favourites page initial query implementation and routing 2021-09-14 13:06:50 +01:00
rainydaysavings
651ec890d6 [UI] Fixing Login CSS issues 2021-09-14 13:06:49 +01:00
Hugo Sales
55466143f2 [UI][FEED] Fix scope in timelines; major rewrite of home timeline query, still missing scoping and paging 2021-09-14 13:06:49 +01:00
Hugo Sales
e1181ab998 [DB][DEFAULTS] Change attachment storage location from uploads to attachments 2021-09-14 13:06:49 +01:00
Hugo Sales
3b86a46625 [DB] Rename notice to activity in notification table 2021-09-14 13:06:49 +01:00
Hugo Sales
51a398f27a [UI] Subtract self follow from total user follows 2021-09-14 13:06:49 +01:00
Hugo Sales
6e11143b79 [DB][NOTE] Update scope 2021-09-14 13:06:49 +01:00
Hugo Sales
6165f7cd55 [Media] Display images and videos inline in notes 2021-09-14 13:06:49 +01:00
rainydaysavings
a9d5f8ac5b [UI][TWIG] Fixing note actions placement and size, more descriptive rules 2021-09-14 13:06:49 +01:00
Hugo Sales
26f01c4c92 [Posting] Fix posting form name and css 2021-09-14 13:06:49 +01:00
Hugo Sales
07078414bb [FORM] Add names to forms 2021-09-14 13:06:48 +01:00
Hugo Sales
adc843c1d6 [DB] Add Activity table, to store all known activity 2021-09-14 13:06:48 +01:00
Hugo Sales
d95c22cb3e [EXCEPTION] Fix exceptions not being translated 2021-09-14 13:06:48 +01:00
Hugo Sales
80cefca90d [DB] Add wrapper for making native queries 2021-09-14 13:06:48 +01:00
Hugo Sales
a98af6ab6a [Directory] Add directory plugin, for listing people and groups 2021-09-14 13:06:48 +01:00
Hugo Sales
6dbd239544 [Posting] Add missing includes 2021-09-14 13:06:48 +01:00
rainydaysavings
7cb13ee5e9 [UI] All radio buttons now look like they should 2021-09-14 13:06:48 +01:00
rainydaysavings
ff9b5d9c01 [PLUGIN] Removing unnecessary labels 2021-09-14 13:06:48 +01:00
rainydaysavings
0649095e88 [TWIG] Making notes view more easily customizable 2021-09-14 13:06:48 +01:00
rainydaysavings
a7744351dd [UI] Fixing note actions views 2021-09-14 13:06:47 +01:00
rainydaysavings
6188524586 [PLUGIN] Recycle initial implementation 2021-09-14 13:06:47 +01:00
rainydaysavings
8a0418d8cf [Favourite] Add backend support for favourite 2021-09-14 13:06:47 +01:00
rainydaysavings
b6fb0255da [DB] Temporarily add favourite entity in core, as plugins don't support them yet 2021-09-14 13:06:47 +01:00
Hugo Sales
5b7fcc44cf [DB] Add helper for removing entities 2021-09-14 13:06:47 +01:00
Hugo Sales
ea4f2c522b [DB] Add support for calling methods with FQCN 2021-09-14 13:06:47 +01:00
rainydaysavings
4328a11eb1 [TWIG] Various routes added 2021-09-14 13:06:47 +01:00
rainydaysavings
3d3db96312 [UI] Replies border fix and other minor fixes 2021-09-14 13:06:47 +01:00
rainydaysavings
413247d344 [CONTROLLER] Replies and network queries implemented 2021-09-14 13:06:47 +01:00
rainydaysavings
a804c5f981 [COMPONENT][CONTROLLER][TWIG] Recycle component work 2021-09-14 13:06:47 +01:00
rainydaysavings
55e468bd70 [TWIG][UI] Replies CSS fixes 2021-09-14 13:06:46 +01:00
rainydaysavings
62e76cb036 [ROUTE] Network and replies routes added 2021-09-14 13:06:46 +01:00
rainydaysavings
e1e3e2d9b3 [COMPONENT] Favourite initial implementation 2021-09-14 13:06:46 +01:00
rainydaysavings
fee81f8499 [UI] Fixing note actions placement 2021-09-14 13:06:46 +01:00
rainydaysavings
0b3543aaff [UI] Responsiveness overall polish 2021-09-14 13:06:46 +01:00
rainydaysavings
baac5ef19c [UI] Browser compatibility improvements, various small fixes 2021-09-14 13:06:46 +01:00
rainydaysavings
59da3df28c [UI] Checkboxes now display a custom tick 2021-09-14 13:06:46 +01:00
rainydaysavings
189da22204 [TWIG] Timeline attachment form restructure 2021-09-14 13:06:46 +01:00
rainydaysavings
04f43a9d37 [Controller] Attempting to fix home timeline query 2021-09-14 13:06:46 +01:00
rainydaysavings
ecdd393c8e [UI] No focus outlines by default 2021-09-14 13:06:45 +01:00
rainydaysavings
47901e7ed6 [UI] Reply icon now shows accordingly, same for the replies themselves 2021-09-14 13:06:45 +01:00
rainydaysavings
1742bce78e [UI] Custom and accessible checkboxes, radio buttons and normal buttons 2021-09-14 13:06:45 +01:00
rainydaysavings
f1a687b057 [COMPONENT] Fixing typo 2021-09-14 13:06:45 +01:00
Hugo Sales
0b50905ac8 [NOTE][UI] Add note replying and UI displaying 2021-09-14 13:06:45 +01:00
Hugo Sales
459f0bf41f [CACHE] Fix bug in list caching 2021-09-14 13:06:45 +01:00
Hugo Sales
496dec4254 [MODULE] Fix avatars not loading 2021-09-14 13:06:45 +01:00
Hugo Sales
f60bdaa2f0 [DB][MODULES][ActivityPub] Cleanup table definitions 2021-09-14 13:06:45 +01:00
rainydaysavings
522f40ca2f [TWIG] Timeline template rework 2021-09-14 13:06:45 +01:00
rainydaysavings
7bc62868f7 [ROUTE] Home timeline route url now shows accordingly as the user nickname 2021-09-14 13:06:44 +01:00
rainydaysavings
e812dba033 [UI] Posting form re-styling 2021-09-14 13:06:44 +01:00
rainydaysavings
ecfda08d37 [CONTROLLER] Reply initial implementation 2021-09-14 13:06:44 +01:00
rainydaysavings
bc66e2c2a2 [COMPONENT] Posting form restructure and minor fixes 2021-09-14 13:06:44 +01:00
rainydaysavings
a8e43a4867 [TWIG] Timeline structure rework 2021-09-14 13:06:44 +01:00
rainydaysavings
7fab19fd8f [ROUTE] Home timeline added 2021-09-14 13:06:44 +01:00
rainydaysavings
d08757d0a4 [CONTROLLER] Home timeline controller work 2021-09-14 13:06:44 +01:00
rainydaysavings
9e3eb9992f [UI] Posting form styling work 2021-09-14 13:06:44 +01:00
rainydaysavings
1eab561b40 [COMPONENT] Posting form now shows a random default string 2021-09-14 13:06:44 +01:00
rainydaysavings
315a70ba0d [UI] Links removed since they are part of a plugin 2021-09-14 13:06:44 +01:00
rainydaysavings
9fcd18f751 [UI] Post form new structure first styling implementation 2021-09-14 13:06:43 +01:00
rainydaysavings
8a5cadf8d2 [UI] Small border fix 2021-09-14 13:06:43 +01:00
rainydaysavings
9b421e0095 [COMPONENTS] Small fix 2021-09-14 13:06:43 +01:00
rainydaysavings
160e56c61e [UI][TWIG] Better, divided form rendring of the posting form 2021-09-14 13:06:43 +01:00
rainydaysavings
c837194af2 [UI] Small border radius problem fix 2021-09-14 13:06:43 +01:00
rainydaysavings
1563ebc546 [COMPONENT] Posts scope initial form 2021-09-14 13:06:43 +01:00
rainydaysavings
fe0af2caed [UI] Fixing login and register styling, refactoring 2021-09-14 13:06:43 +01:00
rainydaysavings
99ac4dc2a3 [UI] Fixing issue where notices wouldn't break text 2021-09-14 13:06:43 +01:00
rainydaysavings
396af498a2 [UI] Fixing static pages styling 2021-09-14 13:06:43 +01:00
rainydaysavings
a85b5b44f1 [UI] Fixes to settings CSS 2021-09-14 13:06:42 +01:00
rainydaysavings
2c59dcefcf [UI] Finalizing timeline structure and CSS 2021-09-14 13:06:42 +01:00
rainydaysavings
33832297bd [UI] New reset CSS to deal with firefox's abysmal and evil defaults 2021-09-14 13:06:42 +01:00
Hugo Sales
b624359b9a [ActivityPub] Initial cleanup, removing 'die' statements, and ignoring the subfolders 2021-09-14 13:06:39 +01:00
Hugo Sales
38cfec8593 [UI][TWIG] Small UI cleanup and change twig 'active' function to check for starts with, rather than equals 2021-09-14 13:05:58 +01:00
Hugo Sales
f2ab77c3a9 [DB][MEDIA] Small database structure changes 2021-09-14 13:05:58 +01:00
Hugo Sales
4507b12976 [MEDIA] Only try to get an avatar if a user is logged in 2021-09-14 13:05:58 +01:00
Hugo Sales
6ed89c77f4 [UI][NOTE] Post and see attachments 2021-09-14 13:05:57 +01:00
Hugo Sales
a5cf89674e [DEPENDENCY] Add tgalopin/html-sanitizer-bundle and transitively tgalopin/html-sanitizer 2021-09-14 13:05:57 +01:00
Hugo Sales
9649bec01e [MEDIA][CACHE] Cache avatar queries and delete stale values; small refactoring 2021-09-14 13:05:57 +01:00
Hugo Sales
e3c5d7e5dc [UI][MEDIA] Add actor avatar in feed timeline 2021-09-14 13:05:57 +01:00
Hugo Sales
de22f18abf [SECURITY] Fix error in user registering where password wasn't hashed 2021-09-14 13:05:57 +01:00
rainydaysavings
1b350d51fc [UI] Fixing timeline notice structure and CSS 2021-09-14 13:05:57 +01:00
rainydaysavings
3def39fed3 [UI] Fix left panel new dynamic components view 2021-09-14 13:05:57 +01:00
Hugo Sales
09a2541c36 [UI][SELFTAGS] Display 'none' if the user doesn't have selftags 2021-09-14 13:05:57 +01:00
Hugo Sales
2486eb1949 [UI][ACCOUNT][SETTINGS] Hack to fix error related to phone number, until a solution is found upstream 2021-09-14 13:05:57 +01:00
Hugo Sales
b19be6be52 [UI][LEFT] Add # before selftags, and link 2021-09-14 13:05:56 +01:00
Hugo Sales
7e4138399c [UI][FAQ] Fix static pages 2021-09-14 13:05:56 +01:00
Hugo Sales
9159fe8d05 [CONTROLLER] Stop propagation of kernel.controller so notices aren't posted 5 times. Not sure why it happens otherwise 2021-09-14 13:05:56 +01:00
Hugo Sales
6f01b0cebe [WRAPPER][HTTPClient] Static wrapper around Symfony's HTTP Client 2021-09-14 13:05:56 +01:00
Hugo Sales
be83d3532e [DB][FOLLOW] Change Follow table 2021-09-14 13:05:56 +01:00
Hugo Sales
65a129aac6 [UI][CACHE][DB] Add follow counts to left panel, caching the results; change follow table 2021-09-14 13:05:56 +01:00
Hugo Sales
3c67773e59 [UI][LEFT] Add link to settings on avatar and personal info 2021-09-14 13:05:56 +01:00
Hugo Sales
c8e8f1f057 [ENTITY] Add Entity base class to all entities 2021-09-14 13:05:56 +01:00
Hugo Sales
d548dc9284 [MODULE][Left][UI][TAGS] Add Left module which handles fetching tags and followers, fix self tags 2021-09-14 13:05:56 +01:00
Hugo Sales
31ccb2d07b [DB][File] Remove timestamp, add actor_id 2021-09-14 13:05:55 +01:00
Hugo Sales
2e9c340684 [DB][AVATAR] Remove extraneous slash 2021-09-14 13:05:55 +01:00
Hugo Sales
44d4aade95 [DB][DEFAULTS] Add avatar/default 2021-09-14 13:05:55 +01:00
Hugo Sales
7f1ce816ae [UserPanel] Fix upload of avatar 2021-09-14 13:05:55 +01:00
Hugo Sales
f255d29078 [Media] Use utils 2021-09-14 13:05:55 +01:00
Hugo Sales
8cfa883c1b [DB] Add 'dql' method to wrap 'createQuery' and replace 'Gsactor' with 'GSActor' 2021-09-14 13:05:55 +01:00
Hugo Sales
688ee18411 [Media] Move code from media.php to utils.php 2021-09-14 13:05:55 +01:00
Hugo Sales
72876fe8dc [MODULE][Posting] Add Posting module, which handles notice posting 2021-09-14 13:05:55 +01:00
Hugo Sales
e1002eb605 [MEDIA] Move avatar fetching and adding to ouput to media component 2021-09-14 13:05:55 +01:00
Hugo Sales
8506a0248d [UTIL][Common] Fix import 2021-09-14 13:05:54 +01:00
Hugo Sales
22d1c55faf [CONTROLLER][AdminPanel] Add missing use statement 2021-09-14 13:05:54 +01:00
Hugo Sales
eb138ebdae [STREAM][NetworkPublic] Add skeleton of public timeline and posting 2021-09-14 13:05:54 +01:00
Hugo Sales
a2269f5745 [UTIL][Common] Implement 'isSystemPath' 2021-09-14 13:05:54 +01:00
Hugo Sales
95a1938d0f [SECURITY] Wrap getUser in a try catch, in case the user doesn't exist 2021-09-14 13:05:54 +01:00
Hugo Sales
213cfe5285 [COMMAND] Fix 'bin/console doctrine:database:create' by only loading defaults if we have a connection 2021-09-14 13:05:54 +01:00
Hugo Sales
e3e9ece614 [DEPENDENCY] Update dependencies 2021-09-14 13:05:54 +01:00
Hugo Sales
51a1a1180e [AUTOGENERATED] Update autogenerated code 2021-09-14 13:05:54 +01:00
Hugo Sales
636f564672 [TOOLS] Fix bin/generate_entity_fields 2021-09-14 13:05:54 +01:00
Hugo Sales
8716d700a6 [CORE][DB] Fix uses of db tables after previous restructure 2021-09-14 13:05:53 +01:00
Hugo Sales
1111ee95f1 [CORE] Data Representation and Modelling refactor 2021-09-14 13:05:53 +01:00
rainydaysavings
b80479dc4e [UI] Public feed responsive CSS work 2021-09-14 13:05:53 +01:00
rainydaysavings
5d859b6459 [UI] Responsive settings CSS work 2021-09-14 13:05:53 +01:00
rainydaysavings
ea61109932 [UI] Responsive base design polish 2021-09-14 13:05:53 +01:00
rainydaysavings
ccd6667e2c [UI] Feed structure done, feed CSS work 2021-09-14 13:05:53 +01:00
rainydaysavings
5f0ece177b [UI] Polishing design, settings pages 2021-09-14 13:05:53 +01:00
rainydaysavings
46eaccb4b0 [UI] Polishing base template 2021-09-14 13:05:53 +01:00
rainydaysavings
e9a0528275 [UI] Left panel theme now looks like it should 2021-09-14 13:05:53 +01:00
rainydaysavings
8fe12f48b5 [UI] Right panel checkbox size fix 2021-09-14 13:05:52 +01:00
rainydaysavings
80d92e3c88 [UI] Settings theme according to base theme 2021-09-14 13:05:52 +01:00
rainydaysavings
915f1dfcdb [UI] Complete base CSS overhaul and new theme 2021-09-14 13:05:52 +01:00
rainydaysavings
565140adcf [UI] Right panel added 2021-09-14 13:05:52 +01:00
rainydaysavings
1c7ea95b1f [UI] Additional fixes to settings page 2021-09-14 13:05:52 +01:00
rainydaysavings
13700edb3e [UI] Small general settings CSS fixes 2021-09-14 13:05:52 +01:00
rainydaysavings
fb0f3b9dfb [UI] Settings small fix 2021-09-14 13:05:52 +01:00
rainydaysavings
f0558feb98 [UI] Notification settings checkbox placement fix 2021-09-14 13:05:52 +01:00
Hugo Sales
9781d43f08 [LocalUser] Fix missing extend 2021-09-14 13:05:52 +01:00
Hugo Sales
cd89cf04ff [ROUTES] Add redirect from root to main/all, link from header to root, and change parameter order on RouteLoader::connect 2021-09-14 13:05:52 +01:00
Hugo Sales
365168d03e [EXCEPTION] Add ServerException and inherit previous throwable
imported from v2/5ea5d3007563f76a77efbfb66936315441922542
2021-09-14 13:05:51 +01:00
Alexei Sorokin
eb12ac5ef1 [DATABASE] Enable fulltext search by default
Also rename fulltext indices to more fitting names

Imported from v2/f84dbb369f01a1d4a9bc362d01cdd100cdc79313
2021-09-14 13:05:51 +01:00
Hugo Sales
4128a5403d [MEDIA][EXCEPTIONS] Fix errors and deprecations 2021-09-14 13:05:51 +01:00
Hugo Sales
08e5b313ce [SECURITY] Refactor 2021-09-14 13:05:51 +01:00
Hugo Sales
6438092d86 [AVATAR] Update way avatar is sent, to use proper symfony responses, make config('site', 's_static_delivery') into a boolean 2021-09-14 13:05:51 +01:00
Hugo Sales
699f25a397 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:05:51 +01:00
Hugo Sales
689a5df670 [TOOLS] Update generate_entity_fields 2021-09-14 13:05:51 +01:00
Hugo Sales
bd8f4bd277 [AVATAR] Fixed avatar upload, added avatar inline download and updated template and base controller 2021-09-14 13:05:51 +01:00
Hugo Sales
2bf914f96f [AVATAR] Handle avatar upload without js and save and validate uploaded files 2021-09-14 13:05:51 +01:00
Hugo Sales
48971b70a0 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:05:50 +01:00
Hugo Sales
9c660f39ed [DEPENDENCIES] Update dependencies 2021-09-14 13:05:50 +01:00
Hugo Sales
0cddbc1783 [JS] Whitespace cleanup 2021-09-14 13:05:50 +01:00
Hugo Sales
113c250c41 [MEDIA][AVATAR] Handle avatar validation and storage 2021-09-14 13:05:50 +01:00
Hugo Sales
0ab6c2ef54 [DB] Add entity base class to allow sharing methods such as 'create' 2021-09-14 13:05:50 +01:00
Hugo Sales
4d99bfb9fd [DB][FILE][AVATAR] Handle deleting files, change file and avatar tables 2021-09-14 13:05:50 +01:00
Hugo Sales
5a68fd287b [UI][SETTINGS] User notification settings with configurable transports (through plugins) 2021-09-14 13:05:50 +01:00
Hugo Sales
0230dd04df [PLUGIN] Remove Test plugin 2021-09-14 13:05:50 +01:00
Hugo Sales
86b9f7d7a1 [COMPONENT][PLUGIN] Move Email and XMPP notification handlers from components to plugins, so they can be disabled 2021-09-14 13:05:50 +01:00
Hugo Sales
3fb45176b8 [COMPONENT] Remove 'post on status change' option for email transport 2021-09-14 13:05:49 +01:00
Hugo Sales
c7dbae8067 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:05:49 +01:00
rainydaysavings
db8e783233 [UI] Notifications settings styling finished 2021-09-14 13:05:49 +01:00
rainydaysavings
6b85d38ad6 [UI] Notification settings styling progress 2021-09-14 13:05:49 +01:00
rainydaysavings
bdae49718f [UI] Notification settings tabs functional 2021-09-14 13:05:49 +01:00
rainydaysavings
884d2529d3 [UI] Notifications settings page CSS work 2021-09-14 13:05:49 +01:00
rainydaysavings
ea33243b60 [UI] Accessibility improvements all around 2021-09-14 13:05:49 +01:00
rainydaysavings
54bade96ad [UI] Fixed issue where certain form element would be on top the left panel 2021-09-14 13:05:49 +01:00
rainydaysavings
d9b48d33b6 [ROUTES] Fix use statement 2021-09-14 13:05:49 +01:00
rainydaysavings
d717aac67f [JS][UI][AVATAR] JS cropping script 2021-09-14 13:05:48 +01:00
rainydaysavings
774383a3c1 [UI] Cropping avatar as a circle, proper preview done 2021-09-14 13:05:48 +01:00
rainydaysavings
574fb38225 [CONTROLLER][UI] Avatar JS cropping added 2021-09-14 13:05:48 +01:00
rainydaysavings
1d5a1818c1 [UI] Login and Register button fixes 2021-09-14 13:05:48 +01:00
rainydaysavings
f840d4350f [UI] Controller and Route for FAQ page created as well as basic template structure 2021-09-14 13:05:48 +01:00
rainydaysavings
9899601777 [UI] Settings pages routes and styling done. 2021-09-14 13:05:48 +01:00
Hugo Sales
e5082657b7 [COMPONENT][PLUGIN] Small refactor and add license 2021-09-14 13:05:48 +01:00
Hugo Sales
b436a0641d [CONTROLLER][UI] Add notification settings form 2021-09-14 13:05:48 +01:00
Hugo Sales
4ba71426b6 [MODULE] Reload modules if modified, except in production environment 2021-09-14 13:05:48 +01:00
Hugo Sales
58b6026607 [UTIL] Fix and rename arrayRemoveKeys 2021-09-14 13:05:47 +01:00
Hugo Sales
ef1788949c [CONFIG] Cleanup services.yaml config file 2021-09-14 13:05:47 +01:00
Hugo Sales
b3d5c73ab4 [FORMATTING] Update license header in SchemaDef compiler pass 2021-09-14 13:05:47 +01:00
Hugo Sales
62e093c650 [EVENT][ROUTES] Add event to allow modules to add routes, 'add_route' 2021-09-14 13:05:47 +01:00
Hugo Sales
152828ed68 [FRAMEWORK] Avoid double initializing the framework 2021-09-14 13:05:47 +01:00
Hugo Sales
aac653d2bd [EVENT] Fix event handler and rename events to snake_case 2021-09-14 13:05:47 +01:00
Hugo Sales
995b4cfb9b [PLUGIN] Update example plugin 2021-09-14 13:05:47 +01:00
Hugo Sales
ea7d43172a [MODULE][COMPILER] Add compiler pass responsible for loading, instantiating and wiring enabled modules 2021-09-14 13:05:47 +01:00
Hugo Sales
bc9de1c0fa [MODULE] Added module base class 2021-09-14 13:05:47 +01:00
Hugo Sales
b71eded942 [COMMAND] Fix ListEvents command to properly display the callable 2021-09-14 13:05:47 +01:00
Hugo Sales
292c9dc862 [COMPOSER][MODULE] Add autoloading rules for components and plugins 2021-09-14 13:05:46 +01:00
Hugo Sales
da0d88e0f2 [MODULE] Renamed modules to components 2021-09-14 13:05:46 +01:00
Hugo Sales
48252d6b8c [UTIL][FORM] Add form transformer array <--> string 2021-09-14 13:05:46 +01:00
Hugo Sales
3e6eb114c4 [UTIL] Add Common::array_remove_keys 2021-09-14 13:05:46 +01:00
Hugo Sales
fe3a3978af [UI][CONTROLLER] Work on tabbed notification settings panel 2021-09-14 13:05:46 +01:00
Hugo Sales
04e31d273d [MAIL] Make mailserver a required service 2021-09-14 13:05:46 +01:00
Hugo Sales
71db1870db [SECURITY] Ensure ARGON2 constants are defined, or throw exception 2021-09-14 13:05:46 +01:00
Hugo Sales
7560db4d5f [DEPENDENCY] Update dependencies 2021-09-14 13:05:46 +01:00
Hugo Sales
fe394e9b20 [UI][CONTROLLER] Refactor UserPanel to use the new Form::handle method and add placeholders in the password fields 2021-09-14 13:05:46 +01:00
Hugo Sales
6fc120571d [DATABASE] Remove DATABASE::flush from Profile::setSelfTags 2021-09-14 13:05:45 +01:00
Hugo Sales
b3bce3efa1 [FORMATTING] Add option to split a string to array by both a comma and a space 2021-09-14 13:05:45 +01:00
Hugo Sales
921da28884 [FORM] Add DataTransformer to and from array 2021-09-14 13:05:45 +01:00
Hugo Sales
93f576679c [FORM] Add Form::handle which automagically creates a form, handles a request, and writes the data to the given entity and update Form::create to do some more magic as well 2021-09-14 13:05:45 +01:00
Hugo Sales
59eba851f7 [DATABASE][TOOLS] Update local_user to return the proper PhoneNumber type 2021-09-14 13:05:45 +01:00
Hugo Sales
22e292276c [CORE][SECURITY][UX] Save previous url on /register and /logout 2021-09-14 13:05:45 +01:00
rainydaysavings
199fa0278c [CONTROLLER][UI] Fixing controller display errors, Settings CSS fixes 2021-09-14 13:05:45 +01:00
rainydaysavings
6d729de07c [UI][ROUTES][CONTROLLER] Fixing Settings navs and templates 2021-09-14 13:05:45 +01:00
rainydaysavings
a9b614bbdd [UI] Register and Login rework 2021-09-14 13:05:45 +01:00
rainydaysavings
e8d03e70ea [UI] Left panel fix 2021-09-14 13:05:44 +01:00
Hugo Sales
d08c4a1f62 [Controller] Update settings/avatar 2021-09-14 13:05:44 +01:00
Hugo Sales
db32a5fcfc [DATABASE][TOOLS] Update local_user, SchemaDefDriver and bin/generate_entity_fields to use the phone_number type (which maps to a varchar 35 and does validation) 2021-09-14 13:05:44 +01:00
Hugo Sales
db52e282b9 [UI] Update settings/account to reflect the current values 2021-09-14 13:05:44 +01:00
Hugo Sales
fd36e6fa7a [UI][ROUTES] s%settings/profile%settings/personal_info% 2021-09-14 13:05:44 +01:00
Hugo Sales
17f854b1d9 [CORE][UX] Save previous url to redirect back after registering 2021-09-14 13:05:44 +01:00
Hugo Sales
ead29a636d [DEPENDENCY] Add odolbeau/phone-number-bundle 2021-09-14 13:05:44 +01:00
Hugo Sales
576d6eb11c [UTIL][NICKNAME][FIX] Add self to constant 2021-09-14 13:05:44 +01:00
Hugo Sales
8b7dd48344 [UI][USERPANEL] Add prefilled fields, mark some as optional and handle self tags in the profile settings page 2021-09-14 13:05:44 +01:00
Hugo Sales
207eeb39ca [SELFTAGS] Add Profile::{set,get}SelfTags 2021-09-14 13:05:43 +01:00
Hugo Sales
1e911f1ba4 [FORM] Add Form::isRequired 2021-09-14 13:05:43 +01:00
Hugo Sales
6adb527fe0 [DATABASE] Refactor DB.php and make findBy always return an array, instead of a doctrine collection 2021-09-14 13:05:43 +01:00
Hugo Sales
040c400bfe [UTIL] Update Formatting::{toString,toArray} to allow spliting by either space or comma 2021-09-14 13:05:43 +01:00
Alexei Sorokin
66c38d777a [SECURITY] Update way passwords are checked and update 2021-09-14 13:05:43 +01:00
rainydaysavings
ea131d03e1 [TWIG][ROUTES] Footer links routes and pages added 2021-09-14 13:05:43 +01:00
rainydaysavings
fa613b7098 [UI][ROUTES] Footer links added 2021-09-14 13:05:43 +01:00
rainydaysavings
6d4f6b5109 [UI] Fixing problem due to Firefox's autofill filter 2021-09-14 13:05:43 +01:00
rainydaysavings
51f79242b7 [UI] Login template small fix 2021-09-14 13:05:43 +01:00
rainydaysavings
0dc3d3023e [UI] Left panel template and Login page CSS work done 2021-09-14 13:05:42 +01:00
Hugo Sales
3ba46a9a60 [SECURITY][CONTROLLER] Remove unreachable code from the Security controller 2021-09-14 13:05:42 +01:00
Hugo Sales
c26ffe09b6 [CONTROLLER][ADMIN][CONFIG] Fix form to use static strings as keys and add labels seperately; convert input from string to appropriate type 2021-09-14 13:05:42 +01:00
Hugo Sales
b772702895 [LIB][Util] Update Common::setConfig to throw an exception if appropriate, add Formatting::{toString,toArray} 2021-09-14 13:05:42 +01:00
Hugo Sales
647fd421ee [I18N] Remove incomplete autogenerated translation file, to be fixed later 2021-09-14 13:05:42 +01:00
Hugo Sales
91af1be470 [AUTOGENERATED] Update all entity fields 2021-09-14 13:05:42 +01:00
Hugo Sales
e0cc125907 [SECURITY] Small refactor in Authenticator.php, to remove unused services 2021-09-14 13:05:42 +01:00
Hugo Sales
c973517397 [USER][UI][AUTHENTICATION] Add registration form 2021-09-14 13:05:42 +01:00
Hugo Sales
17dc298dfa [UTIL][NICKNAME] Small refactor and remove the check between user nickname and group_alias, as these will have different semantics 2021-09-14 13:05:42 +01:00
Hugo Sales
5b578b9519 [DATABASE] Fix typos in user_notification_prefs 2021-09-14 13:05:42 +01:00
Hugo Sales
6e6c7ede1e [DATABASE] Fix typo in profile table and add a constructor 2021-09-14 13:05:41 +01:00
Hugo Sales
7c6112b887 [FORM][WRAPPER] Merge argument options, not replace 2021-09-14 13:05:41 +01:00
Hugo Sales
a85fc2d0bd [DATABASE] Update LocalUser table to not have a numerical id, add is_email_verified and fix getProfile accordingly 2021-09-14 13:05:41 +01:00
Hugo Sales
7a7f7d3ae1 [MAILER][WRAPPER] Add mailer wrapper that respects the configuration 2021-09-14 13:05:41 +01:00
Hugo Sales
7c35fde8bc [FIX] Fix bug in DATABASE.php, since findBy can return different types 2021-09-14 13:05:41 +01:00
Hugo Sales
72bf62adc3 [TOOLS] Update generate fields script to output default values 2021-09-14 13:05:41 +01:00
Hugo Sales
672e2b80eb [DEPENDENCY] Add symfonycasts/verify-email-bundle 2021-09-14 13:05:41 +01:00
Hugo Sales
c0da90bd3e [COMMON][SECURITY][WRAPPER] Added security service static wrapper and Common::getUser 2021-09-14 13:05:41 +01:00
Hugo Sales
97fd7620e7 [CORE][ROUTES] Small refactor on entrypoint and RouteLoader 2021-09-14 13:05:41 +01:00
Hugo Sales
1572261617 [TWIG] Add twig function to output the active tag if the current route matches a given one 2021-09-14 13:05:40 +01:00
Hugo Sales
cac00dd6d4 [CONTROLLER][ROUTES] Refactor the base Controller to not reinvent the wheel too much and rely on Symfony's events 2021-09-14 13:05:40 +01:00
Hugo Sales
a1c90f2e15 [ROUTES] Change name of admin and settings routes and refactor the way they're specified 2021-09-14 13:05:40 +01:00
Hugo Sales
56f74fffe8 [CONTROLLER][ROUTES] Refactor controllers to use the new base class and remove controller from the class name 2021-09-14 13:05:40 +01:00
Hugo Sales
2796ac5228 [NOTIFICATION][DATABASE] Update user notification prefs table, implementation of Notification and define a base class for notification transport 2021-09-14 13:05:40 +01:00
Hugo Sales
df4d246ede [CONTROLLER] Remove example enqueue 2021-09-14 13:05:40 +01:00
Hugo Sales
59fcd042e9 [DEFAULTS] Add password hashing algorithm default settings 2021-09-14 13:05:40 +01:00
Hugo Sales
0eba267a73 [LOGIN] Implement password checking and related systems 2021-09-14 13:05:40 +01:00
Hugo Sales
f3ccdf8017 [USER] Add UserRoles 2021-09-14 13:05:40 +01:00
Hugo Sales
5a74354703 [DATABASE] Add role collumn to profile table 2021-09-14 13:05:39 +01:00
Hugo Sales
8ce0f05371 [UTIL] Update Common::config to ensure the values queried exist 2021-09-14 13:05:39 +01:00
Hugo Sales
ae373c7d96 [DEFAULTS][FIX] Fix logic error that kept reloading the table when the file wasn't modified 2021-09-14 13:05:39 +01:00
Hugo Sales
3313897671 [UI][SESSION] Add login and logout pages 2021-09-14 13:05:39 +01:00
Hugo Sales
fb53700be2 [LIB][Util] Remove Functional::arity as it got merged upstream as Functional\ary 2021-09-14 13:05:39 +01:00
Hugo Sales
d6cd52cede [LIB][Util] Make Common::config return the unserialized value instead of the entity 2021-09-14 13:05:39 +01:00
Hugo Sales
284fbe2c5b [CORE] Refactor GNUsocial.php so it initializes itself as a service 2021-09-14 13:05:39 +01:00
Hugo Sales
e482ecfb87 [I18N] Remove support for context until it proves necessary, as it broke the code 2021-09-14 13:05:39 +01:00
Hugo Sales
028d7c929f [TOOLS] Update shebang on scripts to use the correct php executable 2021-09-14 13:05:39 +01:00
Hugo Sales
f246667fe5 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:05:38 +01:00
Hugo Sales
7c8ab40e3a [DATABASE] Rename user table to local_user, since doctrine shits itself otherwise ._. 2021-09-14 13:05:38 +01:00
Hugo Sales
8f68bde21a [DEPENDENCY] Add symfony/config as a dependency 2021-09-14 13:05:38 +01:00
Hugo Sales
01b5c4b2f7 [CONFIG] Change way configuration is done to use Symfony's system instead of environment vars 2021-09-14 13:05:38 +01:00
Hugo Sales
fffa17448f [CORE][I18n][DEFAULTS] Remove I18nHelper 2021-09-14 13:05:38 +01:00
Hugo Sales
339003f210 [LIB][Util] Change methods in the Common class to camelCase, add isSystemPath (previously in Nickname.php) 2021-09-14 13:05:38 +01:00
Hugo Sales
d0771f77bc [UTIL][NICKNAME] Import nickname utilities and exceptions from v2 2021-09-14 13:05:38 +01:00
Hugo Sales
07c033de33 [DEFAULTS] Update default reserved usernames 2021-09-14 13:05:38 +01:00
Hugo Sales
3992629a08 [DATABASE][WRAPPER] Add findBy method which allows finding entities with a complex expression 2021-09-14 13:05:38 +01:00
Hugo Sales
0ac1d563de [DATABASE] Re-import the local_group table, as it'll be used as per the new group semantics 2021-09-14 13:05:37 +01:00
rainydaysavings
d6320943ce [UI] Settings routes refactor, avatar and misc settings added. 2021-09-14 13:05:37 +01:00
rainydaysavings
6dd966bd3f [UI][CONTROLLER][ROUTES] UserPanel account page form added, account page CSS work 2021-09-14 13:05:37 +01:00
rainydaysavings
5a53915f80 [UI] Fixed top header spacing issue, hamburger menu weird rendering 2021-09-14 13:05:37 +01:00
rainydaysavings
d0b04b6084 [UI] FAQ pages markdown fixes 2021-09-14 13:05:37 +01:00
rainydaysavings
c9f731507b [UI][CONTROLLER] Form help messages added, fixed checkbox trick hitbox 2021-09-14 13:05:37 +01:00
rainydaysavings
0dfb96cdeb [UI] Reset CSS added, small fixes all around 2021-09-14 13:05:37 +01:00
rainydaysavings
1558bffcac [UI] Settings page CSS redesign port completed 2021-09-14 13:05:37 +01:00
rainydaysavings
63a443e78e [UI][CONTROLLER][ROUTES] Corrected core action name, UserPanel CSS work 2021-09-14 13:05:37 +01:00
rainydaysavings
0b0bd31a4b [UI] Fixed FAQ template issues 2021-09-14 13:05:36 +01:00
rainydaysavings
19a96539aa [UI] Side panel animation added 2021-09-14 13:05:36 +01:00
rainydaysavings
22042a8cb7 [UI] Redesign responsiveness work done 2021-09-14 13:05:36 +01:00
rainydaysavings
f8fc226673 [UI] Further work into new side panel design 2021-09-14 13:05:36 +01:00
rainydaysavings
4eaf272929 [UI] Basic implementation of the new base design 2021-09-14 13:05:36 +01:00
rainydaysavings
115257f3bb [UI] Polishing FAQ CSS 2021-09-14 13:05:36 +01:00
rainydaysavings
0117883bd5 [UI] Standardization of sizes and variable usage for faster theming 2021-09-14 13:05:36 +01:00
rainydaysavings
02318d954c [UI][CONTROLLER] Profile settings action functionality working 2021-09-14 13:05:36 +01:00
Hugo Sales
643a937152 [DATABASE][WRAPPER] Update DATABASE wrapper so entity names are provided without the namespace 2021-09-14 13:05:36 +01:00
Hugo Sales
0615adbb51 [DOCKER] Add redis to the docker image 2021-09-14 13:05:36 +01:00
Hugo Sales
09c2a762ef [CACHE] Fix usage of the redis extension 2021-09-14 13:05:35 +01:00
Hugo Sales
c0a17af062 [DOCKER] Fix redis extension build process to use LZ4 and add APCu 2021-09-14 13:05:35 +01:00
Hugo Sales
4776cff969 [DEPENDENCY] Add mock polyfill implementations of the redis and memcached extension 2021-09-14 13:05:35 +01:00
Hugo Sales
31ad75564f [CACHE][Redis] Add special support for redis (fixed size lists), set method and general fixes 2021-09-14 13:05:35 +01:00
Hugo Sales
c602cf8422 [LIB][Util] Refactor and implement array indexing methods on RingBuffer 2021-09-14 13:05:35 +01:00
Hugo Sales
071c1aaec4 [DOCKER] Updated php image so redis is compiled with LZ4 compression available, as it's the fastest at decompressing and really fast at compressing. Read performance is more important, with this being used as a cache 2021-09-14 13:05:35 +01:00
Hugo Sales
c549bea4a9 [CACHE] Add support for multiple pools with the syntax (as an example) SOCIAL_CACHE_ADAPTER='default=redis://localhost:6379,memcached://localhost:11211;db.config=apcu://' 2021-09-14 13:05:35 +01:00
Hugo Sales
d35b6f3437 [DOCKER] Add msgpack and redis extensions to docker image 2021-09-14 13:05:35 +01:00
Hugo Sales
cfe1901b51 [DEFAULTS] Add modified time to config table (as value) and only reload the defaults if the file is newer 2021-09-14 13:05:35 +01:00
Hugo Sales
e91a141474 [CACHE] Extend the static wrapper to support working with lists in caches that don't natively support them 2021-09-14 13:05:34 +01:00
Hugo Sales
7f5e574cbf [LIB][Util] Add Ring Buffer data structure 2021-09-14 13:05:34 +01:00
Hugo Sales
05ffc0db9c [DOCKER] Move certbot files to hidden folder, so it's ignored by GNU global 2021-09-14 13:05:34 +01:00
Hugo Sales
0e0321cfef [DEFAULTS][FIX] Fix defaults to use value from environment 2021-09-14 13:05:34 +01:00
Hugo Sales
2671c37039 [PHP][EXTENSION][POLYFILL] Add php-ds polyfill, which is used if the native extension is not available 2021-09-14 13:05:34 +01:00
Hugo Sales
279b7e775b [DOCKER][PHP][EXTENSION] Add PHP ds extension to docker PHP image 2021-09-14 13:05:34 +01:00
Hugo Sales
52aad30030 [TOOLS][configure] Fix to use quotation marks properly 2021-09-14 13:05:34 +01:00
Hugo Sales
9d2f6e7425 [CACHE][WRAPPER] Fix cache wrapper 2021-09-14 13:05:34 +01:00
Hugo Sales
4f85594bec [FORMATTING][DEFAULTS] Fix formatting and remove redundant config defaults 2021-09-14 13:05:34 +01:00
Hugo Sales
b7b50f749b [CACHE][HTTP] Configure simple HTTP cache 2021-09-14 13:05:33 +01:00
Hugo Sales
65cc487a29 [CACHE] Add a static wrapper around symfony/cache 2021-09-14 13:05:33 +01:00
Hugo Sales
81dd2e4c72 [DEPENDENCY] Add symfony/cache as a dependency 2021-09-14 13:05:33 +01:00
Hugo Sales
aae883880f [LOG][WRAPPER] Refactor log wrapper 2021-09-14 13:05:33 +01:00
Hugo Sales
d48cb3f0b8 [QUEUE] Add queueing wrapper, default configuration and example usage 2021-09-14 13:05:33 +01:00
Hugo Sales
441fd8490f [DEPENDENCY] Add symfony/messenger as a dependency 2021-09-14 13:05:33 +01:00
Hugo Sales
9c483e6e79 [DATABASE] Delete queue_item table, as queueing will be handled by messenger 2021-09-14 13:05:33 +01:00
Hugo Sales
423129486c [CONTROLLER] Show dummy notices in main/all 2021-09-14 13:05:33 +01:00
Hugo Sales
8e30b9423b [MODULES] Make ModulesManager check if file exists 2021-09-14 13:05:33 +01:00
Hugo Sales
59b2b98537 [CONTROLLER] Add Controller base class, which handles rendering templates if requested HTML or json, accordingly 2021-09-14 13:05:32 +01:00
Hugo Sales
a56c7934ec [ROUTE] Fix routes, config_admin got deleted 2021-09-14 13:05:32 +01:00
Hugo Sales
936d13d966 [COMMAND][DEPRECATION][FIX] Fix app:events's deprecation 2021-09-14 13:05:32 +01:00
Hugo Sales
3483be1770 [GIT] Update gitignore to ignore composer.local.json, where plugin settings will be placed 2021-09-14 13:05:32 +01:00
Hugo Sales
e97ba23a99 [DEPENDENCY] Update all dependencies 2021-09-14 13:05:32 +01:00
Hugo Sales
5b5ca6ccea [DEPENDENCY] Add wikimedia/composer-merge-plugin as a dependency, to allow managing plugins 2021-09-14 13:05:32 +01:00
rainydaysavings
ac46c14344 [DEPENDENCY] Add erusev/parsedown and twig/markdown-extra as dependencies 2021-09-14 13:05:32 +01:00
rainydaysavings
81e8173ed4 [TWIG][CONFIG] Change default_path and add public_path 2021-09-14 13:05:32 +01:00
rainydaysavings
996f2338a5 [CONTROLLER] UserAdminPanel handle request fix
[UI] Minor CSS font size corrections
2021-09-14 13:05:32 +01:00
rainydaysavings
82d50cc962 [TWIG][UI] Settings: removed unnecessary pages, responsive CSS work 2021-09-14 13:05:32 +01:00
rainydaysavings
1498c44e74 [UI][CONTROLLER] Settings page styling almost done. 2021-09-14 13:05:31 +01:00
rainydaysavings
79b9e66315 [TWIG] faq/contact template route fix 2021-09-14 13:05:31 +01:00
rainydaysavings
8674d20327 [FAQ] Removing unnecessary categories. 2021-09-14 13:05:31 +01:00
rainydaysavings
22b68766db [UI][FAQ] FAQ polish, better use of twig, responsive css. 2021-09-14 13:05:31 +01:00
rainydaysavings
17d737b590 [UI][Mobile][FAQ][ROUTES] FAQ sub pages and routing added, small screen css work started. 2021-09-14 13:05:31 +01:00
rainydaysavings
768607fe98 [UI] Icon assets now work with Symfony asset component, header icon placement fixes 2021-09-14 13:05:31 +01:00
rainydaysavings
432dfdd0ae [UI][ROUTES][CONTROLLER] Settings pages routes and styling done. 2021-09-14 13:05:31 +01:00
rainydaysavings
f3e9671b1a [UI] Work started on profile settings page. 2021-09-14 13:05:31 +01:00
rainydaysavings
6cf90954dd [UI][Header] New header implemented. 2021-09-14 13:05:31 +01:00
rainydaysavings
ad107542d9 [TWIG][UI] CSS refactoring, containerized twig blocks and settings initial work 2021-09-14 13:05:30 +01:00
rainydaysavings
bf0e944aaa [TWIG] FAQ base template hierarchy fixes
Minor refactoring of routes for FAQ static pages
2021-09-14 13:05:30 +01:00
rainydaysavings
02ddf96371 [UI][ROUTES] Better use of icons, fixing static pages routing. 2021-09-14 13:05:30 +01:00
rainydaysavings
1989cb481a [UI][FAQ] Better FAQ organization, removing unnecessary categories. 2021-09-14 13:05:30 +01:00
rainydaysavings
727083ec88 [UI][Mobile][FAQ] FAQ polish, better use of twig, responsive css. 2021-09-14 13:05:30 +01:00
rainydaysavings
ecd7aedf0c [UI][Mobile][FAQ] FAQ sub pages and routing added, small screen css work started. 2021-09-14 13:05:30 +01:00
rainydaysavings
aac90a9c9e [UI] SVG icons added
[TWIG][UI] Header completed
2021-09-14 13:05:30 +01:00
rainydaysavings
9d36861076 [UI][FAQ] FAQ page progress, assets folder and assets added 2021-09-14 13:05:30 +01:00
rainydaysavings
124e1a70c1 [UI][FAQ] Controller and Route for FAQ page created as well as basic template structure 2021-09-14 13:05:30 +01:00
Hugo Sales
0eb0d21007 [TOOLS] Fix bootstrap and pre-commit scripts 2021-09-14 13:05:29 +01:00
Hugo Sales
4d92915846 [MODULE] Move 'foreign' entities from core to a module 2021-09-14 13:05:29 +01:00
Hugo Sales
97b583aee7 [AUTOGENERATED] Update autogenerated code 2021-09-14 13:05:29 +01:00
Hugo Sales
2eab90bbb0 [TOOLS] Update bin/generate_entity_fields script 2021-09-14 13:05:29 +01:00
Hugo Sales
5eae3dc351 [CORE][DATABASE] Replace zero dates with CURRENT_TIMESTAMP and add defaults to all 'created' or 'modified'
This commit is a port from v2's 9a515b9234 ([SCHEMA] Improve timestamp storage) to v3.

As explained by Alexei Sorokin:

Avoid the use of deprecated MariaDATABASE "zero dates" globally. If they're present
as attribute defaults somewhere, they will be replaced with NULL implicitly.
The existing "zero dates" in MariaDATABASE storage will be left intact and this
should not present any issues.

The "timestamp" type in table definitions now corresponds to DATETIME in
MariaDATABASE with "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", which
should be close enough to the original behaviour for compatibility purposes.
It is now the recommended type for "modified" attributes, because of the
update trigger on MariaDATABASE. But there is no such trigger implemented on
PostgreSQL as of this moment.
2021-09-14 13:05:29 +01:00
Hugo Sales
25aeac80a3 [CORE][DATABASE] Restructure the database 2021-09-14 13:05:29 +01:00
Hugo Sales
44eaf43ba9 [ROUTE][ADMIN][CONFIG] Add route to update values in the config table 2021-09-14 13:05:29 +01:00
Hugo Sales
e286f39551 [DEFAULTS] Small fixes and add check of SOCIAL_NO_RELOAD_DEFAULTS from the environment, to override the reloading of default values 2021-09-14 13:05:29 +01:00
Hugo Sales
84be8e1711 [DOCUMENTATION] Fixed type annotations and documentation of Common::size_str_to_int 2021-09-14 13:05:29 +01:00
Hugo Sales
b7a8861f55 [CORE][Event] Move GSEvent to Event, no longer a name collision 2021-09-14 13:05:28 +01:00
Hugo Sales
b32e173749 [FORM] Added Symfony Form wrapper 2021-09-14 13:05:28 +01:00
Hugo Sales
bc6ead4ab1 [CORE][Symfony] Fixed deprecation resultant from Symfony 5.1 upgrade
User Deprecated: Since symfony/framework-bundle 5.1: Using type
"Symfony\Component\Routing\RouteCollectionBuilder" for argument 1 of
method "App\Kernel:configureRoutes()" is deprecated, use
"Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator"
instead.
2021-09-14 13:05:28 +01:00
Hugo Sales
7a52c1d823 [TESTS][I18N] Added test for I18n::_m and I18nHelper::formatICU 2021-09-14 13:05:28 +01:00
Hugo Sales
753f852941 [I18N] Fix implementation of I18nHelper::formatICU 2021-09-14 13:05:28 +01:00
Hugo Sales
491e82f94e [ROUTES] Add easier support for using TemplateController and improved documentation 2021-09-14 13:05:28 +01:00
Hugo Sales
f28ff24f2a [I18N] Small fixes. Still broken, though :') 2021-09-14 13:05:28 +01:00
Hugo Sales
fb457d4a45 [AUTOLOAD] Always autoload _m file 2021-09-14 13:05:28 +01:00
Hugo Sales
8649b72192 [DEPENDENCY] Upgrade Symfony framework from 5.0 to 5.1 2021-09-14 13:05:28 +01:00
Hugo Sales
db5811a77e [DEPENDENCY][DEV] Added Symfony Bridge PHPUnit 2021-09-14 13:05:27 +01:00
Hugo Sales
4133fc3290 [GIT] Updated gitignore 2021-09-14 13:05:27 +01:00
Hugo Sales
10ca51e72a [I18N] Overhaul _m() implementation to support ICU message formats 2021-09-14 13:05:27 +01:00
Hugo Sales
a65a46f14e [I18N] Dumped english translation files 2021-09-14 13:05:27 +01:00
Hugo Sales
e3d8ea7912 [TOOLS] Small fix to composer install hook 2021-09-14 13:05:27 +01:00
Hugo Sales
711af58dcd [I18N] Added ability to call _m_dynamic from any class, allowing it to define translations for dynamic-valued calls to _m 2021-09-14 13:05:27 +01:00
Hugo Sales
5be901f9ce [TOOLS] Improve configure script to disallow reserved database names 2021-09-14 13:05:27 +01:00
Hugo Sales
dc7387cc8d [I18N] Custom translation extractor based on Symfony's PhpExtractor, since we use instead of 2021-09-14 13:05:27 +01:00
Hugo Sales
f283613443 [I18N] Refactor since rfc/use-static-function is not implemented
As the above mentioned RFC is not implemented, `_m` needs to be
outside of the I18n class, otherwise it would have to always be called
with `I18n::_m`.
2021-09-14 13:05:27 +01:00
Hugo Sales
186b9e7683 [ROUTES] Add static wrapper around Symfony's router 2021-09-14 13:05:27 +01:00
Hugo Sales
4ffb7c338f [CORE] Refactoring core 2021-09-14 13:05:26 +01:00
Hugo Sales
87bc60a806 [UI][CONFIG] Added admin configuration form 2021-09-14 13:05:26 +01:00
Hugo Sales
436b44df89 [I18N] Fix i18n and add default domain 'Core' 2021-09-14 13:05:26 +01:00
Hugo Sales
13ae29cae2 [DEFAULTS] Fix default config loading logic 2021-09-14 13:05:26 +01:00
Hugo Sales
7c18f10bd2 [TOOLS] Fixed all licence blocks, to use the same, foldable, format 2021-09-14 13:05:26 +01:00
Hugo Sales
82ae4f8920 [DEPENDENCY] Added alchemy/zippy, which adds support for multiple archive formats. Used to allow theme uploads in zip, tar, gz, or other formats 2021-09-14 13:05:26 +01:00
Hugo Sales
02db154495 [DOCUMENTATION][DEFAULTS] Documented all defaults and updated some. Restructured other documentation. 2021-09-14 13:05:26 +01:00
Hugo Sales
4deb446f39 [DEFAULTS] Remove deprecated defaults and convert them to snake_case 2021-09-14 13:05:26 +01:00
Hugo Sales
6cc19b83df [GIT] Added docker-compose to gitignore, in preparation for using a script to generate them 2021-09-14 13:05:26 +01:00
Hugo Sales
a794c28e75 [DATABASE] Change the way defaults are loaded, bulk insert, reload everything in debug mode, only on http requests (not command line) 2021-09-14 13:05:25 +01:00
Hugo Sales
5a61098d28 [TOOLS] Update install scripts 2021-09-14 13:05:25 +01:00
Hugo Sales
988be9dbf1 [DATABASE] Fix 'relation config doesn't exist' 2021-09-14 13:05:25 +01:00
Hugo Sales
0ccc359880 [DATABASE] Postgres doesn't understand '0000-00-00 00:00:00' for datetime, use '-infinity' 2021-09-14 13:05:25 +01:00
Hugo Sales
aaf38353ea [DOCKER] Change postgres data path to the correct one 2021-09-14 13:05:25 +01:00
Hugo Sales
b9bc88ddbf [DATABASE][CONFIG] Loading defaults into database, doctrine static wrapper 2021-09-14 13:05:25 +01:00
Hugo Sales
cdb863ba17 [DATABASE][CONFIG] Bring default configs from V2 and implement DATABASE wrapper 2021-09-14 13:05:25 +01:00
Hugo Sales
736a1f7012 [ASSETS] Import old favicon.ico 2021-09-14 13:05:25 +01:00
Hugo Sales
dd559402cd [CORE][UTIL] Moved classes from util to core
And splitted up Common
2021-09-14 13:05:25 +01:00
Hugo Sales
f628665589 [FORMATTING] Cherry-pick of Diogo's 763ac735c0758624ebd5957993dc0676b865927a 2021-09-14 13:05:24 +01:00
Diogo Cordeiro
f60e37ba3d [DOCKER][BOOTSTRAP] Add option to use a self signed cert 2021-09-14 13:05:24 +01:00
Diogo Cordeiro
cb7518a750 [DOCUMENTATION][TOOL] Small bug fixes and docblock elaboration 2021-09-14 13:05:24 +01:00
Hugo Sales
243aefe683 [CORE] Rename GNU social constants 2021-09-14 13:05:04 +01:00
Hugo Sales
09e70da366 [DOCUMENTATION] Updated INSTALL.md to reflect the fact that a CNAME record can be used directly 2021-09-14 13:05:04 +01:00
Hugo Sales
9449dd26c5 [DOCUMENTATION] Import installation instructions for installing without docker from V2 2021-09-14 13:05:04 +01:00
Hugo Sales
1ce5661f70 [DOCKER] Small fixes to docker setup, imported from V2 2021-09-14 13:05:03 +01:00
Hugo Sales
bf88c97f4a [DOCUMENTATION] Added a code walkthrough document, which explains how the codebase works 2021-09-14 13:05:03 +01:00
Hugo Sales
0620d9a726 [DOCUMENTATION] Added install documentation 2021-09-14 13:05:03 +01:00
Hugo Sales
8a86c5940d [TOOLS] Updated install script to also support mariadb 2021-09-14 13:05:03 +01:00
Hugo Sales
50e450f082 [TOOLS][DOCKER] Further fixes in the docker environment and fixed the install script 2021-09-14 13:05:03 +01:00
Hugo Sales
b1afa9cf91 [DOCKER] Fixed docker environment to properly configure the app environment 2021-09-14 13:05:03 +01:00
Hugo Sales
cf1a9fe893 [TOOLS] Added script to configure the installation and to bootstrap certificate creation with Let's Encrypt 2021-09-14 13:05:03 +01:00
Hugo Sales
e0b26ad38b [TOOLS][SSL] Added bin/boostrap_certificates.sh, allowing for easy configuration of SSL certificates with Let's Encrypt 2021-09-14 13:05:03 +01:00
Hugo Sales
06b5fe2cdf [TOOLS][DOCUMENTATION] Improvements to entity generation script 2021-09-14 13:04:39 +01:00
Hugo Sales
cbbdae6831 [DOCUMENTATION] Added generated diagram 2021-09-14 13:04:39 +01:00
Hugo Sales
bb5cdc03e4 [DOCUMENTATION][TOOLS] Wrote a tool to generate entity diagrams from the database definition 2021-09-14 13:04:39 +01:00
Hugo Sales
a7cc7f4e27 [UTIL] Wrote HTML library to convert arrays to html 2021-09-14 13:04:39 +01:00
Hugo Sales
8ff0f230ed [GITIGNORE] Add file folder to gitignore 2021-09-14 13:04:39 +01:00
Hugo Sales
fe73001c36 [DATABASE] Changed the type of the 'id' field of the ProfileList table from serial to int, as doctrine complains otherwise 2021-09-14 13:04:39 +01:00
Hugo Sales
afc4e87353 [TOOLS][CS-FIX] Altered some php-cs-fix rules 2021-09-14 13:04:38 +01:00
Hugo Sales
1f35af7169 [DATABASE][AUTOCODE] Update autocode to use \DateTimeInterface instead of DateTime 2021-09-14 13:04:38 +01:00
Hugo Sales
c583c0f266 [TOOLS] Fix the generate_entities_fields script 2021-09-14 13:04:38 +01:00
Hugo Sales
42321b936f [SCHEMADEF] Bug fixes 2021-09-14 13:04:38 +01:00
Hugo Sales
eee803d2e9 [DATABASE] Add missing table names 2021-09-14 13:04:38 +01:00
Hugo Sales
e022a5e65e [MODULES] Fix small error in modules manager 2021-09-14 13:04:38 +01:00
Hugo Sales
6193062590 [DATABASE][AUTOCODE] Autogenerated fields, setters and getters for each entity 2021-09-14 13:04:38 +01:00
Hugo Sales
55bc66d7ed [TOOLS][AUTOCODE] Fixed generate_entities script 2021-09-14 13:04:38 +01:00
Hugo Sales
7e47846b80 [TOOLS] Change autocode tag to allow editor folding 2021-09-14 13:04:38 +01:00
Hugo Sales
f1e72255f1 [DATABASE] Removed calls to common_config 2021-09-14 13:04:37 +01:00
Hugo Sales
03a8c80c4f [FORMATTING] Changed license block format to allow folding 2021-09-14 13:04:37 +01:00
Hugo Sales
8d41944f90 [DATABASE] Extracted schemaDef method from old files and refactored onto new files 2021-09-14 13:04:37 +01:00
Hugo Sales
c38b9a1503 [COMMON] Added toCamelCase and toSnakeCase functions 2021-09-14 13:04:37 +01:00
Hugo Sales
98a5b89e42 [TOOLS] Add script used to port from old class files to entities 2021-09-14 13:04:37 +01:00
Hugo Sales
24c8fefe58 [CORE][DATABASE] Import old classes folder into src/Entity 2021-09-14 13:04:37 +01:00
Hugo Sales
8fd54efe8d [DATABASE] Remove testing entity 2021-09-14 13:04:37 +01:00
Hugo Sales
1c3ed4cddb [TOOLS] Add command which imports a file with it's history 2021-09-14 13:04:37 +01:00
Hugo Sales
0c79dfc67b [MODULES] Rename extensions to modules, add example plugin, change plugin location 2021-09-14 13:04:37 +01:00
Hugo Sales
596009c924 [DOCUMENTATION] Document All The Things! 2021-09-14 13:04:33 +01:00
Hugo Sales
5d1685b142 [CORE][ROUTES] Implemented custom router, with an interface similar to the old one, which allows routes to be seperated into files 2021-09-14 13:01:35 +01:00
Hugo Sales
e92a0227a1 [COMMAND][EVENTS] Added command to list events and handlers, and search by regex 2021-09-14 13:01:35 +01:00
Hugo Sales
b50909a335 [CORE][COMMAND] Register internal structures on command event 2021-09-14 13:01:34 +01:00
Hugo Sales
6df658a987 [CORE][EXTENSIONS] Added extension (modules, plugins) loading and test plugin, which is able to handle events 2021-09-14 13:01:34 +01:00
Hugo Sales
cfc269aca4 [CORE][SCHEMADEF] Clean up SchemaDef compiler pass 2021-09-14 13:01:34 +01:00
Hugo Sales
23b47b2aeb [CS-FIXER] Updated cs-fixer rules and added a temporary exception to the src/Entity folder (awaiting pr merge) 2021-09-14 13:01:34 +01:00
Hugo Sales
e3c0669b37 [DOCTRINE][SCRIPT] Created a script to generate the class fields and accessors from the schema definition 2021-09-14 13:01:34 +01:00
Hugo Sales
1b45936f19 [DOCTRINE][SCHEMADEF] Small refactoring 2021-09-14 13:01:34 +01:00
Hugo Sales
2c9a732256 [CORE][DOCTRINE] Implement SchemaDefDriver, which transforms the old syntax from to doctrine's metadata 2021-09-14 13:01:34 +01:00
Hugo Sales
4d7c8628f6 [DOCTRINE] Configure cli-config for doctrine console and ensure DATABASE is set to UTF-8 2021-09-14 13:01:34 +01:00
Hugo Sales
cc72373e3d [DOCTRINE][SCHEMADEF] Create a new metadata driver to allow us to continue using static schemaDef functions
This has the benefit of requiring fewer code changes, as well as providing a better isolation
between GNU social and symfony, useful in case the framework needs to be changed
2021-09-14 13:01:34 +01:00
Hugo Sales
710aa946ab [COMPOSER] Update composer dependencies 2021-09-14 13:01:33 +01:00
Hugo Sales
2215b05894 [CORE][I18N] Fix the translation system 2021-09-14 13:01:33 +01:00
Hugo Sales
2b9a15c1e9 [CORE][I18N] Port the translation system to rely on symfony's 2021-09-14 13:01:33 +01:00
Hugo Sales
feaee1b238 [CORE][EVENTS] Bring existing Events and Boostrapper back
- Adapt the existing event system to rely on Symfony's event dispatcher
2021-09-14 13:01:33 +01:00
Hugo Sales
b396f1227a [CORE][ROUTES] Example route 2021-09-14 13:01:33 +01:00
Hugo Sales
7da61f8db5 [DOCKER] Updated dockerfile to add memcached 2021-09-14 13:01:33 +01:00
Hugo Sales
d6bb3e7caa [DOCKER] Added preliminary docker container 2021-09-14 13:01:33 +01:00
Hugo Sales
633a3b0773 [COMPOSER] Update composer packages 2021-09-14 13:01:33 +01:00
Hugo Sales
4b31c1f48f [V3] Big Bang
Beginning anew, this time with a modern framework: symfony
2021-09-14 13:01:33 +01:00
11154a0d8c [V3] Big Crunch
And so, just as it begins, so too must it end
One should not dwell and stall, for more is to come
2021-09-14 13:01:23 +01:00
50dd4fee0e [CORE] Bump PHP version to 7.4 2021-07-18 13:08:41 +01:00
7d8988d50c [VersionBump] 2.0.0beta0
Updated composer and translations

composer install --no-dev
composer dump-autoload --optimize
git add vendor/ --force
make updatepo
2021-07-18 12:51:07 +01:00
Maiyannah Bishop
58d9b91dd3 [API] Security hotfix for source, picked from postActive 2021-07-17 20:25:36 +01:00
b6bcd3b8c0 [PEAR] Force using extlib's patched version 2021-07-16 18:31:30 +01:00
31433db539 [ActivityPub][POSTMAN] Do not die on network errors 2021-07-16 18:03:12 +01:00
bdb4c54fa8 [DirectMessage] Fix some wrong calls after MessageModel introduction in d9a9a3746b 2021-07-16 17:36:33 +01:00
55293e4008 [ActivityPub][INBOX] CREATE NOTE Attachments, we handle enclosures elsewhere
It was trying to make enclosures with objects instead of strings, also attachments don't use this, only links
2021-07-16 17:07:13 +01:00
066dfbb66d [MEDIA][MediaFile] Prevent issues with huge original filenames 2021-07-16 15:48:02 +01:00
bb2c845f62 [ActivityPub][INBOX] CREATE NOTE now accepts <br> tag 2021-07-16 15:48:02 +01:00
fbeadc1d49 [StoreRemoteMedia][SCRIPTS] Fix typo in query
This was introduced in 11ebb98919f56f7dcf888adfbebf9e8826f995b4#diff-96141878409d7418ea5a9eefbde509b43482c01R70
The arg number should have been one, as there's only one...
2021-07-16 15:46:25 +01:00
Alexei Sorokin
b6ce12a267 Update the project homepage and IRC channel 2021-06-12 11:30:12 +03:00
3b86f06134 [INSTALL] Update mailing list URL, as recommended by @Gijs
Closes notabug issue #327
2021-05-10 13:56:16 +00:00
976 changed files with 50623 additions and 14756 deletions

3
.dir-locals.el Normal file
View File

@@ -0,0 +1,3 @@
((nil . ((php-project-root . auto)
(phpstan-executable . (root . "bin/phpstan"))
)

View File

@@ -3,4 +3,4 @@ KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st' APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999 SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther PANTHER_APP_ENV=panther
DATABASE_URL=postgresql://postgres:password@db:5432/social DATABASE_URL=postgresql://postgres:password@db:5432/test

3
.gitignore vendored
View File

@@ -16,8 +16,9 @@
###< symfony/phpunit-bridge ### ###< symfony/phpunit-bridge ###
###> friendsofphp/php-cs-fixer ### ###> friendsofphp/php-cs-fixer ###
!.php_cs !.php-cs-fixer.php
/.php_cs.cache /.php_cs.cache
/.php-cs-fixer.cache
###< friendsofphp/php-cs-fixer ### ###< friendsofphp/php-cs-fixer ###
###> phpunit/phpunit ### ###> phpunit/phpunit ###

View File

@@ -1,42 +1,46 @@
<?php <?php
declare(strict_types = 1);
/* /*
* This document has been generated with * This document has been generated with
* https://mlocati.github.io/php-cs-fixer-configurator/#version:2.16.1|configurator * https://mlocati.github.io/php-cs-fixer-configurator/#version:3.2.1|configurator
* you can change this configuration by importing this file. * you can change this configuration by importing this file.
*/ */
return PhpCsFixer\Config::create() $config = new PhpCsFixer\Config();
return $config
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setRules([ ->setRules([
// Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one.
'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'],
// PHP arrays should be declared using the configured syntax.
'array_syntax' => ['syntax' => 'short'],
// Binary operators should be surrounded by space as configured.
'binary_operator_spaces' => [
'default' => 'align_single_space_minimal',
'operators' => ['??' => 'align'],
],
// There MUST be one blank line after the namespace declaration.
'blank_line_after_namespace' => true,
// Each element of an array must be indented exactly once. // Each element of an array must be indented exactly once.
'array_indentation' => true, 'array_indentation' => true,
// Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`.
'array_push' => true,
// PHP arrays should be declared using the configured syntax.
'array_syntax' => ['syntax' => 'short'],
// Use the null coalescing assignment operator `??=` where possible.
'assign_null_coalescing_to_coalesce_equal' => true,
// Binary operators should be surrounded by space as configured.
'binary_operator_spaces' => ['default' => 'align_single_space_minimal', 'operators' => ['??' => 'align']],
// There MUST be one blank line after the namespace declaration.
'blank_line_after_namespace' => true,
// Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line. // Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line.
'blank_line_after_opening_tag' => false, 'blank_line_after_opening_tag' => true,
// The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented. // The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented.
'braces' => ['allow_single_line_closure' => true, 'position_after_functions_and_oop_constructs' => 'next', 'braces' => ['allow_single_line_anonymous_class_with_empty_body' => true, 'allow_single_line_closure' => true, 'position_after_functions_and_oop_constructs' => 'next'],
// 'allow_single_line_functions' => true, // Awaiting PR merge...
],
// A single space or none should be between cast and variable. // A single space or none should be between cast and variable.
'cast_spaces' => true, 'cast_spaces' => true,
// Class, trait and interface elements must be separated with one blank line.
'class_attributes_separation' => false,
// Whitespace around the keywords of a class, trait or interfaces definition should be one space. // Whitespace around the keywords of a class, trait or interfaces definition should be one space.
'class_definition' => ['single_item_single_line' => true, 'single_line' => true], 'class_definition' => ['single_item_single_line' => true, 'single_line' => true],
// Using `isset($var) &&` multiple times should be done in one call. // Using `isset($var) &&` multiple times should be done in one call.
'combine_consecutive_issets' => true, 'combine_consecutive_issets' => true,
// Calling `unset` on multiple items should be done in one call. // Calling `unset` on multiple items should be done in one call.
'combine_consecutive_unsets' => true, 'combine_consecutive_unsets' => true,
// Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.
'combine_nested_dirname' => true,
// Comments with annotation should be docblock when used on structural elements.
'comment_to_phpdoc' => false,
// Remove extra spaces in a nullable typehint. // Remove extra spaces in a nullable typehint.
'compact_nullable_typehint' => true, 'compact_nullable_typehint' => true,
// Concatenation should be spaced according configuration. // Concatenation should be spaced according configuration.
@@ -45,8 +49,20 @@ return PhpCsFixer\Config::create()
'constant_case' => true, 'constant_case' => true,
// Equal sign in declare statement should be surrounded by spaces or not following configuration. // Equal sign in declare statement should be surrounded by spaces or not following configuration.
'declare_equal_normalize' => ['space' => 'single'], 'declare_equal_normalize' => ['space' => 'single'],
// There must not be spaces around `declare` statement parentheses.
'declare_parentheses' => true,
// Force strict types declaration in all files. Requires PHP >= 7.0.
'declare_strict_types' => true,
// Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant. // Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant.
'dir_constant' => true, 'dir_constant' => true,
// Replaces short-echo `<?=` with long format `<?php echo`/`<?php print` syntax, or vice-versa.
'echo_tag_syntax' => true,
// The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words.
'elseif' => true,
// Empty loop-body must be in configured style.
'empty_loop_body' => true,
// Empty loop-condition must be in configured style.
'empty_loop_condition' => true,
// PHP code MUST use only UTF-8 without BOM (remove BOM). // PHP code MUST use only UTF-8 without BOM (remove BOM).
'encoding' => true, 'encoding' => true,
// Replace deprecated `ereg` regular expression functions with `preg`. // Replace deprecated `ereg` regular expression functions with `preg`.
@@ -65,14 +81,30 @@ return PhpCsFixer\Config::create()
'function_declaration' => ['closure_function_spacing' => 'one'], 'function_declaration' => ['closure_function_spacing' => 'one'],
// Ensure single space between function's argument and its typehint. // Ensure single space between function's argument and its typehint.
'function_typehint_space' => true, 'function_typehint_space' => true,
// Imports or fully qualifies global classes/functions/constants.
'global_namespace_import' => true,
// Heredoc/nowdoc content must be properly indented. Requires PHP >= 7.3.
'heredoc_indentation' => true,
// Convert `heredoc` to `nowdoc` where possible.
'heredoc_to_nowdoc' => true,
// Function `implode` must be called with 2 arguments in the documented order.
'implode_call' => true,
// Pre- or post-increment and decrement operators should be used if possible. // Pre- or post-increment and decrement operators should be used if possible.
'increment_style' => true, 'increment_style' => true,
// Code MUST use configured indentation type. // Code MUST use configured indentation type.
'indentation_type' => true, 'indentation_type' => true,
// Lambda must not import variables it doesn't use.
'lambda_not_used_import' => true,
// All PHP files must use same line ending. // All PHP files must use same line ending.
'line_ending' => true, 'line_ending' => true,
// Ensure there is no code on the same line as the PHP open tag.
'linebreak_after_opening_tag' => true,
// List (`array` destructuring) assignment should be declared using the configured syntax. Requires PHP >= 7.1.
'list_syntax' => ['syntax' => 'short'],
// Use `&&` and `||` logical operators instead of `and` and `or`. // Use `&&` and `||` logical operators instead of `and` and `or`.
'logical_operators' => true, 'logical_operators' => true,
// Cast should be written in lower case.
'lowercase_cast' => true,
// PHP keywords MUST be in lower case. // PHP keywords MUST be in lower case.
'lowercase_keywords' => true, 'lowercase_keywords' => true,
// Class static references `self`, `static` and `parent` MUST be in lower case. // Class static references `self`, `static` and `parent` MUST be in lower case.
@@ -81,6 +113,14 @@ return PhpCsFixer\Config::create()
'magic_constant_casing' => true, 'magic_constant_casing' => true,
// Magic method definitions and calls must be using the correct casing. // Magic method definitions and calls must be using the correct casing.
'magic_method_casing' => true, 'magic_method_casing' => true,
// Replace non multibyte-safe functions with corresponding mb function.
'mb_str_functions' => true,
// In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.
'method_argument_space' => ['after_heredoc' => true, 'on_multiline' => 'ensure_fully_multiline'],
// Method chaining MUST be properly indented. Method chaining with different levels of indentation is not supported.
'method_chaining_indentation' => true,
// Replace `strpos()` calls with `str_starts_with()` or `str_contains()` if possible.
'modernize_strpos' => true,
// Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator. // Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator.
'modernize_types_casting' => true, 'modernize_types_casting' => true,
// DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash. // DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash.
@@ -88,13 +128,17 @@ return PhpCsFixer\Config::create()
// Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls. // Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls.
'multiline_whitespace_before_semicolons' => true, 'multiline_whitespace_before_semicolons' => true,
// Add leading `\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`. // Add leading `\` before constant invocation of internal constant to speed up resolving. Constant name match is case-sensitive, except for `null`, `false` and `true`.
'native_constant_invocation' => false, 'native_constant_invocation' => true,
// Function defined by PHP should be called using the correct casing. // Function defined by PHP should be called using the correct casing.
'native_function_casing' => true, 'native_function_casing' => true,
// Add leading `\` before function invocation to speed up resolving. // Add leading `\` before function invocation to speed up resolving.
'native_function_invocation' => false, 'native_function_invocation' => true,
// Native type hints for functions should use the correct case. // Native type hints for functions should use the correct case.
'native_function_type_declaration_casing' => true, 'native_function_type_declaration_casing' => true,
// Master language constructs shall be used instead of aliases.
'no_alias_language_construct_call' => true,
// Replace control structure alternative syntax to use braces.
'no_alternative_syntax' => true,
// There should be no empty lines after class opening brace. // There should be no empty lines after class opening brace.
'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_class_opening' => true,
// There should not be blank lines between docblock and the documented element. // There should not be blank lines between docblock and the documented element.
@@ -107,7 +151,7 @@ return PhpCsFixer\Config::create()
'no_empty_comment' => true, 'no_empty_comment' => true,
// There should not be empty PHPDoc blocks. // There should not be empty PHPDoc blocks.
'no_empty_phpdoc' => true, 'no_empty_phpdoc' => true,
// Remove useless semicolon statements. // Remove useless (semicolon) statements.
'no_empty_statement' => true, 'no_empty_statement' => true,
// Removes extra blank lines and/or blank lines following configuration. // Removes extra blank lines and/or blank lines following configuration.
'no_extra_blank_lines' => true, 'no_extra_blank_lines' => true,
@@ -123,18 +167,18 @@ return PhpCsFixer\Config::create()
'no_null_property_initialization' => true, 'no_null_property_initialization' => true,
// Short cast `bool` using double exclamation mark should not be used. // Short cast `bool` using double exclamation mark should not be used.
'no_short_bool_cast' => true, 'no_short_bool_cast' => true,
// Replace short-echo `<?=` with long format `<?php echo` syntax.
'no_short_echo_tag' => true,
// Single-line whitespace before closing semicolon are prohibited. // Single-line whitespace before closing semicolon are prohibited.
'no_singleline_whitespace_before_semicolons' => true, 'no_singleline_whitespace_before_semicolons' => true,
// There must be no space around double colons (also called Scope Resolution Operator or Paamayim Nekudotayim).
'no_space_around_double_colon' => true,
// When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis. // When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis.
'no_spaces_after_function_name' => true, 'no_spaces_after_function_name' => true,
// There MUST NOT be spaces around offset braces. // There MUST NOT be spaces around offset braces.
'no_spaces_around_offset' => true, 'no_spaces_around_offset' => true,
// There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis. // There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis.
'no_spaces_inside_parenthesis' => true, 'no_spaces_inside_parenthesis' => true,
// Replaces superfluous `elseif` with `if`. // Removes `@param`, `@return` and `@var` tags that don't provide any useful information.
'no_superfluous_elseif' => false, 'no_superfluous_phpdoc_tags' => true,
// Remove trailing commas in list function calls. // Remove trailing commas in list function calls.
'no_trailing_comma_in_list_call' => true, 'no_trailing_comma_in_list_call' => true,
// PHP single-line arrays should not have trailing comma. // PHP single-line arrays should not have trailing comma.
@@ -150,13 +194,9 @@ return PhpCsFixer\Config::create()
// In function arguments there must not be arguments with default values before non-default ones. // In function arguments there must not be arguments with default values before non-default ones.
'no_unreachable_default_argument_value' => true, 'no_unreachable_default_argument_value' => true,
// Variables must be set `null` instead of using `(unset)` casting. // Variables must be set `null` instead of using `(unset)` casting.
'no_unset_cast' => false, 'no_unset_cast' => true,
// Properties should be set to `null` instead of using `unset`.
'no_unset_on_property' => true,
// Unused `use` statements must be removed. // Unused `use` statements must be removed.
'no_unused_imports' => true, 'no_unused_imports' => true,
// There should not be useless `else` cases.
'no_useless_else' => false,
// There should not be an empty `return` statement at the end of a function. // There should not be an empty `return` statement at the end of a function.
'no_useless_return' => true, 'no_useless_return' => true,
// In array declaration, there MUST NOT be a whitespace before each comma. // In array declaration, there MUST NOT be a whitespace before each comma.
@@ -167,22 +207,32 @@ return PhpCsFixer\Config::create()
'non_printable_character' => true, 'non_printable_character' => true,
// Array index should always be written by using square braces. // Array index should always be written by using square braces.
'normalize_index_brace' => true, 'normalize_index_brace' => true,
// There should not be space before or after object `T_OBJECT_OPERATOR` `->`. // Adds or removes `?` before type declarations for parameters with a default `null` value.
'nullable_type_declaration_for_default_null_value' => true,
// There should not be space before or after object operators `->` and `?->`.
'object_operator_without_whitespace' => true, 'object_operator_without_whitespace' => true,
// Operators - when multiline - must always be at the beginning or at the end of the line.
'operator_linebreak' => true,
// Ordering `use` statements. // Ordering `use` statements.
'ordered_imports' => true, 'ordered_imports' => true,
// Orders the elements of classes/interfaces/traits.
'ordered_class_elements' => false,
// PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`. // PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`.
'php_unit_construct' => true, 'php_unit_construct' => true,
// PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.
'php_unit_dedicate_assert' => true,
// PHPUnit assertions like `assertIsArray` should be used over `assertInternalType`.
'php_unit_dedicate_assert_internal_type' => true,
// Usages of `->setExpectedException*` methods MUST be replaced by `->expectException*` methods.
'php_unit_expectation' => true,
// PHPUnit annotations should be a FQCNs including a root namespace. // PHPUnit annotations should be a FQCNs including a root namespace.
'php_unit_fqcn_annotation' => true, 'php_unit_fqcn_annotation' => true,
// Enforce camel (or snake) case for PHPUnit test methods, following configuration. // Enforce camel (or snake) case for PHPUnit test methods, following configuration.
'php_unit_method_casing' => true, 'php_unit_method_casing' => true,
// Usage of PHPUnit's mock e.g. `->will($this->returnValue(..))` must be replaced by its shorter equivalent such as `->willReturn(...)`. // Usage of PHPUnit's mock e.g. `->will($this->returnValue(..))` must be replaced by its shorter equivalent such as `->willReturn(...)`.
'php_unit_mock_short_will_return' => true, 'php_unit_mock_short_will_return' => true,
// Order `@covers` annotation of PHPUnit tests. // PHPUnit classes MUST be used in namespaced version, e.g. `\PHPUnit\Framework\TestCase` instead of `\PHPUnit_Framework_TestCase`.
'php_unit_ordered_covers' => true, 'php_unit_namespaced' => true,
// Usages of `@expectedException*` annotations MUST be replaced by `->setExpectedException*` methods.
'php_unit_no_expectation_annotation' => true,
// Changes the visibility of the `setUp()` and `tearDown()` functions of PHPUnit to `protected`, to match the PHPUnit TestCase. // Changes the visibility of the `setUp()` and `tearDown()` functions of PHPUnit to `protected`, to match the PHPUnit TestCase.
'php_unit_set_up_tear_down_visibility' => true, 'php_unit_set_up_tear_down_visibility' => true,
// PHPUnit methods like `assertSame` should be used instead of `assertEquals`. // PHPUnit methods like `assertSame` should be used instead of `assertEquals`.
@@ -193,16 +243,26 @@ return PhpCsFixer\Config::create()
'phpdoc_add_missing_param_annotation' => true, 'phpdoc_add_missing_param_annotation' => true,
// All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically. // All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically.
'phpdoc_align' => true, 'phpdoc_align' => true,
// PHPDoc annotation descriptions should not be a sentence.
'phpdoc_annotation_without_dot' => true,
// Docblocks should have the same indentation as the documented subject. // Docblocks should have the same indentation as the documented subject.
'phpdoc_indent' => true, 'phpdoc_indent' => true,
// Fix PHPDoc inline tags, make `@inheritdoc` always inline. // Fixes PHPDoc inline tags.
'phpdoc_inline_tag' => true, 'phpdoc_inline_tag_normalizer' => true,
// Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only.
'phpdoc_line_span' => true,
// `@access` annotations should be omitted from PHPDoc. // `@access` annotations should be omitted from PHPDoc.
'phpdoc_no_access' => true, 'phpdoc_no_access' => true,
// No alias PHPDoc tags should be used. // No alias PHPDoc tags should be used.
'phpdoc_no_alias_tag' => true, 'phpdoc_no_alias_tag' => true,
// `@return void` and `@return null` annotations should be omitted from PHPDoc.
'phpdoc_no_empty_return' => true,
// Classy that does not inherit must not have `@inheritdoc` tags.
'phpdoc_no_useless_inheritdoc' => true,
// Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations. // Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations.
'phpdoc_order' => true, 'phpdoc_order' => true,
// Order phpdoc tags by value.
'phpdoc_order_by_value' => ['annotations' => ['covers', 'throws']],
// The type of `@return` annotations of methods returning a reference to itself must the configured one. // The type of `@return` annotations of methods returning a reference to itself must the configured one.
'phpdoc_return_self_reference' => true, 'phpdoc_return_self_reference' => true,
// Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`. // Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`.
@@ -210,7 +270,18 @@ return PhpCsFixer\Config::create()
// Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line. // Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line.
'phpdoc_separation' => true, 'phpdoc_separation' => true,
// Single line `@var` PHPDoc should have proper spacing. // Single line `@var` PHPDoc should have proper spacing.
'phpdoc_single_line_var_spacing' => false, 'phpdoc_single_line_var_spacing' => true,
// Fixes casing of PHPDoc tags.
'phpdoc_tag_casing' => true,
// Would be neat, but breaks cases where the parents don't have annotations
// // EXPERIMENTAL: Takes `@param` annotations of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.
// 'phpdoc_to_param_type' => true,
// // EXPERIMENTAL: Takes `@var` annotation of non-mixed types and adjusts accordingly the property signature. Requires PHP >= 7.4.
// 'phpdoc_to_property_type' => true,
// // EXPERIMENTAL: Takes `@return` annotation of non-mixed types and adjusts accordingly the function signature. Requires PHP >= 7.0.
// 'phpdoc_to_return_type' => true,
// PHPDoc should start and end with content, excluding the very first and last line of the docblocks.
'phpdoc_trim' => true,
// Removes extra blank lines after summary and after description in PHPDoc. // Removes extra blank lines after summary and after description in PHPDoc.
'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true,
// The correct case must be used for standard PHP types in PHPDoc. // The correct case must be used for standard PHP types in PHPDoc.
@@ -219,8 +290,14 @@ return PhpCsFixer\Config::create()
'phpdoc_types_order' => true, 'phpdoc_types_order' => true,
// `@var` and `@type` annotations must have type and name in the correct order. // `@var` and `@type` annotations must have type and name in the correct order.
'phpdoc_var_annotation_correct_order' => true, 'phpdoc_var_annotation_correct_order' => true,
// Class names should match the file name. // Converts `pow` to the `**` operator.
'psr4' => true, 'pow_to_exponentiation' => true,
// Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs or `random_int`.
'random_api_migration' => true,
// Callables must be called without using `call_user_func*` when possible.
'regular_callable_call' => true,
// Local, dynamic and directly referenced variables should not be assigned and directly returned by a function or method.
'return_assignment' => true,
// There should be one or no space before colon, and one space after it in return type declarations, according to configuration. // There should be one or no space before colon, and one space after it in return type declarations, according to configuration.
'return_type_declaration' => true, 'return_type_declaration' => true,
// Inside class or interface element `self` should be preferred to the class name itself. // Inside class or interface element `self` should be preferred to the class name itself.
@@ -233,18 +310,26 @@ return PhpCsFixer\Config::create()
'short_scalar_cast' => true, 'short_scalar_cast' => true,
// Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`). // Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`).
'simple_to_complex_string_variable' => true, 'simple_to_complex_string_variable' => true,
// Simplify `if` control structures that return the boolean result of their condition.
'simplified_if_return' => true,
// A return statement wishing to return `void` should not return `null`.
'simplified_null_return' => true,
// A PHP file without end tag must always end with a single empty line feed.
'single_blank_line_at_eof' => true,
// There should be exactly one blank line before a namespace declaration. // There should be exactly one blank line before a namespace declaration.
'single_blank_line_before_namespace' => true, 'single_blank_line_before_namespace' => true,
// There MUST be one use keyword per declaration.
'single_import_per_statement' => true,
// There MUST NOT be more than one property or constant declared per statement. // There MUST NOT be more than one property or constant declared per statement.
'single_class_element_per_statement' => true, 'single_class_element_per_statement' => true,
// There MUST be one use keyword per declaration.
'single_import_per_statement' => true,
// Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block. // Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block.
'single_line_after_imports' => true, 'single_line_after_imports' => true,
// Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax. // Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax.
'single_line_comment_style' => true, 'single_line_comment_style' => true,
// Convert double quotes to single quotes for simple strings. // Convert double quotes to single quotes for simple strings.
'single_quote' => true, 'single_quote' => true,
// Ensures a single space after language constructs.
'single_space_after_construct' => true,
// Each trait `use` must be done as single statement. // Each trait `use` must be done as single statement.
'single_trait_insert_per_statement' => true, 'single_trait_insert_per_statement' => true,
// Fix whitespace after a semicolon. // Fix whitespace after a semicolon.
@@ -253,26 +338,41 @@ return PhpCsFixer\Config::create()
'standardize_increment' => true, 'standardize_increment' => true,
// Replace all `<>` with `!=`. // Replace all `<>` with `!=`.
'standardize_not_equals' => true, 'standardize_not_equals' => true,
// String tests for empty must be done against `''`, not with `strlen`.
'string_length_to_empty' => true,
// A case should be followed by a colon and not a semicolon. // A case should be followed by a colon and not a semicolon.
'switch_case_semicolon_to_colon' => true, 'switch_case_semicolon_to_colon' => true,
// Removes extra spaces between colon and case value. // Removes extra spaces between colon and case value.
'switch_case_space' => true, 'switch_case_space' => true,
// Switch case must not be ended with `continue` but with `break`.
'switch_continue_to_break' => true,
// Standardize spaces around ternary operator. // Standardize spaces around ternary operator.
'ternary_operator_spaces' => true, 'ternary_operator_spaces' => true,
// PHP multi-line arrays should have a trailing comma. // Use the Elvis operator `?:` where possible.
'trailing_comma_in_multiline_array' => true, 'ternary_to_elvis_operator' => true,
// Use `null` coalescing operator `??` where possible. Requires PHP >= 7.0.
'ternary_to_null_coalescing' => true,
// Multi-line arrays, arguments list and parameters list must have a trailing comma.
'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'arrays', 'parameters']],
// Arrays should be formatted like function/method arguments, without leading or trailing single line space.
'trim_array_spaces' => true,
// A single space or none should be around union type operator.
'types_spaces' => true,
// Unary operators should be placed adjacent to their operands. // Unary operators should be placed adjacent to their operands.
'unary_operator_spaces' => true, 'unary_operator_spaces' => true,
// Anonymous functions with one-liner return statement must use arrow functions.
'use_arrow_functions' => true,
// Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility. // Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility.
'visibility_required' => true, 'visibility_required' => true,
// In array declaration, there MUST be a whitespace after each comma. // In array declaration, there MUST be a whitespace after each comma.
'whitespace_after_comma_in_array' => true, 'whitespace_after_comma_in_array' => true,
]) ])
->setFinder(PhpCsFixer\Finder::create() ->setFinder(
->exclude('vendor') PhpCsFixer\Finder::create()
->exclude('var') ->exclude('vendor')
->exclude('docker') ->exclude('var')
->exclude('src/Entity') ->exclude('docker')
->notPath('src/Core/DB/DefaultSettings.php') ->exclude('src/Entity')
->in(__DIR__) ->notPath('src/Core/DB/DefaultSettings.php')
->in(__DIR__),
); );

View File

@@ -3,16 +3,22 @@ Credits for GNU social
The following is an incomplete list of developers The following is an incomplete list of developers
who've worked on GNU social, or its predecessors who've worked on GNU social, or its predecessors
StatusNet and Free Social. Apologies for any StatusNet and Free Social. Apologies for any
oversight; please let mattl@gnu.org know if oversight; please let mail@diogo.site know if
anyone's been overlooked in error. anyone's been overlooked in error.
Current team Current team
------------ ------------
* Alexei Sorokin * Alexei Sorokin
* Bruno Casteleiro
* Diogo Cordeiro * Diogo Cordeiro
* Eliseu Amaro
* Hugo Sales * Hugo Sales
V2 team
-------
* Diogo Cordeiro
* Alexei Sorokin
* Bruno Casteleiro
Additional Contributors Additional Contributors
----------------------- -----------------------
* Akio * Akio

View File

@@ -491,7 +491,7 @@ For configuring the public stream.
some kind of automated poster, testing bots, etc. some kind of automated poster, testing bots, etc.
* `exclude_sources` (array, default []): Sources of notices that should be kept off of * `exclude_sources` (array, default []): Sources of notices that should be kept off of
the public timeline (because they're from automatic posters, for instance). the public feed (because they're from automatic posters, for instance).
throttle throttle

109
Makefile
View File

@@ -1,5 +1,12 @@
DIR=$(strip $(notdir $(CURDIR))) # Seems a bit hack-ish, but `basename` works differently DIR=$(strip $(notdir $(CURDIR))) # Seems a bit hack-ish, but `basename` works differently
translate-container-name = $$(if docker container inspect $(1) > /dev/null 2>&1; then echo $(1); else echo $(1) | sed 'y/_/-/' ; fi)
args = `arg="$(filter-out $@,$(MAKECMDGOALS))" && echo $${arg:-${1}}`
%:
@:
.PHONY: .PHONY:
@if ! docker info > /dev/null; then echo "Docker does not seem to be running"; exit 1; fi @if ! docker info > /dev/null; then echo "Docker does not seem to be running"; exit 1; fi
@@ -10,22 +17,106 @@ down: .PHONY
docker-compose down docker-compose down
redis-shell: redis-shell:
docker exec -it $(strip $(DIR))_redis_1 sh -c 'redis-cli' docker exec -it $(call translate-container-name,$(strip $(DIR))_redis_1) sh -c 'redis-cli'
php-repl: .PHONY php-repl: .PHONY
docker exec -it $(strip $(DIR))_php_1 sh -c '/var/www/social/bin/console psysh' docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c '/var/www/social/bin/console psysh'
php-shell: .PHONY php-shell: .PHONY
docker exec -it $(strip $(DIR))_php_1 sh -c 'cd /var/www/social; sh' docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c 'cd /var/www/social; sh'
psql-shell: .PHONY psql-shell: .PHONY
docker exec -it $(strip $(DIR))_db_1 sh -c "psql -U postgres social" docker exec -it $(call translate-container-name,$(strip $(DIR))_db_1) sh -c "psql -U postgres social"
database-force-nuke:
docker stop $(call translate-container-name,$(strip $(DIR))_worker_1) \
&& docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c "cd /var/www/social; bin/console doctrine:database:drop --force && bin/console doctrine:database:create && bin/console doctrine:schema:update --dump-sql --force && bin/console app:populate_initial_values" \
&& docker-compose up -d
database-force-schema-update: database-force-schema-update:
docker exec -it $(strip $(DIR))_php_1 sh -c "/var/www/social/bin/console doctrine:schema:update --dump-sql --force" docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c "/var/www/social/bin/console doctrine:schema:update --dump-sql --force"
test: .PHONY tooling-docker: .PHONY
cd docker/testing && docker-compose run php; docker-compose down @cd docker/tooling && docker-compose up -d --build > /dev/null 2>&1
stop-test: .PHONY stop-tooling: .PHONY
cd docker/testing && docker-compose down cd docker/tooling && docker-compose down
tooling-php-shell: tooling-docker
docker exec -it $(call translate-container-name,tooling_php_1) sh
test-accesibility: tooling-docker
cd docker/tooling && docker-compose run pa11y /accessibility.sh
test: tooling-docker
docker exec $(call translate-container-name,tooling_php_1) /var/tooling/coverage.sh $(call args,'')
cs-fixer: tooling-docker
@bin/php-cs-fixer $${CS_FIXER_FILE}
doc-check: tooling-docker
bin/php-doc-check
phpstan: tooling-docker
bin/phpstan
remove-var:
rm -rf var/*
remove-file:
sudo rm -rf file/*
flush-redis-cache:
docker exec -it $(call translate-container-name,$(strip $(DIR))_redis_1) sh -c 'redis-cli flushall'
install-plugins:
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) /var/www/social/bin/install_plugins.sh
update-dependencies:
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c 'cd /var/www/social && composer update'
update-autocode:
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c 'cd /var/www/social && bin/update_autocode'
backup-actors:
docker exec -it $(call translate-container-name,$(strip $(DIR))_db_1) \
sh -c 'su postgres -c "mkdir -p /tmp/backup"' && \
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) \
sh -c "cd /var/www/social && bin/console doctrine:query:sql \"\
copy actor to '/tmp/backup/actor.csv';\
copy local_user to '/tmp/backup/local_user.csv';\
copy local_group to '/tmp/backup/local_group.csv';\
\
copy activitypub_actor to '/tmp/backup/ap_actor.csv';\
copy activitypub_rsa to '/tmp/backup/ap_rsa.csv';\
\
copy actor_subscription to '/tmp/backup/actor_subscription.csv';\
copy group_member to '/tmp/backup/group_member.csv';\
\
copy feed to '/tmp/backup/feed.csv';\
copy (SELECT 'ALTER SEQUENCE ' || c.relname || ' RESTART WITH ' || nextval(c.relname::regclass) || ';'\
FROM pg_class c WHERE c.relkind = 'S') to '/tmp/backup/sequences';\"" && \
mkdir -p /tmp/social-sql-backup && \
docker cp $(call translate-container-name,$(strip $(DIR))_db_1):/tmp/backup/. /tmp/social-sql-backup
restore-actors:
docker cp /tmp/social-sql-backup/. $(call translate-container-name,$(strip $(DIR))_db_1):/tmp/backup
docker exec -it $(call translate-container-name,$(strip $(DIR))_db_1) sh -c 'chown postgres /tmp/backup' && \
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) \
sh -c "cd /var/www/social && bin/console doctrine:query:sql \"\
copy actor from '/tmp/backup/actor.csv';\
copy local_user from '/tmp/backup/local_user.csv';\
copy local_group from '/tmp/backup/local_group.csv';\
\
copy activitypub_actor from '/tmp/backup/ap_actor.csv';\
copy activitypub_rsa from '/tmp/backup/ap_rsa.csv';\
\
copy actor_subscription from '/tmp/backup/actor_subscription.csv';\
copy group_member from '/tmp/backup/group_member.csv';\
\
copy feed from '/tmp/backup/feed.csv';\
`cat /tmp/social-sql-backup/sequences`\""
force-nuke-everything: down remove-var remove-file up flush-redis-cache database-force-nuke install-plugins
force-delete-content: backup-actors force-nuke-everything restore-actors

10
bin/configure vendored
View File

@@ -47,7 +47,7 @@ Choose whether you prefer social to handle all the services it needs though dock
3>&1 1>&2 2>&3) 3>&1 1>&2 2>&3)
validate_exit $? validate_exit $?
case ${SERVICES} in case ${SERVICES} in
'docker') DOCKER='"nginx" "certbot" "php" "db" "redis"' ;; # TODO enable and configure "mail" 'docker') DOCKER='"nginx" "certbot" "php" "db" "redis" "worker"' ;; # TODO enable and configure "mail"
'mixed') 'mixed')
DOCKER=$(${WHIPTAIL} --title 'GNU social Docker services' --clear --backtitle 'GNU social' \ DOCKER=$(${WHIPTAIL} --title 'GNU social Docker services' --clear --backtitle 'GNU social' \
--checklist "\nPick which of the following services you'd like to add to docker-compose.\n* indicates a service that has extra configuration" 0 0 0 \ --checklist "\nPick which of the following services you'd like to add to docker-compose.\n* indicates a service that has extra configuration" 0 0 0 \
@@ -57,6 +57,7 @@ case ${SERVICES} in
db 'Configure a DBMS*' on \ db 'Configure a DBMS*' on \
redis 'Configure Redis (optional, recommended)' on \ redis 'Configure Redis (optional, recommended)' on \
mail 'Confugure a mail server*' on \ mail 'Confugure a mail server*' on \
worker 'Confugure container with worker queues' on \
3>&1 1>&2 2>&3) 3>&1 1>&2 2>&3)
validate_exit $? validate_exit $?
;; ;;
@@ -141,6 +142,7 @@ fi
if echo "${DOCKER}" | grep -Fq '"php"'; then if echo "${DOCKER}" | grep -Fq '"php"'; then
${WHIPTAIL} --title "Build PHP container locally?" --clear --backtitle 'GNU social' \ ${WHIPTAIL} --title "Build PHP container locally?" --clear --backtitle 'GNU social' \
--yesno "\nDo you want to compile the needed PHP extensions and build the container locally? (May provide better performance but requires more than 1GiB of RAM)" 0 0 \ --yesno "\nDo you want to compile the needed PHP extensions and build the container locally? (May provide better performance but requires more than 1GiB of RAM)" 0 0 \
--defaultno \
3>&1 1>&2 2>&3 3>&1 1>&2 2>&3
BUILD_PHP=$((1-$?)) # Invert output BUILD_PHP=$((1-$?)) # Invert output
fi fi
@@ -226,7 +228,7 @@ PROFILE=$(${WHIPTAIL} --title 'GNU social site profile' --clear --backtitle 'GNU
public 'Make this node publicly accessible, with open registration' \ public 'Make this node publicly accessible, with open registration' \
community 'Make this node publicly accessible, but with invite-only registration' \ community 'Make this node publicly accessible, but with invite-only registration' \
isolated 'Make this node publicly accessible, with open registration but do not federate' \ isolated 'Make this node publicly accessible, with open registration but do not federate' \
private 'Make this node publicly accessible, but with invite-only registration, only registered users can see timelines' \ private 'Make this node publicly accessible, but with invite-only registration, only registered users can see feeds' \
single_user 'Like public, but only allows registering one user' \ single_user 'Like public, but only allows registering one user' \
3>&1 1>&2 2>&3) 3>&1 1>&2 2>&3)
validate_exit $? validate_exit $?
@@ -350,8 +352,8 @@ SOCIAL_DBMS=${DBMS}
SOCIAL_DB=${DB_NAME} SOCIAL_DB=${DB_NAME}
SOCIAL_USER=${DB_USER} SOCIAL_USER=${DB_USER}
SOCIAL_PASSWORD=${DB_PASSWORD} SOCIAL_PASSWORD=${DB_PASSWORD}
SOCIAL_DOMAIN=${DOMAIN} CONFIG_DOMAIN=${DOMAIN}
SOCIAL_NODE_NAME=${NODE_NAME} CONFIG_NODE_NAME=${NODE_NAME}
SOCIAL_ADMIN_EMAIL=${EMAIL} SOCIAL_ADMIN_EMAIL=${EMAIL}
SOCIAL_SITE_PROFILE=${PROFILE} SOCIAL_SITE_PROFILE=${PROFILE}
MAILER_DSN=${MAILER_DSN} MAILER_DSN=${MAILER_DSN}

View File

@@ -8,11 +8,9 @@ require INSTALLDIR . '/vendor/autoload.php';
use Functional as F; use Functional as F;
use App\Util\Functional;
$filenames = glob(INSTALLDIR . '/src/*/*.php'); $filenames = glob(INSTALLDIR . '/src/*/*.php');
$files = F\map($filenames, Functional::arity('file_get_contents', 1)); $files = F\map($filenames, F\ary('file_get_contents', 1));
$old_licenses = ['/* {{{ License $old_licenses = ['/* {{{ License
* This file is part of GNU social - https://www.gnu.org/software/social * This file is part of GNU social - https://www.gnu.org/software/social

View File

@@ -51,7 +51,7 @@ foreach ($files as $file) {
foreach ($schema['fields'] as $field => $opts) { foreach ($schema['fields'] as $field => $opts) {
if (isset($opts['foreign key'])) { if (isset($opts['foreign key'])) {
[$foreign_entity, $foreign_key] = explode('.', $opts['target']); [$foreign_entity, $foreign_key] = explode('.', $opts['target']);
$foreign_table = Formatting::camelCaseToSnakeCase(preg_replace('/GSActor/', 'gsactor', $foreign_entity)); $foreign_table = Formatting::camelCaseToSnakeCase(preg_replace('/Actor/', 'actor', $foreign_entity));
$edges[] = "{$table}:{$field} -- {$foreign_table}:{$foreign_key}"; $edges[] = "{$table}:{$field} -- {$foreign_table}:{$foreign_key}";
} }
} }

11
bin/install_plugins.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
for plugin in plugins/*; do
install="${plugin}/bin/install.sh"
if [ -x "${install}" ]; then
( # subshell, to clear options/environment
set -x
"${install}"
)
fi
done

View File

@@ -1 +0,0 @@
../vendor/bin/php-cs-fixer

9
bin/php-cs-fixer Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
. bin/translate_container_name.sh
if [ "$#" -eq 0 ] || [ -z "$*" ]; then
docker exec "$(translate_container_name tooling_php_1)" /var/www/social/vendor/bin/php-cs-fixer -n --config=".php-cs-fixer.php" fix
else
docker exec "$(translate_container_name tooling_php_1)" sh -c "/var/www/social/vendor/bin/php-cs-fixer -q -n --config=\".php-cs-fixer.php\" fix --path-mode=intersection -- $*"
fi

View File

@@ -1 +0,0 @@
../vendor/bin/php-doc-check

5
bin/php-doc-check Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
. bin/translate_container_name.sh
docker exec -it "$(translate_container_name tooling_php_1)" /var/www/social/vendor/bin/php-doc-check src components plugins

5
bin/phpstan Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
. bin/translate_container_name.sh
docker exec "$(translate_container_name tooling_php_1)" /var/tooling/phpstan.sh "$@"

View File

@@ -1,28 +1,32 @@
#!/usr/bin/env bash #!/usr/bin/env sh
root="$(git rev-parse --show-toplevel)" root="$(git rev-parse --show-toplevel)"
# get the list of changed files # get the list of changed files that didn't get only partially added
staged_files="$(git status --porcelain | sed -rn "s/^[^ ][ ] (.*)/\1/p")" staged_files="$(git status --porcelain | sed -rn "s/^[^ ][ ] (.*)/\1/p")"
echo "Running php-cs-fixer on edited files" if (! (: "${SKIP_ALL?}") 2>/dev/null) && (! (: "${SKIP_CS_FIX?}") 2>/dev/null); then
echo "Running php-cs-fixer on edited files"
for staged in ${staged_files}; do for staged in ${staged_files}; do
# work only with existing files # work only with existing files
if [ -f "${staged}" ] && [[ "${staged}" = *.php ]] if [ -f "${staged}" ] && expr "${staged}" : '^.*\.php$' > /dev/null; then
then # use php-cs-fixer and get flag of correction
# use php-cs-fixer and get flag of correction CS_FIXER_FILE="${staged}" make cs-fixer
if "${root}/bin/php-cs-fixer" -q fix "${staged}" git add "${staged}"
then
git add "${staged}" # execute git add directly
fi fi
done
fi
if (! (: "${SKIP_ALL?}") 2>/dev/null) && (! (: "${SKIP_DOC_CHECK?}") 2>/dev/null); then
if echo "${staged_files}" | grep -F ".php" > /dev/null 2>&1; then
echo "Running php-doc-checker"
make doc-check < /dev/tty
fi fi
done fi
echo "Running php-doc-checker" if (! (: "${SKIP_ALL?}") 2>/dev/null) && (! (: "${SKIP_PHPSTAN?}") 2>/dev/null); then
echo "Running phpstan"
if echo "${staged_files}" | grep -F ".php"; then make phpstan
"${root}/bin/php-doc-check" src plugins components
fi fi
# Only commit if there wasn't an error # Only commit if there wasn't an error

View File

@@ -0,0 +1,9 @@
#!/bin/sh
translate_container_name () {
if docker container inspect "$1" > /dev/null 2>&1; then
echo "$1"
else
echo "$1" | sed 'y/_/-/'
fi
}

38
bin/update-dependencies Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env php
<?php
/**
* Raise/update static version numbers in composer.json.
*
* Run on the CLI: "composer outdated --direct > outdated.txt"
*/
$composerJson = json_decode(file_get_contents('composer.json'), true);
system('composer outdated --direct > outdated.txt');
$listOfOutdatedPackages = file('outdated.txt');
foreach($listOfOutdatedPackages as $line) {
$regexp = '/(?P<package>[\w]+\/[\w]+).*(?P<currentVersion>\d.\d.\d).*(?P<latestVersion>\d.\d.\d)/';
preg_match($regexp, $line, $matches);
$matches = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
if(isset($matches['package']))
{
$package = $matches['package'];
if(isset($composerJson['require'][$package]))
{
$currentVersion = $composerJson['require'][$package];
echo sprintf('Updating %s from %s to %s', $package, $currentVersion, $matches['latestVersion']);
$composerJson['require'][$package] = $matches['latestVersion'];
}
if(isset($composerJson['require-dev'][$package]))
{
$currentVersion = $composerJson['require-dev'][$package];
echo sprintf('Updating %s from %s to %s', $package, $currentVersion, $matches['latestVersion']);
$composerJson['require-dev'][$package] = $matches['latestVersion'];
}
}
}
file_put_contents('composer.json', json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

View File

@@ -1,4 +1,4 @@
#!/usr/local/bin/php #!/usr/bin/env php
<?php <?php
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@@ -20,25 +20,23 @@ const types = [
'text' => 'string', 'text' => 'string',
'varchar' => 'string', 'varchar' => 'string',
'phone_number' => 'PhoneNumber', 'phone_number' => 'PhoneNumber',
'float' => 'float', // TODO REMOVE THIS
]; ];
$files = array_merge(glob(ROOT . '/src/Entity/*.php'), $files = array_merge(glob(ROOT . '/src/Entity/*.php'),
array_merge(glob(ROOT . '/components/*/Entity/*.php'), array_merge(glob(ROOT . '/components/*/Entity/*.php'),
glob(ROOT . '/plugins/*/Entity/*.php'))); glob(ROOT . '/plugins/*/Entity/*.php')));
$classes = []; $nullable_no_defaults_warning = [];
foreach ($files as $file) { foreach ($files as $file) {
require_once $file; require_once $file;
$declared = get_declared_classes(); $class = str_replace(['/', 'src', 'components', 'plugins'], ['\\', 'App', 'Component', 'Plugin'], substr($file, strlen(ROOT) + 1, -4));
foreach ($declared as $dc) {
if (preg_match('/(App|(Component|Plugin)\\\\[^\\\\]+)\\\\Entity/', $dc) && !in_array($dc, $classes)) { if (!method_exists($class, 'schemaDef')) {
$class = $dc; continue;
$classes[] = $class;
break;
}
} }
$no_ns_class = preg_replace('/.*?\\\\/', '', $class); $no_ns_class = preg_replace('/.*?\\\\/', '', $class);
@@ -47,16 +45,32 @@ foreach ($files as $file) {
$fields_code = []; $fields_code = [];
$methods_code = []; $methods_code = [];
foreach ($fields as $field) { foreach ($fields as $field) {
$nullable = !@$schema['fields'][$field]['not null'] ? '?' : ''; $field_schema = $schema['fields'][$field];
$type = types[$schema['fields'][$field]['type']]; $nullable = ($field_schema['not null'] ?? false) ? '' : '?';
$type = types[$field_schema['type']];
$type = $type !== '' ? $nullable . $type : $type; $type = $type !== '' ? $nullable . $type : $type;
$method_name = str_replace([' ', 'Gsactor'], ['', 'GSActor'], ucwords(str_replace('_', ' ', $field))); $method_name = str_replace([' ', 'actor'], ['', 'Actor'], ucwords(str_replace('_', ' ', $field)));
$default = @$schema['fields'][$field]['default']; $length = $field_schema['length'] ?? null;
if (isset($default) && $nullable != '?' && $type != '\DateTimeInterface') { $field_setter = "\${$field}";
if (is_string($default)) { if (\is_int($length)) {
if ($nullable === '?') {
$field_setter = "\is_null(\${$field}) ? null : \mb_substr(\${$field}, 0, $length)";
} else {
$field_setter = "\mb_substr(\${$field}, 0, $length)";
}
}
if (($nullable === '?' || \array_key_exists('default', $field_schema)) && $type != '\DateTimeInterface') {
if (!\array_key_exists('default', $field_schema)) {
$nullable_no_defaults_warning[] = "{$class}::{$field}";
}
$default = $field_schema['default'] ?? null;
if (\is_string($default)) {
$default = "'{$default}'"; $default = "'{$default}'";
} elseif ($type == 'bool') { } elseif (\is_null($default)) {
$default = "null";
} elseif ($type === 'bool' || $type === '?bool') {
$default = $default ? 'true' : 'false'; $default = $default ? 'true' : 'false';
} }
@@ -66,13 +80,13 @@ foreach ($files as $file) {
} }
$methods_code[] = " public function set{$method_name}({$type} \${$field}): self" . $methods_code[] = " public function set{$method_name}({$type} \${$field}): self" .
"\n {\n \$this->{$field} = \${$field};\n return \$this;\n }" . "\n\n" . "\n {\n \$this->{$field} = {$field_setter};\n return \$this;\n }" . "\n\n" .
" public function get{$method_name}()" . ($type !== '' ? ": {$type}" : '') . " public function get{$method_name}()" . ($type !== '' ? ": {$type}" : '') .
"\n {\n return \$this->{$field};\n }" . "\n"; "\n {\n return \$this->{$field};\n }" . "\n";
} }
$fields_code = implode("\n", $fields_code); $fields_code = implode("\n", $fields_code);
$methods_code = implode("\n", $methods_code) . "\n"; $methods_code = implode("\n", $methods_code);
$begin = '// {{{ Autocode'; $begin = '// {{{ Autocode';
$end = '// }}} Autocode'; $end = '// }}} Autocode';
@@ -91,7 +105,13 @@ foreach ($files as $file) {
} }
$in_file = file_get_contents($file); $in_file = file_get_contents($file);
$out_file = preg_replace("/\\s*{$begin}[^\\/]*{$end}/m", $code, $in_file); $out_file = preg_replace("%\\s*{$begin}.*{$end}%smu", $code, $in_file);
file_put_contents($file, $out_file); file_put_contents($file, $out_file);
} }
if (!empty($nullable_no_defaults_warning)) {
echo "Warning: The following don't have a default value, but we're assigning it `null`. Doctrine might not like this, so update it\n";
foreach ($nullable_no_defaults_warning as $n) {
echo " {$n}\n";
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Attachment;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Event;
use App\Core\Modules\Component;
use App\Core\Router;
use App\Entity\Actor;
use App\Entity\Note;
use App\Util\Formatting;
use Component\Attachment\Controller as C;
use Component\Attachment\Entity as E;
use Doctrine\Common\Collections\ExpressionBuilder;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
class Attachment extends Component
{
public function onAddRoute(Router $r): bool
{
$r->connect('note_attachment_show', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}', [C\Attachment::class, 'attachmentShowWithNote']);
$r->connect('note_attachment_view', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}/view', [C\Attachment::class, 'attachmentViewWithNote']);
$r->connect('note_attachment_download', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}/download', [C\Attachment::class, 'attachmentDownloadWithNote']);
$r->connect('note_attachment_thumbnail', '/object/note/{note_id<\d+>}/attachment/{attachment_id<\d+>}/thumbnail/{size<big|medium|small>}', [C\Attachment::class, 'attachmentThumbnailWithNote']);
return Event::next;
}
/**
* Get a unique representation of a file on disk
*
* This can be used in the future to deduplicate images by visual content
*/
public function onHashFile(string $filename, ?string &$out_hash): bool
{
$out_hash = hash_file(E\Attachment::FILEHASH_ALGO, $filename);
return Event::stop;
}
public function onNoteDeleteRelated(Note &$note, Actor $actor): bool
{
Cache::delete("note-attachments-{$note->getId()}");
foreach ($note->getAttachments() as $attachment) {
$attachment->kill();
}
DB::wrapInTransaction(fn () => E\AttachmentToNote::removeWhereNoteId($note->getId()));
Cache::delete("note-attachments-{$note->getId()}");
return Event::next;
}
public function onCollectionQueryAddJoins(QueryBuilder &$note_qb, QueryBuilder &$actor_qb): bool
{
if (!\in_array('attachment_to_note', $note_qb->getAllAliases())) {
$note_qb->leftJoin(
join: E\AttachmentToNote::class,
alias: 'attachment_to_note',
conditionType: Expr\Join::WITH,
condition: 'note.id = attachment_to_note.note_id',
);
}
return Event::next;
}
/**
* Populate $note_expr with the criteria for looking for notes with attachments
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): bool
{
$include_term = str_contains($term, ':') ? explode(':', $term)[1] : $term;
if (Formatting::startsWith($term, ['note-types:', 'notes-incude:', 'note-filter:'])) {
if (\is_null($note_expr)) {
$note_expr = [];
}
if (array_intersect(explode(',', $include_term), ['media', 'image', 'images', 'attachment']) !== []) {
$note_expr[] = $eb->neq('attachment_to_note.note_id', null);
} else {
$note_expr[] = $eb->eq('attachment_to_note.note_id', null);
}
}
return Event::next;
}
}

View File

@@ -0,0 +1,180 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Attachment\Controller;
use App\Core\Controller;
use App\Core\DB;
use App\Core\Event;
use App\Core\GSFile;
use function App\Core\I18n\_m;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\NoSuchFileException;
use App\Util\Exception\NotFoundException;
use App\Util\Exception\ServerException;
use Component\Attachment\Entity\AttachmentThumbnail;
use Component\Attachment\Entity\AttachmentToNote;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mime\MimeTypes;
class Attachment extends Controller
{
/**
* Generic function that handles getting a representation for an attachment
*/
private function attachment(int $attachment_id, Note|int $note, callable $handle)
{
$attachment = DB::findOneBy('attachment', ['id' => $attachment_id]);
$note = \is_int($note) ? Note::getById($note) : $note;
// Before anything, two very important things!
// first: ensure this attachment is associated with this note
if (DB::count(AttachmentToNote::class, ['attachment_id' => $attachment->getId(), 'note_id' => $note->getId()]) <= 0) {
throw new ClientException(_m('No such attachment.'), 404);
}
// second: ensure proper scope
if (!$note->isVisibleTo(Common::actor())) {
throw new ClientException(_m('You don\'t have permissions to view this attachment.'), 401);
}
$res = null;
if (Event::handle('AttachmentFileInfo', [$attachment, $note, &$res]) !== Event::stop) {
// If no one else claims this attachment, use the default representation
try {
$res = GSFile::getAttachmentFileInfo($attachment_id);
} catch (NoSuchFileException $e) {
// Continue below
}
}
if (empty($res)) {
throw new ClientException(_m('No such attachment'), 404);
} else {
if (!\array_key_exists('filepath', $res)) {
// @codeCoverageIgnoreStart
throw new ServerException('This attachment is not stored locally.');
// @codeCoverageIgnoreEnd
} else {
$res['attachment'] = $attachment;
$res['note'] = $note;
$res['title'] = $attachment->getBestTitle($note);
return $handle($res);
}
}
}
/**
* The page where the attachment and it's info is shown
*/
public function attachmentShowWithNote(Request $request, int $note_id, int $attachment_id)
{
try {
return $this->attachment($attachment_id, $note_id, function ($res) use ($note_id, $attachment_id) {
return [
'_template' => 'attachment/view.html.twig',
'download' => $res['attachment']->getDownloadUrl(note: $note_id),
'title' => $res['title'],
'attachment' => $res['attachment'],
'note' => $res['note'],
'right_panel_vars' => ['attachment_id' => $attachment_id, 'note_id' => $note_id],
];
});
} catch (NotFoundException) {
throw new ClientException(_m('No such attachment.'), 404);
}
}
/**
* Display the attachment inline
*/
public function attachmentViewWithNote(Request $request, int $note_id, int $attachment_id)
{
return $this->attachment(
$attachment_id,
$note_id,
fn (array $res) => GSFile::sendFile(
$res['filepath'],
$res['mimetype'],
GSFile::ensureFilenameWithProperExtension($res['title'], $res['mimetype']) ?? $res['filename'],
HeaderUtils::DISPOSITION_INLINE,
),
);
}
public function attachmentDownloadWithNote(Request $request, int $note_id, int $attachment_id)
{
return $this->attachment(
$attachment_id,
$note_id,
fn (array $res) => GSFile::sendFile(
$res['filepath'],
$res['mimetype'],
GSFile::ensureFilenameWithProperExtension($res['title'], $res['mimetype']) ?? $res['filename'],
HeaderUtils::DISPOSITION_ATTACHMENT,
),
);
}
/**
* Controller to produce a thumbnail for a given attachment id
*
* @param int $attachment_id Attachment ID
*
* @throws \App\Util\Exception\DuplicateFoundException
* @throws ClientException
* @throws NotFoundException
* @throws ServerException
*/
public function attachmentThumbnailWithNote(Request $request, int $note_id, int $attachment_id, string $size = 'small'): Response
{
$attachment = DB::findOneBy('attachment', ['id' => $attachment_id]);
$note = Note::getById($note_id);
// Before anything, two very important things!
// first: ensure this attachment is associated with this note
if (DB::count(AttachmentToNote::class, ['attachment_id' => $attachment->getId(), 'note_id' => $note->getId()]) <= 0) {
throw new ClientException(_m('No such attachment.'), 404);
}
// second: ensure proper scope
if (!$note->isVisibleTo(Common::actor())) {
throw new ClientException(_m('You don\'t have permissions to view this attachment.'), 401);
}
$crop = Common::config('thumbnail', 'smart_crop');
$thumbnail = AttachmentThumbnail::getOrCreate(attachment: $attachment, size: $size, crop: $crop);
if (\is_null($thumbnail)) {
throw new ClientException(_m('Can not generate thumbnail for attachment with id={id}', ['id' => $attachment->getId()]));
}
$filename = $thumbnail->getFilename();
$path = $thumbnail->getPath();
$mimetype = $thumbnail->getMimetype();
return GSFile::sendFile(filepath: $path, mimetype: $mimetype, output_filename: $filename . '.' . MimeTypes::getDefault()->getExtensions($mimetype)[0], disposition: HeaderUtils::DISPOSITION_INLINE);
}
}

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -17,8 +19,9 @@
// along with GNU social. If not, see <http://www.gnu.org/licenses/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Entity; namespace Component\Attachment\Entity;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;
@@ -32,13 +35,13 @@ use DateTimeInterface;
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class GSActorToAttachment extends Entity class ActorToAttachment extends Entity
{ {
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $attachment_id; private int $attachment_id;
private int $gsactor_id; private int $actor_id;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setAttachmentId(int $attachment_id): self public function setAttachmentId(int $attachment_id): self
{ {
@@ -51,15 +54,15 @@ class GSActorToAttachment extends Entity
return $this->attachment_id; return $this->attachment_id;
} }
public function setGSActorId(int $gsactor_id): self public function setActorId(int $actor_id): self
{ {
$this->gsactor_id = $gsactor_id; $this->actor_id = $actor_id;
return $this; return $this;
} }
public function getGSActorId(): int public function getActorId(): int
{ {
return $this->gsactor_id; return $this->actor_id;
} }
public function setModified(DateTimeInterface $modified): self public function setModified(DateTimeInterface $modified): self
@@ -76,19 +79,53 @@ class GSActorToAttachment extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public static function removeWhereAttachmentId(int $attachment_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM actor_to_attachment ata
WHERE ata.attachment_id = :attachment_id
EOF,
['attachment_id' => $attachment_id],
);
}
public static function removeWhere(int $attachment_id, int $actor_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM actor_to_attachment ata
WHERE (ata.attachment_id = :attachment_id
OR ata.actor_id = :actor_id)
EOF,
['attachment_id' => $attachment_id, 'actor_id' => $actor_id],
);
}
public static function removeWhereActorId(int $actor_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM actor_to_attachment ata
WHERE ata.actor_id = :actor_id
EOF,
['actor_id' => $actor_id],
);
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [
'name' => 'gsactor_to_attachment', 'name' => 'actor_to_attachment',
'fields' => [ 'fields' => [
'attachment_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Attachment.id', 'multiplicity' => 'one to one', 'name' => 'attachment_to_note_attachment_id_fkey', 'not null' => true, 'description' => 'id of attachment'], 'attachment_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Attachment.id', 'multiplicity' => 'one to one', 'name' => 'attachment_to_note_attachment_id_fkey', 'not null' => true, 'description' => 'id of attachment'],
'gsactor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'GSActor.id', 'multiplicity' => 'one to one', 'name' => 'attachment_to_note_note_id_fkey', 'not null' => true, 'description' => 'id of the note it belongs to'], 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'attachment_to_note_note_id_fkey', 'not null' => true, 'description' => 'id of the note it belongs to'],
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
], ],
'primary key' => ['attachment_id', 'gsactor_id'], 'primary key' => ['attachment_id', 'actor_id'],
'indexes' => [ 'indexes' => [
'attachment_id_idx' => ['attachment_id'], 'attachment_id_idx' => ['attachment_id'],
'gsactor_id_idx' => ['gsactor_id'], 'actor_id_idx' => ['actor_id'],
], ],
]; ];
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
@@ -19,16 +21,21 @@
// }}} // }}}
namespace App\Entity; namespace Component\Attachment\Entity;
use App\Core\DB\DB; use App\Core\Cache;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Router\Router; use App\Core\Router;
use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\DuplicateFoundException;
use App\Util\Exception\NoSuchFileException;
use App\Util\Exception\NotFoundException; use App\Util\Exception\NotFoundException;
use App\Util\Exception\ServerException; use App\Util\Exception\ServerException;
use DateTimeInterface; use DateTimeInterface;
@@ -53,13 +60,13 @@ class Attachment extends Entity
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $id; private int $id;
private int $lives = 1; private int $lives = 1;
private ?string $filehash; private ?string $filehash = null;
private ?string $mimetype; private ?string $mimetype = null;
private ?string $filename; private ?string $filename = null;
private ?int $size; private ?int $size = null;
private ?int $width; private ?int $width = null;
private ?int $height; private ?int $height = null;
private DateTimeInterface $modified; private DateTimeInterface $modified;
public function setId(int $id): self public function setId(int $id): self
@@ -73,25 +80,20 @@ class Attachment extends Entity
return $this->id; return $this->id;
} }
/** public function setLives(int $lives): self
* @return int {
*/ $this->lives = $lives;
return $this;
}
public function getLives(): int public function getLives(): int
{ {
return $this->lives; return $this->lives;
} }
/**
* @param int $lives
*/
public function setLives(int $lives): void
{
$this->lives = $lives;
}
public function setFilehash(?string $filehash): self public function setFilehash(?string $filehash): self
{ {
$this->filehash = $filehash; $this->filehash = \is_null($filehash) ? null : mb_substr($filehash, 0, 64);
return $this; return $this;
} }
@@ -102,7 +104,7 @@ class Attachment extends Entity
public function setMimetype(?string $mimetype): self public function setMimetype(?string $mimetype): self
{ {
$this->mimetype = $mimetype; $this->mimetype = \is_null($mimetype) ? null : mb_substr($mimetype, 0, 255);
return $this; return $this;
} }
@@ -113,7 +115,7 @@ class Attachment extends Entity
public function setFilename(?string $filename): self public function setFilename(?string $filename): self
{ {
$this->filename = $filename; $this->filename = \is_null($filename) ? null : mb_substr($filename, 0, 191);
return $this; return $this;
} }
@@ -172,44 +174,36 @@ class Attachment extends Entity
public function getMimetypeMajor(): ?string public function getMimetypeMajor(): ?string
{ {
$mime = $this->getMimetype(); $mime = $this->getMimetype();
return is_null($mime) ? $mime : GSFile::mimetypeMajor($mime); return \is_null($mime) ? $mime : GSFile::mimetypeMajor($mime);
} }
public function getMimetypeMinor(): ?string public function getMimetypeMinor(): ?string
{ {
$mime = $this->getMimetype(); $mime = $this->getMimetype();
return is_null($mime) ? $mime : GSFile::mimetypeMinor($mime); return \is_null($mime) ? $mime : GSFile::mimetypeMinor($mime);
} }
/**
* @return int
*/
public function livesIncrementAndGet(): int public function livesIncrementAndGet(): int
{ {
++$this->lives; ++$this->lives;
return $this->lives; return $this->lives;
} }
/**
* @return int
*/
public function livesDecrementAndGet(): int public function livesDecrementAndGet(): int
{ {
--$this->lives; --$this->lives;
return $this->lives; return $this->lives;
} }
const FILEHASH_ALGO = 'sha256'; public const FILEHASH_ALGO = 'sha256';
/** /**
* Delete a file if safe, removes dependencies, cleanups and flushes * Delete a file if safe, removes dependencies, cleanups and flushes
*
* @return bool
*/ */
public function kill(): bool public function kill(): bool
{ {
if ($this->livesDecrementAndGet() <= 0) { if ($this->livesDecrementAndGet() <= 0) {
return $this->delete(); return DB::wrapInTransaction(fn () => $this->delete());
} }
return true; return true;
} }
@@ -219,7 +213,7 @@ class Attachment extends Entity
*/ */
public function deleteStorage(): bool public function deleteStorage(): bool
{ {
if (!is_null($filepath = $this->getPath())) { if (!\is_null($filepath = $this->getPath())) {
if (file_exists($filepath)) { if (file_exists($filepath)) {
if (@unlink($filepath) === false) { if (@unlink($filepath) === false) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
@@ -230,8 +224,7 @@ class Attachment extends Entity
$this->setFilename(null); $this->setFilename(null);
$this->setSize(null); $this->setSize(null);
// Important not to null neither width nor height // Important not to null neither width nor height
DB::persist($this); DB::wrapInTransaction(fn () => DB::persist($this));
DB::flush();
} }
} else { } else {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
@@ -244,24 +237,44 @@ class Attachment extends Entity
/** /**
* Attachment delete always removes dependencies, cleanups and flushes * Attachment delete always removes dependencies, cleanups and flushes
* WARNING: Wrap this function in a transaction!
*
* @see kill() It's more likely that you want to use that rather than call delete directly
*/ */
protected function delete(): bool protected function delete(): bool
{ {
// Friendly warning because the caller usually doesn't want to delete an attachment that is still referred elsewhere
if ($this->getLives() > 0) { if ($this->getLives() > 0) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
Log::warning("Deleting file {$this->getId()} with {$this->getLives()} lives. Why are you killing it so young?"); Log::warning("Deleting file {$this->getId()} with {$this->getLives()} lives. Why are you killing it so old?");
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
// Delete related files from storage
// Collect files starting with the one associated with this attachment
$files = []; $files = [];
if (!is_null($filepath = $this->getPath())) { if (!\is_null($filepath = $this->getPath())) {
$files[] = $filepath; $files[] = $filepath;
} }
// Collect thumbnail files and delete thumbnails
foreach ($this->getThumbnails() as $at) { foreach ($this->getThumbnails() as $at) {
$files[] = $at->getPath(); $files[] = $at->getPath();
$at->delete(flush: false); $at->delete(flush: false);
} }
// Delete eventual remaining relations with Actors
ActorToAttachment::removeWhereAttachmentId($this->getId());
// Delete eventual remaining relations with Notes
AttachmentToNote::removeWhereAttachmentId($this->getId());
// Delete eventual remaining relations with Links
AttachmentToLink::removeWhereAttachmentId($this->getId());
// Remove this attachment
DB::remove($this); DB::remove($this);
// Delete the files from disk
foreach ($files as $f) { foreach ($files as $f) {
if (file_exists($f)) { if (file_exists($f)) {
if (@unlink($f) === false) { if (@unlink($f) === false) {
@@ -275,6 +288,8 @@ class Attachment extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
} }
// Flush these changes as we have deleted the files from disk
DB::flush(); DB::flush();
return true; return true;
} }
@@ -282,28 +297,35 @@ class Attachment extends Entity
/** /**
* TODO: Maybe this isn't the best way of handling titles * TODO: Maybe this isn't the best way of handling titles
* *
* @param null|Note $note
*
* @throws DuplicateFoundException * @throws DuplicateFoundException
* @throws NotFoundException * @throws NotFoundException
* @throws ServerException * @throws ServerException
*
* @return string
*/ */
public function getBestTitle(?Note $note = null): string public function getBestTitle(?Note $note = null): string
{ {
// If we have a note, then the best title is the title itself // If we have a note, then the best title is the title itself
if (!is_null(($note))) { if (!\is_null(($note))) {
$attachment_to_note = DB::findOneBy('attachment_to_note', [ $title = Cache::get('attachment-title-' . $this->getId() . '-' . $note->getId(), function () use ($note) {
'attachment_id' => $this->getId(), try {
'note_id' => $note->getId(), $attachment_to_note = DB::findOneBy('attachment_to_note', [
]); 'attachment_id' => $this->getId(),
if (!is_null($attachment_to_note->getTitle())) { 'note_id' => $note->getId(),
return $attachment_to_note->getTitle(); ]);
if (!\is_null($attachment_to_note->getTitle())) {
return $attachment_to_note->getTitle();
}
} catch (NotFoundException) {
$title = null;
Event::handle('AttachmentGetBestTitle', [$this, $note, &$title]);
return $title;
}
});
if ($title != null) {
return $title;
} }
} }
// Else // Else
if (!is_null($filename = $this->getFilename())) { if (!\is_null($filename = $this->getFilename())) {
// A filename would do just as well // A filename would do just as well
return $filename; return $filename;
} else { } else {
@@ -317,23 +339,51 @@ class Attachment extends Entity
*/ */
public function getThumbnails() public function getThumbnails()
{ {
return DB::findBy('attachment_thumbnail', ['attachment_id' => $this->id]); return DB::findBy(
AttachmentThumbnail::class,
['attachment_id' => $this->id],
order_by: ['size' => 'ASC', 'mimetype' => 'ASC'],
);
} }
public function getPath() public function getPath()
{ {
$filename = $this->getFilename(); $filename = $this->getFilename();
return is_null($filename) ? null : Common::config('attachments', 'dir') . DIRECTORY_SEPARATOR . $filename; return \is_null($filename) ? null : Common::config('attachments', 'dir') . \DIRECTORY_SEPARATOR . $filename;
} }
public function getUrl() public function getUrl(Note|int $note, int $type = Router::ABSOLUTE_URL): string
{ {
return Router::url('attachment_view', ['id' => $this->getId()]); return Router::url(id: 'note_attachment_view', args: ['note_id' => \is_int($note) ? $note : $note->getId(), 'attachment_id' => $this->getId()], type: $type);
} }
public function getThumbnailUrl() public function getShowUrl(Note|int $note, int $type = Router::ABSOLUTE_URL): string
{ {
return Router::url('attachment_thumbnail', ['id' => $this->getId(), 'w' => Common::config('thumbnail', 'width'), 'h' => Common::config('thumbnail', 'height')]); return Router::url(id: 'note_attachment_show', args: ['note_id' => \is_int($note) ? $note : $note->getId(), 'attachment_id' => $this->getId()], type: $type);
}
public function getDownloadUrl(Note|int $note, int $type = Router::ABSOLUTE_URL): string
{
return Router::url(id: 'note_attachment_download', args: ['note_id' => \is_int($note) ? $note : $note->getId(), 'attachment_id' => $this->getId()], type: $type);
}
/**
* @throws ClientException
* @throws NotFoundException
* @throws ServerException
*/
public function getThumbnail(?string $size = null, bool $crop = false): ?AttachmentThumbnail
{
try {
return AttachmentThumbnail::getOrCreate(attachment: $this, size: $size, crop: $crop);
} catch (NoSuchFileException) {
return null;
}
}
public function getThumbnailUrl(Note|int $note, ?string $size = null): string
{
return Router::url('note_attachment_thumbnail', ['note_id' => \is_int($note) ? $note : $note->getId(), 'attachment_id' => $this->getId(), 'size' => $size ?? Common::config('thumbnail', 'default_size')]);
} }
public static function schemaDef(): array public static function schemaDef(): array
@@ -342,7 +392,7 @@ class Attachment extends Entity
'name' => 'attachment', 'name' => 'attachment',
'fields' => [ 'fields' => [
'id' => ['type' => 'serial', 'not null' => true], 'id' => ['type' => 'serial', 'not null' => true],
'lives' => ['type' => 'int', 'not null' => true, 'description' => 'RefCount'], 'lives' => ['type' => 'int', 'default' => 1, 'not null' => true, 'description' => 'RefCount, starts with 1'],
'filehash' => ['type' => 'varchar', 'length' => 64, 'description' => 'sha256 of the file contents, if the file is stored locally'], 'filehash' => ['type' => 'varchar', 'length' => 64, 'description' => 'sha256 of the file contents, if the file is stored locally'],
'mimetype' => ['type' => 'varchar', 'length' => 255, 'description' => 'resource mime type 127+1+127 as per rfc6838#section-4.2'], 'mimetype' => ['type' => 'varchar', 'length' => 255, 'description' => 'resource mime type 127+1+127 as per rfc6838#section-4.2'],
'filename' => ['type' => 'varchar', 'length' => 191, 'description' => 'file name of resource when available'], 'filename' => ['type' => 'varchar', 'length' => 191, 'description' => 'file name of resource when available'],

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
@@ -19,21 +21,22 @@
// }}} // }}}
namespace App\Entity; namespace Component\Attachment\Entity;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB\DB; use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Core\Router\Router; use App\Core\Router;
use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;
use App\Util\Exception\NotFoundException; use App\Util\Exception\NotFoundException;
use App\Util\Exception\NotStoredLocallyException; use App\Util\Exception\NotStoredLocallyException;
use App\Util\Exception\ServerException; use App\Util\Exception\ServerException;
use App\Util\TemporaryFile;
use DateTimeInterface; use DateTimeInterface;
use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Mime\MimeTypes;
@@ -49,19 +52,30 @@ use Symfony\Component\Mime\MimeTypes;
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
* @author Hugo Sales <hugo@hsal.es> * @author Hugo Sales <hugo@hsal.es>
* @author Diogo Peralta Cordeiro <mail@diogo.site> * @author Diogo Peralta Cordeiro <mail@diogo.site>
* @author Eliseu Amaro <mail@eliseuama.ro>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class AttachmentThumbnail extends Entity class AttachmentThumbnail extends Entity
{ {
public const SIZE_SMALL = 0;
public const SIZE_MEDIUM = 1;
public const SIZE_BIG = 2;
public const SIZE_MAP = [
'small' => self::SIZE_SMALL,
'medium' => self::SIZE_MEDIUM,
'big' => self::SIZE_BIG,
];
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $attachment_id; private int $attachment_id;
private ?string $mimetype; private ?string $mimetype = null;
private int $size = 0;
private string $filename;
private int $width; private int $width;
private int $height; private int $height;
private string $filename; private DateTimeInterface $modified;
private \DateTimeInterface $modified;
public function setAttachmentId(int $attachment_id): self public function setAttachmentId(int $attachment_id): self
{ {
@@ -76,7 +90,7 @@ class AttachmentThumbnail extends Entity
public function setMimetype(?string $mimetype): self public function setMimetype(?string $mimetype): self
{ {
$this->mimetype = $mimetype; $this->mimetype = \is_null($mimetype) ? null : mb_substr($mimetype, 0, 129);
return $this; return $this;
} }
@@ -85,6 +99,28 @@ class AttachmentThumbnail extends Entity
return $this->mimetype; return $this->mimetype;
} }
public function setSize(int $size): self
{
$this->size = $size;
return $this;
}
public function getSize(): int
{
return $this->size;
}
public function setFilename(string $filename): self
{
$this->filename = mb_substr($filename, 0, 191);
return $this;
}
public function getFilename(): string
{
return $this->filename;
}
public function setWidth(int $width): self public function setWidth(int $width): self
{ {
$this->width = $width; $this->width = $width;
@@ -107,17 +143,6 @@ class AttachmentThumbnail extends Entity
return $this->height; return $this->height;
} }
public function setFilename(string $filename): self
{
$this->filename = $filename;
return $this;
}
public function getFilename(): string
{
return $this->filename;
}
public function setModified(DateTimeInterface $modified): self public function setModified(DateTimeInterface $modified): self
{ {
$this->modified = $modified; $this->modified = $modified;
@@ -132,6 +157,17 @@ class AttachmentThumbnail extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public static function sizeIntToStr(?int $size): string
{
$map = array_flip(self::SIZE_MAP);
return $map[$size] ?? $map[self::SIZE_SMALL];
}
public static function sizeStrToInt(string $size)
{
return self::SIZE_MAP[$size] ?? self::SIZE_SMALL;
}
private ?Attachment $attachment = null; private ?Attachment $attachment = null;
public function setAttachment(?Attachment $attachment) public function setAttachment(?Attachment $attachment)
@@ -141,45 +177,50 @@ class AttachmentThumbnail extends Entity
public function getAttachment() public function getAttachment()
{ {
if (isset($this->attachment) && !is_null($this->attachment)) { if (isset($this->attachment) && !\is_null($this->attachment)) {
return $this->attachment; return $this->attachment;
} else { } else {
return $this->attachment = DB::findOneBy('attachment', ['id' => $this->attachment_id]); return $this->attachment = DB::findOneBy(Attachment::class, ['id' => $this->attachment_id]);
} }
} }
public static function getCacheKey(int $id, int $size)
{
return "thumb-{$id}-{$size}";
}
/** /**
* @param Attachment $attachment * @param ?string $size 'small'|'medium'|'big'
* @param int $width
* @param int $height
* @param bool $crop
* *
* @throws ClientException * @throws ClientException
* @throws NotFoundException * @throws NotFoundException
* @throws ServerException * @throws ServerException
* *
* @return mixed * @return ?self
*/ */
public static function getOrCreate(Attachment $attachment, int $width, int $height, bool $crop) public static function getOrCreate(Attachment $attachment, ?string $size = null, bool $crop = false): ?self
{ {
// We need to keep these in mind for DB indexing $size ??= Common::config('thumbnail', 'default_size');
$predicted_width = null; $size_int = self::sizeStrToInt($size);
$predicted_height = null;
try { try {
if (is_null($attachment->getWidth()) || is_null($attachment->getHeight())) { return Cache::get(
// @codeCoverageIgnoreStart self::getCacheKey($attachment->getId(), $size_int),
// TODO: check if we can generate from an existing thumbnail fn () => DB::findOneBy(self::class, ['attachment_id' => $attachment->getId(), 'size' => $size_int]),
throw new ClientException(_m('Invalid dimensions requested for thumbnail.')); );
// @codeCoverageIgnoreEnd } catch (NotFoundException) {
if (\is_null($attachment->getWidth()) || \is_null($attachment->getHeight())) {
return null;
} }
return Cache::get('thumb-' . $attachment->getId() . "-{$width}x{$height}", [$predicted_width, $predicted_height] = self::predictScalingValues($attachment->getWidth(), $attachment->getHeight(), $size, $crop);
function () use ($crop, $attachment, $width, $height, &$predicted_width, &$predicted_height) { if (\is_null($attachment->getPath()) || !file_exists($attachment->getPath())) {
[$predicted_width, $predicted_height] = self::predictScalingValues($attachment->getWidth(), $attachment->getHeight(), $width, $height, $crop); // Before we quit, check if there's any other thumb
return DB::findOneBy('attachment_thumbnail', ['attachment_id' => $attachment->getId(), 'width' => $predicted_width, 'height' => $predicted_height]); $alternative_thumbs = DB::findBy(self::class, ['attachment_id' => $attachment->getId()]);
}); usort($alternative_thumbs, fn ($l, $r) => $r->getSize() <=> $l->getSize());
} catch (NotFoundException $e) { if (empty($alternative_thumbs)) {
if (!file_exists($attachment->getPath())) { throw new NotStoredLocallyException();
throw new NotStoredLocallyException(); } else {
return $alternative_thumbs[0];
}
} }
$thumbnail = self::create(['attachment_id' => $attachment->getId()]); $thumbnail = self::create(['attachment_id' => $attachment->getId()]);
$mimetype = $attachment->getMimetype(); $mimetype = $attachment->getMimetype();
@@ -188,48 +229,39 @@ class AttachmentThumbnail extends Entity
$event_map[$major_mime] = []; $event_map[$major_mime] = [];
Event::handle('FileResizerAvailable', [&$event_map, $mimetype]); Event::handle('FileResizerAvailable', [&$event_map, $mimetype]);
// Always prefer specific encoders // Always prefer specific encoders
/** @var callable[] function(string $source, ?TemporaryFile &$destination, int &$width, int &$height, bool $smart_crop, ?string &$mimetype): bool */
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]); $encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
foreach ($encoders as $encoder) { foreach ($encoders as $encoder) {
/** @var ?TemporaryFile */
$temp = null; // Let the EncoderPlugin create a temporary file for us $temp = null; // Let the EncoderPlugin create a temporary file for us
if ($encoder($attachment->getPath(), $temp, $width, $height, $crop, $mimetype)) { if ($encoder($attachment->getPath(), $temp, $predicted_width, $predicted_height, $crop, $mimetype)) {
$thumbnail->setAttachment($attachment); $thumbnail->setAttachment($attachment);
$thumbnail->setWidth($predicted_width); $thumbnail->setSize($size_int);
$thumbnail->setHeight($predicted_height); $mimetype = $temp->getMimeType();
$ext = '.' . MimeTypes::getDefault()->getExtensions($temp->getMimeType())[0]; $ext = '.' . MimeTypes::getDefault()->getExtensions($mimetype)[0];
$filename = "{$predicted_width}x{$predicted_height}{$ext}-" . $attachment->getFilehash(); $filename = "{$predicted_width}x{$predicted_height}{$ext}-" . $attachment->getFilehash();
$thumbnail->setFilename($filename); $thumbnail->setFilename($filename);
$thumbnail->setMimetype($mimetype); $thumbnail->setMimetype($mimetype);
$thumbnail->setWidth($predicted_width);
$thumbnail->setHeight($predicted_height);
DB::persist($thumbnail); DB::persist($thumbnail);
DB::flush(); DB::flush();
$temp->move(Common::config('thumbnail', 'dir'), $filename); $temp->move(Common::config('thumbnail', 'dir'), $filename);
return $thumbnail; return $thumbnail;
} }
} }
throw new ClientException(_m('Can not generate thumbnail for attachment with id={id}', ['id' => $attachment->getId()])); return null;
} }
} }
public function getPath() public function getPath(): string
{ {
return Common::config('thumbnail', 'dir') . DIRECTORY_SEPARATOR . $this->getFilename(); return Common::config('thumbnail', 'dir') . \DIRECTORY_SEPARATOR . $this->getFilename();
} }
public function getUrl() public function getUrl(Note|int $note): string
{ {
return Router::url('attachment_thumbnail', ['id' => $this->getAttachmentId(), 'w' => $this->getWidth(), 'h' => $this->getHeight()]); return Router::url('note_attachment_thumbnail', ['note_id' => \is_int($note) ? $note : $note->getId(), 'attachment_id' => $this->getAttachmentId(), 'size' => self::sizeIntToStr($this->getSize())]);
}
/**
* Get the HTML attributes for this thumbnail
*/
public function getHTMLAttributes(array $orig = [], bool $overwrite = true)
{
$attrs = [
'height' => $this->getHeight(),
'width' => $this->getWidth(),
'src' => $this->getUrl(),
];
return $overwrite ? array_merge($orig, $attrs) : array_merge($attrs, $orig);
} }
/** /**
@@ -241,13 +273,15 @@ class AttachmentThumbnail extends Entity
if (file_exists($filepath)) { if (file_exists($filepath)) {
if (@unlink($filepath) === false) { if (@unlink($filepath) === false) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
Log::warning("Failed deleting file for attachment thumbnail with id={$this->attachment_id}, width={$this->width}, height={$this->height} at {$filepath}"); Log::warning("Failed deleting file for attachment thumbnail with id={$this->getAttachmentId()}, size={$this->getSize()} at {$filepath}");
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }
} }
DB::remove($this); Cache::delete(self::getCacheKey($this->getAttachmentId(), $this->getSize()));
if ($flush) { if ($flush) {
DB::flush(); DB::wrapInTransaction(fn () => DB::remove($this));
} else {
DB::remove($this);
} }
} }
@@ -257,35 +291,75 @@ class AttachmentThumbnail extends Entity
* Values will scale _up_ to fit max values if cropping is enabled! * Values will scale _up_ to fit max values if cropping is enabled!
* With cropping disabled, the max value of each axis will be respected. * With cropping disabled, the max value of each axis will be respected.
* *
* @param $width int Original width * @param int $existing_width Original width
* @param $height int Original height * @param int $existing_height Original height
* @param $maxW int Resulting max width
* @param $maxH int Resulting max height
* @param $crop bool Crop to the size (not preserving aspect ratio)
* *
* @return array [predicted width, predicted height] * @return array [predicted width, predicted height]
*/ */
public static function predictScalingValues( public static function predictScalingValues(
int $existing_width, int $existing_width,
int $existing_height, int $existing_height,
int $requested_width, string $requested_size,
int $requested_height, bool $crop,
bool $crop
): array { ): array {
if ($crop) { /**
$rw = min($existing_width, $requested_width); * 1:1 => Square
$rh = min($existing_height, $requested_height); * 4:3 => SD
} else { * 11:8 => Academy Ratio
if ($existing_width > $existing_height) { * 3:2 => Classic 35mm
$rw = min($existing_width, $requested_width); * 16:10 => Golden Ratio
$rh = ceil($existing_height * $rw / $existing_width); * 16:9 => Widescreen
} else { * 2.2:1 => Standard 70mm film
$rh = min($existing_height, $requested_height); */
$rw = ceil($existing_width * $rh / $existing_height); $allowed_aspect_ratios = [1, 1.3, 1.376, 1.5, 1.6, 1.7, 2.2]; // Ascending array
} $sizes = [
'small' => Common::config('thumbnail', 'small'),
'medium' => Common::config('thumbnail', 'medium'),
'big' => Common::config('thumbnail', 'big'),
];
// We only scale if the image is larger than the minimum width and height for a thumbnail
if ($existing_width < Common::config('thumbnail', 'minimum_width') && $existing_height < Common::config('thumbnail', 'minimum_height')) {
return [$existing_width, $existing_height];
} }
return [(int) $rw, (int) $rh]; // We only scale if the total of pixels is greater than the maximum allowed for a thumbnail
$total_of_pixels = $existing_width * $existing_height;
if ($total_of_pixels < Common::config('thumbnail', 'maximum_pixels')) {
return [$existing_width, $existing_height];
}
// Is this a portrait image?
$flip = $existing_height > $existing_width;
// Find the aspect ratio of the given image
$existing_aspect_ratio = !$flip ? $existing_width / $existing_height : $existing_height / $existing_width;
// Binary search the closer allowed aspect ratio
$left = 0;
$right = \count($allowed_aspect_ratios) - 1;
while ($left < $right) {
$mid = floor($left + ($right - $left) / 2);
// Comparing absolute distances with middle value and right value
if (abs($existing_aspect_ratio - $allowed_aspect_ratios[$mid]) < abs($existing_aspect_ratio - $allowed_aspect_ratios[$right])) {
// search the left side of the array
$right = $mid;
} else {
// search the right side of the array
$left = $mid + 1;
}
}
$closest_aspect_ratio = $allowed_aspect_ratios[$left];
unset($mid, $left, $right);
// TODO: For crop, we should test a threshold and understand if the image would better be cropped
// Resulting width and height
$rw = (int) ($sizes[$requested_size]);
$rh = (int) ($rw / $closest_aspect_ratio);
return !$flip ? [$rw, $rh] : [$rh, $rw];
} }
public static function schemaDef(): array public static function schemaDef(): array
@@ -295,12 +369,13 @@ class AttachmentThumbnail extends Entity
'fields' => [ 'fields' => [
'attachment_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Attachment.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'thumbnail for what attachment'], 'attachment_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Attachment.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'thumbnail for what attachment'],
'mimetype' => ['type' => 'varchar', 'length' => 129, 'description' => 'resource mime type 64+1+64, images hardly will show up with long mimetypes, this is probably safe considering rfc6838#section-4.2'], 'mimetype' => ['type' => 'varchar', 'length' => 129, 'description' => 'resource mime type 64+1+64, images hardly will show up with long mimetypes, this is probably safe considering rfc6838#section-4.2'],
'width' => ['type' => 'int', 'not null' => true, 'description' => 'width of thumbnail'], 'size' => ['type' => 'int', 'not null' => true, 'default' => 0, 'description' => '0 = small; 1 = medium; 2 = big'],
'height' => ['type' => 'int', 'not null' => true, 'description' => 'height of thumbnail'],
'filename' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'thumbnail filename'], 'filename' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'thumbnail filename'],
'width' => ['type' => 'int', 'not null' => true, 'description' => 'width in pixels, if it can be described as such and data is available'],
'height' => ['type' => 'int', 'not null' => true, 'description' => 'height in pixels, if it can be described as such and data is available'],
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
], ],
'primary key' => ['attachment_id', 'width', 'height'], 'primary key' => ['attachment_id', 'size'],
'indexes' => [ 'indexes' => [
'attachment_thumbnail_attachment_id_idx' => ['attachment_id'], 'attachment_thumbnail_attachment_id_idx' => ['attachment_id'],
], ],

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -17,8 +19,9 @@
// along with GNU social. If not, see <http://www.gnu.org/licenses/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Entity; namespace Component\Attachment\Entity;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;
@@ -36,20 +39,9 @@ class AttachmentToLink extends Entity
{ {
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $attachment_id;
private int $link_id; private int $link_id;
private \DateTimeInterface $modified; private int $attachment_id;
private DateTimeInterface $modified;
public function setAttachmentId(int $attachment_id): self
{
$this->attachment_id = $attachment_id;
return $this;
}
public function getAttachmentId(): int
{
return $this->attachment_id;
}
public function setLinkId(int $link_id): self public function setLinkId(int $link_id): self
{ {
@@ -62,6 +54,17 @@ class AttachmentToLink extends Entity
return $this->link_id; return $this->link_id;
} }
public function setAttachmentId(int $attachment_id): self
{
$this->attachment_id = $attachment_id;
return $this;
}
public function getAttachmentId(): int
{
return $this->attachment_id;
}
public function setModified(DateTimeInterface $modified): self public function setModified(DateTimeInterface $modified): self
{ {
$this->modified = $modified; $this->modified = $modified;
@@ -76,6 +79,40 @@ class AttachmentToLink extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public static function removeWhereAttachmentId(int $attachment_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM attachment_to_link atl
WHERE atl.attachment_id = :attachment_id
EOF,
['attachment_id' => $attachment_id],
);
}
public static function removeWhere(int $link_id, int $attachment_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM attachment_to_link atl
WHERE (atl.link_id = :link_id
OR atl.attachment_id = :attachment_id)
EOF,
['link_id' => $link_id, 'attachment_id' => $attachment_id],
);
}
public static function removeWhereLinkId(int $link_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM attachment_to_link atl
WHERE atl.link_id = :link_id
EOF,
['link_id' => $link_id],
);
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -17,8 +19,9 @@
// along with GNU social. If not, see <http://www.gnu.org/licenses/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Entity; namespace Component\Attachment\Entity;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;
@@ -42,8 +45,8 @@ class AttachmentToNote extends Entity
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $attachment_id; private int $attachment_id;
private int $note_id; private int $note_id;
private ?string $title; private ?string $title = null;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setAttachmentId(int $attachment_id): self public function setAttachmentId(int $attachment_id): self
{ {
@@ -92,6 +95,40 @@ class AttachmentToNote extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public static function removeWhere(int $note_id, int $attachment_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM attachment_to_note atn
WHERE (atn.attachment_id = :attachment_id
OR atn.note_id = :note_id)
EOF,
['note_id' => $note_id, 'attachment_id' => $attachment_id],
);
}
public static function removeWhereNoteId(int $note_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM attachment_to_note atn
WHERE atn.note_id = :note_id
EOF,
['note_id' => $note_id],
);
}
public static function removeWhereAttachmentId(int $attachment_id): mixed
{
return DB::dql(
<<<'EOF'
DELETE FROM attachment_to_note atn
WHERE atn.attachment_id = :attachment_id
EOF,
['attachment_id' => $attachment_id],
);
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
@@ -19,10 +21,12 @@
// }}} // }}}
namespace App\Tests\Core; namespace Component\Attachment\tests\Controller;
use App\Core\DB\DB; use App\Core\DB;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentToNote;
class AttachmentTest extends GNUsocialTestCase class AttachmentTest extends GNUsocialTestCase
{ {
@@ -30,31 +34,33 @@ class AttachmentTest extends GNUsocialTestCase
{ {
// This calls static::bootKernel(), and creates a "client" that is acting as the browser // This calls static::bootKernel(), and creates a "client" that is acting as the browser
$client = static::createClient(); $client = static::createClient();
$client->request('GET', '/attachment'); //$client->request('GET', '/attachment');
//$this->assertResponseStatusCodeSame(404);
$client->request('GET', '/object/note/1/attachment/-1');
$this->assertResponseStatusCodeSame(404); $this->assertResponseStatusCodeSame(404);
$client->request('GET', '/attachment/-1'); $client->request('GET', '/object/note/1/attachment/asd');
$this->assertResponseStatusCodeSame(404); $this->assertResponseStatusCodeSame(404);
$client->request('GET', '/attachment/asd'); $client->request('GET', '/object/note/1/attachment/0');
$this->assertResponseStatusCodeSame(404);
$client->request('GET', '/attachment/0');
// In the meantime, throwing ClientException doesn't actually result in the reaching the UI, as it's intercepted // In the meantime, throwing ClientException doesn't actually result in the reaching the UI, as it's intercepted
// by the helpful framework that displays the stack traces and such. This should be easily fixable when we have // by the helpful framework that displays the stack traces and such. This should be easily fixable when we have
// our own error pages // our own error pages
$this->assertSelectorTextContains('.stacktrace', 'ClientException'); $this->assertResponseStatusCodeSame(500); // TODO (exception page) 404
$this->assertSelectorTextContains('.stacktrace', 'No such attachment.');
} }
private function testAttachment(string $suffix = '') private function testAttachment(string $suffix = '')
{ {
$client = static::createClient(); $client = static::createClient();
$id = DB::findOneBy('attachment', ['filehash' => '5d8ee7ead51a28803b4ee5cb2306a0b90b6ba570f1e5bcc2209926f6ab08e7ea'])->getId(); $attachment_id = DB::findOneBy(Attachment::class, ['filehash' => '5d8ee7ead51a28803b4ee5cb2306a0b90b6ba570f1e5bcc2209926f6ab08e7ea'])->getId();
$crawler = $client->request('GET', "/attachment/{$id}{$suffix}"); $note_id = DB::findOneBy(AttachmentToNote::class, ['attachment_id' => $attachment_id])->getNoteId();
$crawler = $client->request('GET', "/object/note/{$note_id}/attachment/{$attachment_id}{$suffix}");
} }
public function testAttachmentShow() public function testAttachmentShow()
{ {
$this->testAttachment(); $this->testAttachment();
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('figure figcaption', '5d8ee7ead51a28803b4ee5cb2306a0b90b6ba570f1e5bcc2209926f6ab08e7ea'); $this->assertSelectorTextContains('figure figcaption', 'image.jpg');
} }
public function testAttachmentView() public function testAttachmentView()
@@ -63,32 +69,27 @@ class AttachmentTest extends GNUsocialTestCase
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
} }
public function testAttachmentViewNotStored()
{
$client = static::createClient();
$last_attachment = DB::findBy('attachment', [], orderBy: ['id' => 'DESC'], limit: 1)[0];
$id = $last_attachment->getId() + 1;
$crawler = $client->request('GET', "/attachment/{$id}/view");
$this->assertResponseStatusCodeSame(500); // TODO (exception page) 404
$this->assertSelectorTextContains('.stacktrace', 'ClientException');
}
public function testAttachmentDownload() public function testAttachmentDownload()
{ {
$this->testAttachment('/download'); $this->testAttachment('/download');
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
} }
public function testAttachmentThumbnail() public function testAttachmentThumbnailSmall()
{ {
$this->testAttachment('/thumbnail'); $this->testAttachment('/thumbnail/small');
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
} }
public function testAttachmentThumbnailWrongSize() public function testAttachmentThumbnailMedium()
{ {
$this->testAttachment('/thumbnail?w=1&h=1'); $this->testAttachment('/thumbnail/medium');
$this->assertSelectorTextContains('.stacktrace', 'ClientException'); $this->assertResponseIsSuccessful();
// $this->assertResponseStatusCodeSame(400); }
public function testAttachmentThumbnailBig()
{
$this->testAttachment('/thumbnail/big');
$this->assertResponseIsSuccessful();
} }
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -17,16 +19,19 @@
// along with GNU social. If not, see <http://www.gnu.org/licenses/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Tests\Entity; namespace Component\Attachment\tests\Entity;
use App\Core\DB\DB; use App\Core\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use App\Entity\AttachmentToNote; use App\Core\Router;
use App\Entity\Note; use App\Entity\Note;
use App\Util\GNUsocialTestCase; use App\Util\GNUsocialTestCase;
use App\Util\TemporaryFile; use App\Util\TemporaryFile;
use Component\Attachment\Entity\AttachmentToNote;
use Component\Conversation\Conversation;
use Jchook\AssertThrows\AssertThrows; use Jchook\AssertThrows\AssertThrows;
use SplFileInfo;
use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\File;
class AttachmentTest extends GNUsocialTestCase class AttachmentTest extends GNUsocialTestCase
@@ -39,35 +44,36 @@ class AttachmentTest extends GNUsocialTestCase
// Setup first attachment // Setup first attachment
$file = new TemporaryFile(); $file = new TemporaryFile();
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($file); $attachment = GSFile::storeFileAsAttachment($file, check_is_supported_mimetype: false);
$path = $attachment->getPath(); $path = $attachment->getPath();
$hash = $attachment->getFilehash(); $hash = $attachment->getFilehash();
static::assertTrue(file_exists($attachment->getPath())); static::assertFileExists($attachment->getPath());
static::assertSame(1, $attachment->getLives()); static::assertSame(1, $attachment->getLives());
static::assertTrue(file_exists($path)); static::assertFileExists($path);
// Delete the backed storage of the attachment // Delete the backed storage of the attachment
static::assertTrue($attachment->deleteStorage()); static::assertTrue($attachment->deleteStorage());
static::assertFalse(file_exists($path)); static::assertFileDoesNotExist($path);
static::assertNull($attachment->getPath()); static::assertNull($attachment->getPath());
DB::flush($attachment); DB::persist($attachment);
DB::flush();
// Setup the second attachment, re-adding the backed store // Setup the second attachment, re-adding the backed store
$file = new TemporaryFile(); $file = new TemporaryFile();
$repeated_attachment = GSFile::sanitizeAndStoreFileAsAttachment($file); $repeated_attachment = GSFile::storeFileAsAttachment($file, check_is_supported_mimetype: false);
$path = $attachment->getPath(); $path = $attachment->getPath();
static::assertSame(2, $repeated_attachment->getLives()); static::assertSame(2, $repeated_attachment->getLives());
static::assertTrue(file_exists($path)); static::assertFileExists($path);
// Garbage collect the attachment // Garbage collect the attachment
$attachment->kill(); $attachment->kill();
static::assertTrue(file_exists($path)); static::assertFileExists($path);
static::assertSame(1, $repeated_attachment->getLives()); static::assertSame(1, $repeated_attachment->getLives());
// Garbage collect the second attachment, which should delete everything // Garbage collect the second attachment, which should delete everything
$repeated_attachment->kill(); $repeated_attachment->kill();
static::assertSame(0, $repeated_attachment->getLives()); static::assertSame(0, $repeated_attachment->getLives());
static::assertFalse(file_exists($path)); static::assertFileDoesNotExist($path);
static::assertSame([], DB::findBy('attachment', ['filehash' => $hash])); static::assertSame([], DB::findBy('attachment', ['filehash' => $hash]));
} }
@@ -76,15 +82,16 @@ class AttachmentTest extends GNUsocialTestCase
$test = function (string $method) { $test = function (string $method) {
$temp_file = new TemporaryFile(); $temp_file = new TemporaryFile();
$temp_file->write(file_get_contents(INSTALLDIR . '/tests/sample-uploads/gnu-logo.png')); $temp_file->write(file_get_contents(INSTALLDIR . '/tests/sample-uploads/gnu-logo.png'));
$hash = null;
Event::handle('HashFile', [$temp_file->getPathname(), &$hash]); Event::handle('HashFile', [$temp_file->getPathname(), &$hash]);
$attachment = DB::findOneBy('attachment', ['filehash' => $hash]); $attachment = DB::findOneBy('attachment', ['filehash' => $hash]);
$attachment->{$method}(); $attachment->{$method}();
DB::flush(); DB::flush();
$file = new File($temp_file->getRealPath()); $file = new File($temp_file->getRealPath());
GSFile::sanitizeAndStoreFileAsAttachment($file); GSFile::storeFileAsAttachment($file);
static::assertNotNull($attachment->getFilename()); static::assertNotNull($attachment->getFilename());
static::assertTrue(file_exists($attachment->getPath())); static::assertFileExists($attachment->getPath());
}; };
$test('deleteStorage'); $test('deleteStorage');
@@ -100,8 +107,9 @@ class AttachmentTest extends GNUsocialTestCase
static::assertSame('Untitled attachment', $attachment->getBestTitle()); static::assertSame('Untitled attachment', $attachment->getBestTitle());
$attachment->setFilename($filename); $attachment->setFilename($filename);
$actor = DB::findOneBy('gsactor', ['nickname' => 'taken_user']); $actor = DB::findOneBy('actor', ['nickname' => 'taken_user']);
DB::persist($note = Note::create(['gsactor_id' => $actor->getId(), 'content' => 'some content'])); DB::persist($note = Note::create(['actor_id' => $actor->getId(), 'content' => 'attachment: some content', 'content_type' => 'text/plain', 'is_local' => true]));
Conversation::assignLocalConversation($note, null);
DB::persist(AttachmentToNote::create(['attachment_id' => $attachment->getId(), 'note_id' => $note->getId(), 'title' => 'A title'])); DB::persist(AttachmentToNote::create(['attachment_id' => $attachment->getId(), 'note_id' => $note->getId(), 'title' => 'A title']));
DB::flush(); DB::flush();
@@ -110,14 +118,17 @@ class AttachmentTest extends GNUsocialTestCase
public function testGetUrl() public function testGetUrl()
{ {
static::bootKernel();
$attachment = DB::findBy('attachment', ['mimetype' => 'image/png'], limit: 1)[0]; $attachment = DB::findBy('attachment', ['mimetype' => 'image/png'], limit: 1)[0];
$id = $attachment->getId(); $id = $attachment->getId();
static::assertSame("/attachment/{$id}/view", $attachment->getUrl()); static::assertSame("/object/note/42/attachment/{$id}/view", $attachment->getUrl(note: 42, type: Router::ABSOLUTE_PATH));
} }
public function testMimetype() public function testMimetype()
{ {
$file = new \SplFileInfo(INSTALLDIR . '/tests/sample-uploads/image.jpg'); static::bootKernel();
$file = new SplFileInfo(INSTALLDIR . '/tests/sample-uploads/image.jpg');
$hash = null;
Event::handle('HashFile', [$file->getPathname(), &$hash]); Event::handle('HashFile', [$file->getPathname(), &$hash]);
$attachment = DB::findOneBy('attachment', ['filehash' => $hash]); $attachment = DB::findOneBy('attachment', ['filehash' => $hash]);

View File

@@ -0,0 +1,177 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Attachment\tests\Entity;
use App\Core\DB;
use App\Core\Event;
use App\Util\Exception\NotStoredLocallyException;
use App\Util\GNUsocialTestCase;
use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentThumbnail;
use Functional as F;
use Jchook\AssertThrows\AssertThrows;
use SplFileInfo;
class AttachmentThumbnailTest extends GNUsocialTestCase
{
use AssertThrows;
public function testAttachmentThumbnailLifecycle()
{
parent::bootKernel();
// Data fixture already loaded this file, but we need to get its hash to find it
$file = new SplFileInfo(INSTALLDIR . '/tests/sample-uploads/attachment-lifecycle-target.jpg');
$hash = null;
Event::handle('HashFile', [$file->getPathname(), &$hash]);
$attachment = DB::findOneBy(Attachment::class, ['filehash' => $hash]);
$expected = [
AttachmentThumbnail::getOrCreate($attachment, 'small', crop: false),
AttachmentThumbnail::getOrCreate($attachment, 'medium', crop: false),
$thumb = AttachmentThumbnail::getOrCreate($attachment, 'big', crop: false),
];
static::assertSame($attachment, $thumb->getAttachment());
$thumb->setAttachment(null);
static::assertSame($attachment, $thumb->getAttachment());
$actual = $attachment->getThumbnails();
static::assertSame(\count($expected), \count($actual));
foreach ($expected as $e) {
$a = array_shift($actual);
static::assertObjectEquals($e, $a);
}
array_pop($expected);
$thumb->delete();
$actual = $attachment->getThumbnails();
static::assertSame(\count($expected), \count($actual));
foreach ($expected as $e) {
$a = array_shift($actual);
static::assertObjectEquals($e, $a);
}
$attachment->deleteStorage();
foreach (array_reverse($attachment->getThumbnails()) as $t) {
// Since we still have thumbnails, those will be used as the new thumbnail, even though we don't have the original
$new = AttachmentThumbnail::getOrCreate($attachment, 'big', crop: false);
static::assertSame([$t->getFilename(), $t->getSize()], [$new->getFilename(), $new->getSize()]);
$t->delete();
}
// Since the backed storage was deleted and we don't have any more previous thumnbs, we can't generate another thumbnail
static::assertThrows(NotStoredLocallyException::class, fn () => AttachmentThumbnail::getOrCreate($attachment, 'big', crop: false));
$attachment->kill();
// static::assertThrows(NotStoredLocallyException::class, fn () => AttachmentThumbnail::getOrCreate($attachment, 'big', crop: false));
}
public function testInvalidThumbnail()
{
parent::bootKernel();
$file = new SplFileInfo(INSTALLDIR . '/tests/sample-uploads/spreadsheet.ods');
$hash = null;
Event::handle('HashFile', [$file->getPathname(), &$hash]);
$attachment = DB::findOneBy('attachment', ['filehash' => $hash]);
static::assertNull(AttachmentThumbnail::getOrCreate($attachment, 'small', crop: false));
}
public function testPredictScalingValues()
{
parent::bootKernel();
// TODO test with cropping
$inputs = [
[100, 100],
[400, 200],
[800, 400],
[1600, 800],
[1600, 1600],
// 16:9 video
[854, 480],
[1280, 720],
[1920, 1080],
[2560, 1440],
[3840, 2160],
];
$outputs = [
'small' => [
[100, 100],
[400, 200],
[32, 14],
[32, 14],
[32, 32],
// 16:9 video
[32, 21],
[32, 21],
[32, 21],
[32, 21],
[32, 21],
],
'medium' => [
[100, 100],
[400, 200],
[256, 116],
[256, 116],
[256, 256],
// 16:9 video
[256, 170],
[256, 170],
[256, 170],
[256, 170],
[256, 170],
],
'big' => [
[100, 100],
[400, 200],
[496, 225],
[496, 225],
[496, 496],
// 16:9 video
[496, 330],
[496, 330],
[496, 330],
[496, 330],
[496, 330],
],
];
foreach (['small', 'medium', 'big'] as $size) {
foreach (F\zip($inputs, $outputs[$size]) as [$existing, $results]) {
static::assertSame($results, AttachmentThumbnail::predictScalingValues(existing_width: $existing[0], existing_height: $existing[1], requested_size: $size, crop: false));
}
}
}
public function testGetUrl()
{
parent::bootKernel();
$attachment = DB::findBy(Attachment::class, ['mimetype' => 'image/png'], limit: 1)[0];
$thumb = AttachmentThumbnail::getOrCreate($attachment, 'big', crop: false);
$id = $attachment->getId();
$expected = "/object/note/42/attachment/{$id}/thumbnail/big";
static::assertSame($expected, $thumb->getUrl(note: 42));
}
}

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -20,104 +22,125 @@
namespace Component\Avatar; namespace Component\Avatar;
use App\Core\Cache; use App\Core\Cache;
use App\Core\DB\DB; use App\Core\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\GSFile; use App\Core\GSFile;
use App\Core\Modules\Component; use App\Core\Modules\Component;
use App\Core\Router\Router; use App\Core\Router;
use App\Util\Common; use App\Util\Common;
use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentThumbnail;
use Component\Avatar\Controller as C; use Component\Avatar\Controller as C;
use Component\Avatar\Exception\NoAvatarException; use Component\Avatar\Exception\NoAvatarException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
class Avatar extends Component class Avatar extends Component
{ {
public function onAddRoute($r): bool public function onInitializeComponent()
{ {
$r->connect('avatar', '/{gsactor_id<\d+>}/avatar/{size<full|big|medium|small>?full}', [Controller\Avatar::class, 'avatar_view']); }
$r->connect('settings_avatar', '/settings/avatar', [Controller\Avatar::class, 'settings_avatar']);
public function onAddRoute(Router $r): bool
{
$r->connect('avatar_actor', '/actor/{actor_id<\d+>}/avatar/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'avatar_view']);
$r->connect('avatar_default', '/avatar/default/{size<full|big|medium|small>?medium}', [Controller\Avatar::class, 'default_avatar_view']);
$r->connect('avatar_settings', '/settings/avatar', [Controller\Avatar::class, 'settings_avatar']);
return Event::next; return Event::next;
} }
public function onPopulateProfileSettingsTabs(Request $request, &$tabs): bool /**
* @throws \App\Util\Exception\ClientException
*/
public function onPopulateSettingsTabs(Request $request, string $section, &$tabs): bool
{ {
// TODO avatar template shouldn't be on settings folder if ($section === 'profile') {
$tabs[] = [ $tabs[] = [
'title' => 'Avatar', 'title' => 'Avatar',
'desc' => 'Change your avatar.', 'desc' => 'Change your avatar.',
'controller' => C\Avatar::settings_avatar($request), 'id' => 'settings-avatar',
]; 'controller' => C\Avatar::settings_avatar($request),
];
return Event::next;
}
public function onStartTwigPopulateVars(array &$vars): bool
{
if (Common::user() != null) {
$vars['user_avatar'] = self::getAvatarUrl();
} }
return Event::next; return Event::next;
} }
public function onGetAvatarUrl(int $gsactor_id, ?string &$url): bool public function onAvatarUpdate(int $actor_id): bool
{ {
$url = self::getAvatarUrl($gsactor_id); Cache::delete("avatar-{$actor_id}");
return Event::next; foreach (['full', 'big', 'medium', 'small'] as $size) {
} foreach ([Router::ABSOLUTE_PATH, Router::ABSOLUTE_URL] as $type) {
Cache::delete("avatar-url-{$actor_id}-{$size}-{$type}");
public function onAvatarUpdate(int $gsactor_id): bool }
{ Cache::delete("avatar-file-info-{$actor_id}-{$size}");
Cache::delete('avatar-' . $gsactor_id); }
Cache::delete('avatar-url-' . $gsactor_id);
Cache::delete('avatar-file-info-' . $gsactor_id);
return Event::next; return Event::next;
} }
// UTILS ---------------------------------- // UTILS ----------------------------------
/** /**
* Get the avatar associated with the given GSActor id * Get the avatar associated with the given Actor id
*/ */
public static function getAvatar(?int $gsactor_id = null): Entity\Avatar public static function getAvatar(?int $actor_id = null): Entity\Avatar
{ {
$gsactor_id = $gsactor_id ?: Common::userId(); $actor_id = $actor_id ?: Common::userId();
return GSFile::error(NoAvatarException::class, return GSFile::error(
$gsactor_id, NoAvatarException::class,
Cache::get("avatar-{$gsactor_id}", $actor_id,
function () use ($gsactor_id) { Cache::get(
return DB::dql('select a from Component\Avatar\Entity\Avatar a ' . "avatar-{$actor_id}",
'where a.gsactor_id = :gsactor_id', function () use ($actor_id) {
['gsactor_id' => $gsactor_id]); return DB::dql(
})); 'select a from Component\Avatar\Entity\Avatar a '
. 'where a.actor_id = :actor_id',
['actor_id' => $actor_id],
);
},
),
);
} }
/** /**
* Get the cached avatar associated with the given GSActor id, or the current user if not given * Get the cached avatar associated with the given Actor id, or the current user if not given
*/ */
public static function getAvatarUrl(?int $gsactor_id = null, string $size = 'full'): string public static function getUrl(int $actor_id, string $size = 'medium', int $type = Router::ABSOLUTE_PATH): string
{ {
$gsactor_id = $gsactor_id ?: Common::userId(); try {
return Cache::get("avatar-url-{$gsactor_id}", function () use ($gsactor_id) { return self::getAvatar($actor_id)->getUrl($size, $type);
return Router::url('avatar', ['gsactor_id' => $gsactor_id, 'size' => 'full']); } catch (NoAvatarException) {
}); return Router::url('avatar_default', ['size' => $size], $type);
}
}
public static function getDimensions(int $actor_id, string $size = 'medium')
{
try {
$attachment = self::getAvatar($actor_id)->getAttachment();
return ['width' => (int) $attachment->getWidth(), 'height' => (int) $attachment->getHeight()];
} catch (NoAvatarException) {
return ['width' => (int) (Common::config('thumbnail', 'small')), 'height' => (int) (Common::config('thumbnail', 'small'))];
}
} }
/** /**
* Get the cached avatar file info associated with the given GSActor id * Get the cached avatar file info associated with the given Actor id
* *
* Returns the avatar file's hash, mimetype, title and path. * Returns the avatar file's hash, mimetype, title and path.
* Ensures exactly one cached value exists * Ensures exactly one cached value exists
*/ */
public static function getAvatarFileInfo(int $gsactor_id): array public static function getAvatarFileInfo(int $actor_id, string $size = 'medium'): array
{ {
$res = Cache::get("avatar-file-info-{$gsactor_id}", $res = Cache::get(
function () use ($gsactor_id) { "avatar-file-info-{$actor_id}-{$size}",
return DB::dql('select f.id, f.filename, a.filename title, f.mimetype ' . function () use ($actor_id) {
'from App\Entity\Attachment f ' . return DB::dql(
'join Component\Avatar\Entity\Avatar a with f.id = a.attachment_id ' . 'select f.id, f.filename, a.title, f.mimetype '
'where a.gsactor_id = :gsactor_id', . 'from Component\Attachment\Entity\Attachment f '
['gsactor_id' => $gsactor_id]); . 'join Component\Avatar\Entity\Avatar a with f.id = a.attachment_id '
} . 'where a.actor_id = :actor_id',
['actor_id' => $actor_id],
);
},
); );
if ($res === []) { // Avatar not found if ($res === []) { // Avatar not found
$filepath = INSTALLDIR . '/public/assets/default-avatar.svg'; $filepath = INSTALLDIR . '/public/assets/default-avatar.svg';
@@ -129,8 +152,12 @@ class Avatar extends Component
'title' => 'default_avatar.svg', 'title' => 'default_avatar.svg',
]; ];
} else { } else {
$res = $res[0]; // A user must always only have one avatar. $res = $res[0]; // A user must always only have one avatar.
$res['filepath'] = DB::findOneBy('attachment', ['id' => $res['id']])->getPath(); if ($size === 'full') {
$res['filepath'] = Attachment::getByPK(['id' => $res['id']])->getPath();
} else {
$res['filepath'] = AttachmentThumbnail::getOrCreate(Attachment::getByPK(['id' => $res['id']]), $size)->getPath();
}
return $res; return $res;
} }
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
@@ -22,7 +24,7 @@
namespace Component\Avatar\Controller; namespace Component\Avatar\Controller;
use App\Core\Controller; use App\Core\Controller;
use App\Core\DB\DB; use App\Core\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Form; use App\Core\Form;
use App\Core\GSFile; use App\Core\GSFile;
@@ -31,7 +33,6 @@ use function App\Core\I18n\_m;
use App\Core\Log; use App\Core\Log;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException; use App\Util\Exception\ClientException;
use App\Util\Exception\NotFoundException;
use App\Util\TemporaryFile; use App\Util\TemporaryFile;
use Component\Avatar\Entity\Avatar as AvatarEntity; use Component\Avatar\Entity\Avatar as AvatarEntity;
use Exception; use Exception;
@@ -45,18 +46,18 @@ use Symfony\Component\HttpFoundation\Response;
class Avatar extends Controller class Avatar extends Controller
{ {
public function default_avatar_view(Request $request, string $size): Response
{
return $this->avatar_view($request, 0, $size);
}
/** /**
* @throws Exception * @throws Exception
*/ */
public function avatar_view(Request $request, int $gsactor_id, string $size): Response public function avatar_view(Request $request, int $actor_id, string $size): Response
{ {
switch ($size) { $res = \Component\Avatar\Avatar::getAvatarFileInfo($actor_id, $size);
case 'full': return M::sendFile($res['filepath'], $res['mimetype'], $res['title']);
$res = \Component\Avatar\Avatar::getAvatarFileInfo($gsactor_id);
return M::sendFile($res['filepath'], $res['mimetype'], $res['title']);
default:
throw new Exception('Not implemented');
}
} }
/** /**
@@ -74,52 +75,54 @@ class Avatar extends Controller
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData(); $data = $form->getData();
$user = Common::user(); $user = Common::user();
$gsactor_id = $user->getId(); $actor_id = $user->getId();
if ($data['remove'] == true) { if ($data['remove'] == true) {
try { if (\is_null($avatar = DB::findOneBy(AvatarEntity::class, ['actor_id' => $actor_id], return_null: true))) {
$avatar = DB::findOneBy('avatar', ['gsactor_id' => $gsactor_id]); $form->addError(new FormError(_m('No avatar set, so cannot delete.')));
} else {
$avatar->delete(); $avatar->delete();
Event::handle('AvatarUpdate', [$user->getId()]);
} catch (NotFoundException) {
$form->addError(new FormError(_m('No avatar set, so cannot delete')));
} }
} else { } else {
$attachment = null;
$title = $data['avatar']?->getClientOriginalName() ?? null;
if (isset($data['hidden'])) { if (isset($data['hidden'])) {
// Cropped client side // Cropped client side
$matches = []; $matches = [];
if (!empty(preg_match('/data:([^;]*)(;(base64))?,(.*)/', $data['hidden'], $matches))) { if (!empty(preg_match('/data:([^;]*)(;(base64))?,(.*)/', $data['hidden'], $matches))) {
list(, , , $encoding_user, $data_user) = $matches; [, , , $encoding_user, $data_user] = $matches;
if ($encoding_user === 'base64') { if ($encoding_user === 'base64') {
$data_user = base64_decode($data_user); $data_user = base64_decode($data_user);
$tempfile = new TemporaryFile(['prefix' => 'gs-avatar']); $tempfile = new TemporaryFile(['prefix' => 'gs-avatar']);
$tempfile->write($data_user); $tempfile->write($data_user);
$attachment = GSFile::storeFileAsAttachment($tempfile);
} else { } else {
Log::info('Avatar upload got an invalid encoding, something\'s fishy and/or wrong'); Log::info('Avatar upload got an invalid encoding, something\'s fishy and/or wrong');
} }
} }
} elseif (isset($data['avatar'])) { } elseif (isset($data['avatar'])) {
// Cropping failed (e.g. disabled js), use file as uploaded // Cropping failed (e.g. disabled js), use file as uploaded
$file = $data['avatar']; $file = $data['avatar'];
$attachment = GSFile::storeFileAsAttachment($file);
} else { } else {
throw new ClientException('Invalid form'); throw new ClientException(_m('Invalid form.'));
} }
$attachment = GSFile::sanitizeAndStoreFileAsAttachment(
$file
);
// Delete current avatar if there's one // Delete current avatar if there's one
$avatar = DB::find('avatar', ['gsactor_id' => $gsactor_id]); if (!\is_null($avatar = DB::findOneBy(AvatarEntity::class, ['actor_id' => $actor_id], return_null: true))) {
$avatar?->delete(); $avatar->delete();
}
DB::persist($attachment); DB::persist($attachment);
// Can only get new id after inserting DB::persist(AvatarEntity::create([
DB::flush(); 'actor_id' => $actor_id,
DB::persist(AvatarEntity::create(['gsactor_id' => $gsactor_id, 'attachment_id' => $attachment->getId(), 'filename' => $file->getClientOriginalName()])); 'attachment_id' => $attachment->getId(),
'title' => $title,
]));
DB::flush(); DB::flush();
Event::handle('AvatarUpdate', [$user->getId()]); Event::handle('AvatarUpdate', [$user->getId()]);
} }
} }
return ['_template' => 'settings/avatar.html.twig', 'avatar' => $form->createView()]; return ['_template' => 'avatar/settings.html.twig', 'avatar' => $form->createView()];
} }
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
@@ -21,11 +23,14 @@
namespace Component\Avatar\Entity; namespace Component\Avatar\Entity;
use App\Core\DB\DB; use App\Core\Cache;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Router\Router; use App\Core\Event;
use App\Entity\Attachment; use App\Core\Router;
use App\Util\Common; use App\Util\Common;
use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentThumbnail;
use DateTimeInterface; use DateTimeInterface;
/** /**
@@ -46,21 +51,21 @@ class Avatar extends Entity
{ {
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $gsactor_id; private int $actor_id;
private int $attachment_id; private int $attachment_id;
private ?string $filename; private ?string $title = null;
private DateTimeInterface $created; private DateTimeInterface $created;
private DateTimeInterface $modified; private DateTimeInterface $modified;
public function setGSActorId(int $gsactor_id): self public function setActorId(int $actor_id): self
{ {
$this->gsactor_id = $gsactor_id; $this->actor_id = $actor_id;
return $this; return $this;
} }
public function getGSActorId(): int public function getActorId(): int
{ {
return $this->gsactor_id; return $this->actor_id;
} }
public function setAttachmentId(int $attachment_id): self public function setAttachmentId(int $attachment_id): self
@@ -74,20 +79,15 @@ class Avatar extends Entity
return $this->attachment_id; return $this->attachment_id;
} }
/** public function setTitle(?string $title): self
* @return null|string
*/
public function getFilename(): ?string
{ {
return $this->filename; $this->title = \is_null($title) ? null : mb_substr($title, 0, 191);
return $this;
} }
/** public function getTitle(): ?string
* @param null|string $filename
*/
public function setFilename(?string $filename): void
{ {
$this->filename = $filename; return $this->title;
} }
public function setCreated(DateTimeInterface $created): self public function setCreated(DateTimeInterface $created): self
@@ -117,17 +117,23 @@ class Avatar extends Entity
private ?Attachment $attachment = null; private ?Attachment $attachment = null;
public function getUrl(): string public function getUrl(string $size = 'medium', int $type = Router::ABSOLUTE_PATH): string
{ {
return Router::url('avatar', ['gsactor_id' => $this->gsactor_id]); $actor_id = $this->getActorId();
return Cache::get("avatar-url-{$actor_id}-{$size}-{$type}", fn () => Router::url('avatar_actor', ['actor_id' => $actor_id, 'size' => $size], $type));
} }
public function getAttachment(): Attachment public function getAttachment(): Attachment
{ {
$this->attachment = $this->attachment ?: DB::findOneBy('attachment', ['id' => $this->attachment_id]); $this->attachment ??= DB::findOneBy('attachment', ['id' => $this->getAttachmentId()]);
return $this->attachment; return $this->attachment;
} }
public function getAttachmentThumbnail(string $size): ?AttachmentThumbnail
{
return AttachmentThumbnail::getOrCreate($this->getAttachment(), $size);
}
public static function getFilePathStatic(string $filename): string public static function getFilePathStatic(string $filename): string
{ {
return Common::config('avatar', 'dir') . $filename; return Common::config('avatar', 'dir') . $filename;
@@ -140,15 +146,14 @@ class Avatar extends Entity
/** /**
* Delete this avatar and kill corresponding attachment * Delete this avatar and kill corresponding attachment
*
* @return bool
*/ */
public function delete(): bool public function delete(): bool
{ {
DB::remove($this); $actor_id = $this->getActorId();
$attachment = $this->getAttachment(); $attachment = $this->getAttachment();
DB::wrapInTransaction(fn () => DB::removeBy(static::class, ['actor_id' => $actor_id]));
$attachment->kill(); $attachment->kill();
DB::flush(); Event::handle('AvatarUpdate', [$actor_id]);
return true; return true;
} }
@@ -157,13 +162,13 @@ class Avatar extends Entity
return [ return [
'name' => 'avatar', 'name' => 'avatar',
'fields' => [ 'fields' => [
'gsactor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'GSActor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'foreign key to gsactor table'], 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'foreign key to actor table'],
'attachment_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Attachment.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'foreign key to attachment table'], 'attachment_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Attachment.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'foreign key to attachment table'],
'filename' => ['type' => 'varchar', 'length' => 191, 'description' => 'file name of resource when available'], 'title' => ['type' => 'varchar', 'length' => 191, 'description' => 'file name of resource when available'],
'created' => ['type' => 'datetime', 'not null' => true, 'description' => 'date this record was created', 'default' => 'CURRENT_TIMESTAMP'], 'created' => ['type' => 'datetime', 'not null' => true, 'description' => 'date this record was created', 'default' => 'CURRENT_TIMESTAMP'],
'modified' => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified', 'default' => 'CURRENT_TIMESTAMP'], 'modified' => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified', 'default' => 'CURRENT_TIMESTAMP'],
], ],
'primary key' => ['gsactor_id'], 'primary key' => ['actor_id'],
'indexes' => [ 'indexes' => [
'avatar_attachment_id_idx' => ['attachment_id'], 'avatar_attachment_id_idx' => ['attachment_id'],
], ],

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social

View File

@@ -0,0 +1,3 @@
parameters:
avatar:
use_avatar: true

View File

@@ -1,5 +1,4 @@
<div class='form'> <div class='form'>
<link href="{{ asset('assets/css/cropperjs/cropper.css') }}" rel="stylesheet">
<script type="text/javascript" src="{{ asset('assets/javascript/cropping.js') }}"></script> <script type="text/javascript" src="{{ asset('assets/javascript/cropping.js') }}"></script>
{{ form(avatar) }} {{ form(avatar) }}

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -42,14 +44,14 @@ class ForeignLink
private int $user_id; private int $user_id;
private int $foreign_id; private int $foreign_id;
private int $service; private int $service;
private ?string $credentials; private ?string $credentials = null;
private int $noticesync = 1; private int $noticesync = 1;
private int $friendsync = 2; private int $friendsync = 2;
private int $profilesync = 1; private int $profilesync = 1;
private ?\DateTimeInterface $last_noticesync; private ?DateTimeInterface $last_noticesync = null;
private ?\DateTimeInterface $last_friendsync; private ?DateTimeInterface $last_friendsync = null;
private \DateTimeInterface $created; private DateTimeInterface $created;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setUserId(int $user_id): self public function setUserId(int $user_id): self
{ {
@@ -86,7 +88,7 @@ class ForeignLink
public function setCredentials(?string $credentials): self public function setCredentials(?string $credentials): self
{ {
$this->credentials = $credentials; $this->credentials = \is_null($credentials) ? null : mb_substr($credentials, 0, 191);
return $this; return $this;
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -41,9 +43,9 @@ class ForeignService
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $id; private int $id;
private string $name; private string $name;
private ?string $description; private ?string $description = null;
private \DateTimeInterface $created; private DateTimeInterface $created;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setId(int $id): self public function setId(int $id): self
{ {
@@ -58,7 +60,7 @@ class ForeignService
public function setName(string $name): self public function setName(string $name): self
{ {
$this->name = $name; $this->name = mb_substr($name, 0, 32);
return $this; return $this;
} }
@@ -69,7 +71,7 @@ class ForeignService
public function setDescription(?string $description): self public function setDescription(?string $description): self
{ {
$this->description = $description; $this->description = \is_null($description) ? null : mb_substr($description, 0, 191);
return $this; return $this;
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -42,7 +44,7 @@ class ForeignSubscription
private int $service; private int $service;
private int $subscriber; private int $subscriber;
private int $subscribed; private int $subscribed;
private \DateTimeInterface $created; private DateTimeInterface $created;
public function setService(int $service): self public function setService(int $service): self
{ {

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -42,9 +44,9 @@ class ForeignUser
private int $id; private int $id;
private int $service; private int $service;
private string $uri; private string $uri;
private ?string $nickname; private ?string $nickname = null;
private \DateTimeInterface $created; private DateTimeInterface $created;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setId(int $id): self public function setId(int $id): self
{ {
@@ -70,7 +72,7 @@ class ForeignUser
public function setUri(string $uri): self public function setUri(string $uri): self
{ {
$this->uri = $uri; $this->uri = mb_substr($uri, 0, 191);
return $this; return $this;
} }
@@ -81,7 +83,7 @@ class ForeignUser
public function setNickname(?string $nickname): self public function setNickname(?string $nickname): self
{ {
$this->nickname = $nickname; $this->nickname = \is_null($nickname) ? null : mb_substr($nickname, 0, 191);
return $this; return $this;
} }

View File

@@ -0,0 +1,232 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Circle;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Event;
use function App\Core\I18n\_m;
use App\Core\Modules\Component;
use App\Core\Router;
use App\Entity\Actor;
use App\Entity\Feed;
use App\Entity\LocalUser;
use App\Util\Nickname;
use Component\Circle\Controller as CircleController;
use Component\Circle\Entity\ActorCircle;
use Component\Circle\Entity\ActorCircleSubscription;
use Component\Circle\Entity\ActorTag;
use Component\Collection\Util\MetaCollectionTrait;
use Component\Tag\Tag;
use Functional as F;
use Symfony\Component\HttpFoundation\Request;
/**
* Component responsible for handling and representing ActorCircles and ActorTags
*
* @author Hugo Sales <hugo@hsal.es>
* @author Phablulo <phablulo@gmail.com>
* @author Diogo Peralta Cordeiro <@diogo.site>
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class Circle extends Component
{
use MetaCollectionTrait;
public const TAG_CIRCLE_REGEX = '/' . Nickname::BEFORE_MENTIONS . '@#([\pL\pN_\-\.]{1,64})/';
protected const SLUG = 'circle';
protected const PLURAL_SLUG = 'circles';
public function onAddRoute(Router $r): bool
{
$r->connect('actor_circle_view_by_circle_id', '/circle/{circle_id<\d+>}', [CircleController\Circle::class, 'circleById']);
// View circle members by (tagger id or nickname) and tag
$r->connect('actor_circle_view_by_circle_tagger_tag', '/circle/actor/{tagger_id<\d+>/{tag<' . Tag::TAG_SLUG_REGEX . '>}}', [CircleController\Circle::class, 'circleByTaggerIdAndTag']);
$r->connect('actor_circle_view_by_circle_tagger_tag', '/circle/@{nickname<' . Nickname::DISPLAY_FMT . '>}/{tag<' . Tag::TAG_SLUG_REGEX . '>}', [CircleController\Circle::class, 'circleByTaggerNicknameAndTag']);
// View all circles by actor id or nickname
$r->connect(
id: 'actor_circles_view_by_actor_id',
uri_path: '/actor/{tag<' . Tag::TAG_SLUG_REGEX . '>}/circles',
target: [CircleController\Circles::class, 'collectionsViewByActorId'],
);
$r->connect(
id: 'actor_circles_view_by_nickname',
uri_path: '/@{nickname<' . Nickname::DISPLAY_FMT . '>}/circles',
target: [CircleController\Circles::class, 'collectionsViewByActorNickname'],
);
$r->connect('actor_circle_view_feed_by_circle_id', '/circle/{circle_id<\d+>}/feed', [CircleController\Circles::class, 'feedByCircleId']);
// View circle feed by (tagger id or nickname) and tag
$r->connect('actor_circle_view_feed_by_circle_tagger_tag', '/circle/actor/{tagger_id<\d+>/{tag<' . Tag::TAG_SLUG_REGEX . '>}}/feed', [CircleController\Circles::class, 'feedByTaggerIdAndTag']);
$r->connect('actor_circle_view_feed_by_circle_tagger_tag', '/circle/@{nickname<' . Nickname::DISPLAY_FMT . '>}/{tag<' . Tag::TAG_SLUG_REGEX . '>}/feed', [CircleController\Circles::class, 'feedByTaggerNicknameAndTag']);
return Event::next;
}
public static function cacheKeys(string $tag_single_or_multi): array
{
return [
'actor_single' => "actor-tag-feed-{$tag_single_or_multi}",
'actor_multi' => "actor-tags-feed-{$tag_single_or_multi}",
];
}
public function onPopulateSettingsTabs(Request $request, string $section, array &$tabs): bool
{
if ($section === 'profile' && \in_array($request->get('_route'), ['person_actor_settings', 'group_actor_settings'])) {
$tabs[] = [
'title' => _m('Self tags'),
'desc' => _m('Add or remove tags to this actor'),
'id' => 'settings-self-tags',
'controller' => CircleController\SelfTagsSettings::settingsSelfTags($request, Actor::getById((int) $request->get('id')), 'settings-self-tags-details'),
];
}
return Event::next;
}
public function onPostingFillTargetChoices(Request $request, Actor $actor, array &$targets): bool
{
$circles = $actor->getCircles();
foreach ($circles as $circle) {
$tag = $circle->getTag();
$targets["#{$tag}"] = $tag;
}
return Event::next;
}
// Meta Collection -------------------------------------------------------------------
private function getActorIdFromVars(array $vars): int
{
$id = $vars['request']->get('id', null);
if ($id) {
return (int) $id;
}
$nick = $vars['request']->get('nickname');
$user = LocalUser::getByNickname($nick);
return $user->getId();
}
public static function createCircle(Actor|int $tagger_id, string $tag): int
{
$tagger_id = \is_int($tagger_id) ? $tagger_id : $tagger_id->getId();
$circle = ActorCircle::create([
'tagger' => $tagger_id,
'tag' => $tag,
'description' => null, // TODO
'private' => false, // TODO
]);
DB::persist($circle);
Cache::delete(Actor::cacheKeys($tagger_id)['circles']);
return $circle->getId();
}
protected function createCollection(Actor $owner, array $vars, string $name)
{
$this->createCircle($owner, $name);
DB::persist(ActorTag::create([
'tagger' => $owner->getId(),
'tagged' => self::getActorIdFromVars($vars),
'tag' => $name,
]));
}
protected function removeItem(Actor $owner, array $vars, $items, array $collections)
{
$tagger_id = $owner->getId();
$tagged_id = $this->getActorIdFromVars($vars);
$circles_to_remove_tagged_from = DB::findBy(ActorCircle::class, ['id' => $items]);
foreach ($circles_to_remove_tagged_from as $circle) {
DB::removeBy(ActorCircleSubscription::class, ['actor_id' => $tagged_id, 'circle_id' => $circle->getId()]);
}
$tags = F\map($circles_to_remove_tagged_from, fn ($x) => $x->getTag());
foreach ($tags as $tag) {
DB::removeBy(ActorTag::class, ['tagger' => $tagger_id, 'tagged' => $tagged_id, 'tag' => $tag]);
}
Cache::delete(Actor::cacheKeys($tagger_id)['circles']);
}
protected function addItem(Actor $owner, array $vars, $items, array $collections)
{
$tagger_id = $owner->getId();
$tagged_id = $this->getActorIdFromVars($vars);
$circles_to_add_tagged_to = DB::findBy(ActorCircle::class, ['id' => $items]);
foreach ($circles_to_add_tagged_to as $circle) {
DB::persist(ActorCircleSubscription::create(['actor_id' => $tagged_id, 'circle_id' => $circle->getId()]));
}
$tags = F\map($circles_to_add_tagged_to, fn ($x) => $x->getTag());
foreach ($tags as $tag) {
DB::persist(ActorTag::create(['tagger' => $tagger_id, 'tagged' => $tagged_id, 'tag' => $tag]));
}
Cache::delete(Actor::cacheKeys($tagger_id)['circles']);
}
/**
* @see MetaCollectionPlugin->shouldAddToRightPanel
*/
protected function shouldAddToRightPanel(Actor $user, $vars, Request $request): bool
{
return \in_array($vars['path'], ['actor_view_nickname', 'actor_view_id']);
}
/**
* Retrieves an array of Collections owned by an Actor.
* In this case, Collections of those within Actor's own circle of Actors, aka ActorCircle.
*
* Differs from the overwritten method in MetaCollectionsTrait, since retrieved Collections come from the $owner
* itself, and from every Actor that is a part of its ActorCircle.
*
* @param Actor $owner the Actor, and by extension its own circle of Actors
* @param null|array $vars Page vars sent by AppendRightPanelBlock event
* @param bool $ids_only true if only the Collections ids are to be returned
*/
protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array
{
$tagged_id = !\is_null($vars) ? $this->getActorIdFromVars($vars) : null;
$circles = \is_null($tagged_id) ? $owner->getCircles() : F\select($owner->getCircles(), function ($x) use ($tagged_id) {
foreach ($x->getActorTags() as $at) {
if ($at->getTagged() === $tagged_id) {
return true;
}
}
return false;
});
return $ids_only ? array_map(fn ($x) => $x->getId(), $circles) : $circles;
}
public function onCreateDefaultFeeds(int $actor_id, LocalUser $user, int &$ordering)
{
DB::persist(Feed::create([
'actor_id' => $actor_id,
'url' => Router::url($route = 'actor_circles_view_by_nickname', ['nickname' => $user->getNickname()]),
'route' => $route,
'title' => _m('Circles'),
'ordering' => $ordering++,
]));
return Event::next;
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Circle\Controller;
use function App\Core\I18n\_m;
use App\Entity\LocalUser;
use App\Util\Exception\ClientException;
use Component\Circle\Entity\ActorCircle;
use Component\Collection\Util\Controller\CircleController;
class Circle extends CircleController
{
/**
* Render an existing ActorCircle with the given id as a Collection of Actors
*
* @param ActorCircle|int $circle_id the desired ActorCircle id
*
* @throws \App\Util\Exception\ServerException
* @throws ClientException
*/
public function circleById(int|ActorCircle $circle_id): array
{
$circle = \is_int($circle_id) ? ActorCircle::getByPK(['id' => $circle_id]) : $circle_id;
unset($circle_id);
if (\is_null($circle)) {
throw new ClientException(_m('No such circle.'), 404);
} else {
return [
'_template' => 'collection/actors.html.twig',
'title' => _m('Circle'),
'empty_message' => _m('No members.'),
'sort_form_fields' => [],
'page' => $this->int('page') ?? 1,
'actors' => $circle->getTaggedActors(),
];
}
}
public function circleByTaggerIdAndTag(int $tagger_id, string $tag): array
{
return $this->circleById(ActorCircle::getByPK(['tagger' => $tagger_id, 'tag' => $tag]));
}
public function circleByTaggerNicknameAndTag(string $tagger_nickname, string $tag): array
{
return $this->circleById(ActorCircle::getByPK(['tagger' => LocalUser::getByNickname($tagger_nickname)->getId(), 'tag' => $tag]));
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Circle\Controller;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Router;
use App\Entity\Actor;
use App\Entity\LocalUser;
use Component\Circle\Entity\ActorCircle;
use Component\Collection\Util\Controller\MetaCollectionController;
class Circles extends MetaCollectionController
{
protected const SLUG = 'circle';
protected const PLURAL_SLUG = 'circles';
protected string $page_title = 'Actor circles';
public function createCollection(int $owner_id, string $name)
{
return \Component\Circle\Circle::createCircle($owner_id, $name);
}
public function getCollectionUrl(int $owner_id, ?string $owner_nickname, int $collection_id): string
{
return Router::url(
'actor_circle_view_by_circle_id',
['circle_id' => $collection_id],
);
}
public function getCollectionItems(int $owner_id, $collection_id): array
{
$notes = []; // TODO: Use Feed::query
return [
'_template' => 'collection/notes.html.twig',
'notes' => $notes,
];
}
public function feedByCircleId(int $circle_id)
{
// Owner id isn't used
return $this->getCollectionItems(0, $circle_id);
}
public function feedByTaggerIdAndTag(int $tagger_id, string $tag)
{
// Owner id isn't used
$circle_id = ActorCircle::getByPK(['tagger' => $tagger_id, 'tag' => $tag])->getId();
return $this->getCollectionItems($tagger_id, $circle_id);
}
public function feedByTaggerNicknameAndTag(string $tagger_nickname, string $tag)
{
$tagger_id = LocalUser::getByNickname($tagger_nickname)->getId();
$circle_id = ActorCircle::getByPK(['tagger' => $tagger_id, 'tag' => $tag])->getId();
return $this->getCollectionItems($tagger_id, $circle_id);
}
public function getCollectionsByActorId(int $owner_id): array
{
return DB::findBy(ActorCircle::class, ['tagger' => $owner_id], order_by: ['id' => 'desc']);
}
public function getCollectionBy(int $owner_id, int $collection_id): ActorCircle
{
return DB::findOneBy(ActorCircle::class, ['id' => $collection_id, 'actor_id' => $owner_id]);
}
public function setCollectionName(int $actor_id, string $actor_nickname, ActorCircle $collection, string $name)
{
foreach ($collection->getActorTags(db_reference: true) as $at) {
$at->setTag($name);
}
$collection->setTag($name);
Cache::delete(Actor::cacheKeys($actor_id)['circles']);
}
public function removeCollection(int $actor_id, string $actor_nickname, ActorCircle $collection)
{
foreach ($collection->getActorTags(db_reference: true) as $at) {
DB::remove($at);
}
DB::remove($collection);
Cache::delete(Actor::cacheKeys($actor_id)['circles']);
}
}

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types = 1);
namespace Component\Circle\Controller;
use App\Core\Cache;
use App\Core\Controller;
use App\Core\DB;
use function App\Core\I18n\_m;
use App\Entity as E;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\RedirectException;
use Component\Circle\Entity\ActorCircle;
use Component\Circle\Entity\ActorTag;
use Component\Circle\Form\SelfTagsForm;
use Component\Tag\Tag as CompTag;
use Symfony\Component\Form\SubmitButton;
use Symfony\Component\HttpFoundation\Request;
class SelfTagsSettings extends Controller
{
/**
* Generic settings page for an Actor's self tags
* TODO: We should have $actor->setSelfTags(), $actor->addSelfTags(), $actor->removeSelfTags()
*/
public static function settingsSelfTags(Request $request, E\Actor $target, string $details_id)
{
$actor = Common::actor();
if (!$actor->canModerate($target)) {
throw new ClientException(_m('You don\'t have enough permissions to edit {nickname}\'s settings', ['{nickname}' => $target->getNickname()]));
}
$actor_self_tags = $target->getSelfTags();
[$add_form, $existing_form] = SelfTagsForm::handleTags(
$request,
$actor_self_tags,
handle_new: /**
* Handle adding tags
*/
function ($form) use ($request, $target, $details_id) {
$data = $form->getData();
$tags = $data['new-tags'];
foreach ($tags as $tag) {
$tag = CompTag::sanitize($tag);
[$actor_tag, $actor_tag_existed] = ActorTag::checkExistingAndCreateOrUpdate([
'tagger' => $target->getId(), // self tag means tagger = tagger in ActorTag
'tagged' => $target->getId(),
'tag' => $tag,
]);
if (!$actor_tag_existed) {
DB::persist($actor_tag);
// Try to find the self-tag circle
$actor_circle = DB::findOneBy(
ActorCircle::class,
[
'tagger' => null, // Self-tag circle
'tag' => $tag,
],
return_null: true,
);
// It is the first time someone uses this self-tag!
if (\is_null($actor_circle)) {
DB::persist(ActorCircle::create([
'tagger' => null, // Self-tag circle
'tag' => $tag,
'private' => false, // by definition
'description' => null, // The controller can show this in every language as appropriate
]));
}
}
}
DB::flush();
Cache::delete(E\Actor::cacheKeys($target->getId())['self-tags']);
throw new RedirectException($request->get('_route'), ['nickname' => $target->getNickname(), 'open' => $details_id, '_fragment' => $details_id]);
},
handle_existing: /**
* Handle changes to the existing tags
*/
function ($form, array $form_definition) use ($request, $target, $details_id) {
$changed = false;
foreach (array_chunk($form_definition, 2) as $entry) {
$tag = CompTag::sanitize($entry[0][2]['data']);
/** @var SubmitButton $remove */
$remove = $form->get($entry[1][0]);
if ($remove->isClicked()) {
$changed = true;
DB::removeBy(
'actor_tag',
[
'tagger' => $target->getId(),
'tagged' => $target->getId(),
'tag' => $tag,
],
);
// We intentionally leave the self-tag actor circle, even if it is now empty
}
}
if ($changed) {
DB::flush();
Cache::delete(E\Actor::cacheKeys($target->getId())['self-tags']);
throw new RedirectException($request->get('_route'), ['nickname' => $target->getNickname(), 'open' => $details_id, '_fragment' => $details_id]);
}
},
remove_label: _m('Remove self tag'),
add_label: _m('Add self tag'),
);
return [
'_template' => 'self_tags_settings.fragment.html.twig',
'add_self_tags_form' => $add_form->createView(),
'existing_self_tags_form' => $existing_form?->createView(),
];
}
}

View File

@@ -0,0 +1,221 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Circle\Entity;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Entity;
use App\Core\Router;
use DateTimeInterface;
/**
* Entity for List of actors
* This entity only makes sense when considered together with the ActorTag one.
* Because, every circle entry will be an ActorTag.
*
* @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@hsal.es>
* @author Diogo Peralta Cordeiro <@diogo.site>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class ActorCircle extends Entity
{
// {{{ Autocode
// @codeCoverageIgnoreStart
private int $id;
private ?int $tagger = null;
private string $tag;
private ?string $description = null;
private ?bool $private = false;
private DateTimeInterface $created;
private DateTimeInterface $modified;
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getId(): int
{
return $this->id;
}
public function setTagger(?int $tagger): self
{
$this->tagger = $tagger;
return $this;
}
public function getTagger(): ?int
{
return $this->tagger;
}
public function setTag(string $tag): self
{
$this->tag = mb_substr($tag, 0, 64);
return $this;
}
public function getTag(): string
{
return $this->tag;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setPrivate(?bool $private): self
{
$this->private = $private;
return $this;
}
public function getPrivate(): ?bool
{
return $this->private;
}
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;
}
// @codeCoverageIgnoreEnd
// }}} Autocode
/**
* For use with MetaCollection trait only
*/
public function getName(): string
{
return $this->tag;
}
public function getActorTags(bool $db_reference = false): array
{
$handle = fn () => DB::findBy('actor_tag', ['tagger' => $this->getTagger(), 'tag' => $this->getTag()]);
if ($db_reference) {
return $handle();
}
return Cache::get(
"circle-{$this->getId()}-tagged",
$handle,
);
}
public function getTaggedActors()
{
return Cache::get(
"circle-{$this->getId()}-tagged-actors",
function () {
if ($this->getTagger()) {
return DB::dql('SELECT a FROM actor AS a JOIN actor_tag AS at WITH at.tagged = a.id WHERE at.tag = :tag AND at.tagger = :tagger', ['tag' => $this->getTag(), 'tagger' => $this->getTagger()]);
} else { // Self-tag
return DB::dql('SELECT a FROM actor AS a JOIN actor_tag AS at WITH at.tagged = a.id WHERE at.tag = :tag AND at.tagger = at.tagged', ['tag' => $this->getTag()]);
}
},
);
}
public function getSubscribedActors(?int $offset = null, ?int $limit = null): array
{
return Cache::get(
"circle-{$this->getId()}-subscribers",
fn () => DB::dql(
<<< 'EOQ'
SELECT a
FROM actor a
JOIN actor_circle_subscription s
WITH a.id = s.actor_id
ORDER BY s.created DESC, a.id DESC
EOQ,
options: [
'offset' => $offset,
'limit' => $limit,
],
),
);
}
public function getUrl(int $type = Router::ABSOLUTE_PATH): string
{
return Router::url('actor_circle_view_by_circle_id', ['circle_id' => $this->getId()], type: $type);
}
public static function schemaDef(): array
{
return [
'name' => 'actor_circle',
'description' => 'An actor can have lists of actors, to separate their feed or quickly mention his friend',
'fields' => [
'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], // An actor can be tagged by many actors
'tagger' => ['type' => 'int', 'default' => null, 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'many to one', 'name' => 'actor_list_tagger_fkey', 'description' => 'user making the tag, null if self-tag. If null, is the special global self-tag circle'],
'tag' => ['type' => 'varchar', 'length' => 64, 'foreign key' => true, 'target' => 'ActorTag.tag', 'multiplicity' => 'many to one', 'not null' => true, 'description' => 'actor tag'], // Join with ActorTag // // so, Doctrine doesn't like that the target is not unique, even though the pair is // Many Actor Circles can reference (and probably will) an Actor Tag
'description' => ['type' => 'text', 'description' => 'description of the people tag'],
'private' => ['type' => 'bool', 'default' => false, 'description' => 'is this tag private'],
'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' => ['id'], // But we will mostly refer to them with `tagger` and `tag`
'indexes' => [
'actor_list_modified_idx' => ['modified'],
'actor_list_tagger_tag_idx' => ['tagger', 'tag'], // The actual identifier we will use the most
'actor_list_tag_idx' => ['tag'],
'actor_list_tagger_idx' => ['tagger'],
],
];
}
}

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -17,13 +19,15 @@
// along with GNU social. If not, see <http://www.gnu.org/licenses/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Entity; namespace Component\Circle\Entity;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use DateTimeInterface;
/** /**
* Entity for subscription * Entity for actor circle subscriptions
* This entity only makes sense when considered with the ActorCircle entity.
* Because you can only subscribe a Circle that exists.
* *
* @category DB * @category DB
* @package GNUsocial * @package GNUsocial
@@ -33,38 +37,39 @@ use DateTimeInterface;
* @author Mikael Nordfeldth <mmn@hethane.se> * @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
* @author Hugo Sales <hugo@hsal.es> * @author Hugo Sales <hugo@hsal.es>
* @author Diogo Peralta Cordeiro <@diogo.site>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class Follow extends Entity class ActorCircleSubscription extends Entity
{ {
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $follower; private int $actor_id;
private int $followed; private int $circle_id;
private \DateTimeInterface $created; private DateTimeInterface $created;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setFollower(int $follower): self public function setActorId(int $actor_id): self
{ {
$this->follower = $follower; $this->actor_id = $actor_id;
return $this; return $this;
} }
public function getFollower(): int public function getActorId(): int
{ {
return $this->follower; return $this->actor_id;
} }
public function setFollowed(int $followed): self public function setCircleId(int $circle_id): self
{ {
$this->followed = $followed; $this->circle_id = $circle_id;
return $this; return $this;
} }
public function getFollowed(): int public function getCircleId(): int
{ {
return $this->followed; return $this->circle_id;
} }
public function setCreated(DateTimeInterface $created): self public function setCreated(DateTimeInterface $created): self
@@ -95,17 +100,18 @@ class Follow extends Entity
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [
'name' => 'follow', 'name' => 'actor_circle_subscription',
'fields' => [ 'fields' => [
'follower' => ['type' => 'int', 'foreign key' => true, 'target' => 'GSActor.id', 'multiplicity' => 'one to one', 'name' => 'follow_follower_fkey', 'not null' => true, 'description' => 'gsactor listening'], 'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'actor_circle_subscription_actor_id_fkey', 'not null' => true, 'description' => 'foreign key to actor table'],
'followed' => ['type' => 'int', 'foreign key' => true, 'target' => 'GSActor.id', 'multiplicity' => 'one to one', 'name' => 'follow_followed_fkey', 'not null' => true, 'description' => 'gsactor being listened to'], // An actor subscribes many circles; A Circle is subscribed by many actors.
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], 'circle_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'ActorCircle.id', 'multiplicity' => 'one to many', 'name' => 'actor_circle_subscription_actor_circle_fkey', 'not null' => true, 'description' => 'foreign key to actor_circle'],
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], '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' => ['follower', 'followed'], 'primary key' => ['circle_id', 'actor_id'],
'indexes' => [ 'indexes' => [
'follow_follower_idx' => ['follower', 'created'], 'actor_circle_subscription_actor_id_idx' => ['actor_id'],
'follow_followed_idx' => ['followed', 'created'], 'actor_circle_subscription_created_idx' => ['created'],
], ],
]; ];
} }

View File

@@ -1,5 +1,7 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // This file is part of GNU social - https://www.gnu.org/software/social
// //
@@ -17,13 +19,21 @@
// along with GNU social. If not, see <http://www.gnu.org/licenses/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Entity; namespace Component\Circle\Entity;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use App\Core\Router;
use App\Entity\Actor;
use Component\Tag\Tag;
use DateTimeInterface; use DateTimeInterface;
/** /**
* Entity for GSActor Tag * Entity for Actor Tag
* This entity represents the relationship between an Actor and a Tag.
* That relationship works as follows:
* An Actor A tags an Actor B (which can be A - a self tag).
* For every tagging that happens between two actors, a new ActorTag is born.
* *
* @category DB * @category DB
* @package GNUsocial * @package GNUsocial
@@ -33,17 +43,18 @@ use DateTimeInterface;
* @author Mikael Nordfeldth <mmn@hethane.se> * @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
* @author Hugo Sales <hugo@hsal.es> * @author Hugo Sales <hugo@hsal.es>
* @author Diogo Peralta Cordeiro <@diogo.site>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class GSActorTag extends Entity class ActorTag extends Entity
{ {
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $tagger; private int $tagger;
private int $tagged; private int $tagged;
private string $tag; private string $tag;
private \DateTimeInterface $modified; private DateTimeInterface $modified;
public function setTagger(int $tagger): self public function setTagger(int $tagger): self
{ {
@@ -69,7 +80,7 @@ class GSActorTag extends Entity
public function setTag(string $tag): self public function setTag(string $tag): self
{ {
$this->tag = $tag; $this->tag = mb_substr($tag, 0, 64);
return $this; return $this;
} }
@@ -92,27 +103,39 @@ class GSActorTag extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public function getUrl(?Actor $actor = null, int $type = Router::ABSOLUTE_PATH): string
{
$params = ['tag' => $this->getTag()];
if (!\is_null($actor)) {
$params['locale'] = $actor->getTopLanguage()->getLocale();
}
return Router::url('single_actor_tag', $params, type: $type);
}
public function getCircle(): ActorCircle
{
if ($this->getTagger() === $this->getTagged()) { // Self-tag
return DB::findOneBy(ActorCircle::class, ['tagger' => null, 'tag' => $this->getTag()]);
} else {
return DB::findOneBy(ActorCircle::class, ['tagger' => $this->getTagger(), 'tag' => $this->getTag()]);
}
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [
'name' => 'gsactor_tag', 'name' => 'actor_tag',
'fields' => [ 'fields' => [
'tagger' => ['type' => 'int', 'foreign key' => true, 'target' => 'GSActor.id', 'multiplicity' => 'one to one', 'nmae' => 'gsactor_tag_tagger_fkey', 'not null' => true, 'description' => 'user making the tag'], 'tagger' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'actor_tag_tagger_fkey', 'description' => 'actor making the tag'],
'tagged' => ['type' => 'int', 'foreign key' => true, 'target' => 'GSActor.id', 'multiplicity' => 'one to one', 'name' => 'gsactor_tag_tagged_fkey', 'not null' => true, 'description' => 'gsactor tagged'], 'tagged' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'name' => 'actor_tag_tagged_fkey', 'description' => 'actor tagged'],
'tag' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this notice'], 'tag' => ['type' => 'varchar', 'length' => Tag::MAX_TAG_LENGTH, 'not null' => true, 'description' => 'hashtag associated with this note'],
'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
], ], // We will always assume the tagger's preferred language for tags and circles
'primary key' => ['tagger', 'tagged', 'tag'], 'primary key' => ['tagger', 'tagged', 'tag'],
'indexes' => [ 'indexes' => [
'gsactor_tag_modified_idx' => ['modified'], 'actor_tag_tagger_tag_idx' => ['tagger', 'tag'], // For Circles
'gsactor_tag_tagger_tag_idx' => ['tagger', 'tag'], // For Circles 'actor_tag_tagged_idx' => ['tagged'],
'gsactor_tag_tagged_idx' => ['tagged'],
], ],
]; ];
} }
public function __toString()
{
return $this->tag;
}
} }

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types = 1);
namespace Component\Circle\Form;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Util\Form\ArrayTransformer;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
abstract class SelfTagsForm
{
/**
* @return array [Form (add), ?Form (existing)]
*/
public static function handleTags(
Request $request,
array $actor_self_tags,
callable $handle_new,
callable $handle_existing,
string $remove_label,
string $add_label,
): array {
$form_definition = [];
foreach ($actor_self_tags as $tag) {
$tag = $tag->getTag();
$form_definition[] = ["{$tag}:old-tag", TextType::class, ['data' => $tag, 'label' => ' ', 'disabled' => true]];
$form_definition[] = [$existing_form_name = "{$tag}:remove", SubmitType::class, ['label' => $remove_label]];
}
$existing_form = !empty($form_definition) ? Form::create($form_definition) : null;
$add_form = Form::create([
['new-tags', TextType::class, ['label' => ' ', 'data' => [], 'required' => false, 'help' => _m('Tags for this actor (letters, numbers, -, ., and _), comma- or space-separated.'), 'transformer' => ArrayTransformer::class]],
[$add_form_name = 'new-tags-add', SubmitType::class, ['label' => $add_label]],
]);
if ($request->getMethod() === 'POST' && $request->request->has($add_form_name)) {
$add_form->handleRequest($request);
if ($add_form->isSubmitted() && $add_form->isValid()) {
$handle_new($add_form);
}
}
if (!\is_null($existing_form) && $request->getMethod() === 'POST' && $request->request->has($existing_form_name ?? '')) {
$existing_form->handleRequest($request);
if ($existing_form->isSubmitted() && $existing_form->isValid()) {
$handle_existing($existing_form, $form_definition);
}
}
return [$add_form, $existing_form];
}
}

View File

@@ -0,0 +1,163 @@
<?php
declare(strict_types = 1);
namespace Component\Collection;
use App\Core\DB;
use App\Core\Event;
use App\Core\Modules\Component;
use App\Entity\Actor;
use App\Util\Formatting;
use Component\Collection\Util\Parser;
use Component\Subscription\Entity\ActorSubscription;
use Doctrine\Common\Collections\ExpressionBuilder;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
class Collection extends Component
{
/**
* Perform a high level query on notes or actors
*
* Supports a variety of query terms and is used both in feeds and
* in search. Uses query builders to allow for extension
*/
public static function query(string $query, int $page, ?string $locale = null, ?Actor $actor = null, array $note_order_by = [], array $actor_order_by = []): array
{
$note_criteria = null;
$actor_criteria = null;
if (!empty($query = trim($query))) {
[$note_criteria, $actor_criteria] = Parser::parse($query, $locale, $actor);
}
$note_qb = DB::createQueryBuilder();
$actor_qb = DB::createQueryBuilder();
// TODO consider selecting note related stuff, to avoid separate queries (though they're cached, so maybe it's okay)
$note_qb->select('note')->from('App\Entity\Note', 'note');
$actor_qb->select('actor')->from('App\Entity\Actor', 'actor');
Event::handle('CollectionQueryAddJoins', [&$note_qb, &$actor_qb, $note_criteria, $actor_criteria]);
// Handle ordering
$note_order_by = !empty($note_order_by) ? $note_order_by : ['note.created' => 'DESC', 'note.id' => 'DESC'];
$actor_order_by = !empty($actor_order_by) ? $actor_order_by : ['actor.created' => 'DESC', 'actor.id' => 'DESC'];
foreach ($note_order_by as $field => $order) {
$note_qb->addOrderBy($field, $order);
}
foreach ($actor_order_by as $field => $order) {
$actor_qb->addOrderBy($field, $order);
}
$notes = [];
$actors = [];
if (!\is_null($note_criteria)) {
$note_qb->addCriteria($note_criteria);
$notes = $note_qb->getQuery()->execute();
}
if (!\is_null($actor_criteria)) {
$actor_qb->addCriteria($actor_criteria);
$actors = $actor_qb->getQuery()->execute();
}
// N.B.: Scope is only enforced at FeedController level
return ['notes' => $notes ?? null, 'actors' => $actors ?? null];
}
public function onCollectionQueryAddJoins(QueryBuilder &$note_qb, QueryBuilder &$actor_qb): bool
{
$note_aliases = $note_qb->getAllAliases();
if (!\in_array('subscription', $note_aliases)) {
$note_qb->leftJoin(ActorSubscription::class, 'subscription', Expr\Join::WITH, 'note.actor_id = subscription.subscribed_id');
}
if (!\in_array('note_actor', $note_aliases)) {
$note_qb->leftJoin(Actor::class, 'note_actor', Expr\Join::WITH, 'note.actor_id = note_actor.id');
}
return Event::next;
}
/**
* Convert $term to $note_expr and $actor_expr, search criteria. Handles searching for text
* notes, for different types of actors and for the content of text notes
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr)
{
if (str_contains($term, ':')) {
$term = explode(':', $term);
if (Formatting::startsWith($term[0], 'note')) {
switch ($term[0]) {
case 'notes-all':
$note_expr = $eb->neq('note.created', null);
break;
case 'note-local':
$note_expr = $eb->eq('note.is_local', filter_var($term[1], \FILTER_VALIDATE_BOOLEAN));
break;
case 'note-types':
case 'notes-include':
case 'note-filter':
if (\is_null($note_expr)) {
$note_expr = [];
}
if (array_intersect(explode(',', $term[1]), ['text', 'words']) !== []) {
$note_expr[] = $eb->neq('note.content', null);
} else {
$note_expr[] = $eb->eq('note.content', null);
}
break;
case 'note-conversation':
$note_expr = $eb->eq('note.conversation_id', (int) trim($term[1]));
break;
case 'note-from':
case 'notes-from':
$subscribed_expr = $eb->eq('subscription.subscriber_id', $actor->getId());
$type_consts = [];
if ($term[1] === 'subscribed') {
$type_consts = null;
}
foreach (explode(',', $term[1]) as $from) {
if (str_starts_with($from, 'subscribed-')) {
[, $type] = explode('-', $from);
if (\in_array($type, ['actor', 'actors'])) {
$type_consts = null;
} else {
$type_consts[] = \constant(Actor::class . '::' . mb_strtoupper($type === 'organisation' ? 'group' : $type));
}
}
}
if (\is_null($type_consts)) {
$note_expr = $subscribed_expr;
} elseif (!empty($type_consts)) {
$note_expr = $eb->andX($subscribed_expr, $eb->in('note_actor.type', $type_consts));
}
break;
}
} elseif (Formatting::startsWith($term, 'actor-')) {
switch ($term[0]) {
case 'actor-types':
case 'actors-include':
case 'actor-filter':
case 'actor-local':
if (\is_null($actor_expr)) {
$actor_expr = [];
}
foreach (
[
Actor::PERSON => ['person', 'people'],
Actor::GROUP => ['group', 'groups', 'org', 'orgs', 'organisation', 'organisations', 'organization', 'organizations'],
Actor::BOT => ['bot', 'bots'],
] as $type => $match) {
if (array_intersect(explode(',', $term[1]), $match) !== []) {
$actor_expr[] = $eb->eq('actor.type', $type);
} else {
$actor_expr[] = $eb->neq('actor.type', $type);
}
}
break;
}
}
} else {
$note_expr = $eb->contains('note.content', $term);
}
return Event::next;
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
class CircleController extends OrderedCollection
{
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
use App\Core\Controller;
use App\Entity\Actor;
use App\Util\Common;
use Component\Collection\Collection as CollectionModule;
class Collection extends Controller
{
public function query(string $query, ?string $locale = null, ?Actor $actor = null, array $note_order_by = [], array $actor_order_by = []): array
{
$actor ??= Common::actor();
$locale ??= Common::currentLanguage()->getLocale();
return CollectionModule::query($query, $this->int('page') ?? 1, $locale, $actor, $note_order_by, $actor_order_by);
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Base class for feed controllers
*
* @package GNUsocial
* @category Controller
*
* @author Hugo Sales <hugo@hsal.es>
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Collection\Util\Controller;
use App\Core\Event;
use App\Entity\Actor;
use App\Entity\Note;
use App\Util\Common;
use Functional as F;
abstract class FeedController extends OrderedCollection
{
/**
* Post-processing of the result of a feed controller, to remove any
* notes or actors the user specified, as well as format the raw
* list of notes into a usable format
*/
protected function postProcess(array $result): array
{
$actor = Common::actor();
if (\array_key_exists('notes', $result)) {
$notes = $result['notes'];
self::enforceScope($notes, $actor, $result['actor'] ?? null);
Event::handle('FilterNoteList', [$actor, &$notes, $result['request']]);
Event::handle('FormatNoteList', [$notes, &$result['notes'], &$result['request']]);
}
return $result;
}
private static function enforceScope(array &$notes, ?Actor $actor, ?Actor $in = null): void
{
$notes = F\select($notes, fn (Note $n) => $n->isVisibleTo($actor, $in));
}
}

View File

@@ -0,0 +1,216 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Collections Controller for GNU social
*
* @package GNUsocial
* @category Plugin
*
* @author Phablulo <phablulo@gmail.com>
* @copyright 2018-2019, 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Collection\Util\Controller;
use App\Core\DB;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Entity\LocalUser;
use App\Util\Common;
use App\Util\Exception\RedirectException;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
abstract class MetaCollectionController extends FeedController
{
protected const SLUG = 'collectionsEntry';
protected const PLURAL_SLUG = 'collectionsList';
protected string $page_title = 'Collections';
abstract public function getCollectionUrl(int $owner_id, string $owner_nickname, int $collection_id): string;
abstract public function getCollectionItems(int $owner_id, $collection_id): array;
abstract public function getCollectionsByActorId(int $owner_id): array;
abstract public function getCollectionBy(int $owner_id, int $collection_id);
abstract public function createCollection(int $owner_id, string $name);
public function collectionsViewByActorNickname(Request $request, string $nickname): array
{
$user = DB::findOneBy(LocalUser::class, ['nickname' => $nickname]);
return self::collectionsView($request, $user->getId(), $nickname);
}
public function collectionsViewByActorId(Request $request, int $id): array
{
return self::collectionsView($request, $id, null);
}
/**
* Generate Collections page
*
* @param int $id actor id
* @param ?string $nickname actor nickname
*
* @return array twig template options
*/
public function collectionsView(Request $request, int $id, ?string $nickname): array
{
$collections = $this->getCollectionsByActorId($id);
$create_title = _m('Create a ' . mb_strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1 $2', static::SLUG)));
$collections_title = _m('The ' . mb_strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1 $2', static::PLURAL_SLUG)));
// create collection form
$create = null;
if (Common::user()?->getId() === $id) {
$create = Form::create([
['name', TextType::class, [
'label' => $create_title,
'attr' => [
'placeholder' => _m('Name'),
'required' => 'required',
],
'data' => '',
]],
['add_collection', SubmitType::class, [
'label' => $create_title,
'attr' => [
'title' => $create_title,
],
]],
]);
$create->handleRequest($request);
if ($create->isSubmitted() && $create->isValid()) {
$this->createCollection($id, $create->getData()['name']);
DB::flush();
throw new RedirectException();
}
}
// We need to inject some functions in twig,
// but I don't want to create an environment for this
// as twig docs suggest in https://twig.symfony.com/doc/2.x/advanced.html#functions.
//
// Instead, I'm using an anonymous class to encapsulate
// the functions and passing that class to the template.
// This is suggested at https://web.archive.org/web/20220226132328/https://stackoverflow.com/questions/3595727/twig-pass-function-into-template/50364502
$fn = new class($id, $nickname, $request, $this, static::SLUG) {
private $id;
private $nick;
private $request;
private $parent;
private $slug;
public function __construct($id, $nickname, $request, $parent, $slug)
{
$this->id = $id;
$this->nick = $nickname;
$this->request = $request;
$this->parent = $parent;
$this->slug = $slug;
}
// there's already an injected function called path,
// that maps to Router::url(name, args), but since
// I want to preserve nicknames, I think it's better
// to use that getUrl function
public function getUrl($cid)
{
return $this->parent->getCollectionUrl($this->id, $this->nick, $cid);
}
// There are many collections in this page and we need two
// forms for each one of them: one form to edit the collection's
// name and another to remove the collection.
// creating the edit form
public function editForm($collection)
{
$edit = Form::create([
['name', TextType::class, [
'attr' => [
'placeholder' => 'New name',
'required' => 'required',
],
'data' => '',
]],
['update_' . $collection->getId(), SubmitType::class, [
'label' => _m('Save'),
'attr' => [
'title' => _m('Save'),
],
]],
]);
$edit->handleRequest($this->request);
if ($edit->isSubmitted() && $edit->isValid()) {
$this->parent->setCollectionName($this->id, $this->nick, $collection, $edit->getData()['name']);
DB::flush();
throw new RedirectException();
}
return $edit->createView();
}
// creating the remove form
public function rmForm($collection)
{
$rm = Form::create([
['remove_' . $collection->getId(), SubmitType::class, [
'label' => _m('Delete ' . $this->slug),
'attr' => [
'title' => _m('Delete ' . $this->slug),
'class' => 'danger',
],
]],
]);
$rm->handleRequest($this->request);
if ($rm->isSubmitted()) {
$this->parent->removeCollection($this->id, $this->nick, $collection);
DB::flush();
throw new RedirectException();
}
return $rm->createView();
}
};
return [
'_template' => 'collection/meta_collections.html.twig',
'page_title' => $this->page_title,
'list_title' => $collections_title,
'add_collection' => $create?->createView(),
'fn' => $fn,
'collections' => $collections,
];
}
public function collectionsEntryViewNotesByNickname(Request $request, string $nickname, int $cid): array
{
$user = DB::findOneBy(LocalUser::class, ['nickname' => $nickname]);
return self::collectionsEntryViewNotesByActorId($request, $user->getId(), $cid);
}
public function collectionsEntryViewNotesByActorId(Request $request, int $id, int $cid): array
{
$collection = $this->getCollectionBy($id, $cid);
$vars = $this->getCollectionItems($id, $cid);
return array_merge([
'_template' => 'collections/collection_entry_view.html.twig',
'page_title' => $collection->getName(),
], $vars);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
class OrderedCollection extends Collection
{
}

View File

@@ -0,0 +1,195 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Collections for GNU social
*
* @package GNUsocial
* @category Plugin
*
* @author Phablulo <phablulo@gmail.com>
* @copyright 2018-2019, 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Collection\Util;
use App\Core\DB;
use App\Core\Event;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Entity\Actor;
use App\Util\Common;
use App\Util\Exception\RedirectException;
use App\Util\Formatting;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
trait MetaCollectionTrait
{
//protected const SLUG = 'collection';
//protected const PLURAL_SLUG = 'collections';
/**
* create a collection owned by Actor $owner.
*
* @param Actor $owner The collection's owner
* @param array $vars Page vars sent by AppendRightPanelBlock event
* @param string $name Collection's name
*/
abstract protected function createCollection(Actor $owner, array $vars, string $name);
/**
* remove item from collections.
*
* @param Actor $owner Current user
* @param array $vars Page vars sent by AppendRightPanelBlock event
* @param array $items Array of collections's ids to remove the current item from
* @param array $collections List of ids of collections owned by $owner
*/
abstract protected function removeItem(Actor $owner, array $vars, array $items, array $collections);
/**
* add item to collections.
*
* @param Actor $owner Current user
* @param array $vars Page vars sent by AppendRightPanelBlock event
* @param array $items Array of collections's ids to add the current item to
* @param array $collections List of ids of collections owned by $owner
*/
abstract protected function addItem(Actor $owner, array $vars, array $items, array $collections);
/**
* Check the route to determine whether the widget should be added
*/
abstract protected function shouldAddToRightPanel(Actor $user, $vars, Request $request): bool;
/**
* Get array of collections's owned by $actor
*
* @param Actor $owner Collection's owner
* @param ?array $vars Page vars sent by AppendRightPanelBlock event
* @param bool $ids_only if true, the function must return only the primary key or each collections
*/
abstract protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array;
/**
* Append Collections widget to the right panel.
* It's compose of two forms: one to select collections to add
* the current item to, and another to create a new collection.
*/
public function onAppendRightPanelBlock(Request $request, $vars, &$res): bool
{
$user = Common::actor();
if (\is_null($user)) {
return Event::next;
}
if (!$this->shouldAddToRightPanel($user, $vars, $request)) {
return Event::next;
}
$collections = $this->getCollectionsBy($user);
// form: add to collection
$choices = [];
foreach ($collections as $col) {
$choices[$col->getName()] = $col->getId();
}
$collections = array_map(fn ($x) => $x->getId(), $collections);
$already_selected = $this->getCollectionsBy($user, $vars, true);
$add_form = Form::create([
['collections', ChoiceType::class, [
'choices' => $choices,
'multiple' => true,
'required' => false,
'choice_attr' => function ($id) use ($already_selected) {
if (\in_array($id, $already_selected)) {
return ['selected' => 'selected'];
}
return [];
},
]],
['add', SubmitType::class, [
'label' => _m('Add to ' . static::PLURAL_SLUG),
'attr' => [
'title' => _m('Add to ' . static::PLURAL_SLUG),
],
]],
]);
$add_form->handleRequest($request);
if ($add_form->isSubmitted() && $add_form->isValid()) {
$selected = $add_form->getData()['collections'];
$removed = array_filter($already_selected, fn ($x) => !\in_array($x, $selected));
$added = array_filter($selected, fn ($x) => !\in_array($x, $already_selected));
if (\count($removed) > 0) {
$this->removeItem($user, $vars, $removed, $collections);
}
if (\count($added) > 0) {
$this->addItem($user, $vars, $added, $collections);
}
DB::flush();
throw new RedirectException();
}
// form: add to new collection
$create_form = Form::create([
['name', TextType::class, [
'label' => _m('Add to a new ' . static::SLUG),
'attr' => [
'placeholder' => _m('New ' . static::SLUG . ' name'),
'required' => 'required',
],
'data' => '',
]],
['create', SubmitType::class, [
'label' => _m('Create a new ' . static::SLUG),
'attr' => [
'title' => _m('Create a new ' . static::SLUG),
],
]],
]);
$create_form->handleRequest($request);
if ($create_form->isSubmitted() && $create_form->isValid()) {
$name = $create_form->getData()['name'];
$this->createCollection($user, $vars, $name);
DB::flush();
throw new RedirectException();
}
$res[] = Formatting::twigRenderFile(
'collection/widget_add_to.html.twig',
[
'ctitle' => _m('Add to ' . static::PLURAL_SLUG),
'user' => $user,
'has_collections' => \count($collections) > 0,
'add_form' => $add_form->createView(),
'create_form' => $create_form->createView(),
],
);
return Event::next;
}
public function onEndShowStyles(array &$styles, string $route): bool
{
$styles[] = 'components/Collection/assets/css/widget.css';
$styles[] = 'components/Collection/assets/css/pages.css';
return Event::next;
}
}

View File

@@ -0,0 +1,130 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Collection\Util;
use App\Core\Event;
use App\Entity\Actor;
use App\Util\Exception\ServerException;
use Doctrine\Common\Collections\Criteria;
abstract class Parser
{
/**
* Merge $parts into $criteria_arr
*/
private static function connectParts(array &$parts, array &$criteria_arr, string $last_op, mixed $eb, bool $force = false): void
{
foreach ([' ' => 'orX', '|' => 'orX', '&' => 'andX'] as $op => $func) {
if ($last_op === $op || $force) {
$criteria_arr[] = $eb->{$func}(...$parts);
break;
}
}
}
/**
* Parse $input string into a Doctrine query Criteria
*
* Currently doesn't support nesting with parenthesis and
* recognises either spaces (currently `or`, should be fuzzy match), `OR` or `|` (`or`) and `AND` or `&` (`and`)
*
* TODO: Better fuzzy match, implement exact match with quotes and nesting with parens
* TODO: Proper parser, tokenize better. Mostly a rewrite
*
* @return array{?Criteria, ?Criteria} [?$note_criteria, ?$actor_criteria]
*/
public static function parse(string $input, ?string $locale = null, ?Actor $actor = null, int $level = 0): array
{
if ($level === 0) {
$input = trim(preg_replace(['/\s+/', '/\s+AND\s+/', '/\s+OR\s+/'], [' ', '&', '|'], $input), ' |&');
}
$left = 0;
$right = 0;
$lenght = mb_strlen($input);
$eb = Criteria::expr();
$note_criteria_arr = [];
$actor_criteria_arr = [];
$note_parts = [];
$actor_parts = [];
$last_op = null;
for ($index = 0; $index < $lenght; ++$index) {
$end = false;
$match = false;
foreach (['&', '|', ' '] as $delimiter) {
if ($input[$index] === $delimiter || $end = ($index === $lenght - 1)) {
$term = mb_substr($input, $left, $end ? null : $right - $left);
$note_res = null;
$actor_res = null;
Event::handle('CollectionQueryCreateExpression', [$eb, $term, $locale, $actor, &$note_res, &$actor_res]);
if (\is_null($note_res) && \is_null($actor_res)) { // @phpstan-ignore-line
//throw new ServerException("No one claimed responsibility for a match term: {$term}");
// It's okay if the term doesn't exist, just perform a regular search
}
if (!empty($note_res)) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234
if (\is_array($note_res)) {
$note_res = $eb->orX(...$note_res);
}
$note_parts[] = $note_res;
}
if (!empty($actor_res)) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234
if (\is_array($actor_res)) {
$actor_res = $eb->orX(...$actor_res);
}
$actor_parts[] = $actor_res;
}
$right = $left = $index + 1;
if (!\is_null($last_op) && $last_op !== $delimiter) {
self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: false);
} else {
$last_op = $delimiter;
}
$match = true;
break;
}
}
// TODO
if (!$match) {
++$right;
}
}
$note_criteria = null;
$actor_criteria = null;
if (!empty($note_parts)) {
self::connectParts($note_parts, $note_criteria_arr, $last_op, $eb, force: true);
$note_criteria = new Criteria($eb->orX(...$note_criteria_arr));
}
if (!empty($actor_parts)) { // @phpstan-ignore-line weird, but this whole thing needs a rewrite
self::connectParts($actor_parts, $actor_criteria_arr, $last_op, $eb, force: true);
$actor_criteria = new Criteria($eb->orX(...$actor_criteria_arr));
}
return [$note_criteria, $actor_criteria];
}
}

View File

@@ -0,0 +1,70 @@
{% extends 'stdgrid.html.twig' %}
{% block title %}{{ title }}{% endblock %}
{% block body %}
<section class="frame-section frame-section-padding">
<header class="feed-header">
{% if actors_feed_title is defined %}
{{ actors_feed_title.getHtml() }}
{% endif %}
</header>
{% set prepend_actors_collection = handle_event('PrependActorsCollection', request) %}
{% for widget in prepend_actors_collection %}
{{ widget | raw }}
{% endfor %}
<details class="frame-section section-details-title">
<summary class="details-summary-title">
<strong>
{% trans %}Ordering rules{% endtrans %}
</strong>
</summary>
<form method="GET" class="section-form">
<div class="container-grid">
<section class="frame-section frame-section-padding">
<strong>{% trans %}Sort by{% endtrans %}</strong>
<hr>
<div class="container-block">
{% for field in sort_form_fields %}
<span class="container-block">
<label for="order_by_{{ field.value }}">{{ field.label }}</label>
<input id="order_by_{{ field.value }}" type="radio" name="order_by" value="{{ field.value }}" {% if field.checked %}checked="checked"{% endif %}>
</span>
{% endfor %}
</div>
</section>
<section class="frame-section frame-section-padding">
<strong class="section-title">{% trans %}Order{% endtrans %}</strong>
<hr>
<section class="container-block">
<span class="container-block">
<label for="order_op_asc">{% trans %}Ascending{% endtrans %}</label>
<input id="order_op_asc" type="radio" name="order_op" value="ASC">
</span>
<span class="container-block">
<label for="order_op_desc">{% trans %}Descending{% endtrans %}</label>
<input id="order_op_desc" type="radio" name="order_op" value="DESC" checked="checked">
</span>
</section>
</section>
</div>
<button type="submit">{% trans %}Order{% endtrans %}</button>
</form>
</details>
<section class="frame-section frame-section-padding">
<h2>{% trans %}Results{% endtrans %}</h2>
{% if actors is defined and actors is not empty %}
{% for actor in actors %}
{% block profile_view %}{% include 'cards/blocks/profile.html.twig' %}{% endblock profile_view %}
<hr>
{% endfor %}
<span class="frame-section-button-like">{% trans %}Page: %page%{% endtrans %}</span>
{% else %}
<span>{{ empty_message }}</span>
{% endif %}
</section>
</section>
{% endblock body %}

View File

@@ -0,0 +1,11 @@
{% extends '/collection/notes.html.twig' %}
{% block title %}{% trans %}%page_title%{% endtrans %}{% endblock %}
{% block body %}
<div class="frame-section frame-section-padding">
<h2 class="frame-section-title">{% trans %}%page_title%{% endtrans %}</h2>
{% block collection_items %}
{% endblock collection_items %}
</div>
{% endblock body %}

View File

@@ -0,0 +1,34 @@
{% extends 'stdgrid.html.twig' %}
{% block title %}{% trans %}%page_title%{% endtrans %}{% endblock %}
{% block body %}
<div class="frame-section frame-section-padding">
<h2 class="frame-section-title">{% trans %}%page_title%{% endtrans %}</h2>
{% if add_collection %}
<div class="frame-section section-form">
{{ form(add_collection) }}
</div>
{% endif %}
<div class="frame-section collections-list">
<h3>{% trans %}%list_title%{% endtrans %}</h3>
{% for col in collections %}
<div class="collection-item">
<a class="name" href="{{ fn.getUrl(col.id) }}">{{ col.name }}</a>
<details title="Expand if you want to edit the collection's name">
<summary>
<span class="collection-action">{{ icon('edit') | raw }}</span>
</summary>
{{ form(fn.editForm(col)) }}
</details>
<details title="Expand if you want to delete the collection">
<summary>
<span class="collection-action">{{ icon('delete') | raw }}</span>
</summary>
{{ form(fn.rmForm(col)) }}
</details>
</div>
{% endfor %}
</div>
</div>
{% endblock body %}

View File

@@ -0,0 +1,58 @@
{% extends 'stdgrid.html.twig' %}
{% import '/cards/macros/note/factory.html.twig' as NoteFactory %}
{% block title %}{% if page_title is defined %}{% trans %}%page_title%{% endtrans %}{% endif %}{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('assets/default_theme/feeds.css') }}" type="text/css">
{% endblock stylesheets %}
{% block body %}
{% for block in handle_event('BeforeFeed', app.request) %}
{{ block | raw }}
{% endfor %}
{% if notes is defined %}
<header class="feed-header">
{% set current_path = app.request.get('_route') %}
{% if notes_feed_title is defined %}
{{ notes_feed_title.getHtml() }}
{% endif %}
<nav class="feed-actions" title="{% trans %}Actions that change how the feed behaves{% endtrans %}">
<details class="feed-actions-details" role="group">
<summary>
{{ icon('filter', 'icon icon-feed-actions') | raw }} {# button-container #}
</summary>
<menu class="feed-actions-details-dropdown" role="toolbar">
{% for block in handle_event('AddFeedActions', app.request, notes is defined and notes is not empty) %}
{{ block | raw }}
{% endfor %}
</menu>
</details>
</nav>
</header>
{% if notes is not empty %}
{# Backwards compatibility with hAtom 0.1 #}
<section class="feed h-feed hfeed notes" role="feed" aria-busy="false" title="{% trans %}Feed content{% endtrans %}">
{% for conversation in notes %}
{% block current_note %}
{% if conversation is instanceof('array') %}
{% set args = conversation | merge({'type': 'vanilla_full'}) %}
{{ NoteFactory.constructor(args) }}
{# {% else %}
{% set args = { 'type': 'vanilla_full', 'note': conversation, 'extra': { 'depth': 0 } } %}
{{ NoteFactory.constructor(args) }}#}
{% endif %}
<hr class="hr-replies-end" role="separator" aria-label="{% trans %}Marks the end of previous conversation's initial note{% endtrans %}">
{% endblock current_note %}
{% endfor %}
</section>
{% else %}
<section class="feed h-feed hfeed notes" tabindex="0" role="feed">
<span>{% trans %}No notes here...{% endtrans %}</span>
</section>
{% endif %}
{% endif %}
{% endblock body %}

View File

@@ -0,0 +1,27 @@
<section class="frame-section collections">
<details class="section-details-title" title="Expand if you want to access more options.">
<summary class="details-summary-title">
<span>{{ctitle}}</span>
</summary>
{% if has_collections %}
<section class="section-form">
{{ form(add_form) }}
</section>
<details class="frame-section-padding section-details-subtitle"
title="Expand if you want to access more options.">
<summary class="details-summary-subtitle">
<strong>{% trans %}Other options{% endtrans %}</strong>
</summary>
<section class="section-form">
{{ form(create_form) }}
</section>
</details>
{% else %}
<section class="section-form">
{{ form(create_form) }}
</section>
{% endif %}
</details>
</section>

View File

@@ -0,0 +1,152 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* @author Hugo Sales <hugo@hsal.es>
* @author Eliseu Amaro <mail@eliseuama.ro>
* @copyright 2021-2022 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Conversation\Controller;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Core\Log;
use App\Core\Router;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\NoLoggedInUser;
use App\Util\Exception\NoSuchNoteException;
use App\Util\Exception\RedirectException;
use App\Util\Exception\ServerException;
use App\Util\HTML\Heading;
use Component\Collection\Util\Controller\FeedController;
use Component\Conversation\Entity\ConversationMute;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
class Conversation extends FeedController
{
/**
* Render conversation page.
*
* @param int $conversation_id To identify what Conversation is to be rendered
*
* @throws \App\Util\Exception\ServerException
*
* @return array Array containing keys: 'notes' (all known notes in the given Conversation), 'should_format' (boolean, stating if onFormatNoteList events may or not format given notes), 'page_title' (used as the title header)
*/
public function showConversation(Request $request, int $conversation_id): array
{
$page_title = _m('Conversation');
return [
'_template' => 'collection/notes.html.twig',
'notes' => $this->query(
query: "note-conversation:{$conversation_id}",
note_order_by: ['note.created' => 'ASC', 'note.id' => 'ASC'],
)['notes'] ?? [],
'should_format' => false,
'page_title' => $page_title,
'notes_feed_title' => (new Heading(1, [], $page_title)),
];
}
/**
* Controller for the note reply non-JS page
*
* Leverages the `PostingModifyData` event to add the `reply_to_id` field from the GET variable 'reply_to_id'
*
* @throws ClientException
* @throws NoLoggedInUser
* @throws NoSuchNoteException
* @throws ServerException
*
* @return array
*/
public function addReply(Request $request)
{
$user = Common::ensureLoggedIn();
$note_id = $this->int('reply_to_id', new ClientException(_m('Malformed query.')));
$note = Note::ensureCanInteract(Note::getByPK($note_id), $user);
$conversation_id = $note->getConversationId();
return $this->showConversation($request, $conversation_id);
}
/**
* Creates form view for Muting Conversation extra action.
*
* @param int $conversation_id The Conversation id that this action targets
*
* @throws \App\Util\Exception\NoLoggedInUser
* @throws \App\Util\Exception\RedirectException
* @throws \App\Util\Exception\ServerException
*
* @return array Array containing templating where the form is to be rendered, and the form itself
*/
public function muteConversation(Request $request, int $conversation_id)
{
$user = Common::ensureLoggedIn();
$is_muted = ConversationMute::isMuted($conversation_id, $user);
$form = Form::create([
['mute_conversation', SubmitType::class, ['label' => $is_muted ? _m('Unmute') : _m('Mute'), 'attr' => ['class' => '']]],
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (!$is_muted) {
DB::persist(ConversationMute::create(['conversation_id' => $conversation_id, 'actor_id' => $user->getId()]));
} else {
DB::removeBy('conversation_mute', ['conversation_id' => $conversation_id, 'actor_id' => $user->getId()]);
}
DB::flush();
Cache::delete(ConversationMute::cacheKeys($conversation_id, $user->getId())['mute']);
// Redirect user to where they came from
// Prevent open redirect
if (!\is_null($from = $this->string('from'))) {
if (Router::isAbsolute($from)) {
Log::warning("Actor {$user->getId()} attempted to mute conversation {$conversation_id} and then get redirected to another host, or the URL was invalid ({$from})");
throw new ClientException(_m('Can not redirect to outside the website from here'), 400); // 400 Bad request (deceptive)
} else {
// TODO anchor on element id
throw new RedirectException(url: $from);
}
} else {
// If we don't have a URL to return to, go to the instance root
throw new RedirectException('root');
}
}
return [
'_template' => 'conversation/mute.html.twig',
'notes' => $this->query(
query: "note-conversation:{$conversation_id}",
note_order_by: ['note.created' => 'ASC', 'note.id' => 'ASC'],
)['notes'] ?? [],
'is_muted' => $is_muted,
'form' => $form->createView(),
];
}
}

View File

@@ -0,0 +1,312 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* @author Hugo Sales <hugo@hsal.es>
* @author Eliseu Amaro <mail@eliseuama.ro>
* @copyright 2021-2022 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Conversation;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Event;
use function App\Core\I18n\_m;
use App\Core\Modules\Component;
use App\Core\Router;
use App\Entity\Activity;
use App\Entity\Actor;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Formatting;
use Component\Conversation\Entity\Conversation as ConversationEntity;
use Component\Conversation\Entity\ConversationMute;
use Functional as F;
use Symfony\Component\HttpFoundation\Request;
class Conversation extends Component
{
public function onAddRoute(Router $r): bool
{
$r->connect('conversation', '/conversation/{conversation_id<\d+>}', [Controller\Conversation::class, 'showConversation']);
$r->connect('conversation_mute', '/conversation/{conversation_id<\d+>}/mute', [Controller\Conversation::class, 'muteConversation']);
$r->connect('conversation_reply_to', '/conversation/reply', [Controller\Conversation::class, 'addReply']);
return Event::next;
}
/**
* **Assigns** the given local Note it's corresponding **Conversation**.
*
* **If a _$parent_id_ is not given**, then the Actor is not attempting a reply,
* therefore, we can assume (for now) that we need to create a new Conversation and assign it
* to the newly created Note (please look at Component\Posting::storeLocalNote, where this function is used)
*
* **On the other hand**, given a _$parent_id_, the Actor is attempting to post a reply. Meaning that,
* this Note conversation_id should be same as the parent Note
*
* @param \App\Entity\Note $current_note Local Note currently being assigned a Conversation
* @param null|int $parent_id If present, it's a reply
*/
public static function assignLocalConversation(Note $current_note, ?int $parent_id): void
{
if (!$parent_id) {
// If none found, we don't know yet if it is a reply or root
// Let's assume for now that it's a new conversation and deal with stitching later
$conversation = ConversationEntity::create(['initial_note_id' => $current_note->getId()]);
// We need the Conversation id itself, so a persist is in order
DB::persist($conversation);
// Set current_note's own conversation_id
$current_note->setConversationId($conversation->getId());
} else {
// It's a reply for sure
// Set reply_to property in newly created Note to parent's id
// Parent will have a conversation of its own, the reply should have the same one
$parent_note = Note::getById($parent_id);
$current_note->setConversationId($parent_note->getConversationId());
}
DB::persist($current_note);
}
/**
* HTML rendering event that adds a reply link as a note
* action, if a user is logged in.
*
* @param \App\Entity\Note $note The Note being rendered
* @param array $actions Contains keys 'url' (linking 'conversation_reply_to'
* route), 'title' (used as title for aforementioned url),
* 'classes' (CSS styling classes used to visually inform the user of action context),
* 'id' (HTML markup id used to redirect user to this anchor upon performing the action)
*
* @throws \App\Util\Exception\ServerException
*
* @return bool EventHook
*/
public function onAddNoteActions(Request $request, Note $note, array &$actions): bool
{
if (\is_null(Common::user())) {
return Event::next;
}
$from = $request->query->has('from')
? $request->query->get('from')
: $request->getPathInfo();
$reply_action_url = Router::url(
'conversation_reply_to',
[
'reply_to_id' => $note->getId(),
'from' => $from,
'_fragment' => 'note-anchor-' . $note->getId(),
],
Router::ABSOLUTE_PATH,
);
$reply_action = [
'url' => $reply_action_url,
'title' => _m('Reply to this note!'),
'classes' => 'button-container reply-button-container note-actions-unset',
'id' => 'reply-button-container-' . $note->getId(),
];
$actions[] = $reply_action;
return Event::next;
}
/**
* Append on note information about user actions.
*
* @param array $vars Contains information related to Note currently being rendered
* @param array $result Contains keys 'actors', and 'action'. Needed to construct a string, stating who ($result['actors']), has already performed a reply ($result['action']), in the given Note (vars['note'])
*
* @return bool EventHook
*/
public function onAppendCardNote(array $vars, array &$result): bool
{
if (str_contains($vars['request']->getPathInfo(), 'conversation')) {
return Event::next;
}
// The current Note being rendered
$note = $vars['note'];
// Will have actors array, and action string
// Actors are the subjects, action is the verb (in the final phrase)
$reply_actors = F\map(
$note->getReplies(),
fn (Note $reply) => Actor::getByPK($reply->getActorId()),
);
if (empty($reply_actors)) {
return Event::next;
}
// Filter out multiple replies from the same actor
$reply_actors = array_unique($reply_actors, \SORT_REGULAR);
$result[] = ['actors' => $reply_actors, 'action' => 'replied to'];
return Event::next;
}
private function getReplyToIdFromRequest(Request $request): ?int
{
if (!\is_array($request->get('post_note')) || !\array_key_exists('_next', $request->get('post_note'))) {
return null;
}
$next = parse_url($request->get('post_note')['_next']);
if (!\array_key_exists('query', $next)) {
return null;
}
parse_str($next['query'], $query);
if (!\array_key_exists('reply_to_id', $query)) {
return null;
}
return (int) $query['reply_to_id'];
}
/**
* Informs **\App\Component\Posting::onAppendRightPostingBlock**, of the **current page context** in which the given
* Actor is in. This is valuable when posting within a group route, allowing \App\Component\Posting to create a
* Note **targeting** that specific Group.
*
* @param \App\Entity\Actor $actor The Actor currently attempting to post a Note
* @param null|\App\Entity\Actor $context_actor The 'owner' of the current route (e.g. Group or Actor), used to target it
*
* @return bool EventHook
*/
public function onPostingGetContextActor(Request $request, Actor $actor, ?Actor &$context_actor): bool
{
$to_note_id = $this->getReplyToIdFromRequest($request);
if (!\is_null($to_note_id)) {
// Getting the actor itself
$context_actor = Actor::getById(Note::getById((int) $to_note_id)->getActorId());
return Event::stop;
}
return Event::next;
}
/**
* Posting event to add extra information to Component\Posting form data
*
* @param array $data Transport data to be filled with reply_to_id
*
* @throws \App\Util\Exception\ClientException
* @throws \App\Util\Exception\NoSuchNoteException
*
* @return bool EventHook
*/
public function onPostingModifyData(Request $request, Actor $actor, array &$data): bool
{
$to_note_id = $this->getReplyToIdFromRequest($request);
if (!\is_null($to_note_id)) {
Note::ensureCanInteract(Note::getById($to_note_id), $actor);
$data['reply_to_id'] = $to_note_id;
}
return Event::next;
}
/**
* Add minimal Note card to RightPanel template
*/
public function onPrependPostingForm(Request $request, array &$elements): bool
{
$elements[] = Formatting::twigRenderFile('cards/blocks/note_compact_wrapper.html.twig', ['note' => Note::getById((int) $request->query->get('reply_to_id'))]);
return Event::next;
}
/**
* Event launched when deleting given Note, it's deletion implies further changes to object related to this Note.
* Please note, **replies are NOT deleted**, their reply_to is only set to null since this Note no longer exists.
*
* @param \App\Entity\Note $note Note being deleted
* @param \App\Entity\Actor $actor Actor that performed the delete action
*
* @return bool EventHook
*/
public function onNoteDeleteRelated(Note &$note, Actor $actor): bool
{
// Ensure we have the most up to date replies
Cache::delete(Note::cacheKeys($note->getId())['replies']);
DB::wrapInTransaction(fn () => F\each($note->getReplies(), fn (Note $note) => $note->setReplyTo(null)));
Cache::delete(Note::cacheKeys($note->getId())['replies']);
return Event::next;
}
/**
* Adds extra actions related to Conversation Component, that act upon/from the given Note.
*
* @param \App\Entity\Note $note Current Note being rendered
* @param array $actions Containing 'url' (Controller connected route), 'title' (used in anchor link containing the url), ?'classes' (CSS classes required for styling, if needed)
*
* @throws \App\Util\Exception\ServerException
*
* @return bool EventHook
*/
public function onAddExtraNoteActions(Request $request, Note $note, array &$actions): bool
{
if (\is_null($user = Common::user())) {
return Event::next;
}
$from = $request->query->has('from')
? $request->query->get('from')
: $request->getPathInfo();
$mute_extra_action_url = Router::url(
'conversation_mute',
[
'conversation_id' => $note->getConversationId(),
'from' => $from,
'_fragment' => 'note-anchor-' . $note->getId(),
],
Router::ABSOLUTE_PATH,
);
$actions[] = [
'title' => ConversationMute::isMuted($note, $user) ? _m('Unmute conversation') : _m('Mute conversation'),
'classes' => '',
'url' => $mute_extra_action_url,
];
return Event::next;
}
/**
* Prevents new Notifications to appear for muted conversations
*
* @param Activity $activity Notification Activity
*
* @return bool EventHook
*/
public function onNewNotificationShould(Activity $activity, Actor $actor): bool
{
if ($activity->getObjectType() === 'note' && ConversationMute::isMuted($activity, $actor)) {
return Event::stop;
}
return Event::next;
}
}

View File

@@ -1,6 +1,9 @@
<?php <?php
declare(strict_types = 1);
// {{{ License // {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social // 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 // GNU social is free software: you can redistribute it and/or modify
@@ -15,17 +18,19 @@
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}} // }}}
namespace App\Entity; namespace Component\Conversation\Entity;
use App\Core\DB;
use App\Core\Entity; use App\Core\Entity;
use DateTimeInterface; use App\Core\Router;
/** /**
* Data class for Conversations * Entity class for Conversations
* *
* @category Data * @category DB
* @package GNUsocial * @package GNUsocial
* *
* @author Zach Copley <zach@status.net> * @author Zach Copley <zach@status.net>
@@ -33,7 +38,8 @@ use DateTimeInterface;
* @copyright 2010 StatusNet Inc. * @copyright 2010 StatusNet Inc.
* @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org
* @author Hugo Sales <hugo@hsal.es> * @author Hugo Sales <hugo@hsal.es>
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org * @author Eliseu Amaro <mail@eliseuama.ro>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class Conversation extends Entity class Conversation extends Entity
@@ -41,9 +47,7 @@ class Conversation extends Entity
// {{{ Autocode // {{{ Autocode
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
private int $id; private int $id;
private int $note_id; private int $initial_note_id;
private \DateTimeInterface $created;
private \DateTimeInterface $modified;
public function setId(int $id): self public function setId(int $id): self
{ {
@@ -56,53 +60,42 @@ class Conversation extends Entity
return $this->id; return $this->id;
} }
public function setNoteId(int $note_id): self public function setInitialNoteId(int $initial_note_id): self
{ {
$this->note_id = $note_id; $this->initial_note_id = $initial_note_id;
return $this; return $this;
} }
public function getNoteId(): int public function getInitialNoteId(): int
{ {
return $this->note_id; return $this->initial_note_id;
}
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;
} }
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public function getUrl(int $type = Router::ABSOLUTE_URL): string
{
return Router::url('conversation', ['conversation_id' => $this->getId()], $type);
}
public function getUri(): string
{
return $this->getUrl(type: Router::ABSOLUTE_URL);
}
public static function schemaDef(): array public static function schemaDef(): array
{ {
return [ return [
'name' => 'conversation', 'name' => 'conversation',
'fields' => [ 'fields' => [
'id' => ['type' => 'serial', 'not null' => true, 'description' => 'Unique identifier'], 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'Serial identifier, since any additional meaning would require updating its value for every reply upon receiving a new aparent root'],
'note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'Root of note for this conversation'], 'initial_note_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'Initial note seen on this host for this conversation'],
'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' => ['id'],
'foreign keys' => [
'initial_note_id_to_id_fkey' => ['note', ['initial_note_id' => 'id']],
], ],
'primary key' => ['id'],
]; ];
} }
} }

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Conversation\Entity;
use App\Core\Cache;
use App\Core\DB;
use App\Core\Entity;
use App\Entity\Activity;
use App\Entity\Actor;
use App\Entity\LocalUser;
use App\Entity\Note;
use DateTimeInterface;
/**
* Entity class for Conversations Mutes
*
* @category DB
* @package GNUsocial
*
* @author Hugo Sales <hugo@hsal.es>
* @copyright 2022 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class ConversationMute extends Entity
{
// {{{ Autocode
// @codeCoverageIgnoreStart
private int $conversation_id;
private int $actor_id;
private DateTimeInterface $created;
public function setConversationId(int $conversation_id): self
{
$this->conversation_id = $conversation_id;
return $this;
}
public function getConversationId(): int
{
return $this->conversation_id;
}
public function setActorId(int $actor_id): self
{
$this->actor_id = $actor_id;
return $this;
}
public function getActorId(): int
{
return $this->actor_id;
}
public function setCreated(DateTimeInterface $created): self
{
$this->created = $created;
return $this;
}
public function getCreated(): DateTimeInterface
{
return $this->created;
}
// @codeCoverageIgnoreEnd
// }}} Autocode
public static function cacheKeys(int $conversation_id, int $actor_id): array
{
return [
'mute' => "conversation-mute-{$conversation_id}-{$actor_id}",
];
}
/**
* Check if a conversation referenced by $object is muted form $actor
*/
public static function isMuted(Activity|Note|int $object, Actor|LocalUser $actor): bool
{
$conversation_id = null;
if (\is_int($object)) {
$conversation_id = $object;
} elseif ($object instanceof Note) {
$conversation_id = $object->getConversationId();
} elseif ($object instanceof Activity) {
$conversation_id = Note::getById($object->getObjectId())->getConversationId();
}
return Cache::get(
self::cacheKeys($conversation_id, $actor->getId())['mute'],
fn () => (bool) DB::count('conversation_mute', ['conversation_id' => $conversation_id, 'actor_id' => $actor->getId()]),
);
}
public static function schemaDef(): array
{
return [
'name' => 'conversation_mute',
'fields' => [
'conversation_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Conversation.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'The conversation being blocked'],
'actor_id' => ['type' => 'int', 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'not null' => true, 'description' => 'Who blocked the conversation'],
'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
],
'primary key' => ['conversation_id', 'actor_id'],
'foreign keys' => [
'conversation_id_to_id_fkey' => ['conversation', ['conversation_id' => 'id']],
'actor_id_to_id_fkey' => ['actor', ['actor_id' => 'id']],
],
];
}
}

View File

@@ -0,0 +1,21 @@
{% extends 'collection/notes.html.twig' %}
{% block body %}
<div class="frame-section frame-section-padding">
{% if is_muted %}
<span class="frame-section-padding alert">
<label>Do you wish to <b>unmute</b> this conversation?</label>
{{ form(form) }}
</span>
{% else %}
<span class="frame-section-padding alert">
<label>Do you wish to <b>mute</b> this conversation?</label>
{{ form(form) }}
</span>
{% endif %}
<hr>
{{ parent() }}
</div>
{% endblock body %}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Handle network public feed
*
* @package GNUsocial
* @category Controller
*
* @author Hugo Sales <hugo@hsal.es>
* @author Eliseu Amaro <eliseu@fc.up.pt>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\Feed\Controller;
use function App\Core\I18n\_m;
use App\Util\Common;
use App\Util\HTML\Heading;
use Component\Collection\Util\Controller\FeedController;
use Symfony\Component\HttpFoundation\Request;
class Feeds extends FeedController
{
/**
* The Planet feed represents every local post. Which is what this instance has to share with the universe.
*/
public function public(Request $request): array
{
$data = $this->query('note-local:true');
$page_title = _m(\is_null(Common::user()) ? 'Feed' : 'Planet');
return [
'_template' => 'collection/notes.html.twig',
'page_title' => $page_title,
'notes_feed_title' => (new Heading(level: 1, classes: ['feed-title'], text: $page_title)),
'notes' => $data['notes'],
];
}
/**
* The Home feed represents everything that concerns a certain actor (its subscriptions)
*/
public function home(Request $request): array
{
Common::ensureLoggedIn();
$data = $this->query('note-from:subscribed-person,subscribed-group,subscribed-organisation');
return [
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Home'),
'notes_feed_title' => (new Heading(level: 1, classes: ['feed-title'], text: 'Home')),
'notes' => $data['notes'],
];
}
}

39
components/Feed/Feed.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Feed;
use App\Core\Event;
use App\Core\Modules\Component;
use App\Core\Router;
use Component\Feed\Controller as C;
class Feed extends Component
{
public function onAddRoute(Router $r): bool
{
$r->connect('feed_public', '/feed/public', [C\Feeds::class, 'public']);
$r->connect('feed_home', '/feed/home', [C\Feeds::class, 'home']);
return Event::next;
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\Feed\tests\Controller;
use App\Core\Router;
use App\Util\GNUsocialTestCase;
use Component\Feed\Controller\Feeds;
use Jchook\AssertThrows\AssertThrows;
class FeedsTest extends GNUsocialTestCase
{
use AssertThrows;
public function testPublic()
{
// This calls static::bootKernel(), and creates a "client" that is acting as the browser
$client = static::createClient();
$crawler = $client->request('GET', Router::url('feed_public'));
$this->assertResponseIsSuccessful();
}
public function testHome()
{
// This calls static::bootKernel(), and creates a "client" that is acting as the browser
$client = static::createClient();
$crawler = $client->request('GET', Router::url('feed_home'));
$this->assertResponseStatusCodeSame(302);
}
// TODO: It would be nice to actually test whether the feeds are respecting scopes and spitting
// out the expected notes... The ActivityPub plugin have a somewhat obvious way of testing it so,
// for now, having that, might fill that need, let's see
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* Handle network public feed
*
* @package GNUsocial
* @category Controller
*
* @author Diogo Peralta Cordeiro <@diogo.site>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\FreeNetwork\Controller;
use App\Core\DB;
use function App\Core\I18n\_m;
use App\Util\Common;
use Component\Collection\Util\Controller\FeedController;
use Symfony\Component\HttpFoundation\Request;
class Feeds extends FeedController
{
/**
* The Meteorites feed represents every post coming from the
* known fediverse to this instance's inbox. I.e., it's our
* known network and excludes everything that is local only
* or federated out.
*/
public function network(Request $request): array
{
Common::ensureLoggedIn();
$data = $this->query('note-local:false');
return [
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Meteorites'),
'should_format' => true,
'notes' => $data['notes'],
];
}
/**
* The Planetary System feed represents every planet-centric post, i.e.,
* everything that is local or comes from outside with relation to local actors
* or posts.
*/
public function clique(Request $request): array
{
Common::ensureLoggedIn();
// TODO: maybe make this a Collection::query
$notes = DB::dql(
<<<'EOF'
SELECT n FROM \App\Entity\Note AS n
WHERE n.is_local = true OR n.id IN (
SELECT act.object_id FROM \App\Entity\Activity AS act
WHERE act.object_type = 'note' AND act.id IN
(SELECT att.activity_id FROM \Component\Notification\Entity\Notification AS att WHERE att.target_id IN
(SELECT a.id FROM \App\Entity\Actor a WHERE a.is_local = true))
)
ORDER BY n.created DESC, n.id DESC
EOF,
);
return [
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Planetary System'),
'should_format' => true,
'notes' => $notes,
];
}
/**
* The Galaxy feed represents everything that is federated out or federated in.
* Given that any local post can be federated out and it's hard to specifically exclude these,
* we simply return everything here, local and remote posts. So, a galaxy.
*/
public function federated(Request $request): array
{
Common::ensureLoggedIn();
$data = $this->query('notes-all:yeah');
return [
'_template' => 'collection/notes.html.twig',
'page_title' => _m('Galaxy'),
'should_format' => true,
'notes' => $data['notes'],
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types = 1);
/**
* @author James Walker <james@status.net>
* @author Craig Andrews <candrews@integralblue.com>
* @author Mikael Nordfeldth <mmn@hethane.se>
*/
namespace Component\FreeNetwork\Controller;
use App\Core\Event;
use Component\FreeNetwork\Util\Discovery;
use Component\FreeNetwork\Util\XrdController;
class HostMeta extends XrdController
{
protected string $default_mimetype = Discovery::JRD_MIMETYPE;
public function setXRD()
{
if (Event::handle('StartHostMetaLinks', [&$this->xrd->links])) {
Event::handle('EndHostMetaLinks', [&$this->xrd->links]);
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);
// 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 Component\FreeNetwork\Controller;
/**
* @package WebFingerPlugin
*
* @author James Walker <james@status.net>
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2010 StatusNet, Inc.
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
use App\Entity\LocalUser;
use App\Util\Common;
use Component\FreeNetwork\Util\Discovery;
use Component\FreeNetwork\Util\XrdController;
use Symfony\Component\HttpFoundation\Request;
class OwnerXrd extends XrdController
{
protected string $default_mimetype = Discovery::XRD_MIMETYPE;
public function handle(Request $request): array
{
$user = LocalUser::siteOwner();
$nick = common_canonical_nickname($user->nickname);
$this->resource = 'acct:' . $nick . '@' . Common::config('site', 'server');
// We have now set $args['resource'] to the configured value, since
// only this local site configuration knows who the owner is!
return parent::handle($request);
}
protected function setXRD()
{
// Check to see if a $config['webfinger']['owner'] has been set
// and then make sure 'subject' is set to that primary identity.
if (!empty($owner = Common::config('webfinger', 'owner'))) {
$this->xrd->aliases[] = $this->xrd->subject;
$this->xrd->subject = Discovery::normalize($owner);
} else {
$this->xrd->subject = $this->resource;
}
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types = 1);
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Component\FreeNetwork\Controller;
use App\Core\Event;
use function App\Core\I18n\_m;
use App\Util\Exception\ClientException;
use App\Util\Exception\NoSuchActorException;
use Component\FreeNetwork\Util\Discovery;
use Component\FreeNetwork\Util\WebfingerResource;
use Component\FreeNetwork\Util\XrdController;
use Symfony\Component\HttpFoundation\Request;
/**
* @package WebFingerPlugin
*
* @author James Walker <james@status.net>
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Diogo Peralta Cordeiro
*/
class Webfinger extends XrdController
{
protected $resource; // string with the resource URI
protected $target; // object of the WebFingerResource class
public function handle(Request $request): array
{
// throws exception if resource is empty
$this->resource = Discovery::normalize($this->string('resource'));
try {
if (Event::handle('StartGetWebFingerResource', [$this->resource, &$this->target, $this->params()])) {
Event::handle('EndGetWebFingerResource', [$this->resource, &$this->target, $this->params()]);
}
} catch (NoSuchActorException $e) {
throw new ClientException($e->getMessage(), 404);
}
if (!$this->target instanceof WebfingerResource) {
// TRANS: Error message when an object URI which we cannot find was requested
throw new ClientException(_m('Resource not found in local database.'), 404);
}
return parent::handle($request);
}
/**
* Configures $this->xrd which will later be printed.
*/
protected function setXRD()
{
$this->xrd->subject = $this->resource;
foreach ($this->target->getAliases() as $alias) {
if ($alias != $this->xrd->subject && !\in_array($alias, $this->xrd->aliases)) {
$this->xrd->aliases[] = $alias;
}
}
$this->target->updateXRD($this->xrd);
}
}

View File

@@ -0,0 +1,46 @@
StartHostMetaLinks: Start /.well-known/host-meta links
- &links: array containing the links elements to be written
EndHostMetaLinks: End /.well-known/host-meta links
- &links: array containing the links elements to be written
StartGetWebFingerResource: Get a WebFingerResource extended object by resource string
- $resource String that contains the requested URI
- &$target WebFingerResource extended object goes here
- $args Array which may contains arguments such as 'rel' filtering values
EndGetWebFingerResource: Last attempts getting a WebFingerResource object
- $resource String that contains the requested URI
- &$target WebFingerResource extended object goes here
- $args Array which may contains arguments such as 'rel' filtering values
StartWebFingerReconstruction: Generate an acct: uri from a Profile object
- $profile: Profile object for which we want a WebFinger ID
- &$acct: String reference where reconstructed ID is stored
EndWebFingerReconstruction: Last attempts to generate an acct: uri from a Profile object
- $profile: Profile object for which we want a WebFinger ID
- &$acct: String reference where reconstructed ID is stored
StartWebFingerNoticeLinks: About to set links for the resource descriptor of a Notice
- $xrd: XML_XRD object being shown
- $target: Notice being shown
EndWebFingerNoticeLinks: Done with links for the resource descriptor of a Notice
- $xrd: XML_XRD object being shown
- $target: Notice being shown
StartWebFingerProfileLinks: About to set links for the resource descriptor of a Profile
- $xrd: XML_XRD object being shown
- $target: Profile being shown
EndWebFingerProfileLinks: Done with links for the resource descriptor of a Profile
- $xrd: XML_XRD object being shown
- $target: Profile being shown
StartDiscoveryMethodRegistration
- $disco: Discovery object that accepts the registrations
EndDiscoveryMethodRegistration: Register remote URI discovery methods
- $disco: Discovery object that accepts the registrations

View File

@@ -0,0 +1,175 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
/**
* ActivityPub implementation for GNU social
*
* @package GNUsocial
*
* @author Diogo Peralta Cordeiro <@diogo.site
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
namespace Component\FreeNetwork\Entity;
use App\Core\DB;
use App\Core\Entity;
use App\Entity\Actor;
use Component\FreeNetwork\Util\Discovery;
use DateTimeInterface;
/**
* Table Definition for free_network_actor_protocol
*
* @copyright 2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class FreeNetworkActorProtocol extends Entity
{
// {{{ Autocode
// @codeCoverageIgnoreStart
private int $actor_id;
private ?string $protocol = null;
private string $addr;
private DateTimeInterface $created;
private DateTimeInterface $modified;
public function setActorId(int $actor_id): self
{
$this->actor_id = $actor_id;
return $this;
}
public function getActorId(): int
{
return $this->actor_id;
}
public function setProtocol(?string $protocol): self
{
$this->protocol = \is_null($protocol) ? null : mb_substr($protocol, 0, 32);
return $this;
}
public function getProtocol(): ?string
{
return $this->protocol;
}
public function setAddr(string $addr): self
{
$this->addr = $addr;
return $this;
}
public function getAddr(): string
{
return $this->addr;
}
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;
}
// @codeCoverageIgnoreEnd
// }}} Autocode
public static function protocolSucceeded(string $protocol, int|Actor $actor_id, string $addr): void
{
$actor_id = \is_int($actor_id) ? $actor_id : $actor_id->getId();
$attributed_protocol = self::getByPK(['actor_id' => $actor_id]);
if (\is_null($attributed_protocol)) {
$attributed_protocol = self::create([
'actor_id' => $actor_id,
'protocol' => $protocol,
'addr' => Discovery::normalize($addr),
]);
} else {
$attributed_protocol->setProtocol($protocol);
}
DB::persist($attributed_protocol);
}
public static function canIActor(string $protocol, int|Actor $actor_id): bool
{
$actor_id = \is_int($actor_id) ? $actor_id : $actor_id->getId();
$attributed_protocol = self::getByPK(['actor_id' => $actor_id])?->getProtocol();
if (\is_null($attributed_protocol)) {
// If it is not attributed, you can go ahead.
return true;
} else {
// If it is attributed, you can on the condition that you're assigned to it.
return $attributed_protocol === $protocol;
}
}
public static function canIAddr(string $protocol, string $target): bool
{
// Normalize $addr, i.e. add 'acct:' if missing
$addr = Discovery::normalize($target);
$attributed_protocol = self::getByPK(['addr' => $addr])?->getProtocol();
if (\is_null($attributed_protocol)) {
// If it is not attributed, you can go ahead.
return true;
} else {
// If it is attributed, you can on the condition that you're assigned to it.
return $attributed_protocol === $protocol;
}
}
public static function schemaDef(): array
{
return [
'name' => 'free_network_actor_protocol',
'fields' => [
'actor_id' => ['type' => 'int', 'not null' => true],
'protocol' => ['type' => 'varchar', 'length' => 32, 'description' => 'the protocol plugin that should handle federation of this actor'],
'addr' => ['type' => 'text', 'not null' => true, 'description' => 'webfinger acct'],
'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' => ['actor_id'],
'foreign keys' => [
'activitypub_actor_actor_id_fkey' => ['actor', ['actor_id' => 'id']],
],
];
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types = 1);
/**
* StatusNet, the distributed open-source microblogging tool
*
* Class for an exception when a WebFinger acct: URI can not be constructed
* using the data we have in a Profile.
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Exception
* @package StatusNet
*
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
*
* @see http://status.net/
*/
namespace Component\FreeNetwork\Exception;
use function App\Core\I18n\_m;
use App\Util\Exception\ServerException;
use Throwable;
/**
* Class for an exception when a WebFinger acct: URI can not be constructed
* using the data we have in a Profile.
*
* @category Exception
* @package StatusNet
*
* @author Mikael Nordfeldth <mmn@hethane.se>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
*
* @see http://status.net/
*/
class WebfingerReconstructionException extends ServerException
{
public function __construct(string $message = '', int $code = 500, ?Throwable $previous = null)
{
// We could log an entry here with the search parameters
parent::__construct(_m('WebFinger URI generation failed.'));
}
}

View File

@@ -0,0 +1,546 @@
<?php
declare(strict_types = 1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Component\FreeNetwork;
use App\Core\DB;
use App\Core\Event;
use App\Core\GSFile;
use App\Core\HTTPClient;
use function App\Core\I18n\_m;
use App\Core\Log;
use App\Core\Modules\Component;
use App\Core\Router;
use App\Entity\Activity;
use App\Entity\Actor;
use App\Entity\LocalUser;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\NicknameEmptyException;
use App\Util\Exception\NicknameException;
use App\Util\Exception\NicknameInvalidException;
use App\Util\Exception\NicknameNotAllowedException;
use App\Util\Exception\NicknameTakenException;
use App\Util\Exception\NicknameTooLongException;
use App\Util\Exception\NoSuchActorException;
use App\Util\Exception\ServerException;
use App\Util\Formatting;
use App\Util\Nickname;
use Component\FreeNetwork\Controller\Feeds;
use Component\FreeNetwork\Controller\HostMeta;
use Component\FreeNetwork\Controller\OwnerXrd;
use Component\FreeNetwork\Controller\Webfinger;
use Component\FreeNetwork\Util\Discovery;
use Component\FreeNetwork\Util\WebfingerResource;
use Component\FreeNetwork\Util\WebfingerResource\WebfingerResourceActor;
use Component\FreeNetwork\Util\WebfingerResource\WebfingerResourceNote;
use Doctrine\Common\Collections\ExpressionBuilder;
use Exception;
use const PREG_SET_ORDER;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use XML_XRD;
use XML_XRD_Element_Link;
/**
* Implements WebFinger (RFC7033) for GNU social, as well as Link-based Resource Descriptor Discovery based on RFC6415,
* Web Host Metadata ('.well-known/host-meta') resource.
*
* @package GNUsocial
*
* @author Mikael Nordfeldth <mmn@hethane.se>
* @author Diogo Peralta Cordeiro <mail@diogo.site>
*/
class FreeNetwork extends Component
{
public const PLUGIN_VERSION = '0.1.0';
public const OAUTH_ACCESS_TOKEN_REL = 'http://apinamespace.org/oauth/access_token';
public const OAUTH_REQUEST_TOKEN_REL = 'http://apinamespace.org/oauth/request_token';
public const OAUTH_AUTHORIZE_REL = 'http://apinamespace.org/oauth/authorize';
private static array $protocols = [];
public function onInitializeComponent(): bool
{
Event::handle('AddFreeNetworkProtocol', [&self::$protocols]);
return Event::next;
}
public function onAddRoute(Router $m): bool
{
// Feeds
$m->connect('feed_network', '/feed/network', [Feeds::class, 'network']);
$m->connect('feed_clique', '/feed/clique', [Feeds::class, 'clique']);
$m->connect('feed_federated', '/feed/federated', [Feeds::class, 'federated']);
$m->connect('freenetwork_hostmeta', '.well-known/host-meta', [HostMeta::class, 'handle']);
$m->connect(
'freenetwork_hostmeta_format',
'.well-known/host-meta.:format',
[HostMeta::class, 'handle'],
['format' => '(xml|json)'],
);
// the resource GET parameter can be anywhere, so don't mention it here
$m->connect('freenetwork_webfinger', '.well-known/webfinger', [Webfinger::class, 'handle']);
$m->connect(
'freenetwork_webfinger_format',
'.well-known/webfinger.:format',
[Webfinger::class, 'handle'],
['format' => '(xml|json)'],
);
$m->connect('freenetwork_ownerxrd', 'main/ownerxrd', [OwnerXrd::class, 'handle']);
return Event::next;
}
public function onCreateDefaultFeeds(int $actor_id, LocalUser $user, int &$ordering)
{
DB::persist(\App\Entity\Feed::create(['actor_id' => $actor_id, 'url' => Router::url($route = 'feed_network'), 'route' => $route, 'title' => _m('Meteorites'), 'ordering' => $ordering++]));
DB::persist(\App\Entity\Feed::create(['actor_id' => $actor_id, 'url' => Router::url($route = 'feed_clique'), 'route' => $route, 'title' => _m('Planetary System'), 'ordering' => $ordering++]));
DB::persist(\App\Entity\Feed::create(['actor_id' => $actor_id, 'url' => Router::url($route = 'feed_federated'), 'route' => $route, 'title' => _m('Galaxy'), 'ordering' => $ordering++]));
return Event::next;
}
public function onStartGetProfileAcctUri(Actor $profile, &$acct): bool
{
$wfr = new WebFingerResourceActor($profile);
try {
$acct = $wfr->reconstructAcct();
} catch (Exception) {
return Event::next;
}
return Event::stop;
}
/**
* Last attempts getting a WebFingerResource object
*
* @param string $resource String that contains the requested URI
* @param null|WebfingerResource $target WebFingerResource extended object goes here
* @param array $args Array which may contains arguments such as 'rel' filtering values
*
* @throws NicknameEmptyException
* @throws NicknameException
* @throws NicknameInvalidException
* @throws NicknameNotAllowedException
* @throws NicknameTakenException
* @throws NicknameTooLongException
* @throws NoSuchActorException
* @throws ServerException
*/
public function onEndGetWebFingerResource(string $resource, ?WebfingerResource &$target = null, array $args = []): bool
{
// * Either we didn't find the profile, then we want to make
// the $profile variable null for clarity.
// * Or we did find it but for a possibly malicious remote
// user who might've set their profile URL to a Note URL
// which would've caused a sort of DoS unless we continue
// our search here by discarding the remote profile.
$profile = null;
if (Discovery::isAcct($resource)) {
$parts = explode('@', mb_substr(urldecode($resource), 5)); // 5 is strlen of 'acct:'
if (\count($parts) === 2) {
[$nick, $domain] = $parts;
if ($domain !== Common::config('site', 'server')) {
throw new ServerException(_m('Remote profiles not supported via WebFinger yet.'));
}
$nick = Nickname::normalize(nickname: $nick, check_already_used: false, check_is_allowed: false);
$freenetwork_actor = LocalUser::getByPK(['nickname' => $nick]);
if (!($freenetwork_actor instanceof LocalUser)) {
throw new NoSuchActorException($nick);
}
$profile = $freenetwork_actor->getActor();
}
} else {
try {
if (Common::isValidHttpUrl($resource)) {
// This means $resource is a valid url
$resource_parts = parse_url($resource);
// TODO: Use URLMatcher
if ($resource_parts['host'] === Common::config('site', 'server')) {
$str = $resource_parts['path'];
// actor_view_nickname
$renick = '/\/@(' . Nickname::DISPLAY_FMT . ')\/?/m';
// actor_view_id
$reuri = '/\/actor\/(\d+)\/?/m';
if (preg_match_all($renick, $str, $matches, PREG_SET_ORDER, 0) === 1) {
$profile = LocalUser::getByPK(['nickname' => $matches[0][1]])->getActor();
} elseif (preg_match_all($reuri, $str, $matches, PREG_SET_ORDER, 0) === 1) {
$profile = Actor::getById((int) $matches[0][1]);
}
}
}
} catch (NoSuchActorException $e) {
// not a User, maybe a Note? we'll try that further down...
// try {
// Log::debug(__METHOD__ . ': Finding User_group URI for WebFinger lookup on resource==' . $resource);
// $group = new User_group();
// $group->whereAddIn('uri', array_keys($alt_urls), $group->columnType('uri'));
// $group->limit(1);
// if ($group->find(true)) {
// $profile = $group->getProfile();
// }
// unset($group);
// } catch (Exception $e) {
// Log::error(get_class($e) . ': ' . $e->getMessage());
// throw $e;
// }
}
}
if ($profile instanceof Actor) {
Log::debug(__METHOD__ . ': Found Profile with ID==' . $profile->getID() . ' for resource==' . $resource);
$target = new WebfingerResourceActor($profile);
return Event::stop; // We got our target, stop handler execution
}
if (!\is_null($note = DB::findOneBy(Note::class, ['url' => $resource], return_null: true))) {
$target = new WebfingerResourceNote($note);
return Event::stop; // We got our target, stop handler execution
}
return Event::next;
}
public function onStartHostMetaLinks(array &$links): bool
{
foreach (Discovery::supportedMimeTypes() as $type) {
$links[] = new XML_XRD_Element_Link(
Discovery::LRDD_REL,
Router::url(id: 'freenetwork_webfinger', args: [], type: Router::ABSOLUTE_URL) . '?resource={uri}',
$type,
isTemplate: true,
);
}
// TODO OAuth connections
//$links[] = new XML_XRD_Element_link(self::OAUTH_ACCESS_TOKEN_REL, common_local_url('ApiOAuthAccessToken'));
//$links[] = new XML_XRD_Element_link(self::OAUTH_REQUEST_TOKEN_REL, common_local_url('ApiOAuthRequestToken'));
//$links[] = new XML_XRD_Element_link(self::OAUTH_AUTHORIZE_REL, common_local_url('ApiOAuthAuthorize'));
return Event::next;
}
/**
* Add a link header for LRDD Discovery
*/
public function onStartShowHTML($action): bool
{
if ($action instanceof ShowstreamAction) {
$resource = $action->getTarget()->getUri();
$url = common_local_url('webfinger') . '?resource=' . urlencode($resource);
foreach ([Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE] as $type) {
header('Link: <' . $url . '>; rel="' . Discovery::LRDD_REL . '"; type="' . $type . '"', false);
}
}
return Event::next;
}
public function onStartDiscoveryMethodRegistration(Discovery $disco): bool
{
$disco->registerMethod('\Component\FreeNetwork\Util\LrddMethod\LrddMethodWebfinger');
return Event::next;
}
public function onEndDiscoveryMethodRegistration(Discovery $disco): bool
{
$disco->registerMethod('\Component\FreeNetwork\Util\LrddMethod\LrddMethodHostMeta');
$disco->registerMethod('\Component\FreeNetwork\Util\LrddMethod\LrddMethodLinkHeader');
$disco->registerMethod('\Component\FreeNetwork\Util\LrddMethod\LrddMethodLinkHtml');
return Event::next;
}
/**
* @throws ClientException
* @throws ServerException
*/
public function onControllerResponseInFormat(string $route, array $accept_header, array $vars, ?Response &$response = null): bool
{
if (!\in_array($route, ['freenetwork_hostmeta', 'freenetwork_hostmeta_format', 'freenetwork_webfinger', 'freenetwork_webfinger_format', 'freenetwork_ownerxrd'])) {
return Event::next;
}
$mimeType = array_intersect(array_values(Discovery::supportedMimeTypes()), $accept_header);
/*
* "A WebFinger resource MUST return a JRD as the representation
* for the resource if the client requests no other supported
* format explicitly via the HTTP "Accept" header. [...]
* The WebFinger resource MUST silently ignore any requested
* representations that it does not understand and support."
* -- RFC 7033 (WebFinger)
* http://tools.ietf.org/html/rfc7033
*/
$mimeType = \count($mimeType) !== 0 ? array_pop($mimeType) : $vars['default_mimetype'];
$headers = [];
if (Common::config('discovery', 'cors')) {
$headers['Access-Control-Allow-Origin'] = '*';
}
$headers['Content-Type'] = $mimeType;
$response = match ($mimeType) {
Discovery::XRD_MIMETYPE => new Response(content: $vars['xrd']->to('xml'), headers: $headers),
Discovery::JRD_MIMETYPE, Discovery::JRD_MIMETYPE_OLD => new JsonResponse(data: $vars['xrd']->to('json'), headers: $headers, json: true),
};
$response->headers->set('cache-control', 'no-store, no-cache, must-revalidate');
return Event::stop;
}
/**
* Webfinger matches: @user@example.com or even @user--one.george_orwell@1984.biz
*
* @param string $text The text from which to extract webfinger IDs
* @param string $preMention Character(s) that signals a mention ('@', '!'...)
*
* @return array the matching IDs (without $preMention) and each respective position in the given string
*/
public static function extractWebfingerIds(string $text, string $preMention = '@'): array
{
$wmatches = [];
$result = preg_match_all(
'/' . Nickname::BEFORE_MENTIONS . preg_quote($preMention, '/') . '(' . Nickname::WEBFINGER_FMT . ')/',
$text,
$wmatches,
\PREG_OFFSET_CAPTURE,
);
if ($result === false) {
Log::error(__METHOD__ . ': Error parsing webfinger IDs from text (preg_last_error==' . preg_last_error() . ').');
return [];
} elseif (($n_matches = \count($wmatches)) != 0) {
Log::debug((sprintf('Found %d matches for WebFinger IDs: %s', $n_matches, print_r($wmatches, true))));
}
return $wmatches[1];
}
/**
* Profile URL matches: @param string $text The text from which to extract URL mentions
*
* @param string $preMention Character(s) that signals a mention ('@', '!'...)
*
* @return array the matching URLs (without @ or acct:) and each respective position in the given string
* @example.com/mublog/user
*/
public static function extractUrlMentions(string $text, string $preMention = '@'): array
{
$wmatches = [];
// In the regexp below we need to match / _before_ URL_REGEX_VALID_PATH_CHARS because it otherwise gets merged
// with the TLD before (but / is in URL_REGEX_VALID_PATH_CHARS anyway, it's just its positioning that is important)
$result = preg_match_all(
'/' . Nickname::BEFORE_MENTIONS . preg_quote($preMention, '/') . '(' . URL_REGEX_DOMAIN_NAME . '(?:\/[' . URL_REGEX_VALID_PATH_CHARS . ']*)*)/',
$text,
$wmatches,
\PREG_OFFSET_CAPTURE,
);
if ($result === false) {
Log::error(__METHOD__ . ': Error parsing profile URL mentions from text (preg_last_error==' . preg_last_error() . ').');
return [];
} elseif (\count($wmatches)) {
Log::debug((sprintf('Found %d matches for profile URL mentions: %s', \count($wmatches), print_r($wmatches, true))));
}
return $wmatches[1];
}
/**
* Find any explicit remote mentions. Accepted forms:
* Webfinger: @user@example.com
* Profile link: @param Actor $sender
*
* @param string $text input markup text
* @param $mentions
*
* @return bool hook return value
* @example.com/mublog/user
*/
public function onEndFindMentions(Actor $sender, string $text, array &$mentions): bool
{
$matches = [];
foreach (self::extractWebfingerIds($text, $preMention = '@') as $wmatch) {
[$target, $pos] = $wmatch;
Log::info("Checking webfinger person '{$target}'");
$actor = null;
$resource_parts = explode($preMention, $target);
if ($resource_parts[1] === Common::config('site', 'server')) {
$actor = LocalUser::getByPK(['nickname' => $resource_parts[0]])->getActor();
} else {
Event::handle('FreeNetworkFindMentions', [$target, &$actor]);
if (\is_null($actor)) {
continue;
}
}
\assert($actor instanceof Actor);
$displayName = !empty($actor->getFullname()) ? $actor->getFullname() : $actor->getNickname() ?? $target; // TODO: we could do getBestName() or getFullname() here
$matches[$pos] = [
'mentioned' => [$actor],
'type' => 'mention',
'text' => $displayName,
'position' => $pos,
'length' => mb_strlen($target),
'url' => $actor->getUri(),
];
}
foreach (self::extractUrlMentions($text) as $wmatch) {
[$target, $pos] = $wmatch;
$url = "https://{$target}";
if (Common::isValidHttpUrl($url)) {
// This means $resource is a valid url
$resource_parts = parse_url($url);
// TODO: Use URLMatcher
if ($resource_parts['host'] === Common::config('site', 'server')) {
$str = $resource_parts['path'];
// actor_view_nickname
$renick = '/\/@(' . Nickname::DISPLAY_FMT . ')\/?/m';
// actor_view_id
$reuri = '/\/actor\/(\d+)\/?/m';
if (preg_match_all($renick, $str, $matches, PREG_SET_ORDER, 0) === 1) {
$actor = LocalUser::getByPK(['nickname' => $matches[0][1]])->getActor();
} elseif (preg_match_all($reuri, $str, $matches, PREG_SET_ORDER, 0) === 1) {
$actor = Actor::getById((int) $matches[0][1]);
} else {
Log::error('Unexpected behaviour onEndFindMentions at FreeNetwork');
throw new ServerException('Unexpected behaviour onEndFindMentions at FreeNetwork');
}
} else {
Log::info("Checking actor address '{$url}'");
$link = new XML_XRD_Element_Link(
Discovery::LRDD_REL,
'https://' . parse_url($url, \PHP_URL_HOST) . '/.well-known/webfinger?resource={uri}',
Discovery::JRD_MIMETYPE,
true, // isTemplate
);
$xrd_uri = Discovery::applyTemplate($link->template, $url);
$response = HTTPClient::get($xrd_uri, ['headers' => ['Accept' => $link->type]]);
if ($response->getStatusCode() !== 200) {
continue;
}
$xrd = new XML_XRD();
switch (GSFile::mimetypeBare($response->getHeaders()['content-type'][0])) {
case Discovery::JRD_MIMETYPE_OLD:
case Discovery::JRD_MIMETYPE:
$type = 'json';
break;
case Discovery::XRD_MIMETYPE:
$type = 'xml';
break;
default:
// fall back to letting XML_XRD auto-detect
Log::debug('No recognized content-type header for resource descriptor body on ' . $xrd_uri);
$type = null;
}
$xrd->loadString($response->getContent(), $type);
$actor = null;
Event::handle('FreeNetworkFoundXrd', [$xrd, &$actor]);
if (\is_null($actor)) {
continue;
}
}
$displayName = $actor->getFullname() ?? $actor->getNickname() ?? $target; // TODO: we could do getBestName() or getFullname() here
$matches[$pos] = [
'mentioned' => [$actor],
'type' => 'mention',
'text' => $displayName,
'position' => $pos,
'length' => mb_strlen($target),
'url' => $actor->getUri(),
];
}
}
foreach ($mentions as $i => $other) {
// If we share a common prefix with a local user, override it!
$pos = $other['position'];
if (isset($matches[$pos])) {
$mentions[$i] = $matches[$pos];
unset($matches[$pos]);
}
}
foreach ($matches as $mention) {
$mentions[] = $mention;
}
return Event::next;
}
public static function notify(Actor $sender, Activity $activity, array $targets, ?string $reason = null): bool
{
foreach (self::$protocols as $protocol) {
$protocol::freeNetworkDistribute($sender, $activity, $targets, $reason);
}
return false;
}
public static function mentionTagToName(string $nickname, string $uri): string
{
return '@' . $nickname . '@' . parse_url($uri, \PHP_URL_HOST);
}
public static function groupTagToName(string $nickname, string $uri): string
{
return '!' . $nickname . '@' . parse_url($uri, \PHP_URL_HOST);
}
/**
* Add fediverse: query expression
* // TODO: adding WebFinger would probably be nice
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): bool
{
if (Formatting::startsWith($term, ['fediverse:'])) {
foreach (self::$protocols as $protocol) {
// 10 is strlen of `fediverse:`
if ($protocol::freeNetworkGrabRemote(mb_substr($term, 10))) {
break;
}
}
}
return Event::next;
}
public function onPluginVersion(array &$versions): bool
{
$versions[] = [
'name' => 'WebFinger',
'version' => self::PLUGIN_VERSION,
'author' => 'Mikael Nordfeldth',
'homepage' => GNUSOCIAL_ENGINE_URL,
// TRANS: Plugin description.
'rawdescription' => _m('WebFinger and LRDD support'),
];
return true;
}
}

View File

@@ -0,0 +1,2 @@
The FreeNetwork component adds WebFinger (RFC7033) lookup and implements Link-based Resource Descriptor Discovery (LRDD)
based on RFC6415, Web Host Metadata.

View File

@@ -0,0 +1,226 @@
<?php
declare(strict_types = 1);
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* This class performs lookups based on methods implemented in separate
* classes, where a resource uri is given. Examples are WebFinger (RFC7033)
* and the LRDD (Link-based Resource Descriptor Discovery) in RFC6415.
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Discovery
* @package GNUsocial
*
* @author James Walker <james@status.net>
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2010 StatusNet, Inc.
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://www.gnu.org/software/social/
*/
namespace Component\FreeNetwork\Util;
use App\Core\Event;
use App\Core\GSFile;
use App\Core\HTTPClient;
use function App\Core\I18n\_m;
use App\Core\Log;
use App\Util\Exception\ClientException;
use Exception;
use XML_XRD;
use XML_XRD_Element_Link;
class Discovery
{
public const LRDD_REL = 'lrdd';
public const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from';
public const HCARD = 'http://microformats.org/profile/hcard';
public const MF2_HCARD = 'http://microformats.org/profile/h-card'; // microformats2 h-card
public const JRD_MIMETYPE_OLD = 'application/json'; // RFC6415 uses this
public const JRD_MIMETYPE = 'application/jrd+json';
public const XRD_MIMETYPE = 'application/xrd+xml';
public array $methods = [];
/**
* Constructor for a discovery object
*
* Registers different discovery methods.
*/
public function __construct()
{
if (Event::handle('StartDiscoveryMethodRegistration', [$this])) {
Event::handle('EndDiscoveryMethodRegistration', [$this]);
}
}
public static function supportedMimeTypes(): array
{
return [
'json' => self::JRD_MIMETYPE,
'jsonold' => self::JRD_MIMETYPE_OLD,
'xml' => self::XRD_MIMETYPE,
];
}
/**
* Register a discovery class
*
* @param string $class Class name
*/
public function registerMethod($class): void
{
$this->methods[] = $class;
}
/**
* Given a user ID, return the first available resource descriptor
*
* @param string $id User ID URI
*
* @return XML_XRD object for the resource descriptor of the id
*/
public function lookup(string $id): XML_XRD
{
// Normalize the incoming $id to make sure we have an uri
$uri = self::normalize($id);
Log::debug(sprintf('Performing discovery for "%s" (normalized "%s")', $id, $uri));
foreach ($this->methods as $class) {
try {
$xrd = new XML_XRD();
Log::debug("LRDD discovery method for '{$uri}': {$class}");
$lrdd = new $class;
$links = $lrdd->discover($uri);
$link = self::getService($links, self::LRDD_REL);
// Load the LRDD XRD
if (!empty($link->template)) {
$xrd_uri = self::applyTemplate($link->template, $uri);
} elseif (!empty($link->href)) {
$xrd_uri = $link->href;
} else {
throw new Exception('No resource descriptor URI in link.');
}
$headers = [];
if (!\is_null($link->type)) {
$headers['Accept'] = $link->type;
}
$response = HTTPClient::get($xrd_uri, ['headers' => $headers]);
if ($response->getStatusCode() !== 200) {
throw new Exception('Unexpected HTTP status code.');
}
switch (GSFile::mimetypeBare($response->getHeaders()['content-type'][0])) {
case self::JRD_MIMETYPE_OLD:
case self::JRD_MIMETYPE:
$type = 'json';
break;
case self::XRD_MIMETYPE:
$type = 'xml';
break;
default:
// fall back to letting XML_XRD auto-detect
Log::debug('No recognized content-type header for resource descriptor body on ' . $xrd_uri);
$type = null;
}
$xrd->loadString($response->getContent(), $type);
return $xrd;
} catch (ClientException $e) {
if ($e->getCode() === 403) {
Log::info(sprintf('%s: Aborting discovery on URL %s: %s', $class, $uri, $e->getMessage()));
break;
}
} catch (Exception $e) {
Log::info(sprintf('%s: Failed for %s: %s', $class, $uri, $e->getMessage()));
continue;
}
}
// TRANS: Exception. %s is an ID.
throw new Exception(sprintf(_m('Unable to find services for %s.'), $id));
}
/**
* Given an array of links, returns the matching service
*
* @param array $links Links to check (as instances of XML_XRD_Element_Link)
* @param string $service Service to find
*
* @return XML_XRD_Element_Link $link
*/
public static function getService(array $links, $service): XML_XRD_Element_Link
{
foreach ($links as $link) {
if ($link->rel === $service) {
return $link;
}
Log::debug('LINK: rel ' . $link->rel . ' !== ' . $service);
}
throw new Exception('No service link found');
}
/**
* Given a "user id" make sure it's normalized to an acct: uri
*
* @param string $uri User ID to normalize
*
* @return string normalized acct: URI
*/
public static function normalize(string $uri): string
{
$parts = parse_url($uri);
// If we don't have a scheme, but the path implies user@host,
// though this is far from a perfect matching procedure...
if (!isset($parts['scheme']) && isset($parts['path'])
&& preg_match('/[\w@\w]/u', $parts['path'])) {
return 'acct:' . $uri;
}
return $uri;
}
public static function isAcct(string $uri): bool
{
return mb_strtolower(mb_substr($uri, 0, 5)) == 'acct:';
}
/**
* Apply a template using an ID
*
* Replaces {uri} in template string with the ID given.
*
* @param string $template Template to match
* @param string $uri URI to replace with
*
* @return string replaced values
*/
public static function applyTemplate($template, $uri): string
{
return str_replace('{uri}', urlencode($uri), $template);
}
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types = 1);
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* Parse HTTP response for interesting Link: headers
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Discovery
* @package StatusNet
*
* @author James Walker <james@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://status.net/
*/
namespace Component\FreeNetwork\Util;
/**
* Class to represent Link: headers in an HTTP response
*
* Since these are a fairly important part of Hammer-stack discovery, they're
* reified and implemented here.
*
* @category Discovery
* @package StatusNet
*
* @author James Walker <james@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://status.net/
* @see Discovery
*/
class LinkHeader
{
public $href;
public $rel;
public $type;
/**
* Initialize from a string
*
* @param string $str Link: header value
*/
public function __construct(string $str = '')
{
preg_match('/^<[^>]+>/', $str, $uri_reference);
//if (empty($uri_reference)) return;
$this->href = trim($uri_reference[0], '<>');
$this->rel = [];
$this->type = null;
// remove uri-reference from header
$str = mb_substr($str, mb_strlen($uri_reference[0]));
// parse link-params
$params = explode(';', $str);
foreach ($params as $param) {
if (empty($param)) {
continue;
}
[$param_name, $param_value] = explode('=', $param, 2);
$param_name = trim($param_name);
$param_value = preg_replace('(^"|"$)', '', trim($param_value));
// for now we only care about 'rel' and 'type' link params
// TODO do something with the other links-params
switch ($param_name) {
case 'rel':
$this->rel = trim($param_value);
break;
case 'type':
$this->type = trim($param_value);
}
}
}
/**
* Given an HTTP response, return the requested Link: header
*
* @param HTTP_Request2_Response $response response to check
* @param string $rel relationship to look for
* @param string $type media type to look for
*
* @return LinkHeader discovered header, or null on failure
*/
public static function getLink($response, $rel = null, $type = null)
{
$headers = $response->getHeader('Link');
if ($headers) {
// Can get an array or string, so try to simplify the path
if (!\is_array($headers)) {
$headers = [$headers];
}
foreach ($headers as $header) {
$lh = new self($header);
if ((\is_null($rel) || $lh->rel == $rel) && (\is_null($type) || $lh->type == $type)) {
return $lh->href;
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util;
use App\Core\Event;
use App\Core\HTTPClient;
use Exception;
use Symfony\Contracts\HttpClient\ResponseInterface;
use XML_XRD;
/**
* Abstract class for LRDD discovery methods
*
* Objects that extend this class can retrieve an array of
* resource descriptor links for the URI. The array consists
* of XML_XRD_Element_Link elements.
*
* @category Discovery
* @package StatusNet
*
* @author James Walker <james@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://status.net/
*/
abstract class LrddMethod
{
protected $xrd;
public function __construct()
{
$this->xrd = new XML_XRD();
}
/**
* Discover interesting info about the URI
*
* @param string $uri URI to inquire about
*
* @return array of XML_XRD_Element_Link elements to discovered resource descriptors
*/
abstract public function discover($uri);
protected function fetchUrl($url, $method = 'get'): ResponseInterface
{
// If we have a blacklist enabled, let's check against it
Event::handle('UrlBlacklistTest', [$url]);
$response = HTTPClient::$method($url);
if ($response->getStatusCode() !== 200) {
throw new Exception('Unexpected HTTP status code.');
}
return $response;
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util\LrddMethod;
// 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/>.
use App\Core\Log;
use Component\FreeNetwork\Util\Discovery;
use Component\FreeNetwork\Util\LrddMethod;
use Exception;
/**
* Implementation of discovery using host-meta file
*
* Discovers resource descriptor file for a user by going to the
* organization's host-meta file and trying to find a template for LRDD.
*
* @category Discovery
* @package GNUsocial
*
* @author James Walker <james@status.net>
* @copyright 2010 StatusNet, Inc.
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class LrddMethodHostMeta extends LRDDMethod
{
/**
* For RFC6415 and HTTP URIs, fetch the host-meta file
* and look for LRDD templates
*/
public function discover($uri)
{
// This is allowed for RFC6415 but not the 'WebFinger' RFC7033.
$try_schemes = ['https', 'http'];
$scheme = mb_strtolower(parse_url($uri, \PHP_URL_SCHEME));
switch ($scheme) {
case 'acct':
// We can't use parse_url data for this, since the 'host'
// entry is only set if the scheme has '://' after it.
$parts = explode('@', parse_url($uri, \PHP_URL_PATH), 2);
if (!Discovery::isAcct($uri) || \count($parts) != 2) {
throw new Exception('Bad resource URI: ' . $uri);
}
[, $domain] = $parts;
break;
case 'http':
case 'https':
$domain = mb_strtolower(parse_url($uri, \PHP_URL_HOST));
$try_schemes = [$scheme];
break;
default:
throw new Exception('Unable to discover resource descriptor endpoint.');
}
foreach ($try_schemes as $scheme) {
$url = $scheme . '://' . $domain . '/.well-known/host-meta';
try {
$response = self::fetchUrl($url);
$this->xrd->loadString($response->getContent());
} catch (Exception $e) {
Log::debug('LRDD could not load resource descriptor: ' . $url . ' (' . $e->getMessage() . ')');
continue;
}
return $this->xrd->links;
}
throw new Exception('Unable to retrieve resource descriptor links.');
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util\LrddMethod;
use App\Core\Log;
use Component\FreeNetwork\Util\LinkHeader;
use Component\FreeNetwork\Util\LrddMethod;
use Exception;
use XML_XRD_Element_Link;
/**
* Implementation of discovery using HTTP Link header
*
* Discovers XRD file for a user by fetching the URL and reading any
* Link: headers in the HTTP response.
*
* @category Discovery
* @package StatusNet
*
* @author James Walker <james@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://status.net/
*/
class LrddMethodLinkHeader extends LRDDMethod
{
/**
* For HTTP IDs fetch the URL and look for Link headers.
*
* @todo fail out of WebFinger URIs faster
*/
public function discover($uri)
{
$response = self::fetchUrl($uri, 'head');
$link_header = $response->getHeaders()['link'][0];
if (empty($link_header)) {
throw new Exception('No Link header found');
}
Log::debug('LRDD LinkHeader found: ' . var_export($link_header, true));
return self::parseHeader($link_header);
}
/**
* Given a string or array of headers, returns JRD-like assoc array
*
* @param array|string $header string or array of strings for headers
*
* @return array of associative arrays in JRD-like array format
*/
protected static function parseHeader($header)
{
$lh = new LinkHeader($header);
$link = new XML_XRD_Element_Link($lh->rel, $lh->href, $lh->type);
return [$link];
}
}

View File

@@ -0,0 +1,105 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util\LrddMethod;
// 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/>.
use Component\FreeNetwork\Util\LrddMethod;
use XML_XRD_Element_Link;
/**
* Implementation of discovery using HTML <link> element
*
* Discovers XRD file for a user by fetching the URL and reading any
* <link> elements in the HTML response.
*
* @category Discovery
* @package GNUsocial
*
* @author James Walker <james@status.net>
* @copyright 2010 StatusNet, Inc.
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class LrddMethodLinkHtml extends LRDDMethod
{
/**
* For HTTP IDs, fetch the URL and look for <link> elements
* in the HTML response.
*
* @todo fail out of WebFinger URIs faster
*/
public function discover($uri)
{
$response = self::fetchUrl($uri);
return self::parse($response->getContent());
}
/**
* Parse HTML and return <link> elements
*
* Given an HTML string, scans the string for <link> elements
*
* @param string $html HTML to scan
*
* @return array array of associative arrays in JRD-ish array format
*/
public function parse($html)
{
$links = [];
preg_match('/<head(\s[^>]*)?>(.*?)<\/head>/is', $html, $head_matches);
if (\count($head_matches) != 3) {
return [];
}
[, , $head_html] = $head_matches;
preg_match_all('/<link\s[^>]*>/i', $head_html, $link_matches);
foreach ($link_matches[0] as $link_html) {
$link_url = null;
$link_rel = null;
$link_type = null;
preg_match('/\srel=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $rel_matches);
if (\count($rel_matches) > 3) {
$link_rel = $rel_matches[3];
} elseif (\count($rel_matches) > 1) {
$link_rel = $rel_matches[1];
}
preg_match('/\shref=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $href_matches);
if (\count($href_matches) > 3) {
$link_uri = $href_matches[3];
} elseif (\count($href_matches) > 1) {
$link_uri = $href_matches[1];
}
preg_match('/\stype=(("|\')([^\\2]*?)\\2|[^"\'\s]+)/i', $link_html, $type_matches);
if (\count($type_matches) > 3) {
$link_type = $type_matches[3];
} elseif (\count($type_matches) > 1) {
$link_type = $type_matches[1];
}
$links[] = new XML_XRD_Element_Link($link_rel, $link_uri, $link_type);
}
return $links;
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types = 1);
// 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 Component\FreeNetwork\Util\LrddMethod;
use Component\FreeNetwork\Util\Discovery;
use Component\FreeNetwork\Util\LrddMethod;
use Exception;
use XML_XRD_Element_Link;
/**
* Implementation of WebFinger resource discovery (RFC7033)
*
* @category Discovery
* @package GNUsocial
*
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class LrddMethodWebfinger extends LRDDMethod
{
/**
* Simply returns the WebFinger URL over HTTPS at the uri's domain:
* https://{domain}/.well-known/webfinger?resource={uri}
*/
public function discover($uri)
{
$parts = explode('@', parse_url($uri, \PHP_URL_PATH), 2);
if (!Discovery::isAcct($uri) || \count($parts) != 2) {
throw new Exception('Bad resource URI: ' . $uri);
}
[, $domain] = $parts;
if (!filter_var($domain, \FILTER_VALIDATE_IP)
&& !filter_var(gethostbyname($domain), \FILTER_VALIDATE_IP)) {
throw new Exception('Bad resource host.');
}
$link = new XML_XRD_Element_Link(
Discovery::LRDD_REL,
'https://' . $domain . '/.well-known/webfinger?resource={uri}',
Discovery::JRD_MIMETYPE,
true, // isTemplate
);
return [$link];
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util;
use App\Core\Entity;
use App\Util\Common;
use App\Util\Exception\ServerException;
use XML_XRD;
/**
* WebFinger resource parent class
*
* @package GNUsocial
*
* @author Mikael Nordfeldth
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://status.net/
*/
abstract class WebfingerResource
{
protected $identities = [];
protected $object;
protected $type;
public function __construct(Entity $object)
{
$this->object = $object;
}
public function getObject()
{
if ($this->object === null) {
throw new ServerException('Object is not set');
}
return $this->object;
}
/**
* List of alternative IDs of a certain Actor
*/
public function getAliases(): array
{
$aliases = $this->object->getAliasesWithIDs();
// Some sites have changed from http to https and still want
// (because remote sites look for it) verify that they are still
// the same identity as they were on HTTP. Should NOT be used if
// you've run HTTPS all the time!
if (Common::config('fix', 'legacy_http')) {
foreach ($aliases as $alias => $id) {
if (!mb_strtolower(parse_url($alias, \PHP_URL_SCHEME)) === 'https') {
continue;
}
$aliases[preg_replace('/^https:/i', 'http:', $alias, 1)] = $id;
}
}
// return a unique set of aliases by extracting only the keys
return array_keys($aliases);
}
abstract public function updateXRD(XML_XRD $xrd);
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util\WebfingerResource;
use App\Core\Event;
use App\Core\Log;
use App\Core\Router;
use App\Entity\Actor;
use App\Util\Common;
use Component\FreeNetwork\Exception\WebfingerReconstructionException;
use Component\FreeNetwork\Util\WebfingerResource;
use XML_XRD;
use XML_XRD_Element_Link;
/**
* WebFinger resource for Profile objects
*
* @package GNUsocial
*
* @author Mikael Nordfeldth
* @author Diogo Peralta Cordeiro
* @copyright 2013, 2021 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*/
class WebfingerResourceActor extends WebFingerResource
{
public const PROFILEPAGE = 'http://webfinger.net/rel/profile-page';
public function __construct(?Actor $object = null)
{
// The type argument above verifies that it's our class
parent::__construct($object);
}
public function getAliases(): array
{
$aliases = [];
try {
// Try to create an acct: URI if we're dealing with a profile
$aliases[] = $this->reconstructAcct();
} catch (WebFingerReconstructionException $e) {
Log::debug("WebFinger reconstruction for Profile failed (id={$this->object->getID()})");
}
return array_merge($aliases, parent::getAliases());
}
/**
* Reconstruct WebFinger acct: from object
*
* @throws WebfingerReconstructionException
*
* @return null|array|false|mixed|string|string[]
*/
public function reconstructAcct()
{
$acct = null;
if (Event::handle('StartWebFingerReconstruction', [$this->object, &$acct])) {
// TODO: getUri may not always give us the correct host on remote users?
$host = parse_url($this->object->getUri(Router::ABSOLUTE_URL), \PHP_URL_HOST);
if (empty($this->object->getNickname()) || empty($host)) {
throw new WebFingerReconstructionException(print_r($this->object, true));
}
$acct = mb_strtolower(sprintf('acct:%s@%s', $this->object->getNickname(), $host));
Event::handle('EndWebFingerReconstruction', [$this->object, &$acct]);
}
return $acct;
}
public function updateXRD(XML_XRD $xrd)
{
if (Event::handle('StartWebFingerProfileLinks', [$xrd, $this->object])) {
// Profile page, can give more metadata from Link header or HTML parsing
$xrd->links[] = new XML_XRD_Element_Link(
self::PROFILEPAGE,
$this->object->getUrl(Router::ABSOLUTE_URL),
'text/html',
);
// // XFN
// $xrd->links[] = new XML_XRD_Element_Link('http://gmpg.org/xfn/11',
// $this->object->getUrl(), 'text/html');
// if ($this->object->isPerson()) {
// // FOAF for user
// $xrd->links[] = new XML_XRD_Element_Link('describedby',
// common_local_url('foaf',
// ['nickname' => $this->object->getNickname()]),
// 'application/rdf+xml');
//
// // nickname discovery for apps etc.
// $link = new XML_XRD_Element_Link('http://apinamespace.org/atom',
// common_local_url('ApiAtomService',
// ['id' => $this->object->getNickname()]),
// 'application/atomsvc+xml');
// // XML_XRD must implement changing properties first $link['http://apinamespace.org/atom/username'] = $this->object->getNickname();
// $xrd->links[] = clone $link;
//
// $link = new XML_XRD_Element_Link('http://apinamespace.org/twitter', $apiRoot);
// // XML_XRD must implement changing properties first $link['http://apinamespace.org/twitter/username'] = $this->object->getNickname();
// $xrd->links[] = clone $link;
// } elseif ($this->object->isGroup()) {
// // FOAF for group
// $xrd->links[] = new XML_XRD_Element_Link('describedby',
// common_local_url('foafgroup',
// ['nickname' => $this->object->getNickname()]),
// 'application/rdf+xml');
// }
Event::handle('EndWebFingerProfileLinks', [$xrd, $this->object]);
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util\WebfingerResource;
use App\Core\Event;
use App\Entity\Note;
use Component\FreeNetwork\Util\WebfingerResource;
use PharIo\Manifest\InvalidUrlException;
use XML_XRD;
use XML_XRD_Element_Link;
/**
* WebFinger resource for Note objects
*
* @package GNUsocial
*
* @author Mikael Nordfeldth
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
*
* @see http://status.net/
*/
class WebfingerResourceNote extends WebfingerResource
{
public function __construct(?Note $object = null)
{
// The type argument above verifies that it's our class
parent::__construct($object);
}
/**
* Update given XRD with self's data
*/
public function updateXRD(XML_XRD $xrd)
{
if (Event::handle('StartWebFingerNoticeLinks', [$xrd, $this->object])) {
if ($this->object->isLocal()) {
$xrd->links[] = new XML_XRD_Element_Link(
'alternate',
common_local_url(
'ApiStatusesShow',
['id' => $this->object->id,
'format' => 'atom', ],
),
'application/atom+xml',
);
$xrd->links[] = new XML_XRD_Element_Link(
'alternate',
common_local_url(
'ApiStatusesShow',
['id' => $this->object->id,
'format' => 'json', ],
),
'application/json',
);
} else {
try {
$xrd->links[] = new XML_XRD_Element_Link(
'alternate',
$this->object->getUrl(),
'text/html',
);
} catch (InvalidUrlException $e) {
// don't do a fallback in webfinger
}
}
Event::handle('EndWebFingerNoticeLinks', [$xrd, $this->object]);
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util;
use App\Core\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use XML_XRD;
abstract class XrdController extends Controller
{
protected string $default_mimetype = Discovery::JRD_MIMETYPE;
protected XML_XRD $xrd;
/*
* Configures $this->xrd which will later be printed. Must be
* implemented by child classes.
*/
abstract protected function setXRD();
public function __construct(RequestStack $requestStack)
{
parent::__construct($requestStack);
if ($this->request->headers->get('format', null) === null) {
$this->request->headers->set('format', $this->default_mimetype);
}
$this->xrd = new XML_XRD();
}
public function handle(Request $request): array
{
$this->setXRD();
return ['xrd' => $this->xrd, 'default_mimetype' => $this->default_mimetype];
}
}

View File

@@ -0,0 +1,5 @@
{
"require": {
"pear/xml_xrd": "^0.3.1"
}
}

View File

@@ -0,0 +1,23 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-07-18 12:38+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#. TRANS: Plugin description.
#: LRDDPlugin.php:64
msgid "Implements LRDD support for GNU Social."
msgstr ""

View File

@@ -0,0 +1,28 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-07-18 12:38+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#. TRANS: Error message when an object URI which we cannot find was requested
#: actions/webfinger.php:49
msgid "Resource not found in local database."
msgstr ""
#. TRANS: Plugin description.
#: WebFingerPlugin.php:230
msgid "Adds WebFinger lookup to GNU Social"
msgstr ""

View File

@@ -0,0 +1,23 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 08:45+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Afrikaans (http://www.transifex.com/gnu-social/gnu-social/language/af/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: af\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: Plugin description.
#: LRDDPlugin.php:61
msgid "Implements LRDD support for GNU Social."
msgstr ""

View File

@@ -0,0 +1,28 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 12:23+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Afrikaans (http://www.transifex.com/gnu-social/gnu-social/language/af/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: af\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: Error message when an object URI which we cannot find was requested
#: actions/webfinger.php:49
msgid "Resource not found in local database."
msgstr ""
#. TRANS: Plugin description.
#: WebFingerPlugin.php:147
msgid "Adds WebFinger lookup to GNU Social"
msgstr ""

View File

@@ -0,0 +1,23 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 08:45+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Arabic (http://www.transifex.com/gnu-social/gnu-social/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. TRANS: Plugin description.
#: LRDDPlugin.php:61
msgid "Implements LRDD support for GNU Social."
msgstr ""

View File

@@ -0,0 +1,28 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 12:23+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Arabic (http://www.transifex.com/gnu-social/gnu-social/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. TRANS: Error message when an object URI which we cannot find was requested
#: actions/webfinger.php:49
msgid "Resource not found in local database."
msgstr ""
#. TRANS: Plugin description.
#: WebFingerPlugin.php:147
msgid "Adds WebFinger lookup to GNU Social"
msgstr ""

View File

@@ -0,0 +1,23 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 08:45+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Arabic (Egypt) (http://www.transifex.com/gnu-social/gnu-social/language/ar_EG/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar_EG\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. TRANS: Plugin description.
#: LRDDPlugin.php:61
msgid "Implements LRDD support for GNU Social."
msgstr ""

View File

@@ -0,0 +1,28 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 12:23+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Arabic (Egypt) (http://www.transifex.com/gnu-social/gnu-social/language/ar_EG/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar_EG\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. TRANS: Error message when an object URI which we cannot find was requested
#: actions/webfinger.php:49
msgid "Resource not found in local database."
msgstr ""
#. TRANS: Plugin description.
#: WebFingerPlugin.php:147
msgid "Adds WebFinger lookup to GNU Social"
msgstr ""

View File

@@ -0,0 +1,23 @@
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 08:45+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Asturian (http://www.transifex.com/gnu-social/gnu-social/language/ast/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ast\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. TRANS: Plugin description.
#: LRDDPlugin.php:61
msgid "Implements LRDD support for GNU Social."
msgstr ""

Some files were not shown because too many files have changed in this diff Show More