diff --git a/languages/language-flags.php b/languages/language-flags.php index 0dfcc54..9ac15d4 100644 --- a/languages/language-flags.php +++ b/languages/language-flags.php @@ -4,6 +4,13 @@ * Provide language flags for hardcoded Communities. */ + /** + * @var array $server_languages + * + * Dictionary of language flags for hardcoded Communities. + * + * The array key a Community ID (long or legacy short). + */ $server_languages = []; // https://reccacon.com/Ukraine diff --git a/php/generate-html.php b/php/generate-html.php index 18e099e..57f8888 100644 --- a/php/generate-html.php +++ b/php/generate-html.php @@ -8,10 +8,14 @@ require_once 'utils/getopt.php'; /** - * Recursively match the last segment of the given path pattern. - * @source https://stackoverflow.com/a/17161106 + * Return file names matching the glob pattern in all subdirectories. + * @param string $pattern Glob pattern. + * @param int $flags Glob flags. + * @author Tony Chen + * @see https://stackoverflow.com/a/17161106 + * @return string[] Array of file names. */ - function rglob($pattern, $flags = 0) { + function rglob($pattern, $flags = 0): array { $files = glob($pattern, $flags); foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { $files = array_merge( @@ -22,7 +26,12 @@ return $files; } - function serialize_shell_environment(array $env_vars) { + /** + * Produce shell code to set the environment variables given. + * + * @param string[] $env_vars Dictionary of environment variables. + */ + function serialize_shell_environment(array $env_vars): string { $env_assignments = array_map(function(string $key, string $value) { return "$key=".escapeshellarg($value); }, array_keys($env_vars), array_values($env_vars)); @@ -30,7 +39,10 @@ } /** - * Generate files from PHP templates in the templates directory. + * Generate files from PHP templates. + * + * Templates used are in the {@link $TEMPLATES_ROOT} directory. + * Generated files are places in the {@link $DOCUMENT_ROOT} directory. */ function generate_files() { global $LOGGING_VERBOSITY, $TEMPLATES_ROOT, $DOCUMENT_ROOT; diff --git a/php/getenv.php b/php/getenv.php index a02a43e..03d1d46 100644 --- a/php/getenv.php +++ b/php/getenv.php @@ -10,6 +10,10 @@ */ $PROJECT_ROOT = dirname(__FILE__); + /** + * @var string $PHPENV_FILENAME + * File comtaining PHP environment variables. Marks root folder. + */ $PHPENV_FILENAME = ".phpenv.php"; (function(){ diff --git a/php/servers/room-database.php b/php/servers/room-database.php index 67213a4..5ed4f47 100644 --- a/php/servers/room-database.php +++ b/php/servers/room-database.php @@ -11,25 +11,50 @@ } /** + * Stored Community servers.. * @var CommunityServer[] $servers */ public array $servers; /** + * Stored Communities. * @var CommunityRoom[] $rooms */ public array $rooms; + /** + * Read a database of Session Open Group servers and Communities. + * + * @param string $rooms_file JSON file containing fetched server data. + * Typically equal to {@link $ROOMS_FILE}. + * + * @return CommunityDatabase + */ public static function read_from_file(string $rooms_file): CommunityDatabase { $servers = CommunityServer::read_servers_from_file($rooms_file); return new CommunityDatabase($servers); } + /** + * Re-fetch outdated assets for Communities stored by the database. + * + * @return CommunityDatabase self + */ public function fetch_assets(): CommunityDatabase { CommunityRoom::fetch_assets($this->rooms); return $this; } + /** + * Return the Communities and servers stored by the database. + * + * Usage: + * ```php + * list($rooms, $servers) = communityDatabase->unpack(); + * ``` + * + * @return array + */ public function unpack() { return [$this->rooms, $this->servers]; } diff --git a/php/servers/room-listings-api.php b/php/servers/room-listings-api.php index 4b7db7d..e2e9536 100644 --- a/php/servers/room-listings-api.php +++ b/php/servers/room-listings-api.php @@ -12,6 +12,11 @@ /** * Construct Community listings from listing configuration and cached Communities. + * + * @param string $listings_ini File containing listing configuration. + * @param CommunityServer[] $servers Array of Community servers used to resolve the listing. Leave empty to fetch from cache. + * + * @return CommunityListingDatabase */ public static function resolve_listings_from_ini(string $listings_ini, array $servers = null): CommunityListingDatabase { global $ROOMS_FILE; @@ -59,7 +64,17 @@ return array_values($this->listings); } - public function get_listing(string $id): CommunityListing { + /** + * Get the Community listing with the given ID. + * + * @param string $id Configured listing identifier. + * + * @return CommunityListing + */ + public function get_listing(string $id): CommunityListing|null { + if (!isset($this->listings[$id])) { + throw new Error("No such listing: '$id'"); + } return $this->listings[$id]; } } diff --git a/php/servers/servers-rooms.php b/php/servers/servers-rooms.php index 204fd5f..fafbddf 100644 --- a/php/servers/servers-rooms.php +++ b/php/servers/servers-rooms.php @@ -173,6 +173,12 @@ /** * Create incomplete CommunityRoom instance from intermediate data. + * + * Use when room data has been fetched, but the server's public key is still unknown. + * + * @param CommunityServer $server Open Group Server hosting given Community. + * @param array $details Associative data describing given Community. + * @return CommunityRoom Incomplete CommunityRoom instance. Expect errors. */ public static function _from_intermediate_data( CommunityServer $server, @@ -273,7 +279,9 @@ /** * Create a CommunityRoom instance from associative data. - * @param CommunityServer $server + * @param CommunityServer $server Open Group Server hosting given Community. + * @param array $details Associative data describing Community. + * @return CommunityRoom */ public static function from_details($server, array $details) { return new CommunityRoom($server, $details); @@ -281,7 +289,8 @@ /** * Create an array of CommunityRoom instances from associative data. - * @param array[] $details + * @param CommunityServer $server Open Group server hosting the given rooms. + * @param array[] $details_array Array of associative arrays holding room data. * @return CommunityRoom[] */ public static function from_details_array($server, array $details_array) { @@ -294,9 +303,11 @@ * Sort Community rooms in-place by the given string property. * @param CommunityRoom[] $rooms Rooms to sort by given key. * @param string $key String property of CommunityRoom to sort by. + * @param bool $descending If true, sort in descending order. + * @return void */ - public static function sort_rooms_str(array &$rooms, string $key, bool $reverse = false) { - usort($rooms, $reverse ? function(CommunityRoom $a, CommunityRoom $b) use ($key) { + public static function sort_rooms_str(array &$rooms, string $key, bool $descending = false) { + usort($rooms, $descending ? function(CommunityRoom $a, CommunityRoom $b) use ($key) { return strcmp( $b->$key, $a->$key @@ -313,9 +324,11 @@ * Sort Community rooms in-place by the given numeric property. * @param CommunityRoom[] $rooms Rooms to sort by given key. * @param string $key Numeric property of CommunityRoom to sort by. + * @param bool $descending If true, sort in descending order. + * @return void */ - public static function sort_rooms_num(array &$rooms, string $key, bool $reverse = false) { - usort($rooms, $reverse ? function(CommunityRoom $a, CommunityRoom $b) use ($key) { + public static function sort_rooms_num(array &$rooms, string $key, bool $descending = false) { + usort($rooms, $descending ? function(CommunityRoom $a, CommunityRoom $b) use ($key) { return $b->$key - $a->$key; } : function(CommunityRoom $a, CommunityRoom $b) use ($key) { return $a->$key - $b->$key; @@ -325,6 +338,8 @@ /** * Sort Community rooms in-place by their server. * @param CommunityRoom[] $rooms Rooms to sort by server. + * @param bool $random If true, use random server order to sort rooms. + * @return void */ public static function sort_rooms_by_server(array &$rooms, bool $random = false) { if ($random) { @@ -345,7 +360,9 @@ } /** + * Re-fetch assets for the given Communities as necessary. * @param CommunityRoom[] $rooms + * @return void */ public static function fetch_assets(array $rooms) { // Sequential in each server, see note in fetch_room_hints_coroutine() @@ -362,9 +379,12 @@ } /** - * @param CommunityRoom[] $rooms + * Keep only pinned Communities from the input list. + * @param CommunityRoom[] $rooms Input list of Communities. + * @param CommunityRoom[] $rest (output) Remainder of Communities from the list. (optional) + * @return CommunityRoom[] Pinned Communities. */ - public static function get_stickied_rooms(array &$rooms, array &$rest = null) { + public static function get_stickied_rooms(array $rooms, array &$rest = null) { global $STICKIED_ROOMS; return CommunityRoom::select_rooms($rooms, $STICKIED_ROOMS, unmatched: $rest); } @@ -538,7 +558,7 @@ * Check whether the given list matches the current Community or its parent server. * @param string[] $filter * Array of unique room identifiers, server pubkeys and/or server hostnames. - * @param string $matchee String matching room. Output parameter. + * @param string $matchee (output) String matching current room. * @return bool True if the array matches the Community, false otherwise. */ public function matched_by_list(array $filter, string &$matchee = null): bool { @@ -553,9 +573,11 @@ } /** + * Select Communities matching the given filter list. * @param CommunityRoom[] $rooms - * @param string[] $filter - * @param string[] $matchees output parameter + * @param string[] $filter List of room identifiers, server pubkeys and/or hostnames. + * @param string[] $matchees (output) Filter list items used to select at least one Community. (optional) + * @param CommunityRoom[] $unmatched (output) Communities not matched by any filter items. (optional) * @return CommunityRoom[] */ public static function select_rooms(array $rooms, array|string $filter, array &$matchees = null, array &$unmatched = null): array { @@ -615,11 +637,21 @@ return $this->has_nsfw_keywords() || $this->matched_by_list($NSFW_INCLUDE); } + /** + * Return true if the Community is marked for testing. + * + * @return bool + */ public function is_testing_room(): bool { global $TESTING_INCLUDE; return in_array("test", $this->string_tags) || $this->matched_by_list($TESTING_INCLUDE); } + /** + * Return true if the Community is manually stickied. + * + * @return bool + */ public function is_stickied_room(): bool { global $STICKIED_ROOMS; return $this->matched_by_list($STICKIED_ROOMS); @@ -642,7 +674,12 @@ return 0; } - public function get_recommended_staff_count() { + /** + * Compute the recommended threshold for Community staff count. + * + * @return float + */ + public function get_recommended_staff_count(): float { if ($this->active_users == null || $this->active_users == 0) return INF; $recommended_staff_count = ceil($this->active_users ** 0.25); return max(2, $recommended_staff_count); @@ -650,18 +687,30 @@ /** * Estimate whether the Community has enough staff. + * + * @return bool */ public function has_good_staff_rating(): bool { $staff_count = count($this->get_staff()); return $staff_count >= $this->get_recommended_staff_count(); } - public function get_numeric_staff_rating() { + /** + * Return a rating for the Community's staff count relative to active users. + * + * @return float + */ + public function get_numeric_staff_rating(): float { if (!$this->write || !$this->read) return 2; return min(2, count($this->get_staff()) / $this->get_recommended_staff_count()); } - public function get_minimal_staff_count() { + /** + * Compute the lower threshold for Community staff count. + * + * @return float + */ + public function get_minimal_staff_count(): float { if ($this->active_users == null || $this->active_users == 0) return INF; $minimal_staff_count = 1 + round((0.38 * log($this->active_users)) ** 1.15); return max(2, $minimal_staff_count); @@ -858,6 +907,7 @@ /** * Sort an array of servers in place based on URL. * @param CommunityServer[] &$servers + * @return void */ static function sort_by_url(array &$servers) { usort($servers, 'CommunityServer::compare_by_url'); @@ -878,6 +928,7 @@ /** * Sorts an array of servers in place by public key. * @param CommunityServer[] $servers + * @return void */ public static function sort_by_pubkey(&$servers) { foreach ($servers as $server) { @@ -893,6 +944,9 @@ /** * Return true whether the two servers given share a room. + * @param CommunityServer $a First server to compare. + * @param CommunityServer $b Second server to compare. + * @return bool */ public static function rooms_in_common(CommunityServer $a, CommunityServer $b): bool { // Rely on at least token or creation date differing. @@ -1122,7 +1176,7 @@ /** * Create Community server instance from array loaded server data. - * @param array $details Decoded JSON associative arrays about server. + * @param array $details_array Array of associative arrays holding server data. * @return CommunityServer[] Servers represented by given data. */ static function from_details_array(array $details_array) { @@ -1139,6 +1193,7 @@ * Add to the given servers additional data extracted from our sources. * @param CommunityServer[] $servers * @param CommunitySources $source + * @return void */ static function source_additional_info(array $servers, CommunitySources $source): void { foreach ($servers as $server) { @@ -1214,13 +1269,17 @@ /** * Returns the hostname for this server. - * @param bool $include_scheme [optional] - * Includes the port. `true` by default. + * @param bool $include_port + * Include port in output, if provided. Default: `true`. * @return string URL with hostname and port, if applicable. * Scheme not included. */ function get_hostname(bool $include_port = true) { - return url_get_base($this->base_url, include_scheme: false, include_port: $include_port); + return url_get_base( + $this->base_url, + include_scheme: false, + include_port: $include_port + ); } /** @@ -1232,7 +1291,9 @@ } /** - * Returns the URL to the endpoint listing this server's rooms. + * Returns the URL to the endpoint describing this server's rooms. + * + * @return string */ function get_rooms_api_url(): string { $base_url = $this->base_url; @@ -1240,7 +1301,11 @@ } /** - * Returns the URL for the endpoint of the particular room. + * Returns the URL for the endpoint describing a particular room. + * + * @param string $token Token of Community to construct URL. + * + * @return string */ function get_room_api_url(string $token): string { $base_url = $this->base_url; @@ -1318,12 +1383,19 @@ return $pubkey_prefix . $hostname_hash_prefix; } + /** + * Return string value used to sort Communities by host. + * + * @return string + */ public function get_server_sort_key(): string { return $this->get_pubkey() . $this->get_hostname(); } /** * Returns the room of the given token, or null if one does not exist. + * @param string $token The string token of a room on the server. + * @return CommunityRoom|null */ function get_room_by_token(string $token): CommunityRoom | null { $candidates = array_filter($this->rooms, function(CommunityRoom $room) use ($token) { @@ -1577,7 +1649,14 @@ return true; } + /** + * Construct full-fledged Community objects if the public key is available. + * @return void + */ public function construct_rooms() { + if (!$this->has_pubkey()) { + throw new Error("Cannot construct rooms before pubkey is fetched"); + } $this->rooms = CommunityRoom::from_details_array( $this, $this->_intermediate_room_data @@ -1585,7 +1664,9 @@ } /** - * @return CommunityServer[] + * Deserialize Community servers from the given JSON file. + * @param string $file Path to JSON file containing fetched server data. + * @return CommunityServer[] Array of Session Open Group servers. */ public static function read_servers_from_file(string $file): array { // Read the server data from disk. diff --git a/php/servers/sources.php b/php/servers/sources.php index 6568f94..bf6b9a9 100644 --- a/php/servers/sources.php +++ b/php/servers/sources.php @@ -22,8 +22,12 @@ } /** - * Create new instance of SDIRCommunitySource on the given source text. + * Create new instance of SDIRCommunitySource. * Returns false if processing the source fails. + * + * @param string $contents Text from Session.directory to process. + * + * @return SDIRCommunitySource|false */ public static function from_contents(string $contents): SDIRCommunitySource | false { $source = new SDIRCommunitySource($contents); @@ -142,8 +146,13 @@ private array $tags = []; /** - * Attempt to create an ASGLCommunitySource instance on the given source text. + * Attempt to create an ASGLCommunitySource instance. + * * Returns false if processing the source fails. + * + * @param string $contents Text from ASGL to process. + * + * @return ASGLCommunitySource|false */ public static function from_contents(string $contents): ASGLCommunitySource | false { $source = new ASGLCommunitySource($contents); diff --git a/php/servers/tags.php b/php/servers/tags.php index 073317e..4920f2b 100644 --- a/php/servers/tags.php +++ b/php/servers/tags.php @@ -75,10 +75,16 @@ return html_sanitize($this->get_text()); } - public static $descriptions = []; + /** + * @var string[] $descriptions + * Dictionary of reserved tag descriptions. + */ + public static array $descriptions = []; /** - * Return tag description. + * Return the tag's description. + * + * @return string */ public function get_description_sanitized(): string { // Feels out-of-place anywhere else. @@ -89,15 +95,34 @@ return html_sanitize(CommunityTag::$descriptions[$this->text] ?? "Tag: $this->text"); } + /** + * Associate the current tag's text with the given description. + * + * @param string $description New description for the current tag text. + * + * @return CommunityTag Return self. + */ public function set_description_globally(string $description): self { CommunityTag::$descriptions[$this->text] = $description; return $this; } - public static function serializeClassData() { + /** + * Serialize tag data into a string. + * + * @return string JSON data + */ + public static function serializeClassData(): string { return json_encode(CommunityTag::$descriptions); } + /** + * Load tag data from the given serialized string. + * + * @param string $data JSON string of tag descriptions. + * + * @return void + */ public static function loadSerializedClassData(string $data) { CommunityTag::$descriptions = json_decode($data, associative: true); } @@ -258,9 +283,18 @@ private const REDUNDANT_TAGS = []; + /** + * @var string[] NSFW_KEYWORDS + * Keywords indicating a not-safe-for-work Community. + */ public const NSFW_KEYWORDS = ["nsfw", "porn", "erotic", "18+", "sex"]; + /** * Check whether the given user tag is reserved by our aggregator. + * + * @param string $tag String tag to check. + * + * @return bool */ public static function is_reserved_tag(string $tag): bool { return in_array(strtolower($tag), CommunityTag::RESERVED_TAGS); @@ -268,6 +302,10 @@ /** * Return true if the tag should be given a chance to appear in more crowded views. + * + * @param string $tag String tag to check. + * + * @return bool */ public static function is_showcased_tag(string $tag): bool { return in_array(strtolower($tag), CommunityTag::SHOWCASED_TAGS); @@ -275,6 +313,10 @@ /** * Return true if the tag should be given visibility in more crowded views. + * + * @param string $tag String tag to check. + * + * @return bool */ public static function is_highlighted_tag(string $tag): bool { return in_array(strtolower($tag), CommunityTag::HIGHLIGHTED_TAGS); @@ -285,7 +327,12 @@ * Constructs Community tags reserved by the aggregator. */ class ReservedTags { - public static function official() { + /** + * Return "official" reserved Community tag. + * + * @return CommunityTag + */ + public static function official(): CommunityTag { $CHECK_MARK = "✅"; return (new CommunityTag( @@ -294,7 +341,12 @@ ))->set_description_globally("This Community is maintained by the Session team. $CHECK_MARK"); } - public static function nsfw() { + /** + * Return "nsfw" reserved Community tag. + * + * @return CommunityTag + */ + public static function nsfw(): CommunityTag { $WARNING_ICON = "⚠️"; return (new CommunityTag( @@ -303,7 +355,12 @@ ))->set_description_globally("This Community may contain adult material. $WARNING_ICON"); } - public static function moderated() { + /** + * Return "moderated" reserved Community tag. + * + * @return CommunityTag + */ + public static function moderated(): CommunityTag { $CHECK_MARK = "✅"; return (new CommunityTag( @@ -312,7 +369,12 @@ ))->set_description_globally("This Community seems to have enough moderators. $CHECK_MARK"); } - public static function not_modded() { + /** + * Return "not_modded" reserved Community tag. + * + * @return CommunityTag + */ + public static function not_modded(): CommunityTag { $WARNING_ICON = "⚠️"; return (new CommunityTag( @@ -321,28 +383,48 @@ ))->set_description_globally("This Community does not seem to have enough moderators. $WARNING_ICON"); } - public static function read_only() { + /** + * Return "read_only" reserved Community tag. + * + * @return CommunityTag + */ + public static function read_only(): CommunityTag { return (new CommunityTag( "read-only", TagType::RESERVED_TAG, ))->set_description_globally("This Community is read-only."); } - public static function no_upload_permission() { + /** + * Return "no_upload_permission" reserved Community tag. + * + * @return CommunityTag + */ + public static function no_upload_permission(): CommunityTag { return (new CommunityTag( "uploads off", TagType::RESERVED_TAG, ))->set_description_globally("This Community does not support uploading files or link previews."); } - public static function recently_created() { + /** + * Return "recently_created" reserved Community tag. + * + * @return CommunityTag + */ + public static function recently_created(): CommunityTag { return (new CommunityTag( "new", TagType::RESERVED_TAG, ))->set_description_globally("This Community was created recently."); } - public static function used_by_project() { + /** + * Return "used_by_project" reserved Community tag. + * + * @return CommunityTag + */ + public static function used_by_project(): CommunityTag { return (new CommunityTag( "we're here", TagType::RESERVED_TAG, @@ -350,14 +432,24 @@ . "or respond to feedback in this Community."); } - public static function testing() { + /** + * Return "testing" reserved Community tag. + * + * @return CommunityTag + */ + public static function testing(): CommunityTag { return (new CommunityTag( "test", TagType::RESERVED_TAG, ))->set_description_globally("This Community is intended for testing only."); } - public static function stickied() { + /** + * Return "stickied" reserved Community tag. + * + * @return CommunityTag + */ + public static function stickied(): CommunityTag { return (new CommunityTag( "pinned", TagType::RESERVED_TAG, diff --git a/php/utils/fetching-coroutines.php b/php/utils/fetching-coroutines.php index 3dd8c59..b54d84c 100644 --- a/php/utils/fetching-coroutines.php +++ b/php/utils/fetching-coroutines.php @@ -42,7 +42,8 @@ * Create a new FetchingCoroutine to fetch the contents of a URL. * @param string $url URL to fetch. * @param array $curlopts Addition cURL options. - * @return FetchingCoroutine Coroutine returning + * @return FetchingCoroutine + * Coroutine returning 1) fulfilled cURL handle, or 2) false in case of failure. */ public static function from_url(string $url, array $curlopts = []): FetchingCoroutine { /** @@ -234,7 +235,7 @@ /** * Step coroutine with network result until next yield point. * Coroutine must not have been consumed by any transformations. - * @param CurlHandle|false $response + * @param CurlHandle|false $response_handle * cURL handle containing fetch result or false in case of failure. * @return bool True if response was accepted by coroutine, false otherwise. */ diff --git a/php/utils/getopt.php b/php/utils/getopt.php index 73e0cac..38ae5d6 100644 --- a/php/utils/getopt.php +++ b/php/utils/getopt.php @@ -6,32 +6,61 @@ include_once 'utils/logging.php'; // Read the -v|--verbose option increasing logging verbosity to debug. + + /** + * @var array $options + * List of options parsed from the command-line. + */ $options = getopt("vn", ["verbose", "fast", "no-color", "dry-run", "archive"]); + if (isset($options["v"]) or isset($options["verbose"])) { + /** + * @var int $LOGGING_VERBOSITY + * Highest verbosity to display in logs. + */ $LOGGING_VERBOSITY = LoggingVerbosity::Debug; } + /** + * @var bool $FAST_FETCH_MODE + * If true, be less patient when polling servers. + */ $FAST_FETCH_MODE = (isset($options["fast"])); + /** + * @var bool $DO_DRY_RUN + * If true, do not overwrite fetched server data. + */ $DO_DRY_RUN = (isset($options["n"]) || isset($options["dry-run"])); if (isset($options["no-color"])) { LoggingVerbosity::$showColor = false; } + /** + * @var bool $DO_ARCHIVE_FILES + * If true, archive fetched server data. + */ $DO_ARCHIVE_FILES = isset($options["archive"]); // set timeout for file_get_contents() ini_set('default_socket_timeout', 6); // in seconds, default is 60 - // curl timeout in milliseconds - - // max time for initiation of the connection + /** + * @var int $CURL_CONNECT_TIMEOUT_MS + * Maximum time to initiate connection. + */ $CURL_CONNECT_TIMEOUT_MS = 2000; - // max time for each connection (incl. transfer) + /** + * @var int $CURL_TIMEOUT_MS + * Maximum time for each connection, including transfer. + */ $CURL_TIMEOUT_MS = $FAST_FETCH_MODE ? 3000 : 9000; - // delay between retries in milliseconds + /** + * @var int $CURL_RETRY_SLEEP + * Delay between fetch retries in milliseconds. + */ $CURL_RETRY_SLEEP = 2000; ?> diff --git a/php/utils/logging.php b/php/utils/logging.php index 5d542bb..e7e5be9 100644 --- a/php/utils/logging.php +++ b/php/utils/logging.php @@ -23,9 +23,21 @@ // Prevent class instantiation private function __construct() {} + /** + * Error log verbosity constant. + */ const Error = 10; + /** + * Warning log verbosity constant. + */ const Warning = 20; + /** + * Info log verbosity constant. + */ const Info = 30; + /** + * Debug log verbosity constant. + */ const Debug = 40; /** @@ -118,6 +130,9 @@ ); } + /** + * @private + */ function _log_message(?string $msg, int $message_verbosity) { global $LOGGING_VERBOSITY; if ($message_verbosity > $LOGGING_VERBOSITY) return; @@ -169,7 +184,8 @@ /** * Logs the given value in a debug message to stderr. * Only logs when `$LOGGING_VERBOSITY` is debug and below. - * @param string $msg String message to log. + * @param mixed $value Value to log. + * @param int $message_verbosity Verbosity to use when logging value. Default: Debug. */ function log_value(mixed $value, int $message_verbosity = LoggingVerbosity::Debug) { _log_message(var_export($value, true), $message_verbosity); diff --git a/php/utils/site-generation.php b/php/utils/site-generation.php index 0c91920..8badb27 100644 --- a/php/utils/site-generation.php +++ b/php/utils/site-generation.php @@ -4,26 +4,68 @@ * Provides site generation context variables. */ class SiteGeneration { - public static function getCanonicalPageURL() { + /** + * Get the absolute web path to the current document, omitting the final 'index.html'. + * + * @return string + */ + public static function getCanonicalPageURL(): string { global $SITE_CANONICAL_URL; return dirname($SITE_CANONICAL_URL.getenv('SSG_TARGET')) . '/'; } - public static function getAbsoluteSourceDocumentPath() { + + /** + * Get the absolute source path of the current document. + * + * @return string + */ + public static function getAbsoluteSourceDocumentPath(): string { return $_SERVER['SCRIPT_NAME']; } - public static function getTargetDocumentPath() { + /** + * Get the relative web path of the current document. + * + * @return string + */ + public static function getTargetDocumentPath(): string { return getenv('SSG_TARGET'); } - public static function getTargetDocumentRoute() { + /** + * Get the directory above the current document's web location. + * + * Returns the path to the directory in which the current document + * will be served, relative to the webroot. + * + * Usage: + * ```php + * // Generating /index.php + * SiteGeneration::getTargetDocumentRoute() // -> '/' + * + * // Generating /privacy/index.php + * SiteGeneration::getTargetDocumentRoute() // -> '/privacy' + * ``` + * + * @return string Path to the directory serving the current document. + */ + public static function getTargetDocumentRoute(): string { return dirname(SiteGeneration::getTargetDocumentPath()); } - public static function getOwnSubDocumentPath(string $identifier) { + /** + * Return the path of a subdocument of the current document. + * + * When generating "index.php", this function will return + * "+index.head.php" when given a subdocument identifier of "head".; + * + * @param string $subdocument Subdocument identifier. + * @return string Absolute source path of subdocument "+current.subdocument.php" + */ + public static function getOwnSubDocumentPath(string $subdocument) { $page = SiteGeneration::getAbsoluteSourceDocumentPath(); - $sub_document = dirname($page) . '/+' . preg_replace('/[.]php$/', ".$identifier.php", basename($page)); + $sub_document = dirname($page) . '/+' . preg_replace('/[.]php$/', ".$subdocument.php", basename($page)); return $sub_document; } } diff --git a/php/utils/utils.php b/php/utils/utils.php index 557f7b8..d01b201 100644 --- a/php/utils/utils.php +++ b/php/utils/utils.php @@ -4,6 +4,10 @@ * Implement basic utility functions. */ + /** + * @var string $REGEX_JOIN_LINK + * Regular expression matching Session Community join links. + */ $REGEX_JOIN_LINK = (function(){ // See https://github.com/oxen-io/session-pysogs/blob/dev/administration.md $protocol = 'https?:'; @@ -47,7 +51,14 @@ return $truncated; } - function make_curl_handle(string $url, $curlopts = []) { + /** + * Constructs a cURL handle for performing network requests. + * + * @param string $url Target resource to fetch. + * @param array $curlopts Associative array of cURL options. (optional) + * @return CurlHandle + */ + function make_curl_handle(string $url, $curlopts = []): CurlHandle { global $CURL_CONNECT_TIMEOUT_MS, $CURL_TIMEOUT_MS; $curl = curl_init($url); @@ -70,7 +81,8 @@ /** * Downgrades a HTTPS-facing cURL handle to HTTP. - * @return CurlHandle|null Handle copy if can downgrade, or null if not applicable. + * @param CurlHandle $handle cURL handle. + * @return CurlHandle|null Handle copy, or null if not applicable. */ function curl_handle_downgrade(CurlHandle $handle): CurlHandle|null { $url = curl_getinfo($handle, CURLINFO_EFFECTIVE_URL); @@ -87,7 +99,7 @@ * @param string $url The URL to slice the path from. * @param bool $include_scheme [optional] * Includes the scheme. `true` by default. - * @param bool $include_scheme [optional] + * @param bool $include_port [optional] * Includes the port. `true` by default. * @return string A URL composed of the original scheme (unless specified), * hostname, and port (if present). @@ -142,10 +154,10 @@ /** * Extracts join links that match $REGEX_JOIN_LINK. - * @param ?string $html Text to find join URLs in. + * @param string $html Text to find join URLs in. * @return string[] Sorted array of unique server join links. */ - function parse_join_links(?string $html){ + function parse_join_links(?string $html): array { global $REGEX_JOIN_LINK; preg_match_all($REGEX_JOIN_LINK, $html, $match_result); $links = $match_result[0]; @@ -159,8 +171,9 @@ * @param string $str String to sanitize * @param int $flags [optional] * A bitmask of one or more of the following flags, - * which specify how to handle quotes, invalid code unit sequences + * which specify how to handle quotes, invalid code unit sequences * and the used document type. The default is ENT_COMPAT | ENT_HTML401. + * @param string $encoding Character encoding used. [optional] * @param bool $double_encode [optional] * When double_encode is turned off, PHP will not encode * existing html entities, the default is to convert everything. @@ -176,6 +189,13 @@ return htmlspecialchars($str, $flags, $encoding, $double_encode); } + /** + * Return the sign of the given number. + * + * @param float $num Floating-point number. + * + * @return int -1 if negative, 1 if positive, 0 otherwise + */ function sign(float $num): int { return ($num > 0) - ($num < 0); } diff --git a/sites/+components/communities-json-ld.php b/sites/+components/communities-json-ld.php index 763ce10..b8acc77 100644 --- a/sites/+components/communities-json-ld.php +++ b/sites/+components/communities-json-ld.php @@ -11,6 +11,10 @@ * @var CommunityRoom[] $rooms */ + /** + * @var array $json_ld_data + * Associative data about the site in JSON-LD format. + */ $json_ld_data = array( '@context' => 'https://schema.org/', '@id' => $SITE_CANONICAL_URL, diff --git a/sites/+templates/index.php b/sites/+templates/index.php index ce9e483..0091b15 100644 --- a/sites/+templates/index.php +++ b/sites/+templates/index.php @@ -11,7 +11,17 @@ // Set the last-updated timestamp // to the time the server data file was last modified. + + /** + * @var int $time_modified + * Timestamp of last Community data fetch. + */ $time_modified = filemtime($ROOMS_FILE); + + /** + * @var string $time_modified_str + * Timestamp of last Community data fetch. + */ $time_modified_str = date("Y-m-d H:i:s", $time_modified); ?> diff --git a/sites/_fragment/+room-sieve.php b/sites/_fragment/+room-sieve.php index 4bfe3a6..fbae298 100644 --- a/sites/_fragment/+room-sieve.php +++ b/sites/_fragment/+room-sieve.php @@ -10,14 +10,23 @@ /** * Filters Session Communities. + * + * RoomSieve methods return copies when mutating the object. + * The stickied room list is preserved after filtering. */ class RoomSieve { /** + * Communities stored. + * * @var CommunityRoom[] $rooms; */ private array $rooms; /** + * Pinned Communities, set aside. + * + * Not affected by filters. + * * @var CommunityRoom[] $stickied */ private array $stickies; @@ -31,53 +40,104 @@ $this->stickies = $stickied; } + /** + * Default limit for number of Communities. + * + * Applies only to certain functions. + */ public const TOP_DEFAULT = 35; /** + * Create new RoomSieve from the given Communities. + * * @param CommunityRoom[] $rooms + * + * @return RoomSieve */ - public static function takeRooms(array $rooms) { + public static function takeRooms(array $rooms): RoomSieve { return new RoomSieve($rooms); } - public function saveStickies() { + /** + * Set aside pinned Communities from the main list. + * + * @return RoomSieve + */ + public function saveStickies(): self { $stickied = CommunityRoom::get_stickied_rooms($this->rooms, $rest); $rooms = $rest; return $this->cloneWith($rooms, $stickied); } /** + * Add the given Communities to a new RoomSieve. + * * @param CommunityRoom[] $rooms + * + * @return RoomSieve */ - public function addRooms(array $rooms) { + public function addRooms(array $rooms): RoomSieve { return $this->cloneWith(array_merge($this->rooms, $rooms)); } - public function apply(Closure $filter) { + /** + * Use a custom filter for Communities. + * + * Creates a new RoomSieve with all Communities that passed the filter. + * + * @param Closure $filter Function which takes an array of Communities and returns an array of Communities. + * + * @return RoomSieve + */ + public function apply(Closure $filter): RoomSieve { return $this->cloneWith($filter($this->rooms)); } - public function getWithStickies() { + /** + * Return all stored Communities, including pinned Communities. + * + * @return CommunityRoom[] + */ + public function getWithStickies(): array { return [...$this->stickies, ...$this->rooms]; } - public function getWithoutStickies() { - return $this->saveStickies()->getRooms(); - } - - public function getRooms() { - return $this->rooms; + /** + * Return stored Communities without pinned Communities. + * + * @return CommunityRoom[] + */ + public function getWithoutStickies(): array { + return $this->saveStickies()->rooms; } - public function onlyTop(int $count = RoomSieve::TOP_DEFAULT) { + /** + * Only keep the top N active Communities. + * + * Does not affect stickied Communities. + * + * @param int $count Number of top Communities to keep. (optional) + * + * @return RoomSieve + */ + public function onlyTop(int $count = RoomSieve::TOP_DEFAULT): RoomSieve { $rooms = $this->rooms; - CommunityRoom::sort_rooms_num($rooms, 'active_users', reverse: true); + CommunityRoom::sort_rooms_num($rooms, 'active_users', descending: true); return $this->cloneWith(array_slice($rooms, 0, $count)); } - public function exceptTop(int $count = RoomSieve::TOP_DEFAULT) { + /** + * Remove the top N active Communities. + * + * Does not affect stickied Communities. + * + * @param int $count Number of top Communities to remove. (optional) + * + * @return RoomSieve + */ + public function exceptTop(int $count = RoomSieve::TOP_DEFAULT): RoomSieve { $rooms = $this->rooms; - CommunityRoom::sort_rooms_num($rooms, 'active_users', reverse: true); + CommunityRoom::sort_rooms_num($rooms, 'active_users', descending: true); return $this->cloneWith(array_slice($rooms, $count)); } @@ -90,14 +150,26 @@ ); } - public function applyStandardSort() { + /** + * Sort Communities by name and server. + * + * @return RoomSieve + */ + public function applyStandardSort(): RoomSieve { $rooms = $this->rooms; CommunityRoom::sort_rooms_str($rooms, 'name'); CommunityRoom::sort_rooms_by_server($rooms); return new RoomSieve($rooms, $this->stickies); } - public function applyPreferentialSort() { + /** + * Sort Communities by staff rating. + * + * Communities with a description are also preferred. + * + * @return RoomSieve + */ + public function applyPreferentialSort(): RoomSieve { $rooms = $this->rooms; CommunityRoom::sort_rooms_num($rooms,'created'); usort($rooms, function($a, $b) { @@ -109,14 +181,24 @@ return new RoomSieve(array_reverse($rooms), $this->stickies); } - public function indexApproved() { + /** + * Keep only Communities heuristically deemed to be appropriate. + * + * @return RoomSieve + */ + public function indexApproved(): RoomSieve { $rooms = array_values(array_filter($this->rooms, function($room) { return RoomSieve::isIndexApproved($room); })); return new RoomSieve($rooms, $this->stickies); } - public function indexNonApproved() { + /** + * Remove Communities heuristically deemed to be appropriate. + * + * @return RoomSieve + */ + public function indexNonApproved(): RoomSieve { $rooms = array_values(array_filter($this->rooms, function($room) { return !RoomSieve::isIndexApproved($room); })); diff --git a/sites/index.php b/sites/index.php index 4ac3cad..daee182 100644 --- a/sites/index.php +++ b/sites/index.php @@ -9,7 +9,16 @@ require_once 'php/servers/room-database.php'; require_once 'sites/_fragment/+room-sieve.php'; + /** + * @var CommunityDatabase $room_database + * Database of fetched servers and Communities. + */ $room_database = CommunityDatabase::read_from_file($ROOMS_FILE)->fetch_assets(); + + /** + * @var CommunityRoom[] $rooms + * Communities shown on page by default. + */ $rooms = RoomSieve::takeRooms($room_database->rooms) ->saveStickies() diff --git a/sites/instructions/index.php b/sites/instructions/index.php index 8036bea..8a77c4f 100644 --- a/sites/instructions/index.php +++ b/sites/instructions/index.php @@ -6,18 +6,47 @@ require_once '+getenv.php'; + /** + * @var string[] $instruction_files + * List of all files containing localized site instructions. + */ $instruction_files = glob("+instructions/*.txt"); + + /** + * Get the language name of the given localized file. + * + * @param string $file File containing site instructions. + * + * @return string + */ function file_language($file) { $filename = pathinfo($file)['filename']; return explode(" ", $filename)[0]; } + + /** + * Get the language code of the given localized file. + * + * @param string $file File containing site instructions. + * + * @return string + */ function file_language_code($file) { $filename = pathinfo($file)['filename']; $code_in_brackets = explode(" ", $filename)[1]; return mb_substr($code_in_brackets, 1, mb_strlen($code_in_brackets) - 2); } + /** + * @var string $languages + * List of languages in which instructions are available. + */ $languages = array_map('file_language', array_slice($instruction_files, 0, 10)); + + /** + * @var string $language_enumeration + * Enumerated list of languages with available instructions. + */ $language_enumeration = join(", ", $languages); ?> @@ -71,6 +100,10 @@ // Sanitization as second layer of protection // for user-submitted instruction files. // Should not ever have to be used. + /** + * @var string $content + * Instructions file contents. + */ $content = trim(file_get_contents($file)); $content = htmlentities($content); // Minimal formatting so that contributions are easier diff --git a/sites/privacy/index.php b/sites/privacy/index.php index 65e2455..9e8cc60 100644 --- a/sites/privacy/index.php +++ b/sites/privacy/index.php @@ -8,6 +8,10 @@ require_once '+getenv.php'; + /** + * @var string[] $HIGHLIGHTED_FIELDS + * List of interactive server log entries. + */ $HIGHLIGHTED_FIELDS = ["ip", "datetime", "resource", "status", "bytes", "referer", "user-agent"]; ?> diff --git a/sites/sitemap.xml.php b/sites/sitemap.xml.php index f070c61..362bbea 100644 --- a/sites/sitemap.xml.php +++ b/sites/sitemap.xml.php @@ -6,8 +6,20 @@ */ require_once '+getenv.php'; + /** + * Generate sitemap fragment containing page location and last modified time. + * + * Only works for pages named "index.php". + * + * @param string $rel_loc Canonical webpage location relative to webroot. + * @param string $changes_under_root The directory to check (source or output) to infer file modification time. + * Typically {@link $DOCUMENT_ROOT} to detect new versions of files with updating content + * and {@link $TEMPLATES_ROOT} for articles which only substantially change on source change. + * + * @return void + */ function loc_lastmod(string $rel_loc, ?string $changes_under_root = null) { - global $SITE_CANONICAL_URL, $DOCUMENT_ROOT, $TEMPLATES_ROOT; + global $SITE_CANONICAL_URL, $TEMPLATES_ROOT; $root = $changes_under_root ?? $TEMPLATES_ROOT; $ext = ($root == $TEMPLATES_ROOT) ? "php" : "html"; ?> diff --git a/sites/support/index.php b/sites/support/index.php index 16f44e2..f8542ae 100644 --- a/sites/support/index.php +++ b/sites/support/index.php @@ -6,11 +6,31 @@ require_once '+getenv.php'; + /** + * Number of background particles. + * @var int $NUM_PARTICLES + */ $NUM_PARTICLES = 20; + + /** + * @var int[] $DELAYS + * Time delays for background particles to appear. + */ $DELAYS = range(0, 240 - 1, 240 / $NUM_PARTICLES); shuffle($DELAYS); + + /** + * @var string[] $PARTICLES + * Array of available background particles. + */ $PARTICLES = explode(" ", "$ 💵 💰 💸 💯"); - function random_particle() { + + /** + * Pick random particle. + * + * @return string + */ + function random_particle(): string { global $PARTICLES; $r = rand(0, count($PARTICLES) - 1); return $PARTICLES[$r];