1<?php
2/*
3 * Gallery - a web based photo album viewer and editor
4 * Copyright (C) 2000-2008 Bharat Mediratta
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 (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21/**
22 * Extra, rarely used core module code.  Most modules will not need to push their extra code into a
23 * separate class, but the core module has a lot of install code that is very rarely used so we tuck
24 * it out of the way.
25 *
26 * @package GalleryCore
27 * @author Bharat Mediratta <bharat@menalto.com>
28 * @version $Revision: 20996 $
29 * @static
30 */
31class CoreModuleExtras {
32
33    /**
34     * @see GalleryModule::upgrade
35     * @param GalleryModule $module the core module
36     * @param string $currentVersion the current installed version
37     */
38    function upgrade($module, $currentVersion, $statusMonitor) {
39	global $gallery;
40	$storage =& $gallery->getStorage();
41	$platform =& $gallery->getPlatform();
42	$gallery->debug('Entering CoreModuleExtras::upgrade');
43
44	/*
45	 * We store our version outside of the database so that we can upgrade even if the database
46	 * is in an undependable state
47	 */
48	$versions = $module->getInstalledVersions();
49	$currentVersion = $versions['core'];
50	if (!isset($currentVersion)) {
51	    $gallery->debug('Current version not set');
52	    /*
53	     * This is either an initial install or an upgrade from version 0.8 (which didn't have
54	     * the core versions.dat file).  Use a module parameter as our acid test.
55	     *
56	     * @todo Get rid of this when we stop supporting upgrades from alphas
57	     */
58	    list ($ret, $paramValue) = $module->getParameter('permissions.directory');
59	    if (isset($paramValue)) {
60		$currentVersion = '0.8';
61	    } else {
62		$currentVersion = '0';
63	    }
64	}
65	$gallery->debug('Old version: ' . $currentVersion
66			. '   New version: ' . $module->getVersion());
67	$currentExactVersion = $currentVersion;
68	/* Enable upgrade from any patch release of earlier versions */
69	$currentVersion = preg_replace('/^(1\.[0-3]\.0)\.\d+$/', '$1.x', $currentVersion);
70
71	/*
72	 * We converted the character set for MySQL to UTF8 in version 1.0.27, but this only
73	 * applies if you are running MySQL 4.  If you install MySQL 4 after upgrading past 1.0.27
74	 * then you'd get stuck with the non-UTF8 character set and you'd get scrambled output.
75	 * To remedy this, we check here to see if you're using a version more recent than 1.0.26
76	 * and if so, we perform the conversion now.  Otherwise, we perform the conversion in-line
77	 * in the 1.0.26 upgrade block below.
78	 */
79	if (version_compare($currentVersion, '1.0.26', '>')) {
80	    list ($ret, $converted) =
81		CoreModuleExtras::convertCharacterSetToUtf8($module, $statusMonitor);
82	    if ($ret) {
83		return $ret;
84	    }
85	}
86
87	/*
88	 * Store Entities.inc and Maps.inc definitions in the Schema table. Do this upgrade before
89	 * the general upgrade code to ensure that all map methods work for the other upgrade code.
90	 * (but skip if upgrading from 1.2.0.4 or newer 1.2.0.x, as this upgrade was done in 2.2.2)
91	 */
92	if (version_compare($currentVersion, '1.2.18', '<') && !($currentVersion == '1.2.0.x'
93		    && version_compare($currentExactVersion, '1.2.0.4', '>='))) {
94	    $ret = $storage->configureStore($module->getId(), array('Schema:1.0', 'Schema:1.1'));
95	    if ($ret) {
96		return $ret;
97	    }
98
99	    list ($ret, $modules) = GalleryCoreApi::fetchPluginStatus('module', true);
100	    if ($ret) {
101		return $ret;
102	    }
103
104	    $count = 1;
105	    $total = count($modules);
106	    $statusText = $module->translate('Converting Schema Table');
107	    $gallery->guaranteeTimeLimit(30);
108	    foreach ($modules as $moduleId => $moduleStatus) {
109		/* Skip uninstalled/unavailable modules */
110		if (!isset($moduleStatus['active']) || empty($moduleStatus['available'])) {
111		    continue;
112		}
113
114		$ret = $storage->updateTableInfo($moduleId);
115		if ($ret) {
116		    return $ret;
117		}
118
119		$gallery->guaranteeTimeLimit(30);
120		$ret = $statusMonitor->renderStatusMessage($statusText, '', $count++ / $total);
121		if ($ret) {
122		    return $ret;
123		}
124	    }
125	    $ret = $storage->checkPoint();
126	    if ($ret) {
127		return $ret;
128	    }
129	}
130
131	if (version_compare($currentVersion, '1.2.5', '<')) {
132	    $ret = $storage->configureStore($module->getId(), array('GalleryMimeTypeMap:1.0'));
133	    if ($ret) {
134		return $ret;
135	    }
136	}
137
138	/**
139	 * README: How to update the block below
140	 *
141	 * If you add a new feature to the core module and revise the version, you should do the
142	 * following.  Supposing the current version is 1.0.1 and you're adding 1.0.2.  Go to the
143	 * end of the switch and find the 'end of upgrade path' case.  Create a new case *above*
144	 * that one with the old version number.  For our example you'd add: "case '1.0.1':" and
145	 * then your code.  Do *not* put in a break statement.  (Update _prepareConfigUpgrade too).
146	 */
147	$gallery->debug(sprintf('The current version is %s', $currentVersion));
148	$progressText = $module->translate('Installing the core module');
149	switch ($currentVersion) {
150	case '0':
151	    $gallery->debug('Install core module');
152	    /*
153	     * Checkpoint (commit configureStore transaction)
154	     * Later in the installation code, we create the root album and therefore need locking.
155	     * Locks are acquired with a non-transactional db connection.  So before we can query
156	     * the db with a second connection, the INSERT id into SequenceLock needs to be
157	     * committed.  Related bug 1235284.
158	     */
159	    $ret = $storage->checkPoint();
160	    if ($ret) {
161		return $ret;
162	    }
163
164	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.15);
165	    if ($ret) {
166		return $ret;
167	    }
168	    $gallery->guaranteeTimeLimit(180);
169
170	    if (GalleryUtilities::isA($platform, 'WinNtPlatform')) {
171		$flockType = 'database';
172	    } else {
173		$fileToLock = $platform->fopen(__FILE__, 'r');
174		$wouldBlock = false;
175		if ($platform->flock($fileToLock, LOCK_SH, $wouldBlock) || $wouldBlock) {
176		    $flockType = 'flock';
177		} else {
178		    $flockType = 'database';
179		}
180		$platform->fclose($fileToLock);
181	    }
182	    $gallery->debug(sprintf('Locktype %s selected', $flockType));
183	    /* Initial install.  Make sure all our module parameters are set. */
184	    $gallery->debug('Set core module parameters');
185
186	    GalleryCoreApi::requireOnce('modules/core/classes/GalleryTranslator.class');
187	    list ($ret, $defaultLanguage) = $gallery->getActiveLanguageCode();
188	    if ($ret) {
189		$defaultLanguage = GalleryTranslator::getLanguageCodeFromRequest();
190	    }
191	    foreach (array('permissions.directory' => '0755',
192			   'permissions.file' => '0644',
193			   'exec.expectedStatus' => '0',
194			   'exec.beNice' => '0',
195			   'default.orderBy' => 'orderWeight',
196			   'default.orderDirection' => '1',
197			   'default.theme' => 'matrix',
198			   'default.language' => $defaultLanguage,
199			   'language.useBrowserPref' => '0',
200			   'default.newAlbumsUseDefaults' => 'false',
201			   'session.lifetime' => 21 * 86400, /* Three weeks */
202			   'session.inactivityTimeout' => 7 * 86400, /* One week */
203			   'session.siteAdministrationTimeout' => 30 * 60, /* 30 minutes */
204			   'misc.markup' => 'bbcode',
205			   'lock.system' => $flockType,
206			   'format.date' => '%x',
207			   'format.time' => '%X',
208			   'format.datetime' => '%c',
209			   'repository.updateTime' => '0',
210			   'acceleration' => serialize(array('guest' => array('type' => 'none'),
211							     'user' => array('type' => 'none'))),
212			   'smarty.compile_check' => '0',
213			   'validation.level' => 'MEDIUM',
214			   'core.repositories' => serialize(array('released' => 1)),
215			   ) as $key => $value) {
216		if (!isset($param[$key])) {
217		    $ret = $module->setParameter($key, (string)$value);
218		    if ($ret) {
219			$gallery->debug(sprintf('Error: Failed to set core module parameter %s, ' .
220						'this is the error stack trace: %s', $key,
221						$ret->getAsText()));
222			return $ret;
223		    }
224		}
225	    }
226
227	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.2);
228	    if ($ret) {
229		return $ret;
230	    }
231	    $gallery->guaranteeTimeLimit(180);
232
233	    /* Activate the default theme */
234	    $gallery->debug('Load default theme');
235	    list ($ret, $themeList) = GalleryCoreApi::fetchPluginStatus('theme');
236	    if ($ret) {
237		return $ret;
238	    }
239	    $defaultThemeId = 'matrix';
240	    if (empty($themeList[$defaultThemeId]['available'])) {
241		$gallery->debug(sprintf('Warning: %s theme is not available. Trying to fall ' .
242			'back to another theme.', $defaultThemeId));
243		$defaultThemeId = null;
244		foreach ($themeList as $themeId => $themeInfo) {
245		    if (!empty($themeInfo['available'])) {
246			$defaultThemeId = $themeId;
247			break;
248		    }
249		}
250		if (empty($defaultThemeId)) {
251		    return GalleryCoreApi::error(ERROR_UNKNOWN, __FILE__, __LINE__,
252				'There is no theme available!');
253		}
254		$ret = $module->setParameter('default.theme', $defaultThemeId);
255		if ($ret) {
256		    return $ret;
257		}
258	    }
259	    $gallery->debug(sprintf('Using %s as default theme', $defaultThemeId));
260
261	    list ($ret, $theme) = GalleryCoreApi::loadPlugin('theme', $defaultThemeId);
262	    if ($ret) {
263		$gallery->debug(sprintf('Error: Failed to load %s theme, this is the error ' .
264				'stack trace; %s', $defaultThemeId, $ret->getAsText()));
265		return $ret;
266	    }
267
268	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.25);
269	    if ($ret) {
270		return $ret;
271	    }
272	    $gallery->guaranteeTimeLimit(180);
273
274	    $gallery->debug('InstallOrUpgrade default theme');
275	    $ret = $theme->installOrUpgrade();
276	    if ($ret) {
277		$gallery->debug(sprintf('Error: Failed to installOrUpgrade %s theme, this is ' .
278				'the error stack trace; %s', $defaultThemeId, $ret->getAsText()));
279		return $ret;
280	    }
281
282	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.3);
283	    if ($ret) {
284		return $ret;
285	    }
286	    $gallery->guaranteeTimeLimit(180);
287
288	    $gallery->debug('Activate default theme');
289	    list ($ret, $ignored) = $theme->activate(false);
290	    if ($ret) {
291		$gallery->debug(sprintf('Error: Failed to activate %s theme, this is ' .
292				'the error stack trace; %s', $defaultThemeId, $ret->getAsText()));
293		return $ret;
294	    }
295
296	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.4);
297	    if ($ret) {
298		return $ret;
299	    }
300	    $gallery->guaranteeTimeLimit(180);
301
302	    /*
303	     * Register our permissions.  Since we're storing internationalized strings in the
304	     * database, we have to give our internationalized string extractor a clue that these
305	     * strings get translated.  So put a line like this translate('key') in for each
306	     * description so that our extractor can find it.
307	     */
308	    $gallery->debug('Register core module permissions');
309	    $permissions[] = array('all', $gallery->i18n('All access'),
310				   GALLERY_PERMISSION_ALL_ACCESS, array());
311	    $permissions[] = array('view', $gallery->i18n('[core] View item'), 0, array());
312	    $permissions[] = array('viewResizes', $gallery->i18n('[core] View resized version(s)'),
313				   0, array());
314	    $permissions[] = array('viewSource', $gallery->i18n('[core] View original version'),
315				   0, array());
316	    $permissions[] = array('viewAll', $gallery->i18n('[core] View all versions'),
317				   GALLERY_PERMISSION_COMPOSITE,
318				   array('core.view', 'core.viewResizes', 'core.viewSource'));
319	    $permissions[] = array('addAlbumItem', $gallery->i18n('[core] Add sub-album'),
320				   0, array());
321	    $permissions[] = array('addDataItem', $gallery->i18n('[core] Add sub-item'),
322				   0, array());
323	    $permissions[] = array('edit', $gallery->i18n('[core] Edit item'), 0, array());
324	    $permissions[] = array('changePermissions',
325				   $gallery->i18n('[core] Change item permissions'), 0, array());
326	    $permissions[] = array('delete', $gallery->i18n('[core] Delete item'), 0, array());
327	    foreach ($permissions as $p) {
328		$ret = GalleryCoreApi::registerPermission(
329		    $module->getId(), 'core.' . $p[0], $p[1], $p[2], $p[3]);
330		if ($ret) {
331		    $gallery->debug(sprintf('Error: Failed to register a permission, ' .
332					    'this is the error stack trace: %s',
333					    $ret->getAsText()));
334		    return $ret;
335		}
336	    }
337
338	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.5);
339	    if ($ret) {
340		return $ret;
341	    }
342	    $gallery->guaranteeTimeLimit(180);
343
344	    foreach (array('_createAccessListCompacterLock',
345			   '_createAllUsersGroup',
346			   '_createSiteAdminsGroup',
347			   '_createEverybodyGroup',
348			   '_createAnonymousUser',
349			   '_createAdminUser',
350			   '_createRootAlbumItem') as $func) {
351
352		$gallery->debug(sprintf('Call user func %s', $func));
353		$ret = call_user_func(array('CoreModuleExtras', $func), $module);
354		if ($ret) {
355		    $gallery->debug(sprintf('Error: %s returned an error, ' .
356					   'this is the error stack trace: %s', $func,
357					   $ret->getAsText()));
358		    return $ret;
359		}
360	    }
361
362	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.6);
363	    if ($ret) {
364		return $ret;
365	    }
366	    $gallery->guaranteeTimeLimit(180);
367
368	    $gallery->debug('Initialize MIME types');
369	    GalleryCoreApi::requireOnce(
370		'modules/core/classes/helpers/GalleryMimeTypeHelper_advanced.class');
371	    $ret = GalleryMimeTypeHelper_advanced::initializeMimeTypes();
372	    if ($ret) {
373		$gallery->debug(sprintf('Error: Failed to initialize MIME types, this is ' .
374					'the error stack trace: %s', $ret->getAsText()));
375		return $ret;
376	    }
377	    $gallery->debug('CoreModulesExtra::upgrade: successfully installed core');
378
379	    $ret = $statusMonitor->renderStatusMessage($progressText, '', 0.7);
380	    if ($ret) {
381		return $ret;
382	    }
383	    $gallery->guaranteeTimeLimit(180);
384	    break;
385
386	case '0.8':
387	    $gallery->debug('Warning: Upgrading from version 0.8 (not supported)');
388	case '0.8.1':
389	case '0.8.2':
390	    /*
391	     * Update our framework module parameters to have a leading underscore so that we have
392	     * our own separate namespace
393	     */
394	    $query = '
395	    UPDATE
396	       [GalleryPluginParameterMap]
397	    SET
398	       [::parameterName] = ?
399	    WHERE
400	       [GalleryPluginParameterMap::parameterName] = ?
401	       AND
402	       [GalleryPluginParameterMap::pluginType] = \'module\'
403	       AND
404	       [GalleryPluginParameterMap::itemId] = 0
405	    ';
406	    $ret = $storage->execute($query, array('_version', 'version'));
407	    if ($ret) {
408		return $ret;
409	    }
410
411	    $ret = $storage->execute($query, array('_callbacks', 'callbacks'));
412	    if ($ret) {
413		return $ret;
414	    }
415
416	    /* Added a new parameter */
417	    $ret = $module->setParameter('misc.login', 'both');
418	    if ($ret) {
419		return $ret;
420	    }
421
422	case '0.8.3':
423	case '0.8.4':
424	case '0.8.5':
425	    /* Added GalleryItem::originationTimestamp */
426	    $ret = $storage->configureStore($module->getId(), array('GalleryItem:1.0'));
427	    if ($ret) {
428		return $ret;
429	    }
430
431	    /* Copy viewedSinceTimestamp to originationTimestamp as both default to time() */
432	    $query = '
433	    UPDATE
434	      [GalleryItem]
435	    SET
436	      [::originationTimestamp] = [::viewedSinceTimestamp]
437	    ';
438	    $ret = $storage->execute($query, array());
439	    if ($ret) {
440		return $ret;
441	    }
442
443	case '0.8.6':
444	case '0.8.7':
445	    $ret = $module->setParameter('default.newAlbumsUseDefaults', 'false');
446	    if ($ret) {
447		return $ret;
448	    }
449
450	case '0.8.8':
451	    /*
452	     * This was not originally part of the 0.8.9 upgrade, but added much later.  Upgrade
453	     * code after this will need valid factory registrations so we can't wait until
454	     * upgrade() completes to register during reactivate().
455	     */
456	    $ret = CoreModuleExtras::performFactoryRegistrations($module);
457	    if ($ret) {
458		return $ret;
459	    }
460
461	case '0.8.9':
462	    /*
463	     * Set all factory implementation weights to 5.  We'll re-register all core
464	     * implementations with a weight of 4 so that they get precedence.
465	     */
466	    $query = 'UPDATE [GalleryFactoryMap] SET [::orderWeight] = 5';
467	    $ret = $storage->execute($query, array());
468	    if ($ret) {
469		return $ret;
470	    }
471
472	case '0.8.10':
473	case '0.8.11':
474	case '0.8.12':
475	    $ret = $module->setParameter('lock.system', 'flock');
476	    if ($ret) {
477		return $ret;
478	    }
479
480	case '0.8.13':
481	    /* We used to add layout versioning here.  Now that's been moved to the 0.9.29 block. */
482
483	case '0.8.14':
484	    /* Added Entity::onLoadHandlers; default all values to null */
485	    $ret = $storage->configureStore($module->getId(), array('GalleryEntity:1.0'));
486	    if ($ret) {
487		return $ret;
488	    }
489
490	case '0.8.15':
491	    /* Removed GalleryItemPropertiesMap */
492
493	case '0.8.16':
494	    /* Schema updates: GalleryPluginMap, GalleryPluginParameterMap, GalleryGroup */
495	    $ret = $storage->configureStore($module->getId(),
496		array('GalleryPluginMap:1.0', 'GalleryPluginParameterMap:1.0', 'GalleryGroup:1.0'));
497	    if ($ret) {
498		return $ret;
499	    }
500
501	case '0.8.17':
502	    /* Beta 1! */
503
504	case '0.9.0':
505	    $ret = $module->removeParameter('misc.useShortUrls');
506	    if ($ret) {
507		return $ret;
508	    }
509
510	case '0.9.1':
511	    /* Set Gallery version to 2.0-beta-1+ */
512
513	case '0.9.2':
514	    /* Changed the data cache format */
515
516	case '0.9.3':
517	    /* CSS refactor across entire app */
518
519	case '0.9.4':
520	    $gallery->guaranteeTimeLimit(30);
521	    GalleryCoreApi::requireOnce(
522		'modules/core/classes/helpers/GalleryMimeTypeHelper_advanced.class');
523	    $ret = GalleryMimeTypeHelper_advanced::initializeMimeTypes();
524	    if ($ret) {
525		return $ret;
526	    }
527
528	case '0.9.5':
529	    $gallery->guaranteeTimeLimit(30);
530	    $ret = CoreModuleExtras::_createAccessListCompacterLock($module);
531	    if ($ret) {
532		return $ret;
533	    }
534
535	    /*
536	     * Choose an item that has permission rows.  Find all other items with the same exact
537	     * permissions.  Create a new ACL, assign all those items to the ACL, delete those rows
538	     * from the permissions table.  Repeat.
539	     */
540	    $totalRowsQuery = '
541	    SELECT
542	      COUNT(DISTINCT [GalleryPermissionMap::itemId])
543	    FROM
544	      [GalleryPermissionMap]
545	    ';
546
547	    $findItemIdQuery = '
548	    SELECT
549	      [GalleryPermissionMap::itemId], COUNT(*) AS C
550	    FROM
551	      [GalleryPermissionMap]
552	    GROUP BY
553	      [GalleryPermissionMap::itemId]
554	    ORDER BY
555	      C DESC
556	    ';
557
558	    $permissionRowCountQuery = '
559	    SELECT
560	      COUNT(*)
561	    FROM
562	      [GalleryPermissionMap]
563	    WHERE
564	      [GalleryPermissionMap::itemId] = ?
565	    ';
566
567	    /* Updated this query for core 1.0.11 to write to userOrGroupId column */
568	    $createAclQuery = '
569	    INSERT INTO
570	      [GalleryAccessMap] ([::accessListId], [::userOrGroupId], [::permission])
571	    SELECT
572	      ?,
573	      [GalleryPermissionMap::userId] + [GalleryPermissionMap::groupId],
574	      [GalleryPermissionMap::permission]
575	    FROM
576	      [GalleryPermissionMap]
577	    WHERE
578	      [GalleryPermissionMap::itemId] = ?
579	    ';
580
581	    $findPossibleDupesQuery = '
582	    SELECT
583	      [GalleryPermissionMap=2::itemId], COUNT(*)
584	    FROM
585	      [GalleryPermissionMap=1], [GalleryPermissionMap=2]
586	    WHERE
587	      [GalleryPermissionMap=1::itemId] = ?
588	      AND
589	      [GalleryPermissionMap=1::userId] = [GalleryPermissionMap=2::userId]
590	      AND
591	      [GalleryPermissionMap=1::groupId] = [GalleryPermissionMap=2::groupId]
592	      AND
593	      [GalleryPermissionMap=1::permission] = [GalleryPermissionMap=2::permission]
594	    GROUP BY
595	      [GalleryPermissionMap=2::itemId]
596	    HAVING
597	      COUNT(*) = ?
598	    ';
599
600	    $refineDupesQuery = '
601	    SELECT
602	      [GalleryPermissionMap::itemId], COUNT(*)
603	    FROM
604	      [GalleryPermissionMap]
605	    WHERE
606	      [GalleryPermissionMap::itemId] IN (%s)
607	    GROUP BY
608	      [GalleryPermissionMap::itemId]
609	    HAVING
610	      COUNT(*) = ?
611	    ';
612
613	    $assignAclQuery = '
614	    INSERT INTO
615	      [GalleryAccessSubscriberMap] ([::itemId], [::accessListId])
616	    SELECT DISTINCT
617	      [GalleryPermissionMap::itemId], ?
618	    FROM
619	      [GalleryPermissionMap]
620	    WHERE
621	      [GalleryPermissionMap::itemId] IN (%s)
622	    ';
623
624	    $deleteOldPermsQuery = '
625	    DELETE FROM
626	      [GalleryPermissionMap]
627	    WHERE
628	      [GalleryPermissionMap::itemId] IN (%s)
629	    ';
630
631	    /* Determine how many items we are going to process for our status message */
632	    list ($ret, $results) =
633		$gallery->search($totalRowsQuery, array(), array('limit' => array('count' => 1)));
634	    if ($ret) {
635		return $ret;
636	    }
637	    if ($results->resultCount() == 0) {
638		break;
639	    }
640	    $result = $results->nextResult();
641	    $totalPermissionItems = $result[0];
642
643	    $itemsProcessed = 0;
644	    if ($totalPermissionItems > 0) {
645		$ret = $statusMonitor->renderStatusMessage(
646		    $module->translate('Upgrading permissions'),
647		    null,
648		    $itemsProcessed / $totalPermissionItems);
649		if ($ret) {
650		    return $ret;
651		}
652	    }
653
654	    while ($totalPermissionItems > 0 && true) {
655		$gallery->guaranteeTimeLimit(60);
656
657		/* Find the next item in the permissions table */
658		list ($ret, $results) = $storage->search($findItemIdQuery);
659		if ($ret) {
660		    return $ret;
661		}
662		if ($results->resultCount() == 0) {
663		    break;
664		}
665		$result = $results->nextResult();
666		list ($targetItemId, $permissionRowCount) = array((int)$result[0], (int)$result[1]);
667
668		/* Create a new ACL */
669		list ($ret, $newAclId) = $storage->getUniqueId();
670		if ($ret) {
671		    return $ret;
672		}
673
674		$ret = $storage->execute($createAclQuery, array($newAclId, $targetItemId));
675		if ($ret) {
676		    return $ret;
677		}
678
679		/*
680		 * Find all items that share the same permissions as the target.  I haven't figured
681		 * out a good way to do aggregation without using temporary tables, which I'd like
682		 * to avoid for portability.  So, figure out how many rows have at least as many
683		 * matching permissions as our target item.  These are potentially dupes.  We'll
684		 * refine them later on.
685		 */
686		list ($ret, $results) = $gallery->search(
687		    $findPossibleDupesQuery, array($targetItemId, $permissionRowCount));
688		if ($ret) {
689		    return $ret;
690		}
691		$possibleDupeIds = array();
692		while ($result = $results->nextResult()) {
693		    $possibleDupeIds[] = (int)$result[0];
694		}
695
696		/*
697		 * Process these queries in chunks since we may have thousands of items with the
698		 * same permissions and we don't want to give the database a heart attack
699		 */
700		$chunkSize = 200;
701		while (!empty($possibleDupeIds)) {
702		    $chunk = array_splice($possibleDupeIds, 0, $chunkSize);
703		    $count = count($chunk);
704
705		    /*
706		     * Refine our dupes by eliminating ones that don't have exactly the same number
707		     * of permission rows as our target.  Our target item is included in the dupes,
708		     * so this will always return at least 1 row.
709		     */
710		    $markers = GalleryUtilities::makeMarkers($count);
711		    $query = sprintf($refineDupesQuery, $markers);
712		    list ($ret, $results) = $gallery->search(
713			$query, array_merge($chunk, array($permissionRowCount)));
714		    $possibleDupeIds = array();
715
716		    $dupeIds = array();
717		    while ($result = $results->nextResult()) {
718			$dupeIds[] = (int)$result[0];
719		    }
720
721		    if (empty($dupeIds)) {
722			/* No actual dupes?  Try the next chunk. */
723			continue;
724		    }
725
726		    $count = count($dupeIds);
727		    $markers = GalleryUtilities::makeMarkers($count);
728
729		    /* Set all the dupe items in this chunk to use the new ACL */
730		    $query = sprintf($assignAclQuery, $markers);
731		    $ret = $storage->execute($query, array_merge(array($newAclId), $dupeIds));
732		    if ($ret) {
733			return $ret;
734		    }
735
736		    /* Remove all the permission rows for the migrated items */
737		    $query = sprintf($deleteOldPermsQuery, $markers);
738		    $ret = $storage->execute($query, $dupeIds);
739		    if ($ret) {
740			return $ret;
741		    }
742
743		    $itemsProcessed += $count;
744
745		    $ret = $statusMonitor->renderStatusMessage(
746			$module->translate(array(
747			    'text' => 'Upgrading permissions (%d items complete, %d remaining)',
748			    'arg1' => $itemsProcessed,
749			    'arg2' => $totalPermissionItems - $itemsProcessed)),
750			'',
751			$itemsProcessed / $totalPermissionItems);
752		    if ($ret) {
753			return $ret;
754		    }
755		}
756	    }
757
758	    if ($totalPermissionItems > 0) {
759		$ret = $statusMonitor->renderStatusMessage(
760		    $module->translate('Deleting old permission tables'),
761		    '',
762		    $itemsProcessed / $totalPermissionItems);
763		if ($ret) {
764		    return $ret;
765		}
766	    }
767
768	    /* Removed GalleryPermissionMap */
769
770	case '0.9.6':
771	    /* Added GalleryMaintenance table */
772
773	case '0.9.7':
774	    /*
775	     * Change GalleryMaintenance::details column to be a serialized array.  The old data is
776	     * transient so just delete it.  Added FlushTemplatesTask, FlushDatabaseCacheTask.
777	     */
778	    $gallery->guaranteeTimeLimit(30);
779	    $ret = GalleryCoreApi::removeAllMapEntries('GalleryMaintenanceMap');
780	    if ($ret) {
781		return $ret;
782	    }
783
784	case '0.9.8':
785	    /*
786	     * Create 'plugins' and 'plugins_data' directories in g2data.  Remove trailing slash for
787	     * config paths using substr so file_exists can detect either file or dir.  Update: in
788	     * core 1.0.6 the data.gallery.plugins dir moved under gallery2 basedir, not in g2data
789	     * anymore; we may not have permission to create a dir here.  So code below is now
790	     * updated to not require those mkdirs to succeed.
791	     */
792	    $gallery->guaranteeTimeLimit(30);
793	    foreach (array(substr($gallery->getConfig('data.gallery.plugins'), 0, -1) => false,
794			   $gallery->getConfig('data.gallery.plugins') . 'modules' => false,
795			   $gallery->getConfig('data.gallery.plugins') . 'layouts' => false,
796			   substr($gallery->getConfig('data.gallery.plugins_data'), 0, -1) => true,
797			   $gallery->getConfig('data.gallery.plugins_data') . 'modules' => true,
798			   $gallery->getConfig('data.gallery.plugins_data') . 'layouts' => true)
799		    as $dir => $isRequired) {
800		if ($platform->file_exists($dir)) {
801		    if ($platform->is_dir($dir)) {
802			/* No need to do anything.  Except maybe we could check permissions here. */
803		    } else {
804			/* There's a file there.  There shouldn't be.  Move it out of the way. */
805			if (!@$platform->rename($newDir, "$newDir.old") ||
806				!@$platform->mkdir($dir)) {
807			    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__,
808				"$dir already exists; unable to replace it");
809			}
810		    }
811		} else {
812		    if (!@$platform->mkdir($dir) && $isRequired) {
813			return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__,
814						    "Unable to create $dir");
815		    }
816		}
817	    }
818
819	case '0.9.9':
820	    /* Beta 2 release! */
821
822	case '0.9.10':
823	    /* Added BuildDerivativesTask */
824
825	case '0.9.11':
826	    /* Added GalleryRecoverPasswordMap */
827
828	case '0.9.12':
829	    /* Added ResetViewCountsTask */
830
831	case '0.9.13':
832	    /* Added SystemInfoTask */
833
834	case '0.9.14':
835	    /* Added SetOriginationTimestampTask */
836
837	case '0.9.15':
838	    /* Remove lock subdirs -- this is from 1.1.8->1.1.9 upgrade */
839	    $locksDir = $gallery->getConfig('data.gallery.locks');
840	    if ($platform->file_exists($locksDir)) {
841		@$platform->recursiveRmDir($locksDir);
842	    }
843	    @$platform->mkdir($locksDir);
844
845	    /*
846	     * Changed 'All Users' to 'Registered Users'
847	     * Don't change if the user modified the name already!
848	     * Don't change if there is already a group with the new name
849	     */
850	    list ($ret, $group) =
851		GalleryCoreApi::fetchGroupByGroupName($module->translate('Registered Users'));
852	    if ($ret) {
853		if ($ret->getErrorCode() & ERROR_MISSING_OBJECT) {
854		    /* OK, we can change the group name */
855
856		    list ($ret, $allUserGroupId) = $module->getParameter('id.allUserGroup');
857		    if ($ret) {
858			return $ret;
859		    }
860		    list ($ret, $lockId) = GalleryCoreApi::acquireWriteLock($allUserGroupId);
861		    if ($ret) {
862			return $ret;
863		    }
864		    list ($ret, $group) =
865			GalleryCoreApi::loadEntitiesById($allUserGroupId, 'GalleryGroup');
866		    if ($ret) {
867			return $ret;
868		    }
869		    $allUserGroupName = $group->getGroupName();
870		    /* We used to entitize data in db; expect that from orignal group name: */
871		    $originalGroupName = GalleryUtilities::utf8ToUnicodeEntities(
872			$module->translate('All Users'));
873		    if (!strcmp($allUserGroupName, $originalGroupName)) {
874			$group->setGroupName($module->translate('Registered Users'));
875			$ret = $group->save();
876			if ($ret) {
877			    return $ret;
878			}
879
880			$ret = GalleryCoreApi::releaseLocks($lockId);
881			if ($ret) {
882			    return $ret;
883			}
884		    }
885		} else {
886		    return $ret;
887		}
888	    } /* Else a group with that name already exists, nothing to do */
889
890	case '0.9.16':
891	    /* Beta 3 release! */
892
893	case '0.9.17':
894	    /* Split uploadLocalServer.dirs list into one parameter per entry */
895	    list ($ret, $dirList) = $module->getParameter('uploadLocalServer.dirs');
896	    if ($ret) {
897		return $ret;
898	    }
899	    if (!empty($dirList)) {
900		$dirList = explode(',', $dirList);
901		for ($i = 1; $i <= count($dirList); $i++) {
902		    $ret = $module->setParameter('uploadLocalServer.dir.' . $i, $dirList[$i - 1]);
903		    if ($ret) {
904			return $ret;
905		    }
906		}
907	    }
908	    $ret = $module->removeParameter('uploadLocalServer.dirs');
909	    if ($ret) {
910		return $ret;
911	    }
912
913	case '0.9.18':
914	    /* Add image/x-photo-cd mime type */
915	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('pcd');
916	    if (!$ret && $mimeType == 'application/unknown') {
917		$ret = GalleryCoreApi::addMimeType('pcd', 'image/x-photo-cd', false);
918		if ($ret) {
919		    return $ret;
920		}
921	    }
922
923	case '0.9.19':
924	    /* New multisite system and support for config.php upgrades */
925
926	case '0.9.20':
927	    /* Change view/controller separator: core:ShowItem -> core.ShowItem */
928	case '0.9.21':
929	    /* Session cookie change, requires new config.php variable */
930
931	case '0.9.22':
932	    /* GalleryModule::getItemLinks API change (GalleryModule API bumped to 0.13) */
933
934	case '0.9.23':
935	    /* Session cookie change, revert the last change and try something new */
936	    foreach (array('cookie.path', 'cookie.domain') as $parameterName) {
937		$ret = $module->setParameter($parameterName, '');
938		if ($ret) {
939		    return $ret;
940		}
941	    }
942
943	case '0.9.24':
944	    /* Add image/jpeg-cmyk mime type */
945	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('jpgcmyk');
946	    if (!$ret && $mimeType == 'application/unknown') {
947		$ret = GalleryCoreApi::addMimeType('jpgcmyk', 'image/jpeg-cmyk', false);
948		if ($ret) {
949		    return $ret;
950		}
951	    }
952	case '0.9.25':
953	    /* Add image/tiff-cmyk mime type */
954	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('tifcmyk');
955	    if (!$ret && $mimeType == 'application/unknown') {
956		$ret = GalleryCoreApi::addMimeType('tifcmyk', 'image/tiff-cmyk', false);
957		if ($ret) {
958		    return $ret;
959		}
960	    }
961	case '0.9.26':
962	    /* Added GalleryDerivative::isBroken; default all values to null */
963	    $ret = $storage->configureStore($module->getId(), array('GalleryDerivative:1.0'));
964	    if ($ret) {
965		return $ret;
966	    }
967
968	case '0.9.27':
969	    /* Remove lock subdirs -- this is from 1.1.8->1.1.9 upgrade */
970	    $locksDir = $gallery->getConfig('data.gallery.locks');
971	    if ($platform->file_exists($locksDir)) {
972		@$platform->recursiveRmDir($locksDir);
973	    }
974	    @$platform->mkdir($locksDir);
975
976	    /* Mark old broken derivatives as such with our new isBroken flag */
977	    /*
978	     * This is the filesize and the crc32 checksum of the broken derivative placeholder
979	     * image that we used in beta 3 and earlier versions.  We may have replaced this image
980	     * by the time this upgrade code is run.  Thus we hardcode filesize(oldImage) and
981	     * crc32(oldImageData) here.
982	     */
983	    $referenceSize = 1589;
984	    /* CRC is a good measure to compare files (not to detect malicous attacks though) */
985	    $referenceCrc = 888290220;
986
987	    /*
988	     * 1. Get a list of all derivatives that are not already marked as isBroken
989	     *    (We can't count on derivativeSize being correct, so check all derivatives)
990	     * Update: upgrade from pre-beta-1 will fail to load RandomHighlightDerivativeImage
991	     *         so restrict this query to only GalleryDerivativeImage
992	     */
993	    $gallery->guaranteeTimeLimit(60);
994	    $query = 'SELECT [GalleryDerivative::id]
995		      FROM [GalleryDerivative], [GalleryEntity]
996		      WHERE [GalleryDerivative::isBroken] IS NULL
997		      AND [GalleryDerivative::id] = [GalleryEntity::id]
998		      AND [GalleryEntity::entityType] = \'GalleryDerviativeImage\'';
999	    list ($ret, $searchResults) = $gallery->search($query);
1000	    if ($ret) {
1001		return $ret;
1002	    }
1003
1004	    /* Check the derivatives that match the search criteria */
1005	    if ($searchResults->resultCount() > 0) {
1006		$derivativeIds = array();
1007		while ($result = $searchResults->nextResult()) {
1008		    $derivativeIds[] = $result[0];
1009		}
1010		$totalDerivatives = sizeof($derivativeIds);
1011
1012		/*
1013		 * The following process is very expensive.  We have to deal with a potentially huge
1014		 * (10^6) amount of derivatives.  To not exceed the memory limit we do everything in
1015		 * batches.  To not exceed the PHP execution time limit and to not exceed the apache
1016		 * timeout we add a progress bar and manipulate the PHP execution time limit
1017		 * periodically.
1018		 */
1019		$gallery->guaranteeTimeLimit(60);
1020
1021		/* Show a progress bar */
1022		$ret = $statusMonitor->renderStatusMessage(
1023		    $module->translate('Detecting broken derivatives'), '', 0);
1024		if ($ret) {
1025		    return $ret;
1026		}
1027
1028		/*
1029		 * The outer loop is for each derivativeId and we upgrade a progress bar every
1030		 * $progressStepSize ids.  We don't load entity by entity, but in batches of
1031		 * $loadBatchSize.  And we don't save the items that were detected as broken
1032		 * derivatives one by one, but also in batches of $saveBatchSize, i.e. we acquire
1033		 * and release the locks in this batch size, but still have to save entity by entity
1034		 * because Gallery has no mass entity save like loadEntitiesById().
1035		 */
1036		$derivatives = array();
1037		$progressStepSize = min(500, intval($totalDerivatives / 10));
1038		$loadBatchSize = 1000;
1039		$saveBatchSize = 1000;
1040		$itemsProcessed = 0;
1041		$brokenDerivatives = array();
1042		do {
1043		    /* 2. Load the entities in batches */
1044		    if (empty($derivatives) && !empty($derivativeIds)) {
1045			/* Prevent PHP timeout */
1046			$gallery->guaranteeTimeLimit(60);
1047			/* Prevent apache timeout */
1048			$ret = $statusMonitor->renderStatusMessage(
1049			    $module->translate(
1050				array('text' => 'Detecting broken derivatives, loading '
1051						. '(%d derivatives checked, %d remaining)',
1052				      'arg1' => $itemsProcessed,
1053				      'arg2' => sizeof($derivativeIds))),
1054			    '',  $itemsProcessed / $totalDerivatives);
1055			if ($ret) {
1056			    return $ret;
1057			}
1058
1059			$currentDerivativeIds = array_splice($derivativeIds, 0, $loadBatchSize);
1060			list ($ret, $derivatives) =
1061			    GalleryCoreApi::loadEntitiesById($currentDerivativeIds,
1062							     'GalleryDerivative');
1063			if ($ret) {
1064			    return $ret;
1065			}
1066		    }
1067
1068		    /* Detect if the derivative is broken */
1069		    if (!empty($derivatives)) {
1070			$itemsProcessed++;
1071			$gallery->guaranteeTimeLimit(30);
1072			$derivative = array_pop($derivatives);
1073
1074			/*
1075			 * Show the progress, but not for each derivative, this would slow down the
1076			 * process considerably
1077			 */
1078			if ($itemsProcessed % $progressStepSize == 0 ||
1079				$itemsProcessed == $totalDerivatives) {
1080			    $ret = $statusMonitor->renderStatusMessage(
1081				$module->translate(
1082				    array('text' => 'Detecting broken derivatives (%d derivatives '
1083						    . 'checked, %d remaining)',
1084					  'arg1' => $itemsProcessed,
1085					  'arg2' => $totalDerivatives - $itemsProcessed)),
1086				'', $itemsProcessed / $totalDerivatives);
1087			    if ($ret) {
1088				return $ret;
1089			    }
1090			    $gallery->guaranteeTimeLimit(30);
1091			}
1092
1093			/*
1094			 * 3. Filter out derivatives that don't return true for isCacheCurrent
1095			 *    (= don't have a cache file yet = would be rebuilt anyway)
1096			 */
1097			list ($ret, $current) = $derivative->isCacheCurrent();
1098			if ($ret) {
1099			    return $ret;
1100			}
1101			if (!$current) {
1102			    continue;
1103			}
1104
1105			/*
1106			 * 4. Filter out derivatives that don't have the same file size as the
1107			 *    broken image placeholder
1108			 */
1109			list ($ret, $path) = $derivative->fetchPath();
1110			if ($ret) {
1111			    return $ret;
1112			}
1113			if (($size = $platform->filesize($path)) === false) {
1114			    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE);
1115			}
1116			if ($size != $referenceSize) {
1117			    continue;
1118			}
1119
1120			/* 5. Binary compare the derivative file with the placeholder file */
1121			if (($data = $platform->file($path)) === false) {
1122			    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE);
1123			}
1124			$data = implode('', $data);
1125			if ($referenceCrc == crc32($data)) {
1126			    /* Add the derivative to the list of broken ones */
1127			    $brokenDerivatives[$derivative->getId()] = $derivative;
1128			}
1129		    }
1130
1131		    /* 6. Mark the detected broken derivative as such and save it in the DB */
1132		    if (sizeof($brokenDerivatives) == $saveBatchSize ||
1133			    (!empty($brokenDerivatives) && empty($derivativeIds))) {
1134			$gallery->guaranteeTimeLimit(30);
1135			$saveProgressStepSize = min(200, intval(sizeof($brokenDerivatives) / 10));
1136
1137			$ret = $statusMonitor->renderStatusMessage(
1138			    $module->translate(
1139				array('text' => 'Detecting broken derivatives, saving '
1140						. '(%d derivatives checked, %d remaining)',
1141				      'arg1' => $itemsProcessed,
1142				      'arg2' => $totalDerivatives - $itemsProcessed)),
1143			    '', $itemsProcessed / $totalDerivatives);
1144			if ($ret) {
1145			    return $ret;
1146			}
1147
1148			list ($ret, $lockId) =
1149			    GalleryCoreApi::acquireWriteLock(array_keys($brokenDerivatives));
1150			if ($ret) {
1151			    return $ret;
1152			}
1153
1154			$itemsSaved = 0;
1155			foreach ($brokenDerivatives as $brokenDerivative) {
1156			    $itemsSaved++;
1157			    if ($itemsSaved % $saveProgressStepSize == 0) {
1158				$ret = $statusMonitor->renderStatusMessage(
1159				    $module->translate(array(
1160					'text' => 'Detecting broken derivatives, saving item '
1161					    . '%d of %d (%d derivatives complete, %d remaining)',
1162					'arg1' => $itemsSaved,
1163					'arg2' => sizeof($brokenDerivatives),
1164					'arg3' => $itemsProcessed,
1165					'arg4' => $totalDerivatives - $itemsProcessed)),
1166				    '', $itemsProcessed / $totalDerivatives);
1167				if ($ret) {
1168				    GalleryCoreApi::releaseLocks($lockId);
1169				    return $ret;
1170				}
1171				$gallery->guaranteeTimeLimit(30);
1172			    }
1173
1174			    $brokenDerivative->setIsBroken(true);
1175			    $ret = $brokenDerivative->save(true, false);
1176			    if ($ret) {
1177				GalleryCoreApi::releaseLocks($lockId);
1178				return $ret;
1179			    }
1180			}
1181			$brokenDerivatives = array();
1182
1183			$ret = GalleryCoreApi::releaseLocks($lockId);
1184			if ($ret) {
1185			    return $ret;
1186			}
1187		    }
1188		    /*
1189		     * Continue if there are either unloaded ids, unchecked derivatives or unsaved
1190		     * derivatives
1191		     */
1192		} while (!empty($derivativeIds) || !empty($brokenDerivatives) ||
1193			 !empty($derivatives));
1194	    }
1195
1196	case '0.9.28':
1197	    /* Changed module API onLoad($entity, $duringUpgrade) definition */
1198
1199	case '0.9.29':
1200	    /* Ginormous layout and theme consolidation refactor */
1201	    $ret = $storage->configureStore($module->getId(),
1202					    array('GalleryPluginParameterMap:1.1'));
1203	    if ($ret) {
1204		return $ret;
1205	    }
1206
1207	    $query = '
1208	    UPDATE
1209	      [GalleryPluginParameterMap]
1210	    SET
1211	      [::pluginType] = \'theme\'
1212	    WHERE
1213	      [GalleryPluginParameterMap::pluginType] = \'layout\'
1214	    ';
1215	    $ret = $storage->execute($query);
1216	    if ($ret) {
1217		return $ret;
1218	    }
1219
1220	    /* After this refactor we only support the matrix theme */
1221	    $query = '
1222	    UPDATE
1223	      [GalleryAlbumItem]
1224	    SET
1225	      [::theme] = \'matrix\'
1226	    ';
1227	    $ret = $storage->execute($query);
1228	    if ($ret) {
1229		return $ret;
1230	    }
1231
1232	    $query = '
1233	    UPDATE
1234	      [GalleryPluginMap]
1235	    SET
1236	      [::pluginType] = \'theme\'
1237	    WHERE
1238	      [GalleryPluginMap::pluginType] = \'layout\'
1239	    ';
1240	    $ret = $storage->execute($query);
1241	    if ($ret) {
1242		return $ret;
1243	    }
1244
1245	    /*
1246	     * Rename g2data 'layouts' directories to be 'themes', or create them if they don't
1247	     * already exist (they should exist, though)
1248	     */
1249	    foreach (array($gallery->getConfig('data.gallery.plugins'),
1250			   $gallery->getConfig('data.gallery.plugins_data')) as $base) {
1251		if ($platform->file_exists("$base/themes")) {
1252		    if ($platform->file_exists("$base/layouts")) {
1253			$platform->recursiveRmDir("$base/layouts");
1254		    }
1255		} else if (file_exists($base)) {
1256		    if ($platform->file_exists("$base/layouts")) {
1257			$platform->rename("$base/layouts", "$base/themes");
1258		    } else {
1259			$platform->mkdir("$base/themes");
1260		    }
1261		}
1262	    }
1263
1264	    /* Removed parameters */
1265	    foreach (array('language.selector', 'misc.login') as $paramName) {
1266		$ret = $module->removeParameter($paramName);
1267		if ($ret) {
1268		    return $ret;
1269		}
1270	    }
1271
1272	    /*
1273	     * If we're coming from 0.8.13 or earlier, then our themes don't have version
1274	     * information, so take care of that here by calling installOrUpgrade() on the currently
1275	     * active themes to let them update their bookkeeping.  Reactivate them too for good
1276	     * measure.
1277	     */
1278	    if (version_compare($currentVersion, '0.8.13', '<=')) {
1279		list ($ret, $themes) = GalleryCoreApi::fetchPluginStatus('theme');
1280		if ($ret) {
1281		    return $ret;
1282		}
1283
1284		foreach ($themes as $themeId => $themeStatus) {
1285		    $gallery->guaranteeTimeLimit(30);
1286		    if (!empty($themeStatus['active'])) {
1287			list ($ret, $theme) = GalleryCoreApi::loadPlugin('theme', $themeId);
1288			if ($ret &&
1289			    !($ret->getErrorCode() & ERROR_PLUGIN_VERSION_MISMATCH)) {
1290			    return $ret;
1291			}
1292
1293			$ret = $theme->installOrUpgrade();
1294			if ($ret) {
1295			    return $ret;
1296			}
1297
1298			list ($ret, $ignored) = $theme->activate(false);
1299			if ($ret &&
1300			    !($ret->getErrorCode() & ERROR_PLUGIN_VERSION_MISMATCH)) {
1301			    /*
1302			     * Theme getSettings may try to load ImageFrame interface, but
1303			     * ImageFrame may need to be upgraded.  Ignore version mismatch here.
1304			     */
1305			    return $ret;
1306			}
1307		    }
1308		}
1309	    }
1310
1311	case '0.9.30':
1312	    /* Removed layout column from AlbumItem; matrix is only theme for now: set default */
1313	    $ret = $storage->configureStore($module->getId(), array('GalleryAlbumItem:1.0'));
1314	    if ($ret) {
1315		return $ret;
1316	    }
1317
1318	    $ret = $module->setParameter('default.theme', 'matrix');
1319	    if ($ret) {
1320		return $ret;
1321	    }
1322	    $ret = $module->removeParameter('default.layout');
1323	    if ($ret) {
1324		return $ret;
1325	    }
1326	    $query = '
1327	    UPDATE
1328	      [GalleryAlbumItem]
1329	    SET
1330	      [::theme] = NULL
1331	    ';
1332	    $ret = $storage->execute($query);
1333	    if ($ret) {
1334		return $ret;
1335	    }
1336
1337	case '0.9.31':
1338	    /* Beta 4! */
1339	case '0.9.32':
1340	    /* Minor core API change */
1341	case '0.9.33':
1342	    /* Release Candidate 1! */
1343
1344	case '0.9.34':
1345	    /* Add date/time formats */
1346	    foreach (array('format.date' => '%x', 'format.time' => '%X', 'format.datetime' => '%c')
1347		     as $key => $value) {
1348		$ret = $module->setParameter($key, $value);
1349		if ($ret) {
1350		    return $ret;
1351		}
1352	    }
1353
1354	case '0.9.35':
1355	    /* Release Candidate 2! */
1356
1357	case '0.9.36':
1358	    /*
1359	     * Fixed GalleryUtilities::getPseudoFileName for derivatives.  Delete fast-download
1360	     * files that may have cached incorrect filenames.
1361	     */
1362	    $slash = $platform->getDirectorySeparator();
1363	    $baseDir = $gallery->getConfig('data.gallery.cache') . 'derivative' . $slash;
1364	    for ($i = 0; $i < 10; $i++) {
1365		$gallery->guaranteeTimeLimit(60);
1366		$ret = $statusMonitor->renderStatusMessage(
1367		    $module->translate('Clearing fast-download cache'), '', $i / 10);
1368		if ($ret) {
1369		    return $ret;
1370		}
1371		for ($j = 0; $j < 10; $j++) {
1372		    $dir = $baseDir . $i . $slash . $j . $slash;
1373		    if ($dh = @$platform->opendir($dir)) {
1374			while (($file = $platform->readdir($dh)) !== false) {
1375			    if (substr($file, -9) == '-fast.inc') {
1376				@$platform->unlink($dir . $file);
1377			    }
1378			}
1379			$platform->closedir($dh);
1380		    }
1381		}
1382	    }
1383	    $ret = $statusMonitor->renderStatusMessage(
1384		$module->translate('Clearing fast-download cache'), '', 1);
1385	    if ($ret) {
1386		return $ret;
1387	    }
1388
1389	case '0.9.37':
1390	    /* 2.0 Release! */
1391
1392	case '1.0.0':
1393	case '1.0.0.x':
1394	    /* Schema only upgrade */
1395	    $ret = $storage->configureStore($module->getId(),
1396					    array('GalleryPluginParameterMap:1.2'));
1397	    if ($ret) {
1398		return $ret;
1399	    }
1400
1401	case '1.0.1':
1402	    /* Add image/wmf mime type */
1403	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('wmf');
1404	    if (!$ret && $mimeType == 'application/unknown') {
1405		$ret = GalleryCoreApi::addMimeType('wmf', 'image/wmf', false);
1406		if ($ret) {
1407		    return $ret;
1408		}
1409	    }
1410
1411	case '1.0.2':
1412	    /* Security fix */
1413
1414	case '1.0.3':
1415	    /* Consolidated .sql files into schema.tpl */
1416
1417	case '1.0.4':
1418	    /* Added maintenance mode */
1419
1420	case '1.0.5':
1421	    /* Remove plugins directory from g2data */
1422	    $pluginDirectory = $gallery->getConfig('data.gallery.base') . 'plugins';
1423	    $pluginDirectories = array($pluginDirectory . '/modules',
1424				       $pluginDirectory . '/themes',
1425				       $pluginDirectory);
1426
1427	    foreach ($pluginDirectories as $pluginDirectory) {
1428		if (@$platform->file_exists($pluginDirectory)) {
1429		    /* We're not interested in whether it succeeded or not */
1430		    @$platform->recursiveRmDir($pluginDirectory);
1431		}
1432	    }
1433
1434	case '1.0.6':
1435	    /* Add PluginPackageMap table */
1436
1437	case '1.0.7':
1438	    $ret = $module->setParameter('exec.beNice', '0');
1439	    if ($ret) {
1440		return $ret;
1441	    }
1442
1443	case '1.0.8':
1444	case '1.0.9':
1445	    /* Security fix in zipcart */
1446
1447	case '1.0.10':
1448	    /* Rename unnamed pre-beta-3 index to named index */
1449	    if ($storage->getType() == 'mysql') {
1450		$gallery->debug('Rename unnamed pre-beta-3 index to named index (ignore errors)');
1451		$query = sprintf('
1452		ALTER TABLE %sAccessMap
1453		DROP INDEX %saccessListId_2,
1454		ADD INDEX %sAccessMap_83732(%saccessListId);',
1455		$storage->_tablePrefix, $storage->_columnPrefix, $storage->_tablePrefix,
1456		$storage->_columnPrefix);
1457		/* Ignore error, since there's nothing to do for most installations */
1458		$storage->execute($query);
1459	    }
1460
1461	    /*
1462	     * Combine AccessMap userId/groupId into single userOrGroupId, and remove unused
1463	     * GALLERY_PERMISSION_ITEM_ADMIN permission flag.  Also increase size of
1464	     * GalleryUser::email column.
1465	     */
1466	    $ret = $storage->configureStore($module->getId(),
1467					    array('GalleryAccessMap:1.0', 'GalleryUser:1.0'));
1468	    if ($ret) {
1469		return $ret;
1470	    }
1471
1472	    /* If coming from 0.9.5 or earlier then GalleryAccessMap already has userOrGroupId */
1473	    if (version_compare($currentVersion, '0.9.5', '>')) {
1474		$query = '
1475		UPDATE
1476		  [GalleryAccessMap]
1477		SET
1478		  [::userOrGroupId] = [::userId] + [::groupId]
1479		';
1480		$ret = $storage->execute($query, array());
1481		if ($ret) {
1482		    return $ret;
1483		}
1484	    }
1485
1486	    $ret = $storage->configureStore($module->getId(), array('GalleryAccessMap:1.1'));
1487	    if ($ret) {
1488		return $ret;
1489	    }
1490
1491	    list ($ret, $flagModifier) =
1492		$storage->getFunctionSql('BITAND', array('[::flags]', '?'));
1493	    if ($ret) {
1494		return $ret;
1495	    }
1496	    $query = '
1497	    UPDATE
1498	      [GalleryPermissionSetMap]
1499	    SET
1500	      [::flags] = ' . $flagModifier . '
1501	    ';
1502	    $ret = $storage->execute($query, array(3));
1503	    if ($ret) {
1504		return $ret;
1505	    }
1506
1507	case '1.0.11':
1508	    /* Several previous upgrades used 'modules' instead of 'module' with plugin params */
1509	    list ($ret, $coreParams) = $module->fetchParameters();
1510	    if ($ret) {
1511		return $ret;
1512	    }
1513	    foreach (array('misc.useShortUrls', 'language.selector') as $key) {
1514		if (isset($coreParams[$key])) {
1515		    $ret = $module->removeParameter('misc.useShortUrls');
1516		    if ($ret) {
1517			return $ret;
1518		    }
1519		}
1520	    }
1521	    foreach (array('cookie.path' => '', 'cookie.domain' => '',
1522			   'exec.beNice' => '0', 'repository.updateTime' => '0')
1523		     as $key => $value) {
1524		if (!isset($coreParams[$key])) {
1525		    $ret = $module->setParameter($key, $value);
1526		    if ($ret) {
1527			return $ret;
1528		    }
1529		}
1530	    }
1531	    $ret = GalleryCoreApi::removeMapEntry(
1532		'GalleryPluginParameterMap', array('pluginType' => 'modules'));
1533	    if ($ret) {
1534		return $ret;
1535	    }
1536
1537	case '1.0.12':
1538	    /* Add param 'language.useBrowserPref' */
1539	    list ($ret, $langCode) = $module->getParameter('default.language');
1540	    if ($ret) {
1541		return $ret;
1542	    }
1543	    $useBrowserPref = '0';
1544	    if (empty($langCode)) {
1545		$useBrowserPref = '1';
1546		$ret = $module->setParameter('default.language', 'en_US');
1547		if ($ret) {
1548		    return $ret;
1549		}
1550	    }
1551	    $ret = $module->setParameter('language.useBrowserPref', $useBrowserPref);
1552	    if ($ret) {
1553		return $ret;
1554	    }
1555
1556	case '1.0.13':
1557	    /* Add config parameter: 'baseUri'*/
1558	case '1.0.14':
1559	    /* GalleryCoreApi 7.0 and GalleryModule 3.0 */
1560	case '1.0.15':
1561	    /*
1562	     * Add fast-download for GalleryDataItems too.  Fast-download files are now in
1563	     * cache/entity/.  Delete the old files in cache/derivative/.
1564	     */
1565	    $gallery->guaranteeTimeLimit(60);
1566	    $query = 'SELECT [GalleryDerivativeImage::id]
1567		      FROM [GalleryDerivativeImage]';
1568	    list ($ret, $searchResults) = $gallery->search($query);
1569	    if ($ret) {
1570		return $ret;
1571	    }
1572
1573	    /* Checkpoint before starting a long filesystem-only task */
1574	    $ret = $storage->checkPoint();
1575	    if ($ret) {
1576		return $ret;
1577	    }
1578
1579	    if ($searchResults->resultCount() > 0) {
1580		$derivativeIds = array();
1581		while ($result = $searchResults->nextResult()) {
1582		    $derivativeIds[] = $result[0];
1583		}
1584		$totalDerivatives = count($derivativeIds);
1585		$base = $gallery->getConfig('data.gallery.cache');
1586		$gallery->guaranteeTimeLimit(60);
1587
1588		/* Show a progress bar */
1589		$ret = $statusMonitor->renderStatusMessage(
1590		    $module->translate('Deleting old fast-download cache'), '', 0);
1591		if ($ret) {
1592		    return $ret;
1593		}
1594
1595		$stepSize = min(100, max(intval($totalDerivatives / 10), 5));
1596		for ($i = 0; $i < $totalDerivatives; $i++) {
1597		    /* Delete the file if it exists */
1598		    list ($first, $second) = GalleryDataCache::getCacheTuple($derivativeIds[$i]);
1599		    $fastDownloadFilePath = sprintf('%derivative/%s/%s/%d-fast.inc',
1600			    $base, $first, $second, $derivativeIds[$i]);
1601		    if ($platform->file_exists($fastDownloadFilePath)) {
1602			$platform->unlink($fastDownloadFilePath);
1603		    }
1604
1605		    /* Update the progress bar / prevent timouts */
1606		    if ($i % $stepSize == 0 || $i == ($totalDerivatives - 1)) {
1607			$gallery->guaranteeTimeLimit(60);
1608			$ret = $statusMonitor->renderStatusMessage(
1609			    $module->translate('Deleting old fast-download cache'),
1610			    '',  ($i+1) / $totalDerivatives);
1611			if ($ret) {
1612			    return $ret;
1613			}
1614		    }
1615		}
1616	    }
1617
1618	    /* We might have lost our database connection so check and reconnect */
1619	    $ret = $storage->validateConnection();
1620	    if ($ret) {
1621		return $ret;
1622	    }
1623
1624	case '1.0.16':
1625	    /* Added 'not-null' to Entities.inc and Map.inc */
1626	    $storageExtras =& $storage->_getExtras();
1627	    $storageExtras->_clearEntityAndMapCache();
1628	case '1.0.17':
1629	    /* Add image/tga mime type */
1630	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('tga');
1631	    if (!$ret && $mimeType == 'application/unknown') {
1632		$ret = GalleryCoreApi::addMimeType('tga', 'image/tga', false);
1633		if ($ret) {
1634		    return $ret;
1635		}
1636	    }
1637
1638	case '1.0.18':
1639	    /* Add index to GalleryEntity::linkId */
1640	    $ret = $storage->configureStore($module->getId(), array('GalleryEntity:1.1'));
1641	    if ($ret) {
1642		return $ret;
1643	    }
1644
1645	case '1.0.19':
1646	    /* Add page level caching and the GalleryCache map */
1647	    $acceleration = serialize(array('guest' => array('type' => 'none'),
1648					    'user' => array('type' => 'none')));
1649	    $ret = GalleryCoreApi::setPluginParameter(
1650		'module', 'core', 'acceleration', $acceleration);
1651	    if ($ret) {
1652		return $ret;
1653	    }
1654
1655	case '1.0.20':
1656	    /* Add configurable captcha security level */
1657	    $ret = GalleryCoreApi::setPluginParameter('module', 'core', 'captcha.level', 'MEDIUM');
1658	    if ($ret) {
1659		return $ret;
1660	    }
1661
1662	case '1.0.21':
1663	    /* GallerySession change: Store sessions in the database and no longer on disk */
1664	    $sessionsDir = $gallery->getConfig('data.gallery.base') . 'sessions' .
1665		$platform->getDirectorySeparator();
1666	    if ($platform->file_exists($sessionsDir)) {
1667		$stepSize = 100;
1668		$count = 0;
1669		$iterationSize = 5000;
1670		$iteration = 1;
1671		/* Show a progress bar while removing the files */
1672		$ret = $statusMonitor->renderStatusMessage(
1673		    $module->translate(array('text' => 'Deleting old session files (iteration %d)',
1674					     'arg1' => $iteration)),
1675		    '', 0);
1676		if ($ret) {
1677		    return $ret;
1678		}
1679		$dir = $platform->opendir($sessionsDir, 'r');
1680		if (!$dir) {
1681		    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__,
1682					     "Can't access session dir");
1683		}
1684		$gallery->guaranteeTimeLimit(60);
1685		while (($filename = $platform->readdir($dir)) !== false) {
1686		    if ($filename == '.' || $filename == '..') {
1687			continue;
1688		    }
1689		    $count++;
1690		    $platform->unlink($sessionsDir . $filename);
1691
1692		    /* Update the progress bar / prevent timouts */
1693		    if ($count % $stepSize == 0) {
1694			$gallery->guaranteeTimeLimit(60);
1695			$ret = $statusMonitor->renderStatusMessage(
1696			    $module->translate(
1697				array('text' => 'Deleting old session files (iteration %d)',
1698				      'arg1' => $iteration)),
1699			    '', $count / $iterationSize);
1700			if ($ret) {
1701			    return $ret;
1702			}
1703		    }
1704
1705		    if ($count > $iterationSize) {
1706			$iteration++;
1707			$count = 0;
1708		    }
1709		}
1710		$platform->closedir($dir);
1711		$platform->rmdir($sessionsDir);
1712		$ret = $statusMonitor->renderStatusMessage(
1713		    $module->translate(array('text' => 'Deleting old session files (iteration %d)',
1714					     'arg1' => $iteration)),
1715		    '', 1);
1716		if ($ret) {
1717		    return $ret;
1718		}
1719	    }
1720
1721	case '1.0.22':
1722	    /* Rename unnamed pre-beta-3 index to named index */
1723	    $gallery->guaranteeTimeLimit(120);
1724	    if ($storage->getType() == 'mysql') {
1725		$gallery->debug('Rename unnamed pre-beta-3 index to named index (ignore errors)');
1726		$indexChanges = array();
1727		$indexChanges[] = array('AccessMap', 'permission',
1728					'AccessMap_18058', array('permission'));
1729		$indexChanges[] = array('AccessSubscriberMap', 'accessListId',
1730					'AccessSubscriberMap_83732', array('accessListId'));
1731		$indexChanges[] = array('ChildEntity', 'parentId',
1732					'ChildEntity_52718', array('parentId'));
1733		$indexChanges[] = array('Derivative', 'derivativeSourceId',
1734					'Derivative_85338', array('derivativeSourceId'));
1735		$indexChanges[] = array('Derivative', 'derivativeOrder',
1736					'Derivative_25243', array('derivativeOrder'));
1737		$indexChanges[] = array('Derivative', 'derivativeType',
1738					'Derivative_97216', array('derivativeType'));
1739		$indexChanges[] = array('DerivativePrefsMap', 'itemId',
1740					'DerivativePrefsMap_75985', array('itemId'));
1741		$indexChanges[] = array('Entity', 'creationTimestamp',
1742					'Entity_76255', array('creationTimestamp'));
1743		$indexChanges[] = array('Entity', 'isLinkable',
1744					'Entity_35978', array('isLinkable'));
1745		$indexChanges[] = array('Entity', 'modificationTimestamp',
1746					'Entity_63025', array('modificationTimestamp'));
1747		$indexChanges[] = array('Entity', 'serialNumber',
1748					'Entity_60702', array('serialNumber'));
1749		$indexChanges[] = array('FileSystemEntity ', 'pathComponent',
1750					'FileSystemEntity_3406', array('pathComponent'));
1751		$indexChanges[] = array('Item', 'keywords', 'Item_99070', array('keywords'));
1752		$indexChanges[] = array('Item', 'ownerId', 'Item_21573', array('ownerId'));
1753		$indexChanges[] = array('Item', 'summary', 'Item_54147', array('summary'));
1754		$indexChanges[] = array('Item', 'title', 'Item_90059', array('title'));
1755		$indexChanges[] = array('ItemAttributesMap', 'parentSequence',
1756					'ItemAttributesMap_95270', array('parentSequence'));
1757		$indexChanges[] = array('MaintenanceMap', 'taskId',
1758					'MaintenanceMap_21687', array('taskId'));
1759		$indexChanges[] = array('PluginParameterMap', 'pluginType_2',
1760					'PluginParameterMap_12808',
1761					array('pluginType', 'pluginId', 'itemId'));
1762		$indexChanges[] = array('PluginParameterMap', 'pluginType_3',
1763					'PluginParameterMap_80596', array('pluginType'));
1764		$indexChanges[] = array('TkOperatnMimeTypeMap', 'operationName',
1765					'TkOperatnMimeTypeMap_2014', array('operationName'));
1766		$indexChanges[] = array('TkOperatnMimeTypeMap', 'mimeType',
1767					'TkOperatnMimeTypeMap_79463', array('mimeType'));
1768		$indexChanges[] = array('TkOperatnParameterMap', 'operationName',
1769					'TkOperatnParameterMap_2014', array('operationName'));
1770		$indexChanges[] = array('TkPropertyMimeTypeMap', 'propertyName',
1771					'TkPropertyMimeTypeMap_52881', array('propertyName'));
1772		$indexChanges[] = array('TkPropertyMimeTypeMap', 'mimeType',
1773					'TkPropertyMimeTypeMap_79463', array('mimeType'));
1774		$indexChanges[] = array('UserGroupMap', 'userId',
1775					'UserGroupMap_69068', array('userId'));
1776		$indexChanges[] = array('UserGroupMap', 'groupId',
1777					'UserGroupMap_89328', array('groupId'));
1778		$indexChanges[] = array('Lock', 'lockId',
1779					'Lock_11039', array('lockId'));
1780		foreach ($indexChanges as $change) {
1781		    $indexColumns = implode('`, `' . $storage->_columnPrefix, $change[3]);
1782		    $indexColumns = $storage->_columnPrefix . $indexColumns;
1783		    $query = sprintf('
1784		    ALTER TABLE `%s%s`
1785		    DROP INDEX `%s%s`,
1786		    ADD INDEX `%s%s`(`%s`);',
1787			$storage->_tablePrefix, $change[0], $storage->_columnPrefix, $change[1],
1788			$storage->_tablePrefix, $change[2], $indexColumns);
1789		    /* Ignore error, since there's nothing to do for most installations */
1790		    $storage->execute($query);
1791		}
1792		$gallery->debug('Finished renaming unnamed pre-beta-3 indices to named indices');
1793	    }
1794
1795	    /* Commit transactions before we execute a query that we expect to fail */
1796	    $ret = $storage->checkPoint();
1797	    if ($ret) {
1798		return $ret;
1799	    }
1800
1801	    /*
1802	     * Also add a single column index on AccessMap.accessListId since it was forgotten in
1803	     * the initial upgrade code.  Ignore errors since some installations already have it.
1804	     */
1805	    $gallery->debug('Adding an index to the AccessMap table, ignore errors');
1806	    $storage->configureStore($module->getId(), array('GalleryAccessMap:1.2'));
1807
1808	    /* Postgres will abort the transaction if the index exists, so checkpoint here */
1809	    $ret = $storage->checkPoint();
1810	    if ($ret) {
1811		return $ret;
1812	    }
1813
1814	    $gallery->debug('Finished adding an index to the AccessMap table');
1815	    /*
1816	     * Make sure the schema update is stored, can't use updateMapEntry because schema is not
1817	     * in Maps.xml
1818	     */
1819	    $query = sprintf('
1820	    UPDATE %sSchema
1821	    SET %smajor=1, %sminor=3
1822	    WHERE %sname=\'AccessMap\' AND %smajor=1 AND %sminor=2',
1823			     $storage->_tablePrefix, $storage->_columnPrefix,
1824			     $storage->_columnPrefix, $storage->_columnPrefix,
1825			     $storage->_columnPrefix, $storage->_columnPrefix);
1826	    $ret = $storage->execute($query);
1827	    if ($ret) {
1828		return $ret;
1829	    }
1830
1831	case '1.0.23':
1832	    /* Rename GalleryCache to GalleryCacheMap, and make the value column TEXT(LARGE) */
1833
1834	case '1.0.24':
1835	    /* Add CoreCaptchaAdminOption, rename level parameter */
1836	    $gallery->guaranteeTimeLimit(60);
1837	    list ($ret, $level) = $module->getParameter('captcha.level');
1838	    if ($ret) {
1839		return $ret;
1840	    }
1841	    $ret = $module->setParameter('validation.level', $level);
1842	    if ($ret) {
1843		return $ret;
1844	    }
1845	    $ret = $module->removeParameter('captcha.level');
1846	    if ($ret) {
1847		return $ret;
1848	    }
1849
1850	case '1.0.25':
1851	case '1.0.26':
1852	    /*
1853	     * 2.1 Release Candidate 1!
1854	     *
1855	     * We used to change the character set for MySQL databases to utf8 here, but now we do
1856	     * it on every upgrade (at the beginning) to allow for the fact that the user can
1857	     * upgrade their MySQL from 3.x to 4.x at any time.  We still call it here for
1858	     * historical accuracy for users upgrading from before 1.0.26.
1859	     */
1860	    list ($ret, $converted) =
1861		CoreModuleExtras::convertCharacterSetToUtf8($module, $statusMonitor);
1862	    if ($ret) {
1863		return $ret;
1864	    }
1865
1866	    /* Clear the cache data since we changed the blob encoding */
1867	    $gallery->guaranteeTimeLimit(60);
1868	    $ret = GalleryCoreApi::removeAllMapEntries('GalleryCacheMap', true);
1869	    if ($ret) {
1870		return $ret;
1871	    }
1872
1873	case '1.0.27':
1874	case '1.0.28':
1875	    /* Change in page cache key format */
1876	case '1.0.29':
1877	    /* Support for transactional locking */
1878
1879	case '1.0.30':
1880	    /* Pull dangerous mime types */
1881	    $ret = GalleryCoreApi::removeMimeType(
1882		array('mimeType' => array('text/html', 'application/xhtml+xml', 'text/xml')));
1883	    if ($ret) {
1884		return $ret;
1885	    }
1886
1887	case '1.0.31':
1888	    list ($ret, $params) = GalleryCoreApi::fetchAllPluginParameters('module', 'core');
1889	    if ($ret) {
1890		return $ret;
1891	    }
1892	    foreach (array('session.lifetime' => array(25 * 365 * 86400, 21 * 86400),
1893			   'session.inactivityTimeout' => array(14 * 86400, 7 * 86400)) as
1894		     $key => $oldAndNew) {
1895		if ($params[$key] == $oldAndNew[0]) {
1896		    $ret = $module->setParameter($key, $oldAndNew[1]);
1897		    if ($ret) {
1898			return $ret;
1899		    }
1900		}
1901	    }
1902
1903	case '1.0.32':
1904	    /* 2.1 Release Candidate 2! */
1905	case '1.0.33':
1906	    /* Security fix in installer/upgrader - RC-2a */
1907	case '1.0.34':
1908	    /* 2.1 Release! */
1909
1910	case '1.1.0':
1911	case '1.1.0.x':
1912	    /* Minimum PHP version now 4.3.0; new versions of ADODb and Smarty */
1913	case '1.1.1':
1914
1915	case '1.1.2':
1916	    /* Add Flash video and Windows playlist mime types */
1917	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('flv');
1918	    if (!$ret && $mimeType == 'application/unknown') {
1919		$ret = GalleryCoreApi::addMimeType('flv', 'video/x-flv', false);
1920		if ($ret) {
1921		    return $ret;
1922		}
1923	    }
1924	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('asx');
1925	    if (!$ret && $mimeType == 'application/unknown') {
1926		$ret = GalleryCoreApi::addMimeType('asx', 'video/x-ms-asx', false);
1927		if ($ret) {
1928		    return $ret;
1929		}
1930	    }
1931
1932	case '1.1.3':
1933	    /* Add renderers to GalleryItem */
1934	    $ret = $storage->configureStore($module->getId(), array('GalleryItem:1.1'));
1935	    if ($ret) {
1936		return $ret;
1937	    }
1938	    $ret = $storage->execute('UPDATE [GalleryItem] SET [::renderer] = NULL');
1939	    if ($ret) {
1940		return $ret;
1941	    }
1942
1943	    /*
1944	     * Switch PanoramaPhotoItem and PanoramaDerivativeImage entities back to their base
1945	     * classes and set the items to use the PanoramaRenderer instead
1946	     */
1947	    $gallery->guaranteeTimeLimit(60);
1948	    $query = '
1949	      SELECT
1950		[GalleryEntity::id], [GalleryEntity::entityType]
1951	      FROM
1952		[GalleryEntity]
1953	      WHERE
1954		[GalleryEntity::entityType] IN (\'PanoramaPhotoItem\', \'PanoramaDerivativeImage\')
1955	    ';
1956	    list ($ret, $searchResults) = $gallery->search($query, array());
1957	    if ($ret) {
1958		return $ret;
1959	    }
1960	    $photos = $derivatives = array();
1961	    while ($result = $searchResults->nextResult()) {
1962		if ($result[1] == 'PanoramaPhotoItem') {
1963		    $photos[] = $result[0];
1964		} else {
1965		    $derivatives[] = $result[0];
1966		}
1967	    }
1968	    $total = count($photos) + count($derivatives);
1969
1970	    /* Switch PanoramaPhotoItems back to GalleryPhotoItems */
1971	    for ($i = 0; $photos; $i += count($ids)) {
1972		$gallery->guaranteeTimeLimit(30);
1973		$ret = $statusMonitor->renderStatusMessage(
1974			$module->translate('Updating panorama items'), '', $i / $total);
1975		if ($ret) {
1976		    return $ret;
1977		}
1978		$ids = array_splice($photos, 0, 500);
1979		$markers = GalleryUtilities::makeMarkers($ids);
1980		$query = "UPDATE [GalleryItem] SET [::renderer] = 'PanoramaRenderer' " .
1981		    "WHERE [GalleryItem::id] IN ($markers)";
1982		$ret = $storage->execute($query, $ids);
1983		if ($ret) {
1984		    return $ret;
1985		}
1986
1987		$query = "UPDATE [GalleryEntity] SET [::entityType] = 'GalleryPhotoItem' " .
1988		    "WHERE [GalleryEntity::id] IN ($markers)";
1989		$ret = $storage->execute($query, $ids);
1990		if ($ret) {
1991		    return $ret;
1992		}
1993	    }
1994
1995	    /* Switch PanoramaDerivativeImage back to GalleryDerivativeImage */
1996	    while ($derivatives) {
1997		$gallery->guaranteeTimeLimit(30);
1998		$ret = $statusMonitor->renderStatusMessage(
1999			$module->translate('Updating panorama items'), '', $i / $total);
2000		if ($ret) {
2001		    return $ret;
2002		}
2003		$ids = array_splice($derivatives, 0, 500);
2004		$markers = GalleryUtilities::makeMarkers($ids);
2005		$query = "UPDATE [GalleryEntity] SET [::entityType] = 'GalleryDerivativeImage' " .
2006		    "WHERE [GalleryEntity::id] IN ($markers)";
2007		$ret = $storage->execute($query, $ids);
2008		if ($ret) {
2009		    return $ret;
2010		}
2011		$i += count($ids);
2012	    }
2013	    if ($total) {
2014		$ret = $statusMonitor->renderStatusMessage(
2015			$module->translate('Updating panorama items'), '', 1);
2016		if ($ret) {
2017		    return $ret;
2018		}
2019	    }
2020
2021	case '1.1.4':
2022	    /* Add mpeg-4 video mime type */
2023	    list ($ret, $mimeType) = GalleryCoreApi::convertExtensionToMime('mp4');
2024	    if (!$ret && $mimeType == 'application/unknown') {
2025		$ret = GalleryCoreApi::addMimeType('mp4', 'video/mp4', false);
2026		if ($ret) {
2027		    return $ret;
2028		}
2029	    }
2030
2031	case '1.1.5':
2032	case '1.1.6':
2033	    /* Remove useless rows in AccessSubscriberMap */
2034	    $gallery->guaranteeTimeLimit(60);
2035	    $query = '
2036	      SELECT
2037		[GalleryAccessSubscriberMap::itemId]
2038	      FROM
2039		[GalleryAccessSubscriberMap], [GalleryEntity]
2040	      WHERE
2041		[GalleryAccessSubscriberMap::accessListId] = 0
2042	      AND
2043		[GalleryAccessSubscriberMap::itemId] = [GalleryEntity::id]
2044	      AND
2045		[GalleryEntity::entityType] IN (?,?,?,?)
2046	    ';
2047	    list ($ret, $searchResults) = $gallery->search($query,
2048		array('GalleryDerivativeImage', 'GalleryUser', 'GalleryGroup', 'GalleryComment'));
2049	    if ($ret) {
2050		return $ret;
2051	    }
2052	    $itemIds = array();
2053	    while ($result = $searchResults->nextResult()) {
2054		$itemIds[] = (int)$result[0];
2055	    }
2056	    $total = count($itemIds);
2057	    $query = 'DELETE FROM [GalleryAccessSubscriberMap] WHERE [::itemId] IN (';
2058
2059	    for ($i = 0; $itemIds; $i += count($ids)) {
2060		$gallery->guaranteeTimeLimit(30);
2061		$ret = $statusMonitor->renderStatusMessage(
2062		    $module->translate('Optimizing AccessSubscriberMap table'), '', $i / $total);
2063		if ($ret) {
2064		    return $ret;
2065		}
2066		$ids = array_splice($itemIds, 0, 500);
2067		$markers = GalleryUtilities::makeMarkers($ids);
2068		$ret = $storage->execute($query . $markers . ')', $ids);
2069		if ($ret) {
2070		    return $ret;
2071		}
2072	    }
2073	    if ($total) {
2074		$ret = $statusMonitor->renderStatusMessage(
2075			$module->translate('Optimizing AccessSubscriberMap table'), '', 1);
2076		if ($ret) {
2077		    return $ret;
2078		}
2079	    }
2080
2081	case '1.1.7':
2082	    /* ItemAddFromServer and ItemAddFromWeb moved to separate module */
2083	    /* Move uploadLocalServer.dir entries to itemadd module in case it is activated later */
2084	    list ($ret, $params) = GalleryCoreApi::fetchAllPluginParameters('module', 'core');
2085	    if ($ret) {
2086		return $ret;
2087	    }
2088	    for ($i = 1; isset($params['uploadLocalServer.dir.' . $i]); $i++) {
2089		$key = 'uploadLocalServer.dir.' . $i;
2090		$ret = GalleryCoreApi::setPluginParameter('module', 'itemadd', $key, $params[$key]);
2091		if ($ret) {
2092		    return $ret;
2093		}
2094		$ret = $module->removeParameter($key);
2095		if ($ret) {
2096		    return $ret;
2097		}
2098	    }
2099
2100	case '1.1.8':
2101	    /* Remove lock subdirs */
2102	    $locksDir = $gallery->getConfig('data.gallery.locks');
2103	    if ($platform->file_exists($locksDir)) {
2104		@$platform->recursiveRmDir($locksDir);
2105	    }
2106	    @$platform->mkdir($locksDir);
2107
2108	case '1.1.9':
2109	    /* Graphics toolkits now support percentages for thumbnail/scale/resize */
2110	case '1.1.10':
2111	    /* Moved ItemCreateLink[Single] to replica module */
2112	case '1.1.11':
2113	    /* GalleryAuthPlugin: set active user from session now handled by SessionAuthPlugin */
2114	case '1.1.12':
2115	    /* GalleryCoreApi::getMapEntry */
2116	case '1.1.13':
2117	    /* GalleryDynamicAlbum */
2118	case '1.1.14':
2119	    /*
2120	     * Add a .htaccess file in the storage folder to protect it against direct access
2121	     * in case it is accessible from the web.
2122	     * Moved to case 1.2.23 to make a small change.
2123	     */
2124	case '1.1.15':
2125	    /* Locked Users */
2126	    $ret = $storage->configureStore($module->getId(), array('GalleryUser:1.1'));
2127	    if ($ret) {
2128		return $ret;
2129	    }
2130
2131	case '1.1.16':
2132	    /* Initialize multiple repositories */
2133	    $ret = $module->setParameter('core.repositories', serialize(array('released' => 1)));
2134	    if ($ret) {
2135		return $ret;
2136	    }
2137
2138	    /* Locked plugins */
2139	    $ret = $storage->configureStore($module->getId(), array('GalleryPluginPackageMap:1.0'));
2140	    if ($ret) {
2141		return $ret;
2142	    }
2143
2144	case '1.1.17':
2145	    /* Rolled SessionAuthPlugin into GallerySession.class, so force a factory update */
2146	case '1.1.18':
2147	    /* Added PHP display_errors ini setting to config.php */
2148	case '1.1.19':
2149	    /* Added ConvertDatabaseToUtf8Task */
2150	case '1.1.20':
2151	    /* Add column isEmpty to CacheMap */
2152	    $ret = GalleryCoreApi::removeAllMapEntries('GalleryCacheMap', true);
2153	    if ($ret) {
2154		return $ret;
2155	    }
2156	    $ret = $storage->configureStore($module->getId(), array('GalleryCacheMap:1.0'));
2157	    if ($ret) {
2158		return $ret;
2159	    }
2160	case '1.1.21':
2161	    /* Added authentication token */
2162	case '1.1.22':
2163	    /* Add FailedLoginsMap */
2164	case '1.1.23':
2165	    /* Add JavaScriptWarning.tpl */
2166	case '1.1.24':
2167	    /* Add page-level caching for embedded mode */
2168	case '1.1.25':
2169	    /* 2.2 Release Candidate 1! */
2170	case '1.1.26':
2171	    /* Prevent PHP from showing errors on direct access to config.php */
2172	case '1.1.27':
2173	    /* Changed repository cache directory, easiest to just blow away the old one. */
2174	    $oldDir = $gallery->getConfig('data.gallery.plugins_data') . 'modules/core/repository';
2175	    if ($platform->file_exists($oldDir)) {
2176		@$platform->recursiveRmDir($oldDir);
2177	    }
2178	case '1.1.28':
2179	    /* Added GalleryUrlGenerator::makeAbsoluteUrl() */
2180	case '1.1.29':
2181	    /* 2.2 Release Candidate 2! */
2182	case '1.1.30':
2183	    /* Reposition display_errors in config.php */
2184	case '1.1.31':
2185	    /* 2.2 Release! */
2186
2187	case '1.2.0':
2188	case '1.2.0.x':
2189	    /* Added GalleryCoreApi::getCodeBasePath */
2190
2191	case '1.2.1':
2192	    /* Add support for g2data/locale hierarchy */
2193	    $ret = $storage->checkPoint();
2194	    if ($ret) {
2195		return $ret;
2196	    }
2197	    foreach (array('module', 'theme') as $pluginType) {
2198		list ($ret, $pluginList[$pluginType]) =
2199		    GalleryCoreApi::fetchPluginList($pluginType);
2200		if ($ret) {
2201		    return $ret;
2202		}
2203	    }
2204
2205	    $totalOperations = count($pluginList['module']) + count($pluginList['theme']);
2206	    $currentOperation = 0;
2207	    $upgradingMessageText = $module->translate('Upgrading Plugin Translations');
2208	    foreach (array('module', 'theme') as $pluginType) {
2209		foreach ($pluginList[$pluginType] as $pluginId => $ignored) {
2210		    $gallery->guaranteeTimeLimit(30);
2211
2212		    $ret = $statusMonitor->renderStatusMessage(
2213			$upgradingMessageText, $pluginId,
2214			++$currentOperation / $totalOperations);
2215		    if ($ret) {
2216			return $ret;
2217		    }
2218
2219		    $ret = GalleryCoreApi::installTranslationsForPlugin($pluginType, $pluginId);
2220		    if ($ret) {
2221			return $ret;
2222		    }
2223		}
2224	    }
2225
2226	    $ret = $statusMonitor->renderStatusMessage($upgradingMessageText, '', 1);
2227	    if ($ret) {
2228		return $ret;
2229	    }
2230
2231	    /* We might have lost our database connection so check and reconnect */
2232	    $ret = $storage->validateConnection();
2233	    if ($ret) {
2234		return $ret;
2235	    }
2236
2237	case '1.2.2':
2238	    /* Move modules/core/locale/ hierarchy into po/ */
2239	case '1.2.3':
2240	    /* Added template version number */
2241	case '1.2.4':
2242	    /* Increase allowed length of mime-type strings (mime type map) */
2243	case '1.2.5':
2244	    /* Expose smarty.compile_check flag as a new option and set the default to false */
2245	    $ret = $module->setParameter('smarty.compile_check', '0');
2246	    if ($ret) {
2247		return $ret;
2248	    }
2249	case '1.2.6':
2250	    /* Remove GalleryDataCache template-map */
2251	    $mapFile = $gallery->getConfig('data.gallery.cache') . 'theme/_all/templateMap.txt';
2252	    if ($platform->file_exists($mapFile)) {
2253		@$platform->unlink($mapFile);
2254	    }
2255	case '1.2.7':
2256	    /* Added the ability to set the maintenance mode programmatically. */
2257	case '1.2.8':
2258	    /* Changed the length of the SessionMap data field for mySql & DB2. */
2259	    $ret = $storage->configureStore($module->getId(), array('GallerySessionMap:1.0'));
2260	    if ($ret) {
2261		return $ret;
2262	    }
2263	case '1.2.9':
2264	    /**
2265	     * Added EventLogMap.  A new G2 install will create this sequence in
2266	     * GalleryStorage::configureStore.  Here in an upgrade we call getUniqueId which will
2267	     * create the sequence if it doesn't exist.  Checkpoint first because the error that
2268	     * leads to sequence creation may invalidate a pending transaction (postgres).
2269	     */
2270	    $ret = $storage->checkPoint();
2271	    if ($ret) {
2272		return $ret;
2273	    }
2274	    list ($ret, $sequenceValue) = $storage->getUniqueId(DATABASE_SEQUENCE_EVENT_LOG);
2275	    if ($ret) {
2276		return $ret;
2277	    }
2278	case '1.2.10':
2279	    /* Adding new $requiredEntityType parameter to all loadEntitiesById calls */
2280	case '1.2.11':
2281	case '1.2.12':
2282	    /* Remove database backup task from the admin maintenance screen */
2283	case '1.2.13':
2284	    /*
2285	     * Adding PluginMap entries for all installed plugins.  Until now, a plugin was said to
2286	     * be installed (vs uninstalled) if the _version parameter is set.  And PluginMap had a
2287	     * rather undefined list of entries (at least all active plugins).
2288	     */
2289	    list ($ret, $installedResults) = GalleryCoreApi::getMapEntry(
2290		'GalleryPluginParameterMap',
2291		array('pluginType', 'pluginId', 'parameterValue'),
2292		array('parameterName' => '_version', 'itemId' => 0));
2293	    if ($ret) {
2294		return $ret;
2295	    }
2296
2297	    list ($ret, $pluginResults) = GalleryCoreApi::getMapEntry('GalleryPluginMap',
2298		array('pluginType', 'pluginId'));
2299	    if ($ret) {
2300		return $ret;
2301	    }
2302
2303	    $installedPlugins = $existingEntries = array();
2304	    while (($row = $pluginResults->nextResult()) !== false) {
2305		$existingEntries[$row[0]][$row[1]] = 1;
2306	    }
2307	    while (($row = $installedResults->nextResult()) !== false) {
2308		list ($pluginType, $pluginId, $installedVersion) = $row;
2309		if (empty($installedVersion) && isset($existingEntries[$pluginType][$pluginId])) {
2310		    /* Remove entries of formerly installed but now uninstalled plugins */
2311		    $ret = GalleryCoreApi::removeMapEntry('GalleryPluginMap',
2312			array('pluginType' => $pluginType, 'pluginId' => $pluginId));
2313		} else if (!empty($installedVersion)
2314			&& !isset($existingEntries[$pluginType][$pluginId])) {
2315		    $ret = GalleryCoreApi::addMapEntry('GalleryPluginMap',
2316			array('pluginType' => $pluginType, 'pluginId' => $pluginId, 'active' => 0));
2317		}
2318		if ($ret) {
2319		    return $ret;
2320		}
2321	    }
2322	case '1.2.14':
2323	    /*
2324	     * Add a new column to the Schema table to store the creation sql for each table. This
2325	     * change is to prepare the way for database export functionality.
2326	     */
2327	    $gallery->guaranteeTimeLimit(30);
2328
2329	    /* configureStore for Schema:1.0 removed; see code above switch ($currentVersion) */
2330
2331	    if ($currentVersion == '1.2.0.x'
2332		    && version_compare($currentExactVersion, '1.2.0.2', '>=')) {
2333		/* Skip this for 1.2.0.2 or newer 1.2.0.x, as this upgrade was done in 2.2.2 */
2334		$modules = array();
2335	    } else {
2336		list ($ret, $modules) = GalleryCoreApi::fetchPluginStatus('module', true);
2337		if ($ret) {
2338		    return $ret;
2339		}
2340
2341		$storageExtras =& $storage->_getExtras();
2342
2343		/* Load all table versions */
2344		list ($ret, $tableVersions) = $storageExtras->_loadTableVersions();
2345		if ($ret) {
2346		    return $ret;
2347		}
2348	    }
2349
2350	    $count = 1;
2351	    $total = count($modules);
2352	    $statusText = $module->translate('Converting Schema Table');
2353
2354	    foreach ($modules as $moduleId => $moduleStatus) {
2355		/* Skip uninstalled/unavailable modules */
2356		if (!isset($moduleStatus['active']) || empty($moduleStatus['available'])) {
2357		    continue;
2358		}
2359
2360		$sql = $storageExtras->getModuleSql($moduleId);
2361
2362		if (empty($sql['table'])){
2363		    continue;
2364		}
2365
2366		foreach ($sql['table'] as $tableName => $tableSql) {
2367		    list ($safeName, $unused, $nameInSchema) =
2368			$storage->_translateTableName($tableName);
2369
2370		    if (!array_key_exists($nameInSchema, $tableVersions)) {
2371			continue;
2372		    }
2373
2374		    /*
2375		     * Check if there is a pending alter for the table and skip if there is.
2376		     * Column will be populated when/if that module is upgraded.
2377		     */
2378		    list ($major, $minor) = $tableVersions[$nameInSchema];
2379		    if (!empty($sql['alter'][$tableName][$major][$minor])) {
2380			continue;
2381		    }
2382
2383		    $query = 'UPDATE [GallerySchema] SET [::createSql] = ? WHERE [::name] = ?';
2384		    $ret = $storage->execute($query, array($tableSql, $nameInSchema));
2385		    if ($ret) {
2386			return $ret;
2387		    }
2388		    $gallery->guaranteeTimeLimit(60);
2389		    $ret = $statusMonitor->renderStatusMessage($statusText, '', $count / $total);
2390		    if ($ret) {
2391			return $ret;
2392		    }
2393		}
2394
2395		$ret = $statusMonitor->renderStatusMessage($statusText, '', $count++ / $total);
2396		if ($ret) {
2397		    return $ret;
2398		}
2399	    }
2400	case '1.2.15':
2401	    /* Not used. */
2402	case '1.2.16':
2403	    /* Add database export task from the admin maintenance screen */
2404	case '1.2.17':
2405	    /* Store Entities.inc and Maps.inc definitions in the Schema table. */
2406	    $gallery->guaranteeTimeLimit(30);
2407	    /* Remove _maps & _entities parameters from GalleryPluginParameterMap. See r16620 */
2408	    $query = 'DELETE FROM [GalleryPluginParameterMap]
2409		       WHERE [::parameterName] in (\'_maps\', \'_entities\')
2410			 AND [::pluginType] = \'module\'';
2411	    $ret = $storage->execute($query);
2412	    if ($ret) {
2413		return $ret;
2414	    }
2415	    /* This upgrade is done before the general upgrade code */
2416	case '1.2.18':
2417	    /* Remove unused derivative-meta cache files */
2418	    $statusText = $module->translate('Deleting old fast-download cache');
2419	    $basePath = $gallery->getConfig('data.gallery.cache') . 'derivative';
2420	    for ($i = 0; $i <= 9; $i++) {
2421		$ret = $statusMonitor->renderStatusMessage($statusText, '', $i / 10);
2422		if ($ret) {
2423		    return $ret;
2424		}
2425		for ($j = 0; $j <= 9; $j++) {
2426		    $gallery->guaranteeTimeLimit(120);
2427		    $fileList = @$platform->glob("$basePath/$i/$j/*-meta.inc");
2428		    if ($fileList) {
2429			$gallery->guaranteeTimeLimit(120);
2430			$count = 0;
2431			foreach ($fileList as $file) {
2432			    if (++$count % 100 == 0) {
2433				$gallery->guaranteeTimeLimit(120);
2434				$ret = $statusMonitor->renderStatusMessage($statusText, '',
2435									   $i / 10 + $j / 100);
2436				if ($ret) {
2437				    return $ret;
2438				}
2439			    }
2440			    @$platform->unlink($file);
2441			}
2442		    }
2443		}
2444	    }
2445	    $ret = $statusMonitor->renderStatusMessage($statusText, '', 1);
2446	    if ($ret) {
2447		return $ret;
2448	    }
2449
2450	case '1.2.19':
2451	    /* Use lightweight event system */
2452	case '1.2.20':
2453	case '1.2.21':
2454	    /* Added GalleryStorage::validateConnection */
2455	case '1.2.22':
2456	    /* Added GalleryCoreApi::registerFactoryImplementationForRequest */
2457	case '1.2.23':
2458	    /* Prevent web-access to files in the storage folder */
2459	    $fh = @fopen($gallery->getConfig('data.gallery.base') . '.htaccess', 'w');
2460	    if ($fh) {
2461		$htaccessContents = "DirectoryIndex .htaccess\n" .
2462				    "SetHandler Gallery_Security_Do_Not_Remove\n" .
2463				    "Options None\n" .
2464				    "<IfModule mod_rewrite.c>\n" .
2465				    "RewriteEngine off\n" .
2466				    "</IfModule>\n" .
2467				    "Order allow,deny\n" .
2468				    "Deny from all\n";
2469		fwrite($fh, $htaccessContents);
2470		fclose($fh);
2471	    }
2472	case '1.2.24':
2473	    /* Combined YUI libraries into a single file */
2474	case '1.2.25':
2475	    /* Added GalleryCoreApi::determineMimeType */
2476	case '1.2.26':
2477	    /* Combined javascript fix. Changed version number to force clearing of the cache */
2478	case '1.2.27':
2479	    /* Populate Plugin Map when a module is installed */
2480	    $ret = $storage->checkPoint();
2481	    if ($ret) {
2482		return $ret;
2483	    }
2484	    foreach (array('module', 'theme') as $pluginType) {
2485		list ($ret, $pluginList[$pluginType]) =
2486		    GalleryCoreApi::fetchPluginList($pluginType);
2487		if ($ret) {
2488		    return $ret;
2489	 	}
2490	    }
2491
2492	    $supportedLanguages = GalleryCoreApi::getSupportedLanguages();
2493	    $totalOperations = count($pluginList['module']) + count($pluginList['theme']);
2494	    $currentOperation = 0;
2495	    $upgradingMessageText = $module->translate('Creating Package Map');
2496
2497	    GalleryCoreApi::requireOnce('modules/core/classes/GalleryRepository.class');
2498	    if (!empty($repositories)) {
2499		$repository = array_pop($repositories);
2500	    } else {
2501		$repository = new GalleryRepository();
2502		$repository->init('bogus');
2503	    }
2504
2505	    foreach (array('module', 'theme') as $pluginType) {
2506		foreach (array_keys($pluginList[$pluginType]) as $pluginId) {
2507		    $gallery->guaranteeTimeLimit(30);
2508
2509		    $ret = $statusMonitor->renderStatusMessage(
2510			$upgradingMessageText, $pluginId, ++$currentOperation / $totalOperations);
2511		    if ($ret) {
2512			return $ret;
2513		    }
2514
2515		    $ret = $repository->scanPlugin($pluginType, $pluginId);
2516		    if ($ret && !($ret->getErrorCode() & ERROR_STORAGE_FAILURE)) {
2517			if ($gallery->getDebug()) {
2518			    $gallery->debug_r($ret);
2519			}
2520		    } else if ($ret) {
2521			return $ret;
2522		    }
2523		}
2524	    }
2525
2526	    $ret = $statusMonitor->renderStatusMessage($upgradingMessageText, '', 1);
2527	    if ($ret) {
2528		return $ret;
2529	    }
2530
2531	    /* We might have lost our database connection so check and reconnect */
2532	    $ret = $storage->validateConnection();
2533	    if ($ret) {
2534		 return $ret;
2535	    }
2536	case '1.2.28':
2537	    /* Language Manager Implementation */
2538	case '1.2.29':
2539	    /* GalleryCoreApi::fetchWebFile accepts post data and can use the POST method */
2540	case '1.2.30':
2541	    /* Implement Multipart request downloads for Language Manager */
2542	case '1.2.31':
2543	    /* Implement re-authentication for Site Admin Access */
2544	    $ret = $module->setParameter('session.siteAdministrationTimeout', 30 * 60);
2545	    if ($ret) {
2546		return $ret;
2547	    }
2548	case '1.2.32':
2549	    /* Set the default baseUri in config.php */
2550	case '1.2.33':
2551	case '1.2.34':
2552	    /* Change the template compile directory to include the themeId */
2553	case '1.2.35':
2554	    /* R2.3 RC-1 */
2555	case '1.2.36':
2556	    /* R2.3 RC-2 */
2557	case '1.2.37':
2558	    /* R2.3 */
2559	case '1.3.0':
2560	    /* R2.3.1 R2.3.2*/
2561	case '1.3.0.x':
2562
2563	case 'end of upgrade path':
2564	    /*
2565	     * Leave this bogus case at the end of the legitimate case statements so that we always
2566	     * properly terminate our upgrade path with a break
2567	     */
2568	    break;
2569
2570	default:
2571	    $gallery->debug('Error: Unknown module version');
2572	    return GalleryCoreApi::error(ERROR_BAD_PLUGIN, __FILE__, __LINE__,
2573					 sprintf('Unknown module version %s', $currentVersion));
2574	}
2575
2576	$gallery->debug('Write new version to versions file');
2577	$versionFile = $gallery->getConfig('data.gallery.version');
2578	$versionDatError =  0;
2579	if ($fd = $platform->fopen($versionFile, 'wb')) {
2580	    $data = sprintf("%s\n%s",
2581			    $module->getVersion(),
2582			    $module->getGalleryVersion());
2583	    if ($platform->fwrite($fd, $data) != strlen($data)) {
2584		$versionDatError = 1;
2585	    }
2586	    $platform->fclose($fd);
2587	} else {
2588	    $versionDatError = 1;
2589	}
2590
2591	if ($versionDatError) {
2592	    $gallery->debug('Error: Can\'t write to versions file');
2593	    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__,
2594					'Can\'t write to the versions file');
2595	}
2596
2597	return null;
2598    }
2599
2600    /**
2601     * Determine what changes to config.php are required for this upgrade.
2602     *
2603     * @param string $currentVersion current core version
2604     * @return array of array('remove' => array of string regexp removals,
2605     *                        'add' => array of string additions)
2606     * @access private
2607     */
2608    function _prepareConfigUpgrade($currentVersion) {
2609	global $gallery;
2610	$configChanges = array();
2611
2612	/* Enable upgrade from any patch release of earlier versions */
2613	$currentVersion = preg_replace('/^(1\.[0-3]\.0)\.\d+$/', '$1.x', $currentVersion);
2614
2615	/**
2616	 * README: How to update the block below
2617	 *
2618	 * If you add a new feature to the core module and revise the version, you should do the
2619	 * following.  Supposing the current version is 1.0.1 and you're adding 1.0.2.  Go to the
2620	 * end of the switch and find the 'end of upgrade path' case.  Create a new case *above*
2621	 * that one with the old version number.  For our example you'd add: "case '1.0.1':" and
2622	 * then your code.  Do *not* put in a break statement.  (Update upgrade function too).
2623	 */
2624	switch ($currentVersion) {
2625	case '0.8.4':
2626	case '0.8.5':
2627	case '0.8.6':
2628	case '0.8.7':
2629	case '0.8.8':
2630	case '0.8.9':
2631	case '0.8.10':
2632	case '0.8.11':
2633	case '0.8.12':
2634	case '0.8.13':
2635	case '0.8.14':
2636	case '0.8.15':
2637	case '0.8.16':
2638	case '0.8.17':
2639	case '0.9.0':
2640	case '0.9.1':
2641	case '0.9.2':
2642	case '0.9.3':
2643	case '0.9.4':
2644	case '0.9.5':
2645	case '0.9.6':
2646	case '0.9.7':
2647	case '0.9.8':
2648	case '0.9.9':
2649	case '0.9.10':
2650	case '0.9.11':
2651	case '0.9.12':
2652	case '0.9.13':
2653	case '0.9.14':
2654	case '0.9.15':
2655	case '0.9.16':
2656	case '0.9.17':
2657	case '0.9.18':
2658	case '0.9.19':
2659	    $add = array();
2660	    if (!isset($gallery->_config['allowSessionAccess'])) {
2661		/*
2662		 * This item was added to config.php before config.php upgrades were supported.  Add
2663		 * it only if not already present.
2664		 */
2665		$add[] =
2666'/*
2667 * Allow a particular IP address to access the session (it still must know the
2668 * session id) even though it doesn\'t match the address/user agent that created
2669 * the session.  Put the address of validator.w3.org (\'128.30.52.13\') here to allow
2670 * validation of non-public Gallery pages from the links at the bottom of the page.
2671 */
2672$gallery->setConfig(\'allowSessionAccess\', false);
2673';
2674	    }
2675
2676	    $add[] =
2677'/*
2678 * URL of Gallery codebase; required only for multisite install.
2679 */
2680$gallery->setConfig(\'galleryBaseUrl\', \'\');
2681';
2682
2683	    $configChanges[] = array(
2684		'remove' => array('{/\*[^/]*\*/\s*\$gallery->setConfig\(\'galleryId\',.*?;\s*}s'),
2685		'add' => $add, 'edit' => array());
2686
2687	case '0.9.20':
2688	case '0.9.21':
2689	    $add = array();
2690
2691	    /* Generate cookieId */
2692	    list ($usec, $sec) = explode(" ", microtime());
2693	    $cookieId = substr(md5(rand()), 0, 6);
2694
2695	    $add[] =
2696'
2697/*
2698 * Set the name for Gallery session cookies.  The name of the session cookie is
2699 * a concatenation of \'GALLERYSID_\' and cookieId, which is randomly generated
2700 * at Gallery installation time.  You can change cookieId at any time, but if
2701 * you change it be aware of two things:
2702 * 1. Users have to login again after the change.  They lose their old session.
2703 * 2. If multiple Gallery installs are running on the same domain (in different paths or
2704 *    different subdomains) choose cookieId such that it is different for all Gallery
2705 *    installs on the same domain.
2706 */
2707$gallery->setConfig(\'cookieId\', \'' . $cookieId . '\');
2708';
2709	    $configChanges[] = array('remove' => array(), 'add' => $add, 'edit' => array());
2710
2711	case '0.9.22':
2712	case '0.9.23':
2713	    /* Session cookie change, revert the last change and try something new */
2714	    $configChanges[] = array(
2715		'remove' => array('{/\*[^/]*\*/\s*\$gallery->setConfig\(\'cookieId\',.*?;\s*}s'),
2716		'add' => array(), 'edit' => array());
2717
2718	case '0.9.24':
2719	case '0.9.25':
2720	case '0.9.26':
2721	case '0.9.27':
2722	case '0.9.28':
2723	case '0.9.29':
2724	case '0.9.30':
2725	case '0.9.31':
2726	case '0.9.32':
2727	case '0.9.33':
2728	case '0.9.34':
2729	case '0.9.35':
2730	case '0.9.36':
2731	case '0.9.37':
2732	case '1.0.0':
2733	case '1.0.0.x':
2734	case '1.0.1':
2735	case '1.0.2':
2736	case '1.0.3':
2737
2738	case '1.0.4':
2739	    $configChanges[] = array('remove' => array(), 'edit' => array(), 'add' => array(
2740'
2741/*
2742 * Maintenance mode.  You can disable access to the site for anyone but
2743 * site administrators by setting this this flag.  Set value below to:
2744 *  true (without quotes) - to use a basic notification page; themed
2745 *    view with admin login link when codebase is up to date, but a
2746 *    plain unstyled page when codebase has been updated but upgrader
2747 *    has not yet been run.
2748 *  url (with quotes) - provide a URL where requests are redirected in
2749 *    either case described above.  Example: \'/maintenance.html\'
2750 */
2751$gallery->setConfig(\'mode.maintenance\', false);
2752'));
2753
2754	case '1.0.5':
2755	case '1.0.6':
2756	case '1.0.7':
2757	case '1.0.8':
2758	case '1.0.9':
2759	case '1.0.10':
2760	case '1.0.11':
2761	case '1.0.12':
2762	case '1.0.13':
2763	    /* Add config parameter: 'baseUri' */
2764	    $configChanges[] = array('remove' => array(), 'edit' => array(), 'add' => array(
2765'
2766/*
2767 * This setting can be used to override Gallery\'s auto-detection of the domain-name,
2768 * protocol (http/https), URL path, and of the file & query string.
2769 * Most users can leave this empty.  If the server is misconfigured or for very special
2770 * setups, this setting can be quite handy.
2771 * Examples (the positions of the slashes (\'/\') are important):
2772 *   override the path: $gallery->setConfig(\'baseUri\', \'/another/path/\');
2773 *   override the host + path: $gallery->setConfig(\'baseUri\', \'example.com/gallery2/\');
2774 *   override the protocol + host + path + file:
2775 *           $gallery->setConfig(\'baseUri\', \'https://example.com:8080/gallery2/index.php\');
2776 */
2777$gallery->setConfig(\'baseUri\', \'\');'));
2778
2779	case '1.0.14':
2780	case '1.0.15':
2781	    /*
2782	     * Normalize the config path 'data.gallery.base' (add a trailing slash if necessary).
2783	     * Escape the backslashes and quotes two times since we feed preg_replace with it.
2784	     */
2785	    $edit = array();
2786	    $tmp = strtr($gallery->getConfig('data.gallery.base'),
2787			 array('\\' => '\\\\\\\\', "'" => "\\\\'"));
2788	    $edit['regexp'] = '{\$gallery->setConfig\(\'data\.gallery\.base\',.*?;}s';
2789	    $edit['replacement'] = '$gallery->setConfig(\'data.gallery.base\', \'' . $tmp . '\');';
2790	    $configChanges[] = array('remove' => array(), 'add' => array(), 'edit' => array($edit));
2791	case '1.0.16':
2792	case '1.0.17':
2793	case '1.0.18':
2794	case '1.0.19':
2795	case '1.0.20':
2796	case '1.0.21':
2797	case '1.0.22':
2798	case '1.0.23':
2799	case '1.0.24':
2800	case '1.0.25':
2801	case '1.0.26':
2802	case '1.0.27':
2803	case '1.0.28':
2804	case '1.0.29':
2805	case '1.0.30':
2806	case '1.0.31':
2807	case '1.0.32':
2808	case '1.0.33':
2809	case '1.0.34':
2810	case '1.1.0':
2811	case '1.1.0.x':
2812	case '1.1.1':
2813	case '1.1.2':
2814	case '1.1.3':
2815	case '1.1.4':
2816	case '1.1.5':
2817	case '1.1.6':
2818	case '1.1.7':
2819	case '1.1.8':
2820	case '1.1.9':
2821	case '1.1.10':
2822	case '1.1.11':
2823	case '1.1.12':
2824	case '1.1.13':
2825	case '1.1.14':
2826	case '1.1.15':
2827	case '1.1.16':
2828	case '1.1.17':
2829	case '1.1.18':
2830	    /* Originally added PHP display_errors setting in this step, but at the end. */
2831	case '1.1.19':
2832	case '1.1.20':
2833	case '1.1.21':
2834	case '1.1.22':
2835	case '1.1.23':
2836	case '1.1.24':
2837	case '1.1.25':
2838	case '1.1.26':
2839	    /*
2840	     * Prevent PHP from showing errors on direct access to config.php by adding a check
2841	     * for the $gallery object before the first setConfig() call.
2842	     */
2843	    $edit = array();
2844	    $edit['regexp'] = '{(<\?php\s*(?:/\*.*?\*/\s*)?)}s';
2845	    $edit['replacement'] = '\1/*
2846 * Prevent direct access to config.php.
2847 */
2848if (!isset($gallery) || !method_exists($gallery, \'setConfig\')) {
2849    exit;
2850}
2851
2852';
2853	    $configChanges[] = array('remove' => array(), 'add' => array(), 'edit' => array($edit));
2854	case '1.1.27':
2855	case '1.1.28':
2856	case '1.1.29':
2857	case '1.1.30':
2858	    /* Reposition display_errors from the end to the beginning of the config file. */
2859	    $remove = array('{/\*[^/]*\*/\s*\@?ini_set\(\'display_errors\',.*?;\s*}s');
2860	    $edit = array();
2861	    $edit['regexp'] = '{(<\?php\s*(?:/\*.*?\*/\s*)?)}s';
2862	    $edit['replacement'] = '\1/*
2863 * When display_errors is enabled, PHP errors are printed to the output.
2864 * For production web sites, you\'re strongly encouraged to turn this feature off,
2865 * and use error logging instead.
2866 * During development, you should set the value to 1 to ensure that you notice PHP
2867 * warnings and notices that are not covered in unit tests (e.g. template issues).
2868 */
2869@ini_set(\'display_errors\', 0);
2870
2871';
2872	    $configChanges[] = array('remove' => $remove, 'add' => array(), 'edit' => array($edit));
2873	case '1.1.31':
2874	case '1.2.0':
2875	case '1.2.0.x':
2876	case '1.2.1':
2877	case '1.2.2':
2878	case '1.2.3':
2879	case '1.2.4':
2880	case '1.2.5':
2881	case '1.2.6':
2882	case '1.2.7':
2883	case '1.2.8':
2884	case '1.2.9':
2885	case '1.2.10':
2886	case '1.2.11':
2887	case '1.2.12':
2888	case '1.2.13':
2889	case '1.2.14':
2890	case '1.2.15':
2891	case '1.2.16':
2892	case '1.2.17':
2893	case '1.2.18':
2894	case '1.2.19':
2895	case '1.2.20':
2896	case '1.2.21':
2897	case '1.2.22':
2898	case '1.2.23':
2899	case '1.2.24':
2900	case '1.2.25':
2901	case '1.2.26':
2902	case '1.2.27':
2903	case '1.2.28':
2904	case '1.2.29':
2905	case '1.2.30':
2906	case '1.2.31':
2907	case '1.2.32':
2908	    /* Change the baseUri if it's not set. */
2909	    $urlGenerator =& $gallery->getUrlGenerator();
2910	    $urlPath = preg_replace('|^(.*/)upgrade/index.php.*$|s', '$1',
2911				    $urlGenerator->getCurrentUrl()) . GALLERY_MAIN_PHP;
2912	    $edit = array();
2913	    $edit['regexp'] = '{\$gallery->setConfig\(\'baseUri\', \'\'\);}s';
2914	    $edit['replacement'] = '$gallery->setConfig(\'baseUri\', \'' . $urlPath . '\');';
2915	    $configChanges[] = array('remove' => array(), 'add' => array(), 'edit' => array($edit));
2916	case '1.2.33':
2917	case '1.2.34':
2918	case '1.2.35':
2919	case '1.2.36':
2920	case '1.2.37':
2921	case '1.3.0':
2922	case '1.3.0.x':
2923
2924	case 'end of upgrade path':
2925	    /*
2926	     * Leave this bogus case at the end of the legitimate case statements so that we always
2927	     * properly terminate our upgrade path with a break
2928	     */
2929	    break;
2930
2931	default:
2932	    $gallery->debug("Unknown module version $currentVersion in prepareConfigUpgrade()");
2933	}
2934
2935	return $configChanges;
2936    }
2937
2938    /**
2939     * Check if any changes to config.php are required for this upgrade.
2940     *
2941     * @param string $currentVersion current core version
2942     * @return boolean true if change is required
2943     */
2944    function isConfigUpgradeRequired($currentVersion) {
2945	$configChanges = CoreModuleExtras::_prepareConfigUpgrade($currentVersion);
2946	return !empty($configChanges);
2947    }
2948
2949    /**
2950     * Perform upgrade of config.php file.
2951     *
2952     * @param string $currentVersion current core version
2953     * @return GalleryStatus a status code
2954     */
2955    function performConfigUpgrade($currentVersion) {
2956	global $gallery;
2957	$platform =& $gallery->getPlatform();
2958
2959	$configFilePath = GALLERY_CONFIG_DIR . '/config.php';
2960	$configContents = implode('', $platform->file($configFilePath));
2961	if (empty($configContents) || strlen($configContents) < 100) {
2962	    return GalleryCoreApi::error(ERROR_MISSING_VALUE, __FILE__, __LINE__,
2963					'Unable to read current config.php contents');
2964	}
2965
2966	$configChanges = CoreModuleExtras::_prepareConfigUpgrade($currentVersion);
2967	foreach ($configChanges as $change) {
2968	    /* preg_replace $count param is only PHP 5.1.0+ */
2969	    foreach ($change['remove'] as $regexp) {
2970		$configContents = preg_replace($regexp, '', $old = $configContents);
2971		if ($configContents == $old) {
2972		    $gallery->debug('Warning: config.php remove pattern not matched: ' . $regexp);
2973		}
2974	    }
2975	    foreach ($change['edit'] as $edit) {
2976		$configContents =
2977		    preg_replace($edit['regexp'], $edit['replacement'], $old = $configContents);
2978		if ($configContents == $old) {
2979		    $gallery->debug(
2980			    'Warning: config.php edit pattern not matched: ' . $edit['regexp']);
2981		}
2982	    }
2983	    foreach ($change['add'] as $content) {
2984		$configContents =
2985		    preg_replace('{\?>\s*\z}', $content . "\n?>\n", $old = $configContents);
2986		if ($configContents == $old) {
2987		    $gallery->debug(
2988			'Warning: config.php add pattern not matched, appending to file instead');
2989		    $configContents .= "\n" . $content . "\n?>\n";
2990		}
2991	    }
2992	}
2993
2994	if (!$out = $platform->fopen($configFilePath, 'w')) {
2995	    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__,
2996					'Unable to write to config.php');
2997	}
2998	if ($platform->fwrite($out, $configContents) < strlen($configContents)) {
2999	    return GalleryCoreApi::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__,
3000					'Unable to write config.php contents');
3001	}
3002	$platform->fclose($out);
3003
3004	return null;
3005    }
3006
3007    /**
3008     * Create the initial all users group.
3009     *
3010     * @param GalleryModule $module the core module
3011     * @return GalleryStatus a status code
3012     */
3013    function _createAllUsersGroup($module) {
3014	global $gallery;
3015
3016	list ($ret, $id) = $module->getParameter('id.allUserGroup');
3017	if ($ret) {
3018	    return $ret;
3019	}
3020
3021	if (!empty($id)) {
3022	    return null;
3023	}
3024
3025	GalleryCoreApi::requireOnce('modules/core/classes/GalleryGroup.class');
3026	$group = new GalleryGroup();
3027
3028	$groupName = $module->translate('Registered Users');
3029	$ret = $group->create($groupName, GROUP_ALL_USERS);
3030	if ($ret) {
3031	    return $ret;
3032	}
3033
3034	$ret = $group->save();
3035	if ($ret) {
3036	    return $ret;
3037	}
3038
3039	$ret = $module->setParameter('id.allUserGroup', $group->getId());
3040	if ($ret) {
3041	    return $ret;
3042	}
3043
3044	return null;
3045    }
3046
3047    /**
3048     * Create the site admins group.
3049     *
3050     * @param GalleryModule $module the core module
3051     * @return GalleryStatus a status code
3052     */
3053    function _createSiteAdminsGroup($module) {
3054	global $gallery;
3055
3056	list ($ret, $id) = $module->getParameter('id.adminGroup');
3057	if ($ret) {
3058	    return $ret;
3059	}
3060
3061	if (!empty($id)) {
3062	    return null;
3063	}
3064
3065	GalleryCoreApi::requireOnce('modules/core/classes/GalleryGroup.class');
3066	$group = new GalleryGroup();
3067
3068	$groupName = $module->translate('Site Admins');
3069	$ret = $group->create($groupName, GROUP_SITE_ADMINS);
3070	if ($ret) {
3071	    return $ret;
3072	}
3073
3074	$ret = $group->save();
3075	if ($ret) {
3076	    return $ret;
3077	}
3078
3079	$ret = $module->setParameter('id.adminGroup', $group->getId());
3080	if ($ret) {
3081	    return $ret;
3082	}
3083
3084	return null;
3085    }
3086
3087    /**
3088     * Create the everybody group.
3089     *
3090     * @param GalleryModule $module the core module
3091     * @return GalleryStatus a status code
3092     */
3093    function _createEverybodyGroup($module) {
3094	global $gallery;
3095
3096	list ($ret, $id) = $module->getParameter('id.everybodyGroup');
3097	if ($ret) {
3098	    return $ret;
3099	}
3100
3101	if (!empty($id)) {
3102	    return null;
3103	}
3104
3105	GalleryCoreApi::requireOnce('modules/core/classes/GalleryGroup.class');
3106	$group = new GalleryGroup();
3107
3108	$groupName = $module->translate('Everybody');
3109	$ret = $group->create($groupName, GROUP_EVERYBODY);
3110	if ($ret) {
3111	    return $ret;
3112	}
3113
3114	$ret = $group->save();
3115	if ($ret) {
3116	    return $ret;
3117	}
3118
3119	$ret = $module->setParameter('id.everybodyGroup', $group->getId());
3120	if ($ret) {
3121	    return $ret;
3122	}
3123
3124	return null;
3125    }
3126
3127    /**
3128     * Create the initial anonymous user.
3129     *
3130     * @param GalleryModule $module the core module
3131     * @return GalleryStatus a status code
3132     */
3133    function _createAnonymousUser($module) {
3134	global $gallery;
3135
3136	list ($ret, $id) = $module->getParameter('id.anonymousUser');
3137	if ($ret) {
3138	    return $ret;
3139	}
3140
3141	if (!empty($id)) {
3142	    return null;
3143	}
3144
3145	GalleryCoreApi::requireOnce('modules/core/classes/GalleryUser.class');
3146	$user = new GalleryUser();
3147
3148	$userName = 'guest';
3149	$fullName = $module->translate('Guest');
3150	$ret = $user->create($userName);
3151	if ($ret) {
3152	    return $ret;
3153	}
3154	$user->setFullName($fullName);
3155	$user->changePassword('');
3156
3157	$ret = $user->save();
3158	if ($ret) {
3159	    return $ret;
3160	}
3161
3162	/* Remove the anonymous user from the Everybody group */
3163	list ($ret, $allUserGroupId) = $module->getParameter('id.allUserGroup');
3164	if ($ret) {
3165	    return $ret;
3166	}
3167	$ret = GalleryCoreApi::removeUserFromGroup($user->getId(), $allUserGroupId);
3168	if ($ret) {
3169	    return $ret;
3170	}
3171
3172	$ret = $module->setParameter('id.anonymousUser', $user->getId());
3173	if ($ret) {
3174	    return $ret;
3175	}
3176
3177	return null;
3178    }
3179
3180    /**
3181     * Create the initial admin user.
3182     *
3183     * @param GalleryModule $module the core module
3184     * @return GalleryStatus a status code
3185     */
3186    function _createAdminUser($module) {
3187	global $gallery;
3188
3189	/* Don't create if there is already a user in the admin group */
3190	list ($ret, $adminGroupId) = $module->getParameter('id.adminGroup');
3191	if ($ret) {
3192	    return $ret;
3193	}
3194
3195	list ($ret, $results) = GalleryCoreApi::fetchUsersForGroup($adminGroupId);
3196	if ($ret) {
3197	    return $ret;
3198	}
3199
3200	if (sizeof($results) > 0) {
3201	    return null;
3202	}
3203
3204	GalleryCoreApi::requireOnce('modules/core/classes/GalleryUser.class');
3205	$user = new GalleryUser();
3206
3207	/*
3208	 * Get the admin name and data from the installer and default to 'admin' if it's not
3209	 * available for some reason
3210	 */
3211	$userName = $gallery->getConfig('setup.admin.userName');
3212	$userName = !strlen($userName) ? 'admin' : $userName;
3213	$email = $gallery->getConfig('setup.admin.email');
3214	$fullName = $gallery->getConfig('setup.admin.fullName');
3215	$ret = $user->create($userName);
3216	if ($ret) {
3217	    return $ret;
3218	}
3219	$user->changePassword($gallery->getConfig('setup.password'));
3220	$user->setFullName($fullName);
3221	$user->setEmail($email);
3222
3223	$ret = $user->save();
3224	if ($ret) {
3225	    return $ret;
3226	}
3227
3228	/* Add her to the admin group */
3229	$ret = GalleryCoreApi::addUserToGroup($user->getId(), $adminGroupId);
3230	if ($ret) {
3231	    return $ret;
3232	}
3233
3234	/*
3235	 * The rest of the bootstrap code won't work so well unless we're logged in, so log in as
3236	 * the admin user now
3237	 */
3238	$gallery->setActiveUser($user);
3239
3240	return null;
3241    }
3242
3243    /**
3244     * Create the root album item.
3245     *
3246     * @param GalleryModule $module the core module
3247     * @return GalleryStatus a status code
3248     */
3249    function _createRootAlbumItem($module) {
3250	global $gallery;
3251
3252	/* Do we already have a root? */
3253	list ($ret, $rootAlbumId) = $module->getParameter('id.rootAlbum');
3254	if ($rootAlbumId) {
3255	    return null;
3256	}
3257
3258	GalleryCoreApi::requireOnce('modules/core/classes/GalleryAlbumItem.class');
3259	$album = new GalleryAlbumItem();
3260
3261	$ret = $album->createRoot();
3262	if ($ret) {
3263	    return $ret;
3264	}
3265	$title = $module->translate('Gallery');
3266	$description = $module->translate('This is the main page of your Gallery');
3267	$album->setTitle($title);
3268	$album->setDescription($description);
3269
3270	$ret = $album->save();
3271	if ($ret) {
3272	    return $ret;
3273	}
3274
3275	/* Give everybody some permissions */
3276	list ($ret, $groupId) = $module->getParameter('id.everybodyGroup');
3277	if ($ret) {
3278	    return $ret;
3279	}
3280
3281	$ret = GalleryCoreApi::addGroupPermission($album->getId(), $groupId, 'core.viewAll');
3282	if ($ret) {
3283	    return $ret;
3284	}
3285
3286	/* Grant admin users everything */
3287	list ($ret, $groupId) = $module->getParameter('id.adminGroup');
3288	if ($ret) {
3289	    return $ret;
3290	}
3291
3292	$ret = GalleryCoreApi::addGroupPermission($album->getId(), $groupId, 'core.all');
3293	if ($ret) {
3294	    return $ret;
3295	}
3296
3297	$ret = $module->setParameter('id.rootAlbum', $album->getId());
3298	if ($ret) {
3299	    return $ret;
3300	}
3301
3302	return null;
3303    }
3304
3305    /**
3306     * Create the access list compactor lock entity.
3307     *
3308     * @param GalleryModule $module the core module
3309     * @return GalleryStatus a status code
3310     */
3311    function _createAccessListCompacterLock($module) {
3312	global $gallery;
3313
3314	/* Do we already have a root? */
3315	list ($ret, $compacterLockId) = $module->getParameter('id.accessListCompacterLock');
3316	if ($compacterLockId) {
3317	    return null;
3318	}
3319
3320	GalleryCoreApi::requireOnce('modules/core/classes/GalleryEntity.class');
3321	$lock = new GalleryEntity();
3322	$lock->create();
3323	$ret = $lock->save(false);
3324	if ($ret) {
3325	    return $ret;
3326	}
3327
3328	$ret = $module->setParameter('id.accessListCompacterLock', $lock->getId());
3329	if ($ret) {
3330	    return $ret;
3331	}
3332
3333	return null;
3334    }
3335
3336    /**
3337     * @see GalleryModule::performFactoryRegistrations
3338     */
3339    function performFactoryRegistrations($module) {
3340	/* Register all our factory implementations */
3341	$regs[] = array('GalleryEntity', 'GalleryEntity', 'class', null);
3342	$regs[] = array('GalleryEntity', 'GalleryChildEntity', 'class', null);
3343	$regs[] = array('GalleryEntity', 'GalleryAlbumItem', 'class', null);
3344	$regs[] = array('GalleryEntity', 'GalleryUser', 'class', null);
3345	$regs[] = array('GalleryEntity', 'GalleryGroup', 'class', null);
3346	$regs[] = array('GalleryEntity', 'GalleryDerivative', 'class', null);
3347	$regs[] = array('GalleryEntity', 'GalleryDerivativeImage', 'class', null);
3348	$regs[] = array('GalleryDerivative', 'GalleryDerivativeImage', 'class', array('*'));
3349	$regs[] = array('GalleryEntity', 'GalleryMovieItem', 'class', null);
3350	$regs[] = array('GalleryEntity', 'GalleryAnimationItem', 'class', null);
3351	$regs[] = array('GalleryEntity', 'GalleryPhotoItem', 'class', null);
3352	$regs[] = array('GalleryEntity', 'GalleryUnknownItem', 'class', null);
3353	$regs[] = array('GalleryItem', 'GalleryPhotoItem', 'class',
3354			array('image/*', 'application/photoshop'));
3355	$regs[] = array('GalleryItem', 'GalleryMovieItem', 'class', array('video/*'));
3356	$regs[] = array('GalleryItem', 'GalleryAnimationItem', 'class',
3357			array('application/x-director', 'application/x-shockwave-flash'));
3358	$regs[] = array('GalleryItem', 'GalleryUnknownItem', 'class', array('*'));
3359	$regs[] = array('GalleryDynamicAlbum', 'GalleryDynamicAlbum', 'class', null);
3360	$regs[] = array('GallerySearchInterface_1_0', 'GalleryCoreSearch', 'class', null);
3361	$regs[] = array('ItemEditPlugin', 'ItemEditItem', 'inc', null, 1);
3362	$regs[] = array('ItemEditPlugin', 'ItemEditAnimation', 'inc', null, 2);
3363	$regs[] = array('ItemEditPlugin', 'ItemEditMovie', 'inc', null, 2);
3364	$regs[] = array('ItemEditPlugin', 'ItemEditAlbum', 'inc', null, 2);
3365	$regs[] = array('ItemEditPlugin', 'ItemEditTheme', 'inc', null, 3);
3366	$regs[] = array('ItemEditPlugin', 'ItemEditPhoto', 'inc', null, 2);
3367	$regs[] = array('ItemEditPlugin', 'ItemEditRotateAndScalePhoto', 'inc', null, 3);
3368	$regs[] = array('ItemEditPlugin', 'ItemEditPhotoThumbnail', 'inc', null, 4);
3369	$regs[] = array('ItemAddPlugin', 'ItemAddFromBrowser', 'inc', null, 2);
3370	$regs[] = array('ItemAddOption', 'CreateThumbnailOption', 'inc', null, 8);
3371	$regs[] = array('MaintenanceTask', 'OptimizeDatabaseTask', 'class', null);
3372	$regs[] = array('MaintenanceTask', 'DatabaseBackupTask', 'class', null);
3373	$regs[] = array('MaintenanceTask', 'FlushTemplatesTask', 'class', null);
3374	$regs[] = array('MaintenanceTask', 'FlushDatabaseCacheTask', 'class', null);
3375	$regs[] = array('MaintenanceTask', 'BuildDerivativesTask', 'class', null);
3376	$regs[] = array('MaintenanceTask', 'ResetViewCountsTask', 'class', null);
3377	$regs[] = array('MaintenanceTask', 'SystemInfoTask', 'class', null);
3378	$regs[] = array('MaintenanceTask', 'SetOriginationTimestampTask', 'class', null);
3379	$regs[] = array('MaintenanceTask', 'DeleteSessionsTask', 'class', null);
3380	$regs[] = array('MaintenanceTask', 'ConvertDatabaseToUtf8Task', 'class', null);
3381	$regs[] = array('CaptchaAdminOption', 'CoreCaptchaAdminOption', 'class', null);
3382
3383	/*
3384	 * Unlike other modules, the core module doesn't get deactivated so its factory
3385	 * registrations may still be around from before.  Unregister them now before reregistering
3386	 * them all.
3387	 */
3388	$ret = GalleryCoreApi::unregisterFactoryImplementationsByModuleId($module->getId());
3389	if ($ret) {
3390	    return $ret;
3391	}
3392
3393	foreach ($regs as $entry) {
3394	    $ret = GalleryCoreApi::registerFactoryImplementation($entry[0],
3395		$entry[1], $entry[1], $entry[2] == 'class'
3396		    ? 'modules/core/classes/' . $entry[1] . '.class'
3397		    : 'modules/core/' . $entry[1] . '.inc',
3398		'core', $entry[3], isset($entry[4]) ? $entry[4] : 4);
3399	    if ($ret) {
3400		return $ret;
3401	    }
3402	}
3403
3404	/* Special cases */
3405	$ret = GalleryCoreApi::registerFactoryImplementation('GalleryAuthPlugin',
3406	    'SessionAuthPlugin', 'SessionAuthPlugin',
3407	    'modules/core/classes/GallerySession.class', 'core', null, 4);
3408	if ($ret) {
3409	    return $ret;
3410	}
3411
3412	$ret = GalleryCoreApi::registerFactoryImplementation('GalleryEventListener',
3413	    'GalleryItemHelper_medium', 'GalleryItemHelper_medium',
3414	    'modules/core/classes/helpers/GalleryItemHelper_medium.class', 'core',
3415	    array('Gallery::ViewableTreeChange',
3416		  'Gallery::RemovePermission',
3417		  'GalleryEntity::save',
3418		  'GalleryEntity::delete'), 4);
3419	if ($ret) {
3420	    return $ret;
3421	}
3422
3423	$ret = GalleryCoreApi::registerFactoryImplementation('GalleryEventListener',
3424	    'GalleryUserHelper_medium', 'GalleryUserHelper_medium',
3425	    'modules/core/classes/helpers/GalleryUserHelper_medium.class', 'core',
3426	    array('Gallery::FailedLogin',
3427		  'Gallery::Login'), 4);
3428	if ($ret) {
3429	    return $ret;
3430	}
3431
3432	return null;
3433    }
3434
3435    /**
3436     * Change character set encoding to utf 8 for MySQL if necessary.  This is public because it
3437     * is also used by ConvertDatabaseToUtf8Task.
3438     *
3439     * @return array GalleryStatus a status code
3440     *               bool true if any conversions took place
3441     * @access public
3442     */
3443    function convertCharacterSetToUtf8($module, $statusMonitor) {
3444	global $gallery;
3445	$storage =& $gallery->getStorage();
3446	$converted = false;
3447
3448	if ($storage->getType() == 'mysql') {
3449	    $version = $storage->getVersion();
3450	    /* MySQL < 4.1.0 does not support UTF8 */
3451	    if ($version && version_compare($version, '4.1.0', '>=')) {
3452		/*
3453		 * Check if the database uses UTF8 already, by looking at the Schema table, which
3454		 * we convert last.
3455		 */
3456		list ($ret, $results) =
3457		    $storage->search('SHOW CREATE TABLE `' . $storage->_tablePrefix . 'Schema`');
3458		if ($ret) {
3459		    return array($ret, null);
3460		}
3461		$row = $results->nextResult();
3462		$result = $row[1];
3463		if (!$result || !preg_match('/utf8/i', $result)) {
3464		    /* Convert all existing tables to UTF-8 */
3465		    $ret = $statusMonitor->renderStatusMessage(
3466			$module->translate('Converting MySQL data to UTF8'), null, 0);
3467		    if ($ret) {
3468			return array($ret, null);
3469		    }
3470		    $gallery->guaranteeTimeLimit(120);
3471		    $storageExtras =& $storage->_getExtras();
3472		    list ($ret, $tableVersions) = $storageExtras->_loadTableVersions();
3473		    if ($ret) {
3474			return array($ret, null);
3475		    }
3476		    $types = array('varchar'  => 'varbinary',
3477				   'text'     => 'blob',
3478				   'longtext' => 'longblob');
3479		    $i = 0;
3480
3481		    foreach ($tableVersions as $tableName => $unused) {
3482			$i++;
3483			$tableName = $storage->_tablePrefix . $tableName;
3484			/* First the table itself */
3485			$query = "ALTER TABLE `$tableName` DEFAULT CHARACTER SET utf8";
3486			$ret = $storage->execute($query);
3487			if ($ret) {
3488			    return array($ret, null);
3489			}
3490			/*
3491			 * Then all character/string columns
3492			 * See: http://dev.mysql.com/doc/refman/4.1/en/charset-conversion.html
3493			 *
3494			 * The following code is based significantly on code from Drupal,
3495			 * For details, refer to:
3496			 *   - http://api.drupal.org/api/4.7/file/update.php/source
3497			 *   - http://api.drupal.org/api/4.7/file/LICENSE.txt
3498			 *   - http://drupal.org/node/40515
3499			 *
3500			 * Drupal is licensed under the GPL:
3501			 *
3502			 * 1. Detect current column attributes
3503			 * 2. Convert text column to binary column
3504			 * 3. Convert them to character/text columns with UTF8 charset
3505			 */
3506			$query = "SHOW FULL COLUMNS FROM `$tableName`";
3507			$originalFetchMode = $storage->_db->SetFetchMode(ADODB_FETCH_ASSOC);
3508			list ($ret, $results) = $storage->search($query);
3509			if ($ret) {
3510			    return array($ret, null);
3511			}
3512			$storage->_db->SetFetchMode($originalFetchMode);
3513			$changeToBinary = $changeToUtf8 = array();
3514			while ($column = $results->nextResult()) {
3515			    list ($type) = explode('(', $column['Type']);
3516			    if (!isset($types[$type])) {
3517				continue;
3518			    }
3519			    $change =
3520				'CHANGE `' . $column['Field'] . '` `' . $column['Field'] . '` ';
3521			    $binaryType = preg_replace('/'. $type .'/i', $types[$type],
3522						       $column['Type']);
3523			    $attributes = ' ';
3524			    if ($column['Default'] == 'NULL') {
3525				$attributes .= 'DEFAULT NULL ';
3526			    } else if (!empty($column['Default'])) {
3527				$attributes .= 'DEFAULT ' . $column['Default'] . ' ';
3528			    }
3529			    $attributes .= $column['Null'] == 'YES' ? 'NULL' : 'NOT NULL';
3530			    $changeToBinary[] = $change . $binaryType . $attributes;
3531			    $changeToUtf8[] =
3532				$change . $column['Type'] . ' CHARACTER SET utf8' . $attributes;
3533			}
3534			if (count($changeToBinary)) {
3535			    $query =
3536				"ALTER TABLE `$tableName` " . implode(', ', $changeToBinary);
3537			    $ret = $storage->Execute($query);
3538			    if ($ret) {
3539				return array($ret, null);
3540			    }
3541			    $query = "ALTER TABLE `$tableName` " . implode(', ', $changeToUtf8);
3542			    $ret = $storage->Execute($query);
3543			    if ($ret) {
3544				return array($ret, null);
3545			    }
3546			    $converted = true;
3547			}
3548
3549			$ret = $statusMonitor->renderStatusMessage(
3550			    $module->translate('Converting MySQL data to UTF8'),
3551			    null, $i / count($tableVersions));
3552			if ($ret) {
3553			    return array($ret, null);
3554			}
3555			$gallery->guaranteeTimeLimit(120);
3556		    } /* End for each table */
3557
3558		    if ($converted) {
3559			/* Clear any cache data since it may be in the wrong character set*/
3560			$gallery->guaranteeTimeLimit(60);
3561			$ret = GalleryCoreApi::removeAllMapEntries('GalleryCacheMap', true);
3562			if ($ret) {
3563			    return array($ret, null);
3564			}
3565		    }
3566		} /* End if database character set not UTF-8 */
3567	    } /* End if MySql version > 4.1.0 */
3568	} /* End if MySQL */
3569
3570	return array(null, $converted);
3571    }
3572
3573    /**
3574     * Sort an associative array where the key is the name of the table.  Force
3575     * the schema table to be last in line.
3576     */
3577    function _sortSchemaTableLast($a, $b) {
3578	if ($a == 'Schema') {
3579	    return -1;
3580	} else if ($b == 'Schema') {
3581	    return 1;
3582	} else {
3583	    return strcmp($a, $b);
3584	}
3585    }
3586}
3587?>
3588