code:
Configuration options
================================================================================

The configuration for GNU social is stored in the database table
`config`.

A Web based configuration panel exists so the site admin can configure
GNU social. The preferred method for changing config options is to use this
panel.

A command-line script, `set_config.php`, can be used to set individual
configuration options. It's in the `bin/` directory.

Almost all configuration options are made through a two-dimensional
associative array, cleverly named `$config`. A typical configuration
line will be:

    $config['section']['setting] = value;

The following documentation describes each section and setting.

site
-------------------------------------------------------------------------------

This section is a catch-all for site-wide variables.

* `name` (string, required, defaults to the value provided in the configre script,
    sitename): the name of your site, like 'YourCompany Microblog'.

* `server` (string, required, defaults to the value provided in the configre script,
    sitename): the server domain, like 'example.net'.

* `notice` (string, default null): A plain string that will appear on every page. A good
    place to put introductory information about your service, or info about upgrades and
    outages, or other community info. Any HTML will be escaped.

* `theme` (string, default 'default'): Theme for your site (see Theme section).

* `logo` (string, default null): URL of an image file to use as the logo for the site.
    Overrides the logo in the theme, if any.

* `language` (string, default "en"): default language for your site. Defaults to English.
    Note that this is overridden, if enabled in the following setting, if a user is logged
    in and has selected a different language or if the user is NOT logged in, but their
    browser requests a different langauge. Since pretty much everybody's browser requests
    a language, that means that changing this setting has little or no effect in practice.

* `detect_language` (boolean, default true): whether to use the most appropriate language
    depending on the requester's browser preferences.

* `languages` (array, default null): A list of languages supported on your site. Typically
    you'd only change this if you wanted to disable support for one or more languages:

    `unset($config['site']['languages']['de'])` will disable support for German.

* `email` (string, required): contact email address for your site. By default, it's
    extracted from your Web server environment or the value provided in the configure
    script; you may want to customize it.

* `recovery_disclose` (boolean, default false): whether to confirm if the email exists
    when attempting to login. Recommended to keep it false, for some privacy.

* `timezone` (string, default 'UTC'): default timezone for message display. Users
    can set their own time zone. Defaults to 'UTC', which is a pretty good
    default.

* `brought_by` (string, default null): text used for the "brought by" link.

* `brought_by_url` (string, default null): name of an organization or individual who
    provides the service. Each page will include a link to this name in the footer or
    sidebar. A good way to link to the blog, forum, wiki, corporate portal, or whoever is
    making the service available.

* `closed` (boolean, default false): If set to 'true', will disallow registration on your
    site. This is a easy way to restrict accounts to only one individual or group; just
    register the accounts you want on the service, *then* set this variable to 'true'.

* `invite_only` (boolean, default false): If set to 'true', will only allow registration
    if the user was invited by an existing user.

* `private` (boolean, default false): If set to 'true', anonymous users will be redirected
    to the 'login' page. Also, API methods that normally require no authentication will
    require it. Note that this does not turn off registration; use 'closed' or
    'invite_only' for that behaviour.

* `ssl` (enum['always', 'sometimes', 'never'], default always'): Whether to use SSL and
    https:// URLs for some or all pages.

    Possible values are 'always' (use it for all pages), 'never' (don't use it for any
    pages), or 'sometimes' (use it for sensitive pages that include passwords like login
    and registration, but not for regular pages).

* `ssl_proxy` (string|boolean, default false): Whether to force GNUsocial to think it is
    HTTPS when the server gives no such information. I.e. when you're using a reverse
    proxy that adds the encryption layer but the webserver that runs PHP isn't configured
    with a key and certificate. If a string is given, it will be used as the URL of the
    proxy server.

* `duplicate_time_limit` (integer, default 60): minimum time allowed for one person to say
    the same thing twice. Default 60s. If it happens faster than this, it's considered a
    user or UI error.

* `text_limit` (integer, default 1000): default max size for texts in the site. Can be
    fine-tuned for notices, messages, profile bios and group descriptions. Zero indicates
    no limit.

* `x-static-delivery` (string, default null): when a string, use this as the header with
    which to serve static files. Possible values are 'X-Sendfile' (for Apache and others)
    and 'X-Accel-Redirect' (for nginx).

security
-------------------------------------------------------------------------------

* `hash_algos` (array, default ['sha1', 'sha256', 'sha512']): set to null for anything
    that `hash_hmac()` can handle; can be any combination of the result of `hash_algos()`

db
-------------------------------------------------------------------------------

* `mirror` (array, default null): you can set this to an array of database connection
    URIs. If it's set, load will be split among these, and replication will be enabled.

fix
-------------------------------------------------------------------------------

* `fancy_urls` (boolean, default true): fix any non-facy url to the correct form, when
    possible.

* `http` (boolean, default true): fixe any http links to https.

queue
-------------------------------------------------------------------------------

You can configure the software to queue time-consuming tasks, like
sending out SMS, email or XMPP messages, for off-line processing.

* `enabled` (boolean, default true): Whether to uses queues.

* `daemon` (boolean, default false): Whether to use queuedaemon. False means
    you'll use OpportunisticQM plugin.

* `threads` (int): How many queue processes to run. Defaults to number of cpu cores in
    unix-like systems or 1 on other OSes.

* `subsystem` (enum["db", "stomp", "redis"], default 'db'): Which kind of
    queueserver to use. Values include "db" for our database queuing (no other server
    required), "stomp" for a stomp server amd "redis" for a Redis server.

* `basename` (string, default '/queue/gnusocial/'): a root name to use for queues (stomp
    and redis only). Typically something like '/queue/sitename/' makes sense. If running
    multiple instances on the same server, make sure that either this setting or
    `$config['site']['nickname']` are unique for each site to keep them separate.

* `control_channel` (string, default '/topic/gnusocial/control'): the control channel used
    for different queue processes to communicate.

* `monitor` (string, default null): URL endpoint to monitor queue status

* `soft_limit` (string, default '90%'): an absolute or relative "soft memory limit";
    daemons will restart themselves gracefully when they find they've hit this amount of
    memory usage. Relative means a percentage of PHP's global `memory_limit` setting.

* `spawn_delay` (integer, default 1): seconds to wait between deamon restarts.

* `debug_memory` (boolean, default false): log daemon's memory usage.

* `stomp_server` (string, default null): URI for stomp server. Something like
    "tcp://hostname:61613". More complicated ones are possible; see your stomp server's
    documentation for details.

* `stomp_username` (string, default null): username for connecting to the stomp server.

* `stomp_password` (string, default null): password for connecting to the stomp server.

* `stomp_persistent` (boolean, default true): Keep items across queue server restart, if
    enabled. Note: Under ActiveMQ, the server configuration determines if and how
    persistent storage is actually saved. Not all stomp servers support persistence.

* `stomp_transactions` (boolean, default true): use transactions to aid in error
    detection. A broken transaction will be seen quickly, allowing a message to be
    redelivered immediately if a daemon crashes. Not all stop servers support
    transactions.

* `stomp_acks` (boolean, default true): send acknowledgements to aid in flow control. An
    acknowledgement of successful processing tells the server we're ready for more and can
    help keep things moving smoothly. This should *not* be turned off when running with
    ActiveMQ, (it breaks if you do), but if using another message queue server that does
    not support acknowledgements you might need to disable this.

* `stomp_manual_failover` (boolean, default true): if multiple servers are listed, treat
    them as separate (enqueue on one randomly, listen on all).

* `max_retries` (integer, default 10): for stomp, drop messages after N failed
    attempts to process.

* `dead_letter_dir` (string, default null): for stomp, optional directory to dump
    data on failed queue processing events after discarding them.

avatar
-------------------------------------------------------------------------------

* `server` (string, default null): If set, defines another server where avatars are
    stored. Note that the `dir` still has to be writeable. You'd typically use this to
    split HTTP requests on the client to speed up page loading, either with another
    virtual server or with an NFS or SAMBA share. Clients typically only make 2
    connections to a single server at a time
    <https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4>, so this can
    parallelize the job.

* `url_base` (string, 'default '/avatar/'): URL where avatars can be found.

* `ssl` (boolean, default null): Whether to access avatars using HTTPS. Defaults
    to null, meaning to guess based on site-wide SSL settings.

* `dir` (string, default 'file/avatar/'): Directory to save avatar files to.

* `max_size_px` (integer, default 300): Maximum width or height for user avatars, in pixels

javascript
-------------------------------------------------------------------------------

* `server` (string, default null): You can speed up page loading by pointing the
    javascript file lookup to another server (virtual or real). Defaults to NULL, meaning
    to use the site server.

* `url_base` (string default '/js/'): URL part for JavaScript files.

* `ssl` (boolean, default null): Whether to use SSL for JavaScript files. Default is null,
    which means guess based on site SSL settings.

* `bust_frames` (boolean, default true): If true, all web pages will break out of
    framesets. If false, can comfortably live in a frame or iframe... probably.

attachments
-------------------------------------------------------------------------------

* `server` (string, default null): Server name to use when creating URLs for uploaded
    files. Defaults to null, meaning to use the default Web server. Using a virtual server
    here can speed up Web performance.

* `url_base` (string, default '/file/'): URL path, relative to the server, to find
    files. Defaults to main path + '/file/'.

* `ssl` (boolean, default null): Whether to use HTTPS for file URLs. Defaults to null,
    meaning to use other SSL settings.

* `dir` (string, default '/file/uploads/'): Directory accessible to the Web process where
    uploads should go.

* `supported` (array): An associative array of mime types you accept to store and
    distribute, like 'image/gif', 'video/mpeg', 'audio/mpeg', to the corresponding file
    extension. Make sure you setup your server to properly recognize the types you want to
    support. It's important to use the result of calling `image_type_to_extension` for the
    appropriate image type, in the case of images. This is so all parts of the code see
    the same file extension for each image type (jpg vs jpeg). For example, to enable BMP
    uploads, add this to the config.php file:
    `image_type_to_mime_type(IMAGETYPE_BMP) => image_type_to_extension(IMAGETYPE_BMP);` See
    https://www.php.net/manual/en/function.image-type-to-mime-type.php for a list of such
    constants. If a filetype is not listed there, it's possible to add the mimetype and
    the extension by hand, but they need to match those returned by the file command.

For quotas, be sure you've set the `upload_max_filesize` and `post_max_size` in php.ini to
be large enough to handle your upload. In httpd.conf (if you're using apache), check that
the LimitRequestBody directive isn't set too low (it's optional, so it may not be there at
all).

* `file_quota` (integer, defaults to minimum of `'post_max_size', 'upload_max_filesize',
    'memory_limit'`): Maximum size for a single file upload, in bytes. A user can send any
    amount of notices with attachments as long as each attachment is smaller than
    file_quota.

* `user_quota` (integer, default 200M): Total size, in bytes, a user can store on this
    server. Each user can store any number of files as long as their total size does not
    exceed the user_quota.

* `monthly_quota` (integer, default 20M): Total size in bytes that a user can upload each
    month.

* `uploads` (boolean, default true): Whether to allow uploading files with notices.

* `show_html` (boolean, default true): Whether to show (filtered) text/html attachments
    (and oEmbed HTML etc.). Doesn't affect AJAX calls.

* `show_thumbs` (boolean, default true): Whether to show thumbnails in notice lists for
    uploaded images, and photos and videos linked remotely that provide oEmbed info.

* `process_links` (boolean, default true): Whether to follow redirects and save all
    available file information (mimetype, date, size, oembed, etc.).

* `ext_blacklist` (array, default []): associative array to either deny certain extensions or
    change them to a different one. For example:

        $config['attachments']['extblacklist']['php'] = 'phps';  // this turns .php into .phps
        $config['attachments']['extblacklist']['exe'] = false;   // this would deny any uploads
                                                                 // of files with the "exe" extension

* `filename` (string, default hash): Name for new files, one of: 'upload', 'hash'.

* `memory_limit` (string, default '1024M'): PHP memory limit to use temporarily when
    handling images

thumbnail
-------------------------------------------------------------------------------

* `server` (string, default null): Server name from which to serve thumbnails. Defaults to
    null, meaning to use the default Web server. Using a virtual server here can speed up
    Web performance.

* `url_base` (string, default '/thumb/'): URL path, relative to the server, to find
    files.

* `ssl` (boolean, default null): Whether to use HTTPS for thumbnail URLs. Defaults to null,
    meaning to use other SSL settings.

* `dir` (string, default '/file/thumbnails/'): Path where to store thumbnails.

* `crop` (boolean, default false): Whether to crop thumbnails (or scale them down)

* `max_size_px` (integer, default 1000): Thumbnails with an edge greater than this will
    not be generated.

* `width` (integer, default 450): Width for generated thumbnails.

* `height` (integer, default 600): Heigth for generated thumbnails.

* `upscale` (boolean, default false): Whether to generate thumbnails bigger than the original.

* `animated` (boolean, default false): Whether to allow animated thumbnails.

theme
-------------------------------------------------------------------------------

* `server` (string, default null): Like avatars, you can speed up page loading
    by pointing the theme file lookup to another server (virtual or real).
    The default of null will use the same server as PA.

* `url_base` (string, default '/theme'): Path part of theme URLs, before the theme name.
    Relative to the theme server. It may make sense to change this path when upgrading,
    (using version numbers as the path) to make sure that all files are reloaded by
    caching clients or proxies.

* `ssl` (boolean, default null): Whether to use SSL for theme elements. Default
    is null, which means guess based on site SSL settings.

* `dir` (string, default "./themes"): Directory where theme files are stored.
    Used to determine whether to show parts of a theme file. Defaults to the
    theme subdirectory of the install directory.

plugins
-------------------------------------------------------------------------------

* `server` (string, default null): Server to find static files for a plugin when the page
	is plain old HTTP. Defaults to site/server (same as pages). You can use this to move
	plugin CSS and JS files to a CDN.

* `url_base` (string, default '/plugins/'): Path to the plugin files. Expects that each
    plugin will have a subdirectory at plugins/NameOfPlugin. Change this if you're using
     a CDN.

* `ssl` (boolean, default null) Whether to use ssl for files served by plugins.

* `core` (associative array, default TODO): Core GNU social modules, cannot be disabled.

* `default`: (associative array, default TODO): Mapping from plugin name to array of
    plugin arguments.

* `locale_path` (string, default null): Path for finding plugin locale files. In the
    plugin's directory by default.

license
-------------------------------------------------------------------------------

The default license to use for your users' notices. The default is the Creative Commons
Attribution 4.0 license, which is probably the right choice for any public site. Note that
some other servers will not accept notices if you apply a stricter license than this.

* `type` (enum["cc", "allrightsreserved", "private"], default 'cc'): One of 'cc' (for
    Creative Commons licenses), 'allrightsreserved' (default copyright), or 'private' (for
    private and confidential information).

* `owner` (string|boolean, default null): For 'allrightsreserved' or 'private', an
    assigned copyright holder (for example, an employer for a private site). Use true to
    attribute it to the poster.

* `url` (string, default 'https://creativecommons.org/licenses/by/4.0/'): URL of the
    license, used for links.

* `title` (string, default 'Creative Commons Attribution 4.0'): Title for the license.

* `image` (string, default '/theme/licenses/cc_by_4.0.png'): URL path for the license image.

mail
-------------------------------------------------------------------------------

This is for configuring out-going email.

* `backend` (enum["mail", "sendmail", "smtp"], default 'mail'): The backend to use for
   mail. We recommend SMTP where your setup supports it as it is of the three the more
   difficult one for script exploits to abuse (relatively speaking - they all have
   potential problems.).

* `params` (array, default null): If the mail backend requires any parameters, you can
    provide them in this array.

* `domain_check` (boolean, default true): Check email origin is valid.

nickname
-------------------------------------------------------------------------------

* `blacklist` (array, default ['doc', 'main', 'avatar', 'theme']): an array of strings for
    usernames that may not be registered. You may want to add others if you have other
    software installed in a subdirectory of GNU social or if you just don't want certain
    words used as usernames.

* `featured` (array, default null): an array of nicknames of 'featured' users of the site.
    Can be useful to draw attention to well-known users, or interesting people, or
    whatever.

profile
-------------------------------------------------------------------------------

* `banned` (array, defualt []): array of users to hell-ban

* `bio_text_limit` (integer, default null): Max character length of bio; 0 means no
    limit; null means to use the site text limit default.

* `allow_nick_change` (boolean, default false): Whether to allow users to change their
    nickname.

* `allow_private_stream` (boolean, default true): Whether users can set their streams to
    private, so only followers can see it.

* `backup` (boolean, default false): Whether users can backup their own profiles. Can
    cause DoS.

* `restore` (boolean, default false): Whether users can restore their profiles from backup
    files. Can cause DoS.

* `delete` (boolean, default false): Whether users can delete their own accounts.

* `move` (boolean, default false): Whether users can move their accounts to another
    server.

image
-------------------------------------------------------------------------------

* `jpegquality` {integer, default 85}: default quality to use when reencoding images as
    jpeg.

theme_upload
-------------------------------------------------------------------------------

* `enabled` (boolean, default true): Whether to allow users to upload themes

* `formats` (array, default ['zip', 'tar', 'gz', 'tar.gz']): Formats to allow

foaf
-------------------------------------------------------------------------------

* `mbox_sha1sum` (boolean, default false): whether to include this box in the FOAF
    protocol page

public
-------------------------------------------------------------------------------

For configuring the public stream.

* `local_only` (boolean, default false): If set to true, only messages posted by users of
    this instance (rather than remote instances) are shown in the public stream.

* `blacklist` (array, default []): An array of IDs of users to hide from the public
    stream. Useful if you have someone making an excessive amount of posts to the site or
    some kind of automated poster, testing bots, etc.

* `exclude_sources` (array, default []): Sources of notices that should be kept off of
    the public timeline (because they're from automatic posters, for instance).

throttle
-------------------------------------------------------------------------------

For notice-posting throttles.

* `enabled` (boolean, default true): Whether to throttle posting.

* `count` (integer, default 20): Each user can make this many posts in 'timespan' seconds.
    So, if count is 100 and timespan is 3600, then there can be only 100 posts from a user
    every hour.

* `timespan` (integer, default 600): See 'count'.

invite
-------------------------------------------------------------------------------

* `enabled` (boolean, default true): Whether to allow users to send invites.

tag
-------------------------------------------------------------------------------

* `dropoff` (integer, default 86400 * 10): Exponential decay factor for tag listing, in
    seconds. You can twiddle with this to try to get better results for your site.

* `cutoff` (integer, default 86400 * 90): Cutoff, in seconds, before which to not look for
    notices.

popular
-------------------------------------------------------------------------------

* `dropoff` (integer, default 86400 * 10): Exponential decay factor for popular notices, in
    seconds. You can twiddle with this to try to get better results for your site.

* `cutoff` (integer, default 86400 * 90): Cutoff, in seconds, before which to not look for
    notices.

daemon
-------------------------------------------------------------------------------

* `piddir` (string, default `sys_get_temp_dir()`): Directory that daemon processes should
    write their PID file (process ID) to.

* `user` (string|integer, default false): If set, the daemons will try to change their
    effective user ID to this user before running. Probably a good idea, especially if you
    start the daemons as root.

* `group` (string|integer, default false): If set, the daemons will try to change their
    effective group ID to this named group.

ping
-------------------------------------------------------------------------------

Using the "XML-RPC Ping" method initiated by weblogs.com, the site can
notify third-party servers of updates.

* `notify` (array, default []): An array of URLs for ping endpoints.

* `timeout` (integer, default 2): Interval in seconds between notifications.

new_users
-------------------------------------------------------------------------------

* `default_subscriptions` (array, default null): Nickname of user accounts to
    automatically subscribe new users to. Typically this would be a system account for e.g.
    service updates or announcements. Users are able to unsub if they want.

* `welcome_user` (string, default null): Nickname of a user account that sends welcome
    messages to new users.

N.B. If either of these special user accounts are specified, the users should be created
     before the configuration is updated.

linkify
-------------------------------------------------------------------------------

* `bare_domain` (boolean, default false): Prepend schema to any linked domains (a href,
    not display text).

* `linkify_ipv4` (boolean, default false): Convert IPv4 addresses into hyperlinks.

* `linkify_ipv6` (boolean, default false): Convert IPv6 addresses into hyperlinks.

group
-------------------------------------------------------------------------------

* `max_aliases` (integer, default 3): Maximum number of aliases a group can have.
    Set to 0 or less to prevent aliases in a group.

* `description_limit` (integer, default null): Maximum number of characters to allow in
    group descriptions. null means to use the site-wide text limits. 0 means no limit.

people_tag
-------------------------------------------------------------------------------

* `max_tags` (integer, default 100): Maximum number of people tags a user can create.

* `max_people` (integer, default 500): Maximum number of people with the same user people tag.

* `allow_tagging` (associative array, default ['local' => true, 'remote' => true])>: Which
    kind of user to allow tagging.

* `description_limit` (integer, default null): Maximum tag description lenght.

search
-------------------------------------------------------------------------------

* `type` (enum('fulltext', 'like'), default 'like'): type of search. Ignored if PostgreSQL
    is enabled. Can either be 'fulltext' or 'like'. The former is faster and more
    efficient but requires the lame old MyISAM engine for MySQL. The latter will work with
    InnoDB but could be miserably slow on large systems.

html_filter
-------------------------------------------------------------------------------

* `tags` (array, default ['img', 'video', 'audio', 'script']): Remove tags from
    user/remotely generated HTML.

notice
-------------------------------------------------------------------------------

* `content_limit` (integer, default null): Max length of the plain-text content of a
    notice. Null means to use the site-wide text limit. 0 means no limit.

* `allow_private` (boolean, default false): Whether to allow users to post notices visible
    only to their subscribers.

* `hide_banned` (boolean, default true): Whether to hide hell-banned users' notices.

message
-------------------------------------------------------------------------------

* `content_limit` (integer, default null): Max length of the plain-text content of a
    message. Null means to use the site-wide text limit. 0 means no limit.

location
-------------------------------------------------------------------------------

* `share` (enum('always', 'user', 'never'), default 'user'): Whether to share user
    location. 'user' means each user can choose.

admin
-------------------------------------------------------------------------------

* `panels` (array, default ['site', 'user', 'paths', 'access', 'sessions', 'sitenotice',
  'license', 'plugins']): Which panels to include in the admin tab.

single_user
-------------------------------------------------------------------------------

If an installation has only one user, this can simplify a lot of the
interface. It also makes the user's profile the root URL.

* `enabled` (boolean, default value provided in configure): Whether to run in "single user mode".

* `nickname` (string, default null): nickname of the single user. If no nickname is
  specified, the site owner account will be used (if present).

robots_txt
-------------------------------------------------------------------------------

* `crawl_delay` (integer, default 0): if non-zero, this value is provided as the
    'Crawl-Delay:' for the robots.txt file. see
    <https://en.wikipedia.org/wiki/Robots_exclusion_standard#Crawl-delay_directive> for
    more information. Default is zero, no explicit delay.

* `disallow`(array, default ['main', 'settings', 'admin', 'search', 'message']): Array of
    paths to disallow. Ignored when site is private, in which case the entire site ('/')
    is disallowed.

nofollow
-------------------------------------------------------------------------------

We optionally put 'rel="nofollow"' on some links in some pages. The following
configuration settings let you fine-tune how or when things are nofollowed. See
http://en.wikipedia.org/wiki/Nofollow for more information on what 'nofollow' means.

* `subscribers` (boolean, default true): Whether to nofollow links to subscribers on the
    profile and personal pages.

* `members` (boolean, default true): Whether to nofollow links to members on the group
    page. Default true.

* `peopletag` (boolean, default true): Whether to nofollow links to people listed in the
    peopletag page. Default true.

* `external` (enum('always', 'sometimes', 'never'), default 'sometimes'): External links
    in notices. One of three values: 'always', 'sometimes', 'never'. If 'sometimes', then
    external links are not nofollowed on profile, notice, and favorites page. Default is
    'sometimes'.

url_shortener
-------------------------------------------------------------------------------

* `service` (string, default 'internal'): URL shortening service to use by default. Users
    can override individually.

* `max_url_length` (integer, default 100): If an URL is strictly longer than this limit,
    it will be shortened. Note that the URL shortener service may return an URL longer
    than this limit. Users can override. If set to 0, all URLs will be shortened.

* `max_notice_length` (integer, default null): If a notice is strictly longer than this
    limit, all URLs in the notice will be shortened. Users can override this.

http
-------------------------------------------------------------------------------

* `ssl_cafile` (string, default '/docker/certbot/files/live/'): location of the CA file
    for SSL connections. If not set, peers won't be able to verify our identity.

* `timeout` (integer, default `ini_get('default_socket_timeout')`): Timeout in seconds
    when to close a connection.

* `proxy_host` (string, default null): Host to use for proxying HTTP requests. If null,
	    doesn't use an HTTP proxy.

* `proxy_port` (integer, default null): Port to use to connect to HTTP proxy host.

* `proxy_user` (string, default null): Username to use for authenticating to the HTTP proxy.

* `proxy_password` (string, default null): Password to use for authenticating to the HTTP proxy.

* `proxy_auth_scheme` (TODO): Scheme to use for authenticating to the HTTP proxy.

discovery
-------------------------------------------------------------------------------

* `CORS` (boolean, default false): Whether to allow Cross-Origin Resource Sharing for
    service discovery (host-meta, XRD, etc.)

performance
-------------------------------------------------------------------------------

* `high` (boolean, default fakse): Disables some high-performance-intensity components.

login_command
-------------------------------------------------------------------------------

* `enabled` (boolean, default false): Whether to enable users to send the text 'login' to
    the site through any channel and receive a link to login to the site automatically in
    return. Possibly useful for users who primarily use an XMPP or SMS interface. Note
    that the security implications of this are pretty serious. You should enable it only
    after you've convinced yourself that it is safe.


code:
GNU social
=====

GNU social is a federated social network.

TABLE OF CONTENTS
=================
* Installation with docker
  + Prerequisites
  + With TLS/SSL
  + Without TLS/SSL
  + Configuration
  + Installing/running

* Installation without docker
  + Prerequisites
    - PHP modules
    - Better performance
  + Installation
    - Getting it up and running
    - Fancy URLs
    - Themes
    - Private
  + Extra features
    - Sphinx
    - SMS
    - Translation
    - Queues and daemons
  + After installation
    - Backups
    - Upgrading
  + Additional configuration

Installation with docker
================

Installation can be done in multiple ways, but the simplest is using
`docker` and `docker-compose`. The compose file currently includes all
the necessary services for running the app. Running the database and
webserver outside of `docker` containers is currently not supported,
unless the app is installed without `docker`.

Prerequisites
-----

In order to host your GNU social instance, you'll need a domain, a
server with a constant IP and both `docker` and `docker-compose`
installed on your system.

Alternatively, for local hosting or development, behind a NAT, use a
dynamic DNS solutions. I recommend you go to
https://gnudip.datasystems24.net or another GnuDIP host and register.
Then clone https://notabug.org/someonewithpc/gnudip.git, inspect and
run the `./install.sh` script. This allows you to have a domain that
dynamically points to your IP address.

With TLS/SSL
----

Next, if you want to setup SSL (which you should in most cases,
exceptions being wanting to use the Tor network), you'll need a
certificate. There are multiple approaches to achieve this, among
which are using a proxy server capable of either proxying an HTTP
connection to HTTPS or an HTTPS connection to HTTPS, or creating a
certificate signed by Let's Encrypt. For the former, follow the
instructions of your proxy provider.

If you're not using a proxy, you can use the
`bin/bootstrap_certificates` script to generate and install
certificates signed by Let's Encrypt. To do this, you should add the
server's IP, if it's static, as an `A` DNS record with your DNS
provider (normally, your domain registrar); the `A` record doesn't
need to be at the root of your domain, meaning it's name can be a
subdomain. For dynamic IPs, create a `CNAME` record pointing to the
hostname you created with the GnuDIP host, above. A `CNAME` cannot be
created for a domain root, so you must use a subdomain. Then, run the
aforementioned script and fill in the details.

Without TLS/SSL
----

Edit the `docker-compose.yaml` file and comment the `certbot` service
to disable it. In the future, this will be handled by the
`bin/configure` script.

Configuration
----

Run the `bin/configure` script and enter the information as asked.
This will generate all the required `.env` files used by
`docker-compose` to configure the application.

Installation/Running
-----

Simply run `docker-compose up` from the root of the project (the
folder where the `.git` folder is). In this form, the application can
be stopped by pressing `C-c` (`CTRL` + `C`); pressing it again will
force the containers to stop immediately. However, this form will show
you all logs, but in most cases, you won't want to see those all the
time. For that, run `docker-compose up -d` from the same directory;
The application can then be stopped with `docker-compose down`.

------------------------------------------------------------------------

Installation without docker
================

Prerequisites
-----------

### PHP modules

The following software packages are *required* for this software to
run correctly.

- PHP 7.3+
- MariaDB 10.3+
- Web server    Apache, lighttpd and nginx will all work. CGI mode is
                recommended and also some variant of 'suexec' (or a
                proper setup php-fpm pool)
                NOTE: mod_rewrite or its equivalent is extremely useful.

Your PHP installation must include the following PHP extensions for a
functional setup of GNU social:

- openssl       (compiled in for Debian, enabled manually in Arch Linux)
- php-curl      Fetching files by HTTP.
- php-exif      Exchangeable image information.
- php-gd        Image manipulation (scaling).
- php-intl      Internationalization support (transliteration et al).
- php-json      For WebFinger lookups and more.
- php-mbstring  String manipulation
- php-mysql     The native driver for MariaDB connections.
- php-gmp       For Salmon signatures (part of OStatus)
- php-bcmath    Arbitrary Precision Mathematics
- php-opcache   Improved PHP performance by precompilation
- php-readline  For interactive scripts
- php-xml       XML parser

NOTE: Some distros require manual enabling in the relevant php.ini for some modules.

### Better performance

For some functionality, you will also need the following extensions:

- opcache       Improves performance a _lot_. Included in PHP, must be
                enabled manually in php.ini for most distributions. Find
                and set at least:  opcache.enable=1
- mailparse     Efficient parsing of email requires this extension.
                Submission by email or SMS-over-email uses this.
- sphinx        A client for the sphinx server, an alternative to MySQL
                or Postgresql fulltext search. You will also need a
                Sphinx server to serve the search queries.
- gettext       For multiple languages. Default on many PHP installs;
                will be emulated if not present.
- exif          For thumbnails to be properly oriented.

You may also experience better performance from your site if you configure
a PHP cache/accelerator. Most distributions come with "opcache" support.
Enable it in your php.ini where it is documented together with its settings.

Installation
--------------

### Getting it up and running

Installing the basic GNU Social web component is relatively easy,
especially if you've previously installed PHP/MariaDB packages.

1. Unpack the tarball you downloaded on your Web server. Usually a
   command like this will work:

       tar zxf gnusocial-*.tar.gz

   ...which will make a gnusocial-x.y.z subdirectory in your current
   directory. (If you don't have shell access on your Web server, you
   may have to unpack the tarball on your local computer and FTP the
   files to the server.)

2. Move the tarball to a directory of your choosing in your Web root
   directory. Usually something like this will work:

       mv gnusocial-x.y.z /var/www/gnusocial

   This will often make your GNU Social instance available in the gnusocial
   path of your server, like "http://example.net/gnusocial". "social" or
   "blog" might also be good path names. If you know how to configure
   virtual hosts on your web server, you can try setting up
   "http://social.example.net/" or the like.

   If you have "rewrite" support on your webserver, and you should,
   then please enable this in order to make full use of your site. This
   will enable "Fancy URL" support, which you can read more about if you
   scroll down a bit in this document.

3. Make your target directory writeable by the Web server, please note
   however that 'a+w' will give _all_ users write access and securing the
   webserver is not within the scope of this document.

       chmod a+w /var/www/gnusocial/

   On some systems, this will work as a more secure alternative:

       chgrp www-data /var/www/gnusocial/
       chmod g+w /var/www/gnusocial/

   If your Web server runs as another user besides "www-data", try
   that user's default group instead. As a last resort, you can create
   a new group like "gnusocial" and add the Web server's user to the group.

4. Create a database to hold your site data. Something like this
   should work (you will be prompted for your database password):

       mysqladmin -u "root" -p create social

   Note that GNU Social should have its own database; you should not share
   the database with another program. You can name it whatever you want,
   though.

   (If you don't have shell access to your server, you may need to use
   a tool like phpMyAdmin to create a database. Check your hosting
   service's documentation for how to create a new MariaDB database.)

5. Create a new database account that GNU Social will use to access the
   database. If you have shell access, this will probably work from the
   MariaDB shell:

       GRANT ALL on social.*
       TO 'social'@'localhost'
       IDENTIFIED BY 'agoodpassword';

   You should change the user identifier 'social' and 'agoodpassword'
   to your preferred new database username and password. You may want to
   test logging in to MariaDB as this new user.

6. In a browser, navigate to the GNU Social install script; something like:

       https://social.example.net/install.php

   Enter the database connection information and your site name. The
   install program will configure your site and install the initial,
   almost-empty database.

7. You should now be able to navigate to your social site's main directory
   and see the "Public Timeline", which will probably be empty. You can
   now register new user, post some notices, edit your profile, etc.

### Fancy URLs

By default, GNU Social will use URLs that include the main PHP program's
name in them. For example, a user's home profile might be found at either
of these URLS depending on the webserver's configuration and capabilities:

    https://social.example.net/index.php/fred
    https://social.example.net/index.php?p=fred

It's possible to configure the software to use fancy URLs so it looks like
this instead:

    https://social.example.net/fred

These "fancy URLs" are more readable and memorable for users. To use
fancy URLs, you must either have Apache 2.x with .htaccess enabled and
mod_rewrite enabled, -OR- know how to configure "url redirection" in
your server (like lighttpd or nginx).

1. See the instructions for each respective webserver software:
    * For Apache, inspect the "htaccess.sample" file and save it as
        ".htaccess" after making any necessary modifications. Our sample
        file is well commented.
    * For lighttpd, inspect the lighttpd.conf.example file and apply the
        appropriate changes in your virtualhost configuration for lighttpd.
    * For nginx, inspect the nginx.conf.sample file and apply the appropriate
        changes.
    * For other webservers, we gladly accept contributions of
        server configuration examples.

2. Assuming your webserver is properly configured and have its settings
    applied (remember to reload/restart it), you can add this to your
    GNU social's config.php file:
       $config['site']['fancy'] = true;

You should now be able to navigate to a "fancy" URL on your server,
like:

    https://social.example.net/main/register

### Themes

As of right now, your ability change the theme is limited to CSS
stylesheets and some image files; you can't change the HTML output,
like adding or removing menu items, without the help of a plugin.

You can choose a theme using the $config['site']['theme'] element in
the config.php file. See below for details.

You can add your own theme by making a sub-directory of the 'theme'
subdirectory with the name of your theme. Each theme can have the
following files:

display.css: a CSS2 file for "default" styling for all browsers.
logo.png: a logo image for the site.
default-avatar-profile.png: a 96x96 pixel image to use as the avatar for
users who don't upload their own.
default-avatar-stream.png: Ditto, but 48x48. For streams of notices.
default-avatar-mini.png: Ditto ditto, but 24x24. For subscriptions
listing on profile pages.

You may want to start by copying the files from the default theme to
your own directory.

### Private

A GNU social node can be configured as "private", which means it will not
federate with other nodes in the network. It is not a recommended method
of using GNU social and we cannot at the current state of development
guarantee that there are no leaks (what a public network sees as features,
private sites will likely see as bugs).

Private nodes are however an easy way to easily setup collaboration and
image sharing within a workgroup or a smaller community where federation
is not a desired feature. Also, it is possible to change this setting and
instantly gain full federation features.

Access to file attachments can also be restricted to logged-in users only:

1. Add a directory outside the web root where your file uploads will be
   stored. Use this command as an initial guideline to create it:

       mkdir /var/www/gnusocial-files

2. Make the file uploads directory writeable by the web server. An
   insecure way to do this is (to do it properly, read up on UNIX file
   permissions and configure your webserver accordingly):

       chmod a+x /var/www/gnusocial-files

3. Tell GNU social to use this directory for file uploads. Add a line
   like this to your config.php:

       $config['attachments']['dir'] = '/var/www/gnusocial-files';

Extra features
---------

### Sphinx

To use a Sphinx server to search users and notices, you'll need to
enable the SphinxSearch plugin. Add to your config.php:

    addPlugin('SphinxSearch');
    $config['sphinx']['server'] = 'searchhost.local';

You also need to install, compile and enable the sphinx pecl extension for
php on the client side, which itself depends on the sphinx development files.

See plugins/SphinxSearch/README for more details and server setup.

### SMS

StatusNet supports a cheap-and-dirty system for sending update messages
to mobile phones and for receiving updates from the mobile. Instead of
sending through the SMS network itself, which is costly and requires
buy-in from the wireless carriers, it simply piggybacks on the email
gateways that many carriers provide to their customers. So, SMS
configuration is essentially email configuration.

Each user sends to a made-up email address, which they keep a secret.
Incoming email that is "From" the user's SMS email address, and "To"
the users' secret email address on the site's domain, will be
converted to a notice and stored in the DB.

For this to work, there *must* be a domain or sub-domain for which all
(or most) incoming email can pass through the incoming mail filter.

1. Run the SQL script carrier.sql in your StatusNet database. This will
   usually work:

       mysql -u "statusnetuser" --password="statusnetpassword" statusnet < db/carrier.sql

   This will populate your database with a list of wireless carriers
   that support email SMS gateways.

2. Make sure the maildaemon.php file is executable:

       chmod +x scripts/maildaemon.php

   Note that "daemon" is kind of a misnomer here; the script is more
   of a filter than a daemon.

2. Edit /etc/aliases on your mail server and add the following line:

       *: /path/to/statusnet/scripts/maildaemon.php

3. Run whatever code you need to to update your aliases database. For
   many mail servers (Postfix, Exim, Sendmail), this should work:

       newaliases

   You may need to restart your mail server for the new database to
   take effect.

4. Set the following in your config.php file:

       $config['mail']['domain'] = 'yourdomain.example.net';

### Translations

For info on helping with translations, see the platform currently in use
for translations: https://www.transifex.com/projects/p/gnu-social/

Translations use the gettext system <http://www.gnu.org/software/gettext/>.
If you for some reason do not wish to sign up to the Transifex service,
you can review the files in the "locale/" sub-directory of GNU social.
Each plugin also has its own translation files.

To get your own site to use all the translated languages, and you are
tracking the git repo, you will need to install at least 'gettext' on
your system and then run:
    $ make translations

### Queues and daemons

Some activities that StatusNet needs to do, like broadcast OStatus, SMS,
XMPP messages and TwitterBridge operations, can be 'queued' and done by
off-line bots instead.

Two mechanisms are available to achieve offline operations:

* New embedded OpportunisticQM plugin, which is enabled by default
* Legacy queuedaemon script, which can be enabled via config file.

#### OpportunisticQM plugin

This plugin is enabled by default. It tries its best to do background
jobs during regular HTTP requests, like API or HTML pages calls.

Since queueing system is enabled by default, notices to be broadcasted
will be stored, by default, into DB (table queue_item).

Whenever it has time, OpportunisticQM will try to handle some of them.

This is a good solution whether you:

* have no access to command line (shared hosting)
* do not want to deal with long-running PHP processes
* run a low traffic GNU social instance

In other case, you really should consider enabling the queuedaemon for
performance reasons. Background daemons are necessary anyway if you wish
to use the Instant Messaging features such as communicating via XMPP.

#### queuedaemon

If you want to use legacy queuedaemon, you must be able to run
long-running offline processes, either on your main Web server or on
another server you control. (Your other server will still need all the
above prerequisites, with the exception of Apache.) Installing on a
separate server is probably a good idea for high-volume sites.

1. You'll need the "CLI" (command-line interface) version of PHP
   installed on whatever server you use.

   Modern PHP versions in some operating systems have disabled functions
   related to forking, which is required for daemons to operate. To make
   this work, make sure that your php-cli config (/etc/php5/cli/php.ini)
   does NOT have these functions listed under 'disable_functions':

       * pcntl_fork, pcntl_wait, pcntl_wifexited, pcntl_wexitstatus,
         pcntl_wifsignaled, pcntl_wtermsig

   Other recommended settings for optimal performance are:
       * mysqli.allow_persistent = On
       * mysqli.reconnect = On

2. If you're using a separate server for queues, install StatusNet
   somewhere on the server. You don't need to worry about the
   .htaccess file, but make sure that your config.php file is close
   to, or identical to, your Web server's version.

3. In your config.php files (on the server where you run the queue
    daemon), set the following variable:

       $config['queue']['daemon'] = true;

   You may also want to look at the 'Queues and Daemons' section in
   this file for more background processing options.

4. On the queues server, run the command scripts/startdaemons.sh.

This will run the queue handlers:

* queuedaemon.php - polls for queued items for inbox processing and
  pushing out to OStatus, SMS, XMPP, etc.
* imdaemon.php - if an IM plugin is enabled (like XMPP)
* other daemons, like TwitterBridge ones, that you may have enabled

These daemons will automatically restart in most cases of failure
including memory leaks (if a memory_limit is set), but may still die
or behave oddly if they lose connections to the XMPP or queue servers.

It may be a good idea to use a daemon-monitoring service, like 'monit',
to check their status and keep them running.

All the daemons write their process IDs (pids) to /var/run/ by
default. This can be useful for starting, stopping, and monitoring the
daemons. If you are running multiple sites on the same machine, it will
be necessary to avoid collisions of these PID files by setting a site-
specific directory in config.php:

       $config['daemon']['piddir'] = __DIR__ . '/../run/';

It is also possible to use a STOMP server instead of our kind of hacky
home-grown DB-based queue solution. This is strongly recommended for
best response time, especially when using XMPP.

After installation
----------

### Backups

There is no built-in system for doing backups in GNU social. You can make
backups of a working StatusNet system by backing up the database and
the Web directory. To backup the database use mysqldump <https://mariadb.com/kb/en/mariadb/mysqldump/>
and to backup the Web directory, try tar.

### Upgrading

Upgrading is strongly recommended to stay up to date with security fixes
and new features. For instructions on how to upgrade GNU social code,
please see the UPGRADE file.

### Additional configuration

Please refer to DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE for information.
----

repo_name: ksuudilla/gnu

license: mit


code:
#!/bin/sh

printf "Domain root: "
read -r domain_root
printf "Subdomain (can be empty): "
read -r sub_domain
printf "Use certificate signed by Let's Encrypt (Y/n): "
read -r signed

[ "${signed}" = "${signed#[Yy]}" ]
signed=$?

if [ $signed -ne 0 ]; then
    printf "Email: "
    read -r email
fi

if [ -z "$sub_domain" ]
then
  domain="${domain_root}"
else
  domain="${sub_domain}.${domain_root}"
fi

mkdir -p ./docker/bootstrap

cat > ./docker/bootstrap/bootstrap.env <<EOF
#!/bin/sh
email=${email}
domain=${domain}
domain_root=${domain_root}
signed=${signed}
EOF

chmod +x ./docker/bootstrap/bootstrap.env

docker-compose -f docker/bootstrap/bootstrap.yaml up


code:
#!/bin/sh

git_dir=$PWD
while [ ! -d .git ]; do
    git_dir=$(dirname "${git_dir}")
done

cd "${git_dir}" || exit

if [ ! -f ./docker/bootstrap/bootstrap.env ]; then
   printf "bootstrap.env missing! Please run the bootstrap_certificates script.\n"
   exit 1
fi

. ./docker/bootstrap/bootstrap.env

while :; do
    printf "DBMS (postgres|mariadb): " && read -r dbms
    [ $(echo "${dbms}" | grep -E 'postgres|mariadb') ] && break
done

printf "Social database name: " && read -r db
[ "${dbms}" = 'mariadb' ] && printf "Database user: " && read -r user
printf "Database password: " && read -r password
printf "Sitename: " && read -r sitename
printf "Admin nickname: " && read -r admin_nick
printf "Admin password: " && read -r admin_password

while :; do
    printf "Site profile (public|private|community|single_user): " && read -r profile
    [ $(echo "${profile}" | grep -E 'public|private|community|single_user') ] && break
done

mkdir -p ./docker/db

if [ "${dbms}" = 'mariadb' ]; then
    printf "DB root password: " && read -r db_root_password
    cat > ./docker/db/db.env <<EOF
DBMS=${dbms}
MYSQL_ROOT_PASSWORD=${db_root_password}
EOF

    database_url="DATABASE_URL=mysql://${user}:${password}@db:3306/${db}"

else
    cat > ./docker/db/db.env <<EOF
DBMS=${dbms}
POSTGRES_USER=postgres
POSTGRES_PASSWORD=${password}
EOF

    user='postgres'
    database_url="DATABASE_URL=postgresql://${user}:${password}@db:5432/${db}"

fi

echo "${database_url}" >> .env.local

printf "Mailer dsn: " && read -r mailer_dsn

mkdir -p ./docker/social

cat > ./docker/social/social.env <<EOF
SOCIAL_DBMS="${dbms}"
SOCIAL_DB="${db}"
SOCIAL_USER="${user}"
SOCIAL_PASSWORD="${password}"
SOCIAL_DOMAIN="${domain}"
SOCIAL_SITENAME="${sitename}"
SOCIAL_ADMIN_NICK="${admin_nick}"
SOCIAL_ADMIN_PASSWORD="${admin_password}"
SOCIAL_ADMIN_EMAIL="${email}"
SOCIAL_SITE_PROFILE="${profile}"
MAILER_DSN="${mailer_dsn}"
EOF

path: bin/configure


code:
#!/usr/bin/env php
<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\ErrorHandler\Debug;

if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
    echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
}

set_time_limit(0);

require dirname(__DIR__).'/vendor/autoload.php';

if (!class_exists(Application::class)) {
    throw new LogicException('You need to add "symfony/framework-bundle" as a Composer dependency.');
}

$input = new ArgvInput();
if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) {
    putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
}

if ($input->hasParameterOption('--no-debug', true)) {
    putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
}

require dirname(__DIR__).'/config/bootstrap.php';

if ($_SERVER['APP_DEBUG']) {
    umask(0000);

    if (class_exists(Debug::class)) {
        Debug::enable();
    }
}

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$application = new Application($kernel);
$application->run($input);

path: bin/console


code:
#!/bin/php

<?php

define('INSTALLDIR', dirname(__DIR__));

require INSTALLDIR . '/vendor/autoload.php';

use Functional as F;

use App\Util\Functional;

$filenames = glob(INSTALLDIR . '/src/*/*.php');

$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
 *
 * 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/>.
 */
', '// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/soci
//
// 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/>.
// }}}
', '/*
 * 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/>.
 */'];

$new_license = "// {{{ 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/>.
// }}}
";

foreach ($old_licenses as $old) {
    $files = str_replace("$old", "$new_license", $files);
}

F\map(F\zip($files, $filenames),
      function ($arg)
      {
          list($file, $filename) = $arg;
          file_put_contents($filename, $file);
      }
);


code:
#!/usr/local/bin/php

<?php

define('INSTALL_DIR', dirname(__DIR__));

require INSTALL_DIR . '/vendor/autoload.php';

use App\Util\Common;
use App\Util\HTML as H;
use Functional as F;

$template = '
graph database {

%tables%

%edges%

}
';

$files = glob(INSTALL_DIR . '/src/Entity/*.php');

$tables = [];
$edges  = [];
foreach ($files as $file) {
    require_once $file;

    $declared = get_declared_classes();
    $class    = end($declared);
    $schema   = $class::schemaDef();
    $table    = $schema['name'];
    $fields   = [['name' => $table, 'type' => '']];
    foreach ($schema['fields'] as $name => $opts) {
        $fields[] = [
            'name' => $name,
            'type' => ": {$opts['type']}" . ($opts['type'] == 'varchar' ? "({$opts['length']})" : ''),
        ];
    }

    if (isset($schema['foreign keys'])) {
        foreach ($schema['foreign keys'] as $name => $map) {
            // Patern matching like above would be nice
            list($foreign_table,
                 $keys)  = $map;
            $local_key   = array_keys($keys)[0];
            $foreign_key = $keys[$local_key];
            $edges[]     = "{$table}:{$local_key} -- {$foreign_table}:{$foreign_key}";
        }
    }

    $cell = function ($field) {
        $def = $field['name'] . $field['type'];
        return ['tr' => ['td' => ['attrs' => ['port' => $field['name']], $def]]];
    };
    $html = ['table' => array_merge(
              ['attrs' => ['border' => '0', 'cellborder' => '1', 'cellspacing' => '0']],
              F\map($fields, $cell)),
    ];

    $tables[] = Common::indent("{$table} [shape=none, label=<\n" . Common::indent(H::html($html)) . "\n>]");
}

$replace = [
    '/%tables%/' => Common::indent(implode("\n", $tables)),
    '/%edges%/'  => Common::indent(implode("\n", $edges)),
    // '/_/'        => '\textunderscore ',
];

$out = $template;
foreach ($replace as $from => $to) {
    $out = preg_replace($from, $to, $out);
}

$path = dirname(__DIR__) . '/DOCUMENTATION/database';

$outfile = $path . '/database.dot';
file_put_contents($outfile, $out);

system("neato -Goverlap=false -Gsplines=true -Tpdf {$path}/database.dot -o {$path}/database.pdf");

echo "Generated database diagram. See {$path}/database.pdf\n";


code:
#!/usr/local/bin/php
<?php

use Symfony\Component\Yaml\Yaml;

define('ROOT', dirname(__DIR__));

require ROOT . '/vendor/autoload.php';

const types = [
    'blob'         => '',
    'bool'         => 'bool',
    'char'         => 'string',
    'datetime'     => 'DateTimeInterface',
    'timestamp'    => 'DateTimeInterface',
    'html'         => 'string',
    'int'          => 'int',
    'numeric'      => 'float',
    'serial'       => 'int',
    'text'         => 'string',
    'varchar'      => 'string',
    'phone_number' => 'PhoneNumber',
];

$files = array_merge(glob(ROOT . '/src/Entity/*.php'),
                     array_merge(glob(ROOT . '/components/*/Entity/*.php'),
                                 glob(ROOT . '/plugins/*/Entity/*.php')));

$classes = [];

foreach ($files as $file) {

    require_once $file;

    $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);
    $schema       = $class::schemaDef();
    $fields       = array_keys($schema['fields']);
    $fields_code  = [];
    $methods_code = [];
    foreach ($fields as $field) {
        $nullable       = !@$schema['fields'][$field]['not null'] ? '?' : '';
        $type           = types[$schema['fields'][$field]['type']];
        $type           = $type !== '' ? $nullable . $type : $type;
        $method_name    = str_replace(' ', '', ucwords(str_replace('_', ' ', $field)));
        $default        = @$schema['fields'][$field]['default'];

        if (isset($default) && $nullable != '?' && $type != 'DateTimeInterface') {
            if (is_string($default)) {
                $default = "'{$default}'";
            } elseif ($type == 'bool') {
                $default = $default ? 'true' : 'false';
            }

            $fields_code[]  = "    private {$type} \${$field} = {$default};";
        } else {
            $fields_code[]  = "    private {$type} \${$field};";
        }

        $methods_code[] = "    public function set{$method_name}({$type} \${$field}): self" .
                        "\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) . "\n";

    $begin = '// {{{ Autocode';
    $end   = '// }}} Autocode';
    $code  = "
    {$begin}

{$fields_code}

{$methods_code}

    {$end}";

    foreach (['/\\//' => '\\/', '/ /' => '\\ '] as $from => $to) {
        $begin = preg_replace($from, $to, $begin);
        $end   = preg_replace($from, $to, $end);
    }

    $in_file  = file_get_contents($file);
    $out_file = preg_replace("/\\s*{$begin}[^\\/]*{$end}/m", $code, $in_file);

    file_put_contents($file, $out_file);
}


code:
#!/bin/bash

case $# in
    0) echo "No filename given" && exit 1 ;;
    1) file=$1 ;;
    2) file=$2; branch=$1 ;;
    *) echo "Too many arguments given" && exit 2 ;;
