Passed
Push — master ( 3979db...fd55c4 )
by Terrence
14:32
created

Skin   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 587
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 11
Bugs 0 Features 0
Metric Value
eloc 196
c 11
b 0
f 0
dl 0
loc 587
ccs 0
cts 234
cp 0
rs 2.32
wmc 76

17 Methods

Rating   Name   Duplication   Size   Complexity  
B readSkinFromDatabase() 0 52 9
A printSkinCSS() 0 8 2
A readDefaultSkin() 0 17 3
A getConfigOption() 0 17 4
A init() 0 12 2
A __construct() 0 3 1
D readSkinConfig() 0 54 11
B readSkinFromFile() 0 34 8
B setMyProxyInfo() 0 40 8
A inPortalList() 0 13 4
A idpRedlit() 0 13 4
A hasRedlitIdps() 0 8 3
A hasGreenlitIdps() 0 8 3
A idpAvailable() 0 10 3
A hasPortalList() 0 8 3
A idpGreenlit() 0 17 5
A getForceSkin() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like Skin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Skin, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\Util;
6
use tubalmartin\CssMin\Minifier as CSSmin;
7
use PEAR;
8
use DB;
9
10
/**
11
 * Skin
12
 *
13
 * This class reads in CSS and configuration options
14
 * for a 'skin'. The skin is set by passing the
15
 * '?skin=...' (or '?cilogon_skin=...') query parameter.
16
 * If found, the associated config XML and CSS are read
17
 * in from either the filesystem (under the /skin/NAME
18
 * directory) or the database. It also sets a PHP
19
 * session variable so that the skin name is remembered
20
 * across page loads. If no skin name is found, then the
21
 * default /skin/config.xml file is read in.
22
 *
23
 * Note that this class uses the SimpleXML class to parse
24
 * the config XML. This stores the XML in a special
25
 * SimpleXMLElement object, which is NOT an array. But
26
 * you can iterate over elements in the structure. See
27
 * the PHP SimpleXML online manual 'Basic Usage' for
28
 * more information.
29
 *
30
 * This class provides a getConfigOption() method to
31
 * access XML (sub)blocks to get at a config value.
32
 * It is important to rememeber that the values returned
33
 * by the getConfigOption() method must be typecast to
34
 * native datatypes in order to be used effectively.
35
 *
36
 * An example configuration file (with all available options) is at
37
 *     /var/www/html/skin/config-example.xml
38
 *
39
 * Example usage:
40
 *   require_once 'Skin.php';
41
 *   $skin = new Skin();
42
 *   // While outputting the <head> HTML block...
43
 *   $skin->printSkinCSS();
44
 *   // Get the value of a configuration option
45
 *   $idpgreenlit = $skin->getConfigOption('idpgreenlit');!!
46
 *   // Now, process entries in the $idpgreenlit list
47
 *   if ((!is_null($idpgreenlit)) && (!empty($idpgreenlit->idp))) {
48
 *       foreach ($idpgreenlit->idp as $entityID) {
49
 *           echo '<p>' , (string)$entityID , "<\p>\n";
50
 *       }
51
 *   }
52
 *   // Check to see if <hideportalinfo> is set
53
 *   $hideportalinfo = false;
54
 *   $hpi=$skin->getConfigOption('portallistaction','hideportalinfo');
55
 *   if ((!is_null($hpi)) && ((int)$hpi > 0)) {
56
 *       $hideportalinfo = true;
57
 *   }
58
 */
