Test Failed
Push — master ( 60b361...3a07f6 )
by Stefan
10:13
created

Entity   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 34
eloc 117
dl 0
loc 281
rs 9.68
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 2 1
A __construct() 0 21 2
B createTemporaryDirectory() 0 31 7
A getAttributeValue() 0 5 3
A randomString() 0 12 3
A rrmdir() 0 9 3
A uuid() 0 14 2
B determineOwnCatalogue() 0 33 8
A intoThePotatoes() 0 10 2
A outOfThePotatoes() 0 6 2
A potatoStack() 0 4 1
1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * This file contains Federation, IdP and Profile classes.
25
 * These should be split into separate files later.
26
 *
27
 * @package Developer
28
 */
29
/**
30
 * 
31
 */
32
33
namespace core\common;
34
35
use Exception;
36
37
/**
38
 * This class represents an Entity in its widest sense. Every entity can log
39
 * and query/change the language settings where needed.
40
 *
41
 * @author Stefan Winter <[email protected]>
42
 * @author Tomasz Wolniewicz <[email protected]>
43
 *
44
 * @license see LICENSE file in root directory
45
 *
46
 * @package Developer
47
 */
48
abstract class Entity {
49
50
    const L_OK = 0;
51
    const L_REMARK = 4;
52
    const L_WARN = 32;
53
    const L_ERROR = 256;
54
55
    /**
56
     * We occasionally log stuff (debug/audit). Have an initialised Logging
57
     * instance nearby is sure helpful.
58
     * 
59
     * @var Logging
60
     */
61
    protected $loggerInstance;
62
63
    /**
64
     * access to language settings to be able to switch textDomain
65
     * 
66
     * @var Language
67
     */
68
    public $languageInstance;
69
70
    /**
71
     * keep internal track of the gettext catalogue that was used outside the
72
     * class call
73
     * 
74
     * @var array
75
     */
76
    protected static $gettextCatalogue;
77
78
    /**
79
     * the custom displayable variant of the term 'federation'
80
     * @var string
81
     */
82
    public static $nomenclature_fed;
83
84
    /**
85
     * the custom displayable variant of the term 'institution'
86
     * @var string
87
     */
88
    public static $nomenclature_inst;
89
90
    /**
91
     * initialise the entity.
92
     * 
93
     * Logs the start of lifetime of the entity to the debug log on levels 3 and higher.
94
     */
95
    public function __construct() {
96
        $this->loggerInstance = new Logging();
97
        $this->loggerInstance->debug(3, "--- BEGIN constructing class " . get_class($this) . " .\n");
98
        $this->languageInstance = new Language();
99
        Entity::intoThePotatoes();
100
        // some config elements are displayable. We need some dummies to 
101
        // translate the common values for them. If a deployment chooses a 
102
        // different wording, no translation, sorry
103
104
        $dummy_NRO = _("National Roaming Operator");
105
        $dummy_inst1 = _("identity provider");
106
        $dummy_inst2 = _("organisation");
107
        $dummy_inst3 = _("Identity Provider");
108
        // and do something useless with the strings so that there's no "unused" complaint
109
        if (strlen($dummy_NRO . $dummy_inst1 . $dummy_inst2 . $dummy_inst3) < 0) {
110
            throw new \Exception("Strings are usually not shorter than 0 characters. We've encountered a string blackhole.");
111
        }
112
        Entity::$nomenclature_fed = _(CONFIG_CONFASSISTANT['CONSORTIUM']['nomenclature_federation']);
113
        Entity::$nomenclature_inst = _(CONFIG_CONFASSISTANT['CONSORTIUM']['nomenclature_institution']);
114
115
        Entity::outOfThePotatoes();
116
    }
117
118
    /**
119
     * destroys the entity.
120
     * 
121
     * Logs the end of lifetime of the entity to the debug log on level 5.
122
     */
123
    public function __destruct() {
124
        (new Logging())->debug(5, "--- KILL Destructing class " . get_class($this) . " .\n");
125
    }
126
127
    /**
128
     * This is a helper fuction to retrieve a value from two-dimensional arrays
129
     * The function tests if the value for the first indes is defined and then
130
     * the same with the second and finally returns the value
131
     * if something on the way is not defined, NULL is returned
132
     * 
133
     * @param array      $attributeArray the array to search in
134
     * @param string|int $index1         first-level index to check
135
     * @param string|int $index2         second-level index to check
136
     * @return mixed
137
     */
138
    public static function getAttributeValue($attributeArray, $index1, $index2) {
139
        if (isset($attributeArray[$index1]) && isset($attributeArray[$index1][$index2])) {
140
            return($attributeArray[$index1][$index2]);
141
        } else {
142
            return(NULL);
143
        }
144
    }
145
146
    /**
147
     * create a temporary directory and return the location
148
     * @param string  $purpose     one of 'installer', 'logo', 'test' defined the purpose of the directory
149
     * @param boolean $failIsFatal decides if a creation failure should cause an error; defaults to true
150
     * @return array the tuple of: base path, absolute path for directory, directory name
151
     */
152
    public static function createTemporaryDirectory($purpose = 'installer', $failIsFatal = 1) {
153
        $loggerInstance = new Logging();
154
        $name = md5(time() . rand());
155
        $path = ROOT;
156
        switch ($purpose) {
157
            case 'silverbullet':
158
                $path .= '/var/silverbullet';
159
                break;
160
            case 'installer':
161
                $path .= '/var/installer_cache';
162
                break;
163
            case 'logo':
164
                $path .= '/web/downloads/logos';
165
                break;
166
            case 'test':
167
                $path .= '/var/tmp';
168
                break;
169
            default:
170
                throw new Exception("unable to create temporary directory due to unknown purpose: $purpose\n");
171
        }
172
        $tmpDir = $path . '/' . $name;
173
        $loggerInstance->debug(4, "temp dir: $purpose : $tmpDir\n");
174
        if (!mkdir($tmpDir, 0700, true)) {
175
            if ($failIsFatal) {
176
                throw new Exception("unable to create temporary directory: $tmpDir\n");
177
            }
178
            $loggerInstance->debug(4, "Directory creation failed for $tmpDir\n");
179
            return ['base' => $path, 'dir' => '', $name => ''];
180
        }
181
        $loggerInstance->debug(4, "Directory created: $tmpDir\n");
182
        return ['base' => $path, 'dir' => $tmpDir, 'name' => $name];
183
    }
184
185
    /**
186
     * this direcory delete function has been copied from PHP documentation
187
     * 
188
     * @param string $dir name of the directory to delete
189
     * @return void
190
     */
191
    public static function rrmdir($dir) {
192
        foreach (glob($dir . '/*') as $file) {
193
            if (is_dir($file)) {
194
                Entity::rrmdir($file);
195
            } else {
196
                unlink($file);
197
            }
198
        }
199
        rmdir($dir);
200
    }
201
202
    /**
203
     * generates a UUID, for the devices which identify file contents by UUID
204
     *
205
     * @param string $prefix              an extra prefix to set before the UUID
206
     * @param mixed  $deterministicSource don't generate a random UUID, base it deterministically on the provided input
207
     * @return string UUID (possibly prefixed)
208
     */
209
    public static function uuid($prefix = '', $deterministicSource = NULL) {
210
        if ($deterministicSource === NULL) {
211
            $chars = md5(uniqid(mt_rand(), true));
212
        } else {
213
            $chars = md5($deterministicSource);
214
        }
215
        // these substr() are guaranteed to yield actual string data, as the
216
        // base string is an MD5 hash - has sufficient length
217
        $uuid = /** @scrutinizer ignore-type */ substr($chars, 0, 8) . '-';
218
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 8, 4) . '-';
219
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 12, 4) . '-';
220
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 16, 4) . '-';
221
        $uuid .= /** @scrutinizer ignore-type */ substr($chars, 20, 12);
