Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
95.24% covered (success)
95.24%
20 / 21
CRAP
94.74% covered (success)
94.74%
72 / 76
Common
0.00% covered (danger)
0.00%
0 / 1
95.24% covered (success)
95.24%
20 / 21
46.31
94.74% covered (success)
94.74%
72 / 76
 setupConfig
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 setRequest
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 route
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isRoute
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 config
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 setConfig
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 getConfigDefaults
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 user
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 actor
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 userNickname
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 userId
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 ensureLoggedIn
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 isLoggedIn
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isSystemPath
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 arrayDiffRecursive
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
11 / 11
 arrayRemoveKeys
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 sizeStrToInt
100.00% covered (success)
100.00%
1 / 1
12
100.00% covered (success)
100.00%
19 / 19
 getPreferredPhpUploadLimit
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 4
 clamp
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isValidHttpUrl
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
5 / 5
 flattenNoteArray
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
1<?php
2
3// {{{ License
4
5// This file is part of GNU social - https://www.gnu.org/software/social
6//
7// GNU social is free software: you can redistribute it and/or modify
8// it under the terms of the GNU Affero General Public License as published by
9// the Free Software Foundation, either version 3 of the License, or
10// (at your option) any later version.
11//
12// GNU social is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15// GNU Affero General Public License for more details.
16//
17// You should have received a copy of the GNU Affero General Public License
18// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
19
20// }}}
21
22/**
23 * Common utility functions
24 *
25 * @package   GNUsocial
26 * @category  Util
27 *
28 * @author    Hugo Sales <hugo@hsal.es>
29 * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
30 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
31 */
32
33namespace App\Util;
34
35use App\Core\Router\Router;
36use App\Core\Security;
37use App\Entity\GSActor;
38use App\Entity\LocalUser;
39use App\Util\Exception\NoLoggedInUser;
40use Functional as F;
41use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
42use Symfony\Component\HttpFoundation\Request;
43use Symfony\Component\Routing\Exception\ResourceNotFoundException;
44use Symfony\Component\Yaml;
45
46abstract class Common
47{
48    private static array $defaults;
49    private static ?array $config = null;
50    public static function setupConfig(ContainerBagInterface $config)
51    {
52        self::$config   = $config->get('gnusocial');
53        self::$defaults = $config->get('gnusocial_defaults');
54    }
55
56    private static ?Request $request = null;
57    public static function setRequest(Request $req)
58    {
59        self::$request = $req;
60    }
61
62    public static function route()
63    {
64        return self::$request->attributes->get('_route');
65    }
66
67    public static function isRoute(string | array $routes)
68    {
69        return in_array(self::route(), is_array($routes) ? $routes : [$routes]);
70    }
71
72    /**
73     * Access sysadmin's configuration preferences for GNU social
74     */
75    public static function config(string $section, ?string $setting = null)
76    {
77        if ($setting !== null) {
78            return self::$config[$section][$setting];
79        } else {
80            return self::$config[$section] ?? [];
81        }
82    }
83
84    /**
85     * Set sysadmin's configuration preferences for GNU social
86     *
87     * @param mixed $value
88     */
89    public static function setConfig(string $section, string $setting, $value): void
90    {
91        self::$config[$section][$setting] = $value;
92        $diff                             = self::arrayDiffRecursive(self::$config, self::$defaults);
93        $yaml                             = (new Yaml\Dumper(indentation: 2))->dump(['parameters' => ['gnusocial' => $diff]], Yaml\Yaml::DUMP_OBJECT_AS_MAP);
94        rename(INSTALLDIR . '/social.local.yaml', INSTALLDIR . '/social.local.yaml.back');
95        file_put_contents(INSTALLDIR . '/social.local.yaml', $yaml);
96    }
97
98    public static function getConfigDefaults()
99    {
100        return self::$defaults;
101    }
102
103    public static function user(): ?LocalUser
104    {
105        return Security::getUser();
106    }
107
108    public static function actor(): ?GSActor
109    {
110        return self::user()->getActor();
111    }
112
113    public static function userNickname(): ?string
114    {
115        return self::ensureLoggedIn()->getNickname();
116    }
117
118    public static function userId(): ?int
119    {
120        return self::ensureLoggedIn()->getId();
121    }
122
123    public static function ensureLoggedIn(): LocalUser
124    {
125        if (($user = self::user()) == null) {
126            throw new NoLoggedInUser();
127        // TODO Maybe redirect to login page and back
128        } else {
129            return $user;
130        }
131    }
132
133    /**
134     * checks if user is logged in
135     *
136     * @return bool true if user is logged; false if it isn't
137     */
138    public static function isLoggedIn(): bool
139    {
140        return  self::user() != null;
141    }
142
143    /**
144     * Is the given string identical to a system path or route?
145     * This could probably be put in some other class, but at
146     * at the moment, only Nickname requires this functionality.
147     */
148    public static function isSystemPath(string $str): bool
149    {
150        try {
151            Router::match('/' . $str);
152            return true;
153        } catch (ResourceNotFoundException $e) {
154            return false;
155        }
156    }
157
158    /**
159     * A recursive `array_diff`, while PHP itself doesn't provide one
160     *
161     * @param mixed $array1
162     * @param mixed $array2
163     */
164    public static function arrayDiffRecursive($array1, $array2): array
165    {
166        $diff = [];
167        foreach ($array1 as $key => $value) {
168            if (array_key_exists($key, $array2)) {
169                if (is_array($value)) {
170                    $recursive_diff = static::arrayDiffRecursive($value, $array2[$key]);
171                    if (count($recursive_diff)) {
172                        $diff[$key] = $recursive_diff;
173                    }
174                } else {
175                    if ($value != $array2[$key]) {
176                        $diff[$key] = $value;
177                    }
178                }
179            } else {
180                $diff[$key] = $value;
181            }
182        }
183        return $diff;
184    }
185
186    /**
187     * Remove keys from the _values_ of $keys from the array $from
188     */
189    public static function arrayRemoveKeys(array $from, array $keys, bool $strict = false)
190    {
191        return F\filter($from, function ($_, $key) use ($keys, $strict) { return !in_array($key, $keys, $strict); });
192    }
193
194    /**
195     * An internal helper function that converts a $size from php.ini for
196     * file size limit from the 'human-readable' shorthand into a int. If
197     * $size is empty (the value is not set in php.ini), returns a default
198     * value (3M)
199     *
200     * @return int the php.ini upload limit in machine-readable format
201     */
202    public static function sizeStrToInt(string $size): int
203    {
204        // `memory_limit` can be -1 and `post_max_size` can be 0
205        // for unlimited. Consistency.
206        if (empty($size) || $size === '-1' || $size === '0') {
207            $size = '3M';
208        }
209
210        $suffix = substr($size, -1);
211        $size   = (int) substr($size, 0, -1);
212        switch (strtoupper($suffix)) {
213        case 'P':
214            $size *= 1024;
215            // no break
216        case 'T':
217            $size *= 1024;
218            // no break
219        case 'G':
220            $size *= 1024;
221            // no break
222        case 'M':
223            $size *= 1024;
224            // no break
225        case 'K':
226            $size *= 1024;
227            break;
228        default:
229            if ($suffix >= '0' && $suffix <= '9') {
230                $size = (int) "{$size}{$suffix}";
231            }
232        }
233        return $size;
234    }
235
236    /**
237     * Uses `size_str_to_int()` to find the smallest value for uploads in php.ini
238     *
239     * @return int
240     */
241    public static function getPreferredPhpUploadLimit(): int
242    {
243        return min(
244            self::sizeStrToInt(ini_get('post_max_size')),
245            self::sizeStrToInt(ini_get('upload_max_filesize')),
246            self::sizeStrToInt(ini_get('memory_limit'))
247        );
248    }
249
250    /**
251     * Clamps a value between 2 numbers
252     *
253     * @return float|int clamped value
254     */
255    public static function clamp(int | float $value, int | float $min, int | float $max): int | float
256    {
257        return min(max($value, $min), $max);
258    }
259
260    /**
261     * If $ensure_secure is true, only allow https URLs to pass
262     */
263    public static function isValidHttpUrl(string $url, bool $ensure_secure = false)
264    {
265        if (empty($url)) {
266            return false;
267        }
268
269        // (if false, we use '?' in 'https?' to say the 's' is optional)
270        $regex = $ensure_secure ? '/^https$/' : '/^https?$/';
271        return filter_var($url, FILTER_VALIDATE_URL)
272            && preg_match($regex, parse_url($url, PHP_URL_SCHEME));
273    }
274
275    /**
276     * Flatten an array of ['note' => note, 'replies' => [notes]] to an array of notes
277     */
278    public static function flattenNoteArray(array $a): array
279    {
280        $notes = [];
281        foreach ($a as $n) {
282            $notes[] = $n['note'];
283            if (isset($n['replies'])) {
284                $notes = array_merge($notes, static::flattenNoteArray($n['replies']));
285            }
286        }
287        return $notes;
288    }
289}