esac

git log --pretty=email --patch-with-stat --reverse --full-index --binary $branch -- $file  | git am --committer-date-is-author-date

path: bin/git-import


code:
#!/usr/bin/env php
<?php

if (!file_exists(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
    echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
    exit(1);
}

if (false === getenv('SYMFONY_PHPUNIT_DIR')) {
    putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit');
}

require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';

path: bin/phpunit


code:
#!/bin/php

<?php

define('INSTALLDIR', dirname(__DIR__));

require INSTALLDIR . '/vendor/autoload.php';

use App\Util\Common;

error_reporting(E_ALL | E_STRICT);

$delete = array_merge(glob(INSTALLDIR . '/src/Entity/*DataObject.php'),
                      glob(INSTALLDIR . '/src/Entity/Status_network*'));

foreach ($delete as $d) {
    system("git rm {$d}");
}

$files = glob(INSTALLDIR . '/src/Entity/*.php');

class CatchAll
{
    const foo = null;
    public function foo()
    {
    }
}

$classes = [];

foreach ($files as $f) {
    system('sed -ri "' . implode('; ', [
        's/(extends )?((DB|GS|Safe|Managed|Memcached)' .
        '_DataObject|CachingNoticeStream)/\1CatchAll/g',
        's/(CatchAll::)[A-Za-z_0-9]*/\1foo/g',
        's/.*(exit|die).*//g',
        's/require[^sd ].*//g',
    ]) . '" ' . $f);
    require $f;
    $cls       = get_declared_classes();
    $classes[] = end($cls);
}

$license_header = '
/* {{{ 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/>.
 */
';

foreach ($classes as $cls) {
    $ref = new ReflectionClass($cls);

    $class_name    = Common::snakeCaseToCamelCase($cls);
    $file          = $ref->getFileName();
    $class_comment = $ref->getDocComment();
    $table_name    = $ref->getDefaultProperties()['__table'];
    $sd            = $ref->getMethod('schemaDef');
    $start         = $sd->getStartLine();
    $end           = $sd->getEndLine();
    $command       = "sed -rn '" . implode('; ',
    [
        "s%(return array\\()%\\1\\
            \"name\" => \"{$table_name}\",%",
        's%schemaDef\(\)%schemaDef(): array%',
        "{$start},{$end}p",
    ]) . "' {$file}";

    // echo $command . "\n";

    $schemaDef = [];
    exec($command, $schemaDef);
    $schemaDef = implode("\n", $schemaDef);

    $class = "<?php

{$license_header}

namespace App\\Entity;

{$class_comment}

class {$class_name}
{
    // AUTOCODE BEGIN

    // AUTOCODE END

{$schemaDef}

}";
    $new_file = dirname($file) . '/' . $class_name . '.php';
    file_put_contents($file, $class);
    if ($file !== $new_file) {
        system("git mv {$file} {$new_file}");
    }
    system("git add {$new_file}");
}


code:
#!/usr/bin/env bash

root="$(git rev-parse --show-toplevel)"

# get the list of changed files
staged_files="$(git diff --cached --name-only)"

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
        "${root}/bin/php-cs-fixer" -q fix "${staged}"

        # if php-cs-fixer fix works, it returns 0
        if [[ $? -eq 0 ]]; then
            git add "${staged}" # execute git add directly
        fi
    fi
done

echo "Running php-doc-checker"

"${root}/bin/php-doc-check" src plugins components

# Only commit if there wasn't an error
exit $?

path: bin/pre-commit


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Bridge;

use App\Core\Module;

class Bridge extends Module
{
}


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Bridge\Entity;

use DateTimeInterface;

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

    private int $user_id;
    private int $foreign_id;
    private int $service;
    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
    {
        $this->user_id = $user_id;
        return $this;
    }

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

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

    public function getForeignId(): int
    {
        return $this->foreign_id;
    }

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

    public function getService(): int
    {
        return $this->service;
    }

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

    public function getCredentials(): ?string
    {
        return $this->credentials;
    }

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

    public function getNoticesync(): int
    {
        return $this->noticesync;
    }

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

    public function getFriendsync(): int
    {
        return $this->friendsync;
    }

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

    public function getProfilesync(): int
    {
        return $this->profilesync;
    }

    public function setLastNoticesync(?DateTimeInterface $last_noticesync): self
    {
        $this->last_noticesync = $last_noticesync;
        return $this;
    }

    public function getLastNoticesync(): ?DateTimeInterface
    {
        return $this->last_noticesync;
    }

    public function setLastFriendsync(?DateTimeInterface $last_friendsync): self
    {
        $this->last_friendsync = $last_friendsync;
        return $this;
    }

    public function getLastFriendsync(): ?DateTimeInterface
    {
        return $this->last_friendsync;
    }

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

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

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

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

    // }}} Autocode

    public static function schemaDef(): array
    {
        return [
            'name'   => 'foreign_link',
            'fields' => [
                'user_id'         => ['type' => 'int', 'not null' => true, 'description' => 'link to user on this system, if exists'],
                'foreign_id'      => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'link to user on foreign service, if exists'],
                'service'         => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to service'],
                'credentials'     => ['type' => 'varchar', 'length' => 191, 'description' => 'authc credentials, typically a password'],
                'noticesync'      => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies'],
                'friendsync'      => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 2, 'description' => 'friend synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'],
                'profilesync'     => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'],
                'last_noticesync' => ['type' => 'datetime', 'description' => 'last time notices were imported'],
                'last_friendsync' => ['type' => 'datetime', 'description' => 'last time friends were imported'],
                'created'         => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
                'modified'        => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'],
            ],
            'primary key'  => ['user_id', 'foreign_id', 'service'],
            'foreign keys' => [
                'foreign_link_user_id_fkey'    => ['user', ['user_id' => 'id']],
                'foreign_link_foreign_id_fkey' => ['foreign_user', ['foreign_id' => 'id', 'service' => 'service']],
                'foreign_link_service_fkey'    => ['foreign_service', ['service' => 'id']],
            ],
            'indexes' => [
                'foreign_user_user_id_idx' => ['user_id'],
            ],
        ];
    }
}


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Bridge\Entity;