222
        return $prefix . $uuid;
223
    }
224
225
    /**
226
     * produces a random string
227
     * @param int    $length   the length of the string to produce
228
     * @param string $keyspace the pool of characters to use for producing the string
229
     * @return string
230
     * @throws Exception
231
     */
232
    public static function randomString(
233
            $length, $keyspace = '23456789abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
234
    ) {
235
        $str = '';
236
        $max = strlen($keyspace) - 1;
237
        if ($max < 1) {
238
            throw new Exception('$keyspace must be at least two characters long');
239
        }
240
        for ($i = 0; $i < $length; ++$i) {
241
            $str .= $keyspace[random_int(0, $max)];
242
        }
243
        return $str;
244
    }
245
246
    /**
247
     * Finds out which gettext catalogue has the translations for the caller
248
     * 
249
     * @return string the catalogue
250
     */
251
    private static function determineOwnCatalogue() {
252
        $loggerInstance = new Logging();
253
        $trace = debug_backtrace();
254
        $caller = [];
255
        // find the first caller in the stack trace which is NOT "Entity" itself
256
        // this means walking back from the end of the trace to the penultimate
257
        // index before something with "Entity" comes in
258
        for ($i = count($trace) - 1; $i--; $i > 0) {
259
            if (preg_match('/Entity/', $trace[$i - 1]['class'])) {
260
                $caller = $trace[$i];
261
                break;
262
            }
263
        }
264
        // if called from a class, guess based on the class name; 
265
        // otherwise, on the filename relative to ROOT
266
        $myName = $caller['class'] ?? substr($caller['file'], strlen(ROOT));
267
        $loggerInstance->debug(1,$caller);
268
        $loggerInstance->debug(1,"\nFOUND ".$myName."\n");
269
        if (preg_match("/diag/", $myName) == 1) {
270
            $ret = "diagnostics";
271
        } elseif (preg_match("/core/", $myName) == 1) {
272
            $ret = "core";
273
        } elseif (preg_match("/common/", $myName) == 1) {
274
            $ret = "core";
275
        } elseif (preg_match("/devices/", $myName) == 1) {
276
            $ret = "devices";
277
        } elseif (preg_match("/admin/", $myName) == 1) {
278
            $ret = "web_admin";
279
        } else {
280
            $ret = "web_user";
281
        }
282
        $loggerInstance->debug(1,"\nRETURNING ".$ret."\n");
283
        return $ret;
284
    }