59
class Skin
60
{
61
    /**
62
     * @var string $skinname The name of the skin
63
     */
64
    protected $skinname;
65
66
    /**
67
     * @var \SimpleXMLElement $configxml A SimpleXMLElement object for the
68
     *      config.xml file
69
     */
70
    protected $configxml;
71
72
    /**
73
     * @var string $css The un-minified CSS for the skin
74
     */
75
    protected $css;
76
77
    /**
78
     * @var array $forcearray An array of (URI,skinname) pairs for forcing
79
     *      skin application
80
     */
81
    protected $forcearray;
82
83
    /**
84
     *  __construct
85
     *
86
     * Default constructor. Calls init() to do the actual work.
87
     *
88
     * @return Skin A new Skin object.
89
     */
90
    public function __construct()
91
    {
92
        $this->init();
93
    }
94
95
    /**
96
     * init
97
     *
98
     * This function does the work of (re)initializing the skin object.
99
     * It finds the name of the skin (if any) and reads in the skin's
100
     * config XML file (if found). Call this function to reset the
101
     * skin in case of possible forced skin due to IdP or portal
102
     * callbackURL.
103
     *
104
     * @param bool $reset True to reset the 'cilogon_skin' PHP session var
105
     *        to blank so that readSkinConfig doesn't check for it.
106
     *        Defaults to 'false'.
107
     */
108
    public function init($reset = false)
109
    {
110
        if ($reset) {
111
            Util::unsetSessionVar('cilogon_skin');
112
        }
113
114
        $this->skinname = '';
115
        $this->configxml = null;
116
        $this->css = '';
117
        $this->forcearray = FORCE_SKIN_ARRAY;
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\FORCE_SKIN_ARRAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
118
        $this->readSkinConfig();
119
        $this->setMyProxyInfo();
120
    }
121
122
    /**
123
     * readSkinConfig
124
     *
125
     * This function checks for the name of the skin in several places:
126
     * (1) The FORCE_SKIN_ARRAY for a matching IdP entityID or portal
127
     * callbackURL, (2) in a URL parameter (can be '?skin=',
128
     * '?cilogon_skin=', '?vo='), (3) cilogon_vo form input variable
129
     * (POST for ECP case), or (4) 'cilogon_skin' PHP session
130
     * variable.  If it finds the skin name in any of these, it then
131
     * checks to see if such a named skin exists, either on the filesystem
132
     * or in the database. If found, the class variable $skinname AND the
133
     * 'cilogon_skin' PHP session variable (for use on future page
134
     * loads by the user) are set. It then reads in the config XML and the
135
     * CSS and stores them in the class variables $configxml and $css.
136
     * If no skin name is found, read in the default /skin/config.xml file.
137
     *
138
     * Side Effect: Sets the 'cilogon_skin' session variable if needed.
139
     */
140
    protected function readSkinConfig()
141
    {
142
        $skinvar = '';
143
144
        // Check for matching IdP, callbackURI (OAuth1),
145
        // redirect_uri (OAuth2), or client_id (OAuth2)
146
        // in the FORCE_SKIN_ARRAY.
147
        $uristocheck = array(
148
            Util::getGetVar('redirect_uri'),
149
            Util::getGetVar('client_id'),
150
            Util::getSessionVar('callbackuri'),
151
            Util::getSessionVar('idp')
152
        );
153
154
        foreach ($uristocheck as $value) {
155
            if (strlen($value) > 0) {
156
                $skin = $this->getForceSkin($value);
157
                if (strlen($skin) > 0) {
158
                    $skinvar = $skin;
159
                    break;
160
                }
161
            }
162
        }
163
164
        // If no force skin found, check GET and POST parameters, as well as
165
        // previously set cilogon_skin PHP session variable.
166
        if (strlen($skinvar) == 0) {
167
            // First, look for '?skin=...'
168
            $skinvar = Util::getGetVar('skin');
169
        }
170
        if (strlen($skinvar) == 0) {
171
            // Next, look for '?cilogon_skin=...'
172
            $skinvar = Util::getGetVar('cilogon_skin');
173
        }
174
        if (strlen($skinvar) == 0) {
175
            // Next, look for '?vo=...'
176
            $skinvar = Util::getGetVar('vo');
177
        }
178
        if (strlen($skinvar) == 0) {
179
            // Next, check 'cilogon_vo' form input variable
180
            $skinvar = Util::getPostVar('cilogon_vo');
181
        }
182
        if (strlen($skinvar) == 0) {
183
            // Finally, check 'cilogon_skin' PHP session variable
184
            $skinvar = Util::getSessionVar('cilogon_skin');
185
        }
186
187
        // If we found $skinvar, attempt to read the skin config/css from
188
        // either the filesystem or the database, or failing that, read the
189
        // default /skin/config.xml file.
190
        $skinvar = strtolower($skinvar); // All skin dirs are lowercase
191
        $this->readSkinFromFile($skinvar) ||
192
            $this->readSkinFromDatabase($skinvar) ||
193
            $this->readDefaultSkin();
194
    }
195
196
    /**
197
     * readSkinFromFile
198
     *
199
     * This function reads in the skin config XML and CSS from the
200
     * filesystem into the class variables $configxml and $css.
201
     *
202
     * @param string $skinvar The name of the skin as found by
203
     *        readSkinConfig().
204
     * @return bool True if at least one of config.xml or skin.css could
205
     *         be found (and read in) for the skin (i.e., the skin
206
     *         directory exists and isn't empty). False otherwise.
207
     */
208
    protected function readSkinFromFile($skinvar)
209
    {
210
        $readin = false; // Make sure we read in either XML or CSS (or both)
211
212
        if (strlen($skinvar) > 0) {
213
            $skindir = Util::getServerVar('DOCUMENT_ROOT') . "/skin/$skinvar";
214
            if (is_dir($skindir)) {
215
                // Read in the config XML
216
                $skinconf = $skindir . '/config.xml';
217
                if (is_readable($skinconf)) {
218
                    if (($xml = @simplexml_load_file($skinconf)) !== false) {
219
                        $this->configxml = $xml;
220
                        $readin = true;
221
                    }
222
                }
223
                //Read in the CSS
224
                $skincss = $skindir . '/skin.css';
225
                if (is_readable($skincss)) {
226
                    if (($css = file_get_contents($skincss)) !== false) {
227
                        $this->css = $css;
228
                        $readin = true;
229
                    }
230
                }
231
            }
232
        }
233
234
        if ($readin) {
235
            $this->skinname = $skinvar;
236
            Util::setSessionVar('cilogon_skin', $skinvar);
237
        } else {
238
            Util::unsetSessionVar('cilogon_skin');
239
        }
240
241
        return $readin;
242
    }
243
244
    /**
245
     * readSkinFromDatabase
246
     *
247
     * This function reads in the skin config XML and CSS from the
248
     * database into the class variables $configxml and $css.
249
     *
250
     * @param string $skinvar The name of the skin as found by
251
     *        readSkinConfig().
252
     * @return bool True if at least one of config XML or CSS could
253
     *         be read from the database for the skin. False otherwise.
254
     */
255
    protected function readSkinFromDatabase($skinvar)
256
    {
257
        $readin = false; // Make sure we read in either XML or CSS (or both)
258
259
        if (strlen($skinvar) > 0) {
260
            $dsn = array(
261
                'phptype'  => 'mysqli',
262
                'username' => MYSQLI_USERNAME,
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\MYSQLI_USERNAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
263
                'password' => MYSQLI_PASSWORD,
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\MYSQLI_PASSWORD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
264
                'database' => 'ciloa2',
265
                'hostspec' => 'localhost'
266
            );
267
268
            $opts = array(
269
                'persistent'  => true,
270
                'portability' => DB_PORTABILITY_ALL
271
            );
272
273
            $db = DB::connect($dsn, $opts);
274
            if (!PEAR::isError($db)) {
275
                $data = $db->getRow(
276
                    'SELECT * from skins WHERE name = ?',
277
                    array($skinvar),
278
                    DB_FETCHMODE_ASSOC
279
                );
280
                if ((!DB::isError($data)) && (!empty($data))) {
281
                    // Read in the config XML
282
                    if (
283
                        (strlen(@$data['config']) > 0) &&
284
                        (($xml = @simplexml_load_string($data['config'])) !== false)
285
                    ) {
286
                        $this->configxml = $xml;
287
                        $readin = true;
288
                    }
289
                    //Read in the CSS
290
                    if (strlen(@$data['css']) > 0) {
291
                        $this->css = $data['css'];
292
                        $readin = true;
293
                    }
294
                }
295
                $db->disconnect();
296
            }
297
        }
298
299
        if ($readin) {
300
            $this->skinname = $skinvar;
301
            Util::setSessionVar('cilogon_skin', $skinvar);
302
        } else {
303
            Util::unsetSessionVar('cilogon_skin');
304
        }
305
306
        return $readin;
307
    }
308
309
    /**
310
     * readDefaultSkin
311
     *
312
     * If we don't read a skin from the filesystem or the database, then
313
     * read in the default "/skin/config.xml" file.
314
     *
315
     * @return bool True if the default config.xml file was read in.
316
     *         False otherwise.
317
     */
318
    protected function readDefaultSkin()
319
    {
320
        $readin = false;
321
322
        $this->skinname = '';
323
        $this->css = '';
324
        Util::unsetSessionVar('cilogon_skin');
325
326
        $skinconf = Util::getServerVar('DOCUMENT_ROOT') . '/skin/config.xml';
327
        if (is_readable($skinconf)) {
328
            if (($xml = @simplexml_load_file($skinconf)) !== false) {
329
                $this->configxml = $xml;
330
                $readin = true;
331
            }
332
        }
333
334
        return $readin;
335
    }
336
337
    /**
338
     * getConfigOption
339
     *
340
     * This method returns a SimpleXMLElement block corresponding to
341
     * the passed in arguments.  For example, to get the redlit list of
342
     * idps, call $idps = getConfigOption('idpredlit') and then
343
     * iterate over $idps with foreach($idps as $idp) { ... }.  To get
344
     * a single subblock value such as the initial lifetime number for
345
     * the PKCS12 download option, call $life =
346
     * (int)getConfigOption('pkcs12','initiallifetime','number'). Note
347
     * that you should explicitly cast the values to int, string,
348
     * float, etc., when you use them.
349
     *
350
     * @param mixed $args Variable number of parameters corresponding to XML
351
     *              blocks (and possible sub-blocks).
352
     * @return \SimpleXMLElement|null A SimpleXMLElement corresponding to the
353
     *         passed-in XML option, or 'null' if no such option exists.
354
     */
355
    public function getConfigOption(...$args)
356
    {
357
        $retval = null;
358
        $numargs = count($args);
359
        if ($numargs > 0) {
360
            $retval = $this->configxml;
361
        }
362
        for ($i = 0; $i < $numargs; $i++) {
363
            $argval = $args[$i];
364
            if (empty($retval->$argval)) {
365
                $retval = null;
366
                break;
367
            } else {
368
                $retval = $retval->$argval;
369
            }
370
        }
371
        return $retval;
372
    }
373
374
    /**
375
     * printSkinCSS
376
     *
377
     * Call this function in the HTML <head> block to print out the
378
     * <style> tag for the internal CSS of the skin. The CSS is minified
379
     * to remove whitespace. Note that you should call readSkinConfig
380
     * to set the contents of $css.
381
     */
382
    public function printSkinCSS()
383
    {
384
        if (strlen($this->css) > 0) {
385
            $cssmin = new CSSmin();
386
            $cssmin->removeImportantComments();
387
            $cssmin->setLineBreakPosition(255);
388
            $outputcss = $cssmin->run($this->css);
389
            echo "<style>$outputcss</style>";
390
        }
391
    }
392
393
    /**
394
     * hasGreenlitIdps
395
     *
396
     * This function checks for the presence of a <idpgreenlit> block
397
     * in the skin's config file.  There must be at least one <idp>
398
     * in the <idpgreenlit>.
399
     *
400
     * @return bool True if skin has a non-empty <idpgreenlit>.
401
     */
402
    public function hasGreenlitIdps()
403
    {
404
        $retval = false;  // Assume no <idpgreenlit> configured
405
        $idpgreenlit = $this->getConfigOption('idpgreenlit');
406
        if ((!is_null($idpgreenlit)) && (!empty($idpgreenlit->idp))) {
407
            $retval = true;
408
        }
409
        return $retval;
410
    }
411
412
    /**
413
     * hasRedlitIdps
414
     *
415
     * This function checks for the presence of a <idpredlit> block
416
     * in the skin's config file.  There must be at least one <idp>
417
     * in the <idpredlit>.
418
     *
419
     * @return bool True if skin has a non-empty <idpredlit>.
420
     */
421
    public function hasRedlitIdps()
422
    {
423
        $retval = false;  // Assume no <idpredlit> configured
424
        $idpredlit = $this->getConfigOption('idpredlit');
425
        if ((!is_null($idpredlit)) && (!empty($idpredlit->idp))) {
426
            $retval = true;
427
        }
428
        return $retval;
429
    }
430
431
    /**
432
     * idpGreenlit
433
     *
434
     * This method checks to see if a given entityId of an IdP
435
     * is greenlit. 'Greenlit' in this case means either (a) the
436
     * entityId is in the skin's <idpgreenlit> list or (b) the skin
437
     * doesn't have a <idpgreenlit> at all.  In the second case, all
438
     * IdPs are by default 'greenlit'.  If you want to find if an
439
     * IdP should be listed in the WAYF, use 'idpAvailable' which
440
     * checks the greenlit AND the redlit lists.
441
     *
442
     * @param string $entityId The entityId of an IdP to check for greenlit.
443
     * @return bool True if the given IdP entityId is in the skin's
444
     *         greenlit list (or if the skin doesn't have a greenlit list).
445
     */
446
    public function idpGreenlit($entityId)
447
    {
448
        $retval = true;  // Assume the entityId is 'greenlit'
449
        if ($this->hasGreenlitIdps()) {
450
            $idpgreenlit = $this->getConfigOption('idpgreenlit');
451
            $found = false;
452
            foreach ($idpgreenlit->idp as $greenidp) {
453
                if ($entityId == ((string)$greenidp)) {
454
                    $found = true;
455
                    break;
456
                }
457
            }
458
            if (!$found) {
459
                $retval = false;
460
            }
461
        }
462
        return $retval;
463
    }
464
465
    /**
466
     * idpRedlit
467
     *
468
     * This method checks to see if a given entityId of an IdP
469
     * appears in the skin's <idpredlit>.
470
     *
471
     * @param string $entityId The entityId of an IdP to check for
472
     *        redlit status.
473
     * @return bool True if the given IdP entityId is in the skin's
474
     *         redlit list.
475
     */
476
    public function idpRedlit($entityId)
477
    {
478
        $retval = false;  // Assume entityId is NOT in the idpredlit
479
        if ($this->hasRedlitIdps()) {
480
            $idpredlit = $this->getConfigOption('idpredlit');
481
            foreach ($idpredlit->idp as $redidp) {
482
                if ($entityId == ((string)$redidp)) {
483
                    $retval = true;
484
                    break;
485
                }
486
            }
487
        }
488
        return $retval;
489
    }
490
491
    /**
492
     * idpAvailable
493
     *
494
     * This method combines idpGreenlit and idpRedlit to return
495
     * a 'yes/no' for if a given IdP should be made available for
496
     * selection in the WAYF.  It first checks to see if the IdP is
497
     * greenlit.  If not, it returns false. Otherwise, it then
498
     * checks if the IdP is redlit.  If not, it returns true.
499
     *
500
     * @param string $entityId The entityId of an IdP to check to see if it
501
     *        should be available in the WAYF.
502
     * @return bool True if the given IdP entityId is available to be
503
     *         selected in the WAYF.
504
     */
505
    public function idpAvailable($entityId)
506
    {
507
        $retval = false;   // Assume IdP is not available in the WAYF
508
        if (
509
            ($this->idpGreenlit($entityId)) &&
510
            (!$this->idpRedlit($entityId))
511
        ) {
512
            $retval = true;
513
        }
514
        return $retval;
515
    }
516
517
    /**
518
     * setMyProxyInfo
519
     *
520
     * This method sets the 'myproxyinfo' PHP session variable.  The
521
     * variable has the form 'info:key1=value1,key2=value2,...' and is
522
     * passed to the 'myproxy-logon' command as part of the username
523
     * when fetching a credential.  The MyProxy server will do extra
524
     * processing based on the content of this 'info:...' tag.  If the
525
     * skinname is not empty, that is added to the info tag.  Also,
526
     * the apache REMOTE_ADDR is added.  For other key=value pairs that
527
     * get added, see the code below.
528
     */
529
    public function setMyProxyInfo()
530
    {
531
        $infostr = '';
532
533
        // Add the skinname if available
534
        if (strlen($this->skinname) > 0) {
535
            $infostr .= 'cilogon_skin=' . $this->skinname;
536
        }
537
538
        // Add the REMOTE_ADDR
539
        $remoteaddr = Util::getServerVar('REMOTE_ADDR');
540
        if (strlen($remoteaddr) > 0) {
541
            $infostr .= (strlen($infostr) > 0 ? ',' : '') .
542
                        "remote_addr=$remoteaddr";
543
        }
544
545
        // Add eppn, eptid, open_id, and oidc if available
546
        // Note that these values are lowercase after an update to make
547
        // them the same as those used by the dbService. BUT, MyProxy
548
        // expects the old versions. So this array maps the new lowercase
549
        // versions back into the old ones.
550
        $mpid = array(
551
            'eppn' => 'ePPN',
552
            'eptid' => 'ePTID',
553
            'open_id' => 'openidID',
554
            'oidc' => 'oidcID'
555
        );
556
        foreach (array('eppn','eptid','open_id','oidc') as $id) {
557
            $sessvar = Util::getSessionVar($id);
558
            if (strlen($sessvar) > 0) {
559
                $infostr .= (strlen($infostr) > 0 ? ',' : '') .
560
                    $mpid[$id] . "=" . $sessvar;
561
            }
562
        }
563
564
        // Finally, set the 'myproxyinfo' PHP session variable
565
        if (strlen($infostr) > 0) {
566
            Util::setSessionVar('myproxyinfo', 'info:' . $infostr);
567
        } else {
568
            Util::unsetSessionVar('myproxyinfo');
569
        }
570
    }
571
572
    /**
573
     * hasPortalList
574
     *
575
     * This function checks for the presence of a <portallist> block in
576
     * the skin's config file.  There must be at least one <portal> in
577
     * the <portallist>.
578
     *
579
     * @return bool True if skin has a non-empty <portallist>.
580
     */
581
    public function hasPortalList()
582
    {
583
        $retval = false;  // Assume no <portallist> configured
584
        $portallist = $this->getConfigOption('portallist');
585
        if ((!is_null($portallist)) && (!empty($portallist->portal))) {
586
            $retval = true;
587
        }
588
        return $retval;
589
    }
590
591
    /**
592
     * inPortalList
593
     *
594
     * This function takes in a 'callback' URL of a portal passed to
595
     * the CILogon Delegate service.  It then looks through the list
596
     * of <portal> patterns in the skin's <portallist>.  If the
597
     * callback URL matches any of these patterns, true is returned.
598
     * This is used to hide the 'Site Name / Site URL / Service URL'
599
     * box on the delegation WAYF page, for example.
600
     *
601
     * @param string $portalurl A 'callback' URL of a portal accessing the
602
     *        delegate service.
603
     * @return bool True if the callback URL matches one of the patterns
604
     *         in the skin's <portallist>.  False otherwise.
605
     */
606
    public function inPortalList($portalurl)
607
    {
608
        $retval = false;  // Assume the portalurl not a listed <portal>
609
        if ($this->hasPortalList()) {
610
            $portallist = $this->getConfigOption('portallist');
611
            foreach ($portallist->portal as $portalmatch) {
612
                if (preg_match(((string)$portalmatch), $portalurl)) {
613
                    $retval = true;
614
                    break;
615
                }
616
            }
617
        }
618
        return $retval;
619
    }
620
621
    /**
622
     * getForceSkin
623
     *
624
     * The FORCE_SKIN_ARRAY contains 'uripattern'=>'skinname' pairs
625
     * corresponding to IdP entityIDs, portal callbackurls, or client_ids
626
     * that should have a particular skin force-applied. This function
627
     * looks in the FORCE_SKIN_ARRAY for a pattern-matched URI and returns
628
     * the corresponding skin name if found. If not found, empty
629
     * string is returned.
630
     *
631
     * @param string $uri A URI to search for in the FORCE_SKIN_ARRAY.
632
     * @return string The skin name for the URI, or empty string if not
633
     *         found.
634
     */
635
    protected function getForceSkin($uri)
636
    {
637
        $retval = '';  // Assume uri is not in FORCE_SKIN_ARRAY
638
639
        foreach ($this->forcearray as $key => $value) {
640
            if (preg_match($key, $uri)) {
641
                $retval = $value;
642
                break;
643
            }
644
        }
645
        return $retval;
646
    }
647
}
648