use DateTimeInterface;

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

    private int $id;
    private string $name;
    private ?string $description;
    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 setName(string $name): self
    {
        $this->name = $name;
        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }

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

    public function getDescription(): ?string
    {
        return $this->description;
    }

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

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

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

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

    // }}} Autocode

    public static function schemaDef(): array
    {
        return [
            'name'   => 'foreign_service',
            'fields' => [
                'id'          => ['type' => 'int', 'not null' => true, 'description' => 'numeric key for service'],
                'name'        => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'name of the service'],
                'description' => ['type' => 'varchar', 'length' => 191, 'description' => 'description'],
                'created'     => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
                'modified'    => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'],
            ],
            'primary key' => ['id'],
            'unique keys' => [
                'foreign_service_name_key' => ['name'],
            ],
        ];
    }
}


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Bridge\Entity;

use DateTimeInterface;

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

    private int $service;
    private int $subscriber;
    private int $subscribed;
    private DateTimeInterface $created;

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

    public function getService(): int
    {
        return $this->service;
    }

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

    public function getSubscriber(): int
    {
        return $this->subscriber;
    }

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

    public function getSubscribed(): int
    {
        return $this->subscribed;
    }

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

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

    // }}} Autocode

    public static function schemaDef(): array
    {
        return [
            'name' => 'foreign_subscription',

            'fields' => [
                'service'    => ['type' => 'int', 'not null' => true, 'description' => 'service where relationship happens'],
                'subscriber' => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'subscriber on foreign service'],
                'subscribed' => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'subscribed user'],
                'created'    => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
            ],
            'primary key'  => ['service', 'subscriber', 'subscribed'],
            'foreign keys' => [
                'foreign_subscription_service_fkey'    => ['foreign_service', ['service' => 'id']],
                'foreign_subscription_subscriber_fkey' => ['foreign_user', ['subscriber' => 'id', 'service' => 'service']],
                'foreign_subscription_subscribed_fkey' => ['foreign_user', ['subscribed' => 'id', 'service' => 'service']],
            ],
            'indexes' => [
                'foreign_subscription_subscriber_idx' => ['service', 'subscriber'],
                'foreign_subscription_subscribed_idx' => ['service', 'subscribed'],
            ],
        ];
    }
}


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Bridge\Entity;

