Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set noai expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
3
/**
4
 * System_Daemon turns PHP-CLI scripts into daemons.
5
 *
6
 * PHP version 5
7
 *
8
 * @category  System
9
 * @package   System_Daemon
10
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
11
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
12
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
13
 * @version   SVN: Release: $Id: Options.php 186 2010-05-10 13:10:04Z tiefland $
14
 * @link      http://trac.plutonia.nl/projects/system_daemon
15
 */
16
 
17
/**
18
 * Mechanism for validating, getting and setting a predefined set of options.
19
 *
20
 * @category  System
21
 * @package   System_Daemon
22
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
23
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
24
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
25
 * @version   SVN: Release: $Id: Options.php 186 2010-05-10 13:10:04Z tiefland $
26
 * @link      http://trac.plutonia.nl/projects/system_daemon
27
 *
28
 */
29
class System_Daemon_Options
30
{
31
    /**
32
     * Keep track of active state for all Options
33
     *
34
     * @var array
35
     */
36
    protected $_options = array();
37
 
38
    /**
39
     * Definitions for all Options
40
     *
41
     * @var array
42
     */
43
    protected $_definitions = array();
44
 
45
    /**
46
     * Wether all the options have been initialized
47
     *
48
     * @var boolean
49
     */
50
    protected $_isInitialized = false;
51
 
52
    /**
53
     * Holds errors
54
     *
55
     * @var array
56
     */
57
    public $errors = array();
58
 
59
 
60
    /**
61
     * Constructor
62
     *
63
     * @param array $definitions The predefined option definitions
64
     */
65
    public function __construct($definitions)
66
    {
67
        if (!is_array($definitions) || !count($definitions)) {
68
            return false;
69
        }
70
 
71
        $this->_definitions = $definitions;
72
    }
73
 
74
    /**
75
     * Retrieves any option found in $_definitions
76
     *
77
     * @param string $name Name of the Option
78
     *
79
     * @return boolean
80
     */
81
    public function getOption($name)
82
    {
83
        if (!$this->isInitialized()) {
84
            $this->init(true);
85
        }
86
 
87
 
88
        if (!isset($this->_options[$name])) {
89
            return null;
90
        }
91
        return $this->_options[$name];
92
    }
93
 
94
    /**
95
     * Gets an array of options found in $_definitions
96
     *
97
     * @return array
98
     */
99
    public function getOptions()
100
    {
101
        return $this->_options;
102
    }
103
 
104
    /**
105
     * Sets any option found in $_definitions
106
     *
107
     * @param string $name  Name of the Option
108
     * @param mixed  $value Value of the Option
109
     *
110
     * @return boolean
111
     */
112
    public function setOption($name, $value)
113
    {
114
        $success = true;
115
        // Not validated?
116
        if (!$this->_validate($name, $value, $reason)) {
117
            // Default not used or failed as well!
118
            $this->errors[] = "Option ".$name." invalid: ".$reason;
119
            $success        = false;
120
        }
121
 
122
        $this->_options[$name] = $value;
123
        return $success;
124
    }
125
 
126
    /**
127
     * Sets an array of options found in $_definitions
128
     *
129
     * @param array $use_options Array with Options
130
     *
131
     * @return boolean
132
     */
133
    public function setOptions($use_options)
134
    {
135
        $success = true;
136
        foreach ($use_options as $name=>$value) {
137
            if (!$this->setOption($name, $value)) {
138
                $success = false;
139
            }
140
        }
141
        return $success;
142
    }
143
 
144
    /**
145
     * Wether options are initialized
146
     *
147
     * @return boolean
148
     */
149
    public function isInitialized()
150
    {
151
        return $this->_isInitialized;
152
    }
153
 
154
    /**
155
     * Checks if all the required options are set & met.
156
     * Initializes, sanitizes & defaults unset variables
157
     *
158
     * @param boolean $premature Whether to do a premature option init
159
     *
160
     * @return mixed integer or boolean
161
     */
162
    public function init($premature=false)
163
    {
164
        // If already initialized, skip
165
        if (!$premature && $this->isInitialized()) {
166
            return true;
167
        }
168
 
169
        $options_met = 0;
170
 
171
        foreach ($this->_definitions as $name=>$definition) {
172
            // Required options remain
173
            if (!isset($this->_options[$name])) {
174
                if (!$this->_setDefault($name)
175
                    && !$premature
176
                    && @$definition['required']
177
                ) {
178
                    $this->errors[] = 'Required option: '.$name.
179
                        ' not set. No default value available either.';
180
                    return false;
181
                }
182
            }
183
 
184
            $options_met++;
185
        }
186
 
187
        if (!$premature) {
188
            $this->_isInitialized = true;
189
        }
190
 
191
        return $options_met;
192
 
193
    }
194
 
195
 
196
 
197
    /**
198
     * Validates any option found in $_definitions
199
     *
200
     * @param string $name    Name of the Option
201
     * @param mixed  $value   Value of the Option
202
     * @param string &$reason Why something does not validate
203
     *
204
     * @return boolean
205
     */
206
    protected function _validate($name, $value, &$reason="")
207
    {
208
        $reason = false;
209
 
210
        if (!$reason && !isset($this->_definitions[$name])) {
211
            $reason = "Option ".$name." not found in definitions";
212
        }
213
 
214
        $definition = $this->_definitions[$name];
215
 
216
        if (!$reason && !isset($definition["type"])) {
217
            $reason = "Option ".$name.":type not found in definitions";
218
        }
219
 
220
        // Compile array of allowd main & subtypes
221
        $_allowedTypes = $this->_allowedTypes($definition["type"]);
222
 
223
        // Loop over main & subtypes to detect matching format
224
        if (!$reason) {
225
            $type_valid = false;
226
            foreach ($_allowedTypes as $type_a=>$sub_types) {
227
                foreach ($sub_types as $type_b) {
228
 
229
                    // Determine range based on subtype
230
                    // Range is used to contain an integer or strlen
231
                    // between min-max
232
                    $parts = explode("-", $type_b);
233
                    $from  = $to = false;
234
                    if (count($parts) == 2 ) {
235
                        $from   = $parts[0];
236
                        $to     = $parts[1];
237
                        $type_b = "range";
238
                    }
239
 
240
                    switch ($type_a) {
241
                    case "boolean":
242
                        $type_valid = is_bool($value);
243
                        break;
244
                    case "object":
245
                        $type_valid = is_object($value) || is_resource($value);
246
                        break;
247
                    case "string":
248
                        switch ($type_b) {
249
                        case "email":
250
                            $exp  = "/^[a-z0-9]+([._-][a-z0-9]+)*@([a-z0-9]+";
251
                            $exp .= "([._-][a-z0-9]+))+$/";
252
                            if (preg_match($exp, $value)) {
253
                                $type_valid = true;
254
                            }
255
                            break;
256
                        case "unix":
257
                            if ($this->strIsUnix($value)) {
258
                                $type_valid = true;
259
                            }
260
                            break;
261
                        case "unix_filepath":
262
                            if ($this->strIsUnixFile($value)) {
263
                                $type_valid = true;
264
                            }
265
                            break;
266
                        case "existing_dirpath":
267
                            if (is_dir($value)) {
268
                                $type_valid = true;
269
                            }
270
                            break;
271
                        case "existing_filepath":
272
                            if (is_file($value)) {
273
                                $type_valid = true;
274
                            }
275
                            break;
276
                        case "creatable_filepath":
277
                            if (is_dir(dirname($value))
278
                                && is_writable(dirname($value))
279
                            ) {
280
                                $type_valid = true;
281
                            }
282
                            break;
283
                        case "normal":
284
                        default:
285
                            // String?
286
                            if (!is_resource($value)
287
                                && !is_array($value)
288
                                && !is_object($value)
289
                            ) {
290
                                // Range?
291
                                if ($from === false && $to === false) {
292
                                    $type_valid = true;
293
                                } else {
294
                                    // Enfore range as well
295
                                    if (strlen($value) >= $from
296
                                        && strlen($value) <= $to
297
                                    ) {
298
                                        $type_valid = true;
299
                                    }
300
                                }
301
                            }
302
                            break;
303
                        }
304
                        break;
305
                    case "number":
306
                        switch ($type_b) {
307
                        default:
308
                        case "normal":
309
                            // Numeric?
310
                            if (is_numeric($value)) {
311
                                // Range ?
312
                                if ($from === false && $to === false) {
313
                                    $type_valid = true;
314
                                } else {
315
                                    // Enfore range as well
316
                                    if ($value >= $from && $value <= $to) {
317
                                        $type_valid = true;
318
                                    }
319
                                }
320
                            }
321
                            break;
322
                        }
323
                        break;
324
                    default:
325
                        $this->errors[] =  "Type ".
326
                            $type_a." not defined";
327
                        break;
328
                    }
329
                }
330
            }
331
        }
332
 
333
        if (!$type_valid) {
334
            $reason = "Option ".$name." does not match type: ".
335
                $definition["type"]."";
336
        }
337
 
338
        if ($reason !== false) {
339
            $this->errors[] = $reason;
340
            return false;
341
        }
342
 
343
        return true;
344
    }
345
 
346
    /**
347
     * Sets any option found in $_definitions to its default value
348
     *
349
     * @param string $name Name of the Option
350
     *
351
     * @return boolean
352
     */
353
    protected function _setDefault($name)
354
    {
355
        if (!isset($this->_definitions[$name])) {
356
            return false;
357
        }
358
        $definition = $this->_definitions[$name];
359
 
360
        if (!isset($definition["type"])) {
361
            return false;
362
        }
363
        if (!isset($definition["default"])) {
364
            return false;
365
        }
366
 
367
        // Compile array of allowd main & subtypes
368
        $_allowedTypes = $this->_allowedTypes($definition["type"]);
369
 
370
        $type  = $definition["type"];
371
        $value = $definition["default"];
372
 
373
        if (isset($_allowedTypes["string"]) && !is_bool($value)) {
374
            // Replace variables
375
            $value = preg_replace_callback(
376
                '/\{([^\{\}]+)\}/is',
377
                array($this, "replaceVars"),
378
                $value
379
            );
380
 
381
            // Replace functions
382
            $value = preg_replace_callback(
383
                '/\@([\w_]+)\(([^\)]+)\)/is',
384
                array($this, "replaceFuncs"),
385
                $value
386
            );
387
        }
388
 
389
        $this->_options[$name] = $value;
390
        return true;
391
    }
392
 
393
    /**
394
     * Callback function to replace variables in defaults
395
     *
396
     * @param array $matches Matched functions
397
     *
398
     * @return string
399
     */
400
    public function replaceVars($matches)
401
    {
402
        // Init
403
        $allowedVars = array(
404
            "SERVER.SCRIPT_NAME",
405
            "OPTIONS.*",
406
        );
407
        $filterVars  = array(
408
            "SERVER.SCRIPT_NAME" => array(
409
                "realpath",
410
            ),
411
        );
412
 
413
        $fullmatch          = array_shift($matches);
414
        $fullvar            = array_shift($matches);
415
 
416
        if (false === strpos($fullvar, '.')) {
417
            $source = 'OPTIONS';
418
            $var    = $fullvar;
419
        } else {
420
            $parts              = explode(".", $fullvar);
421
            list($source, $var) = $parts;
422
        }
423
        $var_use            = false;
424
        $var_key            = $source.".".$var;
425
 
426
        // Allowed
427
        if (!in_array($var_key, $allowedVars)
428
            && !in_array($source.".*", $allowedVars)
429
        ) {
430
            return "FORBIDDEN_VAR_".$var_key;
431
        }
432
 
433
        // Mapping of textual sources to real sources
434
        if ($source == "SERVER") {
435
            $source_use = &$_SERVER;
436
        } elseif ($source == "OPTIONS") {
437
            $source_use = &$this->_options;
438
        } else {
439
            $source_use = false;
440
        }
441
 
442
        // Exists?
443
        if ($source_use === false) {
444
            return "UNUSABLE_VARSOURCE_".$source;
445
        }
446
        if (!isset($source_use[$var])) {
447
            return "NONEXISTING_VAR_".$var_key;
448
        }
449
 
450
        $var_use = $source_use[$var];
451
 
452
        // Filtering
453
        if (isset($filterVars[$var_key]) && is_array($filterVars[$var_key])) {
454
            foreach ($filterVars[$var_key] as $filter_function) {
455
                if (!function_exists($filter_function)) {
456
                    return "NONEXISTING_FILTER_".$filter_function;
457
                }
458
                $var_use = call_user_func($filter_function, $var_use);
459
            }
460
        }
461
 
462
        return $var_use;
463
    }
464
 
465
    /**
466
     * Callback function to replace function calls in defaults
467
     *
468
     * @param array $matches Matched functions
469
     *
470
     * @return string
471
     */
472
    public function replaceFuncs($matches)
473
    {
474
        $allowedFunctions = array(
475
            "basename",
476
            "dirname",
477
        );
478
 
479
        $fullmatch = array_shift($matches);
480
        $function  = array_shift($matches);
481
        $arguments = $matches;
482
 
483
        if (!in_array($function, $allowedFunctions)) {
484
            return "FORBIDDEN_FUNCTION_".$function;
485
        }
486
 
487
        if (!function_exists($function)) {
488
            return "NONEXISTING_FUNCTION_".$function;
489
        }
490
 
491
        return call_user_func_array($function, $arguments);
492
    }
493
 
494
    /**
495
     * Compile array of allowed types
496
     *
497
     * @param string $str String that contains allowed type information
498
     *
499
     * @return array
500
     */
501
    protected function _allowedTypes($str)
502
    {
503
        $allowed_types = array();
504
        $raw_types     = explode("|", $str);
505
        foreach ($raw_types as $raw_type) {
506
            $raw_subtypes = explode("/", $raw_type);
507
            $type_a       = array_shift($raw_subtypes);
508
            if (!count($raw_subtypes)) {
509
                $raw_subtypes = array("normal");
510
            }
511
            $allowed_types[$type_a] = $raw_subtypes;
512
        }
513
        return $allowed_types;
514
    }
515
 
516
 
517
    /**
518
     * Check if a string has a unix proof file-format (stripped spaces,
519
     * special chars, etc)
520
     *
521
     * @param string $str What string to test for unix compliance
522
     *
523
     * @return boolean
524
     */
525
    protected function strIsUnixFile( $str )
526
    {
527
        return preg_match('/^[a-z0-9_\.\/\-]+$/', $str);
528
    }
529
 
530
    /**
531
     * Check if a string has a unix proof format (stripped spaces,
532
     * special chars, etc)
533
     *
534
     * @param string $str What string to test for unix compliance
535
     *
536
     * @return boolean
537
     */
538
    protected function strIsUnix( $str )
539
    {
540
        return preg_match('/^[a-z0-9_]+$/', $str);
541
    }
542
 
543
    /**
544
     * Convert a string to a unix proof format (strip spaces,
545
     * special chars, etc)
546
     *
547
     * @param string $str What string to make unix compliant
548
     *
549
     * @return string
550
     */
551
    protected function strToUnix( $str )
552
    {
553
        return preg_replace('/[^0-9a-z_]/', '', strtolower($str));
554
    }
555
}