Compare commits

..

1096 Commits

Author SHA1 Message Date
Eliseu Amaro 010f70e432
[TWIG][CSS][Accessibility] Base template accessibility improvements. Applying more semantic HTML5 tags and aria to describe content actions. 2021-08-20 11:38:32 +01:00
Eliseu Amaro fc310a0b4e
[CSS] Fixed Favourite button background size and colour. Fixed right panel incorrect font size. 2021-08-20 11:38:32 +01:00
Hugo Sales d398456be8
[TESTS] Fix namespace on Controller Security test 2021-08-20 11:34:08 +01:00
Hugo Sales 3288d48b8a
[TESTS] Fix broken tests and expand tests around Attachments 2021-08-20 11:34:07 +01:00
Hugo Sales 94edde001c
[TESTS] Fix Common test 2021-08-20 11:34:06 +01:00
Hugo Sales 41d759428f
[TESTS][DataFixtures] Use Temporary file instead of an ad-hoc solution to copy the upload files 2021-08-20 11:32:49 +01:00
Diogo Peralta Cordeiro 469cd97b9b
[Posting] Respect process_links setting 2021-08-19 19:37:56 +01:00
Diogo Peralta Cordeiro 8a01224feb
[FileQuota] Update plugin to respect the new entities 2021-08-19 19:37:56 +01:00
Diogo Peralta Cordeiro 61d558b371
[CORE] Fix path configuration 2021-08-19 01:58:29 +01:00
Diogo Peralta Cordeiro 8c5486ba13
[FIXTURES] Catch any exception, we don't have VIPS-related only 2021-08-19 01:58:29 +01:00
Diogo Peralta Cordeiro a7d4a56b14
[TESTS][Forms] Respect new naming conventions 2021-08-19 01:57:19 +01:00
Diogo Peralta Cordeiro bef23f20bc
[TESTS] remove accidentally duplicate sample upload 2021-08-19 01:07:51 +01:00
Diogo Peralta Cordeiro 22ad2bd5cc
[VideoEncoder] Port plugin to v3 properly 2021-08-18 23:38:03 +01:00
Diogo Peralta Cordeiro 968a425459
[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-08-18 23:38:03 +01:00
Diogo Peralta Cordeiro 30975111d9
[ImageEncoder] Make plugin respect instance config and use the new core interface 2021-08-18 23:38:03 +01:00
Diogo Peralta Cordeiro 7c85d312ed
[ENTITY][AttachmentThumbnail] Improve the way EncoderPlugins participate in the thumbnail process 2021-08-18 23:38:03 +01:00
Diogo Peralta Cordeiro c50e3324ef
[TWIG][Attachments] Don't throw event with mimetype in its name 2021-08-18 23:38:02 +01:00
Diogo Peralta Cordeiro aebc5358b5
[Core][GSFile] Improve the way EncoderPlugins participate in the file sanitization process 2021-08-18 23:38:02 +01:00
Diogo Peralta Cordeiro 24b3e22f73
[ENTITY][Attachment] Respect rfc6838#section-4.2 mimetype length 2021-08-18 23:38:02 +01:00
Diogo Peralta Cordeiro 6e9cde8a5c
[TESTS][Controller][AdminTest] Update int tests to use attachment file_quota instead, as we deleted attachment max width 2021-08-18 23:37:57 +01:00
Diogo Peralta Cordeiro 4089fc692d
[CONFIG] Add setting for attachment sanitization 2021-08-18 22:17:40 +01:00
Diogo Peralta Cordeiro f25759d60b
[CONTROLLER][Attachment] Some attachments may not have dimensions 2021-08-18 22:17:39 +01:00
Hugo Sales a383021992
[DOCUMENTATION] Update documentation regarding route accept formats 2021-08-18 21:40:08 +01:00
Hugo Sales d6e6e56814
[ROUTER][UTIL] Allow specifying the Accept format for a route 2021-08-18 21:33:07 +01:00
Hugo Sales 9afa265c30
[DOCKER] Update PHP docker container to include ffmpeg, for the VideoEncoderPlugin 2021-08-18 21:20:25 +01:00
Hugo Sales 027c9a9324
[MODULES][PLUGINS] Move noteActionHandle utility to NoteHandlerPlugin which plugins which handle actions on notes should extend 2021-08-18 19:14:24 +01:00
Hugo Sales 2c10ce5cfc
[UI] Rename all forms to more specific names, to avoid form name collisions 2021-08-18 18:38:54 +01:00
Hugo Sales c03c6f1bb5
[UTIL][Form] Disallow using very generic form names, as they can collide with other forms in the same page 2021-08-18 18:35:03 +01:00
Hugo Sales be5328cdc5
[TESTS] Fix LocalUserTest, i forgor to boot the kernel 2021-08-18 18:09:26 +01:00
Hugo Sales 4a781d483a
[TESTS] Fix tests broken with rebased commits 2021-08-18 17:40:37 +01:00
Hugo Sales 5bcabbb025
[TESTS] Raise test coverage for Link to 100% 2021-08-18 17:35:10 +01:00
Hugo Sales b6cd58d501
[TESTS] Raise test coverage of LocalGroup to 100% 2021-08-18 17:35:10 +01:00
Hugo Sales 2ba6f66b7f
[TESTS] Raise test coverage of LocalUser to 100% 2021-08-18 17:35:10 +01:00
Hugo Sales 152beb5798
[TESTS] Raise GSActor test coverage to 100% 2021-08-18 17:35:10 +01:00
Hugo Sales 96612fcd43
[TESTS][Attachment][AttachmentThumbnail][GSFile] Reorganize tests and raise test coverage to 100% 2021-08-18 17:35:10 +01:00
Hugo Sales 4cda3fc645
[ATTACHMENTS][AttachmentThumbnail] Fix implementation of predictScalingValues and small fixes 2021-08-18 17:35:10 +01:00
Hugo Sales b72fcd2a05
[TESTS] Add code coverage annotations to entities 2021-08-18 17:35:10 +01:00
Hugo Sales 9018b1301a
[TESTS] Raise test coverage for Note to 100% 2021-08-18 17:35:10 +01:00
Hugo Sales 20901d26df
[Note] Fix scope check for group notes, move away from SQL, to DQL 2021-08-18 17:35:10 +01:00
Hugo Sales 0b3ebf841d
[TESTS][DataFixtures] Add user, self follows, group member and group scope note 2021-08-18 17:35:09 +01:00
Hugo Sales fd1bd9838d
[TOOLS] Disable command echo in Makefile 2021-08-18 17:35:09 +01:00
Hugo Sales 7320c6834f
[UI][Attachment] Use Attachment methods to get the proper URL, rather than crafting it in a template 2021-08-18 17:35:09 +01:00
Hugo Sales 9c533a54a7
[ATTACHMENTS] Ensure thumbnail dimensions are bounded and change way cropping is implemented 2021-08-18 17:35:09 +01:00
Diogo Peralta Cordeiro 5be4c6a22e
[VideoEncoder] Add plugin composer dependency php-ffmpeg/php-ffmpeg 2021-08-18 14:46:49 +01:00
Diogo Peralta Cordeiro 61500c5223
[Embed] Move composer dependency embed/embed from core to plugin 2021-08-18 14:46:49 +01:00
Diogo Peralta Cordeiro f7c426e81c
[ENTITY][Link] self urls can't be considered a remote url 2021-08-18 14:34:49 +01:00
Diogo Peralta Cordeiro 40f2f5f977
[Embed][StoreRemoteMedia] Re-add {white,black}list check config 2021-08-18 14:15:53 +01:00
Diogo Peralta Cordeiro b7b54b8a07
[Posting] Fix wrong usage of DB::findBy 2021-08-18 12:23:36 +01:00
Diogo Peralta Cordeiro a6e41d3bd8
[DOCS][Developer] Recommend reading the tests cases for when the doc doesn't cover 2021-08-17 23:39:45 +01:00
Diogo Peralta Cordeiro d4ad0cc3d4
[DOCS][Developer] Update storage documentation 2021-08-17 23:37:19 +01:00
Diogo Peralta Cordeiro 3af33d1317
[ImageEncoder] Fix ImageSanitization, it should never modify the input image 2021-08-17 23:35:35 +01:00
Eliseu Amaro 2448d83ace [CSS] Hotfix: Figure captions do not overflow. 2021-08-17 21:59:54 +01:00
Diogo Peralta Cordeiro 923ff309fe
[Posting] Store uploaded filenames as titles 2021-08-17 21:48:38 +01:00
Diogo Peralta Cordeiro f039c86578
[Avatar] Preserve uploaded filename and use Avatar's own route instead of attachment 2021-08-17 21:48:37 +01:00
Diogo Peralta Cordeiro e76e3b710b
Remove weird empty template 2021-08-17 21:48:37 +01:00
Eliseu Amaro 022a9476cc [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-08-17 21:43:58 +01:00
Hugo Sales 4e5f9a51f0
[TESTS] Hot-fix Security controller tests, broken with ongoing form rendering changes 2021-08-17 20:54:46 +01:00
Eliseu Amaro 44593f2ab4
[TWIG][CSS] More consistent classes. Checkbox styling done. Register and Login pages now done. 2021-08-17 20:52:22 +01:00
Eliseu Amaro 2ae1198704
[UTIL][FORM] Password form now shows the proper HTML class, and it's respective label. 2021-08-17 20:52:22 +01:00
Eliseu Amaro 9e52bd127f
[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-08-17 20:52:21 +01:00
Eliseu Amaro 41b45435ff
[DOC] Fixed installation.md typos. 2021-08-17 20:52:21 +01:00
Hugo Sales e9fa41c5a8
[FORM] Fix bug where options were passed in the data parameter 2021-08-17 20:52:21 +01:00
Diogo Peralta Cordeiro 48c11a3fda
[DOCS][Developer] Add an introduction 2021-08-17 20:27:54 +01:00
Diogo Peralta Cordeiro fa1585bd00
[DOCS][Developer] httpclient: Add an example of lazyness care 2021-08-17 20:27:54 +01:00
Diogo Peralta Cordeiro f5918d8d5c
[DOCS][Developer] Paradigms: apply XRevan86 fixes and remove the return types section, we must revisit it later.. 2021-08-17 20:27:54 +01:00
Diogo Peralta Cordeiro ff4d31404b
[DOCS][Developer] Remove low level index, we will approach these themes in another manner
Fix some broken links
2021-08-17 20:27:54 +01:00
Hugo Sales ac6f2bed5e
[TOOLS] Add make command 'database-force-schema-update' to update the database schema and 'redis-shell' 2021-08-17 01:31:52 +01:00
Hugo Sales 5cb45fcd66
Duplicate public/assets/css/bg.jpg history in tests/sample-uploads/attachment-lifecycle-target.jpg history. 2021-08-17 01:31:52 +01:00
Hugo Sales dd22894f66
[TESTS][TOOLS] Always stop containers regardless of test success 2021-08-17 01:31:52 +01:00
Hugo Sales 11178289fa
[DEPENDENCIES] Update dependencies 2021-08-16 18:05:24 +01:00
Hugo Sales 1e8beefb07
[PLUGINS][ENTITY][Cover][ProfileColor] Clean up after interns and move entity defintions to be inside the corresponding plugin, rather than in core 2021-08-16 17:20:33 +01:00
Hugo Sales f68a2ce481
[TESTS] Raise test coverage for GSFile to 100% 2021-08-16 17:11:28 +01:00
Hugo Sales b0f5352a53
[TESTS] Raise test coverage for Attachment controller to 100% 2021-08-16 17:11:05 +01:00
Hugo Sales 69ff8c2750
[CORE][GSFile] Use pathinfo rather than regular expressions and don't attempt to persist an already persisted entity 2021-08-16 17:10:33 +01:00
Hugo Sales 355b26221d
[CONTROLLER][Attachment] Small refactor and add testing annotation 2021-08-16 17:09:44 +01:00
Hugo Sales d4c3e26f50
[TESTS] Remove copied upload test files, if upload failed 2021-08-16 17:09:02 +01:00
Hugo Sales 5bd5c25dcf
[TESTS] Add coverage ignore flags to trivial methods 2021-08-16 17:08:29 +01:00
Hugo Sales e30ae79eb7
[DB][DOCUMENTATION] Explain limit: 2 in findOneBy 2021-08-16 17:07:00 +01:00
Hugo Sales fb861ed41f
[UTIL][EXCEPTIONS] Introduce NotStoredLocallyException 2021-08-16 17:06:02 +01:00
Hugo Sales 33bf99cfda
[ENTITY][Attachment] Raise mimetype max length to 64 characters and ensure we don't attempt to store more than that 2021-08-14 21:47:49 +01:00
Hugo Sales 4d883d1011
[TESTS] Cleanup attachment test data 2021-08-14 21:46:44 +01:00
Hugo Sales 1d95080f9a
[TESTS] Remove MediaFileTest and move setup code to media data fixture 2021-08-14 19:49:51 +00:00
Hugo Sales bb57d7dc10
[TESTS] Move Media fixtures to their own file, for organization 2021-08-14 19:49:51 +00:00
Hugo Sales f3972abb70
[DOCUMENTATION][Entity] Improve documentation on Entity::getWithPK, explaining the ways it can be used 2021-08-14 19:49:50 +00:00
Diogo Peralta Cordeiro 2e3ab5bdfb
[ENTITY][Link] Sometimes URLs don't work, handle that 2021-08-14 17:08:11 +01:00
Diogo Peralta Cordeiro d23312aff9
[Embed] Add UI element and fix some bugs 2021-08-14 17:04:59 +01:00
Diogo Peralta Cordeiro a43f1a641a
[TWIG] Add Links representation to notes 2021-08-14 17:04:58 +01:00
Diogo Peralta Cordeiro 31c5fd6da7
[ImageEncoder] Handle VIPS unsupported image type 2021-08-14 17:04:58 +01:00
Diogo Peralta Cordeiro 7b3ca428e9
[ATTACHMENT] Some attachments don't have thumbnails and that's okay 2021-08-14 17:04:58 +01:00
Diogo Peralta Cordeiro df5e7b139a
[CORE][GSFile] ensureFilenameWithProperExtension: extension isn't an I/O param 2021-08-14 17:04:57 +01:00
Diogo Peralta Cordeiro 4c1fc40c43
[ENTITY][Note] Add getter for note links 2021-08-14 17:04:57 +01:00
Diogo Peralta Cordeiro c381e58d33
[ENTITY] Refactor RemoteURL entities to Link
RemoteURL was being an awfully confusing term.
2021-08-14 17:04:51 +01:00
Diogo Peralta Cordeiro 333567c6a1
[Embed] Retrieve remote thumbnails and other improvements 2021-08-14 17:00:53 +01:00
Diogo Peralta Cordeiro 632a54208d
[Embed] Fix usage of EmbedLib
Fix other minor bugs
2021-08-14 17:00:53 +01:00
Diogo Peralta Cordeiro daaf7ea236
[Attachment] Sometimes we can't provide download of original file 2021-08-14 17:00:53 +01:00
Diogo Peralta Cordeiro 3019048585
[Posting] Re-add original file to attachment on upload, if it was previously removed 2021-08-14 17:00:52 +01:00
Diogo Peralta Cordeiro 9781ddc8e0
[AttachmentToNote][Attachment] Add title getter to Attachment 2021-08-14 17:00:52 +01:00
Diogo Peralta Cordeiro c12eacc758
[TESTS][Twig] Fix ExtensionTest->testIconsExtension 2021-08-14 17:00:52 +01:00
Diogo Peralta Cordeiro d13da61d30
[StoreRemoteMedia] Implement the first version of it in v3 2021-08-14 17:00:51 +01:00
Diogo Peralta Cordeiro f64436771c
[UTIL] Common::config wasn't a proper port from v2, it has to accept one argument only as well 2021-08-14 17:00:51 +01:00
Diogo Peralta Cordeiro 91666f7d61
[RemoteURLToAttachment] Fix primary key, relation is: One Attachment Has Many URLs, One URL Has One Attachment 2021-08-14 17:00:51 +01:00
Diogo Peralta Cordeiro b20a4c89fb
[Attachment] Allow to delete the associated file 2021-08-14 17:00:50 +01:00
Diogo Peralta Cordeiro 6453593b0d
[MODULES] Add module configuration 2021-08-14 17:00:50 +01:00
Diogo Peralta Cordeiro f72cfd1c2b
[CORE][Event] Fix bug on handler Log 2021-08-14 17:00:50 +01:00
Diogo Peralta Cordeiro c0a404c640
[CORE][Entity] Compare with object properties when creating/updating, instead of class 2021-08-14 17:00:50 +01:00
Diogo Peralta Cordeiro aec8521e4b
[ENTITY][Posting] Remove GSActorToRemoteURL, Fix URL database store 2021-08-14 17:00:49 +01:00
Diogo Peralta Cordeiro eb6ff68f7a
[AttachmentShowRelated] Bug fix after re-introduction of NoteActions 2021-08-14 17:00:49 +01:00
Eliseu Amaro c86cac2095
[CSS][Assets] Minified header icons. Reverted footer links position due to Firefox's corageous interpretation of a fixed element's position. 2021-08-14 17:00:49 +01:00
Eliseu Amaro c14718e8dd
[Favourite][TWIG][CSS] Favourite shows alt-text and properly sets colours. Titles translated on the right panel. 2021-08-14 17:00:48 +01:00
Diogo Peralta Cordeiro ae7516c893
[ATTACHMENTS] Always store in the same location 2021-08-14 17:00:48 +01:00
Diogo Peralta Cordeiro 32ad5dbd74
[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-08-14 17:00:46 +01:00
Diogo Peralta Cordeiro 2ea739ef61
[FileQuota] We'll get back to this plugin later 2021-08-14 16:17:15 +01:00
Hugo Sales 420b3f4aeb
[DB][Attachments] Use count function rathar than fetch and count, rename to refCount, rather than countDepencies 2021-08-14 16:13:01 +01:00
Hugo Sales 6cea2b1d00
[DEPENDENCIES] Update dependencies 2021-08-14 16:13:01 +01:00
Hugo Sales 9c99c11790
[TemporaryFile][TESTS] Throw on attempt to write to null resource and fix tests 2021-08-14 16:13:01 +01:00
Hugo Sales ecbfba1b1a
[TESTS] Add test for JSON response and invalid accept format 2021-08-14 16:13:00 +01:00
Hugo Sales 66b39d3607
[ENTITY] Add JsonSerializable interface to Entity base class and implement it for the Note class 2021-08-14 16:13:00 +01:00
Hugo Sales 8e627f2c18
[TESTS] Add test annotations to core classes 2021-08-14 16:12:59 +01:00
Hugo Sales 7cace2051f
[CORE][Controller] Fix JSON response and add test annotations 2021-08-14 16:12:59 +01:00
Hugo Sales a4cb90ba12
[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-08-14 16:12:58 +01:00
Hugo Sales cb0093bd4a
[TESTS] Fix Entity test in accordance with the changes to createOrUpdate 2021-08-14 16:12:58 +01:00
Hugo Sales c804892672
[TEST] Raise test coverage for UserPanel to 100% 2021-08-14 16:12:57 +01:00
Hugo Sales e053ee451b
[CONTROLLER][UserPanel] Finish implementation of ::notifications so it actually saves the values in the database 2021-08-14 16:12:57 +01:00
Hugo Sales 9a6fddb004
[CONTROLLR][UserPanel][PLUGINS] Add submit button to notification settings for each transport 2021-08-14 16:12:57 +01:00
Hugo Sales 06b9bd9910
[CONTROLLER][AdminPanel] Further ensure form validity 2021-08-14 16:12:56 +01:00
Hugo Sales 47daf6169a
[CORE][Log] Add Log::unexpected_exception utility which logs and throws a ServerException 2021-08-14 16:12:56 +01:00
Hugo Sales 71b1ee7796
[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-08-14 16:12:55 +01:00
Hugo Sales 4266b361c0
[TESTS] Update PHPUnit config to start executing tests by last failure. This doens't seem to actually work currently, though 2021-08-14 16:12:55 +01:00
Hugo Sales 504c8f8935
[TOOLS] Add utility commands to the Makefile 2021-08-14 16:12:55 +01:00
Hugo Sales c38bbed7df
[UTIL][FormFields] Move FormFields class to Util\Form namespace 2021-08-14 16:12:54 +01:00
Eliseu Amaro 7308e66981
[Favourite][TWIG][CSS] Favourite now works. 2021-08-14 16:12:54 +01:00
Eliseu Amaro 2590ea7b67
[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-08-14 16:12:53 +01:00
Eliseu Amaro 6aa61abd81
[CSS] Fixed top margin, footer elements are now actually footer elements. 2021-08-14 16:12:53 +01:00
Eliseu Amaro 96abf53e22
[TWIG][CSS] Buttons are now the correct size in all contexts. Right panel create a notice section re-structure. 2021-08-14 16:12:52 +01:00
Eliseu Amaro b7d205465f
[TWIG][CSS] Feedback provided to active page. Removed top accent border on notes. 2021-08-14 16:12:52 +01:00
Eliseu Amaro d19c990acf
[FAQ][CSS] All FAQ pages stylized, minor structure changes throughout. 2021-08-14 16:12:52 +01:00
Eliseu Amaro 38abbc14b9
[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-08-14 16:12:51 +01:00
Eliseu Amaro 0eb9575534
[CSS] Input file elements polish. It should resize properly and provide feedback when selected (not a thing in Firefox ESR yet). 2021-08-14 16:12:51 +01:00
Eliseu Amaro a02093e848
[CSS] Base CSS polish, reset CSS additions for better cross-browser compatibility. 2021-08-14 16:12:51 +01:00
Diogo Peralta Cordeiro 9343d00110
[Avatar] Delete attachment only if safe 2021-08-14 16:12:47 +01:00
Diogo Peralta Cordeiro 67f5421691
[ATTACHMENTS] Add dependencies counter 2021-08-05 14:44:05 +01:00
Eliseu Amaro bbaeaad052 [CSS] Fixed background incorrect sizing. 2021-08-05 13:09:18 +01:00
Eliseu Amaro 5236278f45 [CSS] Mesh gradient works as intended with no banding. 2021-08-05 13:01:31 +01:00
Diogo Peralta Cordeiro 289eef5cf7
[Avatar] Store as regular attachments 2021-08-05 03:24:21 +01:00
Diogo Peralta Cordeiro c155f4e30e
[DEV] Add PsySH REPL 2021-08-05 03:24:21 +01:00
Diogo Peralta Cordeiro 5896f5bb82
[Avatar] Move entity from core to component 2021-08-05 03:23:05 +01:00
Diogo Peralta Cordeiro 1556b3e019
[CORE][GSFile] Allow storing files under /file 2021-08-05 03:22:57 +01:00
Hugo Sales c58b9fb5b1
[TESTS] Ignore coverage of ResetPasswordRequest, as it uses mailing functionality. We may want to introduce this test in the future 2021-08-04 20:11:47 +00:00
Hugo Sales 97a3c067d9
[UTIL][FormFields] Allow specifying whether a password is required and provide placeholder text 2021-08-04 20:11:47 +00:00
Hugo Sales 92db61a975
[ROUTES] Remove individual settings pages, as they got merged 2021-08-04 20:11:47 +00:00
Hugo Sales 05e10589c3
[ENTITY] Refactor LocalUser::changePassword for easier use 2021-08-04 20:11:47 +00:00
Hugo Sales a590ddd85e
[UTIL][EXCEPTION] Add AuthenticationException, representing an auth error, status code 401 unauthorized 2021-08-04 20:11:47 +00:00
Hugo Sales 0bead1c58a
[TESTS] Change format of data fixtures to allow creating more users 2021-08-04 20:11:47 +00:00
Hugo Sales 0845224188
[CORE][DB] Specify desired case in array_change_case, for clarity 2021-08-04 20:11:47 +00:00
Hugo Sales 1da1f0918e
[TESTS] Raise test coverage for AdminPanel to 100% 2021-08-04 20:11:47 +00:00
Diogo Peralta Cordeiro b075ab610b
[SECURITY] Do not require email when in development 2021-08-04 17:48:00 +01:00
Diogo Peralta Cordeiro 5b858a7bc1
[DOCS][Dev] Add Debugging and Testing 2021-08-04 16:58:27 +01:00
Hugo Sales f760de43b0
[TESTS] Fixup Security controller tests to match new UI 2021-08-03 19:37:56 +00:00
Hugo Sales 960675b459
[TESTS] Raise test coverage for AdminPanel controller to 100% 2021-08-03 19:37:56 +00:00
Hugo Sales f9c1d14c7a
[TESTS] Raise test coverage for Attachment controller to 100% 2021-08-03 19:37:56 +00:00
Hugo Sales ed21290ef4
[TESTS] Add a sample image to the test dataset 2021-08-03 19:37:56 +00:00
Hugo Sales 6b098a26f7
[CORE][GSFile] Ensure files are stored inside the configured storage folder, with a relative filename in the database 2021-08-03 19:37:56 +00:00
Hugo Sales 19a966f1a9
[CORE][CONTROLLER] Add TODO to Controller base class as to where our custom exception pages would be implemented 2021-08-03 19:23:03 +00:00
Hugo Sales f5f11b6e54
[CORE][ENTITY] Allow create'ing will null values 2021-08-03 19:23:03 +00:00
Hugo Sales 9077403f65
[ImageEncoder] Don't print_r the exception as that leads to an OOM error 2021-08-03 19:23:03 +00:00
Hugo Sales bbdad515a2
[CONFIG] Ensure consistency in config file 2021-08-03 19:23:03 +00:00
Hugo Sales 7034476cc7
[TESTS] Make PHPUnit exit on first fail and some other tweaks 2021-08-03 19:23:03 +00:00
Hugo Sales 927472cf06
[UTIL][FORM] Create a utility class that defines common form fields, such as passwords 2021-08-03 19:23:02 +00:00
Hugo Sales b2456d8cd2
[TESTS] Raise test coverage for Controller/Security to 100% 2021-08-03 19:23:02 +00:00
Hugo Sales d1e92a80e5
[CORE][GSFile] Assert that the destination folder where to store the attachment is inside the INSTALLDIR and store the filepath in the database 2021-08-03 19:23:02 +00:00
Hugo Sales af951685ed
[TESTS] Add coverage ignore to TemplateController and ResetPassword (as it requires sending emails) 2021-08-03 19:23:02 +00:00
Hugo Sales 9c61e92257
[CONTROLLER][Attachment] Assert that the supplied is positive and add documentation 2021-08-03 19:23:02 +00:00
Hugo Sales 4297eb71a0
[CORE][GSFile] Change actor_id paramenter of validateAndStoreFileAsAttachment to optional and reorder them and their usages 2021-08-03 19:23:02 +00:00
Hugo Sales b89368bf6a
[AUTOGENERATED] Update entity fields for ResetPasswordRequest entity 2021-08-03 19:23:02 +00:00
Hugo Sales 5fc5df68f5
[CORE] Add repositories, as needed by the Reset Password Bundle 2021-08-03 19:23:02 +00:00
Hugo Sales a83d506d6c
[UTIL] Add a class that defines commonly used form fields 2021-08-03 19:22:54 +00:00
Hugo Sales 56481c8289
[CORE] Add passowrd reset and forgot password functionality 2021-08-03 19:22:54 +00:00
Hugo Sales 6d2f8daeae
[DEPENDENCIES] Add symfonycasts/reset-password-bundle as a dependency 2021-08-03 19:22:54 +00:00
Hugo Sales bdbd588de9
[CORE][SECURITY][EMAIL] Move email confirmation functionality to it's own static wrapper, in preparation for adding password reset functionality 2021-08-03 19:22:54 +00:00
Hugo Sales 176d604abb
[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-08-03 19:22:54 +00:00
Hugo Sales bff65afe5d
[TESTS][Queue] Add @codeCoverageIgnore to select queueing functions 2021-08-03 19:22:50 +00:00
Hugo Sales 6479b698f8
[TESTS][Router] Add tests for Router and use named paramenters, as we can since PHP8 2021-08-03 19:22:16 +00:00
Hugo Sales a01914ddac
[ENTITY][LocalUser] Add method 'findByNicknameOrEmail' 2021-08-03 19:22:16 +00:00
Hugo Sales 2e3ec15827
[CORE][Controller][TESTS] Fix issue that arrises in tests where the Accept header is not specified 2021-08-03 19:22:12 +00:00
Hugo Sales 6deac21960
[EXCEPTION] Add Email related exceptions 2021-08-03 19:21:34 +00:00
Hugo Sales c4de4cab32
[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-08-03 19:21:34 +00:00
Hugo Sales 75af2232dc
[UI] Add TODO annotation to login template, since it's possible to login using email, so the fonrm field names should be updated 2021-08-03 19:21:31 +00:00
Hugo Sales 988d384654
[TESTS][DataFixtures] Add password and email to created test user 2021-08-03 19:15:30 +00:00
Hugo Sales 14db5d9864
[TESTS][Twig] Add @codeCoverageIgnore to select methods, as these are simple wrappers 2021-08-03 19:15:30 +00:00
Hugo Sales e0ebef594f
[TESTS][DB] Update test to reflec fact that GSActor no longer has a normalized_nickname field 2021-08-03 19:15:30 +00:00
Hugo Sales 63d26d1295
[TESTS][Nickname] Update tests to reflect new usage. Normalization functionality will be moved to a plugin 2021-08-03 19:15:30 +00:00
Hugo Sales ed850a7763
[TESTS][TemporaryFile] Update test to reflect new usage 2021-08-03 19:15:30 +00:00
Hugo Sales e196a3577d
[ENTITY] Remove 'normalized_nickname' field from GSActor as that feature will be moved to a plugin 2021-08-03 19:15:30 +00:00
Hugo Sales c0e4dec674
[TESTS][DOCUMENTATION][Module] Add documentation and exclude method from testing in Module base class 2021-08-03 19:15:30 +00:00
Hugo Sales 88ab76c480
[CORE][TemporaryFile] Add option to specify attempts and better handle when reaching the attemp limit without being able to create a file 2021-08-03 19:14:56 +00:00
Hugo Sales 1f9acaf4ef
[TESTS] Add tests for GSFile 2021-08-03 19:13:30 +00:00
Hugo Sales 55710aa33d
[DB] Refactor findOneBy method 2021-08-03 19:13:29 +00:00
Hugo Sales 8e743eabb9
[TESTS][DOCUMENTATION] Add documenation for the list events command and exclude it from unit testing 2021-08-03 19:13:29 +00:00
Hugo Sales d34155c743
[CONFIG] Make password length limits configurable 2021-08-03 19:13:29 +00:00
Hugo Sales 0d6b4093fe
[TESTS] Exclude Data Fixtures from testing, as that happens before testing 2021-08-03 19:13:29 +00:00
Hugo Sales 400716c1b2
[TESTS] Exclude class Security from testing, as it's a simple wrapper 2021-08-03 19:13:29 +00:00
Hugo Sales 75c9ffde31
[TESTS] Raise test coverage for ModuleManager to 100% 2021-08-03 19:13:29 +00:00
Hugo Sales 4258148a03
[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-08-03 19:13:29 +00:00
Hugo Sales 6956e6907c
[TESTS] Raise test coverage for Form to 100% 2021-08-03 19:13:29 +00:00
Hugo Sales 7ee908f4dc
[TESTS] Revert exposing Redis docker container ports, as this conflicts with the actual instance, and was intended for testing 2021-08-03 19:13:29 +00:00
Hugo Sales b2d72673c7
[TESTS][EVENTS] Raise test coverage for Event class to 100% 2021-08-03 19:13:28 +00:00
Hugo Sales b51d43e6e2
[TESTS][ENTITY] Raise test coverage for Entity class to 100% 2021-08-03 19:13:28 +00:00
Hugo Sales 5777cdeaf9
[TESTS][LOG] Raise test coverage for Log class to 100% 2021-08-03 19:13:28 +00:00
Hugo Sales b0ef7599b2
[TESTS] Ignore GNUsocial class from tests, as it simply pipes objects around 2021-08-03 19:13:28 +00:00
Hugo Sales 2c74bd7fb4
[FORM][DOCUMENTATION] Add documentation to Form class 2021-08-03 19:13:28 +00:00
Hugo Sales b9fdaa1401
[EVENT] Fixup implementation, as imformed by tests 2021-08-03 19:13:28 +00:00
Hugo Sales 88bb0c6b38
[ENTITY] Fixup implementation, as imformed by tests 2021-08-03 19:13:28 +00:00
Hugo Sales df956a5f90
[DB] Handle using methods with class name as well as table name and add lookup methods 2021-08-03 19:13:28 +00:00
Hugo Sales aa66263b92
[TESTS] Add missing tests for Common 2021-08-03 19:13:28 +00:00
Hugo Sales ae27d95509
[TESTS] Expand and fix cache tests 2021-08-03 19:13:27 +00:00
Hugo Sales 2beda0dd44
[TESTS] Add ignore annotations to code paths that serve as hooks in DependencyInjection 2021-08-03 19:13:27 +00:00
Hugo Sales b79629b6d2
[TESTS][CACHE] Fixup errors found in cache implementation by testing. Ensure the newest values are kept, in pushList with max_count 2021-08-03 19:13:27 +00:00
Hugo Sales 4f6f4aa512
[ENTITY] Fix foreign key type in Cover entity, as found by tests 2021-08-03 19:13:27 +00:00
Hugo Sales 3554a5c369
[TESTS] Exclude Routes from testing, as well as, temporarily, src/Security 2021-08-03 19:13:27 +00:00
Hugo Sales 5ca8842308
[TESTS] Use vendor/bin/simple-phpunit for running the tests, as it provides the appropriate polyfills 2021-08-03 19:13:20 +00:00
Eliseu Amaro 5bf4a68454 [LEFT][RIGHT][CSS] Panels now occupy full page in smaller screen sizes. 2021-08-03 19:02:39 +01:00
Eliseu Amaro 903e6b33ff [RIGHT][CSS] Right panel now shows an intuitive icon for other note options available. 2021-08-03 19:02:39 +01:00
Eliseu Amaro 0a0ead3081 [TWIG][CSS] Overall CSS optimizations. Image gradients are now used, 64x64 px. 2021-08-03 19:02:39 +01:00
Eliseu Amaro efeb4b4ffe [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-08-03 19:02:39 +01:00
Eliseu Amaro 780d341939 [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-08-03 19:02:39 +01:00
Eliseu Amaro 5e012c39ab [SETTINGS][TWIG][CSS] Settings WIP. Form polished, dropdowns need styling.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-08-03 19:02:38 +01:00
Eliseu Amaro 787afb9b41 [BASE][CSS] Snappier and consistent animations.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-08-03 19:02:38 +01:00
Eliseu Amaro 0e7c657301 [TWIG][SETTINGS] WIP. Settings navigation early sketch.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-08-03 19:02:38 +01:00
Eliseu Amaro 95f92d34db [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-08-03 19:02:38 +01:00
Eliseu Amaro 3cd33fb83a [TWIG][CSS] Panels are fixed and base content acts accordingly on all sizes.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-08-03 19:02:38 +01:00
Diogo Peralta Cordeiro c2fc2300c7
[DOCS][Dev] Add HTTP Client 2021-08-03 18:22:37 +01:00
Diogo Peralta Cordeiro 7fd4149695
[DOCS][Dev] Add Security 2021-08-03 17:54:43 +01:00
Diogo Peralta Cordeiro 0273c8ca24
[DOCS][Dev] Add Queues 2021-08-03 17:23:58 +01:00
Diogo Peralta Cordeiro 477518abf7
[DOCS][Dev] Add Internationalisation 2021-08-03 16:07:32 +01:00
Diogo Peralta Cordeiro 47171069c2
[DOCS][Dev] Add Attachments 2021-08-03 15:37:31 +01:00
Diogo Peralta Cordeiro e8f57e8380
[DOCS][Dev] Configure search 2021-08-03 15:37:30 +01:00
Diogo Peralta Cordeiro 7f3a9bc880
[DOCS][Dev] Add Logging 2021-08-03 15:37:30 +01:00
Diogo Peralta Cordeiro 263a5f67f3
[DOCS][Dev] Add Templates 2021-08-03 13:34:57 +01:00
Diogo Peralta Cordeiro df1f12470f
[DOCS][Dev] Add Routes and Controllers 2021-08-03 13:34:56 +01:00
Diogo Peralta Cordeiro 0cf53a4163
[DOCS][Dev] Add Cache 2021-08-03 13:34:53 +01:00
Diogo Peralta Cordeiro 12f3e1f406
[DOCS][Dev] Cleanup src directory 2021-07-31 01:57:20 +01:00
Diogo Peralta Cordeiro 15f2514aa2
[DOCS][Dev] Add database chapter 2021-07-31 01:46:54 +01:00
Diogo Peralta Cordeiro 900d538e26
[DOCS][DEV] Add events 2021-07-30 19:09:25 +01:00
Diogo Peralta Cordeiro 8f8b66c938
[DOCS][Paradigms] Elaborate on Null, Set and Void 2021-07-29 13:43:57 +01:00
Diogo Peralta Cordeiro 3c0f6b294f
[DOCS] Write exceptions chapter 2021-07-28 21:06:55 +01:00
Eliseu Amaro cfd771283a
[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-07-28 11:52:45 +01:00
Eliseu Amaro 88eea554fc
[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-07-28 11:52:45 +01:00
Eliseu Amaro 5c8735ebea
[TWIG][CSS] Fixed right panel buttons, fix issue where the form was invalid on send.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:44 +01:00
Eliseu Amaro d2a7281a4d
[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-07-28 11:52:44 +01:00
Eliseu Amaro 0ce686d8bb
[TWIG][CSS] Left panel profile section done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:44 +01:00
Eliseu Amaro b4bf720b06
[TWIG][CSS] Register and Login styling done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:44 +01:00
Eliseu Amaro 0d83bbff23
[CSS] Fixing poor responsiveness to main nav element hover animation.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:43 +01:00
Eliseu Amaro a9d710f189
[CSS][TWIG] Left panel HTML nesting to better aid screen readers.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:43 +01:00
Eliseu Amaro 4b095961d8
[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-07-28 11:52:43 +01:00
Eliseu Amaro 14ecf913bf
[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-07-28 11:52:42 +01:00
Eliseu Amaro 2b4bf6c31f
[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-07-28 11:52:42 +01:00
Eliseu Amaro 6437e68132
[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-07-28 11:52:42 +01:00
Eliseu Amaro 37a2db3706
[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-07-28 11:52:41 +01:00
Eliseu Amaro 362be17aba
[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-07-28 11:52:41 +01:00
Eliseu Amaro 3a5ba9b6c4
[CSS] Main container 'order' attribute was somehow a problem in Chromium. 2021-07-28 11:52:41 +01:00
Eliseu Amaro dd7d412e83
[CSS] Fixed top content margin. 2021-07-28 11:52:41 +01:00
Eliseu Amaro 593d5bf96e
[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-07-28 11:52:40 +01:00
Eliseu Amaro d6cf812707
[TWIG][CSS][ICONS] New profile and notice creation panel, alternative text set correctly for header icons. WIP in base styling and panels. 2021-07-28 11:52:40 +01:00
Eliseu Amaro 2d7b201e71
[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-07-28 11:52:40 +01:00
Eliseu Amaro ac5df2f6b3
[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-07-28 11:52:39 +01:00
Eliseu Amaro 375d0097f3
[TWIG][CSS] Panels functionality works as intended, size needs to be worked upon. 2021-07-28 11:52:39 +01:00
Eliseu Amaro a5eb231196
[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-07-28 11:52:39 +01:00
Eliseu Amaro aa6886a62a
[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-07-28 11:52:39 +01:00
Eliseu Amaro aa1fd8ea40
[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-07-28 11:52:38 +01:00
Eliseu Amaro f47fedcfd4
[TWIG][CSS] Fixed right panel buttons, fix issue where the form was invalid on send.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:38 +01:00
Eliseu Amaro f206b55869
[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-07-28 11:52:38 +01:00
Eliseu Amaro 69f5c1e312
[TWIG][CSS] Left panel profile section done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:37 +01:00
Eliseu Amaro 8b4148a00d
[TWIG][CSS] Register and Login styling done.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:37 +01:00
Eliseu Amaro df44d92bb2
[CSS] Fixing poor responsiveness to main nav element hover animation.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:37 +01:00
Eliseu Amaro 021583ea05
[CSS][TWIG] Left panel HTML nesting to better aid screen readers.
Signed-off-by: Eliseu Amaro <mail@eliseuama.ro>
2021-07-28 11:52:36 +01:00
Eliseu Amaro 4afaa6858b
[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-07-28 11:52:36 +01:00
Eliseu Amaro ab7d1b0370
[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-07-28 11:52:36 +01:00
Eliseu Amaro 38225dfa6e
[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-07-28 11:52:36 +01:00
Eliseu Amaro 154025090c
[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-07-28 11:52:30 +01:00
Eliseu Amaro 8ec17086a0
[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-07-28 11:46:14 +01:00
Eliseu Amaro 18f2823e14
[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-07-28 11:46:13 +01:00
Eliseu Amaro 7dd23f3f2c
[CSS] Main container 'order' attribute was somehow a problem in Chromium. 2021-07-28 11:46:13 +01:00
Eliseu Amaro a34f0c2534
[CSS] Fixed top content margin. 2021-07-28 11:46:13 +01:00
Eliseu Amaro 7d15ec1620
[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-07-28 11:46:13 +01:00
Eliseu Amaro 2d253ee5ad
[TWIG][CSS][ICONS] New profile and notice creation panel, alternative text set correctly for header icons. WIP in base styling and panels. 2021-07-28 11:46:12 +01:00
Eliseu Amaro c18879b02f
[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-07-28 11:46:12 +01:00
Eliseu Amaro a48e699133
[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-07-28 11:46:12 +01:00
Eliseu Amaro 77675ea8c4
[TWIG][CSS] Panels functionality works as intended, size needs to be worked upon. 2021-07-28 11:46:11 +01:00
Eliseu Amaro b7b69b549e
[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-07-28 11:46:11 +01:00
Eliseu Amaro 681f001f4e
[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-07-28 11:46:11 +01:00
Eliseu Amaro c5e5708915
[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-07-28 11:46:10 +01:00
Diogo Peralta Cordeiro 86e92fedc2
[MEDIA][Thumbnail] Fix non-instantiated variable 2021-07-26 21:19:40 +01:00
Diogo Peralta Cordeiro 8ef6aceb6a
[DOCS][Dev] Write paradigms 2021-07-26 20:38:23 +01:00
Hugo Sales bf01e97533
[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-07-26 17:12:42 +00:00
Hugo Sales 835a3c6701 [TOOLS] Fix pre commit hook to allow for partial file commits (git add -p/git reset -p) 2021-07-26 15:46:20 +00:00
Diogo Peralta Cordeiro cc0ef73799 [MEDIA][AttachmentThumbnail] Add mimetype to Entity 2021-07-22 21:17:23 +01:00
Diogo Peralta Cordeiro c3eda07521 [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-07-22 21:10:45 +01:00
Diogo Peralta Cordeiro 832a5c0bd9 [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-07-22 21:10:45 +01:00
Diogo Peralta Cordeiro 143ecea376 [Media] File quota should be triggered by the Core 2021-07-22 21:10:45 +01:00
Diogo Peralta Cordeiro 0eebcdbd51 [POSTING] Make it possible for plugins to change the placeholder string 2021-07-22 13:02:09 +01:00
Diogo Peralta Cordeiro aada96beb7 [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-07-22 12:32:52 +01:00
Diogo Peralta Cordeiro 218bec1826 [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-07-20 23:47:27 +01:00
Diogo Peralta Cordeiro 4d2131808a [FILE][TemporaryFile] Fix various issues now that we also have Symfony's file abstractions 2021-07-20 23:46:18 +01:00
Diogo Peralta Cordeiro 4ef400f509 [DOCS][Developer] Adopt a top-down approach
Minor corrections to the overview
2021-07-20 12:41:48 +01:00
Diogo Peralta Cordeiro 086754d95b [CORE][GNUsocial] Fix undefined property typo 2021-07-20 11:48:29 +01:00
Diogo Peralta Cordeiro 5e9cd21db5 [DOCS][User][SysAdmin] Add thomask as an author as we're starting this from his unofficial docs 2021-07-20 11:48:07 +01:00
Eliseu Amaro 65c2c42790 [DOCS] Add designer book. 2021-07-20 10:56:51 +01:00
Eliseu Amaro d6f31ad4b4 [DOCKER][nginx] Removing default nginx config through docker/nginx/domain.sh. The default config conflicts with 'localhost' server_name. 2021-07-20 10:56:51 +01:00
Diogo Peralta Cordeiro 8f7e0f2131 [DOCS] Elaborate initial architecture page 2021-07-20 09:41:30 +01:00
Diogo Peralta Cordeiro 3af3526b5c [CORE] Proxies: constant HEADER_X_FORWARDED_ALL is deprecated
Give ENV preference over SERVER
2021-07-19 15:24:12 +01:00
Hugo Sales a46140fc00 [UTIL] Add utility to flatten the result of note queries 2021-05-23 19:59:42 +00:00
Hugo Sales eecef99372 [TESTS] Raise test coverage for App\Controller\Network to 100% and fixup related code 2021-05-23 19:56:45 +00:00
Hugo Sales 5543f65ce9 [UI] Remove margin in timeline container 2021-05-12 19:40:47 +00:00
Hugo Sales 818a31a690 [UTIL] Provide static access to current request and utilities in Common 2021-05-12 19:33:03 +00:00
Hugo Sales 9b862d6a26 [CORE] Throw more meaningfull error when method doesn't exist in Security and Entity 2021-05-12 15:44:09 +00:00
Hugo Sales f8107c86c5 [TESTS] Raise App\Core\DB\DB test coverage to 100% and fix issues found 2021-05-11 21:04:15 +00:00
Hugo Sales ce98e80836 [TESTS] Raise App\Core\DB\UpdateListener test coverage to 100% 2021-05-06 21:57:06 +00:00
Hugo Sales 75adf2e59f [TESTS] Change relevant tests to use GNUsocialTestCase, so they can access all the needed features 2021-05-06 21:56:28 +00:00
Hugo Sales 31518f97ee [CORE] Clarify message when calling non existent method in Entity 2021-05-06 21:54:50 +00:00
Hugo Sales dab822037c [TESTS] Merge datafixtures to allow for using the correct ID in notes, and add group_inbox 2021-05-06 21:54:50 +00:00
Hugo Sales 79644d1e2b [TESTS] Add GNUsocialTestCase, which initializes our infrastructure when bootKernel is called 2021-05-06 21:54:50 +00:00
Hugo Sales 5f9b61f4bf [AUTOGENERATED] Update autogenerated code 2021-05-05 16:03:03 +00:00
Hugo Sales 3a6a1b71d6 [TOOLS][TESTS] Add coverage ignore tag to autogenerated code 2021-05-05 15:56:03 +00:00
Hugo Sales f25494cd83 [TOOLS][TESTS] Add a data fixture with example notes, for testing 2021-05-05 13:37:43 +00:00
Hugo Sales b79c0595d5 [TESTS] Expand test coverage for App\Util\Forms\ArrayTransformer, App\Util\Notification and App\Twig\Runtime 2021-05-05 13:37:10 +00:00
Hugo Sales 33cdea87ee [TWIG] Remove unused Twig function 'get_note_other_content' 2021-05-05 13:35:25 +00:00
Hugo Sales c532fdb4c8 [TESTS] Add ignored files and folders to config 2021-05-05 13:34:32 +00:00
Hugo Sales 5cc82785c6 [TESTS] Raise App\Util\TemporaryFile test coverage to 100% 2021-05-05 12:46:29 +00:00
Hugo Sales 05fbcdefa8 [TOOLS][TESTS] Make tests run as www-data 2021-05-05 12:46:29 +00:00
Hugo Sales dd218b04e9 [UTIL] Fix App\Util\TemporaryFile, adding default options and preventing warning on not enough permission 2021-05-05 12:46:17 +00:00
Hugo Sales 059ed1fa76 [TESTS] Raise test coverage for NicknameTest to 100% 2021-05-05 12:46:17 +00:00
Hugo Sales f946da6f29 [TOOLS][TESTS] Add data fixtures, which populate the database with users used for testing 2021-05-05 12:46:08 +00:00
Hugo Sales 9e2037e086 [UTIL] Rename and rewrite isTaken to checkTaken 2021-05-05 12:46:08 +00:00
Hugo Sales 84399a76e3 [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-05-05 12:46:08 +00:00
Hugo Sales 4f0bdade45 [DEPENDENCIES] Add doctrine/doctrine-fixtures-bundle, which allows populating the database in the testing environment 2021-05-05 12:46:08 +00:00
Hugo Sales d5db350595 [TOOLS][TESTS] Adjust configuration for testing environment 2021-05-05 12:45:58 +00:00
Hugo Sales f5fcfe628e [TESTS] Raise App\Util\HTML test coverage to 100% 2021-05-02 21:02:43 +00:00
Hugo Sales fde7b87c65 [TESTS] Raise App\Util\Bitmap test coverage to 100% 2021-05-02 20:47:15 +00:00
Hugo Sales f841e5e0dd [TESTS] Raise App\Util\Common test coverage to 100% 2021-05-02 20:42:25 +00:00
Hugo Sales 39ac043d59 [ENTITY] Add uniqueness constraint to Attachment::file_hash 2021-05-02 15:48:33 +00:00
Hugo Sales 041d19a22d [ATTACHMENTS] Don't store an attachment if it's a dupplicate, reuse it 2021-05-02 15:48:33 +00:00
Hugo Sales b99fab00e9 [UTILS][TemporaryFile] Change way TemporaryFile takes arguments and it's internal implementation 2021-05-02 15:48:33 +00:00
Hugo Sales 88e84f2dc5 [UTIL] Fix bugs found in App\Util\Formatting by tests 2021-05-02 15:28:56 +00:00
Hugo Sales 16055c7055 [TESTS] Add tests increasing coverage of App\Util\Formatting to 100% 2021-05-02 15:28:56 +00:00
Hugo Sales 15c406a348 [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-05-02 15:28:56 +00:00
Hugo Sales eff703ca21 [TESTS] Add test container with Xdebug and allow for generation of coverage reports with 'make test' 2021-05-02 13:31:04 +00:00
Diogo Peralta Cordeiro 2e943293e6 [ATTACHMENTS] Do not create thumbnails for attachments with mimetype different from 'image|video' 2021-05-02 00:50:16 +01:00
Diogo Peralta Cordeiro 6aea20db05 [Embed] Do not create AttachmenThumbnail 2021-05-02 00:49:10 +01:00
Diogo Peralta Cordeiro a5a2032e75 [ENTITY] Fix entity->has to access private properties with closure bindTo 2021-05-02 00:14:24 +01:00
Diogo Peralta Cordeiro c948ca6178 [CSS][Network] Minor fixes concerning attachment representation (centering and width) 2021-05-02 00:00:03 +01:00
Diogo Peralta Cordeiro 676210f76a [ATTACHMENTS] Follow URL redirects and don't duplicate attachments 2021-05-02 00:00:03 +01:00
Diogo Peralta Cordeiro af4b0113ba [ATTACHMENTS] Respect config for smart crop 2021-05-02 00:00:03 +01:00
Diogo Peralta Cordeiro 3f565442d2 [Posting] Don't sanitize on storage
We prefer to have the original input in database and sanitize on output when appropriate
2021-05-02 00:00:03 +01:00
Diogo Peralta Cordeiro 4397d12fa4 [AUTOGENERATED] Update auto generated code 2021-05-02 00:00:03 +01:00
Diogo Peralta Cordeiro c58d7e470a [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-05-02 00:00:02 +01:00
Diogo Peralta Cordeiro 5a40d1f3e3 [Embed] Fix some bugs and change AttachmentEmbed::url to ::media_url 2021-05-01 23:58:49 +01:00
Diogo Peralta Cordeiro ced6e236ce [ATTACHMENTS][GSFile] Rename ValidateAndStore functions 2021-05-01 23:57:51 +01:00
Diogo Peralta Cordeiro d5a7f2122a [Embed] Local config 2021-05-01 23:57:51 +01:00
Diogo Peralta Cordeiro d0d98a611d [DEPENDENCIES] Add ext-curl 2021-05-01 23:57:51 +01:00
Hugo Sales 650bfec699 [ATTACHMENTS] In sendFile, check that file exists or show a custom exception 2021-05-01 13:02:14 +00:00
Hugo Sales 6d842d60c5 [ENTITY] Change foreign key definition to new format for cover and profile_color tables 2021-05-01 12:50:49 +00:00
Hugo Sales e0e1dca0f0 [DOCUMENTATION] Add database diagram to developer documentation 2021-05-01 12:49:04 +00:00
Diogo Peralta Cordeiro 6374e30475 [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-04-30 23:47:46 +01:00
Diogo Peralta Cordeiro 0086d8dec4 [CSS][Left] Vertical scroll on left menu when it doesn't fit the screen 2021-04-30 23:43:34 +01:00
Diogo Peralta Cordeiro 6910620d59 [CSS][Network] Fix horizontal menu on smaller screens 2021-04-30 23:40:32 +01:00
Diogo Peralta Cordeiro 0629c1434d [UTIL][Formatting] Add twigRenderFile 2021-04-30 23:08:08 +01:00
Hugo Sales 120571fa42 [DOCUMENTATION] Add high level code walkthrough to developer docs 2021-04-30 23:08:08 +01:00
Hugo Sales d9a3ecb116 [Posting] Add missing default visibility option 2021-04-30 23:08:08 +01:00
Hugo Sales 1bf5e9d117 [ImageEncoder][FileQuota] Move quota enforcement to it's own plugin, so it can be easily shared and disabled 2021-04-30 23:08:08 +01:00
Diogo Peralta Cordeiro aa28251c11 [TEMPLATES] Fix identation 2021-04-30 23:08:08 +01:00
Diogo Peralta Cordeiro c2f6665cce [AttachmentShowRelated] Move Attachment related to plugin 2021-04-30 23:08:08 +01:00
Diogo Peralta Cordeiro b196af5f36 [SECURITY] We can't really show a stream for this, was a nice concept, but not properly doable without requiring JS 2021-04-30 23:08:08 +01:00
Hugo Sales ebfa0e2240 [Avatar][Embed] Change use of TemporaryFile::getPath to getRealPath 2021-04-30 23:08:08 +01:00
Hugo Sales 365a7b436f [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-04-30 23:08:08 +01:00
Hugo Sales 93e1e4b7a9 [ENTITY] Add field size to attachments, used for quota calculations 2021-04-30 23:08:08 +01:00
Hugo Sales e5ee31a2fe [UTIL] Add option for setting a file suffix on TemporaryFile constructor and add missing TemporaryFileException 2021-04-30 23:08:08 +01:00
Hugo Sales e32d8711d6 [CONFIG] Add attachments/max_{width,height} config option, which is used as maximum dimensions when validating attachments 2021-04-30 23:08:08 +01:00
Hugo Sales 78a17425f9 [DEPENDENCIES] Add oroinc/doctrine-extensions, which provides cross database platform date functions 2021-04-30 23:08:08 +01:00
Diogo Peralta Cordeiro 94b100dc06 [CSS] Refactor Right Panel style 2021-04-30 23:08:02 +01:00
Diogo Peralta Cordeiro 75c494dca1 [TEMPLATES] Add dynamic blocks to right panel 2021-04-30 23:05:39 +01:00
Diogo Peralta Cordeiro f95b8ab226 [CSS] Fix invisible checkboxes 2021-04-29 20:36:50 +00:00
Diogo Peralta Cordeiro 6819dd9fb7 [TEMPLATES] Minor refactoring, extending left was weird 2021-04-29 20:36:50 +00:00
Hugo Sales c57a8481b1 [Avatar] Implement avatar deletion 2021-04-29 20:36:50 +00:00
Hugo Sales ec0c551bb3 [AVATAR] Move avatar settings page to Avatar component 2021-04-29 18:14:49 +00:00
Hugo Sales f17d4d2d92 [Embed] Use Formatting utilities rather than substr and such 2021-04-29 18:14:49 +00:00
Diogo Peralta Cordeiro 255055d149 [Embed] Add docblock to handle function to pass pre-commit hook 2021-04-29 17:42:31 +01:00
Diogo Peralta Cordeiro 55c4ad40cd [ENTITY][GSActor] Fix getAvatarUrl method 2021-04-29 17:42:06 +01:00
Diogo Peralta Cordeiro 5fbc079c55 [RIGHT] Introduce component abstraction 2021-04-29 17:40:19 +01:00
Diogo Peralta Cordeiro 22c79db540 [VIEWS] Minor proofreading of templates and css 2021-04-29 17:08:09 +01:00
Hugo Sales bb56b24d8f [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-04-28 21:53:02 +00:00
Hugo Sales b2841cb5fc [UI] Fix 'hide_attachments is not defined' error 2021-04-28 21:50:55 +00:00
Hugo Sales f264cd6125 [ATTACHMENTS] Add controller and templates for the attachment show page, which shows extra info about an attachment, such as related notes and tags 2021-04-28 21:25:35 +00:00
Hugo Sales d49de9d35e [CORE] Typo in GSFile and slight Twig weirdness in base template 2021-04-28 20:16:59 +00:00
Hugo Sales 7f765c530e [Embed][ENTITY] Fix embed route and use attachment_view rather than _show. Rename Entity::have to Entity::has, because grammar 2021-04-28 20:15:43 +00:00
Hugo Sales e699824b1d [Embed] Fix plugin. Only attempt to show an image, if we have one 2021-04-28 15:03:17 +00:00
Hugo Sales 6da8cf7f14 [ATTACHMENTS] Add event 'AttachmentFileInfo' to allow a plugin to override the file displayed 2021-04-28 15:01:40 +00:00
Hugo Sales e08767cec0 [UTIL] Fix remove affix utilities, so they only try to remove an affix if the string starts/ends with it 2021-04-28 15:00:04 +00:00
Hugo Sales 83415b7aa6 [CONFIG] Add attachment related parameters to default config 2021-04-28 14:59:04 +00:00
Hugo Sales 495e66f4ae [AUTOGENERATED] Update autogenerated code 2021-04-27 21:24:48 +00:00
Hugo Sales 17ea4ecce1 [DB] Fix error in config/services.yaml where the wrong namespace was used for the UpdateListener 2021-04-27 21:23:47 +00:00
Hugo Sales 1d7375b9cb [TOOLS] Use \DateTimeInterface rather than DateTimeInterface 2021-04-27 21:23:00 +00:00
Hugo Sales 0a69f6de8c [AUTOGENERATED] Run bin/generate_entity_fields for the Embed plugin 2021-04-27 21:18:44 +00:00
Hugo Sales 72cd2e7a30 [Embed] Review and port v2 code 2021-04-27 20:56:50 +00:00
Hugo Sales c6389c63b8 [ENTITY] Add meta method 'have*' to Entity base class, which checks if a field 'isset' 2021-04-27 20:56:13 +00:00
Hugo Sales f388554166 [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-04-27 20:53:59 +00:00
Hugo Sales b4ad396cd1 [FORMATTING] Add utilities to remove affixes from strings 2021-04-27 20:52:12 +00:00
Hugo Sales c3473e45d2 [DEPENDENCIES] Add 'embed/embed', 'nyholm/psr7' and 'symfony/dom-crawler' 2021-04-27 20:50:43 +00:00
Hugo Sales 075b495f5a [ENTITY] Add utils to Attachment and AttachmentThumbnail to get the corresponding URL and html representation parameters 2021-04-27 18:10:18 +00:00
Hugo Sales 9b3ccac246 [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-04-27 18:10:18 +00:00
Hugo Sales 82d9326343 [PLUGINS] Remove scripts. These will need to be implemented with Commands 2021-04-27 18:10:18 +00:00
Hugo Sales 464406cccc [Emebed] Add Embed plugin and initial cleanup 2021-04-27 18:10:18 +00:00
Hugo Sales 2782aa9924 [UI] Use event 'ShowAttachment' to permit plugins like Embed to alter the representation 2021-04-27 18:10:18 +00:00
Hugo Sales 1df7be7e8a [UTIL] Add method to validate url 2021-04-27 18:10:18 +00:00
Hugo Sales 792a9f097c [HTTPClient] Add utility functions for all HTTP methods 2021-04-27 18:10:18 +00:00
Hugo Sales 4649ee9e71 [DB] Make DB::findOneBy throw a different exception if two values are found 2021-04-27 18:10:18 +00:00
Hugo Sales c1db9bd0a3 [Posting] Extract and store URLs from note content. Introduce 'AttachmentStoreNew' event 2021-04-25 21:20:28 +00:00
Hugo Sales cc47cda3d1 [TESTS] Fix Nickname test 2021-04-23 15:38:26 +00:00
Hugo Sales 1503c98f26 [Favourite] Make twig event operate on array rather than string 2021-04-23 12:55:42 +00:00
Hugo Sales b82658e345 [SECURITY][DB] Make user register 'atomic', by using a single transaction for inserting all objects, to avoid partial inserts 2021-04-23 12:54:25 +00:00
Hugo Sales 1bad2fa050 [TESTS] Fix wrong namespace in tests 2021-04-22 18:26:00 +00:00
Hugo Sales 926d0af663 [PLUGIN] Change base class from Module to Plugin for all plugins 2021-04-19 18:51:42 +00:00
Hugo Sales 0a7496de1e [AVATAR] Display avatar rounded 2021-04-19 18:41:40 +00:00
Diogo Peralta Cordeiro 9814baf192 [UI] Use thumbnail path for thumbs 2021-04-19 13:22:50 +01:00
Hugo Sales 5ec7717fa1 [ATTACHMENTS] Move thumbnail controller to core and cleanup 2021-04-19 13:22:50 +01:00
Hugo Sales d316f9dd6f [ImageEncoder] Change preferred type to always be WEBP 2021-04-19 13:22:50 +01:00
Hugo Sales 529ec19801 [AVATAR] Display avatar as round on the default theme 2021-04-19 13:22:50 +01:00
Hugo Sales e105889a59 [AVATAR] Fix JS cropping script and save square image, in case other themes need it 2021-04-19 13:22:50 +01:00
Hugo Sales c37a75cf7b [ImageEncoder] Move DB::persist call to AttachmentThumbnail 2021-04-19 13:22:40 +01:00
Hugo Sales a33a25983e [ImageEncoder] Fix error when not providing a width and/or height 2021-04-19 13:22:26 +01:00
Diogo Peralta Cordeiro 2f137f8b44 [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-04-19 13:22:04 +01:00
Diogo Peralta Cordeiro 0f52638a80 [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-04-19 13:21:32 +01:00
Hugo Sales bbc2fe1b5a [ENTITY] Cache AttachmentThumbnail query result 2021-04-16 20:28:39 +01:00
Hugo Sales 45a894c953 [Poll] Move tables from core to plugin 2021-04-16 20:28:39 +01:00
Hugo Sales c8915df31e [ImageThumbnail] Finish image thumbnailing functionality 2021-04-16 20:28:39 +01:00
Hugo Sales f6dea6e162 [DB] Fix bug in custom criteria format wrangling 2021-04-16 20:28:39 +01:00
Hugo Sales ec8ad1888a [DEPENDENCIES] Add php-vips 2021-04-16 20:28:39 +01:00
Hugo Sales 8a280c349f [TOOLS] In pre-commit hook, only run php-doc-check if some PHP file changed 2021-04-16 20:28:39 +01:00
Hugo Sales cbb36c9531 [DOCKER] Add VIPS PHP extension to Docker build 2021-04-16 20:28:39 +01:00
Hugo Sales acf5bd1ff5 [ImageThumbnail] Implement image resizing with Intervention/Image 2021-04-16 20:28:39 +01:00
Hugo Sales 6dd6491bee [ImageThumbnail] Structure of plugin to generate thumbnails for image attachments 2021-04-16 20:28:39 +01:00
Hugo Sales 2f65311ae6 [DB] Allow DQL queries with table name rather than entity 2021-04-16 20:28:39 +01:00
Hugo Sales cadd48922d [CORE] Add functionality to App\Core\Controller to get and validate GET parameters 2021-04-16 20:28:39 +01:00
Hugo Sales 2232f28283 [Posting] Rename file to attachment and cache result of actor tag query 2021-04-16 20:28:39 +01:00
Hugo Sales b639ce906c [MEDIA] Rename File to Attachment 2021-04-16 20:28:39 +01:00
Hugo Sales d6414e51a2 [CORE] Rename NoteScope to VisibilityScope, as it will be used for attachment visbility too 2021-04-16 20:28:39 +01:00
Hugo Sales 1fda65bc3d [DB] Add table map which allows using table names rather than entities in Doctrine operations 2021-04-16 20:28:39 +01:00
Hugo Sales a5505bf848 [AUTOGENERATED] Update autogenerated code 2021-04-16 20:28:39 +01:00
Hugo Sales 678d62781b [Directory] Fixup directory plugin 2021-04-16 20:28:39 +01:00
Diogo Peralta Cordeiro b5ffe8a52b [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-04-16 20:28:23 +01:00
Diogo Peralta Cordeiro 8e9da452c6 [MEDIA] Refactor File as Attachment 2021-04-16 20:27:33 +01:00
Hugo Sales 8fc2a83e3c [CONFIG][TWIG] Move twig config to php code to add support for placing templates in modules (plugins and components) 2021-04-16 20:21:54 +01:00
Hugo Sales f4e40002a4 [Directory] Move templates from core to plugin 2021-04-16 20:21:54 +01:00
Hugo Sales 8c6881f526 [COMPOSER][Media] Add example composer.json in plugins/Media 2021-04-16 20:21:54 +01:00
Hugo Sales 0802f7a9e3 [TWIG] Add way to launch events from TWIG, capture service and add way to render from a string 2021-04-16 20:21:54 +01:00
Hugo Sales d95e51a030 [LEFT][EVENT] Add event allowing plugins to add links to the left panel 2021-04-16 20:21:54 +01:00
Hugo Sales 085a98cea3 [Favourite] Move table and left panel links to plugin 2021-04-16 20:21:54 +01:00
Hugo Sales f7af76a1ba [DEPENDENCIES] Add wikimedia/composer-merge-plugin 2021-04-16 20:21:54 +01:00
Hugo Sales c5b26bcffb [FAVOURITE] Temporary commit to fix exception in timeline 2021-04-16 20:21:54 +01:00
Hugo Sales 244cc8dae1 [Favourite] Move controller to plugin 2021-04-16 20:21:54 +01:00
Hugo Sales 520733888d Rename FFmpeg to VideoThumbnail FIXME 2021-04-16 20:21:54 +01:00
Diogo Peralta Cordeiro a1cac40f6a [MODULES] Introduce the concept of abstract modules to V3
Introduce placeholder for abstract upload and thumb modules
Temporarily supress some bugs
2021-04-16 20:21:54 +01:00
Hugo Sales 6bfea8a0df [NETWORK] Fix big brain bug 2021-04-14 19:54:38 +00:00
Hugo Sales ae29a9c00a [ENTITY] Remove extraneous File_thumbnail.php file 2021-04-14 16:12:26 +00:00
Diogo Peralta Cordeiro 5ddc551fd9 [SCRIPTS] pre-commit now has variables double quoted 2021-04-14 15:47:10 +00:00
Diogo Peralta Cordeiro 764ff60c34 [INSTALL] Elaborate on localhost installation 2021-04-14 15:47:06 +00:00
Hugo Sales ae91f75aeb [FFmpeg] Copy FFmpeg plugin from v2 2021-04-14 15:44:45 +00:00
Hugo Sales 5d4f544a03 [TOOLS] Fix missing exported variable in bin/configure 2021-04-14 15:40:14 +00:00
Hugo Sales efd2719481 [Embed][StoreRemoteMedia][Media] Copy and cleanup plugins from v2 2021-04-14 15:37:24 +00:00
Hugo Sales 66ed6fb658 [Media] Copy media subsystem from v2 and roughly structure it for v3 2021-04-14 15:37:24 +00:00
Hugo Sales 6606a72e67 [TOOLS][DOCKER] Make sure composer doesn't require interaction when installing 2021-04-14 15:37:24 +00:00
Hugo Sales fc019d6a6e [ActivityPub] Remove ActivityPub plugin until we're ready to work on it, as it needs significant work 2021-04-14 15:37:24 +00:00
Hugo Sales 6be1622fd0 [DB][FKEY] Temporarily disable foreign key mapping, as there seems to be a bug in doctrine, which is under investigation 2021-04-14 15:37:24 +00:00
Hugo Sales d0fd0e6c6c [DB] Remove unique constraint from GSActor.nickname and fix register and related functionality 2021-04-14 15:37:24 +00:00
Hugo Sales 079d230959 [NICKNAME] Don't throw when normalizing reserved nicknames 2021-04-14 15:37:24 +00:00
Hugo Sales 637c25d5fe [WEB] Fix translations and small inconsistency when opening on web 2021-04-14 15:37:24 +00:00
Hugo Sales 051720a686 [TESTS] Add tests to all relevant methods under App\Util and fix errors that popup 2021-04-14 15:37:24 +00:00
Hugo Sales f3c2048c62 [NICKNAME] Add nickname min length config and check it in Nickname::normalize 2021-04-14 15:37:24 +00:00
Hugo Sales 988c5af6d3 [DEPENDENCIES] Add jchook/phpunit-assert-throws and update dependencies 2021-04-14 15:37:24 +00:00
Hugo Sales aa58c3520c Duplicate src/Util/Exception/NicknameTooLongException.php history in src/Util/Exception/NicknameTooShortException.php history. 2021-04-14 15:37:24 +00:00
Hugo Sales cafd9a39a0 [TESTS] Add tests of Common and fix small oddities that pop up 2021-04-14 15:37:24 +00:00
Hugo Sales 120011a2d0 [TESTS] Fix error when testing cold redis cache 2021-04-14 15:37:24 +00:00
Hugo Sales c8b2ce6694 [TESTS] Add tests for the bitmap utility and fix implementation 2021-04-14 15:37:24 +00:00
Hugo Sales b855dd00ac [LOG] Only try to log if setup, so logs can be disabled, in tests, for instance 2021-04-14 15:37:24 +00:00
Hugo Sales d082f4249c 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-04-14 15:37:24 +00:00
Hugo Sales f11f9040b1 [TESTS] Add App\Core\Cache test 2021-04-14 15:37:24 +00:00
Hugo Sales 27dbd5521a [DEPENDENCIES] Update dependencies, including redis-polyfill, to be able to implement a test 2021-04-14 15:37:24 +00:00
Hugo Sales 4f3b797c80 [TESTS] Update PHPUnit configuration and upgrade to version 9.5 2021-04-14 15:37:23 +00:00
Hugo Sales f5df7edc6c [DOCKER][TOOLS] Add option to bin/configure to use a prebuilt PHP container (https://hub.docker.com/repository/docker/gsocial/php) 2021-04-14 15:37:23 +00:00
Hugo Sales 99c4e8ded5 [TESTS] Fix deprecations 2021-04-14 15:37:23 +00:00
Hugo Sales a8b599d213 [DOCKER] Fix default docker-compose file 2021-04-14 15:37:23 +00:00
Hugo Sales ffaf5da984 [TESTS] Fix unkept unit tests 2021-04-14 15:37:23 +00:00
Hugo Sales 49fa11ba07 [TEST] Fix translation test 2021-04-14 15:37:23 +00:00
Hugo Sales a1546a51cd [DEPENDENCIES] Upgrade to Symfony 5.2 to get my upstream ICU translation feature 2021-04-14 15:37:23 +00:00
Hugo Sales 0f0851dbf3 [DOCUMENTATION] Add documentation on installing without docker and other topics 2021-04-14 15:37:23 +00:00
Hugo Sales ef617819e0 [DOCUMENTATION] Add documentation on installing with Docker 2021-04-14 15:37:23 +00:00
Hugo Sales 636f8d1be9 [DOCTRINE][CONFIGURATION] Add new required Doctrine DBAL parameter, for testing environments 2021-04-14 15:37:23 +00:00
Hugo Sales b8c73d2d2a [DEPENDENCIES] Update all dependencies 2021-04-14 15:37:23 +00:00
Hugo Sales b2aff4c75e [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-04-14 15:37:23 +00:00
Hugo Sales f912236114 [DOCKER][MAIL] Temporarily disable mail container 2021-04-14 15:37:23 +00:00
Hugo Sales 349df02f78 [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-04-14 15:37:23 +00:00
Hugo Sales 0a15ccab9b [DOKER][MAIL][BOOTSTRAP] Make bootstrap generate separate certificates for the web root and the mail server 2021-04-14 15:37:23 +00:00
Hugo Sales cfbb28f1ea [DOCKER] Remove quotes from docker env files, as docker (or at least docker-compose) include them in the actual value 2021-04-14 15:37:23 +00:00
Hugo Sales e008bf1863 [DB] Make Note.source reference NoteSource.code, the primary key 2021-04-14 15:37:23 +00:00
Hugo Sales 297d30706f [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-04-14 15:37:23 +00:00
Hugo Sales 2f570fcc2a [TOOLS][DOCKER] Rewrite the configuration script to use whiptail/dialog, and refactor 2021-04-14 15:37:23 +00:00
Angelo D. Moura ea6623f029 [UI][NOTE][MARKDOWN] Add markdown support to the notes 2021-04-14 15:37:23 +00:00
up201706832 ccf4480395 [REPLY] Fixed CSS for reply form, making it now usable 2021-04-14 15:37:23 +00:00
up201706832 98f072bc12 [AUTH][REMEMBER-ME] Changed name of column in rememberme_token database table to fix bug 2021-04-14 15:37:23 +00:00
Daniel 03aa46cf4e [ProfileColor] Added profile color css 2021-04-14 15:37:23 +00:00
Daniel 4d0f87b91b [ProfileColor] Visualize profile color 2021-04-14 15:37:23 +00:00
Daniel a4fdb193bc [ProfileColor] Added Profile Color entity, color form and db store/load to color settings controler 2021-04-14 15:37:23 +00:00
Daniel 9ea47c5385 [ProfileColor] Added plugin base, controller and settings template 2021-04-14 15:37:23 +00:00
Daniel 79f0615441 [REVERSEFAV] Added css to make Reverse favs label in one line 2021-04-14 15:37:23 +00:00
Daniel 7e215d9f9e [REVERSEFAV] Fixed typo 2021-04-14 15:37:23 +00:00
Daniel 04b9c736a6 [REVERSEFAV] Added reverse favorourites stream/template 2021-04-14 15:37:23 +00:00
João Brandão 5e26359783 [UI][TIMELINES] Refactored query for public stream 2021-04-14 15:37:23 +00:00
Daniel 493476f408 [UI][TIMELINES] Fix undefined main_nav_tabs in logged out view 2021-04-14 15:37:23 +00:00
Angelo D. Moura 54fd7eda06 [Directory] Actors stream now includes a link to groups stream 2021-04-14 15:37:23 +00:00
Angelo D. Moura f838dbe5f3 [Directory] Lint fix - missed a coma 2021-04-14 15:37:23 +00:00
Angelo D. Moura 793e1b0417 [Directory] Finished implementing groups stream as a plugin 2021-04-14 15:37:23 +00:00
Angelo D. Moura 66875e93f8 [Directory] Finished implementing groups stream using Directory plugin 2021-04-14 15:37:23 +00:00
Angelo D. Moura 08fe5fb23f [Directory] Changed the route so the plugin Directory doesn't take over - query is not working for some reason 2021-04-14 15:37:23 +00:00
Angelo D. Moura d4038cd520 [Directory] Added the route, controller and a blank template file - something is broken 2021-04-14 15:37:23 +00:00
Angelo D. Moura f29b15924c [Directory] Actors are now organized by nickname 2021-04-14 15:37:23 +00:00
Angelo D. Moura f621e521f9 [Directory] Actors are no longer related with notes, and the logged in user now shows on the stream 2021-04-14 15:37:23 +00:00
Angelo D. Moura b9622e4512 [Directory] Changed the title of the template and add a css rule for actor-bio 2021-04-14 15:37:23 +00:00
Angelo D. Moura f67c41a7ac [Directory] Almost finishied creating the /actors stream - problems with the css files 2021-04-14 15:37:23 +00:00
Angelo D. Moura 19be786da8 [Directory] Add the route, controller function, and blank template file 2021-04-14 15:37:23 +00:00
Daniel e14efe86a4 [Directory] Add documentation 2021-04-14 15:37:23 +00:00
Daniel a19b51f91e [Directory] Moved /actors stream to directory plugin 2021-04-14 15:37:23 +00:00
Daniel 35d2bdfd5e [Cover] Remove of cover form 2021-04-14 15:37:23 +00:00
Daniel 485607169f [Cover] Added cover route verifications 2021-04-14 15:37:23 +00:00
Daniel ee039ab2e9 [Cover] Removed commented code 2021-04-14 15:37:23 +00:00
Daniel e2df8aec10 [Cover] Input restrictions, Code cleanup 2021-04-14 15:37:23 +00:00
Daniel 0d18615fd8 [Cover] Added cover css, changed cover settings route name 2021-04-14 15:37:23 +00:00
Daniel 4f69686968 [Cover] Added temporary css 2021-04-14 15:37:23 +00:00
Daniel 777b8b55fd [Cover] Cover route, cover now renders 2021-04-14 15:37:23 +00:00
Daniel 7688cc39a8 [Cover] Added TWIG vars for profile plugins 2021-04-14 15:37:23 +00:00
Daniel 4fd33bf37f [Cover] Added Cover Entity, updated form handler
Basically the same as the avatar
2021-04-14 15:37:23 +00:00
Daniel 3d3c560516 [Cover] Started implementing Cover plugin: base class, route, base templates, added tabs in profile template 2021-04-14 15:37:23 +00:00
Diogo Machado 1abc3e3e7d [STATIC ANALYSIS] Started removal process for the errors found by PHPStan 2021-04-14 15:37:23 +00:00
Pastilhas f1f4ad7ba7 [DOCKER][MAIL] Fixed hash command
Also added permissions to start.sh
2021-04-14 15:37:23 +00:00
margarida 66670ff220 [TOOLS][DOCKER] Add mail setup to configure 2021-04-14 15:37:23 +00:00
margarida a5eca9f110 [TOOLS][DOCKER] Changed script to write docker-compose.yaml 2021-04-14 15:37:23 +00:00
margarida f9b98f87a4 [TOOLS][DOCKER] Change dialog method to command substitution and redirection and add way of finding git's root 2021-04-14 15:37:23 +00:00
margarida 28b337f793 [TOOLS][DOCKER] Added input verfication 2021-04-14 15:37:23 +00:00
margarida b02564e575 [TOOLS][DOCKER] Added first version of configuration shell script 2021-04-14 15:37:23 +00:00
João Brandão 850f1b327e [UI] Visual restructure of login/register pages 2021-04-14 15:37:23 +00:00
João Brandão b98db96c27 [UI] Show public stream on login/register pages 2021-04-14 15:37:23 +00:00
up201706832 49b0494f28 [UI] Extracted public stream on login/register pages to a twig template 2021-04-14 15:37:23 +00:00
João Brandão 27137b4762 [UI] Visual restructure of login/register pages 2021-04-14 15:37:23 +00:00
Pastilhas 81109c88c7 [DOCKER][MAIL] Fixed variable expansion in run 2021-04-14 15:37:23 +00:00
Pastilhas 748d86d6d3 [DOCKER][MAIL] Cleanup opendkim.conf
Also improved consistency in other files
2021-04-14 15:37:23 +00:00
Pastilhas dde68b1d22 [DOCKER][MAIL] Removed unsued files, modified dovecot.conf 2021-04-14 15:37:23 +00:00
Pastilhas a27e3593fa [DOCKER][MAIL] User is now created on setup and Dockerfile
Continuation of previous commit
2021-04-14 15:37:23 +00:00
Pastilhas aaa6585a1e [DOCKER][MAIL] Removed unused config files and scripts
Now user is created on setup and dockerfile
2021-04-14 15:37:23 +00:00
Pastilhas a3908a22ae [DOCKER][MAIL] Substituted supervisord for s6 2021-04-14 15:37:23 +00:00
Pastilhas 0a5ac7cf7e [DOCKER][MAIL] Moved and modified `setup.sh` 2021-04-14 15:37:23 +00:00
Pastilhas 7e99d5faa8 [DOCKER][MAIL] Switched named volume to shared volume and changed env vars to env file 2021-04-14 15:37:23 +00:00
Pastilhas b43cc4f742 [DOCKER][MAIL] Improved exec.sh 2021-04-14 15:37:23 +00:00
Pastilhas e99d8481b5 [DOCKER][MAIL] Fixed small bug with ssl certificates 2021-04-14 15:37:23 +00:00
Pastilhas 5950986a6f [DOCKER][MAIL] Fixed small bugs in config and scripts 2021-04-14 15:37:23 +00:00
Pastilhas c37f5a59b3 [DOCKER][MAIL] New config files 2021-04-14 15:37:23 +00:00
Pastilhas 92ffc5644f [DOCKER][MAIL] Fused services into single container 2021-04-14 15:37:23 +00:00
Pastilhas 439ea2c182 [DOCKER][MAIL] Changed directory path 2021-04-14 15:37:23 +00:00
Pastilhas 27065e5ead [DOCKER][MAIL] Setup docker mail server 2021-04-14 15:37:23 +00:00
Pastilhas 12cfb5006a [DOCKER][MAIL] Added docker mailserver setup 2021-04-14 15:37:23 +00:00
Daniel ac16b3eff1 [Poll] Removed/refactored unnecessary files, changed redirect to default parameters in new poll route 2021-04-14 15:37:23 +00:00
Daniel 696ebe60e0 [Poll] Restructured templates, added misssing poll related css 2021-04-14 15:37:23 +00:00
Daniel 168b7d313a [Poll] Polls now have an associated note, poll templates, start_show_styles event, started css 2021-04-14 15:37:23 +00:00
Daniel 3a51d3ef89 [Poll] Started testing with note integration 2021-04-14 15:37:23 +00:00
Daniel 7c8dbccee2 [Poll] Added file headers 2021-04-14 15:37:23 +00:00
Daniel 7a925cd9a6 [Poll] Added modified param for Poll/Poll response, added PollTest 2021-04-14 15:37:23 +00:00
Daniel 0a1ea8749b [Poll] Added variable num of options
not sure if it is the right way to do it
2021-04-14 15:37:23 +00:00
Daniel 8543c8c68e [Poll] Added templates, response counting 2021-04-14 15:37:23 +00:00
Daniel 8bbeb79233 [Poll] Store poll response to DB 2021-04-14 15:37:23 +00:00
Daniel 4fcde940ff [Poll] Added New Route, RespondPoll, Poll Response, PollResponseForm 2021-04-14 15:37:23 +00:00
Daniel a98e3a32f9 [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-04-14 15:37:23 +00:00
Daniel a9c35def3f [AUTOGENERATED][Poll] Add auto generated code for poll entity and new route 2021-04-14 15:37:23 +00:00
Daniel b860c6bbb0 [Poll] Started porting Poll Plugin 2021-04-14 15:37:23 +00:00
margarida 03007194c8 [TOOLS][DOCKER] Added input verfication 2021-04-14 15:37:23 +00:00
margarida b600dc0902 [TOOLS][DOCKER] Added first version of configuration shell script 2021-04-14 15:37:23 +00:00
Daniel 0868880d45 [TESTS] Added unit tests 2021-04-14 15:37:23 +00:00
Angelo D. Moura 5ec7e2e092 [TWIG] Moves the SVG custom function to an extension and change the test regex 2021-04-14 15:37:23 +00:00
Angelo D. Moura b60185a97c [TWIG][TESTS] Update IconsExtension test 2021-04-14 15:37:23 +00:00
Angelo D. Moura cacd9a574d [TWIG] Add SVG icon embed function 2021-04-14 15:37:23 +00:00
Diogo Machado 5a7b895476 [DB][TESTS] Implement Doctrine event listener to update timestamps on modification, and related tests 2021-04-14 15:37:23 +00:00
Daniel 630ef3e826 [FORM] Implement ActorArrayTransformer 2021-04-14 15:37:23 +00:00
Hugo Sales bb4f5b88e7 [PLUGIN][Favourite] Move favourite table definition to inside the plugin, as it is now supported 2021-04-14 15:37:23 +00:00
Hugo Sales 23904f326d [SchemaDef] Finish association mapping implementation 2021-04-14 15:37:23 +00:00
Hugo Sales 9b42f525e8 [DB][NoteLocation] Add missing `multiplicity` to column 2021-04-14 15:37:23 +00:00
Hugo Sales 9d12dde7c1 [DB] Fix typo in table definitions and fix name of GSActorCircle table 2021-04-14 15:37:23 +00:00
Hugo Sales adb5cfbb72 [AUTOGENERATED][DB][File][GroupJoinQueue] Update autogenerated code and add select fields as specified in the previous commit 2021-04-14 15:37:23 +00:00
Hugo Sales f8c47387c4 [DB] Change foreign key specification to new format 2021-04-14 15:37:23 +00:00
Hugo Sales b337d6b2eb [SCHEMADEF] Add preliminary support for foreign keys 2021-04-14 15:37:23 +00:00
Hugo Sales f486656756 [DOCKER] Bump to PHP version 8 2021-04-14 15:37:23 +00:00
Hugo Sales 2c9bd3575b [AUTOGENERATED] Update auto generated code in entities 2021-04-14 15:37:23 +00:00
Hugo Sales e1941b6612 [TOOLS] Use GSActor rather than Gsactor in autogenerated code 2021-04-14 15:37:23 +00:00
Hugo Sales 256169a3c4 [GIT] Change my email to the new one in all files and bump copyright year 2021-04-14 15:37:23 +00:00
Hugo Sales f51a772826 Add some missing documentation to ActivityPub 2021-04-14 15:37:23 +00:00
Hugo Sales 0d2cf6eaa6 [DB] Merge definition of SchemaDefDriver with SchemaDefPass for clarity 2021-04-14 15:37:23 +00:00
Hugo Sales 8a14222d51 [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-04-14 15:37:23 +00:00
Hugo Sales 9c2a911dab [Reply] Fix bug where wrong variable is used when replying to a note 2021-04-14 15:37:23 +00:00
Hugo Sales cfc8af675f [DEPENDENCIES] Update all dependencies 2021-04-14 15:37:23 +00:00
Hugo Sales b1cb923036 Ensure group table name is quoted, as it's a reserved word in postgreSQL 2021-04-14 15:37:23 +00:00
Hugo Sales ff1d6d9df8 Small update to php dockerfile 2021-04-14 15:37:23 +00:00
Hugo Sales 5c1b3b99f4 [DEPENDENCIES] Update dependencies 2021-04-14 15:37:23 +00:00
Diogo Peralta Cordeiro 7e9ffbe033 [DOCUMENTATION] Add mdBook stub 2021-04-14 15:37:23 +00:00
Hugo Sales 38deea85e2 [CONFIGURATION] Remove the individual language settings from social.yaml, as these are not something that changes at runtime 2021-04-14 15:37:23 +00:00
Hugo Sales 91eb3354e3 [COMMAND] Change the way ListEventsCommand outputs the results to use the output interface and add some formatting 2021-04-14 15:37:23 +00:00
Hugo Sales 5cced1c9ed [DOCUMENTATION][REFACTOR] Add documentation to all flagged function and do some small cleanup 2021-04-14 15:37:23 +00:00
Hugo Sales 9cc7b6adf5 [HOOKS] Update pre-commit script to check for missing documentation in functions 2021-04-14 15:37:23 +00:00
Hugo Sales 1f4f080bd2 [DEPENDENCIES] Update dependencies 2021-04-14 15:37:23 +00:00
Hugo Sales 8eb32add3a [DEPENDENCIES] Update dependencies 2021-04-14 15:37:23 +00:00
Hugo Sales fdaa89e3c9 [EVENT] Rename event names to camel case to make finding handlers easier 2021-04-14 15:37:23 +00:00
Hugo Sales dd8fe29a98 [REGISTER] Add self follow when registering, fixing the '-1 followers' bug 2021-04-14 15:37:23 +00:00
Hugo Sales ed9e4be6b2 [SETTINGS][NOTIFICATIONS] Fix error when displaying the user notification settings page 2021-04-14 15:37:23 +00:00
Hugo Sales 69202ce7a0 [Reply] Fix missing use statement 2021-04-14 15:37:23 +00:00
Hugo Sales 88ce4cbf80 [CONTROLLER] Fix use of undefined variable 2021-04-14 15:37:23 +00:00
Hugo Sales 4c021a2838 [POSTING] Fix missing use statement 2021-04-14 15:37:23 +00:00
Tiago Magalhaes 4050222bc8 [CORE] made configure script explicitly fail when bootstrap.env is not present 2021-04-14 15:37:23 +00:00
Hugo Sales 4b4da170f2 [CONFIG][CACHE] Move cache configuration from environment variables to the configuration file 2021-04-14 15:37:23 +00:00
Hugo Sales 8ef85e90e9 [UI] Make configured instance name show in UI, fix repeat icon 2021-04-14 15:37:23 +00:00
Hugo Sales 06e92344cc [CONFIG] Various fixes to use new configuration format 2021-04-14 15:37:23 +00:00
Hugo Sales a9944592c4 [CONFIG] Fix error on missing or empty local configuration 2021-04-14 15:37:23 +00:00
Hugo Sales 02c7bdf4f0 [CONFIG][DB] Remove config from the database, put it in yaml, so it can be baked into the container 2021-04-14 15:37:22 +00:00
Hugo Sales b34307b74c [DEPENDENCIES] Update dependencies 2021-04-14 15:37:22 +00:00
Hugo Sales c43f25f4b8 [CSS][UI] Update CSS to fix reply note action (icon swap needed) and remove duplication 2021-04-14 15:37:22 +00:00
Hugo Sales 8547c54103 [CORE] Add missing use statement in module base class 2021-04-14 15:37:22 +00:00
Hugo Sales db608ca3c1 [UTILS] Make bitmap not use a static class var and set object properties as lowercase 2021-04-14 15:37:22 +00:00
Hugo Sales abc32ecc0e [NoteAction] Refactor duplicated code out to base class 2021-04-14 15:37:22 +00:00
Hugo Sales d7ff38fe24 [NOTE] Add isVisibleTo 2021-04-14 15:37:22 +00:00
Hugo Sales 4b84ef5183 [Directory] Add missing use statement 2021-04-14 15:37:22 +00:00
Hugo Sales a7b7d487d7 POSTING remove REPLY route 2021-04-14 15:37:22 +00:00
Hugo Sales 7e7bfd1958 [EXCEPTION] Make findOne return NotFoundException 2021-04-14 15:37:22 +00:00
Hugo Sales 9f4a53dbbd [EXCEPTION] Add base class to invalid form exception and add URL arguments to redirect exception 2021-04-14 15:37:22 +00:00
Hugo Sales 96415f8523 [SECURITY] Fix getRoles 2021-04-14 15:37:22 +00:00
Hugo Sales e0672e559a [MODULES] Fix module manager dev-mode rebuild 2021-04-14 15:37:22 +00:00
Hugo Sales c8b6db650a [UI] Only show note action buttons if a user is logged in 2021-04-14 15:37:22 +00:00
Hugo Sales 9ae31501cc [Controller] Fix exception handler to recurse on the exception's previous (in some contexts, RedirectException gets wrapped) 2021-04-14 15:37:22 +00:00
Hugo Sales 1330c96681 [UTIL] Update bitmap base class, making using easier 2021-04-14 15:37:22 +00:00
Hugo Sales 774d7ffdf9 [Reply] Move reply functionality to a plugin 2021-04-14 15:37:22 +00:00
Hugo Sales 0492d71294 [NoteActions] Refactor note actions and fix bug in favourite 2021-04-14 15:37:22 +00:00
Hugo Sales 1c37eb7c72 [UI] Display error when submitted form is invalid 2021-04-14 15:37:22 +00:00
Hugo Sales 34fab45b6b [MODULE][DB] Added support for loading entity definitions from modules 2021-04-14 15:37:22 +00:00
Hugo Sales b1e49f67f4 FIXUP WITH DOCTRINE DEV 2021-04-14 15:37:22 +00:00
Hugo Sales 6926d70543 [Bridge] Replace zero dates with CURRENT_TIMESTAMP 2021-04-14 15:37:22 +00:00
Hugo Sales 09a1342588 [TOOLS] Add support for updating autocode in modules 2021-04-14 15:37:22 +00:00
Hugo Sales ff96c2bb59 [AUTOGENERATED] Update autogenerated code in module entities 2021-04-14 15:37:22 +00:00
Hugo Sales 4ab7da32ce [CONFIG][DEV][DOCTRINE] Add doctrine stacktrace tracking in dev mode 2021-04-14 15:37:22 +00:00
Hugo Sales 749bec5d52 [EXCEPTION][UI][UX] Add RedirectException, which can be thrown anywhere to redirect somewhere, and an exception handler 2021-04-14 15:37:22 +00:00
Hugo Sales 7a68ba4f05 [UI][NOTE] Add reply to in UI 2021-04-14 15:37:22 +00:00
Hugo Sales 3affbc3c78 [Posting] Fix form name and remove unused recycle route and controller 2021-04-14 15:37:22 +00:00
Hugo Sales 5663e5e58d [Media] Add cache control directive to all files served 2021-04-14 15:37:22 +00:00
Hugo Sales 922c435e28 [Repeat][Favourite] Only display action buttons if logged in (instead of forcing login) 2021-04-14 15:37:22 +00:00
Hugo Sales 5c4be9d29e [DB] Add rendered collumn to note table, so we can preserve microtags from other services 2021-04-14 15:37:22 +00:00
Hugo Sales 13fb9b4698 [DB] Fix local_user table to use a numeric id, since the username is editable 2021-04-14 15:37:22 +00:00
rainydaysavings 8e17dd1829 [TWIG] Improving view template structure 2021-04-14 15:37:22 +00:00
rainydaysavings 3a4d3fc1e2 [TWIG] Adding active rules 2021-04-14 15:37:22 +00:00
rainydaysavings 1f7e3a1d90 [UI] CSS polish all around 2021-04-14 15:37:22 +00:00
rainydaysavings 40aa4fa60e [CONTROLLER][ROUTE] Favourites page initial query implementation and routing 2021-04-14 15:37:22 +00:00
rainydaysavings aa4418e71a [UI] Fixing Login CSS issues 2021-04-14 15:37:22 +00:00
Hugo Sales 76b8b29776 [UI][FEED] Fix scope in timelines; major rewrite of home timeline query, still missing scoping and paging 2021-04-14 15:37:22 +00:00
Hugo Sales d862457623 [DB][DEFAULTS] Change attachment storage location from uploads to attachments 2021-04-14 15:37:22 +00:00
Hugo Sales f6a40390e0 [DB] Rename notice to activity in notification table 2021-04-14 15:37:22 +00:00
Hugo Sales 1387eab434 [UI] Subtract self follow from total user follows 2021-04-14 15:37:22 +00:00
Hugo Sales ca576981a3 [DB][NOTE] Update scope 2021-04-14 15:37:22 +00:00
Hugo Sales 5cf7050008 [Media] Display images and videos inline in notes 2021-04-14 15:37:22 +00:00
rainydaysavings af3ed18d48 [UI][TWIG] Fixing note actions placement and size, more descriptive rules 2021-04-14 15:37:22 +00:00
Hugo Sales 54e8852fb7 [Posting] Fix posting form name and css 2021-04-14 15:37:22 +00:00
Hugo Sales 109b17b1f9 [FORM] Add names to forms 2021-04-14 15:37:22 +00:00
Hugo Sales a129a6e368 [DB] Add Activity table, to store all known activity 2021-04-14 15:37:22 +00:00
Hugo Sales 736fb672a5 [EXCEPTION] Fix exceptions not being translated 2021-04-14 15:37:22 +00:00
Hugo Sales 7b467091d6 [DB] Add wrapper for making native queries 2021-04-14 15:37:22 +00:00
Hugo Sales b364a51f80 [Directory] Add directory plugin, for listing people and groups 2021-04-14 15:37:22 +00:00
Hugo Sales 8f68d7deb4 [Posting] Add missing includes 2021-04-14 15:37:22 +00:00
rainydaysavings b5b39b5f68 [UI] All radio buttons now look like they should 2021-04-14 15:37:22 +00:00
rainydaysavings 49cd0af021 [PLUGIN] Removing unnecessary labels 2021-04-14 15:37:22 +00:00
rainydaysavings c423101c00 [TWIG] Making notes view more easily customizable 2021-04-14 15:37:22 +00:00
rainydaysavings ebf6f8d735 [UI] Fixing note actions views 2021-04-14 15:37:22 +00:00
rainydaysavings ce94d50043 [PLUGIN] Recycle initial implementation 2021-04-14 15:37:22 +00:00
rainydaysavings 0ed0d0470c [Favourite] Add backend support for favourite 2021-04-14 15:37:22 +00:00
rainydaysavings 153c8d0d64 [DB] Temporarily add favourite entity in core, as plugins don't support them yet 2021-04-14 15:37:22 +00:00
Hugo Sales 75bc71f473 [DB] Add helper for removing entities 2021-04-14 15:37:22 +00:00
Hugo Sales 29f30a6932 [DB] Add support for calling methods with FQCN 2021-04-14 15:37:22 +00:00
rainydaysavings f4e52f5e11 [TWIG] Various routes added 2021-04-14 15:37:22 +00:00
rainydaysavings 902a57d10f [UI] Replies border fix and other minor fixes 2021-04-14 15:37:22 +00:00
rainydaysavings 928064c5ee [CONTROLLER] Replies and network queries implemented 2021-04-14 15:37:22 +00:00
rainydaysavings 492f32c555 [COMPONENT][CONTROLLER][TWIG] Recycle component work 2021-04-14 15:37:22 +00:00
rainydaysavings b0566e7b8c [TWIG][UI] Replies CSS fixes 2021-04-14 15:37:22 +00:00
rainydaysavings 6d3dba17d2 [ROUTE] Network and replies routes added 2021-04-14 15:37:22 +00:00
rainydaysavings 2d1200e2e6 [COMPONENT] Favourite initial implementation 2021-04-14 15:37:22 +00:00
rainydaysavings a2c40163f5 [UI] Fixing note actions placement 2021-04-14 15:37:22 +00:00
rainydaysavings a51c546f8c [UI] Responsiveness overall polish 2021-04-14 15:37:22 +00:00
rainydaysavings b2c2e6b6c6 [UI] Browser compatibility improvements, various small fixes 2021-04-14 15:37:22 +00:00
rainydaysavings fa0612c0d1 [UI] Checkboxes now display a custom tick 2021-04-14 15:37:22 +00:00
rainydaysavings 4d0028d95f [TWIG] Timeline attachment form restructure 2021-04-14 15:37:22 +00:00
rainydaysavings 54c54990a4 [Controller] Attempting to fix home timeline query 2021-04-14 15:37:22 +00:00
rainydaysavings 930a9a99f2 [UI] No focus outlines by default 2021-04-14 15:37:22 +00:00
rainydaysavings 36aff803c6 [UI] Reply icon now shows accordingly, same for the replies themselves 2021-04-14 15:37:22 +00:00
rainydaysavings b609932726 [UI] Custom and accessible checkboxes, radio buttons and normal buttons 2021-04-14 15:37:22 +00:00
rainydaysavings 8f7790fa3c [COMPONENT] Fixing typo 2021-04-14 15:37:22 +00:00
Hugo Sales e13e763d5c [NOTE][UI] Add note replying and UI displaying 2021-04-14 15:37:22 +00:00
Hugo Sales c0caf520b8 [CACHE] Fix bug in list caching 2021-04-14 15:37:22 +00:00
Hugo Sales 1c1ef7a572 [MODULE] Fix avatars not loading 2021-04-14 15:37:22 +00:00
Hugo Sales fefee324b4 [DB][MODULES][ActivityPub] Cleanup table definitions 2021-04-14 15:37:22 +00:00
rainydaysavings 07178e6ffa [TWIG] Timeline template rework 2021-04-14 15:37:22 +00:00
rainydaysavings d96e4f9076 [ROUTE] Home timeline route url now shows accordingly as the user nickname 2021-04-14 15:37:22 +00:00
rainydaysavings 6445931493 [UI] Posting form re-styling 2021-04-14 15:37:22 +00:00
rainydaysavings 11d6c19d65 [CONTROLLER] Reply initial implementation 2021-04-14 15:37:22 +00:00
rainydaysavings b9355b49f3 [COMPONENT] Posting form restructure and minor fixes 2021-04-14 15:37:22 +00:00
rainydaysavings be86a05ddb [TWIG] Timeline structure rework 2021-04-14 15:37:22 +00:00
rainydaysavings 79be38992f [ROUTE] Home timeline added 2021-04-14 15:37:22 +00:00
rainydaysavings 6d92230c32 [CONTROLLER] Home timeline controller work 2021-04-14 15:37:22 +00:00
rainydaysavings f5267a1975 [UI] Posting form styling work 2021-04-14 15:37:22 +00:00
rainydaysavings 5dbde32f01 [COMPONENT] Posting form now shows a random default string 2021-04-14 15:37:22 +00:00
rainydaysavings 655b5e36a4 [UI] Links removed since they are part of a plugin 2021-04-14 15:37:22 +00:00
rainydaysavings a4c6fbbbd8 [UI] Post form new structure first styling implementation 2021-04-14 15:37:22 +00:00
rainydaysavings 1f89f3298c [UI] Small border fix 2021-04-14 15:37:22 +00:00
rainydaysavings f16fcb0200 [COMPONENTS] Small fix 2021-04-14 15:37:22 +00:00
rainydaysavings 253705f704 [UI][TWIG] Better, divided form rendring of the posting form 2021-04-14 15:37:22 +00:00
rainydaysavings cf8c85f6ba [UI] Small border radius problem fix 2021-04-14 15:37:22 +00:00
rainydaysavings dec35a6aa1 [COMPONENT] Posts scope initial form 2021-04-14 15:37:22 +00:00
rainydaysavings 9442556c3e [UI] Fixing login and register styling, refactoring 2021-04-14 15:37:22 +00:00
rainydaysavings a761c4e11a [UI] Fixing issue where notices wouldn't break text 2021-04-14 15:37:22 +00:00
rainydaysavings fa40bfb8dc [UI] Fixing static pages styling 2021-04-14 15:37:22 +00:00
rainydaysavings 27d292affd [UI] Fixes to settings CSS 2021-04-14 15:37:22 +00:00
rainydaysavings dc5992bebd [UI] Finalizing timeline structure and CSS 2021-04-14 15:37:22 +00:00
rainydaysavings 3c06a1e24f [UI] New reset CSS to deal with firefox's abysmal and evil defaults 2021-04-14 15:37:22 +00:00
Hugo Sales 84cfa65bc6 [ActivityPub] Initial cleanup, removing 'die' statements, and ignoring the subfolders 2021-04-14 15:37:22 +00:00
Hugo Sales 43665749bb [UI][TWIG] Small UI cleanup and change twig 'active' function to check for starts with, rather than equals 2021-04-14 15:37:22 +00:00
Hugo Sales 57297aba56 [DB][MEDIA] Small database structure changes 2021-04-14 15:37:22 +00:00
Hugo Sales 9204213dbc [MEDIA] Only try to get an avatar if a user is logged in 2021-04-14 15:37:22 +00:00
Hugo Sales 1b0cab6dc8 [UI][NOTE] Post and see attachments 2021-04-14 15:37:22 +00:00
Hugo Sales 9a0c64c3d1 [DEPENDENCY] Add tgalopin/html-sanitizer-bundle and transitively tgalopin/html-sanitizer 2021-04-14 15:37:22 +00:00
Hugo Sales 4b8e6bb198 [MEDIA][CACHE] Cache avatar queries and delete stale values; small refactoring 2021-04-14 15:37:22 +00:00
Hugo Sales 624aef0a8e [UI][MEDIA] Add actor avatar in feed timeline 2021-04-14 15:37:22 +00:00
Hugo Sales d66ec9d85c [SECURITY] Fix error in user registering where password wasn't hashed 2021-04-14 15:37:22 +00:00
rainydaysavings fc6bb1ddf6 [UI] Fixing timeline notice structure and CSS 2021-04-14 15:37:22 +00:00
rainydaysavings 475e78e13f [UI] Fix left panel new dynamic components view 2021-04-14 15:37:22 +00:00
Hugo Sales 8ceeb6be80 [UI][SELFTAGS] Display 'none' if the user doesn't have selftags 2021-04-14 15:37:22 +00:00
Hugo Sales f76bfca921 [UI][ACCOUNT][SETTINGS] Hack to fix error related to phone number, until a solution is found upstream 2021-04-14 15:37:22 +00:00
Hugo Sales 0758b84d2c [UI][LEFT] Add # before selftags, and link 2021-04-14 15:37:22 +00:00
Hugo Sales aab9212ffa [UI][FAQ] Fix static pages 2021-04-14 15:37:22 +00:00
Hugo Sales b3c5fe9e96 [CONTROLLER] Stop propagation of kernel.controller so notices aren't posted 5 times. Not sure why it happens otherwise 2021-04-14 15:37:22 +00:00
Hugo Sales 8ca49478ab [WRAPPER][HTTPClient] Static wrapper around Symfony's HTTP Client 2021-04-14 15:37:22 +00:00
Hugo Sales e142b90653 [DB][FOLLOW] Change Follow table 2021-04-14 15:37:22 +00:00
Hugo Sales 8276baecab [UI][CACHE][DB] Add follow counts to left panel, caching the results; change follow table 2021-04-14 15:37:22 +00:00
Hugo Sales b678ab2191 [UI][LEFT] Add link to settings on avatar and personal info 2021-04-14 15:37:22 +00:00
Hugo Sales 5ed2abaf64 [ENTITY] Add Entity base class to all entities 2021-04-14 15:37:22 +00:00
Hugo Sales 513a1e58b8 [MODULE][Left][UI][TAGS] Add Left module which handles fetching tags and followers, fix self tags 2021-04-14 15:37:22 +00:00
Hugo Sales d86636ebd4 [DB][File] Remove timestamp, add actor_id 2021-04-14 15:37:22 +00:00
Hugo Sales 6d1fa10965 [DB][AVATAR] Remove extraneous slash 2021-04-14 15:37:22 +00:00
Hugo Sales 86bd1dbbbf [DB][DEFAULTS] Add avatar/default 2021-04-14 15:37:22 +00:00
Hugo Sales 96aa98cbcf [UserPanel] Fix upload of avatar 2021-04-14 15:37:22 +00:00
Hugo Sales 30deeaf4ef [Media] Use utils 2021-04-14 15:37:22 +00:00
Hugo Sales fe50909549 [DB] Add 'dql' method to wrap 'createQuery' and replace 'Gsactor' with 'GSActor' 2021-04-14 15:37:22 +00:00
Hugo Sales 651af27674 [Media] Move code from media.php to utils.php 2021-04-14 15:37:22 +00:00
Hugo Sales 75958fc9b4 [MODULE][Posting] Add Posting module, which handles notice posting 2021-04-14 15:37:22 +00:00
Hugo Sales 036b4480f3 [MEDIA] Move avatar fetching and adding to ouput to media component 2021-04-14 15:37:22 +00:00
Hugo Sales d4813b4ce9 [UTIL][Common] Fix import 2021-04-14 15:37:22 +00:00
Hugo Sales 21be5199cc [CONTROLLER][AdminPanel] Add missing use statement 2021-04-14 15:37:22 +00:00
Hugo Sales 8f43c12e22 [STREAM][NetworkPublic] Add skeleton of public timeline and posting 2021-04-14 15:37:22 +00:00
Hugo Sales a752a5a07c [UTIL][Common] Implement 'isSystemPath' 2021-04-14 15:37:22 +00:00
Hugo Sales 4945a1342f [SECURITY] Wrap getUser in a try catch, in case the user doesn't exist 2021-04-14 15:37:22 +00:00
Hugo Sales fd7e06bf18 [COMMAND] Fix 'bin/console doctrine:database:create' by only loading defaults if we have a connection 2021-04-14 15:37:22 +00:00
Hugo Sales e2960aebcb [DEPENDENCY] Update dependencies 2021-04-14 15:37:22 +00:00
Hugo Sales 5b11c26e79 [AUTOGENERATED] Update autogenerated code 2021-04-14 15:37:22 +00:00
Hugo Sales a60c79c35d [TOOLS] Fix bin/generate_entity_fields 2021-04-14 15:37:22 +00:00
Hugo Sales 0508886fc4 [CORE][DB] Fix uses of db tables after previous restructure 2021-04-14 15:37:22 +00:00
Hugo Sales e0af29fd5e [CORE] Data Representation and Modelling refactor 2021-04-14 15:37:22 +00:00
rainydaysavings 9fa363d9bf [UI] Public feed responsive CSS work 2021-04-14 15:37:22 +00:00
rainydaysavings c3a0b08c40 [UI] Responsive settings CSS work 2021-04-14 15:37:22 +00:00
rainydaysavings b52d4faca7 [UI] Responsive base design polish 2021-04-14 15:37:22 +00:00
rainydaysavings 7d593366c7 [UI] Feed structure done, feed CSS work 2021-04-14 15:37:22 +00:00
rainydaysavings 4d00a0f6dd [UI] Polishing design, settings pages 2021-04-14 15:37:22 +00:00
rainydaysavings 648a911055 [UI] Polishing base template 2021-04-14 15:37:22 +00:00
rainydaysavings 0d3a1cc14e [UI] Left panel theme now looks like it should 2021-04-14 15:37:22 +00:00
rainydaysavings c9b36b6030 [UI] Right panel checkbox size fix 2021-04-14 15:37:22 +00:00
rainydaysavings 52caf7cab1 [UI] Settings theme according to base theme 2021-04-14 15:37:22 +00:00
rainydaysavings 46d946f381 [UI] Complete base CSS overhaul and new theme 2021-04-14 15:37:22 +00:00
rainydaysavings 4568578e16 [UI] Right panel added 2021-04-14 15:37:22 +00:00
rainydaysavings c0b6d8807f [UI] Additional fixes to settings page 2021-04-14 15:37:22 +00:00
rainydaysavings c25d33e38a [UI] Small general settings CSS fixes 2021-04-14 15:37:22 +00:00
rainydaysavings 488247119a [UI] Settings small fix 2021-04-14 15:37:22 +00:00
rainydaysavings 08c792fac7 [UI] Notification settings checkbox placement fix 2021-04-14 15:37:22 +00:00
Hugo Sales e96c273351 [LocalUser] Fix missing extend 2021-04-14 15:37:22 +00:00
Hugo Sales a20e95fd38 [ROUTES] Add redirect from root to main/all, link from header to root, and change parameter order on RouteLoader::connect 2021-04-14 15:37:22 +00:00
Hugo Sales 37f21b516d [EXCEPTION] Add ServerException and inherit previous throwable
imported from v2/5ea5d3007563f76a77efbfb66936315441922542
2021-04-14 15:37:22 +00:00
Alexei Sorokin 690b8750c6 [DATABASE] Enable fulltext search by default
Also rename fulltext indices to more fitting names

Imported from v2/f84dbb369f01a1d4a9bc362d01cdd100cdc79313
2021-04-14 15:37:22 +00:00
Hugo Sales 459a60d789 [MEDIA][EXCEPTIONS] Fix errors and deprecations 2021-04-14 15:37:22 +00:00
Hugo Sales 56c4309cb8 [SECURITY] Refactor 2021-04-14 15:37:22 +00:00
Hugo Sales d21d4f5cb1 [AVATAR] Update way avatar is sent, to use proper symfony responses, make config('site', 's_static_delivery') into a boolean 2021-04-14 15:37:22 +00:00
Hugo Sales a498134b13 [AUTOGENERATED] Update autogenerated code 2021-04-14 15:37:22 +00:00
Hugo Sales d91ab6f277 [TOOLS] Update generate_entity_fields 2021-04-14 15:37:22 +00:00
Hugo Sales a5c97762e0 [AVATAR] Fixed avatar upload, added avatar inline download and updated template and base controller 2021-04-14 15:37:22 +00:00
Hugo Sales d5e41ec099 [AVATAR] Handle avatar upload without js and save and validate uploaded files 2021-04-14 15:37:22 +00:00
Hugo Sales 9e45641b7b [AUTOGENERATED] Update autogenerated code 2021-04-14 15:37:22 +00:00
Hugo Sales b7300c6457 [DEPENDENCIES] Update dependencies 2021-04-14 15:37:22 +00:00
Hugo Sales 04258b6072 [JS] Whitespace cleanup 2021-04-14 15:37:22 +00:00
Hugo Sales 480904a4e3 [MEDIA][AVATAR] Handle avatar validation and storage 2021-04-14 15:37:22 +00:00
Hugo Sales 7635f455ab [DB] Add entity base class to allow sharing methods such as 'create' 2021-04-14 15:37:22 +00:00
Hugo Sales c06346ef31 [DB][FILE][AVATAR] Handle deleting files, change file and avatar tables 2021-04-14 15:37:22 +00:00
Hugo Sales 65d6204a01 [UI][SETTINGS] User notification settings with configurable transports (through plugins) 2021-04-14 15:37:22 +00:00
Hugo Sales bdacd638c7 [PLUGIN] Remove Test plugin 2021-04-14 15:37:22 +00:00
Hugo Sales b9a2badc31 [COMPONENT][PLUGIN] Move Email and XMPP notification handlers from components to plugins, so they can be disabled 2021-04-14 15:37:22 +00:00
Hugo Sales be49bfa0c1 [COMPONENT] Remove 'post on status change' option for email transport 2021-04-14 15:37:22 +00:00
Hugo Sales c1963438bc [AUTOGENERATED] Update autogenerated code 2021-04-14 15:37:22 +00:00
rainydaysavings 19e4f120c0 [UI] Notifications settings styling finished 2021-04-14 15:37:22 +00:00
rainydaysavings cc4a95fbd5 [UI] Notification settings styling progress 2021-04-14 15:37:22 +00:00
rainydaysavings 246bf30c41 [UI] Notification settings tabs functional 2021-04-14 15:37:22 +00:00
rainydaysavings a71f54c6bf [UI] Notifications settings page CSS work 2021-04-14 15:37:22 +00:00
rainydaysavings e4db0eb9b9 [UI] Accessibility improvements all around 2021-04-14 15:37:22 +00:00
rainydaysavings e1ff2a0ef1 [UI] Fixed issue where certain form element would be on top the left panel 2021-04-14 15:37:22 +00:00
rainydaysavings 3f98f8fecf [ROUTES] Fix use statement 2021-04-14 15:37:22 +00:00
rainydaysavings 10010552e1 [JS][UI][AVATAR] JS cropping script 2021-04-14 15:37:22 +00:00
rainydaysavings d38bf8ff4c [UI] Cropping avatar as a circle, proper preview done 2021-04-14 15:37:22 +00:00
rainydaysavings caab08b017 [CONTROLLER][UI] Avatar JS cropping added 2021-04-14 15:37:22 +00:00
rainydaysavings 85d8d9b268 [UI] Login and Register button fixes 2021-04-14 15:37:22 +00:00
rainydaysavings a64a099d7d [UI] Controller and Route for FAQ page created as well as basic template structure 2021-04-14 15:37:22 +00:00
rainydaysavings 2a75237c70 [UI] Settings pages routes and styling done. 2021-04-14 15:37:22 +00:00
Hugo Sales d7801737f6 [COMPONENT][PLUGIN] Small refactor and add license 2021-04-14 15:37:22 +00:00
Hugo Sales b2b0990bf6 [CONTROLLER][UI] Add notification settings form 2021-04-14 15:37:22 +00:00
Hugo Sales ed84c1f8bf [MODULE] Reload modules if modified, except in production environment 2021-04-14 15:37:22 +00:00
Hugo Sales 6567f10e69 [UTIL] Fix and rename arrayRemoveKeys 2021-04-14 15:37:22 +00:00
Hugo Sales b2dbf9bc20 [CONFIG] Cleanup services.yaml config file 2021-04-14 15:37:21 +00:00
Hugo Sales 78929629f0 [FORMATTING] Update license header in SchemaDef compiler pass 2021-04-14 15:37:21 +00:00
Hugo Sales 7945a9c825 [EVENT][ROUTES] Add event to allow modules to add routes, 'add_route' 2021-04-14 15:37:21 +00:00
Hugo Sales 4c60aac8f8 [FRAMEWORK] Avoid double initializing the framework 2021-04-14 15:37:21 +00:00
Hugo Sales d394f6fc9c [EVENT] Fix event handler and rename events to snake_case 2021-04-14 15:37:21 +00:00
Hugo Sales 605a8919a7 [PLUGIN] Update example plugin 2021-04-14 15:37:21 +00:00
Hugo Sales bfa3095137 [MODULE][COMPILER] Add compiler pass responsible for loading, instantiating and wiring enabled modules 2021-04-14 15:37:21 +00:00
Hugo Sales 0e401edac2 [MODULE] Added module base class 2021-04-14 15:37:21 +00:00
Hugo Sales 468d00d393 [COMMAND] Fix ListEvents command to properly display the callable 2021-04-14 15:37:21 +00:00
Hugo Sales f5f10890b6 [COMPOSER][MODULE] Add autoloading rules for components and plugins 2021-04-14 15:37:21 +00:00
Hugo Sales 9b2db7608b [MODULE] Renamed modules to components 2021-04-14 15:37:21 +00:00
Hugo Sales 0ca169aad2 [UTIL][FORM] Add form transformer array <--> string 2021-04-14 15:37:21 +00:00
Hugo Sales 9291bfbecb [UTIL] Add Common::array_remove_keys 2021-04-14 15:37:21 +00:00
Hugo Sales e620c20bb4 [UI][CONTROLLER] Work on tabbed notification settings panel 2021-04-14 15:37:21 +00:00
Hugo Sales 4b73024a57 [MAIL] Make mailserver a required service 2021-04-14 15:37:21 +00:00
Hugo Sales b4e42d6562 [SECURITY] Ensure ARGON2 constants are defined, or throw exception 2021-04-14 15:37:21 +00:00
Hugo Sales 3934d403ef [DEPENDENCY] Update dependencies 2021-04-14 15:37:21 +00:00
Hugo Sales e571c62319 [UI][CONTROLLER] Refactor UserPanel to use the new Form::handle method and add placeholders in the password fields 2021-04-14 15:37:21 +00:00
Hugo Sales 9dffd1c93e [DATABASE] Remove DATABASE::flush from Profile::setSelfTags 2021-04-14 15:37:21 +00:00
Hugo Sales 34890aff90 [FORMATTING] Add option to split a string to array by both a comma and a space 2021-04-14 15:37:21 +00:00
Hugo Sales 503fa2e537 [FORM] Add DataTransformer to and from array 2021-04-14 15:37:21 +00:00
Hugo Sales e10e6644e3 [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-04-14 15:37:21 +00:00
Hugo Sales 4d2770319e [DATABASE][TOOLS] Update local_user to return the proper PhoneNumber type 2021-04-14 15:37:21 +00:00
Hugo Sales 0cba00ebbb [CORE][SECURITY][UX] Save previous url on /register and /logout 2021-04-14 15:37:21 +00:00
rainydaysavings 6e52fd4c95 [CONTROLLER][UI] Fixing controller display errors, Settings CSS fixes 2021-04-14 15:37:21 +00:00
rainydaysavings 9a2ac34ba3 [UI][ROUTES][CONTROLLER] Fixing Settings navs and templates 2021-04-14 15:37:21 +00:00
rainydaysavings e334ce9a55 [UI] Register and Login rework 2021-04-14 15:37:21 +00:00
rainydaysavings b8a0d14fd5 [UI] Left panel fix 2021-04-14 15:37:21 +00:00
Hugo Sales 7b0f5ab576 [Controller] Update settings/avatar 2021-04-14 15:37:21 +00:00
Hugo Sales e7f541219d [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-04-14 15:37:21 +00:00
Hugo Sales 0bc59f1b9a [UI] Update settings/account to reflect the current values 2021-04-14 15:37:21 +00:00
Hugo Sales 8088b78a24 [UI][ROUTES] s%settings/profile%settings/personal_info% 2021-04-14 15:37:21 +00:00
Hugo Sales b98d01bd06 [CORE][UX] Save previous url to redirect back after registering 2021-04-14 15:37:21 +00:00
Hugo Sales d0c999199b [DEPENDENCY] Add odolbeau/phone-number-bundle 2021-04-14 15:37:21 +00:00
Hugo Sales f907843d43 [UTIL][NICKNAME][FIX] Add self to constant 2021-04-14 15:37:21 +00:00
Hugo Sales 8aa1a3d05e [UI][USERPANEL] Add prefilled fields, mark some as optional and handle self tags in the profile settings page 2021-04-14 15:37:21 +00:00
Hugo Sales c91c385dec [SELFTAGS] Add Profile::{set,get}SelfTags 2021-04-14 15:37:21 +00:00
Hugo Sales 2838aaad14 [FORM] Add Form::isRequired 2021-04-14 15:37:21 +00:00
Hugo Sales d6a7843240 [DATABASE] Refactor DB.php and make findBy always return an array, instead of a doctrine collection 2021-04-14 15:37:21 +00:00
Hugo Sales 51f65edb55 [UTIL] Update Formatting::{toString,toArray} to allow spliting by either space or comma 2021-04-14 15:37:21 +00:00
Alexei Sorokin 256d57adaa [SECURITY] Update way passwords are checked and update 2021-04-14 15:37:21 +00:00
rainydaysavings 6d6b1447f8 [TWIG][ROUTES] Footer links routes and pages added 2021-04-14 15:37:21 +00:00
rainydaysavings 801399218f [UI][ROUTES] Footer links added 2021-04-14 15:37:21 +00:00
rainydaysavings c2e69a06b0 [UI] Fixing problem due to Firefox's autofill filter 2021-04-14 15:37:21 +00:00
rainydaysavings ae49f82580 [UI] Login template small fix 2021-04-14 15:37:21 +00:00
rainydaysavings 879666fab7 [UI] Left panel template and Login page CSS work done 2021-04-14 15:37:21 +00:00
Hugo Sales 81e45e3ace [SECURITY][CONTROLLER] Remove unreachable code from the Security controller 2021-04-14 15:37:21 +00:00
Hugo Sales 107351a6b5 [CONTROLLER][ADMIN][CONFIG] Fix form to use static strings as keys and add labels seperately; convert input from string to appropriate type 2021-04-14 15:37:21 +00:00
Hugo Sales 292d98a33c [LIB][Util] Update Common::setConfig to throw an exception if appropriate, add Formatting::{toString,toArray} 2021-04-14 15:37:21 +00:00
Hugo Sales 72ee91a8da [I18N] Remove incomplete autogenerated translation file, to be fixed later 2021-04-14 15:37:21 +00:00
Hugo Sales 2eb61543d9 [AUTOGENERATED] Update all entity fields 2021-04-14 15:37:21 +00:00
Hugo Sales 60002df680 [SECURITY] Small refactor in Authenticator.php, to remove unused services 2021-04-14 15:37:21 +00:00
Hugo Sales f081d58e2b [USER][UI][AUTHENTICATION] Add registration form 2021-04-14 15:37:21 +00:00
Hugo Sales 13244c1e37 [UTIL][NICKNAME] Small refactor and remove the check between user nickname and group_alias, as these will have different semantics 2021-04-14 15:37:21 +00:00
Hugo Sales 71c9462d2e [DATABASE] Fix typos in user_notification_prefs 2021-04-14 15:37:21 +00:00
Hugo Sales c410f9b67a [DATABASE] Fix typo in profile table and add a constructor 2021-04-14 15:37:21 +00:00
Hugo Sales b4fb1569ce [FORM][WRAPPER] Merge argument options, not replace 2021-04-14 15:37:21 +00:00
Hugo Sales fdcedb8295 [DATABASE] Update LocalUser table to not have a numerical id, add is_email_verified and fix getProfile accordingly 2021-04-14 15:37:21 +00:00
Hugo Sales 39e3e8a04e [MAILER][WRAPPER] Add mailer wrapper that respects the configuration 2021-04-14 15:37:21 +00:00
Hugo Sales 7bb3717673 [FIX] Fix bug in DATABASE.php, since findBy can return different types 2021-04-14 15:37:21 +00:00
Hugo Sales 8dcf563674 [TOOLS] Update generate fields script to output default values 2021-04-14 15:37:21 +00:00
Hugo Sales a582cfe4f2 [DEPENDENCY] Add symfonycasts/verify-email-bundle 2021-04-14 15:37:21 +00:00
Hugo Sales 0af82054ff [COMMON][SECURITY][WRAPPER] Added security service static wrapper and Common::getUser 2021-04-14 15:37:21 +00:00
Hugo Sales f812d9142f [CORE][ROUTES] Small refactor on entrypoint and RouteLoader 2021-04-14 15:37:21 +00:00
Hugo Sales 92ecb50cff [TWIG] Add twig function to output the active tag if the current route matches a given one 2021-04-14 15:37:21 +00:00
Hugo Sales 1b2c308808 [CONTROLLER][ROUTES] Refactor the base Controller to not reinvent the wheel too much and rely on Symfony's events 2021-04-14 15:37:21 +00:00
Hugo Sales 0c448ee83f [ROUTES] Change name of admin and settings routes and refactor the way they're specified 2021-04-14 15:37:21 +00:00
Hugo Sales a075d35c8c [CONTROLLER][ROUTES] Refactor controllers to use the new base class and remove controller from the class name 2021-04-14 15:37:21 +00:00
Hugo Sales f26b488045 [NOTIFICATION][DATABASE] Update user notification prefs table, implementation of Notification and define a base class for notification transport 2021-04-14 15:37:21 +00:00
Hugo Sales 2fd81e218a [CONTROLLER] Remove example enqueue 2021-04-14 15:37:21 +00:00
Hugo Sales fc4d8bcf65 [DEFAULTS] Add password hashing algorithm default settings 2021-04-14 15:37:21 +00:00
Hugo Sales 0ef9223803 [LOGIN] Implement password checking and related systems 2021-04-14 15:37:21 +00:00
Hugo Sales 9bc186a072 [USER] Add UserRoles 2021-04-14 15:37:21 +00:00
Hugo Sales 70cb6d5d94 [DATABASE] Add role collumn to profile table 2021-04-14 15:37:21 +00:00
Hugo Sales 162a955f41 [UTIL] Update Common::config to ensure the values queried exist 2021-04-14 15:37:21 +00:00
Hugo Sales aadb4832bc [DEFAULTS][FIX] Fix logic error that kept reloading the table when the file wasn't modified 2021-04-14 15:37:21 +00:00
Hugo Sales 0e96ffe287 [UI][SESSION] Add login and logout pages 2021-04-14 15:37:21 +00:00
Hugo Sales 958d5bfe22 [LIB][Util] Remove Functional::arity as it got merged upstream as Functional\ary 2021-04-14 15:37:21 +00:00
Hugo Sales c0ba6250aa [LIB][Util] Make Common::config return the unserialized value instead of the entity 2021-04-14 15:37:21 +00:00
Hugo Sales 0a6b134f23 [CORE] Refactor GNUsocial.php so it initializes itself as a service 2021-04-14 15:37:21 +00:00
Hugo Sales a7715fc9c3 [I18N] Remove support for context until it proves necessary, as it broke the code 2021-04-14 15:37:21 +00:00
Hugo Sales b508fbe3b1 [TOOLS] Update shebang on scripts to use the correct php executable 2021-04-14 15:37:21 +00:00
Hugo Sales 9a9ac8b55f [AUTOGENERATED] Update autogenerated code 2021-04-14 15:37:21 +00:00
Hugo Sales adda4caea4 [DATABASE] Rename user table to local_user, since doctrine shits itself otherwise ._. 2021-04-14 15:37:21 +00:00
Hugo Sales a7ff0ef506 [DEPENDENCY] Add symfony/config as a dependency 2021-04-14 15:37:21 +00:00
Hugo Sales ee1c1bce80 [CONFIG] Change way configuration is done to use Symfony's system instead of environment vars 2021-04-14 15:37:21 +00:00
Hugo Sales 7b00ab4699 [CORE][I18n][DEFAULTS] Remove I18nHelper 2021-04-14 15:37:21 +00:00
Hugo Sales df60e72fb3 [LIB][Util] Change methods in the Common class to camelCase, add isSystemPath (previously in Nickname.php) 2021-04-14 15:37:21 +00:00
Hugo Sales d5b5d97bc1 [UTIL][NICKNAME] Import nickname utilities and exceptions from v2 2021-04-14 15:37:21 +00:00
Hugo Sales 640c4b2ca8 [DEFAULTS] Update default reserved usernames 2021-04-14 15:37:21 +00:00
Hugo Sales 11822cbed0 [DATABASE][WRAPPER] Add findBy method which allows finding entities with a complex expression 2021-04-14 15:37:21 +00:00
Hugo Sales 9fb74c2f27 [DATABASE] Re-import the local_group table, as it'll be used as per the new group semantics 2021-04-14 15:37:21 +00:00
rainydaysavings f361a64ab5 [UI] Settings routes refactor, avatar and misc settings added. 2021-04-14 15:37:21 +00:00
rainydaysavings a4934a4ef3 [UI][CONTROLLER][ROUTES] UserPanel account page form added, account page CSS work 2021-04-14 15:37:21 +00:00
rainydaysavings 46c63b3240 [UI] Fixed top header spacing issue, hamburger menu weird rendering 2021-04-14 15:37:21 +00:00
rainydaysavings f77f56e1f2 [UI] FAQ pages markdown fixes 2021-04-14 15:37:21 +00:00
rainydaysavings 2f05f05dc9 [UI][CONTROLLER] Form help messages added, fixed checkbox trick hitbox 2021-04-14 15:37:21 +00:00
rainydaysavings 02b17049e3 [UI] Reset CSS added, small fixes all around 2021-04-14 15:37:21 +00:00
rainydaysavings ee6791fe97 [UI] Settings page CSS redesign port completed 2021-04-14 15:37:21 +00:00
rainydaysavings de5554f1e2 [UI][CONTROLLER][ROUTES] Corrected core action name, UserPanel CSS work 2021-04-14 15:37:21 +00:00
rainydaysavings e1f9143cf5 [UI] Fixed FAQ template issues 2021-04-14 15:37:21 +00:00
rainydaysavings 4fd69b684a [UI] Side panel animation added 2021-04-14 15:37:21 +00:00
rainydaysavings c133565780 [UI] Redesign responsiveness work done 2021-04-14 15:37:21 +00:00
rainydaysavings 099be93420 [UI] Further work into new side panel design 2021-04-14 15:37:21 +00:00
rainydaysavings 43b7076ff8 [UI] Basic implementation of the new base design 2021-04-14 15:37:21 +00:00
rainydaysavings 49e33557e1 [UI] Polishing FAQ CSS 2021-04-14 15:37:21 +00:00
rainydaysavings f16789f10e [UI] Standardization of sizes and variable usage for faster theming 2021-04-14 15:37:21 +00:00
rainydaysavings 2b4540952e [UI][CONTROLLER] Profile settings action functionality working 2021-04-14 15:37:21 +00:00
Hugo Sales 91ff4dbdec [DATABASE][WRAPPER] Update DATABASE wrapper so entity names are provided without the namespace 2021-04-14 15:37:21 +00:00
Hugo Sales 4cc196a69a [DOCKER] Add redis to the docker image 2021-04-14 15:37:21 +00:00
Hugo Sales 7cedbcd63f [CACHE] Fix usage of the redis extension 2021-04-14 15:37:21 +00:00
Hugo Sales ba7ad5fd28 [DOCKER] Fix redis extension build process to use LZ4 and add APCu 2021-04-14 15:37:21 +00:00
Hugo Sales 7ca22ecc1d [DEPENDENCY] Add mock polyfill implementations of the redis and memcached extension 2021-04-14 15:37:21 +00:00
Hugo Sales 02a23a2aff [CACHE][Redis] Add special support for redis (fixed size lists), set method and general fixes 2021-04-14 15:37:21 +00:00
Hugo Sales 47af6e85b8 [LIB][Util] Refactor and implement array indexing methods on RingBuffer 2021-04-14 15:37:21 +00:00
Hugo Sales 04b0d63d43 [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-04-14 15:37:21 +00:00
Hugo Sales 62c9b56b3f [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-04-14 15:37:21 +00:00
Hugo Sales 155038a5c0 [DOCKER] Add msgpack and redis extensions to docker image 2021-04-14 15:37:21 +00:00
Hugo Sales 89ce298a3b [DEFAULTS] Add modified time to config table (as value) and only reload the defaults if the file is newer 2021-04-14 15:37:21 +00:00
Hugo Sales 9563fb0af3 [CACHE] Extend the static wrapper to support working with lists in caches that don't natively support them 2021-04-14 15:37:21 +00:00
Hugo Sales 2c4fcaaf07 [LIB][Util] Add Ring Buffer data structure 2021-04-14 15:37:21 +00:00
Hugo Sales 3aaad123de [DOCKER] Move certbot files to hidden folder, so it's ignored by GNU global 2021-04-14 15:37:21 +00:00
Hugo Sales 7a07b95240 [DEFAULTS][FIX] Fix defaults to use value from environment 2021-04-14 15:37:21 +00:00
Hugo Sales e5babcd36e [PHP][EXTENSION][POLYFILL] Add php-ds polyfill, which is used if the native extension is not available 2021-04-14 15:37:21 +00:00
Hugo Sales 1134fec173 [DOCKER][PHP][EXTENSION] Add PHP ds extension to docker PHP image 2021-04-14 15:37:21 +00:00
Hugo Sales d2b44f4400 [TOOLS][configure] Fix to use quotation marks properly 2021-04-14 15:37:21 +00:00
Hugo Sales aaba304ca8 [CACHE][WRAPPER] Fix cache wrapper 2021-04-14 15:37:21 +00:00
Hugo Sales 288f8363ae [FORMATTING][DEFAULTS] Fix formatting and remove redundant config defaults 2021-04-14 15:37:21 +00:00
Hugo Sales b09e1525eb [CACHE][HTTP] Configure simple HTTP cache 2021-04-14 15:37:21 +00:00
Hugo Sales 9fadb73ea5 [CACHE] Add a static wrapper around symfony/cache 2021-04-14 15:37:21 +00:00
Hugo Sales cf1483e6b5 [DEPENDENCY] Add symfony/cache as a dependency 2021-04-14 15:37:21 +00:00
Hugo Sales b579842eb6 [LOG][WRAPPER] Refactor log wrapper 2021-04-14 15:37:21 +00:00
Hugo Sales 47ab835549 [QUEUE] Add queueing wrapper, default configuration and example usage 2021-04-14 15:37:21 +00:00
Hugo Sales 69341880d3 [DEPENDENCY] Add symfony/messenger as a dependency 2021-04-14 15:37:21 +00:00
Hugo Sales e146ebc05b [DATABASE] Delete queue_item table, as queueing will be handled by messenger 2021-04-14 15:37:21 +00:00
Hugo Sales ceb5092b34 [CONTROLLER] Show dummy notices in main/all 2021-04-14 15:37:21 +00:00
Hugo Sales 17da1f7fb5 [MODULES] Make ModulesManager check if file exists 2021-04-14 15:37:21 +00:00
Hugo Sales 43e56c08f7 [CONTROLLER] Add Controller base class, which handles rendering templates if requested HTML or json, accordingly 2021-04-14 15:37:21 +00:00
Hugo Sales 6f9c70398b [ROUTE] Fix routes, config_admin got deleted 2021-04-14 15:37:21 +00:00
Hugo Sales 8b9a1dd535 [COMMAND][DEPRECATION][FIX] Fix app:events's deprecation 2021-04-14 15:37:21 +00:00
Hugo Sales c2d9d5b75b [GIT] Update gitignore to ignore composer.local.json, where plugin settings will be placed 2021-04-14 15:37:21 +00:00
Hugo Sales 4fa6295fde [DEPENDENCY] Update all dependencies 2021-04-14 15:37:21 +00:00
Hugo Sales bf4c06295a [DEPENDENCY] Add wikimedia/composer-merge-plugin as a dependency, to allow managing plugins 2021-04-14 15:37:21 +00:00
rainydaysavings 443a5438be [DEPENDENCY] Add erusev/parsedown and twig/markdown-extra as dependencies 2021-04-14 15:37:21 +00:00
rainydaysavings 9b88f93cad [TWIG][CONFIG] Change default_path and add public_path 2021-04-14 15:37:21 +00:00
rainydaysavings d6196a5e69 [CONTROLLER] UserAdminPanel handle request fix
[UI] Minor CSS font size corrections
2021-04-14 15:37:21 +00:00
rainydaysavings b39d43a700 [TWIG][UI] Settings: removed unnecessary pages, responsive CSS work 2021-04-14 15:37:21 +00:00
rainydaysavings 20497bf905 [UI][CONTROLLER] Settings page styling almost done. 2021-04-14 15:37:21 +00:00
rainydaysavings fe20ed08d0 [TWIG] faq/contact template route fix 2021-04-14 15:37:21 +00:00
rainydaysavings a87653860b [FAQ] Removing unnecessary categories. 2021-04-14 15:37:21 +00:00
rainydaysavings 9f2977bfd1 [UI][FAQ] FAQ polish, better use of twig, responsive css. 2021-04-14 15:37:21 +00:00
rainydaysavings 1e8efe180c [UI][Mobile][FAQ][ROUTES] FAQ sub pages and routing added, small screen css work started. 2021-04-14 15:37:21 +00:00
rainydaysavings 29712edbd3 [UI] Icon assets now work with Symfony asset component, header icon placement fixes 2021-04-14 15:37:21 +00:00
rainydaysavings 0211771d5f [UI][ROUTES][CONTROLLER] Settings pages routes and styling done. 2021-04-14 15:37:21 +00:00
rainydaysavings f100d33d94 [UI] Work started on profile settings page. 2021-04-14 15:37:21 +00:00
rainydaysavings 9a05f11b65 [UI][Header] New header implemented. 2021-04-14 15:37:21 +00:00
rainydaysavings b28f3ffa19 [TWIG][UI] CSS refactoring, containerized twig blocks and settings initial work 2021-04-14 15:37:21 +00:00
rainydaysavings 168d138481 [TWIG] FAQ base template hierarchy fixes
Minor refactoring of routes for FAQ static pages
2021-04-14 15:37:21 +00:00
rainydaysavings 3daa764d87 [UI][ROUTES] Better use of icons, fixing static pages routing. 2021-04-14 15:37:21 +00:00
rainydaysavings ff06671cd5 [UI][FAQ] Better FAQ organization, removing unnecessary categories. 2021-04-14 15:37:21 +00:00
rainydaysavings 5736bd1408 [UI][Mobile][FAQ] FAQ polish, better use of twig, responsive css. 2021-04-14 15:37:21 +00:00
rainydaysavings 999b31b615 [UI][Mobile][FAQ] FAQ sub pages and routing added, small screen css work started. 2021-04-14 15:37:21 +00:00
rainydaysavings e925c566ac [UI] SVG icons added
[TWIG][UI] Header completed
2021-04-14 15:37:21 +00:00
rainydaysavings 8faf299a23 [UI][FAQ] FAQ page progress, assets folder and assets added 2021-04-14 15:37:21 +00:00
rainydaysavings 2985284f2b [UI][FAQ] Controller and Route for FAQ page created as well as basic template structure 2021-04-14 15:37:21 +00:00
Hugo Sales 2d1d697498 [TOOLS] Fix bootstrap and pre-commit scripts 2021-04-14 15:37:21 +00:00
Hugo Sales 57310dcb15 [MODULE] Move 'foreign' entities from core to a module 2021-04-14 15:37:21 +00:00
Hugo Sales a574971f0b [AUTOGENERATED] Update autogenerated code 2021-04-14 15:37:21 +00:00
Hugo Sales 68a5551f36 [TOOLS] Update bin/generate_entity_fields script 2021-04-14 15:37:21 +00:00
Hugo Sales 3a6b4cca1e [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-04-14 15:37:21 +00:00
Hugo Sales 6fe35833e7 [CORE][DATABASE] Restructure the database 2021-04-14 15:37:21 +00:00
Hugo Sales 4b4f235481 [ROUTE][ADMIN][CONFIG] Add route to update values in the config table 2021-04-14 15:37:21 +00:00
Hugo Sales 6c0c84c284 [DEFAULTS] Small fixes and add check of SOCIAL_NO_RELOAD_DEFAULTS from the environment, to override the reloading of default values 2021-04-14 15:37:21 +00:00
Hugo Sales 26c966084a [DOCUMENTATION] Fixed type annotations and documentation of Common::size_str_to_int 2021-04-14 15:37:21 +00:00
Hugo Sales bf92c44d81 [CORE][Event] Move GSEvent to Event, no longer a name collision 2021-04-14 15:37:21 +00:00
Hugo Sales 07422c4e1a [FORM] Added Symfony Form wrapper 2021-04-14 15:37:21 +00:00
Hugo Sales 6e6e50939b [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-04-14 15:37:21 +00:00
Hugo Sales 0ecb164e2e [TESTS][I18N] Added test for I18n::_m and I18nHelper::formatICU 2021-04-14 15:37:21 +00:00
Hugo Sales 15cf498e75 [I18N] Fix implementation of I18nHelper::formatICU 2021-04-14 15:37:21 +00:00
Hugo Sales 377965d100 [ROUTES] Add easier support for using TemplateController and improved documentation 2021-04-14 15:37:21 +00:00
Hugo Sales c9b0e994c1 [I18N] Small fixes. Still broken, though :') 2021-04-14 15:37:21 +00:00
Hugo Sales daf4f0727d [AUTOLOAD] Always autoload _m file 2021-04-14 15:37:21 +00:00
Hugo Sales 652c3b5d62 [DEPENDENCY] Upgrade Symfony framework from 5.0 to 5.1 2021-04-14 15:37:21 +00:00
Hugo Sales 95764a0c48 [DEPENDENCY][DEV] Added Symfony Bridge PHPUnit 2021-04-14 15:37:21 +00:00
Hugo Sales 97e9991d85 [GIT] Updated gitignore 2021-04-14 15:37:21 +00:00
Hugo Sales 26be897578 [I18N] Overhaul _m() implementation to support ICU message formats 2021-04-14 15:37:21 +00:00
Hugo Sales b15fb50194 [I18N] Dumped english translation files 2021-04-14 15:37:21 +00:00
Hugo Sales d7218535dd [TOOLS] Small fix to composer install hook 2021-04-14 15:37:21 +00:00
Hugo Sales 3108d82a4d [I18N] Added ability to call `_m_dynamic` from any class, allowing it to define translations for dynamic-valued calls to `_m` 2021-04-14 15:37:21 +00:00
Hugo Sales d5fa31a6f5 [TOOLS] Improve configure script to disallow reserved database names 2021-04-14 15:37:21 +00:00
Hugo Sales a8cd9034ff [I18N] Custom translation extractor based on Symfony's PhpExtractor, since we use instead of 2021-04-14 15:37:21 +00:00
Hugo Sales d73840352b [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-04-14 15:37:21 +00:00
Hugo Sales 766eac8467 [ROUTES] Add static wrapper around Symfony's router 2021-04-14 15:37:20 +00:00
Hugo Sales 0fe5ae7675 [CORE] Refactoring core 2021-04-14 15:37:20 +00:00
Hugo Sales 66c4ab7e24 [UI][CONFIG] Added admin configuration form 2021-04-14 15:37:20 +00:00
Hugo Sales 34f49edf2c [I18N] Fix i18n and add default domain 'Core' 2021-04-14 15:37:20 +00:00
Hugo Sales 27bb76706c [DEFAULTS] Fix default config loading logic 2021-04-14 15:37:20 +00:00
Hugo Sales ce00acdb39 [TOOLS] Fixed all licence blocks, to use the same, foldable, format 2021-04-14 15:37:20 +00:00
Hugo Sales e5a97611d0 [DEPENDENCY] Added alchemy/zippy, which adds support for multiple archive formats. Used to allow theme uploads in zip, tar, gz, or other formats 2021-04-14 15:37:20 +00:00
Hugo Sales d2f49e56bc [DOCUMENTATION][DEFAULTS] Documented all defaults and updated some. Restructured other documentation. 2021-04-14 15:37:20 +00:00
Hugo Sales 520989bc59 [DEFAULTS] Remove deprecated defaults and convert them to snake_case 2021-04-14 15:37:20 +00:00
Hugo Sales 2407853970 [GIT] Added docker-compose to gitignore, in preparation for using a script to generate them 2021-04-14 15:37:20 +00:00
Hugo Sales ccc0d7d401 [DATABASE] Change the way defaults are loaded, bulk insert, reload everything in debug mode, only on http requests (not command line) 2021-04-14 15:37:20 +00:00
Hugo Sales ac68436b0b [TOOLS] Update install scripts 2021-04-14 15:37:20 +00:00
Hugo Sales b0fece57ea [DATABASE] Fix 'relation config doesn't exist' 2021-04-14 15:37:20 +00:00
Hugo Sales 7dda377a79 [DATABASE] Postgres doesn't understand '0000-00-00 00:00:00' for datetime, use '-infinity' 2021-04-14 15:37:20 +00:00
Hugo Sales 4a754553f7 [DOCKER] Change postgres data path to the correct one 2021-04-14 15:37:20 +00:00
Hugo Sales 074797384d [DATABASE][CONFIG] Loading defaults into database, doctrine static wrapper 2021-04-14 15:37:20 +00:00
Hugo Sales 7718b167c3 [DATABASE][CONFIG] Bring default configs from V2 and implement DATABASE wrapper 2021-04-14 15:37:20 +00:00
Hugo Sales a2f5b77ff0 [ASSETS] Import old favicon.ico 2021-04-14 15:37:20 +00:00
Hugo Sales 40ec37bd27 [CORE][UTIL] Moved classes from util to core
And splitted up Common
2021-04-14 15:37:20 +00:00
Hugo Sales 0d5f66e8b8 [FORMATTING] Cherry-pick of Diogo's 763ac735c0758624ebd5957993dc0676b865927a 2021-04-14 15:37:20 +00:00
Diogo Cordeiro c67cf336d4 [DOCKER][BOOTSTRAP] Add option to use a self signed cert 2021-04-14 15:37:20 +00:00
Diogo Cordeiro 4efbf9361c [DOCUMENTATION][TOOL] Small bug fixes and docblock elaboration 2021-04-14 15:37:20 +00:00
Hugo Sales 87a768ac8d [CORE] Rename GNU social constants 2021-04-14 15:37:09 +00:00
Hugo Sales 26ee98a224 [DOCUMENTATION] Updated INSTALL.md to reflect the fact that a CNAME record can be used directly 2021-04-14 15:37:09 +00:00
Hugo Sales 5c1851028a [DOCUMENTATION] Import installation instructions for installing without docker from V2 2021-04-14 15:37:09 +00:00
Hugo Sales f0b8f91a75 [DOCKER] Small fixes to docker setup, imported from V2 2021-04-14 15:37:09 +00:00
Hugo Sales 70fac546da [DOCUMENTATION] Added a code walkthrough document, which explains how the codebase works 2021-04-14 15:37:09 +00:00
Hugo Sales 80755fc6e2 [DOCUMENTATION] Added install documentation 2021-04-14 15:37:09 +00:00
Hugo Sales feb3c16b3f [TOOLS] Updated install script to also support mariadb 2021-04-14 15:37:09 +00:00
Hugo Sales cef20e1332 [TOOLS][DOCKER] Further fixes in the docker environment and fixed the install script 2021-04-14 15:37:09 +00:00
Hugo Sales 3b50815422 [DOCKER] Fixed docker environment to properly configure the app environment 2021-04-14 15:37:09 +00:00
Hugo Sales 33270dabf3 [TOOLS] Added script to configure the installation and to bootstrap certificate creation with Let's Encrypt 2021-04-14 15:37:09 +00:00
Hugo Sales a97c511c7a [TOOLS][SSL] Added bin/boostrap_certificates.sh, allowing for easy configuration of SSL certificates with Let's Encrypt 2021-04-14 15:37:09 +00:00
Hugo Sales 3b5789639b [TOOLS][DOCUMENTATION] Improvements to entity generation script 2021-04-14 15:36:35 +00:00
Hugo Sales 04a59d22a6 [DOCUMENTATION] Added generated diagram 2021-04-14 15:36:35 +00:00
Hugo Sales cb1944aca9 [DOCUMENTATION][TOOLS] Wrote a tool to generate entity diagrams from the database definition 2021-04-14 15:36:35 +00:00
Hugo Sales c2add5e1d1 [UTIL] Wrote HTML library to convert arrays to html 2021-04-14 15:36:35 +00:00
Hugo Sales a72e0a53e7 [GITIGNORE] Add file folder to gitignore 2021-04-14 15:36:35 +00:00
Hugo Sales 3ad81ab730 [DATABASE] Changed the type of the 'id' field of the ProfileList table from serial to int, as doctrine complains otherwise 2021-04-14 15:36:35 +00:00
Hugo Sales 2afb15ee02 [TOOLS][CS-FIX] Altered some php-cs-fix rules 2021-04-14 15:36:35 +00:00
Hugo Sales 160b811669 [DATABASE][AUTOCODE] Update autocode to use \DateTimeInterface instead of DateTime 2021-04-14 15:36:35 +00:00
Hugo Sales fe603928e2 [TOOLS] Fix the generate_entities_fields script 2021-04-14 15:36:35 +00:00
Hugo Sales 57298da60e [SCHEMADEF] Bug fixes 2021-04-14 15:36:35 +00:00
Hugo Sales d14ac1edf6 [DATABASE] Add missing table names 2021-04-14 15:36:35 +00:00
Hugo Sales 8ca49ab511 [MODULES] Fix small error in modules manager 2021-04-14 15:36:35 +00:00
Hugo Sales 04202b59ef [DATABASE][AUTOCODE] Autogenerated fields, setters and getters for each entity 2021-04-14 15:36:35 +00:00
Hugo Sales 4bc3eabd29 [TOOLS][AUTOCODE] Fixed generate_entities script 2021-04-14 15:36:35 +00:00
Hugo Sales 36bc871c65 [TOOLS] Change autocode tag to allow editor folding 2021-04-14 15:36:35 +00:00
Hugo Sales 1589f6e26f [DATABASE] Removed calls to common_config 2021-04-14 15:36:35 +00:00
Hugo Sales b52f0c795e [FORMATTING] Changed license block format to allow folding 2021-04-14 15:36:35 +00:00
Hugo Sales 8f13d331ad [DATABASE] Extracted schemaDef method from old files and refactored onto new files 2021-04-14 15:36:35 +00:00
Hugo Sales b46e3d5bf4 [COMMON] Added toCamelCase and toSnakeCase functions 2021-04-14 15:36:35 +00:00
Hugo Sales 8a8d0f1dcd [TOOLS] Add script used to port from old class files to entities 2021-04-14 15:36:35 +00:00
Hugo Sales e3ef58bd8e [CORE][DATABASE] Import old classes folder into src/Entity 2021-04-14 15:36:35 +00:00
Hugo Sales 57f7f40fa9 [DATABASE] Remove testing entity 2021-04-14 15:36:35 +00:00
Hugo Sales 7e4aacd342 [TOOLS] Add command which imports a file with it's history 2021-04-14 15:36:35 +00:00
Hugo Sales 1caab62200 [MODULES] Rename extensions to modules, add example plugin, change plugin location 2021-04-14 15:36:35 +00:00
Hugo Sales 6c8da48efa [DOCUMENTATION] Document All The Things! 2021-04-14 15:36:35 +00:00
Hugo Sales 10a304ab83 [CORE][ROUTES] Implemented custom router, with an interface similar to the old one, which allows routes to be seperated into files 2021-04-14 15:36:35 +00:00
Hugo Sales a1d83bd2a8 [COMMAND][EVENTS] Added command to list events and handlers, and search by regex 2021-04-14 15:36:35 +00:00
Hugo Sales df6da4d941 [CORE][COMMAND] Register internal structures on command event 2021-04-14 15:36:35 +00:00
Hugo Sales d8d2ad3e10 [CORE][EXTENSIONS] Added extension (modules, plugins) loading and test plugin, which is able to handle events 2021-04-14 15:36:35 +00:00
Hugo Sales 2f5bdeed62 [CORE][SCHEMADEF] Clean up SchemaDef compiler pass 2021-04-14 15:36:35 +00:00
Hugo Sales 40b0812d9b [CS-FIXER] Updated cs-fixer rules and added a temporary exception to the src/Entity folder (awaiting pr merge) 2021-04-14 15:36:35 +00:00
Hugo Sales 26ea268fed [DOCTRINE][SCRIPT] Created a script to generate the class fields and accessors from the schema definition 2021-04-14 15:36:35 +00:00
Hugo Sales 04a5d2bfef [DOCTRINE][SCHEMADEF] Small refactoring 2021-04-14 15:36:35 +00:00
Hugo Sales 06d76a649f [CORE][DOCTRINE] Implement SchemaDefDriver, which transforms the old syntax from to doctrine's metadata 2021-04-14 15:36:35 +00:00
Hugo Sales b0960c5345 [DOCTRINE] Configure cli-config for doctrine console and ensure DATABASE is set to UTF-8 2021-04-14 15:36:35 +00:00
Hugo Sales 6546c088d9 [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-04-14 15:36:35 +00:00
Hugo Sales 69550a1036 [COMPOSER] Update composer dependencies 2021-04-14 15:36:35 +00:00
Hugo Sales 027726205d [CORE][I18N] Fix the translation system 2021-04-14 15:36:35 +00:00
Hugo Sales 4d2b8c26fa [CORE][I18N] Port the translation system to rely on symfony's 2021-04-14 15:36:35 +00:00
Hugo Sales cc47efe4b5 [CORE][EVENTS] Bring existing Events and Boostrapper back
- Adapt the existing event system to rely on Symfony's event dispatcher
2021-04-14 15:36:35 +00:00
Hugo Sales c0e53ae658 [CORE][ROUTES] Example route 2021-04-14 15:36:35 +00:00
Hugo Sales 5ec23f2200 [DOCKER] Updated dockerfile to add memcached 2021-04-14 15:36:35 +00:00
Hugo Sales 51d1ea4f8f [DOCKER] Added preliminary docker container 2021-04-14 15:36:35 +00:00
Hugo Sales fff0ecd1cd [COMPOSER] Update composer packages 2021-04-14 15:36:35 +00:00
Hugo Sales 86b5bfe075 [V3] Big Bang
Beginning anew, this time with a modern framework: symfony
2021-04-14 15:36:35 +00:00
Hugo Sales f01331671c [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-04-14 15:36:13 +00:00
948 changed files with 14277 additions and 47900 deletions

View File

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

3
.gitignore vendored
View File

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

View File

@ -1,46 +1,42 @@
<?php
declare(strict_types = 1);
/*
* This document has been generated with
* https://mlocati.github.io/php-cs-fixer-configurator/#version:3.2.1|configurator
* https://mlocati.github.io/php-cs-fixer-configurator/#version:2.16.1|configurator
* you can change this configuration by importing this file.
*/
$config = new PhpCsFixer\Config();
return $config
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
// 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'],
// Each element of an array must be indented exactly once.
'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']],
'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.
'array_indentation' => true,
// 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' => true,
'blank_line_after_opening_tag' => false,
// 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_anonymous_class_with_empty_body' => true, 'allow_single_line_closure' => true, 'position_after_functions_and_oop_constructs' => 'next'],
'braces' => ['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.
'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.
'class_definition' => ['single_item_single_line' => true, 'single_line' => true],
// Using `isset($var) &&` multiple times should be done in one call.
'combine_consecutive_issets' => true,
// Calling `unset` on multiple items should be done in one call.
'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.
'compact_nullable_typehint' => true,
// Concatenation should be spaced according configuration.
@ -49,20 +45,8 @@ return $config
'constant_case' => true,
// Equal sign in declare statement should be surrounded by spaces or not following configuration.
'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.
'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).
'encoding' => true,
// Replace deprecated `ereg` regular expression functions with `preg`.
@ -81,30 +65,14 @@ return $config
'function_declaration' => ['closure_function_spacing' => 'one'],
// Ensure single space between function's argument and its typehint.
'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.
'increment_style' => true,
// Code MUST use configured indentation type.
'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.
'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`.
'logical_operators' => true,
// Cast should be written in lower case.
'lowercase_cast' => true,
// PHP keywords MUST be in lower case.
'lowercase_keywords' => true,
// Class static references `self`, `static` and `parent` MUST be in lower case.
@ -113,14 +81,6 @@ return $config
'magic_constant_casing' => true,
// Magic method definitions and calls must be using the correct casing.
'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.
'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.
@ -128,17 +88,13 @@ return $config
// Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls.
'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`.
'native_constant_invocation' => true,
'native_constant_invocation' => false,
// Function defined by PHP should be called using the correct casing.
'native_function_casing' => true,
// Add leading `\` before function invocation to speed up resolving.
'native_function_invocation' => true,
'native_function_invocation' => false,
// Native type hints for functions should use the correct case.
'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.
'no_blank_lines_after_class_opening' => true,
// There should not be blank lines between docblock and the documented element.
@ -151,7 +107,7 @@ return $config
'no_empty_comment' => true,
// There should not be empty PHPDoc blocks.
'no_empty_phpdoc' => true,
// Remove useless (semicolon) statements.
// Remove useless semicolon statements.
'no_empty_statement' => true,
// Removes extra blank lines and/or blank lines following configuration.
'no_extra_blank_lines' => true,
@ -167,18 +123,18 @@ return $config
'no_null_property_initialization' => true,
// Short cast `bool` using double exclamation mark should not be used.
'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.
'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.
'no_spaces_after_function_name' => true,
// There MUST NOT be spaces around offset braces.
'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.
'no_spaces_inside_parenthesis' => true,
// Removes `@param`, `@return` and `@var` tags that don't provide any useful information.
'no_superfluous_phpdoc_tags' => true,
// Replaces superfluous `elseif` with `if`.
'no_superfluous_elseif' => false,
// Remove trailing commas in list function calls.
'no_trailing_comma_in_list_call' => true,
// PHP single-line arrays should not have trailing comma.
@ -194,9 +150,13 @@ return $config
// In function arguments there must not be arguments with default values before non-default ones.
'no_unreachable_default_argument_value' => true,
// Variables must be set `null` instead of using `(unset)` casting.
'no_unset_cast' => true,
'no_unset_cast' => false,
// Properties should be set to `null` instead of using `unset`.
'no_unset_on_property' => true,
// Unused `use` statements must be removed.
'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.
'no_useless_return' => true,
// In array declaration, there MUST NOT be a whitespace before each comma.
@ -207,32 +167,22 @@ return $config
'non_printable_character' => true,
// Array index should always be written by using square braces.
'normalize_index_brace' => true,
// 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 `?->`.
// There should not be space before or after object `T_OBJECT_OPERATOR` `->`.
'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.
'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)`.
'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.
'php_unit_fqcn_annotation' => true,
// Enforce camel (or snake) case for PHPUnit test methods, following configuration.
'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(...)`.
'php_unit_mock_short_will_return' => true,
// PHPUnit classes MUST be used in namespaced version, e.g. `\PHPUnit\Framework\TestCase` instead of `\PHPUnit_Framework_TestCase`.
'php_unit_namespaced' => true,
// Usages of `@expectedException*` annotations MUST be replaced by `->setExpectedException*` methods.
'php_unit_no_expectation_annotation' => true,
// Order `@covers` annotation of PHPUnit tests.
'php_unit_ordered_covers' => true,
// 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,
// PHPUnit methods like `assertSame` should be used instead of `assertEquals`.
@ -243,26 +193,16 @@ return $config
'phpdoc_add_missing_param_annotation' => true,
// All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically.
'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.
'phpdoc_indent' => true,
// Fixes PHPDoc inline tags.
'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,
// Fix PHPDoc inline tags, make `@inheritdoc` always inline.
'phpdoc_inline_tag' => true,
// `@access` annotations should be omitted from PHPDoc.
'phpdoc_no_access' => true,
// No alias PHPDoc tags should be used.
'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.
'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.
'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`.
@ -270,18 +210,7 @@ return $config
// 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,
// Single line `@var` PHPDoc should have proper spacing.
'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,
'phpdoc_single_line_var_spacing' => false,
// Removes extra blank lines after summary and after description in PHPDoc.
'phpdoc_trim_consecutive_blank_line_separation' => true,
// The correct case must be used for standard PHP types in PHPDoc.
@ -290,14 +219,8 @@ return $config
'phpdoc_types_order' => true,
// `@var` and `@type` annotations must have type and name in the correct order.
'phpdoc_var_annotation_correct_order' => true,
// Converts `pow` to the `**` operator.
'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,
// Class names should match the file name.
'psr4' => true,
// 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,
// Inside class or interface element `self` should be preferred to the class name itself.
@ -310,26 +233,18 @@ return $config
'short_scalar_cast' => true,
// Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`).
'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.
'single_blank_line_before_namespace' => true,
// There MUST NOT be more than one property or constant declared per statement.
'single_class_element_per_statement' => 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.
'single_class_element_per_statement' => true,
// 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 comments and multi-line comments with only one line of actual content should use the `//` syntax.
'single_line_comment_style' => true,
// Convert double quotes to single quotes for simple strings.
'single_quote' => true,
// Ensures a single space after language constructs.
'single_space_after_construct' => true,
// Each trait `use` must be done as single statement.
'single_trait_insert_per_statement' => true,
// Fix whitespace after a semicolon.
@ -338,41 +253,26 @@ return $config
'standardize_increment' => true,
// Replace all `<>` with `!=`.
'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.
'switch_case_semicolon_to_colon' => true,
// Removes extra spaces between colon and case value.
'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.
'ternary_operator_spaces' => true,
// Use the Elvis operator `?:` where possible.
'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,
// PHP multi-line arrays should have a trailing comma.
'trailing_comma_in_multiline_array' => true,
// Unary operators should be placed adjacent to their operands.
'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_required' => true,
// In array declaration, there MUST be a whitespace after each comma.
'whitespace_after_comma_in_array' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('var')
->exclude('docker')
->exclude('src/Entity')
->notPath('src/Core/DB/DefaultSettings.php')
->in(__DIR__),
->setFinder(PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('var')
->exclude('docker')
->exclude('src/Entity')
->notPath('src/Core/DB/DefaultSettings.php')
->in(__DIR__)
);

View File

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

View File

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

109
Makefile
View File

@ -1,12 +1,5 @@
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:
@if ! docker info > /dev/null; then echo "Docker does not seem to be running"; exit 1; fi
@ -17,106 +10,22 @@ down: .PHONY
docker-compose down
redis-shell:
docker exec -it $(call translate-container-name,$(strip $(DIR))_redis_1) sh -c 'redis-cli'
docker exec -it $(strip $(DIR))_redis_1 sh -c 'redis-cli'
php-repl: .PHONY
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c '/var/www/social/bin/console psysh'
docker exec -it $(strip $(DIR))_php_1 sh -c '/var/www/social/bin/console psysh'
php-shell: .PHONY
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c 'cd /var/www/social; sh'
docker exec -it $(strip $(DIR))_php_1 sh -c 'cd /var/www/social; sh'
psql-shell: .PHONY
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
docker exec -it $(strip $(DIR))_db_1 sh -c "psql -U postgres social"
database-force-schema-update:
docker exec -it $(call translate-container-name,$(strip $(DIR))_php_1) sh -c "/var/www/social/bin/console doctrine:schema:update --dump-sql --force"
docker exec -it $(strip $(DIR))_php_1 sh -c "/var/www/social/bin/console doctrine:schema:update --dump-sql --force"
tooling-docker: .PHONY
@cd docker/tooling && docker-compose up -d --build > /dev/null 2>&1
test: .PHONY
cd docker/testing && docker-compose run php; docker-compose down
stop-tooling: .PHONY
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
stop-test: .PHONY
cd docker/testing && docker-compose down

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)
validate_exit $?
case ${SERVICES} in
'docker') DOCKER='"nginx" "certbot" "php" "db" "redis" "worker"' ;; # TODO enable and configure "mail"
'docker') DOCKER='"nginx" "certbot" "php" "db" "redis"' ;; # TODO enable and configure "mail"
'mixed')
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 \
@ -57,7 +57,6 @@ case ${SERVICES} in
db 'Configure a DBMS*' on \
redis 'Configure Redis (optional, recommended)' on \
mail 'Confugure a mail server*' on \
worker 'Confugure container with worker queues' on \
3>&1 1>&2 2>&3)
validate_exit $?
;;
@ -142,7 +141,6 @@ fi
if echo "${DOCKER}" | grep -Fq '"php"'; then
${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 \
--defaultno \
3>&1 1>&2 2>&3
BUILD_PHP=$((1-$?)) # Invert output
fi
@ -228,7 +226,7 @@ PROFILE=$(${WHIPTAIL} --title 'GNU social site profile' --clear --backtitle 'GNU
public 'Make this node publicly accessible, with open 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' \
private 'Make this node publicly accessible, but with invite-only registration, only registered users can see feeds' \
private 'Make this node publicly accessible, but with invite-only registration, only registered users can see timelines' \
single_user 'Like public, but only allows registering one user' \
3>&1 1>&2 2>&3)
validate_exit $?
@ -352,8 +350,8 @@ SOCIAL_DBMS=${DBMS}
SOCIAL_DB=${DB_NAME}
SOCIAL_USER=${DB_USER}
SOCIAL_PASSWORD=${DB_PASSWORD}
CONFIG_DOMAIN=${DOMAIN}
CONFIG_NODE_NAME=${NODE_NAME}
SOCIAL_DOMAIN=${DOMAIN}
SOCIAL_NODE_NAME=${NODE_NAME}
SOCIAL_ADMIN_EMAIL=${EMAIL}
SOCIAL_SITE_PROFILE=${PROFILE}
MAILER_DSN=${MAILER_DSN}

View File

@ -8,9 +8,11 @@ require INSTALLDIR . '/vendor/autoload.php';
use Functional as F;
use App\Util\Functional;
$filenames = glob(INSTALLDIR . '/src/*/*.php');
$files = F\map($filenames, F\ary('file_get_contents', 1));
$files = F\map($filenames, Functional::arity('file_get_contents', 1));
$old_licenses = ['/* {{{ License
* 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) {
if (isset($opts['foreign key'])) {
[$foreign_entity, $foreign_key] = explode('.', $opts['target']);
$foreign_table = Formatting::camelCaseToSnakeCase(preg_replace('/Actor/', 'actor', $foreign_entity));
$foreign_table = Formatting::camelCaseToSnakeCase(preg_replace('/GSActor/', 'gsactor', $foreign_entity));
$edges[] = "{$table}:{$field} -- {$foreign_table}:{$foreign_key}";
}
}

View File

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

@ -1,11 +0,0 @@
#!/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,9 +0,0 @@
#!/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

1
bin/php-cs-fixer Symbolic link
View File

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

View File

@ -1,5 +0,0 @@
#!/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

1
bin/php-doc-check Symbolic link
View File

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

View File

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

View File

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

View File

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

View File

@ -1,38 +0,0 @@
#!/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,102 +0,0 @@
<?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\DB;
use App\Core\Event;
use App\Core\Modules\Component;
use App\Core\Router\RouteLoader;
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(RouteLoader $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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,166 +0,0 @@
<?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\Blog\Controller;
use App\Core\ActorLocalRoles;
use App\Core\Controller;
use App\Core\Event;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Core\VisibilityScope;
use App\Entity\Actor;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\RedirectException;
use App\Util\Form\FormFields;
use Component\Posting\Posting;
use InvalidArgumentException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints\Length;
class Post extends Controller
{
/**
* Creates and handles Blog post creation form
*
* @throws \App\Util\Exception\DuplicateFoundException
* @throws \App\Util\Exception\NoLoggedInUser
* @throws \App\Util\Exception\ServerException
* @throws ClientException
* @throws RedirectException
*/
public function makePost(Request $request)
{
$actor = Common::ensureLoggedIn()->getActor();
$placeholder_strings = ['How are you feeling?', 'Have something to share?', 'How was your day?'];
Event::handle('PostingPlaceHolderString', [&$placeholder_strings]);
$placeholder = $placeholder_strings[array_rand($placeholder_strings)];
$initial_content = '';
Event::handle('PostingInitialContent', [&$initial_content]);
$available_content_types = [
_m('Plain Text') => 'text/plain',
];
Event::handle('PostingAvailableContentTypes', [&$available_content_types]);
if (!\is_int($this->int('in'))) {
throw new InvalidArgumentException('You must specify an In group/org.');
}
$context_actor = Actor::getById($this->int('in'));
if (!$context_actor->isGroup()) {
throw new InvalidArgumentException('Only group blog posts are supported for now.');
}
$in_targets = ["!{$context_actor->getNickname()}" => $context_actor->getId()];
$form_params[] = ['in', ChoiceType::class, ['label' => _m('In:'), 'multiple' => false, 'expanded' => false, 'choices' => $in_targets]];
$visibility_options = [
_m('Public') => VisibilityScope::EVERYWHERE->value,
_m('Local') => VisibilityScope::LOCAL->value,
_m('Addressee') => VisibilityScope::ADDRESSEE->value,
];
if (!\is_null($context_actor) && $context_actor->isGroup()) { // @phpstan-ignore-line currently an open bug. See https://web.archive.org/web/20220226131651/https://github.com/phpstan/phpstan/issues/6234
if ($actor->canModerate($context_actor)) {
if ($context_actor->getRoles() & ActorLocalRoles::PRIVATE_GROUP) {
$visibility_options = array_merge([_m('Group') => VisibilityScope::GROUP->value], $visibility_options);
} else {
$visibility_options[_m('Group')] = VisibilityScope::GROUP->value;
}
}
}
$form_params[] = ['visibility', ChoiceType::class, ['label' => _m('Visibility:'), 'multiple' => false, 'expanded' => false, 'choices' => $visibility_options]];
$form_params[] = ['title', TextType::class, ['label' => _m('Title:'), 'constraints' => [new Length(['max' => 129])], 'required' => true]];
$form_params[] = ['content', TextareaType::class, ['label' => _m('Content:'), 'data' => $initial_content, 'attr' => ['placeholder' => _m($placeholder)], 'constraints' => [new Length(['max' => Common::config('site', 'text_limit')])]]];
$form_params[] = ['attachments', FileType::class, ['label' => _m('Attachments:'), 'multiple' => true, 'required' => false, 'invalid_message' => _m('Attachment not valid.')]];
$form_params[] = FormFields::language($actor, $context_actor, label: _m('Note language'), help: _m('The selected language will be federated and added as a lang attribute, preferred language can be set up in settings'));
if (\count($available_content_types) > 1) {
$form_params[] = ['content_type', ChoiceType::class,
[
'label' => _m('Text format:'), 'multiple' => false, 'expanded' => false,
'data' => $available_content_types[array_key_first($available_content_types)],
'choices' => $available_content_types,
],
];
}
Event::handle('PostingAddFormEntries', [$request, $actor, &$form_params]);
$form_params[] = ['post_note', SubmitType::class, ['label' => _m('Post')]];
$form = Form::create($form_params);
$form->handleRequest($request);
if ($form->isSubmitted()) {
try {
if ($form->isValid()) {
$data = $form->getData();
Event::handle('PostingModifyData', [$request, $actor, &$data, $form_params, $form]);
if (empty($data['content']) && empty($data['attachments'])) {
// TODO Display error: At least one of `content` and `attachments` must be provided
throw new ClientException(_m('You must enter content or provide at least one attachment to post a note.'));
}
if (\is_null(VisibilityScope::tryFrom($data['visibility']))) {
throw new ClientException(_m('You have selected an impossible visibility.'));
}
$content_type = $data['content_type'] ?? $available_content_types[array_key_first($available_content_types)];
$extra_args = [];
Event::handle('AddExtraArgsToNoteContent', [$request, $actor, $data, &$extra_args, $form_params, $form]);
[,$note,] = Posting::storeLocalPage(
actor: $actor,
content: $data['content'],
content_type: $content_type,
locale: $data['language'],
scope: VisibilityScope::from($data['visibility']),
targets: [(int) $data['in']],
reply_to: $data['reply_to_id'],
attachments: $data['attachments'],
process_note_content_extra_args: $extra_args,
title: $data['title'],
);
return new RedirectResponse($note->getConversationUrl());
}
} catch (FormSizeFileException $e) {
throw new ClientException(_m('Invalid file size given'), previous: $e);
}
}
return [
'_template' => 'blog/make_post.html.twig',
'blog_entry_form' => $form->createView(),
];
}
}

View File

@ -1,10 +0,0 @@
{% extends 'stdgrid.html.twig' %}
{% block title %}{% trans %}Create a blog post{% endtrans %}{% endblock %}
{% block body %}
{{ parent() }}
<section class="frame-section frame-section-padding">
<h1>{% trans %}Create a blog post{% endtrans %}</h1>
{{ form(blog_entry_form) }}
</section>
{% endblock body %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,234 +0,0 @@
<?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\DB;
use App\Core\Event;
use function App\Core\I18n\_m;
use App\Core\Modules\Component;
use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Entity\Actor;
use App\Entity\Feed;
use App\Entity\LocalUser;
use App\Util\Common;
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(RouteLoader $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' => 'Self tags',
'desc' => 'Add or remove tags on yourself',
'id' => 'settings-self-tags',
'controller' => CircleController\SelfTagsSettings::settingsSelfTags($request, Common::actor(), '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

@ -1,69 +0,0 @@
<?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

@ -1,107 +0,0 @@
<?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\DB;
use App\Core\Router\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

@ -1,118 +0,0 @@
<?php
declare(strict_types = 1);
namespace Component\Circle\Controller;
use App\Core\Cache;
use App\Core\Controller;
use App\Core\DB\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::createOrUpdate([
'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

@ -1,221 +0,0 @@
<?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\DB;
use App\Core\Entity;
use App\Core\Router\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,57 +0,0 @@
<?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 yourself (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

@ -1,163 +0,0 @@
<?php
declare(strict_types = 1);
namespace Component\Collection;
use App\Core\DB\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

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

View File

@ -1,20 +0,0 @@
<?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

@ -1,65 +0,0 @@
<?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

@ -1,216 +0,0 @@
<?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\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

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

View File

@ -1,195 +0,0 @@
<?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\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

@ -1,130 +0,0 @@
<?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

@ -1,70 +0,0 @@
{% 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

@ -1,11 +0,0 @@
{% 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

@ -1,34 +0,0 @@
{% 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

@ -1,58 +0,0 @@
{% 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

@ -1,27 +0,0 @@
<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

@ -1,152 +0,0 @@
<?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\DB;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Core\Log;
use App\Core\Router\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

@ -1,313 +0,0 @@
<?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\DB;
use App\Core\Event;
use function App\Core\I18n\_m;
use App\Core\Modules\Component;
use App\Core\Router\RouteLoader;
use App\Core\Router\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(RouteLoader $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,132 +0,0 @@
<?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\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

@ -1,21 +0,0 @@
{% 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

@ -1,75 +0,0 @@
<?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'],
];
}
}

View File

@ -1,54 +0,0 @@
<?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\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

@ -1,108 +0,0 @@
<?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\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

@ -1,27 +0,0 @@
<?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

@ -1,63 +0,0 @@
<?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

@ -1,81 +0,0 @@
<?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

@ -1,46 +0,0 @@
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

@ -1,175 +0,0 @@
<?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\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

@ -1,60 +0,0 @@
<?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

@ -1,547 +0,0 @@
<?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\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\RouteLoader;
use App\Core\Router\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(RouteLoader $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

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

View File

@ -1,226 +0,0 @@
<?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

@ -1,128 +0,0 @@
<?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

@ -1,60 +0,0 @@
<?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

@ -1,86 +0,0 @@
<?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

@ -1,63 +0,0 @@
<?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

@ -1,105 +0,0 @@
<?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

@ -1,64 +0,0 @@
<?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

@ -1,68 +0,0 @@
<?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

@ -1,119 +0,0 @@
<?php
declare(strict_types = 1);
namespace Component\FreeNetwork\Util\WebfingerResource;
use App\Core\Event;
use App\Core\Log;
use App\Core\Router\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

@ -1,73 +0,0 @@
<?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

@ -1,40 +0,0 @@
<?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

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

View File

@ -1,23 +0,0 @@
# 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

@ -1,28 +0,0 @@
# 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

@ -1,23 +0,0 @@
# 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

@ -1,28 +0,0 @@
# 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

@ -1,23 +0,0 @@
# 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

@ -1,28 +0,0 @@
# 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

@ -1,23 +0,0 @@
# 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

@ -1,28 +0,0 @@
# 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

@ -1,23 +0,0 @@
# 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 ""

View File

@ -1,28 +0,0 @@
# 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: 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: 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

@ -1,23 +0,0 @@
# 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: Belarusian (Tarask) (http://www.transifex.com/gnu-social/gnu-social/language/be@tarask/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: be@tarask\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#. TRANS: Plugin description.
#: LRDDPlugin.php:61
msgid "Implements LRDD support for GNU Social."
msgstr ""

View File

@ -1,28 +0,0 @@
# 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: Belarusian (Tarask) (http://www.transifex.com/gnu-social/gnu-social/language/be@tarask/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: be@tarask\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\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

@ -1,23 +0,0 @@
# 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: Bulgarian (http://www.transifex.com/gnu-social/gnu-social/language/bg/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bg\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

@ -1,28 +0,0 @@
# 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: Bulgarian (http://www.transifex.com/gnu-social/gnu-social/language/bg/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bg\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

@ -1,23 +0,0 @@
# 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: Bengali (India) (http://www.transifex.com/gnu-social/gnu-social/language/bn_IN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bn_IN\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

@ -1,28 +0,0 @@
# 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: Bengali (India) (http://www.transifex.com/gnu-social/gnu-social/language/bn_IN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bn_IN\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

@ -1,23 +0,0 @@
# 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: Breton (http://www.transifex.com/gnu-social/gnu-social/language/br/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: br\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

@ -1,28 +0,0 @@
# 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: Breton (http://www.transifex.com/gnu-social/gnu-social/language/br/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: br\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

@ -1,23 +0,0 @@
# 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: Catalan (http://www.transifex.com/gnu-social/gnu-social/language/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ca\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

@ -1,28 +0,0 @@
# 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: Catalan (http://www.transifex.com/gnu-social/gnu-social/language/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ca\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 ""

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