use DateTimeInterface;

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

    private int $id;
    private int $service;
    private string $uri;
    private ?string $nickname;
    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 setService(int $service): self
    {
        $this->service = $service;
        return $this;
    }

    public function getService(): int
    {
        return $this->service;
    }

    public function setUri(string $uri): self
    {
        $this->uri = $uri;
        return $this;
    }

    public function getUri(): string
    {
        return $this->uri;
    }

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

    public function getNickname(): ?string
    {
        return $this->nickname;
    }

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

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

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

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

    // }}} Autocode

    public static function schemaDef(): array
    {
        return [
            'name'   => 'foreign_user',
            'fields' => [
                'id'       => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'unique numeric key on foreign service'],
                'service'  => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to service'],
                'uri'      => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'identifying URI'],
                'nickname' => ['type' => 'varchar', 'length' => 191, 'description' => 'nickname on foreign service'],
                'created'  => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'],
                'modified' => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'],
            ],
            'primary key'  => ['id', 'service'],
            'foreign keys' => [
                'foreign_user_service_fkey' => ['foreign_service', ['service' => 'id']],
            ],
            'unique keys' => [
                'foreign_user_uri_key' => ['uri'],
            ],
        ];
    }
}


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Left;

use App\Core\Event;
use App\Core\Log;
use App\Core\Module;
use App\Util\Common;
use Exception;

