diff --git a/.gitignore b/.gitignore
index b796d58b..6735ac1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@ login.txt
*.swp
gallery2-data
mysqldata
+modules/bulkexcelupload
+themes/bootstrap_matrix
+modules/mapv3/images/routes
diff --git a/.htaccess-sample b/.htaccess-sample
new file mode 100644
index 00000000..2dea77b9
--- /dev/null
+++ b/.htaccess-sample
@@ -0,0 +1,56 @@
+#{gallerySection}
+
+RewriteEngine On
+# Redirect all requests to HTTPS
+RewriteCond %{HTTPS} "!=on"
+RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
+
+RedirectMatch 404 \.(md|txt|ini|Dockerfile|git|htaccess|inc)
+RedirectMatch 404 (docker|config\.php|phpinfo|README|LICENSE)
+
+# BEGIN Url Rewrite section
+# (Automatically generated. Do not edit this section)
+
+ RewriteEngine On
+
+ RewriteBase /
+
+ RewriteCond %{REQUEST_FILENAME} -f [OR]
+ RewriteCond %{REQUEST_FILENAME} -d [OR]
+ RewriteCond %{REQUEST_FILENAME} gallery\_remote2\.php
+ RewriteCond %{REQUEST_URI} !/main\.php$
+ RewriteRule . - [L]
+
+ RewriteCond %{QUERY_STRING} g2_view=core.DownloadItem
+ RewriteCond %{QUERY_STRING} g2_itemId=([0-9]+)
+ RewriteCond %{HTTP:Referer} !^[a-zA-Z0-9\+\.\-]+://%{SERVER_NAME}/ [NC]
+ RewriteCond %{HTTP:Referer} !^$
+ RewriteRule . /main.php?g2_view=watermark.DownloadItem&g2_itemId=%1 [L]
+ RewriteCond %{THE_REQUEST} /sitemap(\?.|\ .)
+ RewriteCond %{REQUEST_URI} !/main\.php$
+ RewriteRule . /main.php?g2_view=sitemap.Sitemap [QSA,L]
+ RewriteCond %{THE_REQUEST} /mapa/(G{1}[0-9]+)*(\?.|\ .)
+ RewriteCond %{REQUEST_URI} !/main\.php$
+ RewriteRule . /main.php?g2_view=mapv3.ShowMap&g2_Group=%1 [QSA,L]
+ RewriteCond %{THE_REQUEST} /fotografia/([^?]+)(\?.|\ .)
+ RewriteCond %{REQUEST_URI} !/main\.php$
+ RewriteRule . /main.php?g2_path=%1 [QSA,L]
+ RewriteCond %{THE_REQUEST} /paraules/([^?/]+)(\?.|\ .)
+ RewriteCond %{REQUEST_URI} !/main\.php$
+ RewriteRule . /main.php?g2_view=keyalbum.KeywordAlbum&g2_keyword=%1 [QSA,L]
+
+
+# END Url Rewrite section
+
+
+ ExpiresActive On
+ ExpiresDefault A30600
+
+
+
+ Header append Cache-Control "public"
+
+
+ ExpiresDefault "access plus 7 day"
+
+
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index b380bfe2..99f7c89a 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -145,6 +145,21 @@ for `ca_ES` locale. Add a line like it using your locale:
&& sed -i -e 's/# ca_ES ISO-8859-1/ca_ES ISO-8859-1/' /etc/locale.gen \
```
+## Copy volumes
+
+You can create a backup copy of the `mysqldata` and `gallery2-data` volumes to start afresh.
+
+There is a `copy-volumes.sh` script that allows you to create a copy of the volume.
+
+In case you would like to reset the state to the one in the vanilla volumes, you just need to copy data from the
+vanilla volumes to the new volumes.
+
+You will need to add the volume as an external volume in docker-compose config file:
+```yaml
+ gallery2-php8_mysqldata-fresh:
+ external: true
+```
+
# Release management
Tools for release management are on folder `lib/tools/release`.
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 53bcee0d..39dcf354 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -19,7 +19,7 @@ services:
PHP_IDE_CONFIG: "serverName=docker"
volumes:
- .:/app
- - ./gallery2-data:/gallery2-data
+ - gallery2-data:/gallery2-data
- ./docker/php.ini:/usr/local/etc/php/conf.d/custom.ini
- ./docker/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini
links:
@@ -32,6 +32,10 @@ services:
MYSQL_PASSWORD: 'secret'
MYSQL_DATABASE: 'gallery2'
volumes:
- - ./mysqldata:/var/lib/mysql
+ - mysqldata:/var/lib/mysql
ports:
- "13306:3306"
+
+volumes:
+ mysqldata: { }
+ gallery2-data: { }
diff --git a/docker/copy-volumes.sh b/docker/copy-volumes.sh
new file mode 100644
index 00000000..b90c77d3
--- /dev/null
+++ b/docker/copy-volumes.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -u
+set -a
+
+SOURCE_VOLUME=$1
+TARGET_VOLUME=$2
+
+docker container run --rm -it -v $SOURCE_VOLUME:/from -v $TARGET_VOLUME:/to alpine ash -c "cd /from ; cp -av . /to"
diff --git a/ga.js.sample b/ga.js.sample
new file mode 100644
index 00000000..1d268405
--- /dev/null
+++ b/ga.js.sample
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/install/steps/AuthenticateStep.class b/install/steps/AuthenticateStep.class
index b77ca44c..efe13402 100644
--- a/install/steps/AuthenticateStep.class
+++ b/install/steps/AuthenticateStep.class
@@ -39,7 +39,7 @@ class AuthenticateStep extends InstallStep {
function processRequest() {
if (!empty($_GET['downloadLogin'])) {
- GallerySetupUtilities::generateTextFileForDownload('login.txt', $this->_uniqueKey);
+ GallerySetupUtilities::generateTextFileForDownload('login.txt', $this::_uniqueKey);
return false;
}
diff --git a/install/steps/CreateConfigFileStep.class b/install/steps/CreateConfigFileStep.class
index d7a28404..3317a557 100644
--- a/install/steps/CreateConfigFileStep.class
+++ b/install/steps/CreateConfigFileStep.class
@@ -36,7 +36,7 @@ class CreateConfigFileStep extends InstallStep {
function processRequest() {
if (!empty($_GET['downloadConfig'])) {
GallerySetupUtilities::generateTextFileForDownload(
- 'config.php', $this->_getConfigContents());
+ 'config.php', $this::_getConfigContents());
return false;
}
@@ -132,7 +132,7 @@ class CreateConfigFileStep extends InstallStep {
$this->_firstTime = false;
}
- function _getConfigContents() {
+ static function _getConfigContents() {
global $galleryStub;
$configDir = $_SESSION['configPath'];
$baseDir = dirname(dirname(dirname(__FILE__)));
diff --git a/install/steps/DatabaseSetupStep.class b/install/steps/DatabaseSetupStep.class
index 6899568a..7ab53596 100644
--- a/install/steps/DatabaseSetupStep.class
+++ b/install/steps/DatabaseSetupStep.class
@@ -71,9 +71,9 @@ class DatabaseSetupStep extends InstallStep {
$this->_config['type'] = 'mysqli';
$mysqltType = 'mysqli';
}
- if ($this->_config['type'] == 'pdo_sqlite') {
- $this->_config['database'] =
- 'sqlite:' . $galleryStub->getConfig('data.gallery.base') . 'gallery2.db';
+ if ($this->_config['type'] == 'pdo_sqlite') {
+ $this->_config['database'] =
+ 'sqlite:' . $galleryStub->getConfig('data.gallery.base') . 'gallery2.db';
}
switch ($this->_config['type']) {
case 'mysql':
@@ -154,7 +154,8 @@ class DatabaseSetupStep extends InstallStep {
require_once(dirname(__FILE__) . '/../../lib/adodb/adodb.inc.php');
$this->_captureStart();
- $this->_db =& ADONewConnection($this->_config['type']);
+ $ADOConnection = ADONewConnection($this->_config['type']);
+ $this->_db =& $ADOConnection;
$this->_captureEnd();
if (empty($this->_db)) {
@@ -162,7 +163,7 @@ class DatabaseSetupStep extends InstallStep {
_('Unable to create a database connection of type %s'),
$this->_config['type']);
}
-
+
if (empty($templateData['errors'])) {
$this->_captureStart();
$this->_db->debug = true;
@@ -616,7 +617,7 @@ class DatabaseSetupStep extends InstallStep {
$GLOBALS['gallery']->setConfig('plugins.dirname',
$galleryStub->getConfig('plugins.dirname'));
$GLOBALS['gallery']->setConfig('data.gallery.version', $configPath . 'versions.dat');
- $GLOBALS['gallery']->setConfig('data.gallery.locale', $configPath . 'locale'
+ $GLOBALS['gallery']->setConfig('data.gallery.locale', $configPath . 'locale'
. DIRECTORY_SEPARATOR);
}
global $gallery;
diff --git a/lib/support/GallerySetupUtilities.class b/lib/support/GallerySetupUtilities.class
index 4f8cc7e6..22ecdf2a 100644
--- a/lib/support/GallerySetupUtilities.class
+++ b/lib/support/GallerySetupUtilities.class
@@ -290,13 +290,14 @@ class GallerySetupUtilities {
* @param string $name the file's name
* @param string $contents the file's contents
*/
- function generateTextFileForDownload($name, $contents) {
- header('Content-Type: text/plain');
- header('Content-Length: ' . strlen($contents));
- header("Content-Description: Download $name to your computer.");
- header("Content-Disposition: attachment; filename=$name");
- print $contents;
- }
+ static function generateTextFileForDownload($name, $contents)
+ {
+ header('Content-Type: text/plain');
+ header('Content-Length: ' . strlen($contents));
+ header("Content-Description: Download $name to your computer.");
+ header("Content-Disposition: attachment; filename=$name");
+ print $contents;
+ }
/**
* Cleanly start up our session.
diff --git a/lib/tools/bin/.htaccess b/lib/tools/bin/.htaccess
new file mode 100644
index 00000000..cb24fd7f
--- /dev/null
+++ b/lib/tools/bin/.htaccess
@@ -0,0 +1,2 @@
+Order allow,deny
+Deny from all
diff --git a/lib/tools/bin/GNUmakefile.GalleryStorage b/lib/tools/bin/GNUmakefile.GalleryStorage
new file mode 100644
index 00000000..ca1190e8
--- /dev/null
+++ b/lib/tools/bin/GNUmakefile.GalleryStorage
@@ -0,0 +1,42 @@
+CLASSDIR ?= ..
+TOOLDIR ?= ../../../../lib/tools
+TMPDIR ?= tmp
+RXP ?= $(shell which rxp 2>/dev/null)
+XMLLINT ?= $(shell which xmllint 2>/dev/null)
+XMLFILES = $(wildcard xml-src/*.xml)
+PHP ?= php
+
+all: validate tmp dbxml sql
+
+tmp:
+ @if [ ! -d tmp ]; then mkdir tmp; fi
+ @if [ ! -d tmp/classxml ]; then mkdir tmp/classxml; fi
+ @if [ ! -d tmp/dbxml ]; then mkdir tmp/dbxml; fi
+
+dbxml:
+ @if [ "x" != "$(wildcard $(CLASSDIR)/*.class)x" ]; then \
+ perl $(TOOLDIR)/bin/extractClassXml.pl --dtd=$(TOOLDIR)/dtd/GalleryClass2.1.dtd --quiet --out-dir=tmp/classxml $(CLASSDIR)/*.class; \
+ else \
+ echo "No .class files to parse!"; \
+ fi
+ @if [ "x" != "$(XMLFILES)x" ]; then cp xml-src/*.xml tmp/dbxml; fi
+ $(PHP) -q -C -f $(TOOLDIR)/bin/generate-dbxml.php
+
+sql:
+ $(PHP) -q -C -f $(TOOLDIR)/bin/generate-sql.php
+
+clean:
+ rm -rf tmp
+
+validate:
+ @if [ "x" != "$(XMLFILES)x" ]; then \
+ if [ -x "$(XMLLINT)" ]; then \
+ xmllint --valid --noout $(XMLFILES); \
+ elif [ -x "$(RXP)" ]; then \
+ for xmlfile in $(XMLFILES); do \
+ rxp -sV $$xmlfile; \
+ done \
+ else \
+ echo "No available XML validators (need xmllint or rxp)"; \
+ fi \
+ fi
diff --git a/lib/tools/bin/GNUmakefile.classes b/lib/tools/bin/GNUmakefile.classes
new file mode 100644
index 00000000..7c59d12a
--- /dev/null
+++ b/lib/tools/bin/GNUmakefile.classes
@@ -0,0 +1,26 @@
+RXP ?= $(shell which rxp 2>/dev/null)
+XMLLINT ?= $(shell which xmllint 2>/dev/null)
+PHP ?= php
+CLASSES = $(shell ls *.class 2>/dev/null)
+
+all: Entities.inc
+ if [ -f Maps.xml ]; then $(MAKE) Maps.inc; fi
+ if [ -f GalleryStorage/GNUmakefile ]; then cd GalleryStorage && $(MAKE); fi
+
+%:
+ if [ -f GalleryStorage/GNUmakefile ]; then cd GalleryStorage && $(MAKE) $@; fi
+
+Maps.inc: Maps.xml ../../../lib/tools/bin/maps.tpl
+ @if [ -x "$(XMLLINT)" ]; then \
+ xmllint --valid --noout Maps.xml; \
+ elif [ -x "$(RXP)" ]; then \
+ for xmlfile in $(XMLFILES); do \
+ rxp -sV Maps.xml; \
+ done \
+ else \
+ echo "No available XML validators (need xmllint or rxp)"; \
+ fi
+ $(PHP) -q -C ../../../lib/tools/bin/generate-maps.php
+
+Entities.inc: $(CLASSES) ../../../lib/tools/bin/entities.tpl
+ $(PHP) -q -C ../../../lib/tools/bin/generate-entities.php
diff --git a/lib/tools/bin/XmlParser.inc b/lib/tools/bin/XmlParser.inc
new file mode 100644
index 00000000..4e05da66
--- /dev/null
+++ b/lib/tools/bin/XmlParser.inc
@@ -0,0 +1,84 @@
+_xmlObject = xml_parser_create();
+ xml_set_object($this->_xmlObject, $this);
+ xml_set_character_data_handler($this->_xmlObject, 'dataHandler');
+ xml_set_element_handler($this->_xmlObject, 'startHandler', 'endHandler');
+ }
+
+ public function parse($path) {
+ $data = file_get_contents($path);
+
+ if ($data === false) {
+ die('Cannot open XML data file: ' . $path);
+ }
+
+ if (!xml_parse($this->_xmlObject, $data, 1)) {
+ die(
+ sprintf(
+ 'XML error: %s at line %d',
+ xml_error_string(xml_get_error_code($this->_xmlObject)),
+ xml_get_current_line_number($this->_xmlObject)
+ )
+ );
+ xml_parser_free($this->_xmlObject);
+ }
+
+ return $this->_output;
+ }
+
+ public function startHandler($parser, $name, $attribs) {
+ $content = array(
+ 'name' => $name,
+ );
+
+ if (!empty($attribs)) {
+ $content['attrs'] = $attribs;
+ }
+ array_push($this->_output, $content);
+ }
+
+ public function dataHandler($parser, $data) {
+ if (isset($data)) {
+ $outputIndex = count($this->_output) - 1;
+ $this->_output[$outputIndex]['content'] = $data;
+ }
+ }
+
+ public function endHandler($parser, $name) {
+ if (count($this->_output) > 1) {
+ $data = array_pop($this->_output);
+ $outputIndex = count($this->_output) - 1;
+ $this->_output[$outputIndex]['child'][] = $data;
+ }
+ }
+
+ public function cleanUp() {
+ xml_parser_free($this->_xmlObject);
+ }
+}
diff --git a/lib/tools/bin/dbxml.tpl b/lib/tools/bin/dbxml.tpl
new file mode 100644
index 00000000..bf9a47ef
--- /dev/null
+++ b/lib/tools/bin/dbxml.tpl
@@ -0,0 +1,54 @@
+
+
+
+
+ {$schema.name}
+
+ {$schema.major}
+ {$schema.minor}
+
+{if $requiresId}
+
+ id
+ INTEGER
+ MEDIUM
+
+
+{/if}
+{foreach from=$members item=member}
+
+ {$member.name}
+ {$member.type}
+ {$member.size}
+{if isset($member.required) || isset($member.primary)}
+{if !empty($member.required.empty)}
+
+{else}
+
+{/if}
+{/if}
+{if isset($member.default)}
+ {$member.default}
+{/if}
+
+{/foreach}
+{foreach from=$keys item=key}
+{if !empty($key.primary)}
+
+{else}
+
+{/if}
+{foreach from=$key.columns item=column}
+ {$column}
+{/foreach}
+
+{/foreach}
+{foreach from=$indexes item=index}
+
+{foreach from=$index.columns item=column}
+ {$column}
+{/foreach}
+
+{/foreach}
+
diff --git a/lib/tools/bin/entities.tpl b/lib/tools/bin/entities.tpl
new file mode 100644
index 00000000..6c7efb9b
--- /dev/null
+++ b/lib/tools/bin/entities.tpl
@@ -0,0 +1,15 @@
+ array(
+{foreach name=outer from=$entity.members key=memberName item=memberInfo}
+ '{$memberName}' => array({foreach name=inner from=$memberInfo key=key item=value}'{$key}' => {$value}{if !$smarty.foreach.inner.last}, {/if}{/foreach}){if !$smarty.foreach.outer.last},{/if}
+
+{/foreach}),
+ 'parent' => {if $entity.parent=='GalleryPersistent'}null{else}'{$entity.parent}'{/if},
+ 'module' => '{$entity.module}',
+ 'linked' => array({foreach name=linked from=$entity.linked item=value}'{$value}'{if !$smarty.foreach.linked.last},{/if}{/foreach}));
+{/foreach}
+?>
+
diff --git a/lib/tools/bin/extractClassXml.pl b/lib/tools/bin/extractClassXml.pl
new file mode 100755
index 00000000..6c0ab012
--- /dev/null
+++ b/lib/tools/bin/extractClassXml.pl
@@ -0,0 +1,62 @@
+#!/usr/local/bin/perl
+#
+use strict;
+use File::Basename;
+use Getopt::Long;
+
+my $DTD;
+my $OUTFILE;
+my $STUB_OK = 0;
+my $QUIET = 0;
+my $OUT_DIR;
+
+GetOptions("dtd:s" => \$DTD,
+ "out:s" => \$OUTFILE,
+ "out-dir:s" => \$OUT_DIR,
+ "stub-ok+" => \$STUB_OK,
+ "quiet!" => \$QUIET);
+
+foreach my $file (@ARGV) {
+ my $tagCount = 0;
+ my $base = basename($file);
+ $base =~ s/\..*?$//;
+ my $xml = $OUTFILE || "$OUT_DIR/$base.xml";
+ my $schemaName = undef;
+
+ open(IFD, "<$file") || die;
+ open(OFD, ">$xml") || die;
+ print OFD "\n";
+ print OFD "\n";
+ while () {
+ if (s/.*\@g2\s*//) {
+ $tagCount++;
+ print OFD $_;
+
+ # NOTE! Keep this in sync with the similar block in generate-entities.php
+ # and generate-maps.php
+ if (m|(.*)|) {
+ ($schemaName = $1) =~ s/^Gallery//;
+ # Shorten some table names to fit Oracle's 30 char name limit..
+ $schemaName =~ s/Preferences/Prefs/;
+ $schemaName =~ s/Toolkit/Tk/;
+ $schemaName =~ s/TkOperation/TkOperatn/;
+ }
+
+ if (m||) {
+ print OFD "$schemaName\n";
+ }
+ }
+ }
+ close(IFD);
+ print OFD "\n";
+ close(OFD);
+
+ # It's gotta have more than the class-name, schema-version tags.
+ #
+ if ($tagCount == 0 || ($tagCount <= 2 && !$STUB_OK)) {
+ print STDERR "No tags detected\n" unless ($QUIET);
+ unlink($xml);
+ }
+}
+
+exit 0;
diff --git a/lib/tools/bin/generate-dbxml.php b/lib/tools/bin/generate-dbxml.php
new file mode 100644
index 00000000..e0d99163
--- /dev/null
+++ b/lib/tools/bin/generate-dbxml.php
@@ -0,0 +1,376 @@
+compile_dir = $tmpdir;
+$smarty->error_reporting = error_reporting();
+$smarty->debugging = true;
+$smarty->use_sub_dirs = false;
+$smarty->template_dir = __DIR__;
+
+function generateEntityDbXml() {
+ global $smarty;
+
+ $entityXmlFiles = glob('tmp/classxml/*.xml');
+
+ if (empty($entityXmlFiles)) {
+ return;
+ }
+
+ foreach ($entityXmlFiles as $xmlFile) {
+ $p = new XmlParser();
+ $root = $p->parse($xmlFile);
+ $base = basename($xmlFile);
+ $base = preg_replace('/\.[^\.]*$/', '', $base);
+ $tmpFile = "tmp/dbxml/$base.xml";
+ $origFile = "$base.xml";
+
+ $membersBase = $root[0]['child'];
+ $schema = array(
+ 'name' => $root[0]['child'][2]['child'][0]['content'],
+ 'major' => $root[0]['child'][2]['child'][1]['content'],
+ 'minor' => (!empty($root[0]['child'][2]['child'][2]['content']) ? $root[0]['child'][2]['child'][2]['content'] : 0),
+ );
+
+ $members = array();
+ $keys = array();
+ $indexes = array();
+ $requiresId = false;
+
+ foreach ($membersBase as $child) {
+ switch ($child['name']) {
+ case 'MEMBER':
+ $member = array(
+ 'name' => $child['child'][0]['content'],
+ 'type' => $child['child'][1]['content'],
+ 'ucName' => ucfirst($child['child'][0]['content']),
+ 'lcType' => strtolower($child['child'][1]['content']),
+ );
+
+ for ($i = 2; $i < count($child['child']); $i++) {
+ switch ($child['child'][$i]['name']) {
+ case 'MEMBER-SIZE':
+ $member['size'] = $child['child'][$i]['content'];
+
+ break;
+
+ case 'INDEXED':
+ $indexes[] = array(
+ 'columns' => array($member['name']),
+ );
+ $member[strtolower($child['child'][$i]['name'])] = 1;
+
+ break;
+
+ case 'UNIQUE':
+ $keys[] = array(
+ 'columns' => array($member['name']),
+ );
+ $member[strtolower($child['child'][$i]['name'])] = 1;
+
+ break;
+
+ case 'PRIMARY':
+ $keys[] = array(
+ 'columns' => array($member['name']),
+ 'primary' => 1,
+ );
+ $member['primary'] = 1;
+
+ break;
+
+ case 'ID':
+ case 'LINKED':
+ $member[strtolower($child['child'][$i]['name'])] = 1;
+
+ break;
+
+ case 'REQUIRED':
+ $member['required'] = array();
+
+ if (isset($child['child'][$i]['attrs']['EMPTY'])) {
+ $member['required']['empty'] = $child['child'][$i]['attrs']['EMPTY'];
+ } else {
+ $member['required']['empty'] = 'disallowed';
+ }
+
+ break;
+
+ case 'DEFAULT':
+ $member['default'] = $child['child'][$i]['content'];
+
+ break;
+
+ case 'MEMBER-EXTERNAL-ACCESS':
+ // Not relevant for storage layer
+ break;
+
+ default:
+ print 'Unknown member type: ' . $child['child'][$i]['name'] . '\n';
+ }
+ }
+
+ if (empty($member['size'])) {
+ $member['size'] = 'MEDIUM';
+ }
+
+ $members[] = $member;
+
+ break;
+
+ case 'KEY':
+ $key = array();
+
+ foreach ($child['child'] as $column) {
+ $key['columns'][] = $column['content'];
+ }
+ $key['primary'] = isset($child['attrs']['PRIMARY']) && $child['attrs']['PRIMARY'] == 'true';
+ $keys[] = $key;
+
+ break;
+
+ case 'INDEX':
+ $index = array();
+
+ foreach ($child['child'] as $column) {
+ $index['columns'][] = $column['content'];
+ }
+ $index['primary'] = isset($child['attrs']['PRIMARY']) && $child['attrs']['PRIMARY'] == 'true';
+
+ $indexes[] = $index;
+
+ break;
+
+ case 'REQUIRES-ID':
+ $requiresId = true;
+ $keys[] = array(
+ 'columns' => array('id'),
+ 'primary' => 1,
+ );
+
+ break;
+ }
+ }
+
+ $smarty->assign('root', $root);
+ $smarty->assign('schema', $schema);
+ $smarty->assign('members', $members);
+ $smarty->assign('keys', $keys);
+ $smarty->assign('indexes', $indexes);
+ $smarty->assign('requiresId', $requiresId);
+ $smarty->assign('isMap', false);
+ $new = $smarty->fetch('dbxml.tpl');
+
+ $fd = fopen($tmpFile, 'w');
+ fwrite($fd, $new);
+ fclose($fd);
+ }
+}
+
+function generateMapDbXml() {
+ global $smarty;
+
+ $xmlFile = '../Maps.xml';
+
+ if (!file_exists($xmlFile)) {
+ return;
+ }
+
+ $p = new XmlParser();
+ $root = $p->parse($xmlFile);
+ $base = basename($xmlFile);
+ $base = preg_replace('/\.[^\.]*$/', '', $base);
+ $origFile = "$base.xml";
+
+ foreach ($root[0]['child'] as $map) {
+ $origMapName = $map['child'][0]['content'];
+
+ /*
+ * NOTE! Keep this in sync with the similar block in extractClassXml.pl
+ * and generate-entities.php
+ */
+ $mapName = $origMapName;
+ $mapName = preg_replace('/^Gallery/', '', $mapName);
+ // Shorten some table names to fit Oracle's 30 char name limit..
+ $mapName = str_replace('Preferences', 'Prefs', $mapName);
+ $mapName = str_replace('Toolkit', 'Tk', $mapName);
+ $mapName = str_replace('TkOperation', 'TkOperatn', $mapName);
+
+ $schema = array(
+ 'name' => $mapName,
+ 'major' => $map['child'][1]['child'][0]['content'],
+ 'minor' => $map['child'][1]['child'][1]['content'],
+ );
+ $tmpFile = "tmp/dbxml/$origMapName.xml";
+
+ $members = array();
+ $keys = array();
+ $indexes = array();
+ $requiresId = false;
+
+ for ($j = 2; $j < count($map['child']); $j++) {
+ $child = $map['child'][$j];
+
+ switch ($child['name']) {
+ case 'MEMBER':
+ $member = array(
+ 'name' => $child['child'][0]['content'],
+ 'type' => $child['child'][1]['content'],
+ 'ucName' => ucfirst($child['child'][0]['content']),
+ 'lcType' => strtolower($child['child'][1]['content']),
+ );
+
+ for ($i = 2; $i < count($child['child']); $i++) {
+ switch ($child['child'][$i]['name']) {
+ case 'MEMBER-SIZE':
+ $member['size'] = $child['child'][$i]['content'];
+
+ break;
+
+ case 'INDEXED':
+ $indexes[] = array(
+ 'columns' => array($member['name']),
+ );
+ $member[strtolower($child['child'][$i]['name'])] = 1;
+
+ break;
+
+ case 'UNIQUE':
+ $keys[] = array(
+ 'columns' => array($member['name']),
+ );
+ $member[strtolower($child['child'][$i]['name'])] = 1;
+
+ break;
+
+ case 'PRIMARY':
+ $keys[] = array(
+ 'columns' => array($member['name']),
+ 'primary' => 1,
+ );
+ $member['primary'] = 1;
+
+ break;
+
+ case 'REQUIRED':
+ $member['required'] = array();
+
+ if (isset($child['child'][$i]['attrs']['EMPTY'])) {
+ $member['required']['empty'] = $child['child'][$i]['attrs']['EMPTY'];
+ } else {
+ $member['required']['empty'] = 'disallowed';
+ }
+
+ break;
+
+ case 'DEFAULT':
+ $member['default'] = $child['child'][$i]['content'];
+
+ break;
+
+ case 'MEMBER-EXTERNAL-ACCESS':
+ // Not relevant for storage layer
+ break;
+
+ default:
+ print 'Unknown member type: ' . $child['child'][$i]['name'] . '\n';
+ }
+ }
+
+ if (empty($member['size'])) {
+ $member['size'] = 'MEDIUM';
+ }
+
+ $members[] = $member;
+
+ break;
+
+ case 'KEY':
+ $key = array();
+
+ foreach ($child['child'] as $column) {
+ $key['columns'][] = $column['content'];
+ }
+ $key['primary'] = isset($child['attrs']['PRIMARY']) && $child['attrs']['PRIMARY'] == 'true';
+ $keys[] = $key;
+
+ break;
+
+ case 'INDEX':
+ $index = array();
+
+ foreach ($child['child'] as $column) {
+ $index['columns'][] = $column['content'];
+ }
+ $index['primary'] = isset($child['attrs']['PRIMARY']) && $child['attrs']['PRIMARY'] == 'true';
+
+ $indexes[] = $index;
+
+ break;
+ }
+
+ $smarty->assign('root', $root);
+ $smarty->assign('schema', $schema);
+ $smarty->assign('members', $members);
+ $smarty->assign('keys', $keys);
+ $smarty->assign('indexes', $indexes);
+ $smarty->assign('requiresId', $requiresId);
+ $smarty->assign('isMap', true);
+ $new = $smarty->fetch('dbxml.tpl');
+
+ $fd = fopen($tmpFile, 'w');
+ fwrite($fd, $new);
+ fclose($fd);
+ }
+ }
+}
+
+generateEntityDbXml();
+generateMapDbXml();
+
+// Clean up the cheap and easy way
+if (file_exists($tmpdir)) {
+ system("rm -rf $tmpdir");
+}
diff --git a/lib/tools/bin/generate-entities.php b/lib/tools/bin/generate-entities.php
new file mode 100644
index 00000000..96c7934b
--- /dev/null
+++ b/lib/tools/bin/generate-entities.php
@@ -0,0 +1,252 @@
+compile_dir = $tmpdir;
+$smarty->error_reporting = error_reporting();
+$smarty->debugging = true;
+$smarty->use_sub_dirs = false;
+$smarty->template_dir = __DIR__;
+
+// Grab all G2 XML from entity class files
+
+$xml = '\n";
+$xml .= "\n";
+
+if (!$dh = opendir('.')) {
+ echo "Unable to opendir(.)\n";
+ cleanExit(1);
+}
+
+$files = array();
+
+while (($file = readdir($dh)) !== false) {
+ if (preg_match('/\.class$/', $file)) {
+ $files[] = $file;
+ }
+}
+closedir($dh);
+sort($files);
+$classXml = '';
+
+foreach ($files as $file) {
+ $snippet = getXml($file);
+
+ if ($snippet) {
+ $classXml .= "\n" . join("\n", $snippet) . "\n\n";
+ }
+}
+
+if (empty($classXml)) {
+ // Nothing to do
+ cleanExit(0);
+}
+
+$xml .= $classXml;
+$xml .= "\n";
+
+$entitiesXml = "$tmpdir/Entities.xml";
+
+if (!$fp = fopen($entitiesXml, 'wb')) {
+ echo "Unable to write to $entitiesXml\n";
+ cleanExit(1);
+}
+fwrite($fp, $xml);
+fclose($fp);
+
+if (system("xmllint --valid --noout $entitiesXml", $retval)) {
+ echo "System error: $retval\n";
+ cleanExit();
+}
+
+$p = new XmlParser();
+$root = $p->parse($entitiesXml);
+
+$entities = array();
+
+foreach ($root[0]['child'] as $entity) {
+ $entityName = $entity['child'][0]['content'];
+ $parentEntityName = $entity['child'][1]['content'];
+
+ $j = 3;
+
+ if ($entity['child'][$j]['name'] == 'REQUIRES-ID') {
+ $j++;
+ }
+
+ $entities[$entityName]['members'] = array();
+ $entities[$entityName]['linked'] = array();
+
+ for (; $j < count($entity['child']); $j++) {
+ $member = $entity['child'][$j];
+ $name = $member['child'][0]['content'];
+
+ $entities[$entityName]['members'][$name]['type'] = 'STORAGE_TYPE_' . $member['child'][1]['content'];
+ $entities[$entityName]['members'][$name]['type'] = 'STORAGE_TYPE_' . $member['child'][1]['content'];
+
+ for ($k = 2; $k < count($member['child']); $k++) {
+ if (!empty($member['child'][$k]['name'])) {
+ switch ($member['child'][$k]['name']) {
+ case 'MEMBER-SIZE':
+ $entities[$entityName]['members'][$name]['size'] = $size = 'STORAGE_SIZE_' . $member['child'][$k]['content'];
+
+ break;
+
+ case 'ID':
+ $entities[$entityName]['members'][$name]['type'] .= '| STORAGE_TYPE_ID';
+
+ break;
+
+ case 'LINKED':
+ $entities[$entityName]['linked'][] = $name;
+
+ break;
+
+ case 'REQUIRED':
+ case 'PRIMARY':
+ $elem = $member['child'][$k];
+
+ if ($elem['name'] != 'REQUIRED' || empty($elem['attrs']['EMPTY'])
+ || $elem['attrs']['EMPTY'] != 'allowed'
+ ) {
+ $entities[$entityName]['members'][$name]['notNull'] = true;
+ } else {
+ $entities[$entityName]['members'][$name]['notNullEmptyAllowed'] = true;
+ }
+
+ break;
+
+ case 'MEMBER-EXTERNAL-ACCESS':
+ switch (trim($member['child'][$k]['content'])) {
+ case 'READ':
+ $entities[$entityName]['members'][$name]['external-access'] = 'EXTERNAL_ACCESS_READ';
+
+ break;
+
+ case 'WRITE':
+ $entities[$entityName]['members'][$name]['external-access'] = 'EXTERNAL_ACCESS_WRITE';
+
+ break;
+
+ case 'FULL':
+ $entities[$entityName]['members'][$name]['external-access'] = 'EXTERNAL_ACCESS_FULL';
+
+ break;
+
+ default:
+ printf(
+ 'Unknown value for member-external-access "%s"\n',
+ $member['child'][$k]['content']
+ );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ $entities[$entityName]['parent'] = $parentEntityName;
+ $entities[$entityName]['module'] = basename(dirname(realpath('.')));
+}
+
+$smarty->assign('entities', $entities);
+$new = $smarty->fetch('entities.tpl');
+
+// Windows leaves a CR at the end of the file
+$new = rtrim($new, "\r");
+
+$fd = fopen('Entities.inc', 'w');
+fwrite($fd, $new);
+fclose($fd);
+
+// Done
+cleanExit(0);
+
+function cleanExit($status = 0) {
+ // Clean up the cheap and easy way
+ global $tmpdir;
+
+ if (file_exists($tmpdir)) {
+ system("rm -rf $tmpdir");
+ }
+
+ exit($status);
+}
+
+function getXml($filename) {
+ $results = array();
+
+ if ($fp = fopen($filename, 'rb')) {
+ while (!feof($fp)) {
+ $line = fgets($fp, 4096);
+
+ if (preg_match('/@g2(.*)/', $line, $matches)) {
+ $results[] = $line = $matches[1];
+
+ /*
+ * NOTE! Keep this in sync with the similar block in extractClassXml.pl
+ * and generate-dbxml.php
+ */
+ if (preg_match('{(.*)}', $line, $matches)) {
+ $schemaName = $matches[1];
+ $schemaName = preg_replace('/^Gallery/', '', $schemaName);
+ // Shorten some table names to fit Oracle's 30 char name limit..
+ $schemaName = preg_replace('/Preferences/', 'Prefs', $schemaName);
+ $schemaName = preg_replace('/Toolkit/', 'Tk', $schemaName);
+ $schemaName = preg_replace('/TkOperation/', 'TkOperatn', $schemaName);
+ }
+
+ if (preg_match('{}', $line)) {
+ $results[] = " $schemaName";
+ }
+ }
+ }
+ fclose($fp);
+ }
+
+ return $results;
+}
diff --git a/lib/tools/bin/generate-maps.php b/lib/tools/bin/generate-maps.php
new file mode 100644
index 00000000..f2db9226
--- /dev/null
+++ b/lib/tools/bin/generate-maps.php
@@ -0,0 +1,131 @@
+compile_dir = $tmpdir;
+$smarty->error_reporting = error_reporting();
+$smarty->debugging = true;
+$smarty->use_sub_dirs = false;
+$smarty->template_dir = __DIR__;
+
+$xmlFile = 'Maps.xml';
+
+if (!file_exists($xmlFile)) {
+ echo "Missing Maps.xml, can't continue.\n";
+ cleanExit(1);
+}
+
+$p = new XmlParser();
+$root = $p->parse($xmlFile);
+
+$maps = array();
+
+foreach ($root[0]['child'] as $map) {
+ $mapName = $map['child'][0]['content'];
+
+ for ($j = 2; $j < count($map['child']); $j++) {
+ $child = $map['child'][$j];
+
+ if ($child['name'] == 'MEMBER') {
+ $member = array(
+ 'name' => $child['child'][0]['content'],
+ 'type' => 'STORAGE_TYPE_' . $child['child'][1]['content'],
+ );
+
+ if (!empty($child['child'][2]['name'])
+ && $child['child'][2]['name'] == 'MEMBER-SIZE'
+ ) {
+ $member['size'] = 'STORAGE_SIZE_' . $child['child'][2]['content'];
+ } else {
+ $member['size'] = 'STORAGE_SIZE_MEDIUM';
+ }
+
+ for ($k = 2; $k < count($child['child']); $k++) {
+ if (!empty($child['child'][$k]['name'])) {
+ $elem = $child['child'][$k];
+
+ if ($elem['name'] == 'PRIMARY' || $elem['name'] == 'REQUIRED') {
+ if ($elem['name'] != 'REQUIRED' || empty($elem['attrs']['EMPTY'])
+ || $elem['attrs']['EMPTY'] != 'allowed'
+ ) {
+ $member['notNull'] = true;
+ } else {
+ $member['notNullEmptyAllowed'] = true;
+ }
+
+ break;
+ }
+ }
+ }
+
+ $maps[$mapName][] = $member;
+ }
+ }
+}
+
+$smarty->assign('maps', $maps);
+$smarty->assign('mapName', $mapName);
+$new = $smarty->fetch('maps.tpl');
+
+// Windows leaves a CR at the end of the file
+$new = rtrim($new, "\r");
+
+$fd = fopen('Maps.inc', 'w');
+fwrite($fd, $new);
+fclose($fd);
+
+// Done
+cleanExit(0);
+
+function cleanExit($status = 0) {
+ // Clean up the cheap and easy way
+ global $tmpdir;
+
+ if (file_exists($tmpdir)) {
+ system("rm -rf $tmpdir");
+ }
+
+ exit($status);
+}
diff --git a/lib/tools/bin/generate-sql.php b/lib/tools/bin/generate-sql.php
new file mode 100755
index 00000000..ffc1f336
--- /dev/null
+++ b/lib/tools/bin/generate-sql.php
@@ -0,0 +1,1986 @@
+parse($xmlFile);
+
+ $generatorClass = "${db}Generator";
+ $generator = new $generatorClass();
+
+ $base = basename($xmlFile);
+ $base = preg_replace('/\.[^\.]*$/', '', $base);
+ $output .= '# ' . $base . "\n";
+ $root[0]['base'] = $base;
+ $output .= $generator->createSql($root[0], 0, 0, null);
+ }
+}
+
+$fd = fopen('schema.tpl', 'w');
+fwrite($fd, $output);
+fclose($fd);
+
+class BaseGenerator {
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'SCHEMA':
+ $output .= "INSERT INTO DB_TABLE_PREFIXSchema (\n";
+ $output .= " DB_COLUMN_PREFIXname,\n";
+ $output .= " DB_COLUMN_PREFIXmajor,\n";
+ $output .= " DB_COLUMN_PREFIXminor\n";
+ $output .= ') VALUES(';
+ $output .= "'" . $parent['child'][0]['content'] . "', " . $child[0]['content'] . ', ' .
+ $child[1]['content'];
+ $output .= ");\n\n";
+
+ break;
+
+ case 'COLUMN':
+ // column-name, column-type, column-size, not-null?
+ $output .= ' DB_COLUMN_PREFIX' . $child[0]['content'];
+ $output .= ' ' . $this->columnDefinition($child);
+
+ break;
+
+ default:
+ $output .= "1. UNIMPLEMLENTED: $node[name]";
+
+ break;
+ }
+
+ return $output;
+ }
+
+ public function getIndexCrc($columns) {
+ $buf = '';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $buf .= $columns[$i]['content'];
+ }
+
+ /*
+ * crc32 returns different results on 32-bit vs. 64-bit systems. e.g. crc32('groupId')
+ * returns -310277968 for 32-bit systems and 3984689328 on 64-bit systems. We don't
+ * completely understand the issue, but adding 2^32 for negative crc32 values
+ * (32-bit overflows?!) seems to do the trick. And we eschew the 64-bit unsafe modulo
+ * operation by using substr instead of % 100000.
+ * Note: We also want strictly positive values since we use the value in SQL index key
+ * names.
+ */
+ $crc = crc32($buf);
+
+ if ($crc > 0) {
+ return $crc % 100000;
+ }
+
+ return (int)substr(crc32($buf) + 2 ** 32, -5);
+ }
+
+ public function getNotNullElement($child) {
+ for ($i = 0; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'NOT-NULL') {
+ return $child[$i];
+ }
+ }
+
+ return null;
+ }
+
+ public function getDefaultElement($child) {
+ for ($i = 0; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'DEFAULT') {
+ return $child[$i]['content'];
+ }
+ }
+
+ return null;
+ }
+
+ public function setColumnDefinitionMap($map) {
+ $this->_columnDefinitionMap = $map;
+ }
+
+ public function columnDefinition($child, $includeNotNull = true, $includeDefault = true) {
+ $output = '';
+ $key = $child[1]['content'] . '-' .
+ (!empty($child[2]['content']) ? $child[2]['content'] : '');
+
+ if (isset($this->_columnDefinitionMap[$key])) {
+ $output .= $this->_columnDefinitionMap[$key];
+ } else {
+ $output .= "2. UNIMPLEMLENTED: $key";
+ }
+
+ if ($includeDefault) {
+ $defaultValue = $this->getDefaultElement($child);
+
+ if (isset($defaultValue)) {
+ $output .= " DEFAULT '$defaultValue'";
+ }
+ }
+
+ if ($includeNotNull) {
+ if ($this->getNotNullElement($child)) {
+ $output .= ' NOT NULL';
+ }
+ }
+
+ return $output;
+ }
+
+ public function generateSchemaUpdate($child) {
+ $output = "UPDATE DB_TABLE_PREFIXSchema\n";
+ $output .= sprintf(
+ " SET DB_COLUMN_PREFIXmajor=%d, DB_COLUMN_PREFIXminor=%d\n",
+ $child[2]['child'][0]['content'],
+ $child[2]['child'][1]['content']
+ );
+ $output .= sprintf(
+ " WHERE DB_COLUMN_PREFIXname='%s' AND DB_COLUMN_PREFIXmajor=%d " .
+ "AND DB_COLUMN_PREFIXminor=%d;\n\n",
+ $child[0]['content'],
+ $child[1]['child'][0]['content'],
+ (!empty($child[1]['child'][1]['content']) ? $child[1]['child'][1]['content'] : 0)
+ );
+
+ return $output;
+ }
+
+ public function isPrimaryKey($child) {
+ return $this->isIndex($child) && !empty($child['attrs']['PRIMARY']);
+ }
+
+ public function isIndex($child) {
+ return $child['name'] == 'INDEX';
+ }
+}
+
+class MySqlGenerator extends BaseGenerator {
+ public function __construct() {
+ $this->setColumnDefinitionMap(
+ array(
+ 'INTEGER-' => 'int(11)',
+ 'INTEGER-MEDIUM' => 'int(11)',
+ 'INTEGER-LARGE' => 'int(11)',
+ 'BIT-LARGE' => 'int(11)',
+ 'BIT-MEDIUM' => 'int(11)',
+ 'STRING-SMALL' => 'varchar(32)',
+ 'STRING-MEDIUM' => 'varchar(128)',
+ 'STRING-LARGE' => 'varchar(255)',
+ 'TEXT-SMALL' => 'text',
+ 'TEXT-' => 'text',
+ 'TEXT-MEDIUM' => 'text',
+ 'TEXT-LARGE' => 'longtext',
+ 'BOOLEAN-' => 'int(1)',
+ 'BOOLEAN-MEDIUM' => 'int(1)',
+ 'TIMESTAMP-' => 'datetime',
+ )
+ );
+ }
+
+ public function columnDefinition($child, $includeNotNull = true, $includeDefault = true) {
+ $output = parent::columnDefinition($child, $includeNotNull, false);
+
+ // MySQL -> DEFAULT expression after NOT NULL
+ if ($includeDefault) {
+ $defaultValue = $this->getDefaultElement($child);
+
+ if (isset($defaultValue)) {
+ $output .= " DEFAULT '$defaultValue'";
+ }
+ }
+
+ return $output;
+ }
+
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'TABLE':
+ // table-name, schema, column+, (key | index)
+ $output .= 'CREATE TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "(\n";
+
+ for ($i = 2; $i < count($child); $i++) {
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+
+ if ($i < count($child) - 1) {
+ $output .= ',';
+ }
+ $output .= "\n";
+ }
+ $output .= ") DB_TABLE_TYPE\n";
+ // Character set, enclosed in comments that are ignored by MySQL < 4.1.0
+ $output .= "/*!40100 DEFAULT CHARACTER SET utf8 */;\n\n";
+
+ // Schema info
+ $output .= $this->createSql($child[1], 0, 0, $node);
+
+ break;
+
+ case 'ALTER':
+ // column+
+ for ($i = 0; $i < count($child); $i++) {
+ $output .= ' MODIFY COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($child[$i]['child']);
+
+ if ($i < count($child) - 1) {
+ $output .= ",\n";
+ }
+ }
+
+ break;
+
+ case 'CHANGE':
+ // table-name, schema-from, schema-to, (add, alter, remove)+
+ if (count($child) > 3) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "\n";
+
+ for ($i = 3; $i < count($child); $i++) {
+ if ($i > 3) {
+ $output .= ",\n";
+ }
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ }
+ $output .= ";\n\n";
+ }
+ $output .= $this->generateSchemaUpdate($child);
+
+ break;
+
+ case 'ADD':
+ // (column, key, index)+
+ for ($i = 0; $i < count($child); $i++) {
+ $c = $child[$i];
+
+ switch ($c['name']) {
+ case 'COLUMN':
+ // column-name
+ $output .= ' ADD COLUMN DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($c['child']);
+
+ break;
+
+ case 'KEY':
+ $output .= ' ADD' . $this->createSql($c, 0, 0, null);
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= ' ADD INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $c['child'];
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($columns);
+ }
+ $output .= '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+
+ break;
+
+ default:
+ $output .= "3. UNIMPLEMLENTED: ADD $c[name]\n";
+ }
+
+ if ($i < count($child) - 1) {
+ $output .= ",\n";
+ }
+ }
+
+ break;
+
+ case 'REMOVE':
+ if (!isset($parent['name'])) {
+ $output .= 'DROP TABLE DB_TABLE_PREFIX' . $node['child'][0]['content'] . ";\n\n";
+
+ if ($node['child'][0]['content'] != 'Schema') {
+ $output .= "DELETE FROM DB_TABLE_PREFIXSchema WHERE DB_COLUMN_PREFIXname='" .
+ $node['child'][0]['content'] . "';\n\n";
+ }
+ } elseif ($parent['name'] == 'CHANGE') {
+ // (column-name, key, index)+
+ $i = 0;
+
+ foreach ($child as $c) {
+ if ($i++ > 0) {
+ $output .= ",\n";
+ }
+
+ switch ($c['name']) {
+ case 'COLUMN-NAME':
+ $output .= ' DROP COLUMN DB_COLUMN_PREFIX' . $c['content'];
+
+ break;
+
+ case 'KEY':
+ if (!empty($child[0]['attrs']['PRIMARY'])) {
+ $output .= ' DROP PRIMARY KEY';
+ } else {
+ /*
+ * For MySQL, our UNIQUE index names are the name of the first
+ * column that is part of the index (MySQL sets the name that way
+ * for unnamed indices (they only need to be unique in each table)
+ */
+ $output .= ' DROP INDEX DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ }
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= ' DROP INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+
+ if (isset($child[0]['attrs'][$nameKey])) {
+ $output .= $child[0]['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($c['child']);
+ }
+
+ break;
+
+ default:
+ $output .= "4. UNIMPLEMENTED: REMOVE $c[name]\n";
+ }
+ }
+ }
+
+ break;
+
+ case 'KEY':
+ // column-name+
+ if (!empty($node['attrs']['PRIMARY'])) {
+ $output .= ' PRIMARY KEY(';
+ } else {
+ /*
+ * In MySQL, it would be UNIQUE [INDEX] so INDEX is optional, since UNIQUE is
+ * often called a KEY and we use in our XML for UNIQUE, we just use UNIQUE
+ * without INDEX here. Don't add an index name, see our REMOVE code.
+ */
+ $output .= ' UNIQUE (';
+ }
+
+ for ($i = 0; $i < count($child); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['content'];
+
+ if ($i < count($child) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+
+ break;
+
+ case 'INDEX':
+ // column-name+
+ $crc = $this->getIndexCrc($child);
+ $output .= ' INDEX DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '_' . $crc . '(';
+
+ for ($i = 0; $i < count($child); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['content'];
+
+ if ($i < count($child) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+
+ break;
+
+ default:
+ $output .= parent::createSql($node, $index, $lastPeerIndex, $parent);
+ }
+
+ return $output;
+ }
+
+ public function getDbType() {
+ return 'mysql';
+ }
+}
+
+class PostgresGenerator extends BaseGenerator {
+ public function __construct() {
+ $this->setColumnDefinitionMap(
+ array(
+ 'INTEGER-' => 'INTEGER',
+ 'INTEGER-MEDIUM' => 'INTEGER',
+ 'INTEGER-LARGE' => 'INTEGER',
+ 'BIT-LARGE' => 'BIT(32)',
+ 'BIT-MEDIUM' => 'BIT(32)',
+ 'STRING-SMALL' => 'VARCHAR(32)',
+ 'STRING-MEDIUM' => 'VARCHAR(128)',
+ 'STRING-LARGE' => 'VARCHAR(255)',
+ 'TEXT-SMALL' => 'text',
+ 'TEXT-' => 'text',
+ 'TEXT-MEDIUM' => 'text',
+ 'TEXT-LARGE' => 'text',
+ 'BOOLEAN-' => 'SMALLINT',
+ 'BOOLEAN-MEDIUM' => 'SMALLINT',
+ 'TIMESTAMP-' => 'datetime',
+ )
+ );
+ }
+
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'CHANGE':
+ // table-name, schema-from, schema-to, (add, alter, remove)+
+ for ($i = 3; $i < count($child); $i++) {
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ }
+ $output .= $this->generateSchemaUpdate($child);
+
+ break;
+
+ case 'REMOVE':
+ if (!isset($parent['name'])) {
+ $output .= 'DROP TABLE DB_TABLE_PREFIX' . $node['child'][0]['content'] . ";\n\n";
+
+ if ($node['child'][0]['content'] != 'Schema') {
+ $output .= "DELETE FROM DB_TABLE_PREFIXSchema WHERE DB_COLUMN_PREFIXname='" .
+ $node['child'][0]['content'] . "';\n\n";
+ }
+ } elseif ($parent['name'] == 'CHANGE') {
+ // (column-name, key, index)+
+ for ($i = 0; $i < count($child); $i++) {
+ $c = $child[$i];
+
+ switch ($c['name']) {
+ case 'COLUMN-NAME':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' DROP COLUMN DB_COLUMN_PREFIX' . $c['content'];
+ $output .= ";\n\n";
+
+ break;
+
+ case 'KEY':
+ if (empty($c['attrs']['PRIMARY'])) {
+ $crc = $this->getIndexCrc($c['child']);
+ $output .= 'DROP INDEX DB_TABLE_PREFIX' .
+ $parent['child'][0]['content'] . '_' . $crc . ";\n\n";
+ } else {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' .
+ $parent['child'][0]['content'] . ' DROP CONSTRAINT DB_TABLE_PREFIX'
+ . $parent['child'][0]['content'] . "_pkey;\n\n";
+ }
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'DROP INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($c['child']);
+ }
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "5. UNIMPLEMENTED: REMOVE $c[name]\n";
+ }
+ }
+ }
+
+ break;
+
+ case 'ADD':
+ // (column, key, index)+
+ foreach ($child as $c) {
+ switch ($c['name']) {
+ case 'COLUMN':
+ /* Add a new column, optionally with a default value and a not null constraint
+ * In PG7, we can not set the default value in the add column statement
+ * (PG8 doesn't have this limitation though). Therefore do it in 3 steps:
+ * 1. Add the column without any options.
+ * 2. Set the default value (only affects future rows) and add the default
+ * value for existing rows.
+ * 3. Add the not-null constraint
+ */
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' ADD COLUMN DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($c['child'], false, false);
+ $output .= ";\n\n";
+
+ $defaultValue = $this->getDefaultElement($c['child']);
+
+ if (isset($defaultValue)) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' ALTER COLUMN DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= " SET DEFAULT '$defaultValue';\n\n";
+
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' SET DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= " = '$defaultValue';\n\n";
+ }
+
+ if ($this->getNotNullElement($c['child'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ALTER DB_COLUMN_PREFIX' . $c['child'][0]['content'] .
+ " SET NOT NULL;\n\n";
+ }
+
+ break;
+
+ case 'KEY':
+ // column-name+
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD ';
+
+ if (!empty($c['attrs']['PRIMARY'])) {
+ $output .= 'PRIMARY KEY(';
+ } else {
+ $output .= 'UNIQUE KEY(';
+ }
+
+ for ($i = 0; $i < count($c['child']); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $c['child'][$i]['content'];
+
+ if ($i < count($c['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'CREATE INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $c['child'];
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($columns);
+ }
+ $output .= ' ON ' . 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "6. UNIMPLEMLENTED: ADD $c[name]\n";
+ }
+ }
+
+ break;
+
+ case 'TABLE':
+ // table-name, schema, column+, (key | index)
+ $output .= 'CREATE TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "(\n";
+
+ for ($i = 2; $i < count($child); $i++) {
+ if ($child[$i]['name'] != 'COLUMN') {
+ $output .= "\n";
+
+ break;
+ }
+
+ if ($i > 2) {
+ $output .= ",\n";
+ }
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ $firstNonColumn = $i + 1;
+ }
+ $output .= ");\n\n";
+
+ for ($i = $firstNonColumn; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'INDEX') {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE INDEX DB_TABLE_PREFIX' . $child[0]['content'] . '_' . $crc .
+ ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else /* key */ {
+ if (!empty($child[$i]['attrs']['PRIMARY'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $child[0]['content'] .
+ ' ADD PRIMARY KEY (';
+ $columns = $child[$i]['child'];
+
+ for ($j = 0; $j < count($columns); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$j]['content'];
+
+ if ($j < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE UNIQUE INDEX DB_TABLE_PREFIX' . $child[0]['content'] .
+ '_' . $crc . ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ }
+ }
+ }
+
+ // Schema info
+ $output .= $this->createSql($child[1], 0, 0, $node);
+
+ break;
+
+ case 'ALTER':
+ // column+
+ for ($i = 0; $i < count($child); $i++) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp';
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ";\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' = CAST(DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ' AS ' .
+ $this->columnDefinition($child[$i]['child'], false) . ");\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' RENAME DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' to DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+
+ if ($this->getNotNullElement($child[$i]['child'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ALTER DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ " SET NOT NULL;\n\n";
+ }
+ }
+
+ break;
+
+ default:
+ $output .= parent::createSql($node, $index, $lastPeerIndex, $parent);
+ }
+
+ return $output;
+ }
+
+ public function getDbType() {
+ return 'postgres';
+ }
+}
+
+class OracleGenerator extends BaseGenerator {
+ public function __construct() {
+ $this->setColumnDefinitionMap(
+ array(
+ 'INTEGER-' => 'INTEGER',
+ 'INTEGER-MEDIUM' => 'INTEGER',
+ 'INTEGER-LARGE' => 'INTEGER',
+ 'BIT-LARGE' => 'INTEGER',
+ 'BIT-MEDIUM' => 'INTEGER',
+ 'STRING-SMALL' => 'VARCHAR2(32)',
+ 'STRING-MEDIUM' => 'VARCHAR2(128)',
+ 'STRING-LARGE' => 'VARCHAR2(255)',
+ 'TEXT-SMALL' => 'VARCHAR2(4000)',
+ 'TEXT-' => 'CLOB',
+ 'TEXT-MEDIUM' => 'CLOB',
+ 'TEXT-LARGE' => 'CLOB',
+ 'BOOLEAN-' => 'NUMBER(1)',
+ 'BOOLEAN-MEDIUM' => 'NUMBER(1)',
+ 'TIMESTAMP-' => 'datetime',
+ )
+ );
+ }
+
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'CHANGE':
+ // table-name, schema-from, schema-to, (add, alter, remove)+
+ for ($i = 3; $i < count($child); $i++) {
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ }
+ $output .= $this->generateSchemaUpdate($child);
+
+ break;
+
+ case 'REMOVE':
+ if (!isset($parent['name'])) {
+ $output .= 'DROP TABLE DB_TABLE_PREFIX' . $node['child'][0]['content'] . ";\n\n";
+
+ if ($node['child'][0]['content'] != 'Schema') {
+ $output .= "DELETE FROM DB_TABLE_PREFIXSchema WHERE DB_COLUMN_PREFIXname='" .
+ $node['child'][0]['content'] . "';\n\n";
+ }
+ } elseif ($parent['name'] == 'CHANGE') {
+ // (column-name, key, index)+
+ foreach ($child as $c) {
+ switch ($c['name']) {
+ case 'COLUMN-NAME':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= "\n" . ' DROP (DB_COLUMN_PREFIX' . $c['content'] . ')';
+
+ break;
+
+ case 'KEY':
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+
+ if (isset($child[0]['attrs']['PRIMARY'])) {
+ $output .= "\n DROP PRIMARY KEY";
+ } else {
+ $keyColumns = array();
+
+ foreach ($c['child'] as $keyColumn) {
+ $keyColumns[] = 'DB_COLUMN_PREFIX' . $keyColumn['content'];
+ }
+ $output .= "\n" . ' DROP UNIQUE (' . implode(', ', $keyColumns) . ')';
+ }
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= ' DROP INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+
+ if (isset($child[0]['attrs'][$nameKey])) {
+ $output .= $child[0]['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($c['child']);
+ }
+
+ break;
+
+ default:
+ $output .= "7. UNIMPLEMENTED: REMOVE $c[name]\n";
+ }
+ $output .= ";\n\n";
+ }
+ }
+
+ break;
+
+ case 'ADD':
+ // (column, key, index)+
+ for ($k = 0; $k < count($child); $k++) {
+ $c = $child[$k];
+
+ switch ($c['name']) {
+ case 'COLUMN':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= "\n" . ' ADD (DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($c['child']) . ')';
+
+ break;
+
+ case 'KEY':
+ // column-name+
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= "\n ADD ";
+
+ if (!empty($c['attrs']['PRIMARY'])) {
+ $output .= 'PRIMARY KEY(';
+ } else {
+ $output .= 'UNIQUE KEY(';
+ }
+
+ for ($i = 0; $i < count($c['child']); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $c['child'][$i]['content'];
+
+ if ($i < count($c['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'CREATE INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $c['child'];
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($columns);
+ }
+ $output .= ' ON DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+
+ break;
+
+ default:
+ $output .= "8. UNIMPLEMLENTED: ADD $c[name]\n";
+ }
+ $output .= ";\n\n";
+ }
+
+ break;
+
+ case 'TABLE':
+ // table-name, schema, column+, (key | index)
+ $output .= 'CREATE TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "(\n";
+
+ for ($i = 2; $i < count($child); $i++) {
+ if ($child[$i]['name'] != 'COLUMN') {
+ $output .= "\n";
+
+ break;
+ }
+
+ if ($i > 2) {
+ $output .= ",\n";
+ }
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ $firstNonColumn = $i + 1;
+ }
+ $output .= ");\n\n";
+
+ $keyColumns = array();
+
+ for ($i = $firstNonColumn; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'INDEX') {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE INDEX DB_TABLE_PREFIX' . $child[0]['content'] . '_' . $crc .
+ "\n " . ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else {
+ $keys[] = $child[$i];
+ }
+ }
+
+ if (!empty($keys)) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "\n";
+
+ foreach ($keys as $key) {
+ if (!empty($key['attrs']['PRIMARY'])) {
+ $output .= ' ADD PRIMARY KEY (';
+ } else {
+ $output .= ' ADD UNIQUE (';
+ }
+
+ for ($i = 0; $i < count($key['child']); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $key['child'][$i]['content'];
+
+ if ($i < count($key['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ")\n";
+ }
+ $output .= ";\n\n";
+ }
+
+ // Schema info
+ $output .= $this->createSql($child[1], 0, 0, $node);
+
+ break;
+
+ case 'COLUMN':
+ // column-name, column-type, column-size, not-null?
+ $output .= ' DB_COLUMN_PREFIX' . $child[0]['content'];
+ $output .= ' ' . $this->columnDefinition($child, false);
+
+ if (($notNull = $this->getNotNullElement($child))
+ && (empty($notNull['attrs']['EMPTY']) || $notNull['attrs']['EMPTY'] != 'allowed')
+ ) {
+ $output .= ' NOT NULL';
+ }
+
+ break;
+
+ case 'ALTER':
+ // column+
+ for ($i = 0; $i < count($child); $i++) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD (DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp';
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ");\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' = DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP (DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ");\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' RENAME COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' TO DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+
+ if (($notNull = $this->getNotNullElement($child[$i]['child']))
+ && (empty($notNull['attrs']['EMPTY'])
+ || $notNull['attrs']['EMPTY'] != 'allowed')
+ ) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' MODIFY (DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ " NOT NULL);\n\n";
+ }
+ }
+
+ break;
+
+ default:
+ $output .= parent::createSql($node, $index, $lastPeerIndex, $parent);
+ }
+
+ return $output;
+ }
+
+ public function getDbType() {
+ return 'oracle';
+ }
+}
+
+/**
+ * Notes regarding DB2 limitations on Table and Index names:
+ *
+ * DB2 currently limits the length of table names to 30 characters, and index names to 18
+ * characters. We don't have to worry about the 30 character table name problem because we force
+ * table names to be shorter than this in GalleryStorage (and it's very important that the table
+ * names we choose here match up with the ones that GalleryStorage expects). However we have
+ * (and need) no such provision for indexes because this is the only place where we define index
+ * names.
+ *
+ * The installer "database setup" step prefixes all tables and indexes with "gtst#" (5 chars).
+ * The installer default is "g2_" (3 chars). So if we allow room for a 5 char prefix, that
+ * leaves us 13 characters for an 18-character index name. Our index CRC values are another 5
+ * characters. That leaves us 8 characters to use for a descriptive index name. I don't know if
+ * DB2 index names are required to be unique in the database or just to the table so to avoid any
+ * risks we can't just use a prefix or suffix of the table name because it may overlap with
+ * another similar table name.
+ *
+ * So for indexes we'll use the following format:
+ * DB_TABLE_PREFIX + substr(table name, 0, 5) + substr(md5(table name), -2) + '_' + index crc
+ *
+ * That works out to:
+ * <= 5 chars + 5 + 2 + 1 + 5 = <= 18
+ */
+class Db2Generator extends BaseGenerator {
+ public function __construct() {
+ // The column size is limited to 32kbyte
+ $this->setColumnDefinitionMap(
+ array(
+ 'INTEGER-' => 'INTEGER',
+ 'INTEGER-MEDIUM' => 'INTEGER',
+ 'INTEGER-LARGE' => 'INTEGER',
+ 'BIT-LARGE' => 'VARCHAR(32) FOR BIT DATA',
+ 'BIT-MEDIUM' => 'VARCHAR(32) FOR BIT DATA',
+ 'STRING-SMALL' => 'VARCHAR(32)',
+ 'STRING-MEDIUM' => 'VARCHAR(128)',
+ 'STRING-LARGE' => 'VARCHAR(255)',
+ 'TEXT-SMALL' => 'VARCHAR(10000)',
+ 'TEXT-' => 'VARCHAR(15000)',
+ 'TEXT-MEDIUM' => 'VARCHAR(15000)',
+ 'TEXT-LARGE' => 'CLOB(2G) NOT LOGGED',
+ 'BOOLEAN-' => 'SMALLINT',
+ 'BOOLEAN-MEDIUM' => 'SMALLINT',
+ 'TIMESTAMP-' => 'datestamp',
+ )
+ );
+ }
+
+ public function columnDefinition($child, $includeNotNull = true, $includeDefault = true) {
+ $output = parent::columnDefinition($child, $includeNotNull, false);
+
+ // DB2 -> Make sure DEFAULT expression doesn't have quotes for numeric
+ if ($includeDefault) {
+ $defaultValue = $this->getDefaultElement($child);
+
+ if (isset($defaultValue)) {
+ if ($child[1]['content'] != 'INTEGER' && $child[1]['content'] != 'BOOLEAN') {
+ $defaultValue = "'$defaultValue'";
+ }
+ $output .= " DEFAULT $defaultValue";
+ }
+ }
+
+ return $output;
+ }
+
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'CHANGE':
+ // table-name, schema-from, schema-to, (add, alter, remove)+
+ for ($i = 3; $i < count($child); $i++) {
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ }
+ $output .= $this->generateSchemaUpdate($child);
+
+ break;
+
+ case 'REMOVE':
+ if (!isset($parent['name'])) {
+ $output .= 'DROP TABLE DB_TABLE_PREFIX' . $node['child'][0]['content'] . ";\n\n";
+
+ if ($node['child'][0]['content'] != 'Schema') {
+ $output .= "DELETE FROM DB_TABLE_PREFIXSchema WHERE DB_COLUMN_PREFIXname='" .
+ $node['child'][0]['content'] . "';\n\n";
+ }
+ } elseif ($parent['name'] == 'CHANGE') {
+ // (column-name, key, index)+
+ for ($i = 0; $i < count($child); $i++) {
+ $c = $child[$i];
+
+ switch ($c['name']) {
+ case 'COLUMN-NAME':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' DROP COLUMN DB_COLUMN_PREFIX' . $c['content'] . ";\n\n";
+ $output .= "CALL ADMIN_CMD ('REORG TABLE DB_TABLE_PREFIX";
+ $output .= $parent['child'][0]['content'] . "');\n\n";
+
+ break;
+
+ case 'KEY':
+ if (empty($c['attrs']['PRIMARY'])) {
+ $crc = $this->getIndexCrc($c['child']);
+ $output .= 'DROP INDEX DB_TABLE_PREFIX' .
+ $parent['child'][0]['content'] . '_' . $crc . ";\n\n";
+ } else {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' .
+ $parent['child'][0]['content'] . " DROP PRIMARY KEY;\n\n";
+ }
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'DROP INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' .
+ substr($parent['child'][0]['content'], 0, 5) .
+ substr(md5($parent['child'][0]['content']), -2) .
+ '_' . $this->getIndexCrc($c['child']);
+ }
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "5. UNIMPLEMENTED: REMOVE $c[name]\n";
+ }
+ }
+ }
+
+ break;
+
+ case 'ADD':
+ // (column, key, index)+
+ foreach ($child as $c) {
+ switch ($c['name']) {
+ case 'COLUMN':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' ADD COLUMN DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($c['child']);
+ $output .= ";\n\n";
+
+ break;
+
+ case 'KEY':
+ // column-name+
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD ';
+
+ if (!empty($c['attrs']['PRIMARY'])) {
+ $output .= 'PRIMARY KEY(';
+ } else {
+ $output .= 'UNIQUE KEY(';
+ }
+
+ for ($i = 0; $i < count($c['child']); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $c['child'][$i]['content'];
+
+ if ($i < count($c['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'CREATE INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $c['child'];
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' .
+ substr($parent['child'][0]['content'], 0, 5) .
+ substr(md5($parent['child'][0]['content']), -2) .
+ '_' . $this->getIndexCrc($c['child']);
+ }
+ $output .= ' ON ' . 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "6. UNIMPLEMLENTED: ADD $c[name]\n";
+ }
+ }
+
+ break;
+
+ case 'TABLE':
+ // table-name, schema, column+, (key | index)
+ $output .= 'CREATE TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "(\n";
+
+ for ($i = 2; $i < count($child); $i++) {
+ if ($child[$i]['name'] != 'COLUMN') {
+ $output .= "\n";
+
+ break;
+ }
+
+ if ($i > 2) {
+ $output .= ",\n";
+ }
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ $firstNonColumn = $i + 1;
+ }
+ $output .= ");\n\n";
+
+ for ($i = $firstNonColumn; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'INDEX') {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE INDEX DB_TABLE_PREFIX' .
+ substr($child[0]['content'], 0, 5) .
+ substr(md5($child[0]['content']), -2) . '_' . $crc .
+ "\n " . ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else /* key */ {
+ if (!empty($child[$i]['attrs']['PRIMARY'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $child[0]['content'] .
+ ' ADD PRIMARY KEY (';
+ $columns = $child[$i]['child'];
+
+ for ($j = 0; $j < count($columns); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$j]['content'];
+
+ if ($j < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE UNIQUE INDEX DB_TABLE_PREFIX' .
+ substr($child[0]['content'], 0, 5) .
+ substr(md5($child[0]['content']), -2) . '_' . $crc .
+ " \n" . ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ }
+ }
+ }
+
+ // Schema info
+ $output .= $this->createSql($child[1], 0, 0, $node);
+
+ break;
+
+ case 'ALTER':
+ // column+
+ for ($i = 0; $i < count($child); $i++) {
+ // DB2's "ALTER TABLE ALTER COLUMN" is somewhat limited. Use a workaround.
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp';
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ";\n\n";
+ // Omit the CAST when the target type is CLOB to avoid invalid SQL state.
+ $targetType = $this->columnDefinition($child[$i]['child'], false);
+ $copyFrom = 'DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'];
+
+ if (strpos($targetType, 'CLOB') === false) {
+ $copyFrom = 'CAST(' . $copyFrom . ' AS ' . $targetType . ')';
+ }
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' = ' . $copyFrom . ";\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+ /*
+ * DROP COLUMN puts the table into a state that requires REORG TABLE before
+ * it can be accessed again.
+ */
+ $output .= "CALL ADMIN_CMD ('REORG TABLE DB_TABLE_PREFIX" .
+ $parent['child'][0]['content'] . "');\n\n";
+ // DB2 can't rename columns
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ";\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ ' = DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . "Temp;\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ "Temp;\n\n";
+ $output .= "CALL ADMIN_CMD ('REORG TABLE DB_TABLE_PREFIX" .
+ $parent['child'][0]['content'] . "');\n\n";
+
+ if ($this->getNotNullElement($child[$i]['child'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ALTER DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ " SET NOT NULL;\n\n";
+ }
+ }
+
+ break;
+
+ default:
+ $output .= parent::createSql($node, $index, $lastPeerIndex, $parent);
+ }
+
+ return $output;
+ }
+
+ public function getDbType() {
+ return 'db2';
+ }
+}
+
+class MSSqlGenerator extends BaseGenerator {
+ public function __construct() {
+ $this->setColumnDefinitionMap(
+ array(
+ 'INTEGER-' => 'INT',
+ 'INTEGER-MEDIUM' => 'INT',
+ 'INTEGER-LARGE' => 'INT',
+ 'BIT-LARGE' => 'INT',
+ 'BIT-MEDIUM' => 'INT',
+ 'STRING-SMALL' => 'NVARCHAR(32)',
+ 'STRING-MEDIUM' => 'NVARCHAR(128)',
+ 'STRING-LARGE' => 'NVARCHAR(255)',
+ 'TEXT-SMALL' => 'NVARCHAR(MAX)',
+ 'TEXT-' => 'NVARCHAR(MAX)',
+ 'TEXT-MEDIUM' => 'NVARCHAR(MAX)',
+ 'TEXT-LARGE' => 'NVARCHAR(MAX)',
+ 'BOOLEAN-' => 'BIT',
+ 'BOOLEAN-MEDIUM' => 'BIT',
+ 'TIMESTAMP-' => 'datetime',
+ )
+ );
+ }
+
+ public function columnDefinition($child, $includeNotNull = true, $includeDefault = true) {
+ $output = parent::columnDefinition($child, $includeNotNull, $includeDefault);
+
+ if ($includeNotNull && !$this->getNotNullElement($child)) {
+ $output .= ' NULL';
+ }
+
+ return $output;
+ }
+
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'CHANGE':
+ // table-name, schema-from, schema-to, (add, alter, remove)+
+ for ($i = 3; $i < count($child); $i++) {
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ }
+ $output .= $this->generateSchemaUpdate($child);
+
+ break;
+
+ case 'REMOVE':
+ if (!isset($parent['name'])) {
+ $output .= 'DROP TABLE DB_TABLE_PREFIX' . $node['child'][0]['content'] . ";\n\n";
+
+ if ($node['child'][0]['content'] != 'Schema') {
+ $output .= "DELETE FROM DB_TABLE_PREFIXSchema WHERE DB_COLUMN_PREFIXname='" .
+ $node['child'][0]['content'] . "';\n\n";
+ }
+ } elseif ($parent['name'] == 'CHANGE') {
+ // (column-name, key, index)+
+ for ($i = 0; $i < count($child); $i++) {
+ $c = $child[$i];
+
+ switch ($c['name']) {
+ case 'COLUMN-NAME':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' DROP COLUMN DB_COLUMN_PREFIX' . $c['content'];
+ $output .= ";\n\n";
+
+ break;
+
+ case 'KEY':
+ if (empty($c['attrs']['PRIMARY'])) {
+ $crc = $this->getIndexCrc($c['child']);
+ $output .= 'DROP INDEX DB_TABLE_PREFIX' .
+ $parent['child'][0]['content'] . '_' . $crc . ";\n\n";
+ } else {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' .
+ $parent['child'][0]['content'] . ' DROP CONSTRAINT DB_TABLE_PREFIX'
+ . $parent['child'][0]['content'] . "_pkey;\n\n";
+ }
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'DROP INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '.DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($c['child']);
+ }
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "5. UNIMPLEMENTED: REMOVE $c[name]\n";
+ }
+ }
+ }
+
+ break;
+
+ case 'ADD':
+ // (column, key, index)+
+ foreach ($child as $c) {
+ switch ($c['name']) {
+ case 'COLUMN':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' ADD DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($c['child']);
+ $output .= ";\n\n";
+
+ break;
+
+ case 'KEY':
+ // column-name+
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD ';
+
+ if (!empty($c['attrs']['PRIMARY'])) {
+ $output .= 'PRIMARY KEY(';
+ } else {
+ $output .= 'UNIQUE KEY(';
+ }
+
+ for ($i = 0; $i < count($c['child']); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $c['child'][$i]['content'];
+
+ if ($i < count($c['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'CREATE INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $c['child'];
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ '_' . $this->getIndexCrc($columns);
+ }
+ $output .= ' ON ' . 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "6. UNIMPLEMLENTED: ADD $c[name]\n";
+ }
+ }
+
+ break;
+
+ case 'TABLE':
+ // table-name, schema, column+, (key | index)
+ $output .= 'CREATE TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "(\n";
+
+ for ($i = 2; $i < count($child); $i++) {
+ if ($child[$i]['name'] != 'COLUMN') {
+ $output .= "\n";
+
+ break;
+ }
+
+ if ($i > 2) {
+ $output .= ",\n";
+ }
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ $firstNonColumn = $i + 1;
+ }
+ $output .= ");\n\n";
+
+ for ($i = $firstNonColumn; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'INDEX') {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE INDEX DB_TABLE_PREFIX' . $child[0]['content'] . '_' . $crc .
+ ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else /* key */ {
+ if (!empty($child[$i]['attrs']['PRIMARY'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $child[0]['content'] .
+ ' ADD PRIMARY KEY (';
+ $columns = $child[$i]['child'];
+
+ for ($j = 0; $j < count($columns); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$j]['content'];
+
+ if ($j < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ } else {
+ $crc = $this->getIndexCrc($child[$i]['child']);
+ $output .= 'CREATE UNIQUE INDEX DB_TABLE_PREFIX' . $child[0]['content'] .
+ '_' . $crc . ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ }
+ }
+ }
+
+ // Schema info
+ $output .= $this->createSql($child[1], 0, 0, $node);
+
+ break;
+
+ case 'ALTER':
+ // column+
+ for ($i = 0; $i < count($child); $i++) {
+ // MSSQL can't add defaults when altering columns. Use a workaround.
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp';
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ";\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' = CAST(DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ' AS ' .
+ $this->columnDefinition($child[$i]['child'], false) . ");\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+ // MSSQL can't rename columns
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ";\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ ' = DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . "Temp;\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ "Temp;\n\n";
+
+ if ($this->getNotNullElement($child[$i]['child'])) {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ALTER COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ ' ' . $this->columnDefinition($child[$i]['child'], true, false) . ";\n\n";
+ }
+ }
+
+ break;
+
+ default:
+ $output .= parent::createSql($node, $index, $lastPeerIndex, $parent);
+ }
+
+ return $output;
+ }
+
+ public function getDbType() {
+ return 'mssql';
+ }
+}
+
+class SQLiteGenerator extends BaseGenerator {
+ public function __construct() {
+ $this->setColumnDefinitionMap(
+ array(
+ 'INTEGER-' => 'INTEGER',
+ 'INTEGER-MEDIUM' => 'INTEGER',
+ 'INTEGER-LARGE' => 'INTEGER',
+ 'BIT-LARGE' => 'TEXT',
+ 'BIT-MEDIUM' => 'TEXT',
+ 'STRING-SMALL' => 'TEXT',
+ 'STRING-MEDIUM' => 'TEXT',
+ 'STRING-LARGE' => 'TEXT',
+ 'TEXT-SMALL' => 'TEXT',
+ 'TEXT-' => 'TEXT',
+ 'TEXT-MEDIUM' => 'TEXT',
+ 'TEXT-LARGE' => 'TEXT',
+ 'BOOLEAN-' => 'INTEGER',
+ 'BOOLEAN-MEDIUM' => 'INTEGER',
+ 'TIMESTAMP-' => 'INTEGER',
+ )
+ );
+ }
+
+ public function columnDefinition($child, $includeNotNull = true, $includeDefault = true) {
+ $output = parent::columnDefinition($child, $includeNotNull, false);
+
+ if ($includeDefault) {
+ $defaultValue = $this->getDefaultElement($child);
+
+ if (isset($defaultValue)) {
+ $output .= " DEFAULT '$defaultValue'";
+ }
+ }
+
+ return $output;
+ }
+
+ public function createSql($node, $index, $lastPeerIndex, $parent) {
+ $output = '';
+
+ $child = $node['child'] = isset($node['child']) ? $node['child'] : array();
+
+ switch ($node['name']) {
+ case 'TABLE':
+ // table-name, schema, column+, (key | index)
+ $output .= 'CREATE TABLE DB_TABLE_PREFIX' . $child[0]['content'] . "(\n";
+
+ for ($i = 2; $i < count($child); $i++) {
+ if ($child[$i]['name'] != 'COLUMN') {
+ $output .= "\n";
+
+ break;
+ }
+
+ if ($i > 2) {
+ $output .= ",\n";
+ }
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ $firstNonColumn = $i + 1;
+ }
+ $output .= ");\n\n";
+
+ for ($i = $firstNonColumn; $i < count($child); $i++) {
+ if ($child[$i]['name'] == 'INDEX') {
+ // column-name
+ $output .= 'CREATE INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $child[$i]['child'];
+
+ if (isset($child[$i]['attrs'][$nameKey])) {
+ $output .= $child[$i]['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $child[0]['content']
+ . '_' . $this->getIndexCrc($columns);
+ }
+ $output .= ' ON ' . 'DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($columns); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$j]['content'];
+
+ if ($j < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+ }
+ $output .= 'CREATE UNIQUE INDEX DB_TABLE_PREFIX' . $child[0]['content'];
+
+ if (!empty($child[$i]['attrs']['PRIMARY'])) {
+ $output .= '_pkey';
+ } else {
+ $output .= '_' . $this->getIndexCrc($child[$i]['child']);
+ }
+ $output .= ' ON DB_TABLE_PREFIX' . $child[0]['content'] . '(';
+
+ for ($j = 0; $j < count($child[$i]['child']); $j++) {
+ $output .= 'DB_COLUMN_PREFIX' . $child[$i]['child'][$j]['content'];
+
+ if ($j < count($child[$i]['child']) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ");\n\n";
+ }
+
+ // Schema info
+ $output .= $this->createSql($child[1], 0, 0, $node);
+
+ break;
+
+ case 'CHANGE':
+ // table-name, schema-from, schema-to, (add, alter, remove)+
+ for ($i = 3; $i < count($child); $i++) {
+ $output .= $this->createSql($child[$i], $i, count($child) - 1, $node);
+ }
+ $output .= $this->generateSchemaUpdate($child);
+
+ break;
+
+ case 'ADD':
+ // (column, key, index)+
+ foreach ($child as $c) {
+ switch ($c['name']) {
+ case 'COLUMN':
+ // column-name
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' ADD DB_COLUMN_PREFIX' . $c['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($c['child']);
+ $output .= ";\n";
+ $output .= 'VACUUM DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ";\n\n";
+
+ break;
+
+ case 'KEY':
+ $output .= 'CREATE UNIQUE INDEX ';
+ $columns = $c['child'];
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '_';
+
+ if (!empty($c['attrs']['PRIMARY'])) {
+ $output .= 'pkey';
+ } else {
+ $output .= $this->getIndexCrc($columns);
+ }
+ $output .= ' ON ' . 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'CREATE INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+ $columns = $c['child'];
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content']
+ . '_' . $this->getIndexCrc($columns);
+ }
+ $output .= ' ON ' . 'DB_TABLE_PREFIX' . $parent['child'][0]['content'] . '(';
+
+ for ($i = 0; $i < count($columns); $i++) {
+ $output .= 'DB_COLUMN_PREFIX' . $columns[$i]['content'];
+
+ if ($i < count($columns) - 1) {
+ $output .= ', ';
+ }
+ }
+ $output .= ')';
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "6. UNIMPLEMLENTED: ADD $c[name]\n";
+ }
+ }
+
+ break;
+
+ case 'REMOVE':
+ if (!isset($parent['name'])) {
+ $output .= 'DROP TABLE DB_TABLE_PREFIX' . $node['child'][0]['content'] . ";\n\n";
+
+ if ($node['child'][0]['content'] != 'Schema') {
+ $output .= "DELETE FROM DB_TABLE_PREFIXSchema WHERE DB_COLUMN_PREFIXname='"
+ . $node['child'][0]['content'] . "';\n\n";
+ }
+ } elseif ($parent['name'] == 'CHANGE') {
+ // (column-name, key, index)+
+ for ($i = 0; $i < count($child); $i++) {
+ $c = $child[$i];
+
+ switch ($c['name']) {
+ case 'COLUMN-NAME':
+ /**
+ * @todo Find a better way to handle DROP COLUMN. The below code doesn't
+ * work in SQLite 3 and our adodb driver intercepts and handles it instead.
+ */
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'];
+ $output .= ' DROP COLUMN DB_COLUMN_PREFIX' . $c['content'];
+ $output .= ";\n\n";
+
+ break;
+
+ case 'KEY':
+ if (empty($c['attrs']['PRIMARY'])) {
+ $crc = $this->getIndexCrc($c['child']);
+ $output .= 'DROP INDEX ' . $parent['child'][0]['content'] . '_' . $crc
+ . ";\n\n";
+ } else {
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX'
+ . $parent['child'][0]['content']
+ . ' DROP CONSTRAINT DB_TABLE_PREFIX'
+ . $parent['child'][0]['content'] . "_pkey;\n\n";
+ }
+
+ break;
+
+ case 'INDEX':
+ // column-name
+ $output .= 'DROP INDEX ';
+ $nameKey = strtoupper('name_' . $this->getDbType());
+
+ if (isset($c['attrs'][$nameKey])) {
+ $output .= $c['attrs'][$nameKey];
+ } else {
+ $output .= 'DB_TABLE_PREFIX' . $parent['child'][0]['content']
+ . '_' . $this->getIndexCrc($c['child']);
+ }
+ $output .= ";\n\n";
+
+ break;
+
+ default:
+ $output .= "5. UNIMPLEMENTED: REMOVE $c[name]\n";
+ }
+ }
+ }
+
+ break;
+
+ case 'ALTER':
+ // column+
+ for ($i = 0; $i < count($child); $i++) {
+ /*
+ * SQLite only supports ADD COLUMN and the workaround DROP COLUMN.
+ * As a workaround for ALTER COLUMN:
+ * Create Temporary Table
+ * Copy current column to temporary
+ * Delete current column
+ * Recreate Column with new settings
+ * Copy content back to current column
+ * Delete Temporary Table
+ */
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp';
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false) . ";\n\n";
+ $output .= 'VACUUM DB_TABLE_PREFIX' . $parent['child'][0]['content'] . ";\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . 'Temp' .
+ ' = DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . ";\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' ADD COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'];
+ $output .= ' ' . $this->columnDefinition($child[$i]['child'], false, false);
+ $defaultValue = $this->getDefaultElement($child[$i]['child']);
+
+ if (($notNull = $this->getNotNullElement($child[$i]['child']))
+ && (empty($notNull['attrs']['EMPTY'])
+ || $notNull['attrs']['EMPTY'] != 'allowed')
+ ) {
+ $output .= " NOT NULL DEFAULT '" . (isset($defaultValue) ? $defaultValue : '') . "'";
+ } elseif ($defaultValue !== null) {
+ $output .= " DEFAULT '" . $defaultValue . "'";
+ }
+ $output .= ";\n\n";
+ $output .= 'VACUUM DB_TABLE_PREFIX' . $parent['child'][0]['content'] . ";\n\n";
+ $output .= 'UPDATE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' SET DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] .
+ ' = DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . "Temp;\n\n";
+ $output .= 'ALTER TABLE DB_TABLE_PREFIX' . $parent['child'][0]['content'] .
+ ' DROP COLUMN DB_COLUMN_PREFIX' . $child[$i]['child'][0]['content'] . "Temp;\n\n";
+ }
+
+ break;
+
+ default:
+ $output .= parent::createSql($node, $index, $lastPeerIndex, $parent);
+ }
+
+ return $output;
+ }
+
+ public function getDbType() {
+ return 'sqlite';
+ }
+}
diff --git a/lib/tools/bin/getIllegalFunctions.pl b/lib/tools/bin/getIllegalFunctions.pl
new file mode 100644
index 00000000..df208e7c
--- /dev/null
+++ b/lib/tools/bin/getIllegalFunctions.pl
@@ -0,0 +1,51 @@
+#!/usr/local/bin/perl
+#
+use strict;
+
+my $versionXmlUrl = 'http://cvs.php.net/co.php/phpdoc/xsl/version.xml?r=1.16&p=1';
+my $versionXmlFile = '/tmp/version.xml';
+
+unless (-f $versionXmlFile) {
+ system("wget -O $versionXmlFile $versionXmlUrl") and die "unable to wget $versionXmlUrl";
+}
+
+open(FD, "<$versionXmlFile") || die;
+
+print ") {
+ # only get functions
+ next unless /\/g;
+ s/</ or >= 4.0* is fine
+ next if ($comparison =~ />=?4\.0/);
+
+ # Anything >= 4.1.0 is fine
+ next if ($comparison =~ />=4\.1\.0/);
+
+ printf('%-70s // %s', "\$illegalFunctions[] = '$function';", $comparison);
+ print "\n";
+}
+
+close(FD);
+
+print "?>\n";
diff --git a/lib/tools/bin/makeManifest.php b/lib/tools/bin/makeManifest.php
new file mode 100644
index 00000000..940cf6c6
--- /dev/null
+++ b/lib/tools/bin/makeManifest.php
@@ -0,0 +1,275 @@
+#!/usr/bin/php -q
+ $entries) {
+ if (!file_exists($baseDir . $manifest)) {
+ $oldLines = array();
+ $oldContent = $oldRevision = '';
+ $nl = DIRECTORY_SEPARATOR == '\\' ? "\r\n" : "\n";
+ } else {
+ $oldLines = file($baseDir . $manifest);
+ $oldContent = implode('', $oldLines);
+ $nl = preg_match('/\r\n/', $oldContent) ? "\r\n" : "\n";
+ $matches = array();
+ $oldRevision = preg_match('/Revision: (\d+\s*)\$/', $oldLines[0], $matches) ? $matches[1] : '';
+ }
+
+ $newContent = '# $Revi' . "sion: $oldRevision\$$nl";
+ $newContent .= "# File crc32 crc32(crlf) size size(crlf) or R File$nl";
+
+ $deleted = $seen = array();
+
+ foreach ($entries as $entry) {
+ list($file, $isBinary) = preg_split('/\@\@/', $entry);
+ $relativeFilePath = $file;
+ $file = $baseDir . $file;
+
+ if (preg_match('/deleted:(.*)/', $relativeFilePath, $matches)) {
+ $deleted[$matches[1]] = true;
+ } else {
+ $seen[$relativeFilePath] = true;
+ $fileHandle = fopen($file, 'rb');
+ $fileSize = filesize($file);
+ $data = fread($fileHandle, $fileSize);
+ fclose($fileHandle);
+
+ $data_crlf = $data;
+
+ if ($isBinary) {
+ $size = $size_crlf = filesize($file);
+ } else {
+ if (preg_match("/\r\n/", $data)) {
+ $data = str_replace("\r\n", "\n", $data);
+ } else {
+ $data_crlf = str_replace("\n", "\r\n", $data_crlf);
+ }
+ $size = strlen($data);
+ $size_crlf = strlen($data_crlf);
+ }
+
+ $cksum = crc32($data);
+ $cksum_crlf = crc32($data_crlf);
+ $newContent .= sprintf(
+ "$relativeFilePath\t%u\t%u\t%d\t%d$nl",
+ $cksum,
+ $cksum_crlf,
+ $size,
+ $size_crlf
+ );
+ }
+ }
+
+ if (!empty($oldLines)) {
+ foreach ($oldLines as $line) {
+ if ($line[0] == '#') {
+ continue;
+ }
+
+ if (preg_match('/^R\t(.*)$/', $line, $matches)) {
+ $file = trim($matches[1]);
+
+ if (empty($seen[$file])) {
+ $deleted[$file] = true;
+ }
+ } else {
+ preg_match('/^(.+?)\t/', $line, $matches);
+ $file = trim($matches[1]);
+
+ if (empty($seen[$file])) {
+ $deleted[$file] = true;
+ }
+ }
+ }
+
+ foreach ($deleted as $file => $unused) {
+ $newContent .= "R\t$file$nl";
+ }
+ }
+
+ if ($oldContent != $newContent) {
+ file_put_contents($baseDir . $manifest, $newContent);
+ $changed++;
+ }
+ $total++;
+ }
+
+ quiet_print(sprintf('Completed in %d seconds', time() - $startTime));
+ quiet_print(sprintf("Manifests changed: $changed (total: $total)"));
+}
+
+/**
+ * Retrieve the SVN Entries
+ * @param string $filterpath Path to create retrieve the SVN entries for.
+ * @return array List of SVN entries
+ */
+function listSvn($filterpath) {
+ $entries = array();
+
+ $binaryList = array();
+ exec("svn propget --non-interactive -R svn:mime-type $filterpath", $output);
+
+ foreach ($output as $line) {
+ $parts = preg_split('/\s-\s/', $line);
+ $file = str_replace('\\', '/', $parts[0]);
+ $binaryList[$file] = 1;
+ }
+
+ $output = array();
+ exec("svn status --non-interactive -v -q $filterpath", $output);
+
+ foreach ($output as $line) {
+ $matches = array();
+
+ if (preg_match('/^(.).....\s*\d+\s+[\d|\?]+\s+\S+\s+(.*)$/', $line, $matches) == 0) {
+ die("Unexpected SVN status format:\n$line\n");
+ }
+
+ if (!file_exists($matches[2])) {
+ die("The file '$matches[2]' does not exist");
+ }
+
+ if (is_dir($matches[2])) {
+ continue;
+ }
+
+ if (preg_match('#[\\/]MANIFEST#', $matches[2]) > 0) {
+ continue;
+ }
+
+ if ($matches[1] == 'M') {
+ quiet_print("Warning: $matches[2] is locally modified");
+ } elseif (!in_array($matches[1], array(' ', 'D', 'M'))) {
+ die("Check {$matches[1]} status for {$matches[2]}");
+ }
+
+ $status = $matches[1] === 'D' ? 'deleted:' : '';
+
+ $file = str_replace('\\', '/', $matches[2]);
+ $entries[] = sprintf('%s%s@@%d', $status, $file, isset($binaryList[$file]));
+ }
+
+ return $entries;
+}
+
+?>
diff --git a/lib/tools/bin/maps.tpl b/lib/tools/bin/maps.tpl
new file mode 100644
index 00000000..1f9b057a
--- /dev/null
+++ b/lib/tools/bin/maps.tpl
@@ -0,0 +1,7 @@
+array('type'=>{$member.type},'size'=>{$member.size}{if !empty($member.notNull)},'notNull'=>true{/if}{if !empty($member.notNullEmptyAllowed)},'notNullEmptyAllowed'=>true{/if}){if !$smarty.foreach.inner.last},{/if}{/foreach});
+{/foreach}
+?>
+
diff --git a/lib/tools/bin/rebuild-modules.pl b/lib/tools/bin/rebuild-modules.pl
new file mode 100755
index 00000000..741542c9
--- /dev/null
+++ b/lib/tools/bin/rebuild-modules.pl
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+use strict;
+chomp(my $CURDIR = `pwd`);
+chomp(my $MAKE = `(which gmake || which make) 2>/dev/null`);
+
+if (!$MAKE) {
+ die "Unable to locate 'make' or 'gmake'";
+}
+
+my @MAKEFILES = ;
+foreach my $makefile (@MAKEFILES) {
+ (my $module = $makefile) =~ s|(modules/.*?)/.*|$1|;
+ print STDERR "Building $module\n";
+ chdir("$CURDIR/$module/classes") || die;
+ system("$MAKE -s clean && $MAKE -s && $MAKE -s clean") and die;
+}
diff --git a/lib/tools/creator/GNUmakefile.tpl b/lib/tools/creator/GNUmakefile.tpl
new file mode 100644
index 00000000..f22e5464
--- /dev/null
+++ b/lib/tools/creator/GNUmakefile.tpl
@@ -0,0 +1,7 @@
+{if $makefileType == 'classes'}
+include ../../../lib/tools/bin/GNUmakefile.classes
+{else}
+include ../../../../lib/tools/bin/GNUmakefile.GalleryStorage
+{/if}
+
+
diff --git a/lib/tools/creator/MyPage.inc.tpl b/lib/tools/creator/MyPage.inc.tpl
new file mode 100644
index 00000000..b8cc72ef
--- /dev/null
+++ b/lib/tools/creator/MyPage.inc.tpl
@@ -0,0 +1,106 @@
+ $itemId));
+ if ($ret) {ldelim}
+ return array($ret, null);
+ {rdelim}
+
+ $ret = GalleryCoreApi::addMapEntry(
+ '{$mapName}',
+ array('itemId' => $itemId, 'itemValue' => $form['value']));
+ if ($ret) {ldelim}
+ return array($ret, null);
+ {rdelim}
+
+ /* Send the user to a confirmation page, for now */
+ $redirect['view'] = '{$moduleId}.{$viewName}';
+ $redirect['itemId'] = (int)$itemId;
+ $status['added'] = 1;
+ {rdelim}
+
+ $results['status'] = $status;
+ $results['error'] = $error;
+ $results['redirect'] = $redirect;
+
+ return array(null, $results);
+ {rdelim}
+{rdelim}
+
+/**
+ * This is a sample page generated by the Gallery 2 module creator.
+ *
+ * @package {$ucModuleId}
+ * @subpackage UserInterface
+ *
+ */
+class {$viewName}View extends GalleryView {ldelim}
+
+ /**
+ * @see GalleryView::loadTemplate
+ */
+ function loadTemplate(&$template = null, &$form = null) {ldelim}
+ /* Load our item */
+ list ($ret, $item) = $this->getItem();
+ if ($ret) {ldelim}
+ return array($ret, null);
+ {rdelim}
+
+ ${$viewName} = array();
+ ${$viewName}['item'] = (array)$item;
+ GalleryCoreApi::requireOnce('modules/{$moduleId}/classes/{$viewName}Helper.class');
+ list ($ret, ${$viewName}['value']) = {$viewName}Helper::getItemValue($item->getId());
+ if ($ret) {ldelim}
+ return array($ret, null);
+ {rdelim}
+
+ $template->setVariable('{$viewName}', ${$viewName});
+
+ return array(null, array('body' => 'modules/{$moduleId}/templates/{$viewName}.tpl'));
+ {rdelim}
+{rdelim}
+?>
diff --git a/lib/tools/creator/MyPage.tpl.tpl b/lib/tools/creator/MyPage.tpl.tpl
new file mode 100644
index 00000000..f655122e
--- /dev/null
+++ b/lib/tools/creator/MyPage.tpl.tpl
@@ -0,0 +1,40 @@
+
+
+
{ldelim}g->text text="My First Page"{rdelim}
+
+
+
+ Hello, my name is {$authorFullName}. This is my first Gallery 2 page!
+
+
+
+ The item you chose for this action was: {ldelim}${$viewName}.item.title|default:${$viewName}.item.pathComponent{rdelim}
+
+
+ {ldelim}if empty(${$viewName}.value){rdelim}
+ There is no value yet for this item.
+ {ldelim}else{rdelim}
+ The value in the database for this item is: {ldelim}${$viewName}.value{rdelim}
+ {ldelim}/if{rdelim}
+
+
+
+
+
diff --git a/lib/tools/creator/MyPageHelper.class.tpl b/lib/tools/creator/MyPageHelper.class.tpl
new file mode 100644
index 00000000..7e72ff73
--- /dev/null
+++ b/lib/tools/creator/MyPageHelper.class.tpl
@@ -0,0 +1,71 @@
+search($query, array($itemId));
+ if ($ret) {ldelim}
+ return array($ret, null);
+ {rdelim}
+
+ if ($searchResults->resultCount() != 0) {ldelim}
+ $result = $searchResults->nextResult();
+ $data = $result[0];
+ {rdelim} else {ldelim}
+ $data = '';
+ {rdelim}
+
+ return array(null, $data);
+ {rdelim}
+{rdelim}
+?>
diff --git a/lib/tools/creator/create-module.php b/lib/tools/creator/create-module.php
new file mode 100755
index 00000000..d57de0e9
--- /dev/null
+++ b/lib/tools/creator/create-module.php
@@ -0,0 +1,225 @@
+compile_dir = $tmpdir;
+$smarty->error_reporting = error_reporting();
+$smarty->debugging = true;
+$smarty->use_sub_dirs = false;
+$smarty->template_dir = __DIR__;
+
+// Gather any info we need from the user
+if (!empty($author)) {
+ $defaultModuleName = 'Hello ' . ucfirst($author);
+} else {
+ $defaultModuleName = 'Hello World';
+}
+
+while (empty($moduleName)) {
+ $moduleName = ask('What is the name of your module?', $defaultModuleName);
+}
+
+while (empty($moduleId)) {
+ $moduleId = ask(
+ 'What is the id of your module?',
+ strtolower(preg_replace('/ /', '', $moduleName))
+ );
+}
+$moduleId = preg_replace('/\W/', '', $moduleId);
+$ucModuleId = ucfirst($moduleId);
+
+$smarty->assign('moduleId', $moduleId);
+$smarty->assign('ucModuleId', $ucModuleId);
+$smarty->assign('moduleName', $moduleName);
+$smarty->assign('author', $author);
+$smarty->assign('authorFullName', $authorFullName);
+$smarty->assign('viewName', $ucModuleId);
+$smarty->assign('mapName', $ucModuleId . 'Map');
+
+// Start building things!
+
+// Make the module directory
+$modulePath = 'modules/' . $moduleId;
+
+if (file_exists($modulePath)) {
+ error("$modulePath already exists!");
+} else {
+ mkdir($modulePath) || error("Can't mkdir($modulePath)");
+}
+
+// Create module.inc
+$fd = safe_fopen("$modulePath/module.inc");
+fwrite($fd, $smarty->fetch(__DIR__ . '/module.inc.tpl'));
+fclose($fd);
+
+// Create our sample view and template
+$fd = safe_fopen("$modulePath/$ucModuleId.inc");
+fwrite($fd, $smarty->fetch(__DIR__ . '/MyPage.inc.tpl'));
+fclose($fd);
+
+mkdir("$modulePath/templates");
+$fd = safe_fopen("$modulePath/templates/$ucModuleId.tpl");
+fwrite($fd, $smarty->fetch(__DIR__ . '/MyPage.tpl.tpl'));
+fclose($fd);
+
+// Create our map
+mkdir($modulePath . '/classes');
+mkdir($modulePath . '/classes/GalleryStorage');
+
+$smarty->assign('makefileType', 'classes');
+$fd = safe_fopen("$modulePath/classes/GNUmakefile");
+fwrite($fd, $smarty->fetch(__DIR__ . '/GNUmakefile.tpl'));
+fclose($fd);
+
+$smarty->assign('makefileType', 'GalleryStorage');
+$fd = safe_fopen("$modulePath/classes/GalleryStorage/GNUmakefile");
+fwrite($fd, $smarty->fetch(__DIR__ . '/GNUmakefile.tpl'));
+fclose($fd);
+
+$fd = safe_fopen("$modulePath/classes/Maps.xml");
+fwrite($fd, $smarty->fetch(__DIR__ . '/map.tpl'));
+fclose($fd);
+
+$fd = safe_fopen($modulePath . '/classes/' . $ucModuleId . 'Helper.class');
+fwrite($fd, $smarty->fetch(__DIR__ . '/MyPageHelper.class.tpl'));
+fclose($fd);
+
+echo "* * * * * * * * * * * * * * * * * * * * * * * * * *\n";
+echo "Your module is ready! You must build it by doing: \n";
+echo "\n";
+echo " cd modules/$moduleId/classes \n";
+echo " make && make clean\n";
+echo "\n";
+echo "Then you can go to the Site Admin -> Modules \n";
+echo "page and install and activate your module!\n";
+echo "* * * * * * * * * * * * * * * * * * * * * * * * * *\n";
+
+function ask($prompt, $default = '') {
+ echo $prompt;
+
+ if (!empty($default)) {
+ echo " [$default]";
+ }
+ echo ' ';
+ $line = trim(fgets(stdin()));
+
+ if (empty($line)) {
+ return $default;
+ }
+
+ return $line;
+}
+
+function error($message) {
+ fwrite(stderr(), "$message\n");
+ fwrite(stderr(), "*** Exiting!\n");
+ cleanup();
+
+ exit(1);
+}
+
+function cleanup() {
+ global $tmpdir;
+
+ if (file_exists($tmpdir)) {
+ system("rm -rf $tmpdir");
+ }
+}
+
+function safe_fopen($path) {
+ ($fd = fopen($path, 'wb')) || error("Can't write to $path");
+
+ return $fd;
+}
+
+function stdin() {
+ static $stdin;
+
+ if (!defined('STDERR')) {
+ // Already defined for CLI but not for CGI
+ $stdin = fopen('php://stdin', 'w');
+ define('STDERR', $stdin);
+ }
+
+ return STDERR;
+}
+
+function stderr() {
+ static $stderr;
+
+ if (!defined('STDERR')) {
+ // Already defined for CLI but not for CGI
+ $stderr = fopen('php://stderr', 'w');
+ define('STDERR', $stderr);
+ }
+
+ return STDERR;
+}
diff --git a/lib/tools/creator/map.tpl b/lib/tools/creator/map.tpl
new file mode 100644
index 00000000..3071eeee
--- /dev/null
+++ b/lib/tools/creator/map.tpl
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/lib/tools/creator/module.inc.tpl b/lib/tools/creator/module.inc.tpl
new file mode 100644
index 00000000..84dbf265
--- /dev/null
+++ b/lib/tools/creator/module.inc.tpl
@@ -0,0 +1,62 @@
+setId('{$moduleId}');
+ $this->setName($gallery->i18n('{$moduleName}'));
+ $this->setDescription($gallery->i18n('My {$moduleName} module'));
+ $this->setVersion('1.0.0');
+ $this->_templateVersion = 1;
+ $this->setCallbacks('getItemLinks');
+ $this->setGroup('other', $gallery->i18n('Other'));
+ $this->setRequiredCoreApi(array(7, 20));
+ $this->setRequiredModuleApi(array(3, 6));
+ {rdelim}
+
+ /**
+ * @see GalleryModule::getItemLinks()
+ */
+ function getItemLinks($items, $wantsDetailedLinks, $permissions) {ldelim}
+ $links = array();
+ foreach ($items as $item) {ldelim}
+ $params['view'] = '{$moduleId}.{$viewName}';
+ $params['itemId'] = $item->getId();
+ $links[$item->getId()][] = array('text' => $this->translate('{$moduleName}'), 'params' => $params);
+ {rdelim}
+
+ return array(null, $links);
+ {rdelim}
+{rdelim}
+?>
diff --git a/lib/tools/dtd/DatabaseChangeDefinition2.0.dtd b/lib/tools/dtd/DatabaseChangeDefinition2.0.dtd
new file mode 100644
index 00000000..2a1a602e
--- /dev/null
+++ b/lib/tools/dtd/DatabaseChangeDefinition2.0.dtd
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/tools/dtd/DatabaseRemoveDefinition2.0.dtd b/lib/tools/dtd/DatabaseRemoveDefinition2.0.dtd
new file mode 100644
index 00000000..a9658ef7
--- /dev/null
+++ b/lib/tools/dtd/DatabaseRemoveDefinition2.0.dtd
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/lib/tools/dtd/DatabaseTableDefinition2.0.dtd b/lib/tools/dtd/DatabaseTableDefinition2.0.dtd
new file mode 100644
index 00000000..abe51bbf
--- /dev/null
+++ b/lib/tools/dtd/DatabaseTableDefinition2.0.dtd
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/tools/dtd/GalleryClass2.1.dtd b/lib/tools/dtd/GalleryClass2.1.dtd
new file mode 100644
index 00000000..6a80dfe6
--- /dev/null
+++ b/lib/tools/dtd/GalleryClass2.1.dtd
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/tools/dtd/MapsDefinition2.0.dtd b/lib/tools/dtd/MapsDefinition2.0.dtd
new file mode 100644
index 00000000..dd02da27
--- /dev/null
+++ b/lib/tools/dtd/MapsDefinition2.0.dtd
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/tools/phpunit/php.ini b/lib/tools/phpunit/php.ini
new file mode 100644
index 00000000..aadb6594
--- /dev/null
+++ b/lib/tools/phpunit/php.ini
@@ -0,0 +1,3 @@
+display_errors=on
+allow_url_fopen=Off
+include_path=/bogus
diff --git a/lib/tools/phpunit/phpinfo.php b/lib/tools/phpunit/phpinfo.php
new file mode 100644
index 00000000..fd741b93
--- /dev/null
+++ b/lib/tools/phpunit/phpinfo.php
@@ -0,0 +1,80 @@
+(.*)