Subversion Repositories oidplus

Rev

Rev 1086 | Rev 1140 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
115 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
115 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
511 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
730 daniel-mar 26
class OIDplusLogger extends OIDplusBaseClass {
115 daniel-mar 27
 
1116 daniel-mar 28
        /**
29
         * This function splits a mask code containing multiple components
30
         * (delimited by '+' or '/') in single components
31
         * It takes care that '+' and '/' inside brackets won't be used to split the codes
32
         * Also, brackets can be escaped.
33
         * The severity block (optional, must be standing in front of a component)
34
         * is handled too. Inside the severity block, you may only use '/' to split components.
35
         * The severity block will be implicitly repeated from the previous components if a component
36
         * does not feature one.
37
         *
38
         * "[S]AAA(BBB)+CCC(DDD)"   ==> array(
39
         *                                 array(array("S"),"AAA(BBB)"),
40
         *                                 array(array("S"),"CCC(DDD)")
41
         *                              )
42
         * "[S]AAA(B+BB)+CCC(DDD)"  ==> array(
43
         *                                 array(array("S"),"AAA(B+BB)"),
44
         *                                 array(array("S"),"CCC(DDD)")
45
         *                              )
46
         * "[S]AAA(B\)BB)+CCC(DDD)" ==> array(
47
         *                                 array(array("S"),"AAA(B\)BB)"),
48
         *                                 array(array("S"),"CCC(DDD)")
49
         *                              )
50
         * @param string $maskcodes
51
         * @return array|false
52
         */
53
        private static function split_maskcodes(string $maskcodes) {
188 daniel-mar 54
                $out = array();
288 daniel-mar 55
                $sevs = array(); // Note: The severity block will repeat for the next components if not changed explicitly
289 daniel-mar 56
 
188 daniel-mar 57
                $code = '';
288 daniel-mar 58
                $sev = '';
188 daniel-mar 59
                $bracket_level = 0;
288 daniel-mar 60
                $is_escaping = false;
61
                $inside_severity_block = false;
188 daniel-mar 62
                for ($i=0; $i<strlen($maskcodes); $i++) {
63
                        $char = $maskcodes[$i];
289 daniel-mar 64
 
288 daniel-mar 65
                        if ($inside_severity_block) {
66
                                // Severity block (optional)
67
                                // e.g.  [?WARN/!OK] ==> $sevs = array("?WARN", "!OK")
68
                                if ($char == '\\') {
69
                                        if ($is_escaping) {
70
                                                $is_escaping = false;
71
                                                $sev .= $char;
72
                                        } else {
73
                                                $is_escaping = true;
74
                                        }
75
                                }
76
                                else if ($char == '[') {
77
                                        if ($is_escaping) {
78
                                                $is_escaping = false;
79
                                        } else {
80
                                                $bracket_level++;
81
                                        }
82
                                        $sev .= $char;
83
                                }
84
                                else if ($char == ']') {
85
                                        if ($is_escaping) {
86
                                                $is_escaping = false;
87
                                                $sev .= $char;
88
                                        } else {
89
                                                $bracket_level--;
90
                                                if ($bracket_level < 0) return false;
91
                                                if ($bracket_level == 0) {
92
                                                        $inside_severity_block = false;
93
                                                        if ($sev != '') $sevs[] = $sev;
94
                                                        $sev = '';
95
                                                } else {
96
                                                        $sev .= $char;
97
                                                }
98
                                        }
99
                                }
100
                                else if ((($char == '/')) && ($bracket_level == 1)) {
101
                                        if ($is_escaping) {
102
                                                $is_escaping = false;
103
                                                $sev .= $char;
104
                                        } else {
105
                                                if ($sev != '') $sevs[] = $sev;
106
                                                $sev = '';
107
                                        }
108
                                } else {
109
                                        if ($is_escaping) {
110
                                                // This would actually be an error, because we cannot escape this
111
                                                $is_escaping = false;
112
                                                $sev .= '\\' . $char;
113
                                        } else {
114
                                                $sev .= $char;
115
                                        }
116
                                }
188 daniel-mar 117
                        } else {
288 daniel-mar 118
                                // Normal data (after the severity block)
119
                                if (($char == '[') && ($code == '')) {
120
                                        $inside_severity_block = true;
121
                                        $bracket_level++;
122
                                        $sevs = array();
123
                                }
124
                                else if ($char == '\\') {
125
                                        if ($is_escaping) {
126
                                                $is_escaping = false;
127
                                                $code .= $char;
128
                                        } else {
129
                                                $is_escaping = true;
130
                                        }
131
                                }
132
                                else if ($char == '(') {
133
                                        if ($is_escaping) {
134
                                                $is_escaping = false;
135
                                        } else {
136
                                                $bracket_level++;
137
                                        }
138
                                        $code .= $char;
139
                                }
140
                                else if ($char == ')') {
141
                                        if ($is_escaping) {
142
                                                $is_escaping = false;
143
                                        } else {
144
                                                $bracket_level--;
145
                                                if ($bracket_level < 0) return false;
146
                                        }
147
                                        $code .= $char;
148
                                }
149
                                else if ((($char == '+') || ($char == '/')) && ($bracket_level == 0)) {
150
                                        if ($is_escaping) {
151
                                                $is_escaping = false;
152
                                                $code .= $char;
153
                                        } else {
154
                                                if ($code != '') $out[] = array($sevs,$code);
155
                                                $code = '';
156
                                        }
157
                                } else {
158
                                        if ($is_escaping) {
159
                                                // This would actually be an error, because we cannot escape this
160
                                                $is_escaping = false;
161
                                                $code .= '\\' . $char;
162
                                        } else {
163
                                                $code .= $char;
164
                                        }
165
                                }
188 daniel-mar 166
                        }
167
                }
288 daniel-mar 168
                if ($code != '') $out[] = array($sevs,$code);
169
                if ($inside_severity_block) return false;
188 daniel-mar 170
 
171
                return $out;
172
        }
173
 
825 daniel-mar 174
        private static $missing_plugin_queue = array();
175
 
1116 daniel-mar 176
        /**
177
         * @return bool
178
         * @throws OIDplusException
179
         */
180
        public static function reLogMissing(): bool {
825 daniel-mar 181
                while (count(self::$missing_plugin_queue) > 0) {
182
                        $item = self::$missing_plugin_queue[0];
183
                        if (!self::log_internal($item[0], $item[1], false)) return false;
184
                        array_shift(self::$missing_plugin_queue);
185
                }
186
                return true;
187
        }
188
 
1116 daniel-mar 189
        /**
190
         * @param string $maskcodes
191
         * @param string $event
192
         * @return bool
193
         * @throws OIDplusException
194
         */
195
        public static function log(string $maskcodes, string $event): bool {
825 daniel-mar 196
                self::reLogMissing(); // try to re-log failed requests
197
                return self::log_internal($maskcodes, $event, true);
198
        }
199
 
1116 daniel-mar 200
        /**
201
         * @param string $maskcodes
202
         * @param string $event
203
         * @param bool $allow_delayed_log
204
         * @return bool
205
         * @throws OIDplusException
206
         */
207
        private static function log_internal(string $maskcodes, string $event, bool $allow_delayed_log): bool {
289 daniel-mar 208
                $loggerPlugins = OIDplus::getLoggerPlugins();
825 daniel-mar 209
                if (count($loggerPlugins) == 0) {
210
                        // The plugin might not be initialized in OIDplus::init()
211
                        // yet. Remember the log entries for later submission during
212
                        // OIDplus::init();
213
                        if ($allow_delayed_log) self::$missing_plugin_queue[] = array($maskcodes, $event);
214
                        return false;
215
                }
289 daniel-mar 216
 
288 daniel-mar 217
                // What is a mask code?
218
                // A mask code gives information about the log event:
219
                // 1. The severity (info, warning, error)
220
                // 2. In which logbook(s) the event shall be placed
221
                // Example:
222
                // The event would be:
223
                // "Person 'X' moves from house 'A' to house 'B'"
224
                // This event would affect the person X and the two houses,
225
                // so, instead of logging into 3 logbooks separately,
226
                // you would create a mask code that tells the system
227
                // to put the message into the logbooks of person X,
1116 daniel-mar 228
                // house A, and house B.
115 daniel-mar 229
 
230
                $users = array();
231
                $objects = array();
232
 
288 daniel-mar 233
                // A mask code with multiple components is split into single codes
234
                // using '+' or '/', e.g. "OID(x)+RA(x)" would be split to "OID(x)" and "RA(x)"
235
                // which would result in the message being placed in the logbook of OID x,
236
                // and the logbook of the RA owning OID x.
188 daniel-mar 237
                $maskcodes_ary = self::split_maskcodes($maskcodes);
238
                if ($maskcodes_ary === false) {
360 daniel-mar 239
                        throw new OIDplusException(_L('Invalid maskcode "%1" (failed to split)',$maskcodes));
188 daniel-mar 240
                }
288 daniel-mar 241
                foreach ($maskcodes_ary as list($sevs,$maskcode)) {
242
                        // At the beginning of each mask code, you can define a severity.
243
                        // If you have a mask code with multiple components, you don't have to place the
244
                        // severity for each component. You can just leave it at the beginning.
245
                        // e.g. "[WARN]OID(x)+RA(x)" is equal to "[WARN]OID(x)+[WARN]RA(x)"
246
                        // You can also put different severities for the components:
247
                        // e.g. "[INFO]OID(x)+[WARN]RA(x)" would be a info for the OID, but a warning for the RA.
248
                        // If you want to make the severity dependent on wheather the user is logged in or not,
249
                        // prepend "?" or "!" and use '/' as delimiter
250
                        // Example: "[?WARN/!OK]RA(x)" means: If RA is not logged in, it is a warning; if it is logged in, it is an success
251
                        $severity = 0; // default severity = none
289 daniel-mar 252
                        $severity_online = 0;
288 daniel-mar 253
                        foreach ($sevs as $sev) {
254
                                switch (strtoupper($sev)) {
255
                                        // [OK]   = Success
256
                                        //          Numeric value: 1
257
                                        //          Rule of thumb: YOU have done something and it was successful
258
                                        case '?OK':
259
                                                $severity_online = 1;
260
                                                break;
261
                                        case '!OK':
262
                                        case  'OK':
263
                                                $severity = 1;
264
                                                break;
265
                                        // [INFO] = Informational
266
                                        //          Numeric value: 2
267
                                        //          Rule of thumb: Someone else has done something (that affects you) and it was successful
268
                                        case '?INFO':
269
                                                $severity_online = 2;
270
                                                break;
271
                                        case '!INFO':
272
                                        case  'INFO':
273
                                                $severity = 2;
274
                                                break;
275
                                        // [WARN] = Warning
276
                                        //          Numeric value: 3
277
                                        //          Rule of thumb: Something happened (probably someone did something) and it affects you
278
                                        case '?WARN':
279
                                                $severity_online = 3;
280
                                                break;
281
                                        case '!WARN':
282
                                        case  'WARN':
283
                                                $severity = 3;
284
                                                break;
285
                                        // [ERR]  = Error
286
                                        //          Numeric value: 4
287
                                        //          Rule of thumb: Something failed (probably someone did something) and it affects you
288
                                        case '?ERR':
289
                                                $severity_online = 4;
290
                                                break;
291
                                        case '!ERR':
292
                                        case  'ERR':
293
                                                $severity = 4;
294
                                                break;
295
                                        // [CRIT] = Critical
296
                                        //          Numeric value: 5
297
                                        //          Rule of thumb: Something happened (probably someone did something) which is not an error,
298
                                        //          but some critical situation (e.g. hardware failure), and it affects you
299
                                        case '?CRIT':
300
                                                $severity_online = 5;
301
                                                break;
302
                                        case '!CRIT':
303
                                        case  'CRIT':
304
                                                $severity = 5;
305
                                                break;
306
                                        default:
360 daniel-mar 307
                                                throw new OIDplusException(_L('Invalid maskcode "%1" (Unknown severity "%2")',$maskcodes,$sev));
288 daniel-mar 308
                                }
309
                        }
289 daniel-mar 310
 
115 daniel-mar 311
                        // OID(x)       Save log entry into the logbook of: Object "x"
419 daniel-mar 312
                        $m = array();
115 daniel-mar 313
                        if (preg_match('@^OID\((.+)\)$@ismU', $maskcode, $m)) {
314
                                $object_id = $m[1];
288 daniel-mar 315
                                $objects[] = array($severity, $object_id);
360 daniel-mar 316
                                if ($object_id == '') throw new OIDplusException(_L('OID logger mask requires OID'));
115 daniel-mar 317
                        }
318
 
288 daniel-mar 319
                        // SUPOID(x)    Save log entry into the logbook of: Parent of object "x"
320
                        else if (preg_match('@^SUPOID\((.+)\)$@ismU', $maskcode, $m)) {
321
                                $object_id         = $m[1];
360 daniel-mar 322
                                if ($object_id == '') throw new OIDplusException(_L('SUPOID logger mask requires OID'));
288 daniel-mar 323
                                $obj = OIDplusObject::parse($object_id);
324
                                if ($obj) {
419 daniel-mar 325
                                        if ($objParent = $obj->getParent()) {
326
                                                $parent = $objParent->nodeId();
327
                                                $objects[] = array($severity, $parent);
328
                                        } else {
329
                                                //throw new OIDplusException(_L('%1 has no parent',$object_id));
330
                                        }
288 daniel-mar 331
                                } else {
360 daniel-mar 332
                                        throw new OIDplusException(_L('SUPOID logger mask: Invalid object %1',$object_id));
288 daniel-mar 333
                                }
334
                        }
335
 
115 daniel-mar 336
                        // OIDRA(x)?    Save log entry into the logbook of: Logged in RA of object "x"
288 daniel-mar 337
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 338
                        else if (preg_match('@^OIDRA\((.+)\)([\?\!])$@ismU', $maskcode, $m)) {
339
                                $object_id         = $m[1];
116 daniel-mar 340
                                $ra_need_login     = $m[2] == '?';
360 daniel-mar 341
                                if ($object_id == '') throw new OIDplusException(_L('OIDRA logger mask requires OID'));
115 daniel-mar 342
                                $obj = OIDplusObject::parse($object_id);
116 daniel-mar 343
                                if ($obj) {
344
                                        if ($ra_need_login) {
345
                                                foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
288 daniel-mar 346
                                                        if ($obj->userHasWriteRights($ra)) $users[] = array($severity_online, $ra->raEmail());
116 daniel-mar 347
                                                }
348
                                        } else {
288 daniel-mar 349
                                                // $users[] = array($severity, $obj->getRa()->raEmail());
116 daniel-mar 350
                                                foreach (OIDplusRA::getAllRAs() as $ra) {
288 daniel-mar 351
                                                        if ($obj->userHasWriteRights($ra)) $users[] = array($severity, $ra->raEmail());
116 daniel-mar 352
                                                }
115 daniel-mar 353
                                        }
288 daniel-mar 354
                                } else {
360 daniel-mar 355
                                        throw new OIDplusException(_L('OIDRA logger mask: Invalid object "%1"',$object_id));
115 daniel-mar 356
                                }
357
                        }
358
 
359
                        // SUPOIDRA(x)? Save log entry into the logbook of: Logged in RA that owns the superior object of "x"
288 daniel-mar 360
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 361
                        else if (preg_match('@^SUPOIDRA\((.+)\)([\?\!])$@ismU', $maskcode, $m)) {
362
                                $object_id         = $m[1];
116 daniel-mar 363
                                $ra_need_login     = $m[2] == '?';
360 daniel-mar 364
                                if ($object_id == '') throw new OIDplusException(_L('SUPOIDRA logger mask requires OID'));
115 daniel-mar 365
                                $obj = OIDplusObject::parse($object_id);
116 daniel-mar 366
                                if ($obj) {
367
                                        if ($ra_need_login) {
368
                                                foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
288 daniel-mar 369
                                                        if ($obj->userHasParentalWriteRights($ra)) $users[] = array($severity_online, $ra->raEmail());
116 daniel-mar 370
                                                }
371
                                        } else {
419 daniel-mar 372
                                                if ($objParent = $obj->getParent()) {
373
                                                        // $users[] = array($severity, $objParent->getRa()->raEmail());
374
                                                        foreach (OIDplusRA::getAllRAs() as $ra) {
375
                                                                if ($obj->userHasParentalWriteRights($ra)) $users[] = array($severity, $ra->raEmail());
376
                                                        }
377
                                                } else {
378
                                                        //throw new OIDplusException(_L('%1 has no parent, therefore also no parent RA',$object_id));
116 daniel-mar 379
                                                }
115 daniel-mar 380
                                        }
288 daniel-mar 381
                                } else {
360 daniel-mar 382
                                        throw new OIDplusException(_L('SUPOIDRA logger mask: Invalid object "%1"',$object_id));
115 daniel-mar 383
                                }
384
                        }
385
 
386
                        // RA(x)?       Save log entry into the logbook of: Logged in RA "x"
288 daniel-mar 387
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
388
                        else if (preg_match('@^RA\((.*)\)([\?\!])$@ismU', $maskcode, $m)) {
115 daniel-mar 389
                                $ra_email          = $m[1];
116 daniel-mar 390
                                $ra_need_login     = $m[2] == '?';
288 daniel-mar 391
                                if (!empty($ra_email)) {
392
                                        if ($ra_need_login && OIDplus::authUtils()->isRaLoggedIn($ra_email)) {
393
                                                $users[] = array($severity_online, $ra_email);
394
                                        } else if (!$ra_need_login) {
395
                                                $users[] = array($severity, $ra_email);
396
                                        }
115 daniel-mar 397
                                }
398
                        }
399
 
400
                        // A?   Save log entry into the logbook of: A logged in admin
288 daniel-mar 401
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 402
                        else if (preg_match('@^A([\?\!])$@ismU', $maskcode, $m)) {
116 daniel-mar 403
                                $admin_need_login = $m[1] == '?';
115 daniel-mar 404
                                if ($admin_need_login && OIDplus::authUtils()->isAdminLoggedIn()) {
288 daniel-mar 405
                                        $users[] = array($severity_online, 'admin');
115 daniel-mar 406
                                } else if (!$admin_need_login) {
288 daniel-mar 407
                                        $users[] = array($severity, 'admin');
115 daniel-mar 408
                                }
409
                        }
410
 
411
                        // Unexpected
412
                        else {
360 daniel-mar 413
                                throw new OIDplusException(_L('Unexpected logger component "%1" in mask code "%2"',$maskcode,$maskcodes));
115 daniel-mar 414
                        }
116 daniel-mar 415
                }
115 daniel-mar 416
 
117 daniel-mar 417
                // Now write the log message
418
 
289 daniel-mar 419
                $result = false;
117 daniel-mar 420
 
289 daniel-mar 421
                foreach ($loggerPlugins as $plugin) {
422
                        $reason = '';
423
                        if ($plugin->available($reason)) {
424
                                $result |= $plugin->log($event, $users, $objects);
425
                        }
117 daniel-mar 426
                }
427
 
289 daniel-mar 428
                return $result;
115 daniel-mar 429
        }
730 daniel-mar 430
}