class Left extends Module
{
    public function onEndTwigPopulateVars(array &$vars)
    {
        try {
            $user = Common::user();
            if ($user != null) {
                $actor                  = $user->getActor();
                $vars['user_nickname']  = $user->getNickname();
                $vars['user_tags']      = $actor->getSelfTags();
                $vars['user_followers'] = $actor->getFollowersCount();
                $vars['user_followed']  = $actor->getFollowedCount();
            }
        } catch (Exception $e) {
            Log::error('Got an exception while populating variables for the left panel: ' . $e);
        }
        return Event::next;
    }
}


code:
<?php

// {{{ License

// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

// }}}

namespace Component\Media\Controller;

use App\Core\Controller;
use Component\Media\Media as M;
use Exception;
use Symfony\Component\HttpFoundation\Request;

class Media extends Controller
{
    public function avatar(Request $request, string $nickname, string $size)
    {
        switch ($size) {
        case 'full':
            $res = M::getAvatarFileInfo($nickname);
            return M::sendFile($res['file_path'], $res['mimetype'], $res['title']);
        default:
            throw new Exception('Not implemented');
        }
    }

    public function attachment_inline(Request $request, int $id)
    {
        $res = M::getAttachmentFileInfo($id);
        return M::sendFile($res['file_path'], $res['mimetype'], $res['title']);
    }
}