285
286
    /**
287
     * sets the language catalogue to one matching the gettext segmentation of
288
     * source files. Also memorises the previous catalogue so that it can be
289
     * restored later on.
290
     * 
291
     * @param string $catalogue the catalogue to select, overrides detection
292
     * @return void
293
     */
294
    public static function intoThePotatoes($catalogue = NULL) {
295
        // array_push, without the function call overhead
296
        Entity::$gettextCatalogue[] = textdomain(NULL);
297
        if ($catalogue === NULL) {
298
            $theCatalogue = Entity::determineOwnCatalogue();
299
            textdomain($theCatalogue);
300
            bindtextdomain($theCatalogue, ROOT . "/translation/");
301
        } else {
302
            textdomain($catalogue);
303
            bindtextdomain($catalogue, ROOT . "/translation/");
304
        }
305
    }
306
307
    /**
308
     * restores the previous language catalogue.
309
     * 
310
     * @return void
311
     */
312
    public static function outOfThePotatoes() {
313
        $restoreCatalogue = array_pop(Entity::$gettextCatalogue);
314
        if ($restoreCatalogue === NULL) {
315
            throw new Exception("Unable to restore previous catalogue - outOfThePotatoes called too often?!");
316
        }
317
        textdomain($restoreCatalogue);
318
    }
319
320
    /**
321
     * for debugging only
322
     * 
323
     * @return array the stack of language contexts
324
     */
325
    public static function potatoStack() {
326
        $debugArray = Entity::$gettextCatalogue;
327
        array_push($debugArray, Entity::determineOwnCatalogue());
328
        return $debugArray;
329
    }
330
}
331