Index: trunk/extensions/SimpleFarm/SimpleFarm_Classes.php |
— | — | @@ -0,0 +1,551 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * 'SimpleFarm' and 'SimpleFarmMember' classes of the 'Simple Farm' extension. |
| 5 | + * |
| 6 | + * @file SimpleFarm_Classes.php |
| 7 | + * @ingroup SimpleFarm |
| 8 | + * |
| 9 | + * @author Daniel Werner < danweetz@web.de > |
| 10 | +*/ |
| 11 | + |
| 12 | +/** |
| 13 | + * Contains functions for 'Simple Farm' farm managment. |
| 14 | + * |
| 15 | + * @since 0.1 |
| 16 | + */ |
| 17 | +class SimpleFarm { |
| 18 | + /** |
| 19 | + * If set to a farm member within '$egSimpleFarmMembers' array (see settings file) it meains that |
| 20 | + * the wiki is not in maintenance mode right now. |
| 21 | + */ |
| 22 | + const MAINTAIN_OFF = false; |
| 23 | + /** |
| 24 | + * Block simple browser acces to the wiki but allow accessing the wiki with 'maintain' url parameter |
| 25 | + */ |
| 26 | + const MAINTAIN_SIMPLE = 1; |
| 27 | + /** |
| 28 | + * Block all alltemts to acces wiki except for command-line |
| 29 | + */ |
| 30 | + const MAINTAIN_STRICT = 2; |
| 31 | + /** |
| 32 | + * Block all attempts to access wiki, even command-line |
| 33 | + */ |
| 34 | + const MAINTAIN_TOTAL = 3; |
| 35 | + |
| 36 | + private static $activeMember = null; |
| 37 | + public static $maintenanceIsRunning = false; |
| 38 | + |
| 39 | + /** |
| 40 | + * Returns the defined main member of the wiki farm. |
| 41 | + * If $wgSimpleFarmMainMemberDB has not been set yet, this will set $wgSimpleFarmMainMemberDB |
| 42 | + * to the first member in $wgSimpleFarmMembers |
| 43 | + * @return SimpleFarmMember or null if no match could be found |
| 44 | + */ |
| 45 | + public static function getMainMember() { |
| 46 | + global $egSimpleFarmMainMemberDB, $egSimpleFarmMembers; |
| 47 | + |
| 48 | + // if variable was not set in config, fill it with first farm member or return null if none is defined: |
| 49 | + if( $egSimpleFarmMainMemberDB === null ) { |
| 50 | + if( (int)$egSimpleFarmMembers ) |
| 51 | + $egSimpleFarmMainMemberDB = $egSimpleFarmMembers[0]['db']; |
| 52 | + else |
| 53 | + return null; |
| 54 | + } |
| 55 | + return SimpleFarmMember::loadFromDatabaseName( $egSimpleFarmMainMemberDB ); |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * Returns the SimpleFarmMember object selected to be loaded for this instance of the farm |
| 60 | + * or the object that has already been loaded. |
| 61 | + * If initialisation has not been kicked off yet, this will find the wiki which would be |
| 62 | + * chosen by self::int(). The result of the function could change after self::intWiki() |
| 63 | + * was called to initialise another wiki instead. |
| 64 | + * $wgSimpleFarmWikiSelectEnvVarName should contain its final value when first calling this. |
| 65 | + * |
| 66 | + * @return SimpleFarmMember or null if no match was found |
| 67 | + */ |
| 68 | + public static function getActiveMember() { |
| 69 | + global $IP, $wgCommandLineMode; |
| 70 | + global $egSimpleFarmMembers, $egSimpleFarmMainMemberDB, $egSimpleFarmWikiSelectEnvVarName; |
| 71 | + |
| 72 | + // return last initialised farm member if available: |
| 73 | + if( self::$activeMember !== null ) { |
| 74 | + return self::$activeMember; |
| 75 | + } |
| 76 | + |
| 77 | + if( ! defined( 'SIMPLEFARM_ENVVAR' ) ) { |
| 78 | + define( 'SIMPLEFARM_ENVVAR', $egSimpleFarmWikiSelectEnvVarName ); |
| 79 | + } |
| 80 | + // in commandline mode we check for environment variable to stelect a wiki: |
| 81 | + if( $wgCommandLineMode ) { |
| 82 | + /* |
| 83 | + * if we are in command-line mode but no wiki was selected |
| 84 | + * and this is not just the initial wiki call to run maintenance |
| 85 | + * on several wikis |
| 86 | + */ |
| 87 | + $wikiEvn = getenv( SIMPLEFARM_ENVVAR ); |
| 88 | + |
| 89 | + if( $wikiEvn === false ) { |
| 90 | + // running SimpleFarm maintenance script which will maintain all wikis: |
| 91 | + if( self::$maintenanceIsRunning ) { |
| 92 | + $wikiEvn = $egSimpleFarmMainMemberDB; |
| 93 | + } else { |
| 94 | + return null; // commandline-mode for specific wiki but no wiki was chosen! |
| 95 | + } |
| 96 | + } |
| 97 | + return SimpleFarmMember::loadFromDatabaseName( $wikiEvn ); |
| 98 | + } |
| 99 | + // farm member called via browser, find out which member via server name: |
| 100 | + else { |
| 101 | + // only interesting if redirect_url is set, otherwise unlikely to be used anyway |
| 102 | + $currScriptPath = isset( $_SERVER['REDIRECT_URL'] ) ? $_SERVER['REDIRECT_URL'] : $_SERVER['SCRIPT_NAME']; |
| 103 | + /* |
| 104 | + * in case the script path was called directly and not some page within the directory, |
| 105 | + * just make sure to add '.' behind the last '/' before getting the directory name |
| 106 | + * '/bla/' returns '/', '/bla/.' returns '/bla' ! |
| 107 | + */ |
| 108 | + $currScriptPath = dirname( preg_replace( '%[\\/\\\]$%', '/.', $currScriptPath ) ); |
| 109 | + |
| 110 | + /* |
| 111 | + * in case "<domain>/<uri>" without '/' after, the above will return '\' (windows) |
| 112 | + * or '/' (unix). Take the whole requested URI then since it could be the scriptpath |
| 113 | + * directory or just a file in the root dir... |
| 114 | + */ |
| 115 | + if( preg_match( '%[\\/\\\]$%', $currScriptPath ) ) |
| 116 | + $currScriptPath = $_SERVER['REDIRECT_URL']; |
| 117 | + /* |
| 118 | + echo $currScriptPath . "<br/>"; |
| 119 | + echo "is_dir: " . is_dir( $_SERVER['DOCUMENT_ROOT'] . $_SERVER['REDIRECT_URL'] ) . "<br/>"; |
| 120 | + echo "<html><body><pre>"; |
| 121 | + print_r( $_SERVER ); |
| 122 | + echo "</pre></br>" . $currScriptPath . "<br/>" . preg_replace( '%[\\/\\\]$%', '/.', $_SERVER['REDIRECT_URL'] ) . "<br/>" . $_SERVER['REQUEST_URI'] . "</body></html>"; |
| 123 | + die( 1 ); |
| 124 | + */ |
| 125 | + // walk all farm members and see whether it fullfils criteria to be the loaded one right now: |
| 126 | + foreach( self::getMembers() as $member ) { |
| 127 | + |
| 128 | + switch( $member->getCfgMode() ) { |
| 129 | + |
| 130 | + // configuration uses script path for this one to identify as selected: |
| 131 | + case SimpleFarmMember::CFG_MODE_SCRIPTPATH: |
| 132 | + if( $member->getScriptPath() === $currScriptPath ) { |
| 133 | + return $member; |
| 134 | + } |
| 135 | + break; |
| 136 | + |
| 137 | + // configuration uses a set of addresses to identifiy as selected: |
| 138 | + case SimpleFarmMember::CFG_MODE_ADDRESS: |
| 139 | + if( in_array( $_SERVER['HTTP_HOST'], $member->getAddresses(), true ) ) { |
| 140 | + return $member; |
| 141 | + } |
| 142 | + break; |
| 143 | + |
| 144 | + // if not set up properly: |
| 145 | + case SimpleFarmMember::CFG_MODE_NONE: |
| 146 | + continue; |
| 147 | + } |
| 148 | + } |
| 149 | + return null; // no macht with configuration array! |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Initialises the selected member wiki of the wiki farm. This is only possible |
| 155 | + * once and must be done during localsettings configuration * |
| 156 | + * This will also modify some global variables, see SimpleFarm::initWiki() for details |
| 157 | + * |
| 158 | + * @return boolean true |
| 159 | + */ |
| 160 | + public static function init() { |
| 161 | + // don't allow multiple calls! |
| 162 | + if( self::$activeMember !== null ) |
| 163 | + return true; // for hook use! |
| 164 | + |
| 165 | + global $egSimpleFarmMainMemberDB, $wgCommandLineMode; |
| 166 | + |
| 167 | + // set some main member if not set in config and farm has members: |
| 168 | + if( $egSimpleFarmMainMemberDB === null ) { |
| 169 | + $egSimpleFarmMainMemberDB = self::getMainMember()->getDB(); |
| 170 | + } |
| 171 | + // get selected member for this farm call: |
| 172 | + $wiki = self::getActiveMember(); |
| 173 | + |
| 174 | + // if wiki is not in farm list: |
| 175 | + if( $wiki === null ) { |
| 176 | + if( $wgCommandLineMode ) { |
| 177 | + self::dieEarly( 'Environment variable "' . SIMPLEFARM_ENVVAR . '" must be set to an existing farm member database name to select a wiki in command-line mode!' ); |
| 178 | + } |
| 179 | + else { |
| 180 | + // wiki not found, try to call user defined callback function and try return value: |
| 181 | + // (can't use hook-system here since it propably isn't loaded at this sage!) |
| 182 | + global $egSimpleFarmErrorNoMemberFoundCallback; |
| 183 | + |
| 184 | + if( is_callable( $egSimpleFarmErrorNoMemberFoundCallback ) ) { |
| 185 | + $wiki = call_user_func( $egSimpleFarmErrorNoMemberFoundCallback ); |
| 186 | + } |
| 187 | + |
| 188 | + if( ! ( $wiki instanceof SimpleFarmMember ) ) { |
| 189 | + header( $_SERVER["SERVER_PROTOCOL"] . " 404 Not Found" ); |
| 190 | + self::dieEarly( 'No wiki farm member found here!' ); |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | + self::initWiki( $wiki ); |
| 195 | + return true; // for hook use! |
| 196 | + } |
| 197 | + |
| 198 | + /** |
| 199 | + * Function to set all setup options to load a specific wiki of the wiki farm. |
| 200 | + * Should only be called while 'LocalSettings.php' is running and after |
| 201 | + * SimpleFarm extension has been initialised. |
| 202 | + * |
| 203 | + * This will modify the following globals: |
| 204 | + * |
| 205 | + * $wgSitename = SimpleFarmMember::getName(); |
| 206 | + * $wgDBname = SimpleFarmMember::getDB(); |
| 207 | + * $wgScriptPath = SimpleFarmMember::getScriptPath(); |
| 208 | + * $wgLocalInterwiki = strtolower( $wgSitename ); |
| 209 | + * $wgUploadDirectory = "{$IP}/images/images_{$wgDBname}"; |
| 210 | + * $wgUploadPath = "{$wgScriptPath}/images/images_{$wgDBname}"; |
| 211 | + * $wgLogo = "{$wgScriptPath}/images/logo/{$wgDBname}.png"; |
| 212 | + * $wgFavicon = "{$wgScriptPath}/images/logo/{$wgDBname}.ico"; |
| 213 | + * |
| 214 | + * @param $wiki SimpleFarmMember to initialise |
| 215 | + */ |
| 216 | + public static function initWiki( SimpleFarmMember $wiki ) { |
| 217 | + global $IP; |
| 218 | + // globals to be configured: |
| 219 | + global $wgScriptPath, $wgSitename, $wgLocalInterwiki, $wgDBname, $wgUploadDirectory, $wgUploadPath, $wgLogo, $wgFavicon; |
| 220 | + |
| 221 | + $wgSitename = $wiki->getName(); |
| 222 | + $wgLocalInterwiki = strtolower( $wgSitename ); |
| 223 | + |
| 224 | + // check for maintain mode: |
| 225 | + if( $wiki->isInMaintainMode() && ! $wiki->userIsMaintaining() ) |
| 226 | + self::dieEarly( "$wgSitename is in maintain mode currently! Please try again later." ); |
| 227 | + |
| 228 | + self::$activeMember = $wiki; |
| 229 | + |
| 230 | + $wgScriptPath = $wiki->getScriptPath(); //in case of 'scriptpath' config and mod-rewrite, otherwise same value anyway |
| 231 | + $wgDBname = $wiki->getDB(); |
| 232 | + $wgUploadDirectory = "{$IP}/images/images_{$wgDBname}"; |
| 233 | + $wgUploadPath = "{$wgScriptPath}/images/images_{$wgDBname}"; |
| 234 | + if( ! is_dir( $wgUploadDirectory ) ) |
| 235 | + mkdir( $wgUploadDirectory, 0777 ); |
| 236 | + |
| 237 | + $wgLogo = "{$wgScriptPath}/images/logo/{$wgDBname}.png"; |
| 238 | + $wgFavicon = "{$wgScriptPath}/images/logo/{$wgDBname}.ico"; |
| 239 | + |
| 240 | + /* |
| 241 | + * it's no good loading an individual config file here since it wouldn't |
| 242 | + * be in the global scope and all globals had to be defined as global first... |
| 243 | + * Hacking around this is too dirty (reading all globals in local scope and then |
| 244 | + * transferring local scope back to global scope). |
| 245 | + * |
| 246 | + * There is an easy way to allow custom config files in LocalSettings directly though: |
| 247 | + * |
| 248 | + * if( file_exists( "$IP/wikiconfigs/$wgDBname.php" ) ) { |
| 249 | + * include( "$IP/wikiconfigs/$wgDBname.php" ); |
| 250 | + * } |
| 251 | + */ |
| 252 | + return true; |
| 253 | + } |
| 254 | + |
| 255 | + /** |
| 256 | + * Return an array with all members as SimpleFarmMember objects. The key of each array item |
| 257 | + * is the database name of the wiki farm member. |
| 258 | + * |
| 259 | + * @return SimpleFarmMember[] |
| 260 | + */ |
| 261 | + public static function getMembers() { |
| 262 | + global $egSimpleFarmMembers; |
| 263 | + $members = array(); |
| 264 | + foreach( $egSimpleFarmMembers as $member ) { |
| 265 | + $members[ $member['db'] ] = new SimpleFarmMember( $member ); |
| 266 | + } |
| 267 | + return $members; |
| 268 | + } |
| 269 | + |
| 270 | + /** |
| 271 | + * Use instead of wfDie() because global functions are loaded after localsettings.php |
| 272 | + * and in some cases this could happen during localsettings is still running! |
| 273 | + * |
| 274 | + * @param $dieMsg string message |
| 275 | + */ |
| 276 | + private static function dieEarly( $dieMsg = '' ) { |
| 277 | + echo $dieMsg; |
| 278 | + die( 1 ); |
| 279 | + } |
| 280 | +} |
| 281 | + |
| 282 | + |
| 283 | +/** |
| 284 | + * Represents a SimpleFarm member wiki. |
| 285 | + * |
| 286 | + * @since 0.1 |
| 287 | + */ |
| 288 | +class SimpleFarmMember { |
| 289 | + |
| 290 | + private $siteOpt; |
| 291 | + |
| 292 | + const CFG_MODE_NONE = 0; |
| 293 | + const CFG_MODE_ADDRESS = 1; |
| 294 | + const CFG_MODE_SCRIPTPATH = 2; |
| 295 | + |
| 296 | + public function __construct( $siteOptions ) { |
| 297 | + $this->siteOpt = $siteOptions; |
| 298 | + } |
| 299 | + |
| 300 | + /** |
| 301 | + * Load new SimpleFarmMember from its database name. |
| 302 | + * If not successful null will be returned. |
| 303 | + * |
| 304 | + * @param $dbName string name of the database, case-insensitive |
| 305 | + * |
| 306 | + * @return SimpleFarmMember|null |
| 307 | + */ |
| 308 | + public static function loadFromDatabaseName( $dbName ) { |
| 309 | + global $egSimpleFarmMembers; |
| 310 | + |
| 311 | + foreach( $egSimpleFarmMembers as $siteOpt ) { |
| 312 | + if( strtolower( $siteOpt['db'] ) === strtolower( trim( $dbName ) ) ) |
| 313 | + return new SimpleFarmMember( $siteOpt ); |
| 314 | + } |
| 315 | + return null; |
| 316 | + } |
| 317 | + |
| 318 | + /** |
| 319 | + * Load new SimpleFarmMember from one of its addresses. |
| 320 | + * |
| 321 | + * @ToDo: More flexible and allowing other ways of forking farm members! |
| 322 | + * |
| 323 | + * @param $url String url or domain to datermine one of the registered server names/url for |
| 324 | + * this wiki-farm member. For example 'www.farm1.wikifarm.org' @ToDo: or 'http://foo.org/wiki1'. |
| 325 | + * Value is case-insensitive. |
| 326 | + * @param $scriptPath String farm member script path as second criteria in addition to address |
| 327 | + * |
| 328 | + * @return SimpleFarmMember if not successful null will be returned |
| 329 | + */ |
| 330 | + public static function loadFromAddress( $url, $scriptPath = null ) { |
| 331 | + global $egSimpleFarmMembers, $egSimpleFarmIgnoredDomainPrefixes; |
| 332 | + |
| 333 | + if( $scriptPath !== null ) |
| 334 | + $scriptPath = str_replace( "\\", "/", trim( $scriptPath ) ); |
| 335 | + |
| 336 | + // url to domain name. Trim url scheme, |
| 337 | + $pref = implode( '|', $egSimpleFarmIgnoredDomainPrefixes ); // no escaping necessary |
| 338 | + $url = str_replace( "\\", "/", strtolower( $url ) ); // for windows-style paths |
| 339 | + $address = preg_replace( '%^(?:.*://)?(?:(?:' . $pref . ')\.)?%', '', $url ); |
| 340 | + $address = preg_replace( '%/.*%', '', $address ); |
| 341 | + |
| 342 | + $members = SimpleFarm::getMembers(); |
| 343 | + foreach( $members as $member ) { |
| 344 | + // if url matches: |
| 345 | + if( in_array( $address, $member->getAddresses() ) ) { |
| 346 | + // if script path is required, then check for it too: |
| 347 | + if( $scriptPath !== null ) { |
| 348 | + if( trim( $scriptPath ) === $member->getScriptPath() ) |
| 349 | + return $member; |
| 350 | + } else { |
| 351 | + return $member; |
| 352 | + } |
| 353 | + } |
| 354 | + |
| 355 | + } |
| 356 | + return null; |
| 357 | + } |
| 358 | + |
| 359 | + /** |
| 360 | + * Load new SimpleFarmMember from its 'scriptpath' config value. |
| 361 | + * |
| 362 | + * @param $scriptPath String configured script path of the farm member which should be returend. |
| 363 | + * |
| 364 | + * @return SimpleFarmMember if not successful null will be returned |
| 365 | + */ |
| 366 | + public static function loadFromScriptPath( $scriptPath ) { |
| 367 | + $members = SimpleFarm::getMembers(); |
| 368 | + foreach( $members as $member ) { |
| 369 | + if( $scriptPath !== null ) { |
| 370 | + if( trim( $scriptPath ) === $member->getScriptPath() ) |
| 371 | + return $member; |
| 372 | + } else { |
| 373 | + return $member; |
| 374 | + } |
| 375 | + } |
| 376 | + } |
| 377 | + |
| 378 | + /** |
| 379 | + * Whether the wiki is set to maintaining mode right now. |
| 380 | + * Returns the maintaining strictness. |
| 381 | + * |
| 382 | + * @return Integer |
| 383 | + */ |
| 384 | + public function isInMaintainMode() { |
| 385 | + if( empty( $this->siteOpt['maintain'] ) ) |
| 386 | + return SimpleFarm::MAINTAIN_OFF; |
| 387 | + else |
| 388 | + return $this->siteOpt['maintain']; |
| 389 | + } |
| 390 | + |
| 391 | + /** |
| 392 | + * Returns whether the user is a maintainer or not. A maintainer is the current user |
| 393 | + * if he accesses the wiki via command-line or if he has the 'maintainer' url parameter set. |
| 394 | + * |
| 395 | + * @param User $user the user we want to know whether he is a maintainer right now. |
| 396 | + * If not set, the information will be returned for the current user. |
| 397 | + * This will only work after 'LocalSettings.php' since $wgUser is undefined ealrier! |
| 398 | + * |
| 399 | + * @return Boolean |
| 400 | + */ |
| 401 | + public function userIsMaintaining( User $user = null ) { |
| 402 | + global $wgUser, $wgCommandLineMode; |
| 403 | + |
| 404 | + // if $user is not the current user, he can't be maintaining anything right now |
| 405 | + if( $user === null ) { |
| 406 | + // $wgUser still null during localsettings.php config |
| 407 | + $user = $wgUser; |
| 408 | + } |
| 409 | + if( $wgUser !== null && $user->getId() !== $wgUser->getId() ) { |
| 410 | + return false; |
| 411 | + } |
| 412 | + |
| 413 | + // commandline usually is maintainer, so is the user if maintainer parameter is set in url |
| 414 | + switch( $this->isInMaintainMode() ) { |
| 415 | + // no break, step by step! |
| 416 | + case SimpleFarm::MAINTAIN_SIMPLE: |
| 417 | + if( isset( $_GET['maintainer'] ) || isset( $_GET['maintain'] ) ) |
| 418 | + return true; |
| 419 | + // no break! |
| 420 | + |
| 421 | + case SimpleFarm::MAINTAIN_STRICT: |
| 422 | + if( $wgCommandLineMode ) |
| 423 | + return true; |
| 424 | + // no break! |
| 425 | + |
| 426 | + case SimpleFarm::MAINTAIN_TOTAL: |
| 427 | + default: |
| 428 | + return false; |
| 429 | + } |
| 430 | + } |
| 431 | + |
| 432 | + /** |
| 433 | + * Returns the Database name |
| 434 | + * |
| 435 | + * @return String |
| 436 | + */ |
| 437 | + public function getDB() { |
| 438 | + return $this->siteOpt['db']; |
| 439 | + } |
| 440 | + |
| 441 | + /** |
| 442 | + * Returns the wiki name |
| 443 | + * |
| 444 | + * @return String |
| 445 | + */ |
| 446 | + public function getName() { |
| 447 | + global $egSimpleFarmMembers; |
| 448 | + return $this->siteOpt['name']; |
| 449 | + } |
| 450 | + |
| 451 | + /** |
| 452 | + * Returns all domains of the wiki (either from $wgSimpleFarmMembers or in case |
| 453 | + * 'scriptpath' is used, from the server directly. |
| 454 | + * |
| 455 | + * @return String[] or null in case of command-line access and missing 'address' |
| 456 | + * key in $wgSimpleFarmMembers config array. |
| 457 | + */ |
| 458 | + public function getAddresses() { |
| 459 | + // if addresses are not configured, return the server name: |
| 460 | + if( isset( $this->siteOpt['addresses'] ) ) { |
| 461 | + $addr = $this->siteOpt['addresses']; |
| 462 | + } |
| 463 | + elseif( isset( $_SERVER['HTTP_HOST'] ) ) { |
| 464 | + return array( $_SERVER['HTTP_HOST'] ); |
| 465 | + } |
| 466 | + else { |
| 467 | + return null; // in case arr option is not set and we are in commandline-mode! |
| 468 | + } |
| 469 | + |
| 470 | + if( is_array( $addr ) ) { |
| 471 | + return $addr; |
| 472 | + } |
| 473 | + else { |
| 474 | + return array( $addr ); |
| 475 | + } |
| 476 | + } |
| 477 | + |
| 478 | + /** |
| 479 | + * returns the configured script path if set. |
| 480 | + * Otherwise the value of $wgScriptPath |
| 481 | + * |
| 482 | + * @return String |
| 483 | + */ |
| 484 | + public function getScriptPath() { |
| 485 | + if( isset( $this->siteOpt['scriptpath'] ) ) { |
| 486 | + $scriptPath = $this->siteOpt['scriptpath']; |
| 487 | + } else { |
| 488 | + global $wgScriptPath; |
| 489 | + $scriptPath = $wgScriptPath; |
| 490 | + } |
| 491 | + return str_replace( "\\", "/", trim( $scriptPath ) ); |
| 492 | + } |
| 493 | + |
| 494 | + /** |
| 495 | + * returns the config mode this farm member uses to be selected as active wiki. |
| 496 | + * |
| 497 | + * @return Integer flag self::CFG_MODE_SCRIPTPATH, self::CFG_MODE_ADDRESS or |
| 498 | + * self::CFG_MODE_NONE if not set up properly. |
| 499 | + */ |
| 500 | + public function getCfgMode() { |
| 501 | + if( isset( $this->siteOpt['scriptpath'] ) ) |
| 502 | + return self::CFG_MODE_SCRIPTPATH; |
| 503 | + elseif( isset( $this->siteOpt['addresses'] ) ) |
| 504 | + return self::CFG_MODE_ADDRESS; //address can be given even if 'scriptpath' is |
| 505 | + else |
| 506 | + return self::CFG_MODE_NONE; |
| 507 | + } |
| 508 | + |
| 509 | + /** |
| 510 | + * Whether or not this member wiki has been declared the main member. |
| 511 | + * The main member is important for maintenance reasons only. |
| 512 | + * |
| 513 | + * @return Boolean |
| 514 | + */ |
| 515 | + public function isMainMember() { |
| 516 | + return ( $this->getDB() === SimpleFarm::getMainMember()->getDB() ); |
| 517 | + } |
| 518 | + |
| 519 | + /** |
| 520 | + * Whether the farm member wiki is the wiki currently accessed in this run. |
| 521 | + * |
| 522 | + * @return Boolean |
| 523 | + */ |
| 524 | + public function isActive() { |
| 525 | + return ( $this->getDB() === SimpleFarm::getActiveMember()->getDB() ); |
| 526 | + } |
| 527 | + |
| 528 | + /** |
| 529 | + * Returns an value previously set for this object via $wgSimpleFarmMembers configuration. |
| 530 | + * |
| 531 | + * @param $name String name of the array key representing an option |
| 532 | + * within the $wgSimpleFarmMembers sub-array for this object |
| 533 | + * @param $default Mixed default value if config key $name was not set for farm member |
| 534 | + * |
| 535 | + * @return Mixed |
| 536 | + */ |
| 537 | + public function getCfgOption( $name, $default = null ) { |
| 538 | + if( array_key_exists( $name, $this->siteOpt ) ) |
| 539 | + return $this->siteOpt[ $name ]; |
| 540 | + else |
| 541 | + return $default; |
| 542 | + } |
| 543 | + |
| 544 | + /** |
| 545 | + * Same as SimpleFarmMember::getCfgOption |
| 546 | + * |
| 547 | + * @return Mixed |
| 548 | + */ |
| 549 | + public function __invoke( $name, $default = null ) { |
| 550 | + return $this->getCfgOption( $name, $default ); |
| 551 | + } |
| 552 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/SimpleFarm/SimpleFarm_Classes.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 553 | + native |
Index: trunk/extensions/SimpleFarm/maintenance/maintainFarm.php |
— | — | @@ -0,0 +1,122 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Connect with one 'Simple Farm' farm member and start several maintenance |
| 5 | + * scripts from there to maintain several farm members at the same time. |
| 6 | + * |
| 7 | + * @file maintainFarm.php |
| 8 | + * @ingroup Maintenance |
| 9 | + * @ingroup SimpleFarm |
| 10 | + * |
| 11 | + * @author Daniel Werner < danweetz@web.de > |
| 12 | + */ |
| 13 | + |
| 14 | +require_once dirname( __FILE__ ) . '/../../../maintenance/Maintenance.php'; |
| 15 | +require_once dirname( __FILE__ ) . '/../SimpleFarm_Classes.php'; |
| 16 | + |
| 17 | +/** |
| 18 | + * 'Simple Farm' maintenance handler |
| 19 | + * |
| 20 | + * @since 0.1 |
| 21 | + */ |
| 22 | +class SimpleFarmerMaintenance extends Maintenance { |
| 23 | + |
| 24 | + function __construct() { |
| 25 | + SimpleFarm::$maintenanceIsRunning = true; |
| 26 | + parent::__construct(); |
| 27 | + |
| 28 | + $this->mDescription = "'Simple Farm' farmer script for maintaining several farm members with just one command"; |
| 29 | + $this->addOption( 'farmexclude', 'comma-separated list of wikis to deselect for farming (by database name)' ); |
| 30 | + $this->addOption( 'farmonly', 'comma-separated list of wikis to select for doing some farming on (by database name)' ); |
| 31 | + $this->addOption( 'farmpreview', 'if set this will output all selected wikis for farming without running any further action' ); |
| 32 | + $this->addArg( 'command', 'the command-line command to execute for each selected farm member', false ); |
| 33 | + } |
| 34 | + |
| 35 | + public function getDbType() { |
| 36 | + return Maintenance::DB_NONE; |
| 37 | + } |
| 38 | + |
| 39 | + public function execute() { |
| 40 | + global $egSimpleFarmWikiSelectEnvVarName; |
| 41 | + |
| 42 | + $prfx = '~~ '; // prefix in front of each line to make an optical difference to passthru scripts |
| 43 | + |
| 44 | + $selected = SimpleFarm::getMembers(); |
| 45 | + $totalMembers = count( $selected ); |
| 46 | + |
| 47 | + // farmexclude: |
| 48 | + $excluded = preg_split( '/\s*,\s*/', trim( $this->getOption( 'farmexclude', '' ) ) ); |
| 49 | + $selected = array_diff_key( $selected, array_flip( $excluded ) ); |
| 50 | + |
| 51 | + // farmonly: |
| 52 | + if( $this->getOption( 'farmonly' ) !== null ) { |
| 53 | + $only = preg_split( '/\s*,\s*/', trim( $this->getOption( 'farmonly', '' ) ) ); |
| 54 | + $selected = array_intersect_key( $selected, array_flip( $only ) ); |
| 55 | + } |
| 56 | + |
| 57 | + // information about how many are selected: |
| 58 | + $selectedMembers = count( $selected ); |
| 59 | + if( $selectedMembers === 0 ) { |
| 60 | + $this->output( "\n{$prfx}None of the $totalMembers farm members are selected!" ); |
| 61 | + } |
| 62 | + elseif( $totalMembers === $selectedMembers ) { |
| 63 | + $this->output( "\n{$prfx}All $totalMembers members of the farm are selected:" ); |
| 64 | + } |
| 65 | + else { |
| 66 | + $this->output( "\n{$prfx}The following $selectedMembers out of $totalMembers farm members are selected:" ); |
| 67 | + } |
| 68 | + |
| 69 | + // print selected members: |
| 70 | + $longest = 0; |
| 71 | + foreach( $selected as $member ) { |
| 72 | + if( ( $currLen = strlen( $member->getName() ) ) > $longest ) |
| 73 | + $longest = $currLen; |
| 74 | + } |
| 75 | + |
| 76 | + $i = 0; |
| 77 | + foreach( $selected as $member ) { |
| 78 | + $i++; |
| 79 | + $this->output( "\n{$prfx}($i) Name: " . str_pad( "'{$member->getName()}'", $longest + 2, ' ', STR_PAD_RIGHT ) . " DB: '{$member->getDB()}'" ); |
| 80 | + } |
| 81 | + |
| 82 | + // if we are in preview mode, just display what has been selected and end: |
| 83 | + if( $this->getOption( 'farmpreview' ) !== null ) |
| 84 | + return; // no further action |
| 85 | + |
| 86 | + // finally, run the script for each: |
| 87 | + $script = $this->getArg( 0 ); |
| 88 | + if( $script === null ) { |
| 89 | + $this->error( "\n{$prfx}No maintaining command has been set! Argument <command> required!", true ); |
| 90 | + } |
| 91 | + |
| 92 | + $this->output( "\n\n{$prfx}Start maintaining selected 'Simple Farm' members:" ); |
| 93 | + $this->output( "\n{$prfx}Command: '{$script}'" ); |
| 94 | + $i = 0; |
| 95 | + $failed = 0; |
| 96 | + |
| 97 | + foreach( $selected as $member ) { |
| 98 | + $i++; |
| 99 | + $this->output( "\n\n{$prfx}({$i}/{$selectedMembers}) Running command for member '{$member->getName()}' ({$egSimpleFarmWikiSelectEnvVarName}={$member->getDB()})...\n\n" ); |
| 100 | + putenv( SIMPLEFARM_ENVVAR . "={$member->getDB()}" ); |
| 101 | + $fail = null; |
| 102 | + passthru( $script, $fail ); |
| 103 | + if( $fail ) { // value other than 0 implies an error |
| 104 | + $failed++; |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + $this->output( "\n\n{$prfx}Finished! All {$selectedMembers} selected 'Simple Farm' members have been maintained" ); |
| 109 | + |
| 110 | + // check whether we had any errors along the way: |
| 111 | + if( $failed > 0 ) { |
| 112 | + // send out some warning! |
| 113 | + $this->output( "\n{$prfx}WARNING: {$failed} of the command executions have returned a value implying an error. See output above!\n" ); |
| 114 | + } |
| 115 | + else { |
| 116 | + // success! |
| 117 | + $this->output( "\n{$prfx}Apparently, no error has occured during command executions :-)\n" ); |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +$maintClass = 'SimpleFarmerMaintenance'; |
| 123 | +require_once( RUN_MAINTENANCE_IF_MAIN ); |
Property changes on: trunk/extensions/SimpleFarm/maintenance/maintainFarm.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 124 | + native |
Index: trunk/extensions/SimpleFarm/SimpleFarm.i18n.php |
— | — | @@ -0,0 +1,27 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Internationalization file of the 'Simple Farm' extension. |
| 6 | + * |
| 7 | + * @since 0.1 |
| 8 | + * |
| 9 | + * @file SimpleFarm.i18n.php |
| 10 | + * @ingroup SimpleFarm |
| 11 | + * @author Daniel Werner < danweetz@web.de > |
| 12 | + */ |
| 13 | + |
| 14 | +$messages = array(); |
| 15 | + |
| 16 | +/** English |
| 17 | + * @author Daniel Werner |
| 18 | + */ |
| 19 | +$messages['en'] = array( |
| 20 | + 'parserfun-desc' => 'Simple, yet powerfull wiki farm extension without any fancy configuration pages.', |
| 21 | +); |
| 22 | + |
| 23 | +/** German (Deutsch) |
| 24 | + * @author Daniel Werner |
| 25 | + */ |
| 26 | +$messages['de'] = array( |
| 27 | + 'parserfun-desc' => 'Einfache, jedoch trotzdem mächtige Wiki-Farm-Erweiterung ohne großartige Konfigurations-Seiten.', |
| 28 | +); |
Property changes on: trunk/extensions/SimpleFarm/SimpleFarm.i18n.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 29 | + native |
Index: trunk/extensions/SimpleFarm/SimpleFarm.php |
— | — | @@ -0,0 +1,78 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * A simple farm extension which allows to direct different urls to different wikis which |
| 5 | + * are all using the same MediaWiki base installation and LocalSettings.php |
| 6 | + * Also comes with a useful maintenance script allowing to maintain several farm members |
| 7 | + * with just one command-line command. |
| 8 | + * |
| 9 | + * Documentation: http://www.mediawiki.org/wiki/Extension:Simple_Farm |
| 10 | + * Support: http://www.mediawiki.org/wiki/Extension_talk:Simple_Farm |
| 11 | + * Source code: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/SubpageFun |
| 12 | + * |
| 13 | + * @version: 0.1rc |
| 14 | + * @license: ISC license |
| 15 | + * @author: Daniel Werner < danweetz@web.de > |
| 16 | + * |
| 17 | + * @file SimpleFarm.php |
| 18 | + * @ingroup SimpleFarm |
| 19 | + */ |
| 20 | + |
| 21 | +$wgExtensionCredits['other'][] = array( |
| 22 | + 'path' => __FILE__, |
| 23 | + 'name' => 'Simple Farm', |
| 24 | + 'descriptionmsg' => 'simplefarm-desc', |
| 25 | + 'version' => ExtSimpleFarm::VERSION, |
| 26 | + 'author' => '[http://www.mediawiki.org/wiki/User:Danwe Daniel Werner]', |
| 27 | + 'url' => 'http://www.mediawiki.org/wiki/Extension:SimpleFarm', |
| 28 | +); |
| 29 | + |
| 30 | +// language file for extension description: |
| 31 | +$wgExtensionMessagesFiles['SimpleFarm'] = ExtSimpleFarm::getDir() . '/SimpleFarm.i18n.php'; |
| 32 | +/* |
| 33 | + * We don't use $wgExtensionMessagesFiles for more messages since most of this extension happens during |
| 34 | + * localsettings.php , so we can't even be sure the global wiki functions we need for it are available |
| 35 | + * already... This means we output error messages if no wiki farm member was found in English always! |
| 36 | + */ |
| 37 | + |
| 38 | +// load default settings: |
| 39 | +require ExtSimpleFarm::getDir() . '/SimpleFarm_Settings.php'; |
| 40 | + |
| 41 | +// include required classes (not in this file because of maintenance script implications): |
| 42 | +require_once ExtSimpleFarm::getDir() . '/SimpleFarm_Classes.php'; |
| 43 | + |
| 44 | +/* |
| 45 | + * It's no good, at that stage it is too late to set some global variables like $wgScriptPath |
| 46 | + * because some others depend on it and being set at the beginning of setup.php after default |
| 47 | + * settings and LocalSettings are loaded. |
| 48 | + * That's also why we can't rely on global functions including hook an message system! |
| 49 | + */ |
| 50 | +// make sure to initialise farm if not manually in 'LocalSettings.php': |
| 51 | +// $wgHooks['ParserFirstCallInit'][] = 'SimpleFarm::init'; |
| 52 | + |
| 53 | +/** |
| 54 | + * 'Ext...' class representing the 'Simple Farm' extension. |
| 55 | + */ |
| 56 | +class ExtSimpleFarm { |
| 57 | + |
| 58 | + /** |
| 59 | + * Version of the 'Simple Farm' extension. |
| 60 | + * |
| 61 | + * @since 0.1 |
| 62 | + */ |
| 63 | + const VERSION = '0.1rc'; |
| 64 | + |
| 65 | + /** |
| 66 | + * Returns the extensions base installation directory. |
| 67 | + * |
| 68 | + * @since 0.1 |
| 69 | + * |
| 70 | + * @return boolean |
| 71 | + */ |
| 72 | + public static function getDir() { |
| 73 | + static $dir = null; |
| 74 | + if( $dir === null ) { |
| 75 | + $dir = dirname( __FILE__ ); |
| 76 | + } |
| 77 | + return $dir; |
| 78 | + } |
| 79 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/SimpleFarm/SimpleFarm.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 80 | + native |
Index: trunk/extensions/SimpleFarm/SimpleFarm_Settings.php |
— | — | @@ -0,0 +1,95 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * File defining the settings for the 'Simple Farm' extension. |
| 5 | + * More info can be found at http://www.mediawiki.org/wiki/Extension:Simple_Farm#Configuration |
| 6 | + * |
| 7 | + * NOTICE: |
| 8 | + * ======= |
| 9 | + * Changing one of these settings can be done by copying and placing |
| 10 | + * it in LocalSettings.php, AFTER the inclusion of 'Simple Farm'. |
| 11 | + * |
| 12 | + * @file SimpleFarm_Settings.php |
| 13 | + * @ingroup SimpleFarm |
| 14 | + * @since 0.1 |
| 15 | + * |
| 16 | + * @author Daniel Werner < danweetz@web.de > |
| 17 | + */ |
| 18 | + |
| 19 | +/** |
| 20 | + * Config var to define members of the wiki farm. The following associative Array keys are obligatory: |
| 21 | + * db - String database name |
| 22 | + * name - String Wiki name, for $wgSitename |
| 23 | + * AND: |
| 24 | + * ( |
| 25 | + * addresses - String|String[] one or more server names associated with the wiki. Can be used for |
| 26 | + * sub-domain based farm members like 'farm1.mw.org', 'farm2.mw.org'... , 'farmX.mw.org' |
| 27 | + * If this method is chosen, $wgScriptPath must be set in 'LocalSettings.php'. |
| 28 | + * Even if this method is not chosen, this should contain one address anyway. |
| 29 | + * AND/OR: |
| 30 | + * scriptpath - String (Virtual) script path to the particular wiki directory. Can be used for mod-rewrite |
| 31 | + * based wiki farm setup. This will set the $wgScriptPath variable. There should be a |
| 32 | + * '.htaccess' file in the parent directory of the farm to redirect all paths to the farm |
| 33 | + * directory. Example: 'mw.org/farm1', 'mw.org/farm2'... , 'mw.org/farmX', for the first |
| 34 | + * one the value would be '/farm1', for the second '/farm2' and so on. |
| 35 | + * ) |
| 36 | + * |
| 37 | + * The following keys are optional: |
| 38 | + * maintain - Flag whether or not wiki is in maintaining mode right now (optional) |
| 39 | + * The following flags are allowed: |
| 40 | + * - SimpleFarm::MAINTAIN_OFF / false |
| 41 | + * - SimpleFarm::MAINTAIN_SIMPLE |
| 42 | + * - SimpleFarm::MAINTAIN_STRICT |
| 43 | + * - SimpleFarm::MAINTAIN_TOTAL |
| 44 | + * |
| 45 | + * Furthermore, it is possible to add custom keys with additional information for each farm member. These information |
| 46 | + * can be accessed later via $simpleFarmMember->getCfgOption() or $simpleFarmMember() (object-function only in PHP 5.3+) |
| 47 | + * |
| 48 | + * @var Array[] |
| 49 | + */ |
| 50 | +$egSimpleFarmMembers = array(); |
| 51 | + |
| 52 | +/** |
| 53 | + * Config var database of one of $wgSimpleFarmMembers wikis. |
| 54 | + * If null, it will be set when SimpleFarm::init() was called. The default value is the first key |
| 55 | + * of $wgSimpleFarmMembers then. |
| 56 | + * This main member is important for maintenance since the generic maintenance script will connect |
| 57 | + * to the main member first to have full basic MediaWiki maintenance support. |
| 58 | + * |
| 59 | + * @var String |
| 60 | + */ |
| 61 | +$egSimpleFarmMainMemberDB = null; |
| 62 | + |
| 63 | +/** |
| 64 | + * allowed sub-domain prefixes that should be ignored when set in front of the |
| 65 | + * allowed addresses per farm member. |
| 66 | + * |
| 67 | + * @var String[] |
| 68 | + */ |
| 69 | +$egSimpleFarmIgnoredDomainPrefixes = array( 'www' ); |
| 70 | + |
| 71 | +/** |
| 72 | + * Name of the environment variable used to select a wiki via command-line access. |
| 73 | + * The value will be put into constant 'SIMPLEFARM_ENVVAR' before final initialisation. |
| 74 | + * |
| 75 | + * @var Strings |
| 76 | + */ |
| 77 | +$egSimpleFarmWikiSelectEnvVarName = 'WIKI'; |
| 78 | + |
| 79 | +/** |
| 80 | + * Callback to call a specific function in case no wiki has been found from the requested |
| 81 | + * address (not in case of command-line acces though!). |
| 82 | + * If the return value of this function is of type SimpleFarmMember, this wiki will be loaded. |
| 83 | + * Otherwise the default message will be shown. |
| 84 | + * Possible value to load the default wiki if no wiki was found would be the callback |
| 85 | + * string 'SimpleFarm::getMainMember'. |
| 86 | + * |
| 87 | + * @var Callback |
| 88 | + */ |
| 89 | +$egSimpleFarmErrorNoMemberFoundCallback = null; |
| 90 | + |
| 91 | +/** |
| 92 | + * @ToDo: More hook-like callback functions, perhaps collected into array $wgSimpleFarmCallbacks |
| 93 | + * since we can't use MW hook system which is not loaded when farm is being initialized |
| 94 | + * during localsettings. |
| 95 | + * Example: $wgSimpleFarmCallbacks['NoMemberFound'] |
| 96 | + */ |
\ No newline at end of file |
Property changes on: trunk/extensions/SimpleFarm/SimpleFarm_Settings.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 97 | + native |
Index: trunk/extensions/SimpleFarm/RELEASE-NOTES |
— | — | @@ -0,0 +1,7 @@ |
| 2 | + Changelog: |
| 3 | + ========== |
| 4 | + * (trunk) -- Version 0.1 (initial release) |
| 5 | + - Automatic forwarding to one of several farm member wikis which are all using |
| 6 | + the same physical MediaWiki installation. |
| 7 | + - Maintenance script allowing to maintain several wikis at the same time. |
| 8 | + - Object oriented classes, representing the farm itself as well as its members. |
\ No newline at end of file |
Property changes on: trunk/extensions/SimpleFarm/RELEASE-NOTES |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 9 | + native |
Index: trunk/extensions/SimpleFarm/COPYING |
— | — | @@ -0,0 +1,13 @@ |
| 2 | +Copyright (c) 2011 by Daniel Werner < danweetz@web.de > |
| 3 | + |
| 4 | +Permission to use, copy, modify, and/or distribute this software for any |
| 5 | +purpose with or without fee is hereby granted, provided that the above |
| 6 | +copyright notice and this permission notice appear in all copies. |
| 7 | + |
| 8 | +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 9 | +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 10 | +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 11 | +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 12 | +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 13 | +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 14 | +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
\ No newline at end of file |
Property changes on: trunk/extensions/SimpleFarm/COPYING |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 15 | + native |
Index: trunk/extensions/SimpleFarm/README |
— | — | @@ -0,0 +1,37 @@ |
| 2 | +== About == |
| 3 | + |
| 4 | +'Simple Farm' is a simple MediaWiki farm extension by Daniel Werner. It |
| 5 | +automatically forwards to the wiki that should be accessed by either a |
| 6 | +virtual script path (using a tiny bit of mod-rewrite) or by using sub-domains. |
| 7 | +It is also possible to have members of the same farm using either of these |
| 8 | +methods, not all members have to use the same. |
| 9 | +Furthermore, 'Simple Farm' comes with a maintenance script which allows to |
| 10 | +maintain several farm members at the same time, e.g. doing database update |
| 11 | +for all or certain farm members by executing just one command. |
| 12 | + |
| 13 | +* Website: http://www.mediawiki.org/wiki/Extension:Simple_Farm |
| 14 | +* License: ISC license |
| 15 | +* Author: Daniel Werner < danweetz@web.de > |
| 16 | + |
| 17 | + |
| 18 | +== Installation == |
| 19 | + |
| 20 | +Once you have downloaded the code, place the 'SimpleFarm' directory within your |
| 21 | +MediaWiki 'extensions' directory. Then add the following code to your |
| 22 | +[[Manual:LocalSettings.php|LocalSettings.php]] file: |
| 23 | + |
| 24 | + # Simple Farm |
| 25 | + require_once( "$IP/extensions/SimpleFarm/SimpleFarm.php" ); |
| 26 | + /* ... SimpleFarm configuration ... */ |
| 27 | + SimpleFarm::init(); |
| 28 | + /* ... chosen member settings ... */ |
| 29 | + |
| 30 | +Please see http://www.mediawiki.org/wiki/Extension:Simple_Farm#Configuration for details. |
| 31 | + |
| 32 | + |
| 33 | +== Contributing == |
| 34 | + |
| 35 | +If you have bug reports or requests, please add them to the 'Simple Farm' Talk page [0]. |
| 36 | +You can also send them to Daniel Werner < danweetz@web.de > |
| 37 | + |
| 38 | +[0] http://www.mediawiki.org/w/index.php?title=Extension_talk:Simple_Farm |
Property changes on: trunk/extensions/SimpleFarm/README |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 39 | + native |