code:
<?php

// {{{ License

// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

// }}}

namespace Component\Media\Exception;

use Exception;

class NoAvatarException extends Exception
{
}


code:
<?php

// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
// }}}

namespace Component\Media;

use App\Core\Cache;
use App\Core\Event;
use App\Core\Module;
use App\Util\Common;
use App\Util\Nickname;

class Media extends Module
{
    public static function __callStatic(string $name, array $args)
    {
        return Utils::{$name}(...$args);
    }

    public function onAddRoute($r)
    {
        $r->connect('avatar', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/avatar/{size<full|big|medium|small>?full}', [Controller\Media::class, 'avatar']);
        $r->connect('attachment_inline', '/attachment/{id<\d+>}', [Controller\Media::class, 'attachment_inline']);
        return Event::next;
    }

    public function onEndTwigPopulateVars(array &$vars)
    {
        if (Common::user() != null) {
            $vars['user_avatar'] = self::getAvatarUrl();
        }
        return Event::next;
    }

    public function onGetAvatarUrl(string $nickname, ?string &$url)
    {
        $url = self::getAvatarUrl($nickname);
        return Event::next;
    }

    public function onDeleteCachedAvatar(string $nickname)
    {
        Cache::delete('avatar-' . $nickname);
        Cache::delete('avatar-url-' . $nickname);
        Cache::delete('avatar-file-info-' . $nickname);
    }
}