Merge "Introduce SkinFactory"
[lhc/web/wiklou.git] / includes / skins / SkinFactory.php
1 <?php
2
3 /**
4 * Copyright 2014
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 */
23
24 /**
25 * Factory class to create Skin objects
26 *
27 * @since 1.24
28 */
29 class SkinFactory {
30
31 /**
32 * Map of name => callback
33 * @var array
34 */
35 private $factoryFunctions = array();
36 /**
37 * Map of name => human readable name
38 * @var array
39 */
40 private $displayNames = array();
41
42 /**
43 * @var SkinFactory
44 */
45 private static $self;
46
47 public static function getDefaultInstance() {
48 if ( !self::$self ) {
49 self::$self = new self;
50 }
51
52 return self::$self;
53 }
54
55 /**
56 * Register a new Skin factory function
57 * Will override if it's already registered
58 * @param string $name
59 * @param string $displayName
60 * @param callable $callback That takes the skin name as an argument
61 * @throws InvalidArgumentException If an invalid callback is provided
62 */
63 public function register( $name, $displayName, $callback ) {
64 if ( !is_callable( $callback ) ) {
65 throw new InvalidArgumentException( 'Invalid callback provided' );
66 }
67 $this->factoryFunctions[$name] = $callback;
68 $this->displayNames[$name] = $displayName;
69 }
70
71 /**
72 * @return array
73 */
74 private function getLegacySkinNames() {
75 global $wgValidSkinNames;
76 static $skinsInitialised = false;
77
78 if ( !$skinsInitialised || !count( $wgValidSkinNames ) ) {
79 # Get a list of available skins
80 # Build using the regular expression '^(.*).php$'
81 # Array keys are all lower case, array value keep the case used by filename
82 #
83 wfProfileIn( __METHOD__ . '-init' );
84
85 global $wgStyleDirectory;
86
87 $skinDir = dir( $wgStyleDirectory );
88
89 if ( $skinDir !== false && $skinDir !== null ) {
90 # while code from www.php.net
91 while ( false !== ( $file = $skinDir->read() ) ) {
92 // Skip non-PHP files, hidden files, and '.dep' includes
93 $matches = array();
94
95 if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
96 $aSkin = $matches[1];
97
98 // Explicitly disallow loading core skins via the autodiscovery mechanism.
99 //
100 // They should be loaded already (in a non-autodicovery way), but old files might still
101 // exist on the server because our MW version upgrade process is widely documented as
102 // requiring just copying over all files, without removing old ones.
103 //
104 // This is one of the reasons we should have never used autodiscovery in the first
105 // place. This hack can be safely removed when autodiscovery is gone.
106 if ( in_array( $aSkin, array( 'CologneBlue', 'Modern', 'MonoBook', 'Vector' ) ) ) {
107 wfLogWarning(
108 "An old copy of the $aSkin skin was found in your skins/ directory. " .
109 "You should remove it to avoid problems in the future." .
110 "See https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery for details."
111 );
112 continue;
113 }
114
115 wfLogWarning(
116 "A skin using autodiscovery mechanism, $aSkin, was found in your skins/ directory. " .
117 "The mechanism will be removed in MediaWiki 1.25 and the skin will no longer be recognized. " .
118 "See https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery for information how to fix this."
119 );
120 $wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
121 }
122 }
123 $skinDir->close();
124 }
125 $skinsInitialised = true;
126 wfProfileOut( __METHOD__ . '-init' );
127 }
128 return $wgValidSkinNames;
129
130 }
131
132 /**
133 * Returns an associative array of:
134 * skin name => human readable name
135 *
136 * @return array
137 */
138 public function getSkinNames() {
139 return array_merge(
140 $this->getLegacySkinNames(),
141 $this->displayNames
142 );
143 }
144
145 /**
146 * Get a legacy skin which uses $wgValidSkinNames
147 * or autoloading
148 *
149 * @param string $name
150 * @return Skin|bool false if the skin couldn't be constructed
151 */
152 private function getLegacySkin( $name ) {
153 $skinNames = $this->getLegacySkinNames();
154 if ( !isset( $skinNames[$name] ) ) {
155 return false;
156 }
157 $skinName = $skinNames[$name];
158 $className = "Skin{$skinName}";
159
160 # Grab the skin class and initialise it.
161 if ( !class_exists( $className ) ) {
162 global $wgStyleDirectory;
163 require_once "{$wgStyleDirectory}/{$skinName}.php";
164
165 # Check if we got it
166 if ( !class_exists( $className ) ) {
167 # DO NOT die if the class isn't found. This breaks maintenance
168 # scripts and can cause a user account to be unrecoverable
169 # except by SQL manipulation if a previously valid skin name
170 # is no longer valid.
171 return false;
172 }
173 }
174 $skin = new $className( $name );
175 return $skin;
176
177 }
178
179 /**
180 * Create a given Skin using the registered callback for $name.
181 * @param string $name Name of the skin you want
182 * @throws SkinException If a factory function isn't registered for $name
183 * @throws UnexpectedValueException If the factory function returns a non-Skin object
184 * @return Skin
185 */
186 public function makeSkin( $name ) {
187 if ( !isset( $this->factoryFunctions[$name] ) ) {
188 // Check the legacy method of skin loading
189 $legacy = $this->getLegacySkin( $name );
190 if ( $legacy ) {
191 return $legacy;
192 }
193 throw new SkinException( "No registered builder available for $name." );
194 }
195 $skin = call_user_func( $this->factoryFunctions[$name], $name );
196 if ( $skin instanceof Skin ) {
197 return $skin;
198 } else {
199 throw new UnexpectedValueException( "The builder for $name returned a non-Skin object." );
200 }
201 }
202 }