]> git.mxchange.org Git - friendica.git/blob - src/Core/System.php
Merge branch '2020.09-rc' into stable
[friendica.git] / src / Core / System.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Core;
23
24 use Friendica\DI;
25 use Friendica\Network\HTTPException\InternalServerErrorException;
26 use Friendica\Util\XML;
27
28 /**
29  * Contains the class with system relevant stuff
30  */
31 class System
32 {
33         /**
34          * Returns a string with a callstack. Can be used for logging.
35          *
36          * @param integer $depth  How many calls to include in the stacks after filtering
37          * @param int     $offset How many calls to shave off the top of the stack, for example if
38          *                        this is called from a centralized method that isn't relevant to the callstack
39          * @return string
40          */
41         public static function callstack(int $depth = 4, int $offset = 0)
42         {
43                 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
44
45                 // We remove at least the first two items from the list since they contain data that we don't need.
46                 $trace = array_slice($trace, 2 + $offset);
47
48                 $callstack = [];
49                 $previous = ['class' => '', 'function' => '', 'database' => false];
50
51                 // The ignore list contains all functions that are only wrapper functions
52                 $ignore = ['call_user_func_array'];
53
54                 while ($func = array_pop($trace)) {
55                         if (!empty($func['class'])) {
56                                 // Don't show multiple calls from the Database classes to show the essential parts of the callstack
57                                 $func['database'] = in_array($func['class'], ['Friendica\Database\DBA', 'Friendica\Database\Database']);
58                                 if (!$previous['database'] || !$func['database']) {     
59                                         $classparts = explode("\\", $func['class']);
60                                         $callstack[] = array_pop($classparts).'::'.$func['function'];
61                                         $previous = $func;
62                                 }
63                         } elseif (!in_array($func['function'], $ignore)) {
64                                 $func['database'] = ($func['function'] == 'q');
65                                 $callstack[] = $func['function'];
66                                 $func['class'] = '';
67                                 $previous = $func;
68                         }
69                 }
70
71                 $callstack2 = [];
72                 while ((count($callstack2) < $depth) && (count($callstack) > 0)) {
73                         $callstack2[] = array_pop($callstack);
74                 }
75
76                 return implode(', ', $callstack2);
77         }
78
79         /**
80          * Generic XML return
81          * Outputs a basic dfrn XML status structure to STDOUT, with a <status> variable
82          * of $st and an optional text <message> of $message and terminates the current process.
83          *
84          * @param        $st
85          * @param string $message
86          * @throws \Exception
87          */
88         public static function xmlExit($st, $message = '')
89         {
90                 $result = ['status' => $st];
91
92                 if ($message != '') {
93                         $result['message'] = $message;
94                 }
95
96                 if ($st) {
97                         Logger::log('xml_status returning non_zero: ' . $st . " message=" . $message);
98                 }
99
100                 header("Content-type: text/xml");
101
102                 $xmldata = ["result" => $result];
103
104                 echo XML::fromArray($xmldata, $xml);
105
106                 exit();
107         }
108
109         /**
110          * Send HTTP status header and exit.
111          *
112          * @param integer $val     HTTP status result value
113          * @param string  $message Error message. Optional.
114          * @param string  $content Response body. Optional.
115          * @throws \Exception
116          */
117         public static function httpExit($val, $message = '', $content = '')
118         {
119                 Logger::log('http_status_exit ' . $val);
120                 header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $message);
121
122                 echo $content;
123
124                 exit();
125         }
126
127         public static function jsonError($httpCode, $data, $content_type = 'application/json')
128         {
129                 header($_SERVER["SERVER_PROTOCOL"] . ' ' . $httpCode);
130                 self::jsonExit($data, $content_type);
131         }
132
133         /**
134          * Encodes content to json.
135          *
136          * This function encodes an array to json format
137          * and adds an application/json HTTP header to the output.
138          * After finishing the process is getting killed.
139          *
140          * @param mixed   $x The input content.
141          * @param string  $content_type Type of the input (Default: 'application/json').
142          * @param integer $options JSON options
143          */
144         public static function jsonExit($x, $content_type = 'application/json', int $options = 0) {
145                 header("Content-type: $content_type");
146                 echo json_encode($x, $options);
147                 exit();
148         }
149
150         /**
151          * Generates a random string in the UUID format
152          *
153          * @param bool|string $prefix A given prefix (default is empty)
154          * @return string a generated UUID
155          * @throws \Exception
156          */
157         public static function createUUID($prefix = '')
158         {
159                 $guid = System::createGUID(32, $prefix);
160                 return substr($guid, 0, 8) . '-' . substr($guid, 8, 4) . '-' . substr($guid, 12, 4) . '-' . substr($guid, 16, 4) . '-' . substr($guid, 20, 12);
161         }
162
163         /**
164          * Generates a GUID with the given parameters
165          *
166          * @param int         $size   The size of the GUID (default is 16)
167          * @param bool|string $prefix A given prefix (default is empty)
168          * @return string a generated GUID
169          * @throws \Exception
170          */
171         public static function createGUID($size = 16, $prefix = '')
172         {
173                 if (is_bool($prefix) && !$prefix) {
174                         $prefix = '';
175                 } elseif (empty($prefix)) {
176                         $prefix = hash('crc32', DI::baseUrl()->getHostname());
177                 }
178
179                 while (strlen($prefix) < ($size - 13)) {
180                         $prefix .= mt_rand();
181                 }
182
183                 if ($size >= 24) {
184                         $prefix = substr($prefix, 0, $size - 22);
185                         return str_replace('.', '', uniqid($prefix, true));
186                 } else {
187                         $prefix = substr($prefix, 0, max($size - 13, 0));
188                         return uniqid($prefix);
189                 }
190         }
191
192         /**
193          * Returns the current Load of the System
194          *
195          * @return integer
196          */
197         public static function currentLoad()
198         {
199                 if (!function_exists('sys_getloadavg')) {
200                         return false;
201                 }
202
203                 $load_arr = sys_getloadavg();
204
205                 if (!is_array($load_arr)) {
206                         return false;
207                 }
208
209                 return max($load_arr[0], $load_arr[1]);
210         }
211
212         /**
213          * Redirects to an external URL (fully qualified URL)
214          * If you want to route relative to the current Friendica base, use App->internalRedirect()
215          *
216          * @param string $url  The new Location to redirect
217          * @param int    $code The redirection code, which is used (Default is 302)
218          *
219          * @throws InternalServerErrorException If the URL is not fully qualified
220          */
221         public static function externalRedirect($url, $code = 302)
222         {
223                 if (empty(parse_url($url, PHP_URL_SCHEME))) {
224                         throw new InternalServerErrorException("'$url' is not a fully qualified URL, please use App->internalRedirect() instead");
225                 }
226
227                 switch ($code) {
228                         case 302:
229                                 // this is the default code for a REDIRECT
230                                 // We don't need a extra header here
231                                 break;
232                         case 301:
233                                 header('HTTP/1.1 301 Moved Permanently');
234                                 break;
235                         case 307:
236                                 header('HTTP/1.1 307 Temporary Redirect');
237                                 break;
238                 }
239
240                 header("Location: $url");
241                 exit();
242         }
243
244         /**
245          * Returns the system user that is executing the script
246          *
247          * This mostly returns something like "www-data".
248          *
249          * @return string system username
250          */
251         public static function getUser()
252         {
253                 if (!function_exists('posix_getpwuid') || !function_exists('posix_geteuid')) {
254                         return '';
255                 }
256
257                 $processUser = posix_getpwuid(posix_geteuid());
258                 return $processUser['name'];
259         }
260
261         /**
262          * Checks if a given directory is usable for the system
263          *
264          * @param      $directory
265          * @param bool $check_writable
266          *
267          * @return boolean the directory is usable
268          */
269         public static function isDirectoryUsable($directory, $check_writable = true)
270         {
271                 if ($directory == '') {
272                         Logger::log('Directory is empty. This shouldn\'t happen.', Logger::DEBUG);
273                         return false;
274                 }
275
276                 if (!file_exists($directory)) {
277                         Logger::log('Path "' . $directory . '" does not exist for user ' . static::getUser(), Logger::DEBUG);
278                         return false;
279                 }
280
281                 if (is_file($directory)) {
282                         Logger::log('Path "' . $directory . '" is a file for user ' . static::getUser(), Logger::DEBUG);
283                         return false;
284                 }
285
286                 if (!is_dir($directory)) {
287                         Logger::log('Path "' . $directory . '" is not a directory for user ' . static::getUser(), Logger::DEBUG);
288                         return false;
289                 }
290
291                 if ($check_writable && !is_writable($directory)) {
292                         Logger::log('Path "' . $directory . '" is not writable for user ' . static::getUser(), Logger::DEBUG);
293                         return false;
294                 }
295
296                 return true;
297         }
298
299         /**
300          * Exit method used by asynchronous update modules
301          *
302          * @param string $o
303          */
304         public static function htmlUpdateExit($o)
305         {
306                 header("Content-type: text/html");
307                 echo "<!DOCTYPE html><html><body>\r\n";
308                 // We can remove this hack once Internet Explorer recognises HTML5 natively
309                 echo "<section>";
310                 // reportedly some versions of MSIE don't handle tabs in XMLHttpRequest documents very well
311                 echo str_replace("\t", "       ", $o);
312                 echo "</section>";
313                 echo "</body></html>\r\n";
314                 exit();
315         }
316
317         /// @todo Move the following functions from boot.php
318         /*
319         function local_user()
320         function public_contact()
321         function remote_user()
322         function notice($s)
323         function info($s)
324         function is_site_admin()
325         function get_temppath()
326         function get_cachefile($file, $writemode = true)
327         function get_itemcachepath()
328         function get_spoolpath()
329         